Rather than mounting a guest disk into the compute node, create the config drive locally and stream it to the XenServer hypervisor. Change-Id: I324538decbadf63e78ba86e20e3d41687b0a195a Implements-Blueprint: xenapi-independent-nova
109 lines
3.2 KiB
Python
109 lines
3.2 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 nova import image
|
|
|
|
_VDI_FORMAT_RAW = 1
|
|
|
|
IMAGE_API = image.API()
|
|
|
|
|
|
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()
|