With the new image handler, it creates an image proxy which will use the vdi streaming function from os-xenapi to remotely export VHD from XenServer(image upload) or import VHD to Xenerver(image download). The existing GlanceStore uses custom functionality to directly manipulate files on-disk, so it has the restriction that SR's type must be file system based: e.g. ext or nfs. The new image handler invokes APIs formally supported by XenServer to export/import VDI remotely, it can support other SR types also e.g. lvm, iscsi, etc. Note: vdi streaming would be supported by XenServer 6.5 or above. The function of image handler depends on os-xenapi 0.3.3 or above, so bump os-xenapi's version to 0.3.3 and also declare depends on the patch which bump version in openstack/requirements. Blueprint: xenapi-image-handler-option-improvement Change-Id: I0ad8e34808401ace9b85e1b937a542f4c4e61690 Depends-On: Ib8bc0f837c55839dc85df1d1f0c76b320b9d97b8
122 lines
3.8 KiB
Python
122 lines
3.8 KiB
Python
# 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 shutil
|
|
import tarfile
|
|
|
|
from oslo_utils import importutils
|
|
|
|
from nova import exception
|
|
from nova import image
|
|
|
|
_VDI_FORMAT_RAW = 1
|
|
|
|
IMAGE_API = image.API()
|
|
IMAGE_HANDLERS = {'direct_vhd': 'glance.GlanceStore',
|
|
'vdi_local_dev': 'vdi_through_dev.VdiThroughDevStore',
|
|
'vdi_remote_stream': 'vdi_stream.VdiStreamStore'}
|
|
|
|
|
|
def get_image_handler(handler_name):
|
|
if handler_name not in IMAGE_HANDLERS:
|
|
raise exception.ImageHandlerUnsupported(image_handler=handler_name)
|
|
return importutils.import_object('nova.virt.xenapi.image.'
|
|
'%s' % IMAGE_HANDLERS[handler_name])
|
|
|
|
|
|
class GlanceImage(object):
|
|
def __init__(self, context, image_href_or_id):
|
|
self._context = context
|
|
self._image_id = image_href_or_id
|
|
self._cached_meta = None
|
|
|
|
@property
|
|
def meta(self):
|
|
if self._cached_meta is None:
|
|
self._cached_meta = IMAGE_API.get(self._context, self._image_id)
|
|
return self._cached_meta
|
|
|
|
def download_to(self, fileobj):
|
|
return IMAGE_API.download(self._context, self._image_id, fileobj)
|
|
|
|
def is_raw_tgz(self):
|
|
return ['raw', 'tgz'] == [
|
|
self.meta.get(key) for key in ('disk_format', 'container_format')]
|
|
|
|
def data(self):
|
|
return IMAGE_API.download(self._context, self._image_id)
|
|
|
|
|
|
class RawImage(object):
|
|
def __init__(self, glance_image):
|
|
self.glance_image = glance_image
|
|
|
|
def get_size(self):
|
|
return int(self.glance_image.meta['size'])
|
|
|
|
def stream_to(self, fileobj):
|
|
return self.glance_image.download_to(fileobj)
|
|
|
|
|
|
class IterableToFileAdapter(object):
|
|
"""A degenerate file-like so that an iterable could be read like a file.
|
|
|
|
As Glance client returns an iterable, but tarfile requires a file like,
|
|
this is the adapter between the two. This allows tarfile to access the
|
|
glance stream.
|
|
"""
|
|
|
|
def __init__(self, iterable):
|
|
self.iterator = iterable.__iter__()
|
|
self.remaining_data = ''
|
|
|
|
def read(self, size):
|
|
chunk = self.remaining_data
|
|
try:
|
|
while not chunk:
|
|
chunk = next(self.iterator)
|
|
except StopIteration:
|
|
return ''
|
|
return_value = chunk[0:size]
|
|
self.remaining_data = chunk[size:]
|
|
return return_value
|
|
|
|
|
|
class RawTGZImage(object):
|
|
def __init__(self, glance_image):
|
|
self.glance_image = glance_image
|
|
self._tar_info = None
|
|
self._tar_file = None
|
|
|
|
def _as_file(self):
|
|
return IterableToFileAdapter(self.glance_image.data())
|
|
|
|
def _as_tarfile(self):
|
|
return tarfile.open(mode='r|gz', fileobj=self._as_file())
|
|
|
|
def get_size(self):
|
|
if self._tar_file is None:
|
|
self._tar_file = self._as_tarfile()
|
|
self._tar_info = self._tar_file.next()
|
|
return self._tar_info.size
|
|
|
|
def stream_to(self, target_file):
|
|
if self._tar_file is None:
|
|
self._tar_file = self._as_tarfile()
|
|
self._tar_info = self._tar_file.next()
|
|
source_file = self._tar_file.extractfile(self._tar_info)
|
|
shutil.copyfileobj(source_file, target_file)
|
|
self._tar_file.close()
|