Browse Source

Infer version from old versioned service type aliases

The last piece of service type alias support is to handle volumev2,
volumev3, workflowv2, workflowv3 and friends.

Although it's an annoying scenario, luckily legacy code that uses them
has a clear meaning. volumev2, version='3' is just legit not a thing.

Needed-By: https://review.openstack.org/564494
Change-Id: Iec09bcb16d8e9b09e09bf12d03c2a55e679ad70c
Monty Taylor 1 year ago
parent
commit
5c79260971
No account linked to committer's email address

+ 49
- 1
keystoneauth1/discover.py View File

@@ -220,7 +220,28 @@ def normalize_version_number(version):
220 220
     raise TypeError('Invalid version specified: %s' % version)
221 221
 
222 222
 
223
-def _normalize_version_args(version, min_version, max_version):
223
+def _normalize_version_args(
224
+        version, min_version, max_version, service_type=None):
225
+    # The sins of our fathers become the blood on our hands.
226
+    # If a user requests an old-style service type such as volumev2, then they
227
+    # are inherently requesting the major API version 2. It's not a good
228
+    # interface, but it's the one that was imposed on the world years ago
229
+    # because the client libraries all hid the version discovery document.
230
+    # In order to be able to ensure that a user who requests volumev2 does not
231
+    # get a block-storage endpoint that only provides v3 of the block-storage
232
+    # service, we need to pull the version out of the service_type. The
233
+    # service-types-authority will prevent the growth of new monstrosities such
234
+    # as this, but in order to move forward without breaking people, we have
235
+    # to just cry in the corner while striking ourselves with thorned branches.
236
+    # That said, for sure only do this hack for officially known service_types.
237
+    if (service_type and
238
+            _SERVICE_TYPES.is_known(service_type) and
239
+            service_type[-1].isdigit() and
240
+            service_type[-2] == 'v'):
241
+        implied_version = normalize_version_number(service_type[-1])
242
+    else:
243
+        implied_version = None
244
+
224 245
     if version and (min_version or max_version):
225 246
         raise ValueError(
226 247
             "version is mutually exclusive with min_version and max_version")
@@ -229,6 +250,12 @@ def _normalize_version_args(version, min_version, max_version):
229 250
         # Explode this into min_version and max_version
230 251
         min_version = normalize_version_number(version)
231 252
         max_version = (min_version[0], LATEST)
253
+        if implied_version:
254
+            if min_version[0] != implied_version[0]:
255
+                raise exceptions.ImpliedVersionMismatch(
256
+                    service_type=service_type,
257
+                    implied=implied_version,
258
+                    given=version_to_string(version))
232 259
         return min_version, max_version
233 260
 
234 261
     if min_version == 'latest':
@@ -257,6 +284,27 @@ def _normalize_version_args(version, min_version, max_version):
257 284
     if None not in (min_version, max_version) and max_version < min_version:
258 285
         raise ValueError("min_version cannot be greater than max_version")
259 286
 
287
+    if implied_version:
288
+        if min_version:
289
+            if min_version[0] != implied_version[0]:
290
+                raise exceptions.ImpliedMinVersionMismatch(
291
+                    service_type=service_type,
292
+                    implied=implied_version,
293
+                    given=version_to_string(min_version))
294
+        else:
295
+            min_version = implied_version
296
+
297
+        # If 'latest' is provided with a versioned service-type like
298
+        # volumev2 - the user wants the latest of volumev2, not the latest
299
+        # of block-storage.
300
+        if max_version and max_version[0] != LATEST:
301
+            if max_version[0] != implied_version[0]:
302
+                raise exceptions.ImpliedMaxVersionMismatch(
303
+                    service_type=service_type,
304
+                    implied=implied_version,
305
+                    given=version_to_string(max_version))
306
+        else:
307
+            max_version = (implied_version[0], LATEST)
260 308
     return min_version, max_version
261 309
 
262 310
 

+ 32
- 0
keystoneauth1/exceptions/discovery.py View File

