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: I9102155c2d4ef1ef8bbb1d0fa26a5b5838108a4c
This commit is contained in:
parent
46054f42d4
commit
2b949de8e9
|
@ -126,6 +126,13 @@ class ServiceCatalog(object):
|
||||||
catalog.append(service)
|
catalog.append(service)
|
||||||
return catalog
|
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()
|
@positional()
|
||||||
def get_endpoints_data(self, service_type=None, interface=None,
|
def get_endpoints_data(self, service_type=None, interface=None,
|
||||||
region_name=None, service_name=None,
|
region_name=None, service_name=None,
|
||||||
|
@ -139,9 +146,25 @@ class ServiceCatalog(object):
|
||||||
be skipped. This allows compatibility with services that existed
|
be skipped. This allows compatibility with services that existed
|
||||||
before the name was available in the catalog.
|
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
|
:returns: a dict, keyed by service_type, of lists of EndpointData
|
||||||
"""
|
"""
|
||||||
interface = self.normalize_interface(interface)
|
interfaces = self._get_interface_list(interface)
|
||||||
|
|
||||||
matching_endpoints = {}
|
matching_endpoints = {}
|
||||||
|
|
||||||
|
@ -161,12 +184,14 @@ class ServiceCatalog(object):
|
||||||
matching_endpoints.setdefault(service['type'], [])
|
matching_endpoints.setdefault(service['type'], [])
|
||||||
|
|
||||||
for endpoint in service.get('endpoints', []):
|
for endpoint in service.get('endpoints', []):
|
||||||
if interface and interface != endpoint['interface']:
|
if interfaces and endpoint['interface'] not in interfaces:
|
||||||
continue
|
continue
|
||||||
if region_name and region_name != endpoint['region_name']:
|
if region_name and region_name != endpoint['region_name']:
|
||||||
continue
|
continue
|
||||||
if endpoint_id and endpoint_id != endpoint['id']:
|
if endpoint_id and endpoint_id != endpoint['id']:
|
||||||
continue
|
continue
|
||||||
|
if not endpoint['url']:
|
||||||
|
continue
|
||||||
|
|
||||||
matching_endpoints[service['type']].append(
|
matching_endpoints[service['type']].append(
|
||||||
discover.EndpointData(
|
discover.EndpointData(
|
||||||
|
@ -179,7 +204,23 @@ class ServiceCatalog(object):
|
||||||
endpoint_id=endpoint['id'],
|
endpoint_id=endpoint['id'],
|
||||||
raw_endpoint=endpoint['raw_endpoint']))
|
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()
|
@positional()
|
||||||
def get_endpoints(self, service_type=None, interface=None,
|
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 attribute. If no attribute is given, return the first
|
||||||
endpoint of the specified type.
|
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 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
|
||||||
Possible values: public or publicURL,
|
of values. If it's a list of values, they will be
|
||||||
internal or internalURL, admin or
|
looked for in order of preference.
|
||||||
adminURL
|
|
||||||
:param string region_name: Region of the endpoint.
|
:param string region_name: Region of the endpoint.
|
||||||
:param string service_name: The assigned name of the service.
|
:param string service_name: The assigned name of the service.
|
||||||
:param string service_id: The identifier of a 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
|
endpoint attribute. If no attribute is given, return the url of the
|
||||||
first endpoint of the specified type.
|
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 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
|
||||||
Possible values: public or publicURL,
|
of values. If it's a list of values, they will be
|
||||||
internal or internalURL, admin or
|
looked for in order of preference.
|
||||||
adminURL
|
|
||||||
:param string region_name: Region of the endpoint.
|
:param string region_name: Region of the endpoint.
|
||||||
:param string service_name: The assigned name of the service.
|
:param string service_name: The assigned name of the service.
|
||||||
:param string service_id: The identifier of a service.
|
:param string service_id: The identifier of a service.
|
||||||
|
@ -281,7 +328,9 @@ class ServiceCatalog(object):
|
||||||
`admin` or 'adminURL`
|
`admin` or 'adminURL`
|
||||||
|
|
||||||
:param string service_type: Service type of the endpoint.
|
: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 region_name: Region of the endpoint.
|
||||||
:param string service_name: The assigned name of the service.
|
:param string service_name: The assigned name of the service.
|
||||||
:param string service_id: The identifier of a service.
|
:param string service_id: The identifier of a service.
|
||||||
|
@ -309,7 +358,9 @@ class ServiceCatalog(object):
|
||||||
`admin` or 'adminURL`
|
`admin` or 'adminURL`
|
||||||
|
|
||||||
:param string service_type: Service type of the endpoint.
|
: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 region_name: Region of the endpoint.
|
||||||
:param string service_name: The assigned name of the service.
|
:param string service_name: The assigned name of the service.
|
||||||
:param string service_id: The identifier of a 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
|
version, min_version and max_version can all be given either as a
|
||||||
string or a tuple.
|
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.
|
:param session: A session object that can be used for communication.
|
||||||
:type session: keystoneauth1.session.Session
|
:type session: keystoneauth1.session.Session
|
||||||
:param string service_type: The type of service to lookup the endpoint
|
:param string service_type: The type of service to lookup the endpoint
|
||||||
for. This plugin will return None (failure)
|
for. This plugin will return None (failure)
|
||||||
if service_type is not provided.
|
if service_type is not provided.
|
||||||
:param string interface: The exposure of the endpoint. Should be
|
:param interface: Type of endpoint. Can be a single value or a list
|
||||||
`public`, `internal`, `admin`, or `auth`.
|
of values. If it's a list of values, they will be
|
||||||
`auth` is special here to use the `auth_url`
|
looked for in order of preference. Can also be
|
||||||
rather than a URL extracted from the service
|
`keystoneauth1.plugin.AUTH_INTERFACE` to indicate
|
||||||
catalog. Defaults to `public`.
|
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.
|
:param string region_name: The region the endpoint should exist in.
|
||||||
(optional)
|
(optional)
|
||||||
:param string service_name: The name of the service in the catalog.
|
: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
|
version, min_version and max_version can all be given either as a
|
||||||
string or a tuple.
|
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.
|
:param session: A session object that can be used for communication.
|
||||||
:type session: keystoneauth1.session.Session
|
:type session: keystoneauth1.session.Session
|
||||||
:param string service_type: The type of service to lookup the endpoint
|
:param string service_type: The type of service to lookup the endpoint
|
||||||
for. This plugin will return None (failure)
|
for. This plugin will return None (failure)
|
||||||
if service_type is not provided.
|
if service_type is not provided.
|
||||||
:param string interface: The exposure of the endpoint. Should be
|
:param interface: Type of endpoint. Can be a single value or a list
|
||||||
`public`, `internal`, `admin`, or `auth`.
|
of values. If it's a list of values, they will be
|
||||||
`auth` is special here to use the `auth_url`
|
looked for in order of preference. Can also be
|
||||||
rather than a URL extracted from the service
|
`keystoneauth1.plugin.AUTH_INTERFACE` to indicate
|
||||||
catalog. Defaults to `public`.
|
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.
|
:param string region_name: The region the endpoint should exist in.
|
||||||
(optional)
|
(optional)
|
||||||
:param string service_name: The name of the service in the catalog.
|
: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_CATALOG_ADMIN = (
|
||||||
TEST_VOLUME_V3_SERVICE_ADMIN + '/{project_id}')
|
TEST_VOLUME_V3_SERVICE_ADMIN + '/{project_id}')
|
||||||
|
|
||||||
|
TEST_BAREMETAL_BASE = 'http://ironic'
|
||||||
|
TEST_BAREMETAL_INTERNAL = TEST_BAREMETAL_BASE + '/internal'
|
||||||
|
|
||||||
TEST_PASS = uuid.uuid4().hex
|
TEST_PASS = uuid.uuid4().hex
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -522,61 +525,26 @@ class CommonIdentityTests(object):
|
||||||
self.assertEqual(v3_compute, v3_data.service_url)
|
self.assertEqual(v3_compute, v3_data.service_url)
|
||||||
self.assertEqual(self.TEST_COMPUTE_ADMIN, v3_data.catalog_url)
|
self.assertEqual(self.TEST_COMPUTE_ADMIN, v3_data.catalog_url)
|
||||||
|
|
||||||
def test_get_versioned_data_project_id(self):
|
def test_interface_list(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)
|
|
||||||
|
|
||||||
a = self.create_auth_plugin()
|
a = self.create_auth_plugin()
|
||||||
s = session.Session(auth=a)
|
s = session.Session(auth=a)
|
||||||
|
|
||||||
v2_catalog_url = self.TEST_VOLUME_V2_CATALOG_PUBLIC.format(
|
ep = s.get_endpoint(service_type='baremetal',
|
||||||
project_id=self.project_id)
|
interface=['internal', 'public'])
|
||||||
v3_catalog_url = self.TEST_VOLUME_V3_CATALOG_PUBLIC.format(
|
self.assertEqual(ep, self.TEST_BAREMETAL_INTERNAL)
|
||||||
project_id=self.project_id)
|
|
||||||
|
|
||||||
data = a.get_endpoint_data(session=s,
|
ep = s.get_endpoint(service_type='baremetal',
|
||||||
service_type='volumev3',
|
interface=['public', 'internal'])
|
||||||
interface='public')
|
self.assertEqual(ep, self.TEST_BAREMETAL_INTERNAL)
|
||||||
self.assertEqual(v3_catalog_url, data.url)
|
|
||||||
|
|
||||||
v3_data = data.get_versioned_data(
|
ep = s.get_endpoint(service_type='compute',
|
||||||
s, version='3.0', project_id=self.project_id)
|
interface=['internal', 'public'])
|
||||||
self.assertEqual(v3_catalog_url, v3_data.url)
|
self.assertEqual(ep, self.TEST_COMPUTE_INTERNAL)
|
||||||
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)
|
|
||||||
|
|
||||||
v2_data = data.get_versioned_data(
|
ep = s.get_endpoint(service_type='compute',
|
||||||
s, version='2.0', project_id=self.project_id)
|
interface=['public', 'internal'])
|
||||||
# Even though we never requested volumev2 from the catalog, we should
|
self.assertEqual(ep, self.TEST_COMPUTE_PUBLIC)
|
||||||
# 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)
|
|
||||||
|
|
||||||
def test_get_versioned_data_compute_project_id(self):
|
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),
|
internal=self.TEST_VOLUME_V3_CATALOG_INTERNAL.format(**kwargs),
|
||||||
region=region)
|
region=region)
|
||||||
|
|
||||||
|
svc = token.add_service('baremetal')
|
||||||
|
svc.add_standard_endpoints(
|
||||||
|
internal=self.TEST_BAREMETAL_INTERNAL,
|
||||||
|
region=region)
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def stub_auth(self, subject_token=None, **kwargs):
|
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),
|
internal=self.TEST_VOLUME_V3_CATALOG_INTERNAL.format(**kwargs),
|
||||||
region=region)
|
region=region)
|
||||||
|
|
||||||
|
svc = token.add_service('baremetal')
|
||||||
|
svc.add_endpoint(
|
||||||
|
public=None, admin=None,
|
||||||
|
internal=self.TEST_BAREMETAL_INTERNAL,
|
||||||
|
region=region)
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def stub_auth(self, **kwargs):
|
def stub_auth(self, **kwargs):
|
||||||
|
|
Loading…
Reference in New Issue