From dc12ecd1ea7ab5fe6f90e4479d4e5727ff64e16c Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanino Date: Tue, 15 Dec 2015 20:19:35 -0500 Subject: [PATCH] Support cinder_img_volume_type in image metadata This patch adds a feature to specify Cinder volume type via cinder_img_volume_type parameter in glance image metadata. In some cases, between image and hypervisor, storage backend are tightly connected in each other. For example, if users want to boot a VMware instance from an image, this image has to be configured some VMware specific parameters in previous, and then Nova can deploy an instance on to proper hypervisor based on the image metadata properties. Currently, Cinder handles few image metadata properties but volume type is not included. If volume type can be retrieved from image metadata which is configured by cloud admin, user doesn't need to care volume type and also storage backends. And then appropriate volume type will be chosen automatically based on the information of cinder_img_volume_type in the image metadata during volume creation. If user has enough knowledge about image, hypervisor and storage backend, user also can specify 'volume_type' via CLI or API as in the past. Priority of volume type related parameters are shown below. 1. volume_type (via API or CLI) 2. cinder_img_volume_type (via glance image metadata) 3. default_volume_type (via cinder.conf) DocImpact: Add usage of this parameter to 'Manage volumes' section in Cloud Administrator Guide Change-Id: I62f02d817d84d3a7b651db36d7297299b1af2fe3 --- .../volume/flows/test_create_volume_flow.py | 233 ++++++++++++++++++ cinder/volume/flows/api/create_volume.py | 47 +++- .../image-volume-type-c91b7cff3cb41c13.yaml | 3 + 3 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/image-volume-type-c91b7cff3cb41c13.yaml diff --git a/cinder/tests/unit/volume/flows/test_create_volume_flow.py b/cinder/tests/unit/volume/flows/test_create_volume_flow.py index 973f5ade2..d4b9f0379 100644 --- a/cinder/tests/unit/volume/flows/test_create_volume_flow.py +++ b/cinder/tests/unit/volume/flows/test_create_volume_flow.py @@ -14,6 +14,7 @@ # under the License. """ Tests for create_volume TaskFlow """ +import ddt import mock from oslo_config import cfg @@ -35,6 +36,7 @@ from cinder.volume.flows.manager import create_volume as create_volume_manager CONF = cfg.CONF +@ddt.ddt class CreateVolumeFlowTestCase(test.TestCase): def time_inc(self): @@ -341,6 +343,237 @@ class CreateVolumeFlowTestCase(test.TestCase): 'cgsnapshot_id': None, } self.assertEqual(expected_result, result) + @mock.patch('cinder.volume.volume_types.is_encrypted') + @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs') + @mock.patch('cinder.volume.volume_types.get_default_volume_type') + @mock.patch('cinder.volume.volume_types.get_volume_type_by_name') + @mock.patch('cinder.volume.flows.api.create_volume.' + 'ExtractVolumeRequestTask.' + '_get_volume_type_id') + def test_extract_image_volume_type_from_image( + self, + fake_get_type_id, + fake_get_vol_type, + fake_get_def_vol_type, + fake_get_qos, + fake_is_encrypted): + + image_volume_type = 'type_from_image' + fake_image_service = fake_image.FakeImageService() + image_id = 6 + image_meta = {} + image_meta['id'] = image_id + image_meta['status'] = 'active' + image_meta['size'] = 1 + image_meta['properties'] = {} + image_meta['properties']['cinder_img_volume_type'] = image_volume_type + fake_image_service.create(self.ctxt, image_meta) + fake_key_manager = mock_key_mgr.MockKeyManager() + + task = create_volume.ExtractVolumeRequestTask( + fake_image_service, + {'nova'}) + + fake_is_encrypted.return_value = False + fake_get_type_id.return_value = 1 + fake_get_vol_type.return_value = image_volume_type + fake_get_def_vol_type.return_value = 'fake_vol_type' + fake_get_qos.return_value = {'qos_specs': None} + result = task.execute(self.ctxt, + size=1, + snapshot=None, + image_id=image_id, + source_volume=None, + availability_zone='nova', + volume_type=None, + metadata=None, + key_manager=fake_key_manager, + source_replica=None, + consistencygroup=None, + cgsnapshot=None) + expected_result = {'size': 1, + 'snapshot_id': None, + 'source_volid': None, + 'availability_zone': 'nova', + 'volume_type': image_volume_type, + 'volume_type_id': 1, + 'encryption_key_id': None, + 'qos_specs': None, + 'source_replicaid': None, + 'consistencygroup_id': None, + 'cgsnapshot_id': None, } + self.assertEqual(expected_result, result) + + @mock.patch('cinder.db.volume_type_get_by_name') + @mock.patch('cinder.volume.volume_types.is_encrypted') + @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs') + @mock.patch('cinder.volume.volume_types.get_default_volume_type') + @mock.patch('cinder.volume.flows.api.create_volume.' + 'ExtractVolumeRequestTask.' + '_get_volume_type_id') + def test_extract_image_volume_type_from_image_invalid_type( + self, + fake_get_type_id, + fake_get_def_vol_type, + fake_get_qos, + fake_is_encrypted, + fake_db_get_vol_type): + + image_volume_type = 'invalid' + fake_image_service = fake_image.FakeImageService() + image_id = 7 + image_meta = {} + image_meta['id'] = image_id + image_meta['status'] = 'active' + image_meta['size'] = 1 + image_meta['properties'] = {} + image_meta['properties']['cinder_img_volume_type'] = image_volume_type + fake_image_service.create(self.ctxt, image_meta) + fake_key_manager = mock_key_mgr.MockKeyManager() + + task = create_volume.ExtractVolumeRequestTask( + fake_image_service, + {'nova'}) + + fake_is_encrypted.return_value = False + fake_get_type_id.return_value = 1 + fake_get_def_vol_type.return_value = 'fake_vol_type' + fake_db_get_vol_type.side_effect = ( + exception.VolumeTypeNotFoundByName(volume_type_name='invalid')) + fake_get_qos.return_value = {'qos_specs': None} + result = task.execute(self.ctxt, + size=1, + snapshot=None, + image_id=image_id, + source_volume=None, + availability_zone='nova', + volume_type=None, + metadata=None, + key_manager=fake_key_manager, + source_replica=None, + consistencygroup=None, + cgsnapshot=None) + expected_result = {'size': 1, + 'snapshot_id': None, + 'source_volid': None, + 'availability_zone': 'nova', + 'volume_type': 'fake_vol_type', + 'volume_type_id': 1, + 'encryption_key_id': None, + 'qos_specs': None, + 'source_replicaid': None, + 'consistencygroup_id': None, + 'cgsnapshot_id': None, } + self.assertEqual(expected_result, result) + + @mock.patch('cinder.db.volume_type_get_by_name') + @mock.patch('cinder.volume.volume_types.is_encrypted') + @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs') + @mock.patch('cinder.volume.volume_types.get_default_volume_type') + @mock.patch('cinder.volume.flows.api.create_volume.' + 'ExtractVolumeRequestTask.' + '_get_volume_type_id') + @ddt.data((8, None), (9, {'cinder_img_volume_type': None})) + @ddt.unpack + def test_extract_image_volume_type_from_image_properties_error( + self, + image_id, + fake_img_properties, + fake_get_type_id, + fake_get_def_vol_type, + fake_get_qos, + fake_is_encrypted, + fake_db_get_vol_type): + + fake_image_service = fake_image.FakeImageService() + image_meta = {} + image_meta['id'] = image_id + image_meta['status'] = 'active' + image_meta['size'] = 1 + image_meta['properties'] = fake_img_properties + fake_image_service.create(self.ctxt, image_meta) + fake_key_manager = mock_key_mgr.MockKeyManager() + + task = create_volume.ExtractVolumeRequestTask( + fake_image_service, + {'nova'}) + + fake_is_encrypted.return_value = False + fake_get_type_id.return_value = 1 + fake_get_def_vol_type.return_value = 'fake_vol_type' + fake_get_qos.return_value = {'qos_specs': None} + result = task.execute(self.ctxt, + size=1, + snapshot=None, + image_id=image_id, + source_volume=None, + availability_zone='nova', + volume_type=None, + metadata=None, + key_manager=fake_key_manager, + source_replica=None, + consistencygroup=None, + cgsnapshot=None) + expected_result = {'size': 1, + 'snapshot_id': None, + 'source_volid': None, + 'availability_zone': 'nova', + 'volume_type': 'fake_vol_type', + 'volume_type_id': 1, + 'encryption_key_id': None, + 'qos_specs': None, + 'source_replicaid': None, + 'consistencygroup_id': None, + 'cgsnapshot_id': None, } + self.assertEqual(expected_result, result) + + @mock.patch('cinder.db.volume_type_get_by_name') + @mock.patch('cinder.volume.volume_types.is_encrypted') + @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs') + @mock.patch('cinder.volume.volume_types.get_default_volume_type') + @mock.patch('cinder.volume.flows.api.create_volume.' + 'ExtractVolumeRequestTask.' + '_get_volume_type_id') + def test_extract_image_volume_type_from_image_invalid_input( + self, + fake_get_type_id, + fake_get_def_vol_type, + fake_get_qos, + fake_is_encrypted, + fake_db_get_vol_type): + + fake_image_service = fake_image.FakeImageService() + image_id = 10 + image_meta = {} + image_meta['id'] = image_id + image_meta['status'] = 'inactive' + fake_image_service.create(self.ctxt, image_meta) + fake_key_manager = mock_key_mgr.MockKeyManager() + + task = create_volume.ExtractVolumeRequestTask( + fake_image_service, + {'nova'}) + + fake_is_encrypted.return_value = False + fake_get_type_id.return_value = 1 + fake_get_def_vol_type.return_value = 'fake_vol_type' + fake_get_qos.return_value = {'qos_specs': None} + + self.assertRaises(exception.InvalidInput, + task.execute, + self.ctxt, + size=1, + snapshot=None, + image_id=image_id, + source_volume=None, + availability_zone='nova', + volume_type=None, + metadata=None, + key_manager=fake_key_manager, + source_replica=None, + consistencygroup=None, + cgsnapshot=None) + class CreateVolumeFlowManagerTestCase(test.TestCase): diff --git a/cinder/volume/flows/api/create_volume.py b/cinder/volume/flows/api/create_volume.py index 160e2333f..d4b524292 100644 --- a/cinder/volume/flows/api/create_volume.py +++ b/cinder/volume/flows/api/create_volume.py @@ -223,6 +223,49 @@ class ExtractVolumeRequestTask(flow_utils.CinderTask): msg = msg % {'volume_size': size, 'min_disk': min_disk} raise exception.InvalidInput(reason=msg) + def _get_image_volume_type(self, context, image_id): + """Get cinder_img_volume_type property from the image metadata.""" + + # Check image existence + if image_id is None: + return None + + image_meta = self.image_service.show(context, image_id) + + # check whether image is active + if image_meta['status'] != 'active': + msg = (_('Image %(image_id)s is not active.') % + {'image_id': image_id}) + raise exception.InvalidInput(reason=msg) + + # Retrieve 'cinder_img_volume_type' property from glance image + # metadata. + image_volume_type = "cinder_img_volume_type" + properties = image_meta.get('properties') + if properties: + try: + img_vol_type = properties.get(image_volume_type) + if img_vol_type is None: + return None + volume_type = volume_types.get_volume_type_by_name( + context, + img_vol_type) + except exception.VolumeTypeNotFoundByName: + LOG.warning(_LW("Failed to retrieve volume_type from image " + "metadata. '%(img_vol_type)s' doesn't match " + "any volume types."), + {'img_vol_type': img_vol_type}) + return None + + LOG.debug("Retrieved volume_type from glance image metadata. " + "image_id: %(image_id)s, " + "image property: %(image_volume_type)s, " + "volume_type: %(volume_type)s." % + {'image_id': image_id, + 'image_volume_type': image_volume_type, + 'volume_type': volume_type}) + return volume_type + @staticmethod def _check_metadata_properties(metadata=None): """Checks that the volume metadata properties are valid.""" @@ -384,7 +427,9 @@ class ExtractVolumeRequestTask(flow_utils.CinderTask): # This strategy avoids any dependency upon the encrypted volume type. def_vol_type = volume_types.get_default_volume_type() if not volume_type and not source_volume and not snapshot: - volume_type = def_vol_type + image_volume_type = self._get_image_volume_type(context, image_id) + volume_type = (image_volume_type if image_volume_type else + def_vol_type) # When creating a clone of a replica (replication test), we can't # use the volume type of the replica, therefore, we use the default. diff --git a/releasenotes/notes/image-volume-type-c91b7cff3cb41c13.yaml b/releasenotes/notes/image-volume-type-c91b7cff3cb41c13.yaml new file mode 100644 index 000000000..ee53d0a86 --- /dev/null +++ b/releasenotes/notes/image-volume-type-c91b7cff3cb41c13.yaml @@ -0,0 +1,3 @@ +--- +features: + - Support cinder_img_volume_type property in glance image metadata to specify volume type. -- 2.45.2