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
This commit is contained in:
Mitsuhiro Tanino 2015-12-15 20:19:35 -05:00
parent d957f90b15
commit dc12ecd1ea
3 changed files with 282 additions and 1 deletions

View 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):

View 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.

View File

@ -0,0 +1,3 @@
---
features:
- Support cinder_img_volume_type property in glance image metadata to specify volume type.