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 <franklin.naval@gmail.com>

Change-Id: Ife6d435e2aa84153d8717463930d45e5f21272f7
Closes-Bug: #1486834
This commit is contained in:
Eric Wehrmeister 2016-02-24 11:11:07 -06:00 committed by Franklin Naval
parent 66a162131e
commit 54c7bd4982
6 changed files with 109 additions and 20 deletions

View File

@ -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.

View File

@ -1,4 +1,5 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P. # Copyright 2014 Hewlett-Packard Development Company, L.P.
# Copyright 2016 Rackspace Inc.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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): def base_url(self, filters, auth_data=None):
"""Base URL from catalog """Base URL from catalog
Filters can be: :param filters: Used to filter results
- service: compute, image, etc Filters can be:
- region: the service region - service: service type name such as compute, image, etc.
- endpoint_type: adminURL, publicURL, internalURL - region: service region name
- api_version: replace catalog version with this - name: service name, only if service exists
- skip_path: take just the base URL - 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: if auth_data is None:
auth_data = self.get_auth() auth_data = self.get_auth()
token, _auth_data = auth_data token, _auth_data = auth_data
service = filters.get('service') service = filters.get('service')
region = filters.get('region') region = filters.get('region')
name = filters.get('name')
endpoint_type = filters.get('endpoint_type', 'publicURL') endpoint_type = filters.get('endpoint_type', 'publicURL')
if service is None: if service is None:
@ -388,17 +395,19 @@ class KeystoneV2AuthProvider(KeystoneAuthProvider):
_base_url = None _base_url = None
for ep in _auth_data['serviceCatalog']: for ep in _auth_data['serviceCatalog']:
if ep["type"] == service: if ep["type"] == service:
if name is not None and ep["name"] != name:
continue
for _ep in ep['endpoints']: for _ep in ep['endpoints']:
if region is not None and _ep['region'] == region: if region is not None and _ep['region'] == region:
_base_url = _ep.get(endpoint_type) _base_url = _ep.get(endpoint_type)
if not _base_url: 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) _base_url = ep['endpoints'][0].get(endpoint_type)
break break
if _base_url is None: if _base_url is None:
raise exceptions.EndpointNotFound( raise exceptions.EndpointNotFound(
"service: %s, region: %s, endpoint_type: %s" % "service: %s, region: %s, endpoint_type: %s, name: %s" %
(service, region, endpoint_type)) (service, region, endpoint_type, name))
return apply_url_filters(_base_url, filters) return apply_url_filters(_base_url, filters)
def is_expired(self, auth_data): 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 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. 'identity', we can use the original auth URL to build the base_url.
Filters can be: :param filters: Used to filter results
- service: compute, image, etc Filters can be:
- region: the service region - service: service type name such as compute, image, etc.
- endpoint_type: adminURL, publicURL, internalURL - region: service region name
- api_version: replace catalog version with this - name: service name, only if service exists
- skip_path: take just the base URL - 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: if auth_data is None:
auth_data = self.get_auth() auth_data = self.get_auth()
token, _auth_data = auth_data token, _auth_data = auth_data
service = filters.get('service') service = filters.get('service')
region = filters.get('region') region = filters.get('region')
name = filters.get('name')
endpoint_type = filters.get('endpoint_type', 'public') endpoint_type = filters.get('endpoint_type', 'public')
if service is None: if service is None:
@ -513,7 +528,15 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
# Select entries with matching service type # Select entries with matching service type
service_catalog = [ep for ep in catalog if ep['type'] == service] service_catalog = [ep for ep in catalog if ep['type'] == service]
if len(service_catalog) > 0: 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: else:
if len(catalog) == 0 and service == 'identity': if len(catalog) == 0 and service == 'identity':
# NOTE(andreaf) If there's no catalog at all and the service # 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 filtered_catalog = [ep for ep in filtered_catalog if
ep['region'] == region] ep['region'] == region]
if len(filtered_catalog) == 0: 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]] filtered_catalog = [service_catalog[0]]
# There should be only one match. If not take the first. # There should be only one match. If not take the first.
_base_url = filtered_catalog[0].get('url', None) _base_url = filtered_catalog[0].get('url', None)

View File

@ -54,6 +54,8 @@ class RestClient(object):
:param auth_provider: an auth provider object used to wrap requests in auth :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 service: The service name to use for the catalog lookup
:param str region: The region 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 str endpoint_type: The endpoint type to use for the catalog lookup
:param int build_interval: Time in seconds between to status checks in :param int build_interval: Time in seconds between to status checks in
wait loops wait loops
@ -76,10 +78,11 @@ class RestClient(object):
endpoint_type='publicURL', endpoint_type='publicURL',
build_interval=1, build_timeout=60, build_interval=1, build_timeout=60,
disable_ssl_certificate_validation=False, ca_certs=None, disable_ssl_certificate_validation=False, ca_certs=None,
trace_requests=''): trace_requests='', name=None):
self.auth_provider = auth_provider self.auth_provider = auth_provider
self.service = service self.service = service
self.region = region self.region = region
self.name = name
self.endpoint_type = endpoint_type self.endpoint_type = endpoint_type
self.build_interval = build_interval self.build_interval = build_interval
self.build_timeout = build_timeout self.build_timeout = build_timeout
@ -191,7 +194,8 @@ class RestClient(object):
_filters = dict( _filters = dict(
service=self.service, service=self.service,
endpoint_type=self.endpoint_type, endpoint_type=self.endpoint_type,
region=self.region region=self.region,
name=self.name
) )
if self.api_version is not None: if self.api_version is not None:
_filters['api_version'] = self.api_version _filters['api_version'] = self.api_version

View File

@ -100,7 +100,8 @@ COMPUTE_ENDPOINTS_V3 = {
], ],
"type": "compute", "type": "compute",
"id": "fake_compute_endpoint" "id": "fake_compute_endpoint",
"name": "nova"
} }
CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ] CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ]

View File

@ -360,6 +360,58 @@ class TestKeystoneV2AuthProvider(BaseAuthTestsSetUp):
self.assertRaises(exceptions.EndpointNotFound, self.assertRaises(exceptions.EndpointNotFound,
self._test_base_url_helper, None, self.filters) 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): def test_base_url_with_api_version_filter(self):
self.filters = { self.filters = {
'service': 'compute', 'service': 'compute',

View File

@ -633,6 +633,7 @@ class TestProperties(BaseRestClientTestClass):
expected = {'api_version': 'v1', expected = {'api_version': 'v1',
'endpoint_type': 'publicURL', 'endpoint_type': 'publicURL',
'region': None, 'region': None,
'name': None,
'service': None, 'service': None,
'skip_path': True} 'skip_path': True}
self.rest_client.skip_path() self.rest_client.skip_path()
@ -643,6 +644,7 @@ class TestProperties(BaseRestClientTestClass):
expected = {'api_version': 'v1', expected = {'api_version': 'v1',
'endpoint_type': 'publicURL', 'endpoint_type': 'publicURL',
'region': None, 'region': None,
'name': None,
'service': None} 'service': None}
self.assertEqual(expected, self.rest_client.filters) self.assertEqual(expected, self.rest_client.filters)