Quota API is now compatible with keystone API v2

Before, the quota API used to fail to perform calls with specified
keystone API v2, because the keystone client used in code was set
to v3 and all quota operations used to assume that the user would
use only keystone API v3.
Now, we use the generic client, which discovers the version of the
keystone API, so depending on that, the quota API can perform
appropriate operations.

Change-Id: I32595a37a9fe74ede77c92f76e0865f4c9371f65
Closes-Bug: 1517043
(cherry picked from commit ffd32c7e19)
This commit is contained in:
Szymon Borkowski 2015-12-17 13:18:31 +01:00
parent f51ffea673
commit bd3b972e72
2 changed files with 73 additions and 16 deletions

View File

@ -57,6 +57,17 @@ class QuotaTemplate(xmlutil.TemplateBuilder):
class QuotaSetsController(wsgi.Controller): class QuotaSetsController(wsgi.Controller):
class GenericProjectInfo(object):
"""Abstraction layer for Keystone V2 and V3 project objects"""
def __init__(self, project_id, project_keystone_api_version,
project_parent_id=None, project_subtree=None):
self.id = project_id
self.keystone_api_version = project_keystone_api_version
self.parent_id = project_parent_id
self.subtree = project_subtree
def _format_quota_set(self, project_id, quota_set): def _format_quota_set(self, project_id, quota_set):
"""Convert the quota object to a result dict.""" """Convert the quota object to a result dict."""
@ -64,6 +75,20 @@ class QuotaSetsController(wsgi.Controller):
return dict(quota_set=quota_set) return dict(quota_set=quota_set)
def _keystone_client(self, context):
"""Creates and returns an instance of a generic keystone client.
:param context: The request context
:return: keystoneclient.client.Client object
"""
auth_plugin = token.Token(
auth_url=CONF.keystone_authtoken.auth_uri,
token=context.auth_token,
project_id=context.project_id)
client_session = session.Session(auth=auth_plugin)
return client.Client(auth_url=CONF.keystone_authtoken.auth_uri,
session=client_session)
def _validate_existing_resource(self, key, value, quota_values): def _validate_existing_resource(self, key, value, quota_values):
if key == 'per_volume_gigabytes': if key == 'per_volume_gigabytes':
return return
@ -177,23 +202,23 @@ class QuotaSetsController(wsgi.Controller):
def _get_project(self, context, id, subtree_as_ids=False): def _get_project(self, context, id, subtree_as_ids=False):
"""A Helper method to get the project hierarchy. """A Helper method to get the project hierarchy.
Along with Hierachical Multitenancy, projects can be hierarchically Along with Hierachical Multitenancy in keystone API v3, projects can be
organized. Therefore, we need to know the project hierarchy, if any, in hierarchically organized. Therefore, we need to know the project
order to do quota operations properly. hierarchy, if any, in order to do quota operations properly.
""" """
try: try:
auth_plugin = token.Token( keystone = self._keystone_client(context)
auth_url=CONF.keystone_authtoken.auth_uri, generic_project = self.GenericProjectInfo(id, keystone.version)
token=context.auth_token, if keystone.version == 'v3':
project_id=context.project_id) project = keystone.projects.get(id,
client_session = session.Session(auth=auth_plugin) subtree_as_ids=subtree_as_ids)
keystone = client.Client(auth_url=CONF.keystone_authtoken.auth_uri, generic_project.parent_id = project.parent_id
session=client_session) generic_project.subtree = (
project = keystone.projects.get(id, subtree_as_ids=subtree_as_ids) project.subtree if subtree_as_ids else None)
except exceptions.NotFound: except exceptions.NotFound:
msg = (_("Tenant ID: %s does not exist.") % id) msg = (_("Tenant ID: %s does not exist.") % id)
raise webob.exc.HTTPNotFound(explanation=msg) raise webob.exc.HTTPNotFound(explanation=msg)
return project return generic_project
@wsgi.serializers(xml=QuotaTemplate) @wsgi.serializers(xml=QuotaTemplate)
def show(self, req, id): def show(self, req, id):

View File

@ -133,17 +133,49 @@ class QuotaSetsControllerTest(test.TestCase):
def test_keystone_client_instantiation(self, ksclient_session, def test_keystone_client_instantiation(self, ksclient_session,
ksclient_class): ksclient_class):
context = self.req.environ['cinder.context'] context = self.req.environ['cinder.context']
self.controller._get_project(context, context.project_id) self.controller._keystone_client(context)
ksclient_class.assert_called_once_with(auth_url=self.auth_url, ksclient_class.assert_called_once_with(auth_url=self.auth_url,
session=ksclient_session()) session=ksclient_session())
@mock.patch('keystoneclient.client.Client') @mock.patch('keystoneclient.client.Client')
def test_get_project(self, ksclient_class): def test_get_project_keystoneclient_v2(self, ksclient_class):
context = self.req.environ['cinder.context'] context = self.req.environ['cinder.context']
keystoneclient = ksclient_class.return_value keystoneclient = ksclient_class.return_value
self.controller._get_project(context, context.project_id) keystoneclient.version = 'v2.0'
expected_project = self.controller.GenericProjectInfo(
context.project_id, 'v2.0')
project = self.controller._get_project(context, context.project_id)
self.assertEqual(expected_project.__dict__, project.__dict__)
@mock.patch('keystoneclient.client.Client')
def test_get_project_keystoneclient_v3(self, ksclient_class):
context = self.req.environ['cinder.context']
keystoneclient = ksclient_class.return_value
keystoneclient.version = 'v3'
returned_project = self.FakeProject(context.project_id, 'bar')
del returned_project.subtree
keystoneclient.projects.get.return_value = returned_project
expected_project = self.controller.GenericProjectInfo(
context.project_id, 'v3', 'bar')
project = self.controller._get_project(context, context.project_id)
self.assertEqual(expected_project.__dict__, project.__dict__)
@mock.patch('keystoneclient.client.Client')
def test_get_project_keystoneclient_v3_with_subtree(self, ksclient_class):
context = self.req.environ['cinder.context']
keystoneclient = ksclient_class.return_value
keystoneclient.version = 'v3'
returned_project = self.FakeProject(context.project_id, 'bar')
subtree_dict = {'baz': {'quux': None}}
returned_project.subtree = subtree_dict
keystoneclient.projects.get.return_value = returned_project
expected_project = self.controller.GenericProjectInfo(
context.project_id, 'v3', 'bar', subtree_dict)
project = self.controller._get_project(context, context.project_id,
subtree_as_ids=True)
keystoneclient.projects.get.assert_called_once_with( keystoneclient.projects.get.assert_called_once_with(
context.project_id, subtree_as_ids=False) context.project_id, subtree_as_ids=True)
self.assertEqual(expected_project.__dict__, project.__dict__)
def test_defaults(self): def test_defaults(self):
self.controller._get_project = mock.Mock() self.controller._get_project = mock.Mock()