diff --git a/nova/compute/api.py b/nova/compute/api.py index a963181adcc2..4414cec49041 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -714,6 +714,14 @@ class API(base.Base): return flavor_defined_bdms + def _merge_with_image_bdms(self, block_device_mapping, image_mappings): + """Override any block devices from the image by device name""" + device_names = set(bdm['device_name'] for bdm in block_device_mapping + if bdm['device_name']) + return (block_device_mapping + + [bdm for bdm in image_mappings + if bdm['device_name'] not in device_names]) + def _check_and_transform_bdm(self, context, base_options, instance_type, image_meta, min_count, max_count, block_device_mapping, legacy_bdm): @@ -758,7 +766,8 @@ class API(base.Base): block_device_mapping = ( filter(not_image_and_root_bdm, block_device_mapping)) - block_device_mapping += image_defined_bdms + block_device_mapping = self._merge_with_image_bdms( + block_device_mapping, image_defined_bdms) if min_count > 1 or max_count > 1: if any(map(lambda bdm: bdm['source_type'] == 'volume', diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index 9fa87b262987..c8aff82c2063 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -8637,8 +8637,7 @@ class ComputeAPITestCase(BaseTestCase): self.context, base_options, {}, image_meta, 1, 1, bdms, legacy_bdms) for expected, got in zip(expected_bdms, transformed_bdm): - self.assertThat(dict(expected.items()), - matchers.DictMatches(dict(got.items()))) + self.assertEqual(dict(expected.items()), dict(got.items())) def test_check_and_transform_legacy_bdm_no_image_bdms(self): legacy_bdms = [ @@ -8716,6 +8715,51 @@ class ComputeAPITestCase(BaseTestCase): self._test_check_and_transform_bdm(bdms, expected_bdms, image_bdms=image_bdms) + def test_check_and_transform_bdm_image_bdms_w_overrides(self): + bdms = [block_device.BlockDeviceDict({'source_type': 'image', + 'destination_type': 'local', + 'image_id': FAKE_IMAGE_REF, + 'boot_index': 0}), + block_device.BlockDeviceDict({'device_name': 'vdb', + 'no_device': True})] + image_bdms = [block_device.BlockDeviceDict( + {'source_type': 'volume', 'destination_type': 'volume', + 'volume_id': '33333333-aaaa-bbbb-cccc-444444444444', + 'device_name': '/dev/vdb'})] + expected_bdms = block_device_obj.block_device_make_list_from_dicts( + self.context, bdms) + self._test_check_and_transform_bdm(bdms, expected_bdms, + image_bdms=image_bdms) + + def test_check_and_transform_bdm_image_bdms_w_overrides_complex(self): + bdms = [block_device.BlockDeviceDict({'source_type': 'image', + 'destination_type': 'local', + 'image_id': FAKE_IMAGE_REF, + 'boot_index': 0}), + block_device.BlockDeviceDict({'device_name': 'vdb', + 'no_device': True}), + block_device.BlockDeviceDict( + {'source_type': 'volume', 'destination_type': 'volume', + 'volume_id': '11111111-aaaa-bbbb-cccc-222222222222', + 'device_name': 'vdc'})] + image_bdms = [ + block_device.BlockDeviceDict( + {'source_type': 'volume', 'destination_type': 'volume', + 'volume_id': '33333333-aaaa-bbbb-cccc-444444444444', + 'device_name': '/dev/vdb'}), + block_device.BlockDeviceDict( + {'source_type': 'volume', 'destination_type': 'volume', + 'volume_id': '55555555-aaaa-bbbb-cccc-666666666666', + 'device_name': '/dev/vdc'}), + block_device.BlockDeviceDict( + {'source_type': 'volume', 'destination_type': 'volume', + 'volume_id': '77777777-aaaa-bbbb-cccc-8888888888888', + 'device_name': '/dev/vdd'})] + expected_bdms = block_device_obj.block_device_make_list_from_dicts( + self.context, bdms + [image_bdms[2]]) + self._test_check_and_transform_bdm(bdms, expected_bdms, + image_bdms=image_bdms) + def test_check_and_transform_bdm_legacy_image_bdms(self): bdms = [block_device.BlockDeviceDict({'source_type': 'image', 'destination_type': 'local',