Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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 ffd32c7)
  • Loading branch information
sborkows committed Dec 29, 2015
1 parent f51ffea commit bd3b972
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 16 deletions.
49 changes: 37 additions & 12 deletions cinder/api/contrib/quotas.py
Expand Up @@ -57,13 +57,38 @@ def construct(self):

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):
"""Convert the quota object to a result dict."""

quota_set['id'] = str(project_id)

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):
if key == 'per_volume_gigabytes':
return
Expand Down Expand Up @@ -177,23 +202,23 @@ def _is_descendant(self, target_project_id, subtree):
def _get_project(self, context, id, subtree_as_ids=False):
"""A Helper method to get the project hierarchy.
Along with Hierachical Multitenancy, projects can be hierarchically
organized. Therefore, we need to know the project hierarchy, if any, in
order to do quota operations properly.
Along with Hierachical Multitenancy in keystone API v3, projects can be
hierarchically organized. Therefore, we need to know the project
hierarchy, if any, in order to do quota operations properly.
"""
try:
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)
keystone = client.Client(auth_url=CONF.keystone_authtoken.auth_uri,
session=client_session)
project = keystone.projects.get(id, subtree_as_ids=subtree_as_ids)
keystone = self._keystone_client(context)
generic_project = self.GenericProjectInfo(id, keystone.version)
if keystone.version == 'v3':
project = keystone.projects.get(id,
subtree_as_ids=subtree_as_ids)
generic_project.parent_id = project.parent_id
generic_project.subtree = (
project.subtree if subtree_as_ids else None)
except exceptions.NotFound:
msg = (_("Tenant ID: %s does not exist.") % id)
raise webob.exc.HTTPNotFound(explanation=msg)
return project
return generic_project

@wsgi.serializers(xml=QuotaTemplate)
def show(self, req, id):
Expand Down
40 changes: 36 additions & 4 deletions cinder/tests/unit/api/contrib/test_quotas.py
Expand Up @@ -133,17 +133,49 @@ def _get_project(self, context, id, subtree_as_ids=False):
def test_keystone_client_instantiation(self, ksclient_session,
ksclient_class):
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,
session=ksclient_session())

@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']
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(
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):
self.controller._get_project = mock.Mock()
Expand Down

0 comments on commit bd3b972

Please sign in to comment.