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():
|
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
|
continue
|
||||||
|
|
||||||
if (service_name and service['name'] and
|
if (service_name and service['name'] and
|
||||||
@ -203,7 +204,7 @@ class ServiceCatalog(object):
|
|||||||
raw_endpoint=endpoint['raw_endpoint']))
|
raw_endpoint=endpoint['raw_endpoint']))
|
||||||
|
|
||||||
if not interfaces:
|
if not interfaces:
|
||||||
return matching_endpoints
|
return self._endpoints_by_type(service_type, matching_endpoints)
|
||||||
|
|
||||||
ret = {}
|
ret = {}
|
||||||
for matched_service_type, endpoints in matching_endpoints.items():
|
for matched_service_type, endpoints in matching_endpoints.items():
|
||||||
@ -218,7 +219,68 @@ class ServiceCatalog(object):
|
|||||||
if i in matches_by_interface.keys()][0]
|
if i in matches_by_interface.keys()][0]
|
||||||
ret[matched_service_type] = matches_by_interface[best_interface]
|
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,
|
def get_endpoints(self, service_type=None, interface=None,
|
||||||
region_name=None, service_name=None,
|
region_name=None, service_name=None,
|
||||||
|
@ -24,6 +24,7 @@ raw data specified in version discovery responses.
|
|||||||
import copy
|
import copy
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import os_service_types
|
||||||
import six
|
import six
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ from keystoneauth1 import exceptions
|
|||||||
_LOGGER = utils.get_logger(__name__)
|
_LOGGER = utils.get_logger(__name__)
|
||||||
|
|
||||||
LATEST = float('inf')
|
LATEST = float('inf')
|
||||||
|
_SERVICE_TYPES = os_service_types.ServiceTypes()
|
||||||
|
|
||||||
|
|
||||||
def _str_or_latest(val):
|
def _str_or_latest(val):
|
||||||
|
@ -56,6 +56,27 @@ class ServiceCatalogTest(utils.TestCase):
|
|||||||
admin='http://glance.south.host/glanceapi/admin',
|
admin='http://glance.south.host/glanceapi/admin',
|
||||||
region='South')
|
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':
|
self.north_endpoints = {'public':
|
||||||
'http://glance.north.host/glanceapi/public',
|
'http://glance.north.host/glanceapi/public',
|
||||||
'internal':
|
'internal':
|
||||||
@ -97,6 +118,66 @@ class ServiceCatalogTest(utils.TestCase):
|
|||||||
self.assertEqual(public_ep['compute'][0]['url'],
|
self.assertEqual(public_ep['compute'][0]['url'],
|
||||||
"https://compute.north.host/novapi/public")
|
"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):
|
def test_service_catalog_regions(self):
|
||||||
self.AUTH_RESPONSE_BODY['token']['region_name'] = "North"
|
self.AUTH_RESPONSE_BODY['token']['region_name'] = "North"
|
||||||
auth_ref = access.create(auth_token=uuid.uuid4().hex,
|
auth_ref = access.create(auth_token=uuid.uuid4().hex,
|
||||||
|
@ -37,6 +37,7 @@ oauthlib==0.6.2
|
|||||||
openstack-requirements==1.2.0
|
openstack-requirements==1.2.0
|
||||||
openstackdocstheme==1.18.1
|
openstackdocstheme==1.18.1
|
||||||
os-client-config==1.29.0
|
os-client-config==1.29.0
|
||||||
|
os-service-types==1.2.0
|
||||||
os-testr==1.0.0
|
os-testr==1.0.0
|
||||||
oslo.config==5.2.0
|
oslo.config==5.2.0
|
||||||
oslo.i18n==3.20.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
|
requests>=2.14.2 # Apache-2.0
|
||||||
six>=1.10.0 # MIT
|
six>=1.10.0 # MIT
|
||||||
stevedore>=1.20.0 # Apache-2.0
|
stevedore>=1.20.0 # Apache-2.0
|
||||||
|
os-service-types>=1.2.0 # Apache-2.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user