Support a list of interface values
Sometimes, especially in places like service-to-service defaults, it's very helpful to express a list of values. For instance, when thinking about nova connecting to ironic, nova would like to have the default value of "interface" be ['internal', 'public'] - which is to say, use internal if it's there, but otherwise use public. This use case is covered in the API-WG specs on discoverability. Change-Id: I9102155c2d4ef1ef8bbb1d0fa26a5b5838108a4cchanges/69/477169/7
parent
46054f42d4
commit
2b949de8e9
|
@ -126,6 +126,13 @@ class ServiceCatalog(object):
|
|||
catalog.append(service)
|
||||
return catalog
|
||||
|
||||
def _get_interface_list(self, interface):
|
||||
if not interface:
|
||||
return []
|
||||
if not isinstance(interface, list):
|
||||
interface = [interface]
|
||||
return [self.normalize_interface(i) for i in interface]
|
||||
|
||||
@positional()
|
||||
def get_endpoints_data(self, service_type=None, interface=None,
|
||||
region_name=None, service_name=None,
|
||||
|
@ -139,9 +146,25 @@ class ServiceCatalog(object):
|
|||
be skipped. This allows compatibility with services that existed
|
||||
before the name was available in the catalog.
|
||||
|
||||
Valid interface types: `public` or `publicURL`,
|
||||
`internal` or `internalURL`,
|
||||
`admin` or 'adminURL`
|
||||
|
||||
:param string service_type: Service type of the endpoint.
|
||||
:param interface: Type of endpoint. Can be a single value or a list
|
||||
of values. If it's a list of values, they will be
|
||||
looked for in order of preference.
|
||||
:param string region_name: Region of the endpoint.
|
||||
:param string service_name: The assigned name of the service.
|
||||
:param string service_id: The identifier of a service.
|
||||
:param string endpoint_id: The identifier of an endpoint.
|
||||
|
||||
:returns: a list of matching EndpointData objects
|
||||
:rtype: list(`keystoneauth1.discover.EndpointData`)
|
||||
|
||||
:returns: a dict, keyed by service_type, of lists of EndpointData
|
||||
"""
|
||||
interface = self.normalize_interface(interface)
|
||||
interfaces = self._get_interface_list(interface)
|
||||
|
||||
matching_endpoints = {}
|
||||
|
||||
|
@ -161,12 +184,14 @@ class ServiceCatalog(object):
|
|||
matching_endpoints.setdefault(service['type'], [])
|
||||
|
||||
for endpoint in service.get('endpoints', []):
|
||||
if interface and interface != endpoint['interface']:
|
||||
if interfaces and endpoint['interface'] not in interfaces:
|
||||
continue
|
||||
if region_name and region_name != endpoint['region_name']:
|
||||
continue
|
||||
if endpoint_id and endpoint_id != endpoint['id']:
|
||||
continue
|
||||
if not endpoint['url']:
|
||||
continue
|
||||
|
||||
matching_endpoints[service['type']].append(
|
||||
discover.EndpointData(
|
||||
|
@ -179,7 +204,23 @@ class ServiceCatalog(object):
|
|||
endpoint_id=endpoint['id'],
|
||||
raw_endpoint=endpoint['raw_endpoint']))
|
||||
|
||||
return matching_endpoints
|
||||
if not interfaces:
|
||||
return matching_endpoints
|
||||
|
||||
ret = {}
|
||||
for service_type, endpoints in matching_endpoints.items():
|
||||
if not endpoints:
|
||||
ret[service_type] = []
|
||||
continue
|
||||
matches_by_interface = {}
|
||||
for endpoint in endpoints:
|
||||
matches_by_interface.setdefault(endpoint.interface, [])
|
||||
matches_by_interface[endpoint.interface].append(endpoint)
|
||||
best_interface = [i for i in interfaces
|
||||
if i in matches_by_interface.keys()][0]
|
||||
ret[service_type] = matches_by_interface[best_interface]
|
||||
|
||||
return ret
|
||||
|
||||
@positional()
|
||||
def get_endpoints(self, service_type=None, interface=None,
|
||||
|
@ -215,11 +256,14 @@ class ServiceCatalog(object):
|
|||
endpoint attribute. If no attribute is given, return the first
|
||||
endpoint of the specified type.
|
||||
|
||||
Valid interface types: `public` or `publicURL`,
|
||||
`internal` or `internalURL`,
|
||||
`admin` or 'adminURL`
|
||||
|
||||
:param string service_type: Service type of the endpoint.
|
||||
:param string interface: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL, admin or
|
||||
adminURL
|
||||
:param interface: Type of endpoint. Can be a single value or a list
|
||||
of values. If it's a list of values, they will be
|
||||
looked for in order of preference.
|
||||
:param string region_name: Region of the endpoint.
|
||||
:param string service_name: The assigned name of the service.
|
||||
:param string service_id: The identifier of a service.
|
||||
|
@ -246,11 +290,14 @@ class ServiceCatalog(object):
|
|||
endpoint attribute. If no attribute is given, return the url of the
|
||||
first endpoint of the specified type.
|
||||
|
||||
Valid interface types: `public` or `publicURL`,
|
||||
`internal` or `internalURL`,
|
||||
`admin` or 'adminURL`
|
||||
|
||||
:param string service_type: Service type of the endpoint.
|
||||
:param string interface: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL, admin or
|
||||
adminURL
|
||||
:param interface: Type of endpoint. Can be a single value or a list
|
||||
of values. If it's a list of values, they will be
|
||||
looked for in order of preference.
|
||||
:param string region_name: Region of the endpoint.
|
||||
:param string service_name: The assigned name of the service.
|
||||
:param string service_id: The identifier of a service.
|
||||
|
@ -281,7 +328,9 @@ class ServiceCatalog(object):
|
|||
`admin` or 'adminURL`
|
||||
|
||||
:param string service_type: Service type of the endpoint.
|
||||
:param string interface: Type of endpoint.
|
||||
:param interface: Type of endpoint. Can be a single value or a list
|
||||
of values. If it's a list of values, they will be
|
||||
looked for in order of preference.
|
||||
:param string region_name: Region of the endpoint.
|
||||
:param string service_name: The assigned name of the service.
|
||||
:param string service_id: The identifier of a service.
|
||||
|
@ -309,7 +358,9 @@ class ServiceCatalog(object):
|
|||
`admin` or 'adminURL`
|
||||
|
||||
:param string service_type: Service type of the endpoint.
|
||||
:param string interface: Type of endpoint.
|
||||
:param interface: Type of endpoint. Can be a single value or a list
|
||||
of values. If it's a list of values, they will be
|
||||
looked for in order of preference.
|
||||
:param string region_name: Region of the endpoint.
|
||||
:param string service_name: The assigned name of the service.
|
||||
:param string service_id: The identifier of a service.
|
||||
|
|
|
@ -171,16 +171,21 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
version, min_version and max_version can all be given either as a
|
||||
string or a tuple.
|
||||
|
||||
Valid interface types: `public` or `publicURL`,
|
||||
`internal` or `internalURL`,
|
||||
`admin` or 'adminURL`
|
||||
|
||||
:param session: A session object that can be used for communication.
|
||||
:type session: keystoneauth1.session.Session
|
||||
:param string service_type: The type of service to lookup the endpoint
|
||||
for. This plugin will return None (failure)
|
||||
if service_type is not provided.
|
||||
:param string interface: The exposure of the endpoint. Should be
|
||||
`public`, `internal`, `admin`, or `auth`.
|
||||
`auth` is special here to use the `auth_url`
|
||||
rather than a URL extracted from the service
|
||||
catalog. Defaults to `public`.
|
||||
:param interface: Type of endpoint. Can be a single value or a list
|
||||
of values. If it's a list of values, they will be
|
||||
looked for in order of preference. Can also be
|
||||
`keystoneauth1.plugin.AUTH_INTERFACE` to indicate
|
||||
that the auth_url should be used instead of the
|
||||
value in the catalog. (optional, defaults to public)
|
||||
:param string region_name: The region the endpoint should exist in.
|
||||
(optional)
|
||||
:param string service_name: The name of the service in the catalog.
|
||||
|
@ -310,16 +315,21 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
version, min_version and max_version can all be given either as a
|
||||
string or a tuple.
|
||||
|
||||
Valid interface types: `public` or `publicURL`,
|
||||
`internal` or `internalURL`,
|
||||
`admin` or 'adminURL`
|
||||
|
||||
:param session: A session object that can be used for communication.
|
||||
:type session: keystoneauth1.session.Session
|
||||
:param string service_type: The type of service to lookup the endpoint
|
||||
for. This plugin will return None (failure)
|
||||
if service_type is not provided.
|
||||
:param string interface: The exposure of the endpoint. Should be
|
||||
`public`, `internal`, `admin`, or `auth`.
|
||||
`auth` is special here to use the `auth_url`
|
||||
rather than a URL extracted from the service
|
||||
catalog. Defaults to `public`.
|
||||
:param interface: Type of endpoint. Can be a single value or a list
|
||||
of values. If it's a list of values, they will be
|
||||
looked for in order of preference. Can also be
|
||||
`keystoneauth1.plugin.AUTH_INTERFACE` to indicate
|
||||
that the auth_url should be used instead of the
|
||||
value in the catalog. (optional, defaults to public)
|
||||
:param string region_name: The region the endpoint should exist in.
|
||||
(optional)
|
||||
:param string service_name: The name of the service in the catalog.
|
||||
|
|
|
@ -62,6 +62,9 @@ class CommonIdentityTests(object):
|
|||
TEST_VOLUME_V3_CATALOG_ADMIN = (
|
||||
TEST_VOLUME_V3_SERVICE_ADMIN + '/{project_id}')
|
||||
|
||||
TEST_BAREMETAL_BASE = 'http://ironic'
|
||||
TEST_BAREMETAL_INTERNAL = TEST_BAREMETAL_BASE + '/internal'
|
||||
|
||||
TEST_PASS = uuid.uuid4().hex
|
||||
|
||||
def setUp(self):
|
||||
|
@ -522,61 +525,26 @@ class CommonIdentityTests(object):
|
|||
self.assertEqual(v3_compute, v3_data.service_url)
|
||||
self.assertEqual(self.TEST_COMPUTE_ADMIN, v3_data.catalog_url)
|
||||
|
||||
def test_get_versioned_data_project_id(self):
|
||||
|
||||
# need to construct list this way for relative
|
||||
disc = fixture.DiscoveryList(v2=False, v3=False)
|
||||
# The version discovery dict will not have a project_id
|
||||
disc.add_microversion(
|
||||
href=self.TEST_VOLUME_V3_SERVICE_PUBLIC,
|
||||
id='v3.0', status='CURRENT',
|
||||
min_version='3.0', max_version='3.20')
|
||||
# Adding a v2 version to a service named volumev3 is not
|
||||
# an error. The service itself is cinder and has more than
|
||||
# one major version.
|
||||
disc.add_microversion(
|
||||
href=self.TEST_VOLUME_V2_SERVICE_PUBLIC,
|
||||
id='v2.0', status='SUPPORTED')
|
||||
|
||||
# We should only try to fetch the non-project_id url and only
|
||||
# once
|
||||
resps = [{'json': disc}, {'status_code': 500}]
|
||||
self.requests_mock.get(self.TEST_VOLUME_V3_SERVICE_PUBLIC, resps)
|
||||
|
||||
body = 'SUCCESS'
|
||||
self.stub_url('GET', ['path'], text=body)
|
||||
def test_interface_list(self):
|
||||
|
||||
a = self.create_auth_plugin()
|
||||
s = session.Session(auth=a)
|
||||
|
||||
v2_catalog_url = self.TEST_VOLUME_V2_CATALOG_PUBLIC.format(
|
||||
project_id=self.project_id)
|
||||
v3_catalog_url = self.TEST_VOLUME_V3_CATALOG_PUBLIC.format(
|
||||
project_id=self.project_id)
|
||||
ep = s.get_endpoint(service_type='baremetal',
|
||||
interface=['internal', 'public'])
|
||||
self.assertEqual(ep, self.TEST_BAREMETAL_INTERNAL)
|
||||
|
||||
data = a.get_endpoint_data(session=s,
|
||||
service_type='volumev3',
|
||||
interface='public')
|
||||
self.assertEqual(v3_catalog_url, data.url)
|
||||
ep = s.get_endpoint(service_type='baremetal',
|
||||
interface=['public', 'internal'])
|
||||
self.assertEqual(ep, self.TEST_BAREMETAL_INTERNAL)
|
||||
|
||||
v3_data = data.get_versioned_data(
|
||||
s, version='3.0', project_id=self.project_id)
|
||||
self.assertEqual(v3_catalog_url, v3_data.url)
|
||||
self.assertEqual(v3_catalog_url, v3_data.service_url)
|
||||
self.assertEqual(v3_catalog_url, v3_data.catalog_url)
|
||||
self.assertEqual((3, 0), v3_data.min_microversion)
|
||||
self.assertEqual((3, 20), v3_data.max_microversion)
|
||||
ep = s.get_endpoint(service_type='compute',
|
||||
interface=['internal', 'public'])
|
||||
self.assertEqual(ep, self.TEST_COMPUTE_INTERNAL)
|
||||
|
||||
v2_data = data.get_versioned_data(
|
||||
s, version='2.0', project_id=self.project_id)
|
||||
# Even though we never requested volumev2 from the catalog, we should
|
||||
# wind up re-constructing it via version discovery and re-appending
|
||||
# the project_id to the URL
|
||||
self.assertEqual(v2_catalog_url, v2_data.url)
|
||||
self.assertEqual(v2_catalog_url, v2_data.service_url)
|
||||
self.assertEqual(v3_catalog_url, v2_data.catalog_url)
|
||||
self.assertEqual(None, v2_data.min_microversion)
|
||||
self.assertEqual(None, v2_data.max_microversion)
|
||||
ep = s.get_endpoint(service_type='compute',
|
||||
interface=['public', 'internal'])
|
||||
self.assertEqual(ep, self.TEST_COMPUTE_PUBLIC)
|
||||
|
||||
def test_get_versioned_data_compute_project_id(self):
|
||||
|
||||
|
@ -782,6 +750,11 @@ class V3(CommonIdentityTests, utils.TestCase):
|
|||
internal=self.TEST_VOLUME_V3_CATALOG_INTERNAL.format(**kwargs),
|
||||
region=region)
|
||||
|
||||
svc = token.add_service('baremetal')
|
||||
svc.add_standard_endpoints(
|
||||
internal=self.TEST_BAREMETAL_INTERNAL,
|
||||
region=region)
|
||||
|
||||
return token
|
||||
|
||||
def stub_auth(self, subject_token=None, **kwargs):
|
||||
|
@ -850,6 +823,12 @@ class V2(CommonIdentityTests, utils.TestCase):
|
|||
internal=self.TEST_VOLUME_V3_CATALOG_INTERNAL.format(**kwargs),
|
||||
region=region)
|
||||
|
||||
svc = token.add_service('baremetal')
|
||||
svc.add_endpoint(
|
||||
public=None, admin=None,
|
||||
internal=self.TEST_BAREMETAL_INTERNAL,
|
||||
region=region)
|
||||
|
||||
return token
|
||||
|
||||
def stub_auth(self, **kwargs):
|
||||
|
|
Loading…
Reference in New Issue