From: Szymon Borkowski Date: Thu, 17 Dec 2015 12:18:31 +0000 (+0100) Subject: Quota API is now compatible with keystone API v2 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=ffd32c7e1947189eee701c417d9f72982f720a11;p=openstack-build%2Fcinder-build.git 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 --- diff --git a/cinder/api/contrib/quotas.py b/cinder/api/contrib/quotas.py index 4dfc53f52..b5e0bb954 100644 --- a/cinder/api/contrib/quotas.py +++ b/cinder/api/contrib/quotas.py @@ -57,6 +57,17 @@ class QuotaTemplate(xmlutil.TemplateBuilder): 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.""" @@ -64,6 +75,20 @@ class QuotaSetsController(wsgi.Controller): 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 @@ -177,23 +202,23 @@ class QuotaSetsController(wsgi.Controller): 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): diff --git a/cinder/tests/unit/api/contrib/test_quotas.py b/cinder/tests/unit/api/contrib/test_quotas.py index 741fa0295..3fbe369c4 100644 --- a/cinder/tests/unit/api/contrib/test_quotas.py +++ b/cinder/tests/unit/api/contrib/test_quotas.py @@ -134,17 +134,49 @@ class QuotaSetsControllerTest(test.TestCase): 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()