# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import contextlib
import eventlet
from eventlet import greenio
import os
import tarfile

from nova.image import glance
from nova import utils
from nova.virt.xenapi import vm_utils


class VdiThroughDevStore(object):
    """Deal with virtual disks by attaching them to the OS domU.

    At the moment it supports upload to Glance, and the upload format is a raw
    disk inside a tgz.
    """

    def upload_image(self, context, session, instance, vdi_uuids, image_id):
        command = UploadToGlanceAsRawTgz(
            context, session, instance, vdi_uuids, image_id)
        return command.upload_image()

    def download_image(self, context, session, instance, image_id):
        # TODO(matelakat) Move through-dev image download functionality to this
        # method.
        raise NotImplementedError()


class UploadToGlanceAsRawTgz(object):
    def __init__(self, context, session, instance, vdi_uuids, image_id):
        self.context = context
        self.image_id = image_id
        self.session = session
        self.vdi_uuids = vdi_uuids

    def _get_virtual_size(self):
        return self.session.call_xenapi(
            'VDI.get_virtual_size', self._get_vdi_ref())

    def _get_vdi_ref(self):
        return self.session.call_xenapi('VDI.get_by_uuid', self.vdi_uuids[0])

    def _perform_upload(self, devpath):
        readfile, writefile = self._create_pipe()
        size = self._get_virtual_size()
        producer = TarGzProducer(devpath, writefile, size, 'disk.raw')
        consumer = UpdateGlanceImage(
            self.context, self.image_id, producer.get_metadata(), readfile)
        pool = eventlet.GreenPool()
        pool.spawn(producer.start)
        pool.spawn(consumer.start)
        pool.waitall()

    def _create_pipe(self):
        rpipe, wpipe = os.pipe()
        rfile = greenio.GreenPipe(rpipe, 'rb', 0)
        wfile = greenio.GreenPipe(wpipe, 'wb', 0)
        return rfile, wfile

    def upload_image(self):
        vdi_ref = self._get_vdi_ref()
        with vm_utils.vdi_attached_here(self.session, vdi_ref,
                                        read_only=True) as dev:
            devpath = utils.make_dev_path(dev)
            with utils.temporary_chown(devpath):
                self._perform_upload(devpath)


class TarGzProducer(object):
    def __init__(self, devpath, writefile, size, fname):
        self.fpath = devpath
        self.output = writefile
        self.size = size
        self.fname = fname

    def get_metadata(self):
        return {
            'disk_format': 'raw',
            'container_format': 'tgz'
        }

    def start(self):
        with contextlib.closing(self.output):
            tinfo = tarfile.TarInfo(name=self.fname)
            tinfo.size = int(self.size)
            with tarfile.open(fileobj=self.output, mode='w|gz') as tfile:
                with self._open_file(self.fpath, 'rb') as input_file:
                    tfile.addfile(tinfo, fileobj=input_file)

    def _open_file(self, *args):
        return open(*args)


class UpdateGlanceImage(object):
    def __init__(self, context, image_id, metadata, stream):
        self.context = context
        self.image_id = image_id
        self.metadata = metadata
        self.image_stream = stream

    def start(self):
        image_service, image_id = (
            glance.get_remote_image_service(self.context, self.image_id))
        image_service.update(self.context, image_id, self.metadata,
                             self.image_stream, purge_props=False)