@@ -10,10 +10,17 @@
10 10
 # License for the specific language governing permissions and limitations
11 11
 # under the License.
12 12
 
13
+import os_service_types
14
+
13 15
 from keystoneauth1.exceptions import base
14 16
 
17
+_SERVICE_TYPES = os_service_types.ServiceTypes()
18
+
15 19
 
16 20
 __all__ = ('DiscoveryFailure',
21
+           'ImpliedVersionMismatch',
22
+           'ImpliedMinVersionMismatch',
23
+           'ImpliedMaxVersionMismatch',
17 24
            'VersionNotAvailable')
18 25
 
19 26
 
@@ -23,3 +30,28 @@ class DiscoveryFailure(base.ClientException):
23 30
 
24 31
 class VersionNotAvailable(DiscoveryFailure):
25 32
     message = "Discovery failed. Requested version is not available."
33
+
34
+
35
+class ImpliedVersionMismatch(ValueError):
36
+    label = 'version'
37
+
38
+    def __init__(self, service_type, implied, given):
39
+        super(ImpliedVersionMismatch, self).__init__(
40
+            "service_type {service_type} was given which implies"
41
+            " major API version {implied} but {label} of"
42
+            " {given} was also given. Please update your code"
43
+            " to use the official service_type {official_type}.".format(
44
+                service_type=service_type,
45
+                implied=str(implied[0]),
46
+                given=given,
47
+                label=self.label,
48
+                official_type=_SERVICE_TYPES.get_service_type(service_type),
49
+            ))
50
+
51
+
52
+class ImpliedMinVersionMismatch(ImpliedVersionMismatch):
53
+    label = 'min_version'
54
+
55
+
56
+class ImpliedMaxVersionMismatch(ImpliedVersionMismatch):
57
+    label = 'max_version'

+ 3
- 3
keystoneauth1/identity/base.py View File

@@ -226,7 +226,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
226 226
         allow = allow or {}
227 227
 
228 228
         min_version, max_version = discover._normalize_version_args(
229
-            None, min_version, max_version)
229
+            None, min_version, max_version, service_type=service_type)
230 230
 
231 231
         # NOTE(jamielennox): if you specifically ask for requests to be sent to
232 232
         # the auth url then we can ignore many of the checks. Typically if you
@@ -365,7 +365,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
365 365
         # Explode `version` into min_version and max_version - everything below
366 366
         # here uses the latter rather than the former.
367 367
         min_version, max_version = discover._normalize_version_args(
368
-            version, min_version, max_version)
368
+            version, min_version, max_version, service_type=service_type)
369 369
         # Set discover_versions to False since we're only going to return
370 370
         # a URL. Fetching the microversion data would be needlessly
371 371
         # expensive in the common case. However, discover_versions=False
@@ -487,7 +487,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
487 487
         # Explode `version` into min_version and max_version - everything below
488 488
         # here uses the latter rather than the former.
489 489
         min_version, max_version = discover._normalize_version_args(
490
-            version, min_version, max_version)
490
+            version, min_version, max_version, service_type=service_type)
491 491
         # Using functools.partial here just to reduce copy-pasta of params
492 492
         get_endpoint_data = functools.partial(
493 493
             self.get_endpoint_data,

+ 22
- 4
keystoneauth1/tests/unit/identity/test_identity_common.py View File

@@ -1053,7 +1053,7 @@ class CommonIdentityTests(object):
1053 1053
 
1054 1054
         # We should only try to fetch the versioned discovery url once
1055 1055
         self.requests_mock.get(
1056
-            self.TEST_VOLUME.versions['v3'].discovery.public, resps)
1056
+            self.TEST_VOLUME.versions['v3'].discovery.public + '/', resps)
1057 1057
 
