Implement service_type alias lookups

The Service Types Authority has grown support for aliases, and the
os-service-types library exposes the data. Add support for matching
known aliases when matching endpoints for a user.

Change-Id: Ie90c265cb17905981d877abfaaa52354a3e63692
This commit is contained in:
Monty Taylor 2018-04-13 08:51:55 -05:00
parent 7b297d48b2
commit 79cd91e755
No known key found for this signature in database
GPG Key ID: 7BAE94BC7141A594
6 changed files with 155 additions and 3 deletions

View File

@ -168,7 +168,8 @@ class ServiceCatalog(object):
for service in self.normalize_catalog():
if service_type and service_type != service['type']:
if service_type and not discover._SERVICE_TYPES.is_match(
service_type, service['type']):
continue
if (service_name and service['name'] and
@ -203,7 +204,7 @@ class ServiceCatalog(object):
raw_endpoint=endpoint['raw_endpoint']))
if not interfaces:
return matching_endpoints
return self._endpoints_by_type(service_type, matching_endpoints)
ret = {}
for matched_service_type, endpoints in matching_endpoints.items():
@ -218,7 +219,68 @@ class ServiceCatalog(object):
if i in matches_by_interface.keys()][0]
ret[matched_service_type] = matches_by_interface[best_interface]
return ret
return self._endpoints_by_type(service_type, ret)
def _endpoints_by_type(self, requested, endpoints):
"""Get the approrpriate endpoints from the list of given endpoints.
Per the service type alias rules:
If a user requests a service by its proper name and that matches, win.
If a user requests a service by its proper name and only a single alias
matches, win.
If a user requests a service by its proper name and more than one alias
matches, choose the first alias from the list given.
Do the "first alias" match after the other filters, as they might limit
the number of choices for us otherwise.
:param str requested:
The service_type as requested by the user.
:param dict sc:
A dictionary keyed by found service_type. Values are opaque to
this method.
:returns:
Dict of service_type/endpoints filtered for the appropriate
service_type based on alias matching rules.
"""
if not requested or not discover._SERVICE_TYPES.is_known(requested):
# The user did not request a service we have any alias information
# about, or did not request a service, which means that we cannot
# further filter the list.
return endpoints
if len(endpoints) < 2:
# There is at most one type found from the initial pass through
# the catalog. Nothing further to do.
return endpoints
# At this point, the user has requested a type, we do know things
# about aliases for that type, and we've found more than one match.
# We must filter out additional types, otherwise clouds that register
# the same endpoint twice as part of a migration will confuse users.
# Only return the one the user requested if there's an exact match
# and there is data for it. There might not be data for this match
# if there are other filters that excluded it from consideration
# after we accepted it as an alias.
if endpoints.get(requested):
return {requested: endpoints[requested]}
# We've matched something that isn't exactly what the user requested.
# Look at the possible types for this service in order or priority and
# return the first match.
for alias in discover._SERVICE_TYPES.get_all_types(requested):
if endpoints.get(alias):
# Return the first one found in the order listed.
return {alias: endpoints[alias]}
# We should never get here - it's a programming logic error on our
# part if we do. Raise this so that we can panic in unit tests.
raise ValueError("Programming error choosing an endpoint.")
def get_endpoints(self, service_type=None, interface=None,
region_name=None, service_name=None,

View File

@ -24,6 +24,7 @@ raw data specified in version discovery responses.
import copy
import re
import os_service_types
import six
from six.moves import urllib
@ -34,6 +35,7 @@ from keystoneauth1 import exceptions
_LOGGER = utils.get_logger(__name__)
LATEST = float('inf')
_SERVICE_TYPES = os_service_types.ServiceTypes()
def _str_or_latest(val):

View File

@ -56,6 +56,27 @@ class ServiceCatalogTest(utils.TestCase):
admin='http://glance.south.host/glanceapi/admin',
region='South')
s = self.AUTH_RESPONSE_BODY.add_service('block-storage', name='cinder')
s.add_standard_endpoints(
public='http://cinder.north.host/cinderapi/public',
internal='http://cinder.north.host/cinderapi/internal',
admin='http://cinder.north.host/cinderapi/admin',
region='North')
s = self.AUTH_RESPONSE_BODY.add_service('volumev2', name='cinder')
s.add_standard_endpoints(
public='http://cinder.south.host/cinderapi/public/v2',
internal='http://cinder.south.host/cinderapi/internal/v2',
admin='http://cinder.south.host/cinderapi/admin/v2',
region='South')
s = self.AUTH_RESPONSE_BODY.add_service('volumev3', name='cinder')
s.add_standard_endpoints(
public='http://cinder.south.host/cinderapi/public/v3',
internal='http://cinder.south.host/cinderapi/internal/v3',
admin='http://cinder.south.host/cinderapi/admin/v3',
region='South')
self.north_endpoints = {'public':
'http://glance.north.host/glanceapi/public',
'internal':
@ -97,6 +118,66 @@ class ServiceCatalogTest(utils.TestCase):
self.assertEqual(public_ep['compute'][0]['url'],
"https://compute.north.host/novapi/public")
def test_service_catalog_alias_find_official(self):
auth_ref = access.create(auth_token=uuid.uuid4().hex,
body=self.AUTH_RESPONSE_BODY)
sc = auth_ref.service_catalog
# Tests that we find the block-storage endpoint when we request
# the volume endpoint.
public_ep = sc.get_endpoints(service_type='volume',
interface='public',
region_name='North')
self.assertEqual(public_ep['block-storage'][0]['region'], 'North')
self.assertEqual(public_ep['block-storage'][0]['url'],
"http://cinder.north.host/cinderapi/public")
def test_service_catalog_alias_find_exact_match(self):
auth_ref = access.create(auth_token=uuid.uuid4().hex,
body=self.AUTH_RESPONSE_BODY)
sc = auth_ref.service_catalog
# Tests that we find the volumev3 endpoint when we request it.
public_ep = sc.get_endpoints(service_type='volumev3',
interface='public')
self.assertEqual(public_ep['volumev3'][0]['region'], 'South')
self.assertEqual(public_ep['volumev3'][0]['url'],
"http://cinder.south.host/cinderapi/public/v3")
def test_service_catalog_alias_find_best_match(self):
auth_ref = access.create(auth_token=uuid.uuid4().hex,
body=self.AUTH_RESPONSE_BODY)
sc = auth_ref.service_catalog
# Tests that we find the volumev3 endpoint when we request
# block-storage when only volumev2 and volumev3 are present since
# volumev3 comes first in the list.
public_ep = sc.get_endpoints(service_type='block-storage',
interface='public',
region_name='South')
self.assertEqual(public_ep['volumev3'][0]['region'], 'South')
self.assertEqual(public_ep['volumev3'][0]['url'],
"http://cinder.south.host/cinderapi/public/v3")
def test_service_catalog_alias_all_by_name(self):
auth_ref = access.create(auth_token=uuid.uuid4().hex,
body=self.AUTH_RESPONSE_BODY)
sc = auth_ref.service_catalog
# Tests that we find all the cinder endpoints since we request
# them by name and that no filtering related to aliases happens.
public_ep = sc.get_endpoints(service_name='cinder',
interface='public')
self.assertEqual(public_ep['volumev2'][0]['region'], 'South')
self.assertEqual(public_ep['volumev2'][0]['url'],
"http://cinder.south.host/cinderapi/public/v2")
self.assertEqual(public_ep['volumev3'][0]['region'], 'South')
self.assertEqual(public_ep['volumev3'][0]['url'],
"http://cinder.south.host/cinderapi/public/v3")
self.assertEqual(public_ep['block-storage'][0]['region'], 'North')
self.assertEqual(public_ep['block-storage'][0]['url'],
"http://cinder.north.host/cinderapi/public")
def test_service_catalog_regions(self):
self.AUTH_RESPONSE_BODY['token']['region_name'] = "North"
auth_ref = access.create(auth_token=uuid.uuid4().hex,

View File

@ -37,6 +37,7 @@ oauthlib==0.6.2
openstack-requirements==1.2.0
openstackdocstheme==1.18.1
os-client-config==1.29.0
os-service-types==1.2.0
os-testr==1.0.0
oslo.config==5.2.0
oslo.i18n==3.20.0

View File

@ -0,0 +1,5 @@
---
features:
- |
Added support for service-type aliases as defined in the Service Types
Authority when doing catalog lookups.

View File

@ -16,3 +16,4 @@ iso8601>=0.1.11 # MIT
requests>=2.14.2 # Apache-2.0
six>=1.10.0 # MIT
stevedore>=1.20.0 # Apache-2.0
os-service-types>=1.2.0 # Apache-2.0