XenAPI: deprecate the config for image handler class path

This commit is to deprecate the config of image_upload_handler
which need to be configured with a class path string. And we
should make download handler be configurable to be consistent
with the upload handler.

So it contains the following changes:
1. deprecate the option of "image_upload_handler".
2. added a new option of "image_handler" which will be used to
choose a short name from the supported image handler plugins.
The proper handler will be loaded at runtime basing on the
option setting.

TODO(jianghuaw): For the legacy handler of VdiThroughDevStore
which has no download function implemented. For downloading, it
is handled in a different way (call xenapi.vm_utils._stream_disk()).
We keep it as it is now. In the future, we should consider to
move _stream_disk to VdiThroughDevStore's download().

Change-Id: I37860e63de32d988525cdc32440fe711777263e6
Blueprint: xenapi-image-handler-option-improvement
This commit is contained in:
jianghua wang 2017-08-24 10:19:49 +00:00 committed by naichuans
parent dd2232c446
commit 238b89b36e
9 changed files with 229 additions and 44 deletions

View File

@ -449,7 +449,16 @@ Related options:
"""),
# TODO(dharinic): Make this, a stevedore plugin
cfg.StrOpt('image_upload_handler',
default='nova.virt.xenapi.image.glance.GlanceStore',
default='',
deprecated_for_removal=True,
deprecated_since='18.0.0',
deprecated_reason="""
Instead of setting the class path here, we will use short names
to represent image handlers. The download and upload handlers
must also be matching. So another new option "image_handler"
will be used to set the short name for a specific image handler
for both image download and upload.
""",
help="""
Dom0 plugin driver used to handle image uploads.
@ -460,6 +469,25 @@ Images, and snapshots from XenServer need to be uploaded to the data
store for use. image_upload_handler takes in a value for the Dom0
plugin driver. This driver is then called to uplaod images to the
GlanceStore.
"""),
cfg.StrOpt('image_handler',
default='direct_vhd',
choices=('direct_vhd', 'vdi_local_dev'),
help="""
The plugin used to handle image uploads and downloads.
Provide a short name representing an image driver required to
handle the image between compute host and glance.
Description for the allowed values:
* ``direct_vhd``: This plugin directly processes the VHD files in XenServer
SR(Storage Repository). So this plugin only works when the host's SR
type is file system based e.g. ext, nfs.
* ``vdi_local_dev``: This plugin implements an image handler which attaches
the instance's VDI as a local disk to the VM where the OpenStack Compute
service runs in. It uploads the raw disk to glance when creating image;
When booting an instance from a glance image, it downloads the image and
streams it into the disk which is attached to the compute VM.
"""),
]

View File

@ -689,6 +689,10 @@ class ImageDeleteConflict(NovaException):
msg_fmt = _("Conflict deleting image. Reason: %(reason)s.")
class ImageHandlerUnsupported(NovaException):
msg_fmt = _("Error: unsupported image handler %(image_handler)s.")
class PreserveEphemeralNotSupported(Invalid):
msg_fmt = _("The current driver does not support "
"preserving ephemeral partitions.")

View File

@ -41,7 +41,8 @@ def stubout_firewall_driver(stubs, conn):
def stubout_instance_snapshot(stubs):
def fake_fetch_image(context, session, instance, name_label, image, type):
def fake_fetch_image(context, session, instance, name_label, image, type,
image_handler):
return {'root': dict(uuid=_make_fake_vdi(), file=None),
'kernel': dict(uuid=_make_fake_vdi(), file=None),
'ramdisk': dict(uuid=_make_fake_vdi(), file=None)}

View File

@ -40,6 +40,7 @@ from nova.tests import uuidsentinel as uuids
from nova.virt import hardware
from nova.virt.xenapi import driver as xenapi_conn
from nova.virt.xenapi import fake
from nova.virt.xenapi.image import utils as image_utils
from nova.virt.xenapi import vm_utils
import time
@ -222,6 +223,8 @@ class FetchVhdImageTestCase(VMUtilsTestBase):
self.context.auth_token = 'auth_token'
self.session = FakeSession()
self.instance = {"uuid": "uuid"}
self.image_handler = image_utils.get_image_handler(
CONF.xenserver.image_handler)
self.flags(group='glance', api_servers=['http://localhost:9292'])
make_uuid_stack_patcher = mock.patch.object(
@ -274,7 +277,8 @@ class FetchVhdImageTestCase(VMUtilsTestBase):
self._stub_glance_download_vhd()
result = vm_utils._fetch_vhd_image(self.context, self.session,
self.instance, 'image_id')
self.instance, 'image_id',
self.image_handler)
self.assertEqual("vdi", result['root']['uuid'])
mock_safe_find_sr.assert_called_once_with(self.session)
@ -300,7 +304,7 @@ class FetchVhdImageTestCase(VMUtilsTestBase):
self.assertRaises(exception.FlavorDiskSmallerThanImage,
vm_utils._fetch_vhd_image, self.context, self.session,
self.instance, 'image_id')
self.instance, 'image_id', self.image_handler)
mock_safe_find_sr.assert_called_once_with(self.session)
mock_scan_sr.assert_called_once_with(self.session, "sr")
@ -315,7 +319,8 @@ class FetchVhdImageTestCase(VMUtilsTestBase):
self._stub_glance_download_vhd(raise_exc=RuntimeError)
self.assertRaises(RuntimeError, vm_utils._fetch_vhd_image,
self.context, self.session, self.instance, 'image_id')
self.context, self.session, self.instance, 'image_id',
self.image_handler)
self._assert_call_plugin_serialized_with_retry()
self._assert_make_uuid_stack_and_get_sr_path()
@ -549,7 +554,8 @@ class CreateCachedImageTestCase(VMUtilsTestBase):
self.assertEqual((False, {'root': {'uuid': 'vdi_uuid', 'file': None}}),
vm_utils._create_cached_image('context', self.session,
'instance', 'name', 'uuid',
vm_utils.ImageType.DISK_VHD))
vm_utils.ImageType.DISK_VHD,
'image_handler'))
@mock.patch.object(vm_utils, '_safe_copy_vdi', return_value='new_vdi_ref')
def test_no_cow(self, mock_safe_copy_vdi, mock_safe_find_sr):
@ -559,7 +565,8 @@ class CreateCachedImageTestCase(VMUtilsTestBase):
self.assertEqual((False, {'root': {'uuid': 'vdi_uuid', 'file': None}}),
vm_utils._create_cached_image('context', self.session,
'instance', 'name', 'uuid',
vm_utils.ImageType.DISK_VHD))
vm_utils.ImageType.DISK_VHD,
'image_handler'))
def test_no_cow_no_ext(self, mock_safe_find_sr):
self.flags(use_cow_images=False)
@ -569,7 +576,8 @@ class CreateCachedImageTestCase(VMUtilsTestBase):
self.assertEqual((False, {'root': {'uuid': 'vdi_uuid', 'file': None}}),
vm_utils._create_cached_image('context', self.session,
'instance', 'name', 'uuid',
vm_utils.ImageType.DISK_VHD))
vm_utils.ImageType.DISK_VHD,
'image_handler'))
@mock.patch.object(vm_utils, '_clone_vdi', return_value='new_vdi_ref')
@mock.patch.object(vm_utils, '_fetch_image',
@ -583,7 +591,8 @@ class CreateCachedImageTestCase(VMUtilsTestBase):
self.assertEqual((True, {'root': {'uuid': 'vdi_uuid', 'file': None}}),
vm_utils._create_cached_image('context', self.session,
'instance', 'name', 'uuid',
vm_utils.ImageType.DISK_VHD))
vm_utils.ImageType.DISK_VHD,
'image_handler'))
class DestroyCachedImageTestCase(VMUtilsTestBase):
@ -909,7 +918,8 @@ class VDIOtherConfigTestCase(VMUtilsTestBase):
self.session.VDI_get_other_config = lambda vdi: {}
vm_utils.create_image(self.context, self.session, self.fake_instance,
'myvdi', 'image1', vm_utils.ImageType.DISK_VHD)
'myvdi', 'image1', vm_utils.ImageType.DISK_VHD,
'image_handler')
expected = {'nova_disk_type': 'root',
'nova_instance_uuid': 'aaaa-bbbb-cccc-dddd'}

View File

@ -25,6 +25,8 @@ except ImportError:
from eventlet import greenthread
import mock
from os_xenapi.client import host_xenstore
from oslo_utils import importutils
from oslo_utils import timeutils
import six
from nova.compute import power_state
@ -43,6 +45,7 @@ from nova import utils
from nova.virt import fake
from nova.virt.xenapi import agent as xenapi_agent
from nova.virt.xenapi import fake as xenapi_fake
from nova.virt.xenapi.image import utils as image_utils
from nova.virt.xenapi import vm_utils
from nova.virt.xenapi import vmops
from nova.virt.xenapi import volume_utils
@ -218,6 +221,88 @@ class VMOpsTestCase(VMOpsTestBase):
try_auto_config.assert_called_once_with(self._vmops._session,
'fake-ref', instance.flavor.root_gb)
@mock.patch.object(vm_utils, 'snapshot_attached_here')
@mock.patch.object(timeutils, 'delta_seconds')
@mock.patch.object(timeutils, 'utcnow')
@mock.patch.object(image_utils, 'get_image_handler')
def test_snapshot_using_image_handler(self,
mock_get_image_handler,
mock_utcnow,
mock_delta_seconds,
mock_snapshot_attached_here):
mock_utcnow.side_effect = ['fake_start', 'fake_end']
self.flags(image_handler='direct_vhd', group='xenserver')
mock_get_image_handler.return_value = mock.Mock()
class FakeVdiUuid(object):
def __enter__(self):
pass
def __exit__(self, Type, value, traceback):
pass
fake_vdi_uuid = FakeVdiUuid()
mock_snapshot_attached_here.return_value = fake_vdi_uuid
self._setup_mock_vmops()
vmops = self._vmops
with mock.patch.object(vmops, '_get_vm_opaque_ref',
return_value='fake_ref') as mock_get_opa_ref:
fake_instance = {'name': 'fake_name'}
vmops.snapshot('fake_ctx', fake_instance, 'fake_image_id',
mock.Mock())
vmops.image_handler.upload_image.assert_called_once_with(
'fake_ctx', vmops._session, fake_instance,
'fake_image_id', None)
mock_get_opa_ref.assert_called_once_with(fake_instance)
mock_delta_seconds.assert_called_once_with('fake_start',
'fake_end')
self.assertEqual(mock_utcnow.call_count, 2)
@mock.patch.object(vm_utils, 'snapshot_attached_here')
@mock.patch.object(timeutils, 'delta_seconds')
@mock.patch.object(timeutils, 'utcnow')
@mock.patch.object(image_utils, 'get_image_handler')
@mock.patch.object(importutils, 'import_object')
def test_snapshot_using_upload_image_handler(self,
mock_import_object,
mock_get_image_handler,
mock_utcnow,
mock_delta_seconds,
mock_snapshot_attached_here):
mock_utcnow.side_effect = ['fake_start', 'fake_end']
self.flags(image_upload_handler='image_upload_handler',
group='xenserver')
mock_get_image_handler.return_value = mock.Mock()
class FakeVdiUuid(object):
def __enter__(self):
pass
def __exit__(self, Type, value, traceback):
pass
fake_vdi_uuid = FakeVdiUuid()
mock_snapshot_attached_here.return_value = fake_vdi_uuid
mock_import_object.return_value = mock.Mock()
self._setup_mock_vmops()
vmops = self._vmops
with mock.patch.object(vmops, '_get_vm_opaque_ref',
return_value='fake_ref') as mock_get_opa_ref:
fake_instance = {'name': 'fake_name'}
vmops.snapshot('fake_ctx', fake_instance, 'fake_image_id',
mock.Mock())
vmops.image_upload_handler.upload_image.assert_called_once_with(
'fake_ctx', vmops._session, fake_instance,
'fake_image_id', None)
mock_get_opa_ref.assert_called_once_with(fake_instance)
mock_delta_seconds.assert_called_once_with('fake_start',
'fake_end')
self.assertEqual(mock_utcnow.call_count, 2)
class InjectAutoDiskConfigTestCase(VMOpsTestBase):
def test_inject_auto_disk_config_when_present(self):

View File

@ -16,11 +16,23 @@
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'}
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):

View File

@ -34,7 +34,6 @@ from os_xenapi.client import vm_management
from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import importutils
from oslo_utils import strutils
from oslo_utils import timeutils
from oslo_utils import units
@ -1227,7 +1226,7 @@ def _get_image_vdi_label(image_id):
def _create_cached_image(context, session, instance, name_label,
image_id, image_type):
image_id, image_type, image_handler):
sr_ref = safe_find_sr(session)
sr_type = session.call_xenapi('SR.get_type', sr_ref)
@ -1244,7 +1243,7 @@ def _create_cached_image(context, session, instance, name_label,
if cache_vdi_ref is None:
downloaded = True
vdis = _fetch_image(context, session, instance, name_label,
image_id, image_type)
image_id, image_type, image_handler)
cache_vdi_ref = session.call_xenapi(
'VDI.get_by_uuid', vdis['root']['uuid'])
@ -1289,7 +1288,7 @@ def _create_cached_image(context, session, instance, name_label,
def create_image(context, session, instance, name_label, image_id,
image_type):
image_type, image_handler):
"""Creates VDI from the image stored in the local cache. If the image
is not present in the cache, it streams it from glance.
@ -1320,10 +1319,10 @@ def create_image(context, session, instance, name_label, image_id,
if cache:
downloaded, vdis = _create_cached_image(context, session, instance,
name_label, image_id,
image_type)
image_type, image_handler)
else:
vdis = _fetch_image(context, session, instance, name_label,
image_id, image_type)
image_id, image_type, image_handler)
downloaded = True
duration = timeutils.delta_seconds(start_time, timeutils.utcnow())
@ -1341,14 +1340,16 @@ def create_image(context, session, instance, name_label, image_id,
return vdis
def _fetch_image(context, session, instance, name_label, image_id, image_type):
def _fetch_image(context, session, instance, name_label, image_id, image_type,
image_handler):
"""Fetch image from glance based on image type.
Returns: A single filename if image_type is KERNEL or RAMDISK
A list of dictionaries that describe VDIs, otherwise
"""
if image_type == ImageType.DISK_VHD:
vdis = _fetch_vhd_image(context, session, instance, image_id)
vdis = _fetch_vhd_image(context, session, instance, image_id,
image_handler)
else:
if CONF.xenserver.independent_compute:
raise exception.NotSupportedWithOption(
@ -1375,12 +1376,6 @@ def _make_uuid_stack():
return [uuidutils.generate_uuid() for i in range(MAX_VDI_CHAIN_SIZE)]
def _default_download_handler():
# TODO(sirp): This should be configurable like upload_handler
return importutils.import_object(
'nova.virt.xenapi.image.glance.GlanceStore')
def get_compression_level():
level = CONF.xenserver.image_compression_level
if level is not None and (level < 1 or level > 9):
@ -1389,20 +1384,15 @@ def get_compression_level():
return level
def _fetch_vhd_image(context, session, instance, image_id):
def _fetch_vhd_image(context, session, instance, image_id, image_handler):
"""Tell glance to download an image and put the VHDs into the SR
Returns: A list of dictionaries that describe VDIs
"""
LOG.debug("Asking xapi to fetch vhd image %s", image_id,
instance=instance)
handler = _default_download_handler()
try:
vdis = handler.download_image(context, session, instance, image_id)
except Exception:
raise
vdis = image_handler.download_image(
context, session, instance, image_id)
# Ensure we can see the import VHDs as VDIs
scan_default_sr(session)

View File

@ -57,6 +57,7 @@ from nova.virt import configdrive
from nova.virt import driver as virt_driver
from nova.virt import firewall
from nova.virt.xenapi import agent as xapi_agent
from nova.virt.xenapi.image import utils as image_utils
from nova.virt.xenapi import vm_utils
from nova.virt.xenapi import volume_utils
from nova.virt.xenapi import volumeops
@ -150,10 +151,22 @@ class VMOps(object):
self.vif_driver = vif_impl(xenapi_session=self._session)
self.default_root_dev = '/dev/sda'
LOG.debug("Importing image upload handler: %s",
CONF.xenserver.image_upload_handler)
self.image_upload_handler = importutils.import_object(
CONF.xenserver.image_upload_handler)
image_handler_cfg = CONF.xenserver.image_handler
self.image_handler = image_utils.get_image_handler(image_handler_cfg)
# TODO(jianghuaw): Remove these lines relative to the deprecated
# option of "image_upload_handler" in the next release - Stein.
self.image_upload_handler = None
image_upload_handler_cfg = CONF.xenserver.image_upload_handler
if image_upload_handler_cfg:
# If *image_upload_handler* is explicitly configured, it
# means it indends to use non-default image upload handler.
# In order to avoid mis-using the default image_handler which
# may have different behavor than the explicitly configured
# handler, we keep using *image_upload_handler*.
LOG.warning("Deprecated: importing image upload handler: %s",
image_upload_handler_cfg)
self.image_upload_handler = importutils.import_object(
image_upload_handler_cfg)
def agent_enabled(self, instance):
if CONF.xenserver.disable_agent:
@ -356,8 +369,9 @@ class VMOps(object):
# If we didn't get a root VDI from volumes,
# then use the Glance image as the root device
if 'root' not in vdis:
create_image_vdis = vm_utils.create_image(context, self._session,
instance, name_label, image_meta.id, image_type)
create_image_vdis = vm_utils.create_image(
context, self._session, instance, name_label, image_meta.id,
image_type, self.image_handler)
vdis.update(create_image_vdis)
# Fetch VDI refs now so we don't have to fetch the ref multiple times
@ -1029,12 +1043,23 @@ class VMOps(object):
post_snapshot_callback=update_task_state) as vdi_uuids:
update_task_state(task_state=task_states.IMAGE_UPLOADING,
expected_state=task_states.IMAGE_PENDING_UPLOAD)
self.image_upload_handler.upload_image(context,
self._session,
instance,
image_id,
vdi_uuids,
)
if self.image_upload_handler:
# TODO(jianghuaw): remove this branch once the
# deprecated option of "image_upload_handler"
# gets removed in the next release - Stein.
self.image_upload_handler.upload_image(context,
self._session,
instance,
image_id,
vdi_uuids,
)
else:
self.image_handler.upload_image(context,
self._session,
instance,
image_id,
vdi_uuids,
)
duration = timeutils.delta_seconds(start_time, timeutils.utcnow())
LOG.debug("Finished snapshot and upload for VM, duration: "

View File

@ -0,0 +1,30 @@
---
features:
- |
Add a new option of ``image_handler`` in the ``xenapi`` section for
configuring the image handler plugin which will be used by XenServer
to download or upload images. The value for this option should be a
short name representing a supported handler.
The following are the short names and description of the plugins which
they represent:
* ``direct_vhd``
This plugin directly processes the VHD files in XenServer SR(Storage
Repository). So this plugin only works when the host's SR type is
file system based e.g. ext, nfs. This is the default plugin.
* ``vdi_local_dev``
This plugin implements an image upload method which attaches the VDI
as a local disk in the VM in which the OpenStack Compute service runs.
It uploads the raw disk to glance when creating an image; When booting
an instance from a glance image, it downloads the image and streams it
into the disk which is attached to the compute VM.
deprecations:
- |
The ``image_upload_handler`` option in the ``xenserver`` conf section
has been deprecated. Please use the new option of ``image_handler`` to
configure the image handler which is used to download or upload images.