1058 1058
         data = a.get_endpoint_data(session=s,
1059 1059
                                    service_type='volumev3',
@@ -1128,7 +1128,7 @@ class CommonIdentityTests(object):
1128 1128
 
1129 1129
         # Fetch v2.0 first - since that doesn't match endpoint optimization,
1130 1130
         # it should fetch the unversioned endpoint
1131
-        v2_data = s.get_endpoint_data(service_type='volumev3',
1131
+        v2_data = s.get_endpoint_data(service_type='block-storage',
1132 1132
                                       interface='public',
1133 1133
                                       min_version='2.0',
1134 1134
                                       max_version='2.latest',
@@ -1179,10 +1179,10 @@ class CommonIdentityTests(object):
1179 1179
         self.requests_mock.get(
1180 1180
             self.TEST_VOLUME.unversioned.public + '/', json=disc)
1181 1181
 
1182
-        # We're requesting version 2 of volumev3 to make sure we
1182
+        # We're requesting version 2 of block-storage to make sure we
1183 1183
         # trigger the logic constructing the unversioned endpoint from the
1184 1184
         # versioned endpoint in the catalog
1185
-        s.get_endpoint_data(service_type='volumev3',
1185
+        s.get_endpoint_data(service_type='block-storage',
1186 1186
                             interface='public',
1187 1187
                             min_version='2.0',
1188 1188
                             max_version='2.latest',
@@ -1413,6 +1413,15 @@ class V3(CommonIdentityTests, utils.TestCase):
1413 1413
             internal=self.TEST_VOLUME.versions['v3'].service.internal,
1414 1414
             region=region)
1415 1415
 
1416
+        # Add block-storage as a versioned endpoint so that we can test
1417
+        # versioned to unversioned inference.
1418
+        svc = token.add_service('block-storage')
1419
+        svc.add_standard_endpoints(
1420
+            admin=self.TEST_VOLUME.versions['v3'].service.admin,
1421
+            public=self.TEST_VOLUME.versions['v3'].service.public,
1422
+            internal=self.TEST_VOLUME.versions['v3'].service.internal,
1423
+            region=region)
1424
+
1416 1425
         svc = token.add_service('baremetal')
1417 1426
         svc.add_standard_endpoints(
1418 1427
             internal=self.TEST_BAREMETAL_INTERNAL,
@@ -1490,6 +1499,15 @@ class V2(CommonIdentityTests, utils.TestCase):
1490 1499
             internal=self.TEST_VOLUME.versions['v3'].service.internal,
1491 1500
             region=region)
1492 1501
 
1502
+        # Add block-storage as a versioned endpoint so that we can test
1503
+        # versioned to unversioned inferance.
1504
+        svc = token.add_service('block-storage')
1505
+        svc.add_endpoint(
1506
+            admin=self.TEST_VOLUME.versions['v3'].service.admin,
1507
+            public=self.TEST_VOLUME.versions['v3'].service.public,
1508
+            internal=self.TEST_VOLUME.versions['v3'].service.internal,
1509
+            region=region)
1510
+
1493 1511
         svc = token.add_service('baremetal')
1494 1512
         svc.add_endpoint(
1495 1513
             public=None, admin=None,

+ 36
- 25
keystoneauth1/tests/unit/test_discovery.py View File

@@ -323,52 +323,63 @@ class DiscoverUtils(utils.TestCase):
323 323
 
324 324
     def test_version_args(self):
325 325
         """Validate _normalize_version_args."""
326
-        def assert_min_max(in_ver, in_min, in_max, out_min, out_max):
326
+        def assert_min_max(in_ver, in_min, in_max, in_type, out_min, out_max):
327 327
             self.assertEqual(
328 328
                 (out_min, out_max),
329
-                discover._normalize_version_args(in_ver, in_min, in_max))
329
+                discover._normalize_version_args(
330
+                    in_ver, in_min, in_max, service_type=in_type))
330 331
 
331
-        def normalize_raises(ver, min, max):
332
-            self.assertRaises(ValueError,
333
-                              discover._normalize_version_args, ver, min, max)
332
+        def normalize_raises(ver, min, max, in_type):
333
+            self.assertRaises(
334
+                ValueError,
335
+                discover._normalize_version_args,
336
+                ver, min, max, service_type=in_type)
334 337
 
335
-        assert_min_max(None, None, None,
338
+        assert_min_max(None, None, None, None,
336 339
                        None, None)
337
-        assert_min_max(None, None, 'v1.2',
340
+        assert_min_max(None, None, 'v1.2', None,
338 341
                        None, (1, 2))
339
-        assert_min_max(None, 'v1.2', 'latest',
342
+        assert_min_max(None, 'v1.2', 'latest', None,
340 343
                        (1, 2), (discover.LATEST, discover.LATEST))
341
-        assert_min_max(None, 'v1.2', '1.6',
344
+        assert_min_max(None, 'v1.2', '1.6', None,
342 345
                        (1, 2), (1, 6))
343
-        assert_min_max(None, 'v1.2', '1.latest',
346
+        assert_min_max(None, 'v1.2', '1.latest', None,
344 347
                        (1, 2), (1, discover.LATEST))
345
-        assert_min_max(None, 'latest', 'latest',
348
+        assert_min_max(None, 'latest', 'latest', None,
346 349
                        (discover.LATEST, discover.LATEST),
347 350
                        (discover.LATEST, discover.LATEST))
348
-        assert_min_max(None, 'latest', None,
351
+        assert_min_max(None, 'latest', None, None,
349 352
                        (discover.LATEST, discover.LATEST),
350 353
                        (discover.LATEST, discover.LATEST))
351
-        assert_min_max(None, (1, 2), None,
354
+        assert_min_max(None, (1, 2), None, None,
352 355
                        (1, 2), (discover.LATEST, discover.LATEST))
353
-        assert_min_max('', ('1', '2'), (1, 6),
356
+        assert_min_max('', ('1', '2'), (1, 6), None,
354 357
                        (1, 2), (1, 6))
355
-        assert_min_max(None, ('1', '2'), (1, discover.LATEST),
358
+        assert_min_max(None, ('1', '2'), (1, discover.LATEST), None,
356 359
                        (1, 2), (1, discover.LATEST))
357
-        assert_min_max('v1.2', '', None,
360
+        assert_min_max('v1.2', '', None, None,
358 361
                        (1, 2), (1, discover.LATEST))
359
-        assert_min_max('1.latest', None, '',
362
+        assert_min_max('1.latest', None, '', None,
360 363
                        (1, discover.LATEST), (1, discover.LATEST))
361
-        assert_min_max('v1', None, None,
364
+        assert_min_max('v1', None, None, None,
362 365
                        (1, 0), (1, discover.LATEST))
363
-        assert_min_max('latest', None, None,
366
+        assert_min_max('latest', None, None, None,
364 367
                        (discover.LATEST, discover.LATEST),
365 368
                        (discover.LATEST, discover.LATEST))
366
-
367
-        normalize_raises('v1', 'v2', None)
368
-        normalize_raises('v1', None, 'v2')
369
-        normalize_raises(None, 'latest', 'v1')
370
-        normalize_raises(None, 'v1.2', 'v1.1')
371
-        normalize_raises(None, 'v1.2', 1)
369
+        assert_min_max(None, None, 'latest', 'volumev2',
370
+                       (2, 0), (2, discover.LATEST))
371
+        assert_min_max(None, None, None, 'volumev2',
372
+                       (2, 0), (2, discover.LATEST))
373
+
374
+        normalize_raises('v1', 'v2', None, None)
375
+        normalize_raises('v1', None, 'v2', None)
376
+        normalize_raises(None, 'latest', 'v1', None)
377
+        normalize_raises(None, 'v1.2', 'v1.1', None)
378
+        normalize_raises(None, 'v1.2', 1, None)
379
+        normalize_raises('v2', None, None, 'volumev3')
380
+        normalize_raises('v3', None, None, 'volumev2')
381
+        normalize_raises(None, 'v2', None, 'volumev3')
382
+        normalize_raises(None, None, 'v3', 'volumev2')
372 383
 
373 384
     def test_version_to_string(self):
374 385
         def assert_string(out, inp):

Loading…
Cancel
Save