]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Support cinder_img_volume_type in image metadata
authorMitsuhiro Tanino <mitsuhiro.tanino@hds.com>
Wed, 16 Dec 2015 01:19:35 +0000 (20:19 -0500)
committerMitsuhiro Tanino <mitsuhiro.tanino@hds.com>
Sat, 23 Jan 2016 01:33:42 +0000 (20:33 -0500)
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

cinder/tests/unit/volume/flows/test_create_volume_flow.py
cinder/volume/flows/api/create_volume.py
releasenotes/notes/image-volume-type-c91b7cff3cb41c13.yaml [new file with mode: 0644]

index 973f5ade282a06c7ed98af34e792b7ddce60dd01..d4b9f03790f57db2a30401f538444db1d19b87a0 100644 (file)
@@ -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):
 
index 160e2333f0bb4f559fb0d774421d68278bff847a..d4b524292f5934328e00b62df44f9f31260032d0 100644 (file)
@@ -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 (file)
index 0000000..ee53d0a
--- /dev/null
@@ -0,0 +1,3 @@
+---
+features:
+  - Support cinder_img_volume_type property in glance image metadata to specify volume type.