From 3b1f7c55c10eec9d438a7d147aba1124b5ff9452 Mon Sep 17 00:00:00 2001 From: Nikola Dipanov Date: Fri, 9 Aug 2013 16:14:41 +0200 Subject: [PATCH] Allow block devices without device_name This patch removes the check in the API that prevented having block devices without 'device_name' and makes the compute manager capable of guessing those names. Before creating the instance, the manager will try to guess a valid name using the method 'default_device_names_for_instance', which should be implemented by the drivers, or defer to the generic function from the utils module 'default_device_names_for_instance' which uses the already existing 'get_device_name_for_instance' function. It also takes care of assigning the correct device name to the root device (boot_index=0) to make it agree with the field `root_device_name' from the instance. If none were supplied - it will try to guess it by calling driver 'default_root_device_name' or defer to the generic 'get_device_name_for_instance'. if driver does not provide it. What is worth noting also is that, should the drivers override the two methods mentioned above, the code should expect to be passed the standard block device mapping format, and not the one used in other driver methods. DocImpact Part of blueprint: improve-block-device-handling Co-authored-by: Xavier Queralt Change-Id: I84541f8ff6e1b5978734e5def69946d014c66fdf --- nova/compute/api.py | 5 +- nova/compute/manager.py | 91 ++++++++++++ nova/compute/utils.py | 94 ++++++++++-- nova/db/sqlalchemy/api.py | 45 +++--- nova/tests/compute/test_compute.py | 75 ++++++++++ nova/tests/compute/test_compute_utils.py | 175 +++++++++++++++++++++++ nova/tests/db/test_db_api.py | 18 +++ nova/tests/virt/test_block_device.py | 8 ++ nova/virt/block_device.py | 15 ++ nova/virt/driver.py | 9 ++ 10 files changed, 502 insertions(+), 33 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e2ad1fe923..383038bdc2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -587,9 +587,12 @@ class API(base.Base): def _check_and_transform_bdm(self, base_options, min_count, max_count, block_device_mapping, legacy_bdm): if legacy_bdm: + # NOTE (ndipanov): Assume root dev name is 'vda' if not supplied. + # It's needed for legacy conversion to work. + root_device_name = (base_options.get('root_device_name') or 'vda') block_device_mapping = block_device.from_legacy_mapping( block_device_mapping, base_options.get('image_ref', ''), - base_options.get('root_device_name')) + root_device_name) if min_count > 1 or max_count > 1: if any(map(lambda bdm: bdm['source_type'] == 'volume', diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 33544dc526..b16d4dd0e5 100755 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -40,6 +40,7 @@ import uuid from eventlet import greenthread from oslo.config import cfg +from nova import block_device from nova.cells import rpcapi as cells_rpcapi from nova.cloudpipe import pipelib from nova import compute @@ -1005,6 +1006,10 @@ class ComputeManager(manager.SchedulerDependentManager): bdms = self.conductor_api.block_device_mapping_get_all_by_instance( context, instance, legacy=False) + # Verify that all the BDMs have a device_name set and assign a default + # one to the ones missing it with the help of the driver. + self._default_block_device_names(context, instance, image_meta, bdms) + # b64 decode the files to inject: injected_files_orig = injected_files injected_files = self._decode_files(injected_files) @@ -1267,6 +1272,92 @@ class ComputeManager(manager.SchedulerDependentManager): requested_networks, macs, security_groups, is_vpn, dhcp_options) + def _default_root_device_name(self, instance, image_meta, root_bdm): + try: + return self.driver.default_root_device_name(instance, + image_meta, + root_bdm) + except NotImplementedError: + return compute_utils.get_next_device_name(instance, []) + + def _default_device_names_for_instance(self, instance, + root_device_name, + update_function, + *block_device_lists): + try: + self.driver.default_device_names_for_instance(instance, + root_device_name, + *block_device_lists) + except NotImplementedError: + compute_utils.default_device_names_for_instance( + instance, root_device_name, + update_function, *block_device_lists) + + def _default_block_device_names(self, context, instance, + image_meta, block_devices): + """Verify that all the devices have the device_name set. If not, + provide a default name. + + It also ensures that there is a root_device_name and is set to the + first block device in the boot sequence (boot_index=0). + """ + try: + root_bdm = (bdm for bdm in block_devices + if bdm['boot_index'] == 0).next() + except StopIteration: + return + + # Get the root_device_name from the root BDM or the instance + root_device_name = None + update_instance = False + update_root_bdm = False + + if root_bdm['device_name']: + root_device_name = root_bdm['device_name'] + instance['root_device_name'] = root_device_name + update_instance = True + elif instance['root_device_name']: + root_device_name = instance['root_device_name'] + root_bdm['device_name'] = root_device_name + update_root_bdm = True + else: + root_device_name = self._default_root_device_name(instance, + image_meta, + root_bdm) + + instance['root_device_name'] = root_device_name + root_bdm['device_name'] = root_device_name + update_instance = update_root_bdm = True + + if update_instance: + self._instance_update(context, instance['uuid'], + root_device_name=root_device_name) + if update_root_bdm: + self.conductor_api.block_device_mapping_update( + context, root_bdm['id'], {'device_name': root_device_name}) + + def _is_mapping(bdm): + return (bdm['source_type'] in ('image', 'volume', 'snapshot') and + driver_block_device.is_implemented(bdm)) + + ephemerals = filter(block_device.new_format_is_ephemeral, + block_devices) + swap = filter(block_device.new_format_is_swap, + block_devices) + block_device_mapping = filter(_is_mapping, block_devices) + + def _update_bdm(bdm_for_update): + self.conductor_api.block_device_mapping_update( + context, instance['uuid'], bdm_for_update['id'], + bdm_for_update['device_name']) + + self._default_device_names_for_instance(instance, + root_device_name, + _update_bdm, + ephemerals, + swap, + block_device_mapping) + def _prep_block_device(self, context, instance, bdms): """Set up the block device for an instance with error logging.""" try: diff --git a/nova/compute/utils.py b/nova/compute/utils.py index fb2ab6d200..5f54e1d537 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -16,6 +16,7 @@ """Compute-related Utilities and helpers.""" +import itertools import re import string import traceback @@ -122,12 +123,78 @@ def pack_action_event_finish(context, instance_uuid, event_name, exc_val=None, def get_device_name_for_instance(context, instance, bdms, device): """Validates (or generates) a device name for instance. + This method is a wrapper for get_next_device_name that gets the list + of used devices and the root device from a block device mapping. + """ + mappings = block_device.instance_block_mapping(instance, bdms) + return get_next_device_name(instance, mappings.values(), + mappings['root'], device) + + +def default_device_names_for_instance(instance, root_device_name, + update_function, *block_device_lists): + """Generate missing device names for an instance.""" + + def _device_names(iterables): + return [bdm['device_name'] + for bdm in itertools.chain(*iterables) if bdm['device_name']] + + dev_list = _device_names(block_device_lists) + if root_device_name not in dev_list: + dev_list.append(root_device_name) + inst_type = flavors.extract_flavor(instance) + + is_libvirt = driver.compute_driver_matches('libvirt.LibvirtDriver') + libvirt_default_ephemerals = [] + + bdm_named_lists = zip(('ephemerals', 'swap', 'block_device_mapping'), + block_device_lists) + + for name, bdm_list in bdm_named_lists: + # Libvirt will create a default ephemeral if instance allows + # and was not overridden. + if (is_libvirt and name == 'ephemerals' and not bdm_list and + instance['ephemeral_gb'] > 0): + default_eph = get_next_device_name(instance, + [root_device_name], + root_device_name) + if default_eph not in dev_list: + dev_list.append(default_eph) + libvirt_default_ephemerals.append(default_eph) + + # Libvirt will create a default swap if it's in instance type + # and it was not supplied or overridden + if (is_libvirt and name == 'swap' and not bdm_list and + inst_type['swap'] > 0): + + ephemerals = (_device_names(bdm_named_lists[0][1]) or + libvirt_default_ephemerals) + default_swap = get_next_device_name(instance, + [root_device_name] + ephemerals, root_device_name) + if default_swap not in dev_list: + dev_list.append(default_swap) + + for bdm in bdm_list: + dev = bdm.get('device_name') + if not dev: + dev = get_next_device_name(instance, dev_list, + root_device_name) + bdm['device_name'] = dev + if update_function: + update_function(bdm) + dev_list.append(dev) + + +def get_next_device_name(instance, device_name_list, + root_device_name=None, device=None): + """Validates (or generates) a device name for instance. + If device is not set, it will generate a unique device appropriate - for the instance. It uses the block device mapping table to find - valid device names. If the device name is valid but applicable to - a different backend (for example /dev/vdc is specified but the - backend uses /dev/xvdc), the device name will be converted to the - appropriate format. + for the instance. It uses the root_device_name (if provided) and + the list of used devices to find valid device names. If the device + name is valid but applicable to a different backend (for example + /dev/vdc is specified but the backend uses /dev/xvdc), the device + name will be converted to the appropriate format. """ req_prefix = None req_letter = None @@ -138,23 +205,29 @@ def get_device_name_for_instance(context, instance, bdms, device): except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=device) - mappings = block_device.instance_block_mapping(instance, bdms) + if not root_device_name: + root_device_name = block_device.DEFAULT_ROOT_DEV_NAME try: - prefix = block_device.match_device(mappings['root'])[0] + prefix = block_device.match_device(root_device_name)[0] except (TypeError, AttributeError, ValueError): - raise exception.InvalidDevicePath(path=mappings['root']) + raise exception.InvalidDevicePath(path=root_device_name) # NOTE(vish): remove this when xenapi is setting default_root_device if driver.compute_driver_matches('xenapi.XenAPIDriver'): prefix = '/dev/xvd' + # NOTE(xqueralt): This can be removed when we have libvirt + # defaulting it's own device names. + if driver.compute_driver_matches('libvirt.LibvirtDriver'): + prefix = '/dev/vd' + if req_prefix != prefix: LOG.debug(_("Using %(prefix)s instead of %(req_prefix)s"), {'prefix': prefix, 'req_prefix': req_prefix}) used_letters = set() - for device_path in mappings.itervalues(): + for device_path in device_name_list: letter = block_device.strip_prefix(device_path) # NOTE(vish): delete numbers in case we have something like # /dev/sda1 @@ -177,8 +250,7 @@ def get_device_name_for_instance(context, instance, bdms, device): if req_letter in used_letters: raise exception.DevicePathInUse(path=device) - device_name = prefix + req_letter - return device_name + return prefix + req_letter def _get_unused_letter(used_letters): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 449fbbe486..6a6d76e341 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3447,32 +3447,35 @@ def block_device_mapping_update(context, bdm_id, values, legacy=True): def block_device_mapping_update_or_create(context, values, legacy=True): _scrub_empty_str_values(values, ['volume_size']) + values = _from_legacy_values(values, legacy, allow_updates=True) + session = get_session() with session.begin(): - result = _block_device_mapping_get_query(context, session=session).\ - filter_by(instance_uuid=values['instance_uuid']).\ - filter_by(device_name=values['device_name']).\ - first() - if not result: - values = _from_legacy_values(values, legacy) - bdm_ref = models.BlockDeviceMapping() - bdm_ref.update(values) - bdm_ref.save(session=session) - result = bdm_ref - else: - values = _from_legacy_values(values, legacy, allow_updates=True) - result.update(values) + result = None + # NOTE(xqueralt): Only update a BDM when device_name was provided. We + # allow empty device names so they will be set later by the manager. + if values['device_name']: + query = _block_device_mapping_get_query(context, session=session) + result = query.filter_by(instance_uuid=values['instance_uuid'], + device_name=values['device_name']).first() - # NOTE(xqueralt): prevent from having multiple swap devices for the - # same instance. So delete the existing ones. + if result: + result.update(values) + else: + # Either the device_name doesn't exist in the database yet, or no + # device_name was provided. Both cases mean creating a new BDM. + result = models.BlockDeviceMapping(**values) + result.save(session=session) + + # NOTE(xqueralt): Prevent from having multiple swap devices for the + # same instance. This will delete all the existing ones. if block_device.new_format_is_swap(values): - query = (_block_device_mapping_get_query(context, session=session). - filter_by(instance_uuid=values['instance_uuid']). - filter_by(source_type='blank'). - filter(models.BlockDeviceMapping.device_name != - values['device_name']). - filter_by(guest_format='swap')) + query = _block_device_mapping_get_query(context, session=session) + query = query.filter_by(instance_uuid=values['instance_uuid'], + source_type='blank', guest_format='swap') + query = query.filter(models.BlockDeviceMapping.id != result.id) query.soft_delete() + return result diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index fc081fad95..0fdbc76619 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -5511,6 +5511,81 @@ class ComputeTestCase(BaseTestCase): instance.refresh() self.assertEqual(vm_states.ACTIVE, instance['vm_state']) + def _get_instance_and_bdm_for_dev_defaults_tests(self): + instance = self._create_fake_instance( + params={'root_device_name': '/dev/vda'}) + block_device_mapping = [ + {'id': 3, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/vda', + 'source_type': 'volume', + 'destination_type': 'volume', + 'image_id': 'fake-image-id-1', + 'boot_index': 0}] + + return instance, block_device_mapping + + def test_default_block_device_names_empty_instance_root_dev(self): + instance, bdms = self._get_instance_and_bdm_for_dev_defaults_tests() + instance['root_device_name'] = None + self.mox.StubOutWithMock(self.compute, '_instance_update') + self.mox.StubOutWithMock(self.compute, + '_default_device_names_for_instance') + self.compute._instance_update(self.context, instance['uuid'], + root_device_name='/dev/vda') + self.compute._default_device_names_for_instance(instance, + '/dev/vda', + mox.IgnoreArg(), + [], [], bdms) + self.mox.ReplayAll() + self.compute._default_block_device_names(self.context, + instance, + {}, bdms) + + def test_default_block_device_names_empty_root_device(self): + instance, bdms = self._get_instance_and_bdm_for_dev_defaults_tests() + bdms[0]['device_name'] = None + self.mox.StubOutWithMock(self.compute.conductor_api, + 'block_device_mapping_update') + self.mox.StubOutWithMock(self.compute, + '_default_device_names_for_instance') + self.compute.conductor_api.block_device_mapping_update( + self.context, bdms[0]['id'], {'device_name': '/dev/vda'}) + self.compute._default_device_names_for_instance(instance, + '/dev/vda', + mox.IgnoreArg(), + [], [], bdms) + self.mox.ReplayAll() + self.compute._default_block_device_names(self.context, + instance, + {}, bdms) + + def test_default_block_device_names_no_root_device(self): + instance, bdms = self._get_instance_and_bdm_for_dev_defaults_tests() + instance['root_device_name'] = None + bdms[0]['device_name'] = None + self.mox.StubOutWithMock(self.compute, '_instance_update') + self.mox.StubOutWithMock(self.compute.conductor_api, + 'block_device_mapping_update') + self.mox.StubOutWithMock(self.compute, + '_default_root_device_name') + self.mox.StubOutWithMock(self.compute, + '_default_device_names_for_instance') + + self.compute._default_root_device_name(instance, mox.IgnoreArg(), + bdms[0]).AndReturn('/dev/vda') + self.compute._instance_update(self.context, instance['uuid'], + root_device_name='/dev/vda') + self.compute.conductor_api.block_device_mapping_update( + self.context, bdms[0]['id'], {'device_name': '/dev/vda'}) + self.compute._default_device_names_for_instance(instance, + '/dev/vda', + mox.IgnoreArg(), + [], [], bdms) + self.mox.ReplayAll() + self.compute._default_block_device_names(self.context, + instance, + {}, bdms) + class ComputeAPITestCase(BaseTestCase): def setUp(self): diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py index e04726e918..d13ec64fe6 100644 --- a/nova/tests/compute/test_compute_utils.py +++ b/nova/tests/compute/test_compute_utils.py @@ -17,6 +17,7 @@ """Tests For miscellaneous util methods used with compute.""" +import copy import string from oslo.config import cfg @@ -36,6 +37,8 @@ from nova import test from nova.tests import fake_instance_actions from nova.tests import fake_network import nova.tests.image.fake +from nova.tests import matchers +from nova.virt import driver CONF = cfg.CONF CONF.import_opt('compute_manager', 'nova.service') @@ -225,6 +228,178 @@ class ComputeValidateDeviceTestCase(test.TestCase): self.assertEqual(device, '/dev/xvdd') +class DefaultDeviceNamesForInstanceTestCase(test.TestCase): + + def setUp(self): + super(DefaultDeviceNamesForInstanceTestCase, self).setUp() + self.ephemerals = [ + {'id': 1, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/vdb', + 'source_type': 'blank', + 'destination_type': 'local', + 'delete_on_termination': True, + 'guest_format': None, + 'boot_index': -1}] + + self.swap = [ + {'id': 2, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/vdc', + 'source_type': 'blank', + 'destination_type': 'local', + 'delete_on_termination': True, + 'guest_format': 'swap', + 'boot_index': -1}] + + self.block_device_mapping = [ + {'id': 3, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/vda', + 'source_type': 'volume', + 'destination_type': 'volume', + 'volume_id': 'fake-volume-id-1', + 'boot_index': 0}, + {'id': 4, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/vdd', + 'source_type': 'snapshot', + 'destination_type': 'volume', + 'snapshot_id': 'fake-snapshot-id-1', + 'boot_index': -1}] + self.instance_type = {'swap': 4} + self.instance = {'uuid': 'fake_instance', 'ephemeral_gb': 2} + self.is_libvirt = False + self.root_device_name = '/dev/vda' + self.update_called = False + + def fake_extract_flavor(instance): + return self.instance_type + + def fake_driver_matches(driver_string): + if driver_string == 'libvirt.LibvirtDriver': + return self.is_libvirt + return False + + self.stubs.Set(flavors, 'extract_flavor', fake_extract_flavor) + self.stubs.Set(driver, 'compute_driver_matches', fake_driver_matches) + + def _test_default_device_names(self, update_function, *block_device_lists): + compute_utils.default_device_names_for_instance(self.instance, + self.root_device_name, + update_function, + *block_device_lists) + + def test_only_block_device_mapping(self): + # Test no-op + original_bdm = copy.deepcopy(self.block_device_mapping) + self._test_default_device_names(None, [], [], + self.block_device_mapping) + self.assertThat(original_bdm, + matchers.DictListMatches(self.block_device_mapping)) + + # Asser it defaults the missing one as expected + self.block_device_mapping[1]['device_name'] = None + self._test_default_device_names(None, [], [], + self.block_device_mapping) + self.assertEquals(self.block_device_mapping[1]['device_name'], + '/dev/vdb') + + def test_with_ephemerals(self): + # Test ephemeral gets assigned + self.ephemerals[0]['device_name'] = None + self._test_default_device_names(None, self.ephemerals, [], + self.block_device_mapping) + self.assertEquals(self.ephemerals[0]['device_name'], '/dev/vdb') + + self.block_device_mapping[1]['device_name'] = None + self._test_default_device_names(None, self.ephemerals, [], + self.block_device_mapping) + self.assertEquals(self.block_device_mapping[1]['device_name'], + '/dev/vdc') + + def test_with_swap(self): + # Test swap only + self.swap[0]['device_name'] = None + self._test_default_device_names(None, [], self.swap, []) + self.assertEquals(self.swap[0]['device_name'], '/dev/vdb') + + # Test swap and block_device_mapping + self.swap[0]['device_name'] = None + self.block_device_mapping[1]['device_name'] = None + self._test_default_device_names(None, [], self.swap, + self.block_device_mapping) + self.assertEquals(self.swap[0]['device_name'], '/dev/vdb') + self.assertEquals(self.block_device_mapping[1]['device_name'], + '/dev/vdc') + + def test_all_together(self): + # Test swap missing + self.swap[0]['device_name'] = None + self._test_default_device_names(None, self.ephemerals, + self.swap, self.block_device_mapping) + self.assertEquals(self.swap[0]['device_name'], '/dev/vdc') + + # Test swap and eph missing + self.swap[0]['device_name'] = None + self.ephemerals[0]['device_name'] = None + self._test_default_device_names(None, self.ephemerals, + self.swap, self.block_device_mapping) + self.assertEquals(self.ephemerals[0]['device_name'], '/dev/vdb') + self.assertEquals(self.swap[0]['device_name'], '/dev/vdc') + + # Test all missing + self.swap[0]['device_name'] = None + self.ephemerals[0]['device_name'] = None + self.block_device_mapping[1]['device_name'] = None + self._test_default_device_names(None, self.ephemerals, + self.swap, self.block_device_mapping) + self.assertEquals(self.ephemerals[0]['device_name'], '/dev/vdb') + self.assertEquals(self.swap[0]['device_name'], '/dev/vdc') + self.assertEquals(self.block_device_mapping[1]['device_name'], + '/dev/vdd') + + def test_libvirt_default_eph(self): + self.block_device_mapping[1]['device_name'] = None + self._test_default_device_names(None, [], [], + self.block_device_mapping) + self.assertEquals(self.block_device_mapping[1]['device_name'], + '/dev/vdb') + + # Test that default eph will be taken into account on libvirt + self.is_libvirt = True + self.instance_type['swap'] = 0 + self.block_device_mapping[1]['device_name'] = None + self._test_default_device_names(None, [], [], + self.block_device_mapping) + self.assertEquals(self.block_device_mapping[1]['device_name'], + '/dev/vdc') + + def test_libvirt_default_swap(self): + # Test that default swap will be taken into account on libvirt + self.is_libvirt = True + self.instance['ephemeral_gb'] = 0 + self.block_device_mapping[1]['device_name'] = None + self._test_default_device_names(None, [], [], + self.block_device_mapping) + self.assertEquals(self.block_device_mapping[1]['device_name'], + '/dev/vdc') + + def test_libvirt_default_swap_ephemeral(self): + self.is_libvirt = True + self.block_device_mapping[1]['device_name'] = None + self._test_default_device_names(None, [], [], + self.block_device_mapping) + self.assertEquals(self.block_device_mapping[1]['device_name'], + '/dev/vdd') + + def test_update_fn_gets_called(self): + def _update(bdm): + self.update_called = True + + self.block_device_mapping[1]['device_name'] = None + compute_utils.default_device_names_for_instance( + self.instance, self.root_device_name, _update, [], [], + self.block_device_mapping) + self.assertTrue(self.update_called) + + class UsageInfoTestCase(test.TestCase): def setUp(self): diff --git a/nova/tests/db/test_db_api.py b/nova/tests/db/test_db_api.py index cfd4f61aa9..868d4a11a8 100644 --- a/nova/tests/db/test_db_api.py +++ b/nova/tests/db/test_db_api.py @@ -4208,6 +4208,24 @@ class BlockDeviceMappingTestCase(test.TestCase): self.assertEqual(bdm_real['device_name'], 'fake_name') self.assertEqual(bdm_real['destination_type'], 'camelot') + # check create without device_name + bdm1 = dict(values) + bdm1['device_name'] = None + db.block_device_mapping_update_or_create(self.ctxt, bdm1, legacy=False) + bdm_real = db.block_device_mapping_get_all_by_instance(self.ctxt, uuid) + self.assertEqual(len(bdm_real), 2) + bdm_real = bdm_real[1] + self.assertEqual(bdm_real['device_name'], None) + + # check create multiple devices without device_name + bdm2 = dict(values) + bdm2['device_name'] = None + db.block_device_mapping_update_or_create(self.ctxt, bdm2, legacy=False) + bdm_real = db.block_device_mapping_get_all_by_instance(self.ctxt, uuid) + self.assertEqual(len(bdm_real), 3) + bdm_real = bdm_real[2] + self.assertEqual(bdm_real['device_name'], None) + def test_block_device_mapping_update_or_create_multiple_ephemeral(self): uuid = self.instance['uuid'] values = { diff --git a/nova/tests/virt/test_block_device.py b/nova/tests/virt/test_block_device.py index c5bbf07b20..5928c1f601 100644 --- a/nova/tests/virt/test_block_device.py +++ b/nova/tests/virt/test_block_device.py @@ -468,3 +468,11 @@ class TestDriverBlockDevice(test.TestCase): driver_block_device.get_swap(legacy_swap)) self.assertEquals(no_swap, driver_block_device.get_swap(no_swap)) self.assertEquals(None, driver_block_device.get_swap([])) + + def test_is_implemented(self): + for bdm in (self.image_bdm, self.volume_bdm, self.swap_bdm, + self.ephemeral_bdm, self.snapshot_bdm): + self.assertTrue(driver_block_device.is_implemented(bdm)) + local_image = self.image_bdm.copy() + local_image['destination_type'] = 'local' + self.assertFalse(driver_block_device.is_implemented(local_image)) diff --git a/nova/virt/block_device.py b/nova/virt/block_device.py index bf0bac6f0d..f58777a1bc 100644 --- a/nova/virt/block_device.py +++ b/nova/virt/block_device.py @@ -327,3 +327,18 @@ def get_swap(transformed_list): return transformed_list.pop() except IndexError: return None + + +_IMPLEMENTED_CLASSES = (DriverSwapBlockDevice, DriverEphemeralBlockDevice, + DriverVolumeBlockDevice, DriverSnapshotBlockDevice, + DriverImageBlockDevice) + + +def is_implemented(bdm): + for cls in _IMPLEMENTED_CLASSES: + try: + cls(bdm) + return True + except _NotTransformable: + pass + return False diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 3ba3b802d7..5c356065d3 100755 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -1068,6 +1068,15 @@ class ComputeDriver(object): """ raise NotImplementedError() + def default_root_device_name(self, instance, image_meta, root_bdm): + """Provide a default root device name for the driver.""" + raise NotImplementedError() + + def default_device_names_for_instance(self, instance, root_device_name, + *block_device_lists): + """Default the missing device names in the block device mapping.""" + raise NotImplementedError() + def load_compute_driver(virtapi, compute_driver=None): """Load a compute driver module.