Browse Source

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)
tags/7.0.2
Szymon Borkowski 3 years ago
parent
commit
bd3b972e72
2 changed files with 73 additions and 16 deletions
  1. 37
    12
      cinder/api/contrib/quotas.py
  2. 36
    4
      cinder/tests/unit/api/contrib/test_quotas.py

+ 37
- 12
cinder/api/contrib/quotas.py View File

@@ -57,6 +57,17 @@ class QuotaTemplate(xmlutil.TemplateBuilder):
57 57
 
58 58
 class QuotaSetsController(wsgi.Controller):
59 59
 
60
+    class GenericProjectInfo(object):
61
+
62
+        """Abstraction layer for Keystone V2 and V3 project objects"""
63
+
64
+        def __init__(self, project_id, project_keystone_api_version,
65
+                     project_parent_id=None, project_subtree=None):
66
+            self.id = project_id
67
+            self.keystone_api_version = project_keystone_api_version
68
+            self.parent_id = project_parent_id
69
+            self.subtree = project_subtree
70
+
60 71
     def _format_quota_set(self, project_id, quota_set):
61 72
         """Convert the quota object to a result dict."""
62 73
 
@@ -64,6 +75,20 @@ class QuotaSetsController(wsgi.Controller):
64 75
 
65 76
         return dict(quota_set=quota_set)
66 77
 
78
+    def _keystone_client(self, context):
79
+        """Creates and returns an instance of a generic keystone client.
80
+
81
+        :param context: The request context
82
+        :return: keystoneclient.client.Client object
83
+        """
84
+        auth_plugin = token.Token(
85
+            auth_url=CONF.keystone_authtoken.auth_uri,
86
+            token=context.auth_token,
87
+            project_id=context.project_id)
88
+        client_session = session.Session(auth=auth_plugin)
89
+        return client.Client(auth_url=CONF.keystone_authtoken.auth_uri,
90
+                             session=client_session)
91
+
67 92
     def _validate_existing_resource(self, key, value, quota_values):
68 93
         if key == 'per_volume_gigabytes':
69 94
             return
@@ -177,23 +202,23 @@ class QuotaSetsController(wsgi.Controller):
177 202
     def _get_project(self, context, id, subtree_as_ids=False):
178 203
         """A Helper method to get the project hierarchy.
179 204
 
180
-        Along with Hierachical Multitenancy, projects can be hierarchically
181
-        organized. Therefore, we need to know the project hierarchy, if any, in
182
-        order to do quota operations properly.
205
+        Along with Hierachical Multitenancy in keystone API v3, projects can be
206
+        hierarchically organized. Therefore, we need to know the project
207
+        hierarchy, if any, in order to do quota operations properly.
183 208
         """
184 209
         try:
185
-            auth_plugin = token.Token(
186
-                auth_url=CONF.keystone_authtoken.auth_uri,
187
-                token=context.auth_token,
188
-                project_id=context.project_id)
189
-            client_session = session.Session(auth=auth_plugin)
190
-            keystone = client.Client(auth_url=CONF.keystone_authtoken.auth_uri,
191
-                                     session=client_session)
192
-            project = keystone.projects.get(id, subtree_as_ids=subtree_as_ids)
210
+            keystone = self._keystone_client(context)
211
+            generic_project = self.GenericProjectInfo(id, keystone.version)
212
+            if keystone.version == 'v3':
213
+                project = keystone.projects.get(id,
214
+                                                subtree_as_ids=subtree_as_ids)
215
+                generic_project.parent_id = project.parent_id
216
+                generic_project.subtree = (
217
+                    project.subtree if subtree_as_ids else None)
193 218
         except exceptions.NotFound:
194 219
             msg = (_("Tenant ID: %s does not exist.") % id)
195 220
             raise webob.exc.HTTPNotFound(explanation=msg)
196
-        return project
221
+        return generic_project
197 222
 
198 223
     @wsgi.serializers(xml=QuotaTemplate)
199 224
     def show(self, req, id):

+ 36
- 4
cinder/tests/unit/api/contrib/test_quotas.py View File

@@ -133,17 +133,49 @@ class QuotaSetsControllerTest(test.TestCase):
133 133
     def test_keystone_client_instantiation(self, ksclient_session,
134 134
                                            ksclient_class):
135 135
         context = self.req.environ['cinder.context']
136
-        self.controller._get_project(context, context.project_id)
136
+        self.controller._keystone_client(context)
137 137
         ksclient_class.assert_called_once_with(auth_url=self.auth_url,
138 138
                                                session=ksclient_session())
139 139
 
140 140
     @mock.patch('keystoneclient.client.Client')
141
-    def test_get_project(self, ksclient_class):
141
+    def test_get_project_keystoneclient_v2(self, ksclient_class):
142 142
         context = self.req.environ['cinder.context']
143 143
         keystoneclient = ksclient_class.return_value
144
-        self.controller._get_project(context, context.project_id)
144
+        keystoneclient.version = 'v2.0'
145
+        expected_project = self.controller.GenericProjectInfo(
146
+            context.project_id, 'v2.0')
147
+        project = self.controller._get_project(context, context.project_id)
148
+        self.assertEqual(expected_project.__dict__, project.__dict__)
149
+
150
+    @mock.patch('keystoneclient.client.Client')
151
+    def test_get_project_keystoneclient_v3(self, ksclient_class):
152
+        context = self.req.environ['cinder.context']
153
+        keystoneclient = ksclient_class.return_value
154
+        keystoneclient.version = 'v3'
155
+        returned_project = self.FakeProject(context.project_id, 'bar')
156
+        del returned_project.subtree
157
+        keystoneclient.projects.get.return_value = returned_project
158
+        expected_project = self.controller.GenericProjectInfo(
159
+            context.project_id, 'v3', 'bar')
160
+        project = self.controller._get_project(context, context.project_id)
161
+        self.assertEqual(expected_project.__dict__, project.__dict__)
162
+
163
+    @mock.patch('keystoneclient.client.Client')
164
+    def test_get_project_keystoneclient_v3_with_subtree(self, ksclient_class):
165
+        context = self.req.environ['cinder.context']
166
+        keystoneclient = ksclient_class.return_value
167
+        keystoneclient.version = 'v3'
168
+        returned_project = self.FakeProject(context.project_id, 'bar')
169
+        subtree_dict = {'baz': {'quux': None}}
170
+        returned_project.subtree = subtree_dict
171
+        keystoneclient.projects.get.return_value = returned_project
172
+        expected_project = self.controller.GenericProjectInfo(
173
+            context.project_id, 'v3', 'bar', subtree_dict)
174
+        project = self.controller._get_project(context, context.project_id,
175
+                                               subtree_as_ids=True)
145 176
         keystoneclient.projects.get.assert_called_once_with(
146
-            context.project_id, subtree_as_ids=False)
177
+            context.project_id, subtree_as_ids=True)
178
+        self.assertEqual(expected_project.__dict__, project.__dict__)
147 179
 
148 180
     def test_defaults(self):
149 181
         self.controller._get_project = mock.Mock()

Loading…
Cancel
Save