Support Cinder API version 2
Add support for the second version of the Cinder API, which brings useful features such as scheduler hints, more consistent responses, caching, filtering, etc. Available volume services (such as 'volume' and 'volumev2') are discovered at run time when creating the Cinder client. Consequently, there is not need for deployers to make a choice between API version 1 or 2, because the newest service is automatically chosen. Implements: blueprint support-cinder-api-v2 Change-Id: If9eedd446e017a2f2a4e1b3257ff0d04834f9603
This commit is contained in:
parent
ec0a6720ab
commit
ac0e68e865
@ -11,22 +11,57 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from cinderclient import client as cc
|
||||
from cinderclient import exceptions
|
||||
|
||||
from heat.engine.clients import client_plugin
|
||||
from heat.common import exception
|
||||
from heat.engine import clients
|
||||
from heat.openstack.common.gettextutils import _
|
||||
|
||||
|
||||
class CinderClientPlugin(client_plugin.ClientPlugin):
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CinderClientPlugin(clients.client_plugin.ClientPlugin):
|
||||
|
||||
exceptions_module = exceptions
|
||||
|
||||
def get_volume_api_version(self):
|
||||
'''Returns the most recent API version.'''
|
||||
|
||||
endpoint_type = self._get_client_option('cinder', 'endpoint_type')
|
||||
try:
|
||||
self.url_for(service_type='volumev2', endpoint_type=endpoint_type)
|
||||
return 2
|
||||
except exceptions.EndpointNotFound:
|
||||
try:
|
||||
self.url_for(service_type='volume',
|
||||
endpoint_type=endpoint_type)
|
||||
return 1
|
||||
except exceptions.EndpointNotFound:
|
||||
return None
|
||||
|
||||
def _create(self):
|
||||
|
||||
con = self.context
|
||||
|
||||
volume_api_version = self.get_volume_api_version()
|
||||
if volume_api_version == 1:
|
||||
service_type = 'volume'
|
||||
client_version = '1'
|
||||
elif volume_api_version == 2:
|
||||
service_type = 'volumev2'
|
||||
client_version = '2'
|
||||
else:
|
||||
raise exception.Error(_('No volume service available.'))
|
||||
LOG.info(_('Creating Cinder client with volume API version %d.'),
|
||||
volume_api_version)
|
||||
|
||||
endpoint_type = self._get_client_option('cinder', 'endpoint_type')
|
||||
args = {
|
||||
'service_type': 'volume',
|
||||
'service_type': service_type,
|
||||
'auth_url': con.auth_url or '',
|
||||
'project_id': con.tenant,
|
||||
'username': None,
|
||||
@ -38,12 +73,14 @@ class CinderClientPlugin(client_plugin.ClientPlugin):
|
||||
'insecure': self._get_client_option('cinder', 'insecure')
|
||||
}
|
||||
|
||||
client = cc.Client('1', **args)
|
||||
management_url = self.url_for(service_type='volume',
|
||||
client = cc.Client(client_version, **args)
|
||||
management_url = self.url_for(service_type=service_type,
|
||||
endpoint_type=endpoint_type)
|
||||
client.client.auth_token = self.auth_token
|
||||
client.client.management_url = management_url
|
||||
|
||||
client.volume_api_version = volume_api_version
|
||||
|
||||
return client
|
||||
|
||||
def is_not_found(self, ex):
|
||||
|
@ -84,10 +84,10 @@ class Volume(resource.Resource):
|
||||
|
||||
default_client_name = 'cinder'
|
||||
|
||||
def _display_name(self):
|
||||
def _name(self):
|
||||
return self.physical_resource_name()
|
||||
|
||||
def _display_description(self):
|
||||
def _description(self):
|
||||
return self.physical_resource_name()
|
||||
|
||||
def _create_arguments(self):
|
||||
@ -111,14 +111,22 @@ class Volume(resource.Resource):
|
||||
vol_id = cinder.restores.restore(backup_id).volume_id
|
||||
|
||||
vol = cinder.volumes.get(vol_id)
|
||||
vol.update(
|
||||
display_name=self._display_name(),
|
||||
display_description=self._display_description())
|
||||
if cinder.volume_api_version == 1:
|
||||
kwargs = {'display_name': self._name(),
|
||||
'display_description': self._description()}
|
||||
else:
|
||||
vol = cinder.volumes.create(
|
||||
display_name=self._display_name(),
|
||||
display_description=self._display_description(),
|
||||
**self._create_arguments())
|
||||
kwargs = {'name': self._name(),
|
||||
'description': self._description()}
|
||||
vol.update(**kwargs)
|
||||
else:
|
||||
kwargs = self._create_arguments()
|
||||
if cinder.volume_api_version == 1:
|
||||
kwargs['display_name'] = self._name()
|
||||
kwargs['display_description'] = self._description()
|
||||
else:
|
||||
kwargs['name'] = self._name()
|
||||
kwargs['description'] = self._description()
|
||||
vol = cinder.volumes.create(**kwargs)
|
||||
self.resource_id_set(vol.id)
|
||||
|
||||
return vol
|
||||
@ -455,8 +463,8 @@ class CinderVolume(Volume):
|
||||
)
|
||||
|
||||
ATTRIBUTES = (
|
||||
AVAILABILITY_ZONE_ATTR, SIZE_ATTR, SNAPSHOT_ID_ATTR, DISPLAY_NAME,
|
||||
DISPLAY_DESCRIPTION, VOLUME_TYPE_ATTR, METADATA_ATTR,
|
||||
AVAILABILITY_ZONE_ATTR, SIZE_ATTR, SNAPSHOT_ID_ATTR, DISPLAY_NAME_ATTR,
|
||||
DISPLAY_DESCRIPTION_ATTR, VOLUME_TYPE_ATTR, METADATA_ATTR,
|
||||
SOURCE_VOLID_ATTR, STATUS, CREATED_AT, BOOTABLE, METADATA_VALUES_ATTR,
|
||||
ENCRYPTED_ATTR, ATTACHMENTS,
|
||||
) = (
|
||||
@ -539,10 +547,10 @@ class CinderVolume(Volume):
|
||||
SNAPSHOT_ID_ATTR: attributes.Schema(
|
||||
_('The snapshot the volume was created from, if any.')
|
||||
),
|
||||
DISPLAY_NAME: attributes.Schema(
|
||||
DISPLAY_NAME_ATTR: attributes.Schema(
|
||||
_('Name of the volume.')
|
||||
),
|
||||
DISPLAY_DESCRIPTION: attributes.Schema(
|
||||
DISPLAY_DESCRIPTION_ATTR: attributes.Schema(
|
||||
_('Description of the volume.')
|
||||
),
|
||||
VOLUME_TYPE_ATTR: attributes.Schema(
|
||||
@ -576,13 +584,13 @@ class CinderVolume(Volume):
|
||||
|
||||
_volume_creating_status = ['creating', 'restoring-backup', 'downloading']
|
||||
|
||||
def _display_name(self):
|
||||
def _name(self):
|
||||
name = self.properties[self.NAME]
|
||||
if name:
|
||||
return name
|
||||
return super(CinderVolume, self)._display_name()
|
||||
return super(CinderVolume, self)._name()
|
||||
|
||||
def _display_description(self):
|
||||
def _description(self):
|
||||
return self.properties[self.DESCRIPTION]
|
||||
|
||||
def _create_arguments(self):
|
||||
@ -608,6 +616,11 @@ class CinderVolume(Volume):
|
||||
return unicode(json.dumps(vol.metadata))
|
||||
elif name == self.METADATA_VALUES_ATTR:
|
||||
return vol.metadata
|
||||
if self.cinder().volume_api_version >= 2:
|
||||
if name == 'display_name':
|
||||
return vol.name
|
||||
elif name == 'display_description':
|
||||
return vol.description
|
||||
return unicode(getattr(vol, name))
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
@ -621,8 +634,12 @@ class CinderVolume(Volume):
|
||||
self.properties.get(self.NAME))
|
||||
update_description = (prop_diff.get(self.DESCRIPTION) or
|
||||
self.properties.get(self.DESCRIPTION))
|
||||
if self.cinder().volume_api_version == 1:
|
||||
kwargs['display_name'] = update_name
|
||||
kwargs['display_description'] = update_description
|
||||
else:
|
||||
kwargs['name'] = update_name
|
||||
kwargs['description'] = update_description
|
||||
self.cinder().volumes.update(vol, **kwargs)
|
||||
# update the metadata for cinder volume
|
||||
if self.METADATA in prop_diff:
|
||||
|
@ -19,6 +19,8 @@ wrong the tests might raise AssertionError. I've indicated in comments the
|
||||
places where actual behavior differs from the spec.
|
||||
"""
|
||||
|
||||
from cinderclient import exceptions as cinder_exceptions
|
||||
|
||||
from heat.common import context
|
||||
|
||||
|
||||
@ -78,13 +80,15 @@ class FakeClient(object):
|
||||
class FakeKeystoneClient(object):
|
||||
def __init__(self, username='test_user', password='apassword',
|
||||
user_id='1234', access='4567', secret='8901',
|
||||
credential_id='abcdxyz', auth_token='abcd1234'):
|
||||
credential_id='abcdxyz', auth_token='abcd1234',
|
||||
only_services=None):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.user_id = user_id
|
||||
self.access = access
|
||||
self.secret = secret
|
||||
self.credential_id = credential_id
|
||||
self.only_services = only_services
|
||||
|
||||
class FakeCred(object):
|
||||
id = self.credential_id
|
||||
@ -128,6 +132,10 @@ class FakeKeystoneClient(object):
|
||||
pass
|
||||
|
||||
def url_for(self, **kwargs):
|
||||
if self.only_services is not None:
|
||||
if 'service_type' in kwargs and \
|
||||
kwargs['service_type'] not in self.only_services:
|
||||
raise cinder_exceptions.EndpointNotFound()
|
||||
return 'http://example.com:1234/v1'
|
||||
|
||||
def create_trust_context(self):
|
||||
|
@ -29,6 +29,7 @@ from testtools.testcase import skip
|
||||
from heat.engine import clients
|
||||
from heat.engine.clients import client_plugin
|
||||
from heat.tests.common import HeatTestCase
|
||||
from heat.tests import utils
|
||||
from heat.tests.v1_1 import fakes
|
||||
|
||||
|
||||
@ -527,3 +528,24 @@ class TestIsNotFound(HeatTestCase):
|
||||
iue = self.is_unprocessable_entity
|
||||
if iue != client_plugin.is_unprocessable_entity(e):
|
||||
raise
|
||||
|
||||
|
||||
class ClientAPIVersionTest(HeatTestCase):
|
||||
|
||||
def test_cinder_api_v1_and_v2(self):
|
||||
self.stub_keystoneclient()
|
||||
ctx = utils.dummy_context()
|
||||
client = clients.Clients(ctx).client('cinder')
|
||||
self.assertEqual(2, client.volume_api_version)
|
||||
|
||||
def test_cinder_api_v1_only(self):
|
||||
self.stub_keystoneclient(only_services=['volume'])
|
||||
ctx = utils.dummy_context()
|
||||
client = clients.Clients(ctx).client('cinder')
|
||||
self.assertEqual(1, client.volume_api_version)
|
||||
|
||||
def test_cinder_api_v2_only(self):
|
||||
self.stub_keystoneclient(only_services=['volumev2'])
|
||||
ctx = utils.dummy_context()
|
||||
client = clients.Clients(ctx).client('cinder')
|
||||
self.assertEqual(2, client.volume_api_version)
|
||||
|
@ -15,7 +15,7 @@ import copy
|
||||
import json
|
||||
|
||||
from cinderclient import exceptions as cinder_exp
|
||||
from cinderclient.v1 import client as cinderclient
|
||||
from cinderclient.v2 import client as cinderclient
|
||||
import mox
|
||||
from oslo.config import cfg
|
||||
import six
|
||||
@ -103,6 +103,7 @@ class BaseVolumeTest(HeatTestCase):
|
||||
super(BaseVolumeTest, self).setUp()
|
||||
self.fc = fakes.FakeClient()
|
||||
self.cinder_fc = cinderclient.Client('username', 'password')
|
||||
self.cinder_fc.volume_api_version = 2
|
||||
self.m.StubOutWithMock(cinder.CinderClientPlugin, '_create')
|
||||
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
|
||||
self.m.StubOutWithMock(self.cinder_fc.volumes, 'create')
|
||||
@ -178,8 +179,8 @@ class VolumeTest(BaseVolumeTest):
|
||||
vol_name = utils.PhysName(stack_name, 'DataVolume')
|
||||
self.cinder_fc.volumes.create(
|
||||
size=1, availability_zone='nova',
|
||||
display_description=vol_name,
|
||||
display_name=vol_name,
|
||||
description=vol_name,
|
||||
name=vol_name,
|
||||
metadata={u'Usage': u'Wiki Data Volume'}).AndReturn(fv)
|
||||
|
||||
def test_volume(self):
|
||||
@ -233,8 +234,8 @@ class VolumeTest(BaseVolumeTest):
|
||||
vol_name = utils.PhysName(stack_name, 'DataVolume')
|
||||
self.cinder_fc.volumes.create(
|
||||
size=1, availability_zone=None,
|
||||
display_description=vol_name,
|
||||
display_name=vol_name,
|
||||
description=vol_name,
|
||||
name=vol_name,
|
||||
metadata={u'Usage': u'Wiki Data Volume'}).AndReturn(fv)
|
||||
vol.VolumeAttachment.handle_create().AndReturn(None)
|
||||
vol.VolumeAttachment.check_create_complete(None).AndReturn(True)
|
||||
@ -609,9 +610,7 @@ class VolumeTest(BaseVolumeTest):
|
||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fv)
|
||||
self.m.StubOutWithMock(fv, 'update')
|
||||
vol_name = utils.PhysName(stack_name, 'DataVolume')
|
||||
fv.update(
|
||||
display_description=vol_name,
|
||||
display_name=vol_name)
|
||||
fv.update(description=vol_name, name=vol_name)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
@ -638,9 +637,7 @@ class VolumeTest(BaseVolumeTest):
|
||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fv)
|
||||
self.m.StubOutWithMock(fv, 'update')
|
||||
vol_name = utils.PhysName(stack_name, 'DataVolume')
|
||||
fv.update(
|
||||
display_description=vol_name,
|
||||
display_name=vol_name)
|
||||
fv.update(description=vol_name, name=vol_name)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
@ -708,8 +705,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
self.cinder_fc)
|
||||
self.cinder_fc.volumes.create(
|
||||
size=size, availability_zone='nova',
|
||||
display_description='test_description',
|
||||
display_name='test_name',
|
||||
description='test_description',
|
||||
name='test_name',
|
||||
metadata={'key': 'value'}).AndReturn(fv)
|
||||
|
||||
def test_cinder_volume_size_constraint(self):
|
||||
@ -730,8 +727,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
self.cinder_fc)
|
||||
self.cinder_fc.volumes.create(
|
||||
size=1, availability_zone='nova',
|
||||
display_description='test_description',
|
||||
display_name='test_name',
|
||||
description='test_description',
|
||||
name='test_name',
|
||||
imageRef='46988116-6703-4623-9dbc-2bc6d284021b',
|
||||
snapshot_id='snap-123',
|
||||
metadata={'key': 'value'},
|
||||
@ -767,8 +764,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
|
||||
self.cinder_fc.volumes.create(
|
||||
size=1, availability_zone='nova',
|
||||
display_description='ImageVolumeDescription',
|
||||
display_name='ImageVolume',
|
||||
description='ImageVolumeDescription',
|
||||
name='ImageVolume',
|
||||
imageRef=image_id).AndReturn(fv)
|
||||
|
||||
self.m.ReplayAll()
|
||||
@ -795,8 +792,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
vol_name = utils.PhysName(stack_name, 'volume')
|
||||
self.cinder_fc.volumes.create(
|
||||
size=1, availability_zone='nova',
|
||||
display_description=None,
|
||||
display_name=vol_name).AndReturn(fv)
|
||||
description=None,
|
||||
name=vol_name).AndReturn(fv)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
@ -812,8 +809,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
|
||||
def test_cinder_fn_getatt(self):
|
||||
fv = FakeVolume('creating', 'available', availability_zone='zone1',
|
||||
size=1, snapshot_id='snap-123', display_name='name',
|
||||
display_description='desc', volume_type='lvm',
|
||||
size=1, snapshot_id='snap-123', name='name',
|
||||
description='desc', volume_type='lvm',
|
||||
metadata={'key': 'value'}, source_volid=None,
|
||||
status='available', bootable=False,
|
||||
created_at='2013-02-25T02:40:21.000000',
|
||||
@ -1081,9 +1078,7 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fv)
|
||||
self.m.StubOutWithMock(fv, 'update')
|
||||
vol_name = utils.PhysName(stack_name, 'volume')
|
||||
fv.update(
|
||||
display_description=None,
|
||||
display_name=vol_name)
|
||||
fv.update(description=None, name=vol_name)
|
||||
|
||||
# update script
|
||||
self.cinder_fc.volumes.get(fv.id).AndReturn(fv)
|
||||
@ -1118,8 +1113,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
meta = {'Key': 'New Value'}
|
||||
update_description = 'update_description'
|
||||
kwargs = {
|
||||
'display_name': update_name,
|
||||
'display_description': update_description
|
||||
'name': update_name,
|
||||
'description': update_description
|
||||
}
|
||||
|
||||
self._mock_create_volume(fv, stack_name)
|
||||
@ -1149,8 +1144,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
cinder.CinderClientPlugin._create().MultipleTimes().AndReturn(
|
||||
self.cinder_fc)
|
||||
self.cinder_fc.volumes.create(
|
||||
size=1, availability_zone='nova', display_name='CustomName',
|
||||
display_description='CustomDescription').AndReturn(fv)
|
||||
size=1, availability_zone='nova', name='CustomName',
|
||||
description='CustomDescription').AndReturn(fv)
|
||||
|
||||
self.m.StubOutWithMock(self.cinder_fc.backups, 'create')
|
||||
self.cinder_fc.backups.create('vol-123').AndReturn(fb)
|
||||
@ -1189,8 +1184,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
cinder.CinderClientPlugin._create().MultipleTimes().AndReturn(
|
||||
self.cinder_fc)
|
||||
self.cinder_fc.volumes.create(
|
||||
size=1, availability_zone='nova', display_name='CustomName',
|
||||
display_description='CustomDescription').AndReturn(fv)
|
||||
size=1, availability_zone='nova', name='CustomName',
|
||||
description='CustomDescription').AndReturn(fv)
|
||||
|
||||
self.m.StubOutWithMock(self.cinder_fc.backups, 'create')
|
||||
self.cinder_fc.backups.create('vol-123').AndReturn(fb)
|
||||
@ -1280,8 +1275,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
||||
vol2_name = utils.PhysName(stack_name, 'volume2')
|
||||
self.cinder_fc.volumes.create(
|
||||
size=2, availability_zone='nova',
|
||||
display_description=None,
|
||||
display_name=vol2_name).AndReturn(fv2)
|
||||
description=None,
|
||||
name=vol2_name).AndReturn(fv2)
|
||||
|
||||
self._mock_create_server_volume_script(fva)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user