diff --git a/nova/api/openstack/compute/extended_volumes.py b/nova/api/openstack/compute/extended_volumes.py index 8779a2649b93..6c15638e6637 100644 --- a/nova/api/openstack/compute/extended_volumes.py +++ b/nova/api/openstack/compute/extended_volumes.py @@ -27,9 +27,7 @@ class ExtendedVolumesController(wsgi.Controller): super(ExtendedVolumesController, self).__init__(*args, **kwargs) self.api_version_2_3 = api_version_request.APIVersionRequest('2.3') - def _extend_server(self, context, server, instance, requested_version): - bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( - context, instance.uuid) + def _extend_server(self, context, server, requested_version, bdms): volumes_attached = [] for bdm in bdms: if bdm.get('volume_id'): @@ -44,25 +42,33 @@ class ExtendedVolumesController(wsgi.Controller): @wsgi.extends def show(self, req, resp_obj, id): context = req.environ['nova.context'] + version = req.api_version_request if soft_authorize(context): server = resp_obj.obj['server'] - db_instance = req.get_db_instance(server['id']) - # server['id'] is guaranteed to be in the cache due to - # the core API adding it in its 'show' method. - self._extend_server(context, server, db_instance, - req.api_version_request) + bdms = objects.BlockDeviceMappingList.bdms_by_instance_uuid( + context, [server['id']]) + instance_bdms = self._get_instance_bdms(bdms, server) + self._extend_server(context, server, version, instance_bdms) @wsgi.extends def detail(self, req, resp_obj): context = req.environ['nova.context'] + version = req.api_version_request if soft_authorize(context): servers = list(resp_obj.obj['servers']) + instance_uuids = [server['id'] for server in servers] + bdms = objects.BlockDeviceMappingList.bdms_by_instance_uuid( + context, instance_uuids) for server in servers: - db_instance = req.get_db_instance(server['id']) - # server['id'] is guaranteed to be in the cache due to - # the core API adding it in its 'detail' method. - self._extend_server(context, server, db_instance, - req.api_version_request) + instance_bdms = self._get_instance_bdms(bdms, server) + self._extend_server(context, server, version, instance_bdms) + + def _get_instance_bdms(self, bdms, server): + # server['id'] is guaranteed to be in the cache due to + # the core API adding it in the 'detail' or 'show' method. + # If that instance has since been deleted, it won't be in the + # 'bdms' dictionary though, so use 'get' to avoid KeyErrors. + return bdms.get(server['id'], []) class ExtendedVolumes(extensions.V21APIExtensionBase): diff --git a/nova/api/openstack/compute/legacy_v2/contrib/extended_volumes.py b/nova/api/openstack/compute/legacy_v2/contrib/extended_volumes.py index f5c3b2bca7fd..90cd90fcc211 100644 --- a/nova/api/openstack/compute/legacy_v2/contrib/extended_volumes.py +++ b/nova/api/openstack/compute/legacy_v2/contrib/extended_volumes.py @@ -25,9 +25,7 @@ class ExtendedVolumesController(wsgi.Controller): def __init__(self, *args, **kwargs): super(ExtendedVolumesController, self).__init__(*args, **kwargs) - def _extend_server(self, context, server, instance): - bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( - context, instance.uuid) + def _extend_server(self, context, server, bdms): volume_ids = [bdm.volume_id for bdm in bdms if bdm.volume_id] key = "%s:volumes_attached" % Extended_volumes.alias server[key] = [{'id': volume_id} for volume_id in volume_ids] @@ -37,21 +35,29 @@ class ExtendedVolumesController(wsgi.Controller): context = req.environ['nova.context'] if authorize(context): server = resp_obj.obj['server'] - db_instance = req.get_db_instance(server['id']) - # server['id'] is guaranteed to be in the cache due to - # the core API adding it in its 'show' method. - self._extend_server(context, server, db_instance) + bdms = objects.BlockDeviceMappingList.bdms_by_instance_uuid( + context, [server['id']]) + instance_bdms = self._get_instance_bdms(bdms, server) + self._extend_server(context, server, instance_bdms) @wsgi.extends def detail(self, req, resp_obj): context = req.environ['nova.context'] if authorize(context): servers = list(resp_obj.obj['servers']) + instance_uuids = [server['id'] for server in servers] + bdms = objects.BlockDeviceMappingList.bdms_by_instance_uuid( + context, instance_uuids) for server in servers: - db_instance = req.get_db_instance(server['id']) - # server['id'] is guaranteed to be in the cache due to - # the core API adding it in its 'detail' method. - self._extend_server(context, server, db_instance) + instance_bdms = self._get_instance_bdms(bdms, server) + self._extend_server(context, server, instance_bdms) + + def _get_instance_bdms(self, bdms, server): + # server['id'] is guaranteed to be in the cache due to + # the core API adding it in the 'detail' or 'show' method. + # If that instance has since been deleted, it won't be in the + # 'bdms' dictionary though, so use 'get' to avoid KeyErrors. + return bdms.get(server['id'], []) class Extended_volumes(extensions.ExtensionDescriptor): diff --git a/nova/db/api.py b/nova/db/api.py index 47168ae75970..da33902832fe 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1197,6 +1197,14 @@ def block_device_mapping_update_or_create(context, values, legacy=True): return IMPL.block_device_mapping_update_or_create(context, values, legacy) +def block_device_mapping_get_all_by_instance_uuids(context, instance_uuids, + use_slave=False): + """Get all block device mapping belonging to a list of instances.""" + return IMPL.block_device_mapping_get_all_by_instance_uuids(context, + instance_uuids, + use_slave) + + def block_device_mapping_get_all_by_instance(context, instance_uuid, use_slave=False): """Get all block device mapping belonging to an instance.""" diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 162f7f4e0989..29164d95e02d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3962,6 +3962,18 @@ def block_device_mapping_update_or_create(context, values, legacy=True): return result +@require_context +def block_device_mapping_get_all_by_instance_uuids(context, instance_uuids, + use_slave=False): + if not instance_uuids: + return [] + return _block_device_mapping_get_query( + context, use_slave=use_slave + ).filter( + models.BlockDeviceMapping.instance_uuid.in_(instance_uuids) + ).all() + + @require_context def block_device_mapping_get_all_by_instance(context, instance_uuid, use_slave=False): diff --git a/nova/exception.py b/nova/exception.py index bf0e771c40f1..a2c37ededff6 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -597,6 +597,11 @@ class VolumeNotFound(NotFound): msg_fmt = _("Volume %(volume_id)s could not be found.") +class UndefinedRootBDM(NovaException): + msg_fmt = _("Undefined Block Device Mapping root: BlockDeviceMappingList " + "contains Block Device Mappings from multiple instances.") + + class BDMNotFound(NotFound): msg_fmt = _("No Block Device Mapping with id %(id)s.") diff --git a/nova/objects/base.py b/nova/objects/base.py index 5cb52ac5fa72..1dd0b791b60e 100644 --- a/nova/objects/base.py +++ b/nova/objects/base.py @@ -308,6 +308,28 @@ def obj_to_primitive(obj): return obj +def obj_make_dict_of_lists(context, list_cls, obj_list, item_key): + """Construct a dictionary of object lists, keyed by item_key. + + :param:context: Request context + :param:list_cls: The ObjectListBase class + :param:obj_list: The list of objects to place in the dictionary + :param:item_key: The object attribute name to use as a dictionary key + """ + + obj_lists = {} + for obj in obj_list: + key = getattr(obj, item_key) + if key not in obj_lists: + obj_lists[key] = list_cls() + obj_lists[key].objects = [] + obj_lists[key].objects.append(obj) + for key in obj_lists: + obj_lists[key]._context = context + obj_lists[key].obj_reset_changes() + return obj_lists + + def obj_make_list(context, list_obj, item_cls, db_list, **extra_args): """Construct an object list from a list of primitives. diff --git a/nova/objects/block_device.py b/nova/objects/block_device.py index 62db24283dbb..c30e6d2eabaf 100644 --- a/nova/objects/block_device.py +++ b/nova/objects/block_device.py @@ -267,12 +267,33 @@ class BlockDeviceMappingList(base.ObjectListBase, base.NovaObject): # Version 1.14: BlockDeviceMapping <= version 1.13 # Version 1.15: BlockDeviceMapping <= version 1.14 # Version 1.16: BlockDeviceMapping <= version 1.15 - VERSION = '1.16' + # Version 1.17: Add get_by_instance_uuids() + VERSION = '1.17' fields = { 'objects': fields.ListOfObjectsField('BlockDeviceMapping'), } + @property + def instance_uuids(self): + return set( + bdm.instance_uuid for bdm in self + if bdm.obj_attr_is_set('instance_uuid') + ) + + @classmethod + def bdms_by_instance_uuid(cls, context, instance_uuids): + bdms = cls.get_by_instance_uuids(context, instance_uuids) + return base.obj_make_dict_of_lists( + context, cls, bdms, 'instance_uuid') + + @base.remotable_classmethod + def get_by_instance_uuids(cls, context, instance_uuids, use_slave=False): + db_bdms = db.block_device_mapping_get_all_by_instance_uuids( + context, instance_uuids, use_slave=use_slave) + return base.obj_make_list( + context, cls(), objects.BlockDeviceMapping, db_bdms or []) + @base.remotable_classmethod def get_by_instance_uuid(cls, context, instance_uuid, use_slave=False): db_bdms = db.block_device_mapping_get_all_by_instance( @@ -281,6 +302,19 @@ class BlockDeviceMappingList(base.ObjectListBase, base.NovaObject): context, cls(), objects.BlockDeviceMapping, db_bdms or []) def root_bdm(self): + """It only makes sense to call this method when the + BlockDeviceMappingList contains BlockDeviceMappings from + exactly one instance rather than BlockDeviceMappings from + multiple instances. + + For example, you should not call this method from a + BlockDeviceMappingList created by get_by_instance_uuids(), + but you may call this method from a BlockDeviceMappingList + created by get_by_instance_uuid(). + """ + + if len(self.instance_uuids) > 1: + raise exception.UndefinedRootBDM() try: return next(bdm_obj for bdm_obj in self if bdm_obj.is_root) except StopIteration: diff --git a/nova/tests/functional/api_sample_tests/test_extended_volumes.py b/nova/tests/functional/api_sample_tests/test_extended_volumes.py index ae51dcbd8937..40ba3cd7a926 100644 --- a/nova/tests/functional/api_sample_tests/test_extended_volumes.py +++ b/nova/tests/functional/api_sample_tests/test_extended_volumes.py @@ -46,8 +46,8 @@ class ExtendedVolumesSampleJsonTests(test_servers.ServersSampleBase): def test_show(self): uuid = self._post_server() - self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', - fakes.stub_bdm_get_all_by_instance) + self.stubs.Set(db, 'block_device_mapping_get_all_by_instance_uuids', + fakes.stub_bdm_get_all_by_instance_uuids) response = self._do_get('servers/%s' % uuid) subs = self._get_regexes() subs['hostid'] = '[a-f0-9]+' @@ -57,8 +57,8 @@ class ExtendedVolumesSampleJsonTests(test_servers.ServersSampleBase): def test_detail(self): uuid = self._post_server() - self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', - fakes.stub_bdm_get_all_by_instance) + self.stubs.Set(db, 'block_device_mapping_get_all_by_instance_uuids', + fakes.stub_bdm_get_all_by_instance_uuids) response = self._do_get('servers/detail') subs = self._get_regexes() subs['id'] = uuid diff --git a/nova/tests/unit/api/openstack/compute/test_extended_volumes.py b/nova/tests/unit/api/openstack/compute/test_extended_volumes.py index 4682470082aa..471bbb441720 100644 --- a/nova/tests/unit/api/openstack/compute/test_extended_volumes.py +++ b/nova/tests/unit/api/openstack/compute/test_extended_volumes.py @@ -41,22 +41,43 @@ def fake_compute_get(*args, **kwargs): def fake_compute_get_all(*args, **kwargs): - db_list = [fakes.stub_instance(1), fakes.stub_instance(2)] + db_list = [ + fakes.stub_instance(1, uuid=UUID1), + fakes.stub_instance(2, uuid=UUID2), + ] fields = instance_obj.INSTANCE_DEFAULT_FIELDS return instance_obj._make_instance_list(args[1], objects.InstanceList(), db_list, fields) -def fake_bdms_get_all_by_instance(*args, **kwargs): - return [fake_block_device.FakeDbBlockDeviceDict( - {'volume_id': UUID1, 'source_type': 'volume', - 'destination_type': 'volume', 'id': 1, - 'delete_on_termination': True}), - fake_block_device.FakeDbBlockDeviceDict( - {'volume_id': UUID2, 'source_type': 'volume', - 'destination_type': 'volume', 'id': 2, - 'delete_on_termination': False})] +def fake_bdms_get_all_by_instance_uuids(*args, **kwargs): + return [ + fake_block_device.FakeDbBlockDeviceDict({ + 'id': 1, + 'volume_id': 'some_volume_1', + 'instance_uuid': UUID1, + 'source_type': 'volume', + 'destination_type': 'volume', + 'delete_on_termination': True, + }), + fake_block_device.FakeDbBlockDeviceDict({ + 'id': 2, + 'volume_id': 'some_volume_2', + 'instance_uuid': UUID2, + 'source_type': 'volume', + 'destination_type': 'volume', + 'delete_on_termination': False, + }), + fake_block_device.FakeDbBlockDeviceDict({ + 'id': 3, + 'volume_id': 'some_volume_3', + 'instance_uuid': UUID2, + 'source_type': 'volume', + 'destination_type': 'volume', + 'delete_on_termination': False, + }), + ] def fake_volume_get(*args, **kwargs): @@ -66,7 +87,11 @@ def fake_volume_get(*args, **kwargs): class ExtendedVolumesTestV21(test.TestCase): content_type = 'application/json' prefix = 'os-extended-volumes:' - exp_volumes = [{'id': UUID1}, {'id': UUID2}] + exp_volumes_show = [{'id': 'some_volume_1'}] + exp_volumes_detail = [ + [{'id': 'some_volume_1'}], + [{'id': 'some_volume_2'}, {'id': 'some_volume_3'}], + ] wsgi_api_version = os_wsgi.DEFAULT_API_VERSION def setUp(self): @@ -74,8 +99,8 @@ class ExtendedVolumesTestV21(test.TestCase): fakes.stub_out_nw_api(self.stubs) self.stubs.Set(compute.api.API, 'get', fake_compute_get) self.stubs.Set(compute.api.API, 'get_all', fake_compute_get_all) - self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', - fake_bdms_get_all_by_instance) + self.stubs.Set(db, 'block_device_mapping_get_all_by_instance_uuids', + fake_bdms_get_all_by_instance_uuids) self._setUp() self.app = self._setup_app() return_server = fakes.fake_instance_get() @@ -113,7 +138,7 @@ class ExtendedVolumesTestV21(test.TestCase): self.assertEqual(200, res.status_int) server = self._get_server(res.body) actual = server.get('%svolumes_attached' % self.prefix) - self.assertEqual(self.exp_volumes, actual) + self.assertEqual(self.exp_volumes_show, actual) def test_detail(self): res = self._make_request('/detail') @@ -121,7 +146,7 @@ class ExtendedVolumesTestV21(test.TestCase): self.assertEqual(200, res.status_int) for i, server in enumerate(self._get_servers(res.body)): actual = server.get('%svolumes_attached' % self.prefix) - self.assertEqual(self.exp_volumes, actual) + self.assertEqual(self.exp_volumes_detail[i], actual) class ExtendedVolumesTestV2(ExtendedVolumesTestV21): @@ -138,8 +163,18 @@ class ExtendedVolumesTestV2(ExtendedVolumesTestV21): class ExtendedVolumesTestV23(ExtendedVolumesTestV21): - exp_volumes = [{'id': UUID1, 'delete_on_termination': True}, - {'id': UUID2, 'delete_on_termination': False}] + exp_volumes_show = [ + {'id': 'some_volume_1', 'delete_on_termination': True}, + ] + exp_volumes_detail = [ + [ + {'id': 'some_volume_1', 'delete_on_termination': True}, + ], + [ + {'id': 'some_volume_2', 'delete_on_termination': False}, + {'id': 'some_volume_3', 'delete_on_termination': False}, + ], + ] wsgi_api_version = '2.3' diff --git a/nova/tests/unit/api/openstack/fakes.py b/nova/tests/unit/api/openstack/fakes.py index 2608a58e77eb..a15ca5096bf9 100644 --- a/nova/tests/unit/api/openstack/fakes.py +++ b/nova/tests/unit/api/openstack/fakes.py @@ -682,13 +682,21 @@ def stub_snapshot_get_all(self, context): stub_snapshot(102, project_id='superduperfake')] -def stub_bdm_get_all_by_instance(context, instance_uuid, use_slave=False): - return [fake_block_device.FakeDbBlockDeviceDict( - {'id': 1, 'source_type': 'volume', 'destination_type': 'volume', - 'volume_id': 'volume_id1', 'instance_uuid': instance_uuid}), - fake_block_device.FakeDbBlockDeviceDict( - {'id': 2, 'source_type': 'volume', 'destination_type': 'volume', - 'volume_id': 'volume_id2', 'instance_uuid': instance_uuid})] +def stub_bdm_get_all_by_instance_uuids(context, instance_uuids, + use_slave=False): + i = 1 + result = [] + for instance_uuid in instance_uuids: + for x in range(2): # add two BDMs per instance + result.append(fake_block_device.FakeDbBlockDeviceDict({ + 'id': i, + 'source_type': 'volume', + 'destination_type': 'volume', + 'volume_id': 'volume_id%d' % (i), + 'instance_uuid': instance_uuid, + })) + i += 1 + return result def fake_get_available_languages(): diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index d51dbdc44960..3e631c4c213a 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -8947,6 +8947,7 @@ class ComputeAPITestCase(BaseTestCase): {'source_type': 'volume', 'device_name': '/dev/vda', 'volume_id': 'fake_volume_id', + 'instance_uuid': 'some_instance_uuid', 'boot_index': 0, 'destination_type': 'volume'})]) self.assertTrue( @@ -8958,11 +8959,13 @@ class ComputeAPITestCase(BaseTestCase): 'device_name': '/dev/vda', 'volume_id': 'fake_volume_id', 'destination_type': 'local', + 'instance_uuid': 'some_instance_uuid', 'boot_index': 0, 'snapshot_id': None}), fake_block_device.FakeDbBlockDeviceDict( {'source_type': 'volume', 'device_name': '/dev/vdb', + 'instance_uuid': 'some_instance_uuid', 'boot_index': 1, 'destination_type': 'volume', 'volume_id': 'c2ec2156-d75e-11e2-985b-5254009297d6', @@ -8975,6 +8978,7 @@ class ComputeAPITestCase(BaseTestCase): {'source_type': 'volume', 'device_name': '/dev/vda', 'snapshot_id': 'de8836ac-d75e-11e2-8271-5254009297d6', + 'instance_uuid': 'some_instance_uuid', 'destination_type': 'volume', 'boot_index': 0, 'volume_id': None})]) diff --git a/nova/tests/unit/db/test_db_api.py b/nova/tests/unit/db/test_db_api.py index 9a833f34a358..2c4eae1e7a2e 100644 --- a/nova/tests/unit/db/test_db_api.py +++ b/nova/tests/unit/db/test_db_api.py @@ -5975,26 +5975,52 @@ class BlockDeviceMappingTestCase(test.TestCase): self.assertEqual(bdm_real['guest_format'], 'swap') db.block_device_mapping_destroy(self.ctxt, bdm_real['id']) - def test_block_device_mapping_get_all_by_instance(self): + def test_block_device_mapping_get_all_by_instance_uuids(self): uuid1 = self.instance['uuid'] uuid2 = db.instance_create(self.ctxt, {})['uuid'] - bmds_values = [{'instance_uuid': uuid1, + bdms_values = [{'instance_uuid': uuid1, 'device_name': '/dev/vda'}, {'instance_uuid': uuid2, 'device_name': '/dev/vdb'}, {'instance_uuid': uuid2, 'device_name': '/dev/vdc'}] - for bdm in bmds_values: + for bdm in bdms_values: self._create_bdm(bdm) - bmd = db.block_device_mapping_get_all_by_instance(self.ctxt, uuid1) - self.assertEqual(len(bmd), 1) - self.assertEqual(bmd[0]['device_name'], '/dev/vda') + bdms = db.block_device_mapping_get_all_by_instance_uuids( + self.ctxt, []) + self.assertEqual(len(bdms), 0) - bmd = db.block_device_mapping_get_all_by_instance(self.ctxt, uuid2) - self.assertEqual(len(bmd), 2) + bdms = db.block_device_mapping_get_all_by_instance_uuids( + self.ctxt, [uuid2]) + self.assertEqual(len(bdms), 2) + + bdms = db.block_device_mapping_get_all_by_instance_uuids( + self.ctxt, [uuid1, uuid2]) + self.assertEqual(len(bdms), 3) + + def test_block_device_mapping_get_all_by_instance(self): + uuid1 = self.instance['uuid'] + uuid2 = db.instance_create(self.ctxt, {})['uuid'] + + bdms_values = [{'instance_uuid': uuid1, + 'device_name': '/dev/vda'}, + {'instance_uuid': uuid2, + 'device_name': '/dev/vdb'}, + {'instance_uuid': uuid2, + 'device_name': '/dev/vdc'}] + + for bdm in bdms_values: + self._create_bdm(bdm) + + bdms = db.block_device_mapping_get_all_by_instance(self.ctxt, uuid1) + self.assertEqual(len(bdms), 1) + self.assertEqual(bdms[0]['device_name'], '/dev/vda') + + bdms = db.block_device_mapping_get_all_by_instance(self.ctxt, uuid2) + self.assertEqual(len(bdms), 2) def test_block_device_mapping_destroy(self): bdm = self._create_bdm({}) diff --git a/nova/tests/unit/objects/test_block_device.py b/nova/tests/unit/objects/test_block_device.py index d85a3ba11bfe..a2bca83cee5f 100644 --- a/nova/tests/unit/objects/test_block_device.py +++ b/nova/tests/unit/objects/test_block_device.py @@ -316,25 +316,62 @@ class TestRemoteBlockDeviceMappingObject(test_objects._RemoteTest, class _TestBlockDeviceMappingListObject(object): - def fake_bdm(self, bdm_id): + def fake_bdm(self, bdm_id, boot_index=-1, instance_uuid='fake-instance'): fake_bdm = fake_block_device.FakeDbBlockDeviceDict({ - 'id': bdm_id, 'instance_uuid': 'fake-instance', + 'id': bdm_id, + 'boot_index': boot_index, + 'instance_uuid': instance_uuid, 'device_name': '/dev/sda2', 'source_type': 'snapshot', 'destination_type': 'volume', 'connection_info': "{'fake': 'connection_info'}", 'snapshot_id': 'fake-snapshot-id-1', - 'boot_index': -1, }) return fake_bdm + @mock.patch.object(db, 'block_device_mapping_get_all_by_instance_uuids') + def test_bdms_by_instance_uuid(self, get_all_by_inst_uuids): + fakes = [self.fake_bdm(123), self.fake_bdm(456)] + get_all_by_inst_uuids.return_value = fakes + bdms_by_uuid = objects.BlockDeviceMappingList.bdms_by_instance_uuid( + self.context, ['fake-instance']) + self.assertEqual(['fake-instance'], list(bdms_by_uuid.keys())) + self.assertIsInstance( + bdms_by_uuid['fake-instance'], objects.BlockDeviceMappingList) + for faked, got in zip(fakes, bdms_by_uuid['fake-instance']): + self.assertIsInstance(got, objects.BlockDeviceMapping) + self.assertEqual(faked['id'], got.id) + + @mock.patch.object(db, 'block_device_mapping_get_all_by_instance_uuids') + def test_bdms_by_instance_uuid_no_result(self, get_all_by_inst_uuids): + get_all_by_inst_uuids.return_value = None + bdms_by_uuid = objects.BlockDeviceMappingList.bdms_by_instance_uuid( + self.context, ['fake-instance']) + self.assertEqual({}, bdms_by_uuid) + + @mock.patch.object(db, 'block_device_mapping_get_all_by_instance_uuids') + def test_get_by_instance_uuids(self, get_all_by_inst_uuids): + fakes = [self.fake_bdm(123), self.fake_bdm(456)] + get_all_by_inst_uuids.return_value = fakes + bdm_list = objects.BlockDeviceMappingList.get_by_instance_uuids( + self.context, ['fake-instance']) + for faked, got in zip(fakes, bdm_list): + self.assertIsInstance(got, objects.BlockDeviceMapping) + self.assertEqual(faked['id'], got.id) + + @mock.patch.object(db, 'block_device_mapping_get_all_by_instance_uuids') + def test_get_by_instance_uuids_no_result(self, get_all_by_inst_uuids): + get_all_by_inst_uuids.return_value = None + bdm_list = objects.BlockDeviceMappingList.get_by_instance_uuids( + self.context, ['fake-instance']) + self.assertEqual(0, len(bdm_list)) + @mock.patch.object(db, 'block_device_mapping_get_all_by_instance') def test_get_by_instance_uuid(self, get_all_by_inst): fakes = [self.fake_bdm(123), self.fake_bdm(456)] get_all_by_inst.return_value = fakes - bdm_list = ( - objects.BlockDeviceMappingList.get_by_instance_uuid( - self.context, 'fake_instance_uuid')) + bdm_list = objects.BlockDeviceMappingList.get_by_instance_uuid( + self.context, 'fake-instance') for faked, got in zip(fakes, bdm_list): self.assertIsInstance(got, objects.BlockDeviceMapping) self.assertEqual(faked['id'], got.id) @@ -342,11 +379,36 @@ class _TestBlockDeviceMappingListObject(object): @mock.patch.object(db, 'block_device_mapping_get_all_by_instance') def test_get_by_instance_uuid_no_result(self, get_all_by_inst): get_all_by_inst.return_value = None - bdm_list = ( - objects.BlockDeviceMappingList.get_by_instance_uuid( - self.context, 'fake_instance_uuid')) + bdm_list = objects.BlockDeviceMappingList.get_by_instance_uuid( + self.context, 'fake-instance') self.assertEqual(0, len(bdm_list)) + @mock.patch.object(db, 'block_device_mapping_get_all_by_instance') + def test_root_bdm(self, get_all_by_inst): + fakes = [self.fake_bdm(123), self.fake_bdm(456, boot_index=0)] + get_all_by_inst.return_value = fakes + bdm_list = objects.BlockDeviceMappingList.get_by_instance_uuid( + self.context, 'fake-instance') + self.assertEqual(456, bdm_list.root_bdm().id) + + @mock.patch.object(db, 'block_device_mapping_get_all_by_instance') + def test_root_bdm_empty_bdm_list(self, get_all_by_inst): + get_all_by_inst.return_value = None + bdm_list = objects.BlockDeviceMappingList.get_by_instance_uuid( + self.context, 'fake-instance') + self.assertIsNone(bdm_list.root_bdm()) + + @mock.patch.object(db, 'block_device_mapping_get_all_by_instance') + def test_root_bdm_undefined(self, get_all_by_inst): + fakes = [ + self.fake_bdm(123, instance_uuid='uuid_1'), + self.fake_bdm(456, instance_uuid='uuid_2') + ] + get_all_by_inst.return_value = fakes + bdm_list = objects.BlockDeviceMappingList.get_by_instance_uuid( + self.context, 'fake-instance') + self.assertRaises(exception.UndefinedRootBDM, bdm_list.root_bdm) + class TestBlockDeviceMappingListObject(test_objects._LocalTest, _TestBlockDeviceMappingListObject): diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index 83d70e9ac5c3..45401d5e6444 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1168,7 +1168,7 @@ object_data = { 'BandwidthUsage': '1.2-c6e4c779c7f40f2407e3d70022e3cd1c', 'BandwidthUsageList': '1.2-5fe7475ada6fe62413cbfcc06ec70746', 'BlockDeviceMapping': '1.15-d44d8d694619e79c172a99b3c1d6261d', - 'BlockDeviceMappingList': '1.16-6fa262c059dad1d519b9fe05b9e4f404', + 'BlockDeviceMappingList': '1.17-1e568eecb91d06d4112db9fd656de235', 'CellMapping': '1.0-7f1a7e85a22bbb7559fc730ab658b9bd', 'ComputeNode': '1.14-a396975707b66281c5f404a68fccd395', 'ComputeNodeList': '1.14-3b6f4f5ade621c40e70cb116db237844',