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 973f5ade282..d4b9f03790f 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 160e2333f0b..d4b524292f5 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 00000000000..ee53d0a860b --- /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.