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
tags/8.0.0.0b2
Szymon Borkowski 3 years ago
parent
commit
ffd32c7e19
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

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

Loading…
Cancel
Save