From 54c7bd498249c021334ac90009bfe174bc551b96 Mon Sep 17 00:00:00 2001 From: Eric Wehrmeister Date: Wed, 24 Feb 2016 11:11:07 -0600 Subject: [PATCH] Allows specifying a name for a particular endpoint. Previously, if there were multiple endpoints with the same type and region, or without a region, the first endpoint would be returned. Now, by specifying the name, a specific one can be used. Co-Authored-By: Franklin Naval Change-Id: Ife6d435e2aa84153d8717463930d45e5f21272f7 Closes-Bug: #1486834 --- .../notes/bug-1486834-7ebca15836ae27a9.yaml | 7 +++ tempest/lib/auth.py | 57 +++++++++++++------ tempest/lib/common/rest_client.py | 8 ++- tempest/tests/lib/fake_identity.py | 3 +- tempest/tests/lib/test_auth.py | 52 +++++++++++++++++ tempest/tests/lib/test_rest_client.py | 2 + 6 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml diff --git a/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml b/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml new file mode 100644 index 0000000000..b2190f3685 --- /dev/null +++ b/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Tempest library auth interface now supports + filtering with catalog name. Note that filtering by + name is only successful if a known service type is + provided. diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py index ffcc4fbe1d..42ce0f3824 100644 --- a/tempest/lib/auth.py +++ b/tempest/lib/auth.py @@ -1,4 +1,5 @@ # Copyright 2014 Hewlett-Packard Development Company, L.P. +# Copyright 2016 Rackspace Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -368,18 +369,24 @@ class KeystoneV2AuthProvider(KeystoneAuthProvider): def base_url(self, filters, auth_data=None): """Base URL from catalog - Filters can be: - - service: compute, image, etc - - region: the service region - - endpoint_type: adminURL, publicURL, internalURL - - api_version: replace catalog version with this - - skip_path: take just the base URL + :param filters: Used to filter results + Filters can be: + - service: service type name such as compute, image, etc. + - region: service region name + - name: service name, only if service exists + - endpoint_type: type of endpoint such as + adminURL, publicURL, internalURL + - api_version: the version of api used to replace catalog version + - skip_path: skips the suffix path of the url and uses base URL + :rtype string + :return url with filters applied """ if auth_data is None: auth_data = self.get_auth() token, _auth_data = auth_data service = filters.get('service') region = filters.get('region') + name = filters.get('name') endpoint_type = filters.get('endpoint_type', 'publicURL') if service is None: @@ -388,17 +395,19 @@ class KeystoneV2AuthProvider(KeystoneAuthProvider): _base_url = None for ep in _auth_data['serviceCatalog']: if ep["type"] == service: + if name is not None and ep["name"] != name: + continue for _ep in ep['endpoints']: if region is not None and _ep['region'] == region: _base_url = _ep.get(endpoint_type) if not _base_url: - # No region matching, use the first + # No region or name matching, use the first _base_url = ep['endpoints'][0].get(endpoint_type) break if _base_url is None: raise exceptions.EndpointNotFound( - "service: %s, region: %s, endpoint_type: %s" % - (service, region, endpoint_type)) + "service: %s, region: %s, endpoint_type: %s, name: %s" % + (service, region, endpoint_type, name)) return apply_url_filters(_base_url, filters) def is_expired(self, auth_data): @@ -489,18 +498,24 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider): the auth_data. In such case, as long as the requested service is 'identity', we can use the original auth URL to build the base_url. - Filters can be: - - service: compute, image, etc - - region: the service region - - endpoint_type: adminURL, publicURL, internalURL - - api_version: replace catalog version with this - - skip_path: take just the base URL + :param filters: Used to filter results + Filters can be: + - service: service type name such as compute, image, etc. + - region: service region name + - name: service name, only if service exists + - endpoint_type: type of endpoint such as + adminURL, publicURL, internalURL + - api_version: the version of api used to replace catalog version + - skip_path: skips the suffix path of the url and uses base URL + :rtype string + :return url with filters applied """ if auth_data is None: auth_data = self.get_auth() token, _auth_data = auth_data service = filters.get('service') region = filters.get('region') + name = filters.get('name') endpoint_type = filters.get('endpoint_type', 'public') if service is None: @@ -513,7 +528,15 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider): # Select entries with matching service type service_catalog = [ep for ep in catalog if ep['type'] == service] if len(service_catalog) > 0: - service_catalog = service_catalog[0]['endpoints'] + if name is not None: + service_catalog = ( + [ep for ep in service_catalog if ep['name'] == name]) + if len(service_catalog) > 0: + service_catalog = service_catalog[0]['endpoints'] + else: + raise exceptions.EndpointNotFound(name) + else: + service_catalog = service_catalog[0]['endpoints'] else: if len(catalog) == 0 and service == 'identity': # NOTE(andreaf) If there's no catalog at all and the service @@ -533,7 +556,7 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider): filtered_catalog = [ep for ep in filtered_catalog if ep['region'] == region] if len(filtered_catalog) == 0: - # No matching region, take the first endpoint + # No matching region (or name), take the first endpoint filtered_catalog = [service_catalog[0]] # There should be only one match. If not take the first. _base_url = filtered_catalog[0].get('url', None) diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py index 30750dec17..03ec3c08e0 100644 --- a/tempest/lib/common/rest_client.py +++ b/tempest/lib/common/rest_client.py @@ -54,6 +54,8 @@ class RestClient(object): :param auth_provider: an auth provider object used to wrap requests in auth :param str service: The service name to use for the catalog lookup :param str region: The region to use for the catalog lookup + :param str name: The endpoint name to use for the catalog lookup; this + returns only if the service exists :param str endpoint_type: The endpoint type to use for the catalog lookup :param int build_interval: Time in seconds between to status checks in wait loops @@ -76,10 +78,11 @@ class RestClient(object): endpoint_type='publicURL', build_interval=1, build_timeout=60, disable_ssl_certificate_validation=False, ca_certs=None, - trace_requests=''): + trace_requests='', name=None): self.auth_provider = auth_provider self.service = service self.region = region + self.name = name self.endpoint_type = endpoint_type self.build_interval = build_interval self.build_timeout = build_timeout @@ -191,7 +194,8 @@ class RestClient(object): _filters = dict( service=self.service, endpoint_type=self.endpoint_type, - region=self.region + region=self.region, + name=self.name ) if self.api_version is not None: _filters['api_version'] = self.api_version diff --git a/tempest/tests/lib/fake_identity.py b/tempest/tests/lib/fake_identity.py index c903e47001..831f8b5bf1 100644 --- a/tempest/tests/lib/fake_identity.py +++ b/tempest/tests/lib/fake_identity.py @@ -100,7 +100,8 @@ COMPUTE_ENDPOINTS_V3 = { ], "type": "compute", - "id": "fake_compute_endpoint" + "id": "fake_compute_endpoint", + "name": "nova" } CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ] diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py index c2531876d2..c08bf6a1b4 100644 --- a/tempest/tests/lib/test_auth.py +++ b/tempest/tests/lib/test_auth.py @@ -360,6 +360,58 @@ class TestKeystoneV2AuthProvider(BaseAuthTestsSetUp): self.assertRaises(exceptions.EndpointNotFound, self._test_base_url_helper, None, self.filters) + def test_base_url_with_known_name(self): + """If name and service is known, return the endpoint.""" + self.filters = { + 'service': 'compute', + 'endpoint_type': 'publicURL', + 'region': 'FakeRegion', + 'name': 'nova' + } + expected = self._get_result_url_from_endpoint( + self._endpoints[0]['endpoints'][1]) + self._test_base_url_helper(expected, self.filters) + + def test_base_url_with_known_name_and_unknown_servce(self): + """Test with Known Name and Unknown service + + If the name is known but the service is unknown, raise an exception. + """ + self.filters = { + 'service': 'AintNoBodyKnowThatService', + 'endpoint_type': 'publicURL', + 'region': 'FakeRegion', + 'name': 'AintNoBodyKnowThatName' + } + self.assertRaises(exceptions.EndpointNotFound, + self._test_base_url_helper, None, self.filters) + + def test_base_url_with_unknown_name_and_known_service(self): + """Test with Unknown Name and Known Service + + If the name is unknown, raise an exception. Note that filtering by + name is only successful service exists. + """ + + self.filters = { + 'service': 'compute', + 'endpoint_type': 'publicURL', + 'region': 'FakeRegion', + 'name': 'AintNoBodyKnowThatName' + } + self.assertRaises(exceptions.EndpointNotFound, + self._test_base_url_helper, None, self.filters) + + def test_base_url_without_name(self): + self.filters = { + 'service': 'compute', + 'endpoint_type': 'publicURL', + 'region': 'FakeRegion', + } + expected = self._get_result_url_from_endpoint( + self._endpoints[0]['endpoints'][1]) + self._test_base_url_helper(expected, self.filters) + def test_base_url_with_api_version_filter(self): self.filters = { 'service': 'compute', diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/test_rest_client.py index 2a6fad5070..d5f7a555a4 100644 --- a/tempest/tests/lib/test_rest_client.py +++ b/tempest/tests/lib/test_rest_client.py @@ -633,6 +633,7 @@ class TestProperties(BaseRestClientTestClass): expected = {'api_version': 'v1', 'endpoint_type': 'publicURL', 'region': None, + 'name': None, 'service': None, 'skip_path': True} self.rest_client.skip_path() @@ -643,6 +644,7 @@ class TestProperties(BaseRestClientTestClass): expected = {'api_version': 'v1', 'endpoint_type': 'publicURL', 'region': None, + 'name': None, 'service': None} self.assertEqual(expected, self.rest_client.filters)