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:
parent
7b297d48b2
commit
79cd91e755
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added support for service-type aliases as defined in the Service Types
|
||||
Authority when doing catalog lookups.
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user