From 00c05a18fa1ce224020b4d3f84f7eba002c9f091 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Wed, 5 Mar 2014 15:19:57 +1000 Subject: [PATCH] Add service name to catalog The service catalog can contain names for the services. These names can be filtered on by other clients and are going to be required if we expect to use the keystoneclient service catalog in other clients. Change-Id: Iab69d67427ed40ce2f556f9a6348f4f1d9c26b5b --- keystoneclient/service_catalog.py | 66 ++++++++++++++----- .../tests/v2_0/test_service_catalog.py | 23 +++++++ keystoneclient/tests/v3/client_fixtures.py | 9 ++- .../tests/v3/test_service_catalog.py | 48 ++++++++++++++ 4 files changed, 126 insertions(+), 20 deletions(-) diff --git a/keystoneclient/service_catalog.py b/keystoneclient/service_catalog.py index 917c59818..3018e346c 100644 --- a/keystoneclient/service_catalog.py +++ b/keystoneclient/service_catalog.py @@ -84,11 +84,15 @@ class ServiceCatalog(object): """ def get_endpoints(self, service_type=None, endpoint_type=None, - region_name=None): + region_name=None, service_name=None): """Fetch and filter endpoints for the specified service(s). Returns endpoints for the specified service (or all) containing - the specified type (or all) and region (or all). + the specified type (or all) and region (or all) and service name. + + If there is no name in the service catalog the service_name check will + be skipped. This allows compatibility with services that existed + before the name was available in the catalog. """ endpoint_type = self._normalize_endpoint_type(endpoint_type) region_name = region_name or self._region_name @@ -104,6 +108,21 @@ class ServiceCatalog(object): if service_type and service_type != st: continue + # NOTE(jamielennox): service_name is different. It is not available + # in API < v3.3. If it is in the catalog then we enforce it, if it + # is not then we don't because the name could be correct we just + # don't have that information to check against. + if service_name: + try: + sn = service['name'] + except KeyError: + # assume that we're in v3.0-v3.2 and don't have the name in + # the catalog. Skip the check. + pass + else: + if service_name != sn: + continue + sc[st] = [] for endpoint in service.get('endpoints', []): @@ -117,13 +136,14 @@ class ServiceCatalog(object): return sc def _get_service_endpoints(self, attr, filter_value, service_type, - endpoint_type, region_name): + endpoint_type, region_name, service_name): """Fetch the endpoints of a particular service_type and handle the filtering. """ sc_endpoints = self.get_endpoints(service_type=service_type, endpoint_type=endpoint_type, - region_name=region_name) + region_name=region_name, + service_name=service_name) try: endpoints = sc_endpoints[service_type] @@ -144,7 +164,7 @@ class ServiceCatalog(object): @utils.positional(enforcement=utils.positional.WARN) def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', - region_name=None): + region_name=None, service_name=None): """Fetch endpoint urls from the service catalog. Fetch the endpoints from the service catalog for a particular @@ -159,6 +179,7 @@ class ServiceCatalog(object): internal or internalURL, admin or adminURL :param string region_name: Region of the endpoint. + :param string service_name: The assigned name of the service. :returns: tuple of urls or None (if no match found) """ @@ -167,7 +188,7 @@ class ServiceCatalog(object): @utils.positional(3, enforcement=utils.positional.WARN) def url_for(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', - region_name=None): + region_name=None, service_name=None): """Fetch an endpoint from the service catalog. Fetch the specified endpoint from the service catalog for @@ -177,6 +198,14 @@ class ServiceCatalog(object): Valid endpoint types: `public` or `publicURL`, `internal` or `internalURL`, `admin` or 'adminURL` + + :param string attr: Endpoint attribute name. + :param string filter_value: Endpoint attribute value. + :param string service_type: Service type of the endpoint. + :param string endpoint_type: Type of endpoint. + :param string region_name: Region of the endpoint. + :param string service_name: The assigned name of the service. + : """ if not self.get_data(): raise exceptions.EmptyCatalog('The service catalog is empty.') @@ -185,19 +214,20 @@ class ServiceCatalog(object): filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, - region_name=region_name) + region_name=region_name, + service_name=service_name) try: return urls[0] except Exception: pass - msg = '%(endpoint)s endpoint for %(service)s%(region)s not found' - region = ' in %s region' % region_name if region_name else '' - msg = msg % {'endpoint': endpoint_type, - 'service': service_type, - 'region': region} - + msg = '%s endpoint for %s service' % (endpoint_type, service_type) + if service_name: + msg += ' named %s' % service_name + if region_name: + msg += ' in %s region' % region_name + msg += ' not found' raise exceptions.EndpointNotFound(msg) @abc.abstractmethod @@ -254,13 +284,14 @@ class ServiceCatalogV2(ServiceCatalog): @utils.positional(enforcement=utils.positional.WARN) def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', - region_name=None): + region_name=None, service_name=None): endpoint_type = self._normalize_endpoint_type(endpoint_type) endpoints = self._get_service_endpoints(attr=attr, filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, - region_name=region_name) + region_name=region_name, + service_name=service_name) if endpoints: return tuple([endpoint[endpoint_type] for endpoint in endpoints]) @@ -319,12 +350,13 @@ class ServiceCatalogV3(ServiceCatalog): @utils.positional(enforcement=utils.positional.WARN) def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='public', - region_name=None): + region_name=None, service_name=None): endpoints = self._get_service_endpoints(attr=attr, filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, - region_name=region_name) + region_name=region_name, + service_name=service_name) if endpoints: return tuple([endpoint['url'] for endpoint in endpoints]) diff --git a/keystoneclient/tests/v2_0/test_service_catalog.py b/keystoneclient/tests/v2_0/test_service_catalog.py index 60fd45073..bc219fd4e 100644 --- a/keystoneclient/tests/v2_0/test_service_catalog.py +++ b/keystoneclient/tests/v2_0/test_service_catalog.py @@ -150,3 +150,26 @@ class ServiceCatalogTest(utils.TestCase): self.assertEqual(len(endpoints['image']), 1) self.assertEqual(endpoints['image'][0]['publicURL'], 'https://image.south.host/v1/') + + def test_service_catalog_service_name(self): + auth_ref = access.AccessInfo.factory(resp=None, + body=self.AUTH_RESPONSE_BODY) + sc = auth_ref.service_catalog + + url = sc.url_for(service_name='Image Servers', endpoint_type='public', + service_type='image', region_name='North') + self.assertEqual('https://image.north.host/v1/', url) + + self.assertRaises(exceptions.EndpointNotFound, sc.url_for, + service_name='Image Servers', service_type='compute') + + urls = sc.get_urls(service_type='image', service_name='Image Servers', + endpoint_type='public') + + self.assertIn('https://image.north.host/v1/', urls) + self.assertIn('https://image.south.host/v1/', urls) + + urls = sc.get_urls(service_type='image', service_name='Servers', + endpoint_type='public') + + self.assertIsNone(urls) diff --git a/keystoneclient/tests/v3/client_fixtures.py b/keystoneclient/tests/v3/client_fixtures.py index 32958b4e6..87361c887 100644 --- a/keystoneclient/tests/v3/client_fixtures.py +++ b/keystoneclient/tests/v3/client_fixtures.py @@ -332,7 +332,8 @@ AUTH_RESPONSE_BODY = { 'region': 'North', 'interface': 'admin' }], - 'type': 'compute' + 'type': 'compute', + 'name': 'nova', }, { 'endpoints': [{ 'url': 'http://swift.north.host/swiftapi/public', @@ -347,7 +348,8 @@ AUTH_RESPONSE_BODY = { 'region': 'South', 'interface': 'admin' }], - 'type': 'object-store' + 'type': 'object-store', + 'name': 'swift', }, { 'endpoints': [{ 'url': 'http://glance.north.host/glanceapi/public', @@ -374,7 +376,8 @@ AUTH_RESPONSE_BODY = { 'region': 'South', 'interface': 'admin' }], - 'type': 'image' + 'type': 'image', + 'name': 'glance', }] } } diff --git a/keystoneclient/tests/v3/test_service_catalog.py b/keystoneclient/tests/v3/test_service_catalog.py index 504cbba8d..5ad303c01 100644 --- a/keystoneclient/tests/v3/test_service_catalog.py +++ b/keystoneclient/tests/v3/test_service_catalog.py @@ -171,3 +171,51 @@ class ServiceCatalogTest(utils.TestCase): for endpoint in endpoints['image']: self.assertEqual(endpoint['url'], self.south_endpoints[endpoint['interface']]) + + def test_service_catalog_service_name(self): + auth_ref = access.AccessInfo.factory(resp=None, + body=self.AUTH_RESPONSE_BODY) + sc = auth_ref.service_catalog + + url = sc.url_for(service_name='glance', endpoint_type='public', + service_type='image', region_name='North') + self.assertEqual('http://glance.north.host/glanceapi/public', url) + + url = sc.url_for(service_name='glance', endpoint_type='public', + service_type='image', region_name='South') + self.assertEqual('http://glance.south.host/glanceapi/public', url) + + self.assertRaises(exceptions.EndpointNotFound, sc.url_for, + service_name='glance', service_type='compute') + + urls = sc.get_urls(service_type='image', service_name='glance', + endpoint_type='public') + + self.assertIn('http://glance.north.host/glanceapi/public', urls) + self.assertIn('http://glance.south.host/glanceapi/public', urls) + + urls = sc.get_urls(service_type='image', service_name='Servers', + endpoint_type='public') + + self.assertIsNone(urls) + + def test_service_catalog_without_name(self): + pr_auth_ref = access.AccessInfo.factory( + resp=None, + body=client_fixtures.PROJECT_SCOPED_TOKEN) + pr_sc = pr_auth_ref.service_catalog + + # this will work because there are no service names on that token + url_ref = 'http://public.com:8774/v2/225da22d3ce34b15877ea70b2a575f58' + url = pr_sc.url_for(service_type='compute', service_name='NotExist', + endpoint_type='public') + self.assertEqual(url_ref, url) + + ab_auth_ref = access.AccessInfo.factory(resp=None, + body=self.AUTH_RESPONSE_BODY) + ab_sc = ab_auth_ref.service_catalog + + # this won't work because there is a name and it's not this one + self.assertRaises(exceptions.EndpointNotFound, ab_sc.url_for, + service_type='compute', service_name='NotExist', + endpoint_type='public')