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:
parent
f51ffea673
commit
bd3b972e72
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue