Merge "Handle config drives being stored on rbd"

This commit is contained in:
Jenkins 2015-08-10 10:12:58 +00:00 committed by Gerrit Code Review
commit 422809c289
6 changed files with 176 additions and 15 deletions

View File

@ -325,3 +325,18 @@ class RbdTestCase(test.NoDBTestCase):
def test_cleanup_volumes_fail_other(self):
self.assertRaises(test.TestingException,
self._test_cleanup_exception, 'DoesNotExist')
@mock.patch.object(rbd_utils, 'rbd')
@mock.patch.object(rbd_utils, 'rados')
@mock.patch.object(rbd_utils, 'RADOSClient')
def test_remove_image(self, mock_client, mock_rados, mock_rbd):
name = '12345_disk.config.rescue'
rbd = mock_rbd.RBD.return_value
client = mock_client.return_value
self.driver.remove_image(name)
rbd.remove.assert_called_once_with(client.ioctx, name)
# Make sure that we entered and exited the RADOSClient
client.__enter__.assert_called_once_with()
client.__exit__.assert_called_once_with(None, None, None)

View File

@ -7511,8 +7511,9 @@ class LibvirtConnTestCase(test.NoDBTestCase):
filename=ephemeral_file_name,
mkfs=True)
def test_create_image_with_swap(self):
def _create_image_helper(self, callback, suffix=''):
gotFiles = []
imported_files = []
def fake_image(self, instance, name, image_type=''):
class FakeImage(imagebackend.Image):
@ -7529,6 +7530,10 @@ class LibvirtConnTestCase(test.NoDBTestCase):
gotFiles.append({'filename': filename,
'size': size})
def import_file(self, instance, local_filename,
remote_filename):
imported_files.append((local_filename, remote_filename))
def snapshot(self, name):
pass
@ -7546,6 +7551,9 @@ class LibvirtConnTestCase(test.NoDBTestCase):
instance_ref = self.test_instance
instance_ref['image_ref'] = 1
# NOTE(mikal): use this callback to tweak the instance to match
# what you're trying to test
callback(instance_ref)
instance = objects.Instance(**instance_ref)
# Turn on some swap to exercise that codepath in _create_image
instance.flavor.swap = 500
@ -7554,15 +7562,27 @@ class LibvirtConnTestCase(test.NoDBTestCase):
self.stubs.Set(drvr, '_get_guest_xml', fake_none)
self.stubs.Set(drvr, '_create_domain_and_network', fake_none)
self.stubs.Set(drvr, 'get_info', fake_get_info)
self.stubs.Set(instance_metadata, 'InstanceMetadata', fake_none)
self.stubs.Set(nova.virt.configdrive.ConfigDriveBuilder,
'make_drive', fake_none)
image_meta = {'id': instance['image_ref']}
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
instance,
image_meta)
drvr._create_image(context, instance, disk_info['mapping'])
drvr._create_image(context, instance, disk_info['mapping'],
suffix=suffix)
drvr._get_guest_xml(self.context, instance, None,
disk_info, image_meta)
return gotFiles, imported_files
def test_create_image_with_swap(self):
def enable_swap(instance_ref):
# Turn on some swap to exercise that codepath in _create_image
instance_ref['system_metadata']['instance_type_swap'] = 500
gotFiles, _ = self._create_image_helper(enable_swap)
wantFiles = [
{'filename': '356a192b7913b04c54574d18c28d46e6395428ab',
'size': 10 * units.Gi},
@ -7573,6 +7593,27 @@ class LibvirtConnTestCase(test.NoDBTestCase):
]
self.assertEqual(gotFiles, wantFiles)
def test_create_image_with_configdrive(self):
def enable_configdrive(instance_ref):
instance_ref['config_drive'] = 'true'
# Ensure that we create a config drive and then import it into the
# image backend store
_, imported_files = self._create_image_helper(enable_configdrive)
self.assertTrue(imported_files[0][0].endswith('/disk.config'))
self.assertEqual('disk.config', imported_files[0][1])
def test_create_image_with_configdrive_rescue(self):
def enable_configdrive(instance_ref):
instance_ref['config_drive'] = 'true'
# Ensure that we create a config drive and then import it into the
# image backend store
_, imported_files = self._create_image_helper(enable_configdrive,
suffix='.rescue')
self.assertTrue(imported_files[0][0].endswith('/disk.config.rescue'))
self.assertEqual('disk.config.rescue', imported_files[0][1])
@mock.patch.object(nova.virt.libvirt.imagebackend.Image, 'cache',
side_effect=exception.ImageNotFound(image_id='fake-id'))
def test_create_image_not_exist_no_fallback(self, mock_cache):
@ -12742,6 +12783,8 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
).AndReturn(fake_imagebackend.Raw())
imagebackend.Backend.image(instance, 'disk.rescue', 'default'
).AndReturn(fake_imagebackend.Raw())
imagebackend.Backend.image(instance, 'disk.config.rescue', 'raw'
).AndReturn(fake_imagebackend.Raw())
imagebackend.Image.cache(context=mox.IgnoreArg(),
fetch_func=mox.IgnoreArg(),

View File

@ -1362,6 +1362,40 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
["server1:1899", "server2:1920"]),
model)
def test_import_file(self):
image = self.image_class(self.INSTANCE, self.NAME)
@mock.patch.object(image, 'check_image_exists')
@mock.patch.object(image.driver, 'remove_image')
@mock.patch.object(image.driver, 'import_image')
def _test(mock_import, mock_remove, mock_exists):
mock_exists.return_value = True
image.import_file(self.INSTANCE, mock.sentinel.file,
mock.sentinel.remote_name)
name = '%s_%s' % (self.INSTANCE.uuid,
mock.sentinel.remote_name)
mock_exists.assert_called_once_with()
mock_remove.assert_called_once_with(name)
mock_import.assert_called_once_with(mock.sentinel.file, name)
_test()
def test_import_file_not_found(self):
image = self.image_class(self.INSTANCE, self.NAME)
@mock.patch.object(image, 'check_image_exists')
@mock.patch.object(image.driver, 'remove_image')
@mock.patch.object(image.driver, 'import_image')
def _test(mock_import, mock_remove, mock_exists):
mock_exists.return_value = False
image.import_file(self.INSTANCE, mock.sentinel.file,
mock.sentinel.remote_name)
name = '%s_%s' % (self.INSTANCE.uuid,
mock.sentinel.remote_name)
mock_exists.assert_called_once_with()
self.assertFalse(mock_remove.called)
mock_import.assert_called_once_with(mock.sentinel.file, name)
_test()
class PloopTestCase(_ImageTestCase, test.NoDBTestCase):
SIZE = 1024

