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:
parent
bc784a1c1f
commit
72cf37bca0
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 = [{
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user