utils: Move 'get_bdm_image_metadata' to nova.block_device

The 'nova.block_device' module is essentially a catchall utils-like
module for all things BDM. The 'get_bdm_image_metadata' module, and
closely related 'get_image_metadata_from_volume' both fall into the
category of functions that belong here so move them. This allows us to
clean up tests and, crucially, avoid a circular reference seen when we
want to use proper type hints in the 'nova.virt.driver' module.

  nova.context imports...
  nova.utils, which imports...
  nova.block_device, which imports...
  nova.virt.driver, which tries to import...
  nova.context, causing a circular dependency

Change-Id: I48177d6e93f2ff132d26b53cd682fd24a43a4b31
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2020-07-08 11:42:47 +01:00
parent bc784a1c1f
commit 72cf37bca0
10 changed files with 283 additions and 251 deletions

View File

@ -13,11 +13,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
import re import re
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils from oslo_utils import strutils
from oslo_utils import units
import nova.conf import nova.conf
from nova import exception from nova import exception
@ -34,6 +35,12 @@ _DEFAULT_MAPPINGS = {'ami': 'sda1',
'root': DEFAULT_ROOT_DEV_NAME, 'root': DEFAULT_ROOT_DEV_NAME,
'swap': 'sda3'} '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', bdm_legacy_fields = set(['device_name', 'delete_on_termination',
'virtual_name', 'snapshot_id', '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): def get_bdm_local_disk_num(block_device_mappings):
return len([bdm for bdm in block_device_mappings return len([bdm for bdm in block_device_mappings
if bdm.get('destination_type') == 'local']) 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

View File

@ -1432,7 +1432,7 @@ class API(base.Base):
"when booting from volume") "when booting from volume")
raise exception.CertificateValidationFailed(message=msg) raise exception.CertificateValidationFailed(message=msg)
image_id = None 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, context, self.image_api, self.volume_api, block_device_mapping,
legacy_bdm) legacy_bdm)

View File

@ -68,7 +68,7 @@ class ImageMetaPayload(base.NotificationPayloadBase):
# * created_at # * created_at
# #
# c. It cannot be got in the boot from volume case. # 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') # * id (not 'image_id')
# * visibility # * visibility

View File