View File

@ -2646,6 +2646,13 @@ class LibvirtDriver(driver.ComputeDriver):
return os.path.join(libvirt_utils.get_instance_path(instance),
'disk.config' + suffix)
@staticmethod
def _get_disk_config_image_type():
# TODO(mikal): there is a bug here if images_type has
# changed since creation of the instance, but I am pretty
# sure that this bug already exists.
return 'rbd' if CONF.libvirt.images_type == 'rbd' else 'raw'
def _chown_console_log_for_instance(self, instance):
console_log = self._get_console_log_path(instance)
if os.path.exists(console_log):
@ -2743,6 +2750,9 @@ class LibvirtDriver(driver.ComputeDriver):
{'img_id': img_id, 'e': e},
instance=instance)
# NOTE(sileht): many callers of this method assume that this
# method doesn't fail if an image already exists but instead
# think that it will be reused (ie: (live)-migration/resize)
def _create_image(self, context, instance,
disk_mapping, suffix='',
disk_images=None, network_info=None,
@ -2909,6 +2919,20 @@ class LibvirtDriver(driver.ComputeDriver):
'with error: %s'),
e, instance=instance)
try:
# Tell the storage backend about the config drive
config_drive_image = self.image_backend.image(
instance, 'disk.config' + suffix,
self._get_disk_config_image_type())
config_drive_image.import_file(
instance, configdrive_path, 'disk.config' + suffix)
finally:
# NOTE(mikal): if the config drive was imported into RBD, then
# we no longer need the local copy
if CONF.libvirt.images_type == 'rbd':
os.unlink(configdrive_path)
# File injection only if needed
elif inject_files and CONF.libvirt.inject_partition != -2:
if booted_from_volume:
@ -3265,11 +3289,9 @@ class LibvirtDriver(driver.ComputeDriver):
block_device.prepend_dev(diskswap.target_dev))
if 'disk.config' in disk_mapping:
diskconfig = self._get_guest_disk_config(instance,
'disk.config',
disk_mapping,
inst_type,
'raw')
diskconfig = self._get_guest_disk_config(
instance, 'disk.config', disk_mapping, inst_type,
self._get_disk_config_image_type())
devices.append(diskconfig)
for vol in block_device.get_bdms_to_connect(block_device_mapping,
@ -5859,14 +5881,21 @@ class LibvirtDriver(driver.ComputeDriver):
image_meta = utils.get_image_from_system_metadata(
instance.system_metadata)
if not (is_shared_instance_path and is_shared_block_storage):
# NOTE(dims): Using config drive with iso format does not work
# because of a bug in libvirt with read only devices. However
# one can use vfat as config_drive_format which works fine.
# Please see bug/1246201 for details on the libvirt bug.
if CONF.config_drive_format != 'vfat':
if configdrive.required_by(instance):
raise exception.NoLiveMigrationForConfigDriveInLibVirt()
if configdrive.required_by(instance):
# NOTE(sileht): configdrive is stored into the block storage
# kvm is a block device, live migration will work
# NOTE(sileht): the configdrive is stored into a shared path
# kvm don't need to migrate it, live migration will work
# NOTE(dims): Using config drive with iso format does not work
# because of a bug in libvirt with read only devices. However
# one can use vfat as config_drive_format which works fine.
# Please see bug/1246201 for details on the libvirt bug.
if (is_shared_block_storage or
is_shared_instance_path or
CONF.config_drive_format == 'vfat'):
pass
else:
raise exception.NoLiveMigrationForConfigDriveInLibVirt()
if not is_shared_instance_path:
instance_dir = libvirt_utils.get_instance_path_at_destination(

View File

@ -386,6 +386,23 @@ class Image(object):
"""
raise NotImplementedError()
def import_file(self, instance, local_file, remote_name):
"""Import an image from local storage into this backend.
Import a local file into the store used by this image type. Note that
this is a noop for stores using local disk (the local file is
considered "in the store").
If the image already exists it will be overridden by the new file
:param local_file: path to the file to import
:param remote_name: the name for the file in the store
"""
# NOTE(mikal): this is a noop for now for all stores except RBD, but
# we should talk about if we want this functionality for everything.
pass
class Raw(Image):
def __init__(self, instance=None, disk_name=None, path=None):
@ -803,6 +820,12 @@ class Rbd(Image):
secret,
servers)
def import_file(self, instance, local_file, remote_name):
name = '%s_%s' % (instance.uuid, remote_name)
if self.check_image_exists():
self.driver.remove_image(name)
self.driver.import_image(local_file, name)
class Ploop(Image):
def __init__(self, instance=None, disk_name=None, path=None):

View File

@ -243,6 +243,23 @@ class RBDDriver(object):
except rbd.ImageNotFound:
return False
def remove_image(self, name):
"""Remove RBD volume
:name: Name of RBD volume
"""
with RADOSClient(self, self.pool) as client:
try:
rbd.RBD().remove(client.ioctx, name)
except rbd.ImageNotFound:
LOG.warn(_LW('image %(volume)s in pool %(pool)s can not be '
'found, failed to remove'),
{'volume': name, 'pool': self.pool})
except rbd.ImageHasSnapshots:
LOG.error(_LE('image %(volume)s in pool %(pool)s has '
'snapshots, failed to remove'),
{'volume': name, 'pool': self.pool})
def import_image(self, base, name):
"""Import RBD volume from image file.