diff --git a/nova/block_device.py b/nova/block_device.py index 2f1afb07332a..413fef20e389 100644 --- a/nova/block_device.py +++ b/nova/block_device.py @@ -13,11 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import re from oslo_log import log as logging from oslo_utils import strutils - +from oslo_utils import units import nova.conf from nova import exception @@ -34,6 +35,12 @@ _DEFAULT_MAPPINGS = {'ami': 'sda1', 'root': DEFAULT_ROOT_DEV_NAME, 'swap': 'sda3'} +# Image attributes which Cinder stores in volume image metadata +# as regular properties +VIM_IMAGE_ATTRIBUTES = ( + 'image_id', 'image_name', 'size', 'checksum', + 'container_format', 'disk_format', 'min_ram', 'min_disk', +) bdm_legacy_fields = set(['device_name', 'delete_on_termination', 'virtual_name', 'snapshot_id', @@ -629,3 +636,86 @@ def get_bdm_swap_list(block_device_mappings): def get_bdm_local_disk_num(block_device_mappings): return len([bdm for bdm in block_device_mappings if bdm.get('destination_type') == 'local']) + + +def get_bdm_image_metadata(context, image_api, volume_api, + block_device_mapping, legacy_bdm=True): + """Attempt to retrive image metadata from a given block_device_mapping. + + If we are booting from a volume, we need to get the volume details from + Cinder and make sure we pass the metadata back accordingly. + + :param context: request context + :param image_api: Image API + :param volume_api: Volume API + :param block_device_mapping: + :param legacy_bdm: + """ + if not block_device_mapping: + return {} + + for bdm in block_device_mapping: + if (legacy_bdm and + get_device_letter( + bdm.get('device_name', '')) != 'a'): + continue + elif not legacy_bdm and bdm.get('boot_index') != 0: + continue + + volume_id = bdm.get('volume_id') + snapshot_id = bdm.get('snapshot_id') + if snapshot_id: + # NOTE(alaski): A volume snapshot inherits metadata from the + # originating volume, but the API does not expose metadata + # on the snapshot itself. So we query the volume for it below. + snapshot = volume_api.get_snapshot(context, snapshot_id) + volume_id = snapshot['volume_id'] + + if bdm.get('image_id'): + try: + image_id = bdm['image_id'] + image_meta = image_api.get(context, image_id) + return image_meta + except Exception: + raise exception.InvalidBDMImage(id=image_id) + elif volume_id: + try: + volume = volume_api.get(context, volume_id) + except exception.CinderConnectionFailed: + raise + except Exception: + raise exception.InvalidBDMVolume(id=volume_id) + + if not volume.get('bootable', True): + raise exception.InvalidBDMVolumeNotBootable(id=volume_id) + + return get_image_metadata_from_volume(volume) + return {} + + +def get_image_metadata_from_volume(volume): + properties = copy.copy(volume.get('volume_image_metadata', {})) + image_meta = {'properties': properties} + # Volume size is no longer related to the original image size, + # so we take it from the volume directly. Cinder creates + # volumes in Gb increments, and stores size in Gb, whereas + # glance reports size in bytes. As we're returning glance + # metadata here, we need to convert it. + image_meta['size'] = volume.get('size', 0) * units.Gi + # NOTE(yjiang5): restore the basic attributes + # NOTE(mdbooth): These values come from volume_glance_metadata + # in cinder. This is a simple key/value table, and all values + # are strings. We need to convert them to ints to avoid + # unexpected type errors. + for attr in VIM_IMAGE_ATTRIBUTES: + val = properties.pop(attr, None) + if attr in ('min_ram', 'min_disk'): + image_meta[attr] = int(val or 0) + # NOTE(mriedem): Set the status to 'active' as a really old hack + # from when this method was in the compute API class and is + # needed for _validate_flavor_image which makes sure the image + # is 'active'. For volume-backed servers, if the volume is not + # available because the image backing the volume is not active, + # then the compute API trying to reserve the volume should fail. + image_meta['status'] = 'active' + return image_meta diff --git a/nova/compute/api.py b/nova/compute/api.py index 6db5019f6d1c..811c7483961f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1432,7 +1432,7 @@ class API(base.Base): "when booting from volume") raise exception.CertificateValidationFailed(message=msg) image_id = None - boot_meta = utils.get_bdm_image_metadata( + boot_meta = block_device.get_bdm_image_metadata( context, self.image_api, self.volume_api, block_device_mapping, legacy_bdm) diff --git a/nova/notifications/objects/image.py b/nova/notifications/objects/image.py index 43e6530ad401..8d7acadabb39 100644 --- a/nova/notifications/objects/image.py +++ b/nova/notifications/objects/image.py @@ -68,7 +68,7 @@ class ImageMetaPayload(base.NotificationPayloadBase): # * created_at # # c. It cannot be got in the boot from volume case. - # See VIM_IMAGE_ATTRIBUTES in nova/utils.py. + # See VIM_IMAGE_ATTRIBUTES in nova/block_device.py. # # * id (not 'image_id') # * visibility diff --git a/nova/tests/unit/api/openstack/compute/test_serversV21.py b/nova/tests/unit/api/openstack/compute/test_serversV21.py index 643af24a9858..cb911dfe51f3 100644 --- a/nova/tests/unit/api/openstack/compute/test_serversV21.py +++ b/nova/tests/unit/api/openstack/compute/test_serversV21.py @@ -4754,7 +4754,7 @@ class ServersControllerCreateTest(test.TestCase): @mock.patch('nova.compute.api.API._get_volumes_for_bdms') @mock.patch.object(compute_api.API, '_validate_bdm') - @mock.patch('nova.utils.get_bdm_image_metadata') + @mock.patch('nova.block_device.get_bdm_image_metadata') def test_create_instance_with_bdms_and_no_image( self, mock_bdm_image_metadata, mock_validate_bdm, mock_get_vols): mock_bdm_image_metadata.return_value = {} @@ -4779,7 +4779,7 @@ class ServersControllerCreateTest(test.TestCase): @mock.patch('nova.compute.api.API._get_volumes_for_bdms') @mock.patch.object(compute_api.API, '_validate_bdm') - @mock.patch('nova.utils.get_bdm_image_metadata') + @mock.patch('nova.block_device.get_bdm_image_metadata') def test_create_instance_with_bdms_and_empty_imageRef( self, mock_bdm_image_metadata, mock_validate_bdm, mock_get_volumes): mock_bdm_image_metadata.return_value = {} @@ -5040,7 +5040,7 @@ class ServersControllerCreateTest(test.TestCase): self.assertRaises(webob.exc.HTTPBadRequest, self._test_create, params, no_image=True) - @mock.patch('nova.utils.get_bdm_image_metadata') + @mock.patch('nova.block_device.get_bdm_image_metadata') def test_create_instance_non_bootable_volume_fails(self, fake_bdm_meta): params = {'block_device_mapping_v2': self.bdm_v2} fake_bdm_meta.side_effect = exception.InvalidBDMVolumeNotBootable(id=1) @@ -5111,7 +5111,7 @@ class ServersControllerCreateTest(test.TestCase): self.stub_out('nova.compute.api.API.create', create) self._test_create_bdm(params) - @mock.patch('nova.utils.get_bdm_image_metadata') + @mock.patch('nova.block_device.get_bdm_image_metadata') def test_create_instance_with_volumes_enabled_and_bdms_no_image( self, mock_get_bdm_image_metadata): """Test that the create works if there is no image supplied but @@ -5137,7 +5137,7 @@ class ServersControllerCreateTest(test.TestCase): mock_get_bdm_image_metadata.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY, self.bdm, True) - @mock.patch('nova.utils.get_bdm_image_metadata') + @mock.patch('nova.block_device.get_bdm_image_metadata') def test_create_instance_with_imageRef_as_empty_string( self, mock_bdm_image_metadata): volume = { @@ -5180,7 +5180,7 @@ class ServersControllerCreateTest(test.TestCase): self.assertRaises(exception.ValidationError, self._test_create_bdm, params) - @mock.patch('nova.utils.get_bdm_image_metadata') + @mock.patch('nova.block_device.get_bdm_image_metadata') def test_create_instance_non_bootable_volume_fails_legacy_bdm( self, fake_bdm_meta): bdm = [{ diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index bdcb10c9e53e..3b1252e7c548 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -684,7 +684,7 @@ class ComputeVolumeTestCase(BaseTestCase): 'delete_on_termination': False, }] - image_meta = utils.get_bdm_image_metadata( + image_meta = block_device.get_bdm_image_metadata( self.context, self.compute_api.image_api, self.compute_api.volume_api, block_device_mapping) if metadata: @@ -705,7 +705,7 @@ class ComputeVolumeTestCase(BaseTestCase): 'delete_on_termination': False, }] - image_meta = utils.get_bdm_image_metadata( + image_meta = block_device.get_bdm_image_metadata( self.context, self.compute_api.image_api, self.compute_api.volume_api, block_device_mapping, legacy_bdm=False) @@ -740,7 +740,7 @@ class ComputeVolumeTestCase(BaseTestCase): 'delete_on_termination': True, }] - image_meta = utils.get_bdm_image_metadata( + image_meta = block_device.get_bdm_image_metadata( self.context, self.compute_api.image_api, self.compute_api.volume_api, block_device_mapping, legacy_bdm=False) diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 6992f313224c..3e38a7233b6e 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -3491,95 +3491,6 @@ class _ComputeAPIUnitTestMixIn(object): self.context, mock.sentinel.volume_id, mock.sentinel.snapshot_id, mock.sentinel.delete_info) - def _test_boot_volume_bootable(self, is_bootable=False): - def get_vol_data(*args, **kwargs): - return {'bootable': is_bootable} - block_device_mapping = [{ - 'id': 1, - 'device_name': 'vda', - 'no_device': None, - 'virtual_name': None, - 'snapshot_id': None, - 'volume_id': '1', - 'delete_on_termination': False, - }] - - expected_meta = {'min_disk': 0, 'min_ram': 0, 'properties': {}, - 'size': 0, 'status': 'active'} - - with mock.patch.object(self.compute_api.volume_api, 'get', - side_effect=get_vol_data): - if not is_bootable: - self.assertRaises(exception.InvalidBDMVolumeNotBootable, - utils.get_bdm_image_metadata, - self.context, self.compute_api.image_api, - self.compute_api.volume_api, - block_device_mapping) - else: - meta = utils.get_bdm_image_metadata( - self.context, self.compute_api.image_api, - self.compute_api.volume_api, block_device_mapping) - self.assertEqual(expected_meta, meta) - - def test_boot_volume_non_bootable(self): - self._test_boot_volume_bootable(False) - - def test_boot_volume_bootable(self): - self._test_boot_volume_bootable(True) - - def test_boot_volume_basic_property(self): - block_device_mapping = [{ - 'id': 1, - 'device_name': 'vda', - 'no_device': None, - 'virtual_name': None, - 'snapshot_id': None, - 'volume_id': '1', - 'delete_on_termination': False, - }] - fake_volume = {"volume_image_metadata": - {"min_ram": 256, "min_disk": 128, "foo": "bar"}} - with mock.patch.object(self.compute_api.volume_api, 'get', - return_value=fake_volume): - meta = utils.get_bdm_image_metadata( - self.context, self.compute_api.image_api, - self.compute_api.volume_api, block_device_mapping) - self.assertEqual(256, meta['min_ram']) - self.assertEqual(128, meta['min_disk']) - self.assertEqual('active', meta['status']) - self.assertEqual('bar', meta['properties']['foo']) - - def test_boot_volume_snapshot_basic_property(self): - block_device_mapping = [{ - 'id': 1, - 'device_name': 'vda', - 'no_device': None, - 'virtual_name': None, - 'snapshot_id': '2', - 'volume_id': None, - 'delete_on_termination': False, - }] - fake_volume = {"volume_image_metadata": - {"min_ram": 256, "min_disk": 128, "foo": "bar"}} - fake_snapshot = {"volume_id": "1"} - with test.nested( - mock.patch.object(self.compute_api.volume_api, 'get', - return_value=fake_volume), - mock.patch.object(self.compute_api.volume_api, 'get_snapshot', - return_value=fake_snapshot)) as ( - volume_get, volume_get_snapshot): - meta = utils.get_bdm_image_metadata( - self.context, self.compute_api.image_api, - self.compute_api.volume_api, block_device_mapping) - self.assertEqual(256, meta['min_ram']) - self.assertEqual(128, meta['min_disk']) - self.assertEqual('active', meta['status']) - self.assertEqual('bar', meta['properties']['foo']) - volume_get_snapshot.assert_called_once_with(self.context, - block_device_mapping[0]['snapshot_id']) - volume_get.assert_called_once_with(self.context, - fake_snapshot['volume_id']) - def _create_instance_with_disabled_disk_config(self, object=False): sys_meta = {"image_auto_disk_config": "Disabled"} params = {"system_metadata": sys_meta} @@ -4430,25 +4341,6 @@ class _ComputeAPIUnitTestMixIn(object): self.context, instance, volume_id, new_volume_id) - @mock.patch.object(cinder.API, 'get', - side_effect=exception.CinderConnectionFailed(reason='error')) - def test_get_bdm_image_metadata_with_cinder_down(self, mock_get): - bdms = [objects.BlockDeviceMapping( - **fake_block_device.FakeDbBlockDeviceDict( - { - 'id': 1, - 'volume_id': 1, - 'source_type': 'volume', - 'destination_type': 'volume', - 'device_name': 'vda', - }))] - self.assertRaises(exception.CinderConnectionFailed, - utils.get_bdm_image_metadata, - self.context, - self.compute_api.image_api, - self.compute_api.volume_api, - bdms, legacy_bdm=True) - def test_get_volumes_for_bdms_errors(self): """Simple test to make sure _get_volumes_for_bdms raises up errors.""" # Use a mix of pre-existing and source_type=image volumes to test the diff --git a/nova/tests/unit/test_block_device.py b/nova/tests/unit/test_block_device.py index abf265818a32..74a8c4e764fb 100644 --- a/nova/tests/unit/test_block_device.py +++ b/nova/tests/unit/test_block_device.py @@ -16,15 +16,21 @@ """ Tests for Block Device utility functions. """ + +import mock from oslo_utils.fixture import uuidsentinel as uuids +from oslo_utils import units import six from nova import block_device +from nova.compute import api as compute_api +from nova import context from nova import exception from nova import objects from nova import test from nova.tests.unit import fake_block_device from nova.tests.unit import matchers +from nova.volume import cinder class BlockDeviceTestCase(test.NoDBTestCase): @@ -723,3 +729,177 @@ class TestBlockDeviceDict(test.NoDBTestCase): None, obj, fake_block_device.FakeDbBlockDeviceDict( bdm)) self._test_snapshot_from_bdm(obj) + + +class GetBDMImageMetadataTestCase(test.NoDBTestCase): + + def setUp(self): + super().setUp() + self.compute_api = compute_api.API() + self.context = context.RequestContext('fake', 'fake') + + def _test_get_bdm_image_metadata__bootable(self, is_bootable=False): + block_device_mapping = [{ + 'id': 1, + 'device_name': 'vda', + 'no_device': None, + 'virtual_name': None, + 'snapshot_id': None, + 'volume_id': '1', + 'delete_on_termination': False, + }] + + expected_meta = { + 'min_disk': 0, 'min_ram': 0, 'properties': {}, 'size': 0, + 'status': 'active', + } + + def get_vol_data(*args, **kwargs): + return {'bootable': is_bootable} + + with mock.patch.object( + self.compute_api.volume_api, 'get', side_effect=get_vol_data, + ): + if not is_bootable: + self.assertRaises( + exception.InvalidBDMVolumeNotBootable, + block_device.get_bdm_image_metadata, + self.context, + self.compute_api.image_api, + self.compute_api.volume_api, + block_device_mapping) + else: + meta = block_device.get_bdm_image_metadata( + self.context, self.compute_api.image_api, + self.compute_api.volume_api, block_device_mapping) + self.assertEqual(expected_meta, meta) + + def test_get_bdm_image_metadata__non_bootable(self): + self._test_get_bdm_image_metadata__bootable(False) + + def test_get_bdm_image_metadata__bootable(self): + self._test_get_bdm_image_metadata__bootable(True) + + def test_get_bdm_image_metadata__basic_property(self): + block_device_mapping = [{ + 'id': 1, + 'device_name': 'vda', + 'no_device': None, + 'virtual_name': None, + 'snapshot_id': None, + 'volume_id': '1', + 'delete_on_termination': False, + }] + fake_volume = { + 'volume_image_metadata': { + 'min_ram': 256, 'min_disk': 128, 'foo': 'bar', + }, + } + with mock.patch.object( + self.compute_api.volume_api, 'get', return_value=fake_volume, + ): + meta = block_device.get_bdm_image_metadata( + self.context, self.compute_api.image_api, + self.compute_api.volume_api, block_device_mapping) + self.assertEqual(256, meta['min_ram']) + self.assertEqual(128, meta['min_disk']) + self.assertEqual('active', meta['status']) + self.assertEqual('bar', meta['properties']['foo']) + + def test_get_bdm_image_metadata__snapshot_basic_property(self): + block_device_mapping = [{ + 'id': 1, + 'device_name': 'vda', + 'no_device': None, + 'virtual_name': None, + 'snapshot_id': '2', + 'volume_id': None, + 'delete_on_termination': False, + }] + fake_volume = { + 'volume_image_metadata': { + 'min_ram': 256, 'min_disk': 128, 'foo': 'bar', + }, + } + fake_snapshot = {'volume_id': '1'} + with test.nested( + mock.patch.object( + self.compute_api.volume_api, 'get', + return_value=fake_volume), + mock.patch.object( + self.compute_api.volume_api, 'get_snapshot', + return_value=fake_snapshot), + ) as (volume_get, volume_get_snapshot): + meta = block_device.get_bdm_image_metadata( + self.context, self.compute_api.image_api, + self.compute_api.volume_api, block_device_mapping) + + self.assertEqual(256, meta['min_ram']) + self.assertEqual(128, meta['min_disk']) + self.assertEqual('active', meta['status']) + self.assertEqual('bar', meta['properties']['foo']) + volume_get_snapshot.assert_called_once_with( + self.context, block_device_mapping[0]['snapshot_id']) + volume_get.assert_called_once_with( + self.context, fake_snapshot['volume_id']) + + @mock.patch.object( + cinder.API, 'get', + side_effect=exception.CinderConnectionFailed(reason='error')) + def test_get_bdm_image_metadata__cinder_down(self, mock_get): + bdms = [ + objects.BlockDeviceMapping( + **fake_block_device.FakeDbBlockDeviceDict({ + 'id': 1, + 'volume_id': 1, + 'source_type': 'volume', + 'destination_type': 'volume', + 'device_name': 'vda', + }) + ) + ] + self.assertRaises( + exception.CinderConnectionFailed, + block_device.get_bdm_image_metadata, + self.context, + self.compute_api.image_api, + self.compute_api.volume_api, + bdms, legacy_bdm=True) + + +class GetImageMetadataFromVolumeTestCase(test.NoDBTestCase): + def test_inherit_image_properties(self): + properties = {'fake_prop': 'fake_value'} + volume = {'volume_image_metadata': properties} + image_meta = block_device.get_image_metadata_from_volume(volume) + self.assertEqual(properties, image_meta['properties']) + + def test_image_size(self): + volume = {'size': 10} + image_meta = block_device.get_image_metadata_from_volume(volume) + self.assertEqual(10 * units.Gi, image_meta['size']) + + def test_image_status(self): + volume = {} + image_meta = block_device.get_image_metadata_from_volume(volume) + self.assertEqual('active', image_meta['status']) + + def test_values_conversion(self): + properties = {'min_ram': '5', 'min_disk': '7'} + volume = {'volume_image_metadata': properties} + image_meta = block_device.get_image_metadata_from_volume(volume) + self.assertEqual(5, image_meta['min_ram']) + self.assertEqual(7, image_meta['min_disk']) + + def test_suppress_not_image_properties(self): + properties = { + 'min_ram': '256', 'min_disk': '128', 'image_id': 'fake_id', + 'image_name': 'fake_name', 'container_format': 'ami', + 'disk_format': 'ami', 'size': '1234', 'checksum': 'fake_checksum', + } + volume = {'volume_image_metadata': properties} + image_meta = block_device.get_image_metadata_from_volume(volume) + self.assertEqual({}, image_meta['properties']) + self.assertEqual(0, image_meta['size']) + # volume's properties should not be touched + self.assertNotEqual({}, properties) diff --git a/nova/tests/unit/test_utils.py b/nova/tests/unit/test_utils.py index d6cb92b2064e..e9930fcb7aa7 100644 --- a/nova/tests/unit/test_utils.py +++ b/nova/tests/unit/test_utils.py @@ -31,7 +31,6 @@ from oslo_context import context as common_context from oslo_context import fixture as context_fixture from oslo_utils import encodeutils from oslo_utils import fixture as utils_fixture -from oslo_utils import units import six from nova import context @@ -737,43 +736,6 @@ class GetImageFromSystemMetadataTestCase(test.NoDBTestCase): self.assertNotIn(key, image) -class GetImageMetadataFromVolumeTestCase(test.NoDBTestCase): - def test_inherit_image_properties(self): - properties = {"fake_prop": "fake_value"} - volume = {"volume_image_metadata": properties} - image_meta = utils.get_image_metadata_from_volume(volume) - self.assertEqual(properties, image_meta["properties"]) - - def test_image_size(self): - volume = {"size": 10} - image_meta = utils.get_image_metadata_from_volume(volume) - self.assertEqual(10 * units.Gi, image_meta["size"]) - - def test_image_status(self): - volume = {} - image_meta = utils.get_image_metadata_from_volume(volume) - self.assertEqual("active", image_meta["status"]) - - def test_values_conversion(self): - properties = {"min_ram": "5", "min_disk": "7"} - volume = {"volume_image_metadata": properties} - image_meta = utils.get_image_metadata_from_volume(volume) - self.assertEqual(5, image_meta["min_ram"]) - self.assertEqual(7, image_meta["min_disk"]) - - def test_suppress_not_image_properties(self): - properties = {"min_ram": "256", "min_disk": "128", - "image_id": "fake_id", "image_name": "fake_name", - "container_format": "ami", "disk_format": "ami", - "size": "1234", "checksum": "fake_checksum"} - volume = {"volume_image_metadata": properties} - image_meta = utils.get_image_metadata_from_volume(volume) - self.assertEqual({}, image_meta["properties"]) - self.assertEqual(0, image_meta["size"]) - # volume's properties should not be touched - self.assertNotEqual({}, properties) - - class SafeTruncateTestCase(test.NoDBTestCase): def test_exception_to_dict_with_long_message_3_bytes(self): # Generate Chinese byte string whose length is 300. This Chinese UTF-8 diff --git a/nova/utils.py b/nova/utils.py index e2d9d5e657cc..0d601710c135 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -18,7 +18,6 @@ """Utilities and helper functions.""" import contextlib -import copy import datetime import functools import hashlib @@ -46,11 +45,9 @@ 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 import six from six.moves import range -from nova import block_device import nova.conf from nova import exception from nova.i18n import _ @@ -78,12 +75,6 @@ SM_SKIP_KEYS = ( # Modern names 'img_mappings', 'img_block_device_mapping', ) -# Image attributes which Cinder stores in volume image metadata -# as regular properties -VIM_IMAGE_ATTRIBUTES = ( - 'image_id', 'image_name', 'size', 'checksum', - 'container_format', 'disk_format', 'min_ram', 'min_disk', -) _FILE_CACHE = {} @@ -788,89 +779,6 @@ def get_image_from_system_metadata(system_meta): return image_meta -def get_bdm_image_metadata(context, image_api, volume_api, - block_device_mapping, legacy_bdm=True): - """Attempt to retrive image metadata from a given block_device_mapping. - - If we are booting from a volume, we need to get the volume details from - Cinder and make sure we pass the metadata back accordingly. - - :param context: request context - :param image_api: Image API - :param volume_api: Volume API - :param block_device_mapping: - :param legacy_bdm: - """ - if not block_device_mapping: - return {} - - for bdm in block_device_mapping: - if (legacy_bdm and - block_device.get_device_letter( - bdm.get('device_name', '')) != 'a'): - continue - elif not legacy_bdm and bdm.get('boot_index') != 0: - continue - - volume_id = bdm.get('volume_id') - snapshot_id = bdm.get('snapshot_id') - if snapshot_id: - # NOTE(alaski): A volume snapshot inherits metadata from the - # originating volume, but the API does not expose metadata - # on the snapshot itself. So we query the volume for it below. - snapshot = volume_api.get_snapshot(context, snapshot_id) - volume_id = snapshot['volume_id'] - - if bdm.get('image_id'): - try: - image_id = bdm['image_id'] - image_meta = image_api.get(context, image_id) - return image_meta - except Exception: - raise exception.InvalidBDMImage(id=image_id) - elif volume_id: - try: - volume = volume_api.get(context, volume_id) - except exception.CinderConnectionFailed: - raise - except Exception: - raise exception.InvalidBDMVolume(id=volume_id) - - if not volume.get('bootable', True): - raise exception.InvalidBDMVolumeNotBootable(id=volume_id) - - return get_image_metadata_from_volume(volume) - return {} - - -def get_image_metadata_from_volume(volume): - properties = copy.copy(volume.get('volume_image_metadata', {})) - image_meta = {'properties': properties} - # Volume size is no longer related to the original image size, - # so we take it from the volume directly. Cinder creates - # volumes in Gb increments, and stores size in Gb, whereas - # glance reports size in bytes. As we're returning glance - # metadata here, we need to convert it. - image_meta['size'] = volume.get('size', 0) * units.Gi - # NOTE(yjiang5): restore the basic attributes - # NOTE(mdbooth): These values come from volume_glance_metadata - # in cinder. This is a simple key/value table, and all values - # are strings. We need to convert them to ints to avoid - # unexpected type errors. - for attr in VIM_IMAGE_ATTRIBUTES: - val = properties.pop(attr, None) - if attr in ('min_ram', 'min_disk'): - image_meta[attr] = int(val or 0) - # NOTE(mriedem): Set the status to 'active' as a really old hack - # from when this method was in the compute API class and is - # needed for _validate_flavor_image which makes sure the image - # is 'active'. For volume-backed servers, if the volume is not - # available because the image backing the volume is not active, - # then the compute API trying to reserve the volume should fail. - image_meta['status'] = 'active' - return image_meta - - def get_hash_str(base_str): """Returns string that represents MD5 hash of base_str (in hex format). diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 4f67ec869710..04cc7802d379 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -3530,7 +3530,7 @@ class LibvirtDriver(driver.ComputeDriver): # NOTE(lyarwood): If instance.image_ref isn't set attempt to # lookup the original image_meta from the bdms. This will # return an empty dict if no valid image_meta is found. - image_meta_dict = utils.get_bdm_image_metadata( + image_meta_dict = block_device.get_bdm_image_metadata( context, self._image_api, self._volume_api, block_device_info['block_device_mapping'], legacy_bdm=False)