Browse Source

Merge "Infer version from old versioned service type aliases"

Zuul 11 months ago
parent
commit
3308cd9944

+ 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