@ -4754,7 +4754,7 @@ class ServersControllerCreateTest(test.TestCase):
@mock.patch('nova.compute.api.API._get_volumes_for_bdms') @mock.patch('nova.compute.api.API._get_volumes_for_bdms')
@mock.patch.object(compute_api.API, '_validate_bdm') @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( def test_create_instance_with_bdms_and_no_image(
self, mock_bdm_image_metadata, mock_validate_bdm, mock_get_vols): self, mock_bdm_image_metadata, mock_validate_bdm, mock_get_vols):
mock_bdm_image_metadata.return_value = {} 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('nova.compute.api.API._get_volumes_for_bdms')
@mock.patch.object(compute_api.API, '_validate_bdm') @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( def test_create_instance_with_bdms_and_empty_imageRef(
self, mock_bdm_image_metadata, mock_validate_bdm, mock_get_volumes): self, mock_bdm_image_metadata, mock_validate_bdm, mock_get_volumes):
mock_bdm_image_metadata.return_value = {} mock_bdm_image_metadata.return_value = {}
@ -5040,7 +5040,7 @@ class ServersControllerCreateTest(test.TestCase):
self.assertRaises(webob.exc.HTTPBadRequest, self.assertRaises(webob.exc.HTTPBadRequest,
self._test_create, params, no_image=True) 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): def test_create_instance_non_bootable_volume_fails(self, fake_bdm_meta):
params = {'block_device_mapping_v2': self.bdm_v2} params = {'block_device_mapping_v2': self.bdm_v2}
fake_bdm_meta.side_effect = exception.InvalidBDMVolumeNotBootable(id=1) 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.stub_out('nova.compute.api.API.create', create)
self._test_create_bdm(params) 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( def test_create_instance_with_volumes_enabled_and_bdms_no_image(
self, mock_get_bdm_image_metadata): self, mock_get_bdm_image_metadata):
"""Test that the create works if there is no image supplied but """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_get_bdm_image_metadata.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY, self.bdm, True) 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( def test_create_instance_with_imageRef_as_empty_string(
self, mock_bdm_image_metadata): self, mock_bdm_image_metadata):
volume = { volume = {
@ -5180,7 +5180,7 @@ class ServersControllerCreateTest(test.TestCase):
self.assertRaises(exception.ValidationError, self.assertRaises(exception.ValidationError,
self._test_create_bdm, params) 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( def test_create_instance_non_bootable_volume_fails_legacy_bdm(
self, fake_bdm_meta): self, fake_bdm_meta):
bdm = [{ bdm = [{

View File

@ -684,7 +684,7 @@ class ComputeVolumeTestCase(BaseTestCase):
'delete_on_termination': False, '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.context, self.compute_api.image_api,
self.compute_api.volume_api, block_device_mapping) self.compute_api.volume_api, block_device_mapping)
if metadata: if metadata:
@ -705,7 +705,7 @@ class ComputeVolumeTestCase(BaseTestCase):
'delete_on_termination': False, '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.context, self.compute_api.image_api,
self.compute_api.volume_api, block_device_mapping, self.compute_api.volume_api, block_device_mapping,
legacy_bdm=False) legacy_bdm=False)
@ -740,7 +740,7 @@ class ComputeVolumeTestCase(BaseTestCase):
'delete_on_termination': True, '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.context, self.compute_api.image_api,
self.compute_api.volume_api, block_device_mapping, self.compute_api.volume_api, block_device_mapping,
legacy_bdm=False) legacy_bdm=False)

View File

@ -3491,95 +3491,6 @@ class _ComputeAPIUnitTestMixIn(object):
self.context, mock.sentinel.volume_id, self.context, mock.sentinel.volume_id,
mock.sentinel.snapshot_id, mock.sentinel.delete_info) 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): def _create_instance_with_disabled_disk_config(self, object=False):
sys_meta = {"image_auto_disk_config": "Disabled"} sys_meta = {"image_auto_disk_config": "Disabled"}
params = {"system_metadata": sys_meta} params = {"system_metadata": sys_meta}
@ -4430,25 +4341,6 @@ class _ComputeAPIUnitTestMixIn(object):
self.context, instance, self.context, instance,
volume_id, new_volume_id) 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): def test_get_volumes_for_bdms_errors(self):
"""Simple test to make sure _get_volumes_for_bdms raises up errors.""" """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 # Use a mix of pre-existing and source_type=image volumes to test the

View File

@ -16,15 +16,21 @@
""" """
Tests for Block Device utility functions. Tests for Block Device utility functions.
""" """
import mock
from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import units
import six import six
from nova import block_device from nova import block_device
from nova.compute import api as compute_api
from nova import context
from nova import exception from nova import exception
from nova import objects from nova import objects
from nova import test from nova import test
from nova.tests.unit import fake_block_device from nova.tests.unit import fake_block_device
from nova.tests.unit import matchers from nova.tests.unit import matchers
from nova.volume import cinder
class BlockDeviceTestCase(test.NoDBTestCase): class BlockDeviceTestCase(test.NoDBTestCase):
@ -723,3 +729,177 @@ class TestBlockDeviceDict(test.NoDBTestCase):
None, obj, fake_block_device.FakeDbBlockDeviceDict( None, obj, fake_block_device.FakeDbBlockDeviceDict(
bdm)) bdm))
self._test_snapshot_from_bdm(obj) 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)

View File

@ -31,7 +31,6 @@ from oslo_context import context as common_context
from oslo_context import fixture as context_fixture from oslo_context import fixture as context_fixture
from oslo_utils import encodeutils from oslo_utils import encodeutils
from oslo_utils import fixture as utils_fixture from oslo_utils import fixture as utils_fixture
from oslo_utils import units
import six import six
from nova import context from nova import context
@ -737,43 +736,6 @@ class GetImageFromSystemMetadataTestCase(test.NoDBTestCase):
self.assertNotIn(key, image) 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): class SafeTruncateTestCase(test.NoDBTestCase):
def test_exception_to_dict_with_long_message_3_bytes(self): def test_exception_to_dict_with_long_message_3_bytes(self):
# Generate Chinese byte string whose length is 300. This Chinese UTF-8 # Generate Chinese byte string whose length is 300. This Chinese UTF-8

View File

@ -18,7 +18,6 @@
"""Utilities and helper functions.""" """Utilities and helper functions."""
import contextlib import contextlib
import copy
import datetime import datetime
import functools import functools
import hashlib import hashlib
@ -46,11 +45,9 @@ from oslo_utils import excutils
from oslo_utils import importutils from oslo_utils import importutils
from oslo_utils import strutils from oslo_utils import strutils
from oslo_utils import timeutils from oslo_utils import timeutils
from oslo_utils import units
import six import six
from six.moves import range from six.moves import range
from nova import block_device
import nova.conf import nova.conf
from nova import exception from nova import exception
from nova.i18n import _ from nova.i18n import _
@ -78,12 +75,6 @@ SM_SKIP_KEYS = (
# Modern names # Modern names
'img_mappings', 'img_block_device_mapping', '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 = {} _FILE_CACHE = {}
@ -788,89 +779,6 @@ def get_image_from_system_metadata(system_meta):
return image_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): def get_hash_str(base_str):
"""Returns string that represents MD5 hash of base_str (in hex format). """Returns string that represents MD5 hash of base_str (in hex format).

View File

@ -3530,7 +3530,7 @@ class LibvirtDriver(driver.ComputeDriver):
# NOTE(lyarwood): If instance.image_ref isn't set attempt to # NOTE(lyarwood): If instance.image_ref isn't set attempt to
# lookup the original image_meta from the bdms. This will # lookup the original image_meta from the bdms. This will
# return an empty dict if no valid image_meta is found. # 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, context, self._image_api, self._volume_api,
block_device_info['block_device_mapping'], block_device_info['block_device_mapping'],
legacy_bdm=False) legacy_bdm=False)