Fixing multi-region support in templated v3 catalog

Previously each region had its own entry for each service_type
with only it's own endpoints listed. This patch changes this
so that each service type has the endpoints from all regions
listed.

We also move the flawed templated get_v3_catalog function call
from the base catalog class into the templated class.

Change-Id: Ifddf08990539b6ac7d8289d410092b2ae9f5cbed
Closes-Bug: #1703666
This commit is contained in:
Erik Olof Gunnar Andersson 2017-07-10 17:43:20 -07:00
parent 7bf25736bd
commit 1483e056bd
5 changed files with 141 additions and 34 deletions

View File

@ -240,8 +240,6 @@ class CatalogDriverBase(provider_api.ProviderAPIMixin, object):
def get_v3_catalog(self, user_id, project_id):
"""Retrieve and format the current V3 service catalog.
The default implementation builds the V3 catalog from the V2 catalog.
Example::
[
@ -267,38 +265,7 @@ class CatalogDriverBase(provider_api.ProviderAPIMixin, object):
:raises keystone.exception.NotFound: If the endpoint doesn't exist.
"""
v2_catalog = self.get_catalog(user_id, project_id)
v3_catalog = []
for region_name, region in v2_catalog.items():
for service_type, service in region.items():
service_v3 = {
'type': service_type,
'endpoints': []
}
for attr, value in service.items():
# Attributes that end in URL are interfaces. In the V2
# catalog, these are internalURL, publicURL, and adminURL.
# For example, <region_name>.publicURL=<URL> in the V2
# catalog becomes the V3 interface for the service:
# { 'interface': 'public', 'url': '<URL>', 'region':
# 'region: '<region_name>' }
if attr.endswith('URL'):
v3_interface = attr[:-len('URL')]
service_v3['endpoints'].append({
'interface': v3_interface,
'region': region_name,
'url': value,
})
continue
# Other attributes are copied to the service.
service_v3[attr] = value
v3_catalog.append(service_v3)
return v3_catalog
raise exception.NotImplemented() # pragma: no cover
@abc.abstractmethod
def add_endpoint_to_project(self, endpoint_id, project_id):

View File

@ -241,6 +241,53 @@ class Catalog(base.CatalogDriverBase):
return catalog
def get_v3_catalog(self, user_id, project_id):
"""Retrieve and format the current V3 service catalog.
This implementation builds the V3 catalog from the V2 catalog.
:param user_id: The id of the user who has been authenticated for
creating service catalog.
:param project_id: The id of the project. 'project_id' will be None in
the case this being called to create a catalog to go in a domain
scoped token. In this case, any endpoint that requires a project_id
as part of their URL will be skipped.
:returns: A list representing the service catalog or an empty list
"""
v2_catalog = self.get_catalog(user_id, project_id)
v3_catalog = {}
for region_name, region in v2_catalog.items():
for service_type, service in region.items():
if service_type not in v3_catalog:
v3_catalog[service_type] = {
'type': service_type,
'endpoints': []
}
for attr, value in service.items():
# Attributes that end in URL are interfaces. In the V2
# catalog, these are internalURL, publicURL, and adminURL.
# For example, <region_name>.publicURL=<URL> in the V2
# catalog becomes the V3 interface for the service:
# { 'interface': 'public', 'url': '<URL>', 'region':
# 'region: '<region_name>' }
if attr.endswith('URL'):
v3_interface = attr[:-len('URL')]
v3_catalog[service_type]['endpoints'].append({
'interface': v3_interface,
'region': region_name,
'url': value,
})
continue
# Other attributes are copied to the service.
v3_catalog[service_type][attr] = value
return list(v3_catalog.values())
def add_endpoint_to_project(self, endpoint_id, project_id):
raise exception.NotImplemented()

View File

@ -0,0 +1,27 @@
# config for templated.Catalog, using camelCase because I don't want to do
# translations for keystone compat
catalog.RegionOne.identity.publicURL = http://region-one:$(public_port)s/v3
catalog.RegionOne.identity.adminURL = http://region-one:$(admin_port)s/v3
catalog.RegionOne.identity.internalURL = http://region-one:$(admin_port)s/v3
catalog.RegionOne.identity.name = 'Identity Service'
catalog.RegionOne.identity.id = 1
# fake compute service for now to help novaclient tests work
catalog.RegionOne.compute.publicURL = http://region-one:8774/v1.1/$(tenant_id)s
catalog.RegionOne.compute.adminURL = http://region-one:8774/v1.1/$(tenant_id)s
catalog.RegionOne.compute.internalURL = http://region-one:8774/v1.1/$(tenant_id)s
catalog.RegionOne.compute.name = 'Compute Service'
catalog.RegionOne.compute.id = 2
# second region for multi-region testing
catalog.RegionTwo.identity.publicURL = http://region-two:$(public_port)s/v3
catalog.RegionTwo.identity.adminURL = http://region-two:$(admin_port)s/v3
catalog.RegionTwo.identity.internalURL = http://region-two:$(admin_port)s/v3
catalog.RegionTwo.identity.name = 'Identity Service'
catalog.RegionTwo.identity.id = 1
catalog.RegionTwo.compute.publicURL = http://region-two:8774/v1.1/$(tenant_id)s
catalog.RegionTwo.compute.adminURL = http://region-two:8774/v1.1/$(tenant_id)s
catalog.RegionTwo.compute.internalURL = http://region-two:8774/v1.1/$(tenant_id)s
catalog.RegionTwo.compute.name = 'Compute Service'
catalog.RegionTwo.compute.id = 2

View File

@ -129,6 +129,64 @@ class TestTemplatedCatalog(unit.TestCase, catalog_tests.CatalogTests):
'id': '1'}]
self.assert_catalogs_equal(exp_catalog, catalog_ref)
def test_get_multi_region_v3_catalog(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
catalog_api = PROVIDERS.catalog_api
# Load the multi-region catalog.
catalog_api._load_templates(
unit.dirs.tests('default_catalog_multi_region.templates'))
catalog_ref = catalog_api.get_v3_catalog(user_id, project_id)
exp_catalog = [
{'endpoints': [
{'interface': 'admin',
'region': 'RegionOne',
'url': 'http://region-one:8774/v1.1/%s' % project_id},
{'interface': 'public',
'region': 'RegionOne',
'url': 'http://region-one:8774/v1.1/%s' % project_id},
{'interface': 'internal',
'region': 'RegionOne',
'url': 'http://region-one:8774/v1.1/%s' % project_id},
{'interface': 'admin',
'region': 'RegionTwo',
'url': 'http://region-two:8774/v1.1/%s' % project_id},
{'interface': 'public',
'region': 'RegionTwo',
'url': 'http://region-two:8774/v1.1/%s' % project_id},
{'interface': 'internal',
'region': 'RegionTwo',
'url': 'http://region-two:8774/v1.1/%s' % project_id}],
'type': 'compute',
'name': "'Compute Service'",
'id': '2'},
{'endpoints': [
{'interface': 'admin',
'region': 'RegionOne',
'url': 'http://region-one:35357/v3'},
{'interface': 'public',
'region': 'RegionOne',
'url': 'http://region-one:5000/v3'},
{'interface': 'internal',
'region': 'RegionOne',
'url': 'http://region-one:35357/v3'},
{'interface': 'admin',
'region': 'RegionTwo',
'url': 'http://region-two:35357/v3'},
{'interface': 'public',
'region': 'RegionTwo',
'url': 'http://region-two:5000/v3'},
{'interface': 'internal',
'region': 'RegionTwo',
'url': 'http://region-two:35357/v3'}],
'type': 'identity',
'name': "'Identity Service'",
'id': '1'}]
self.assert_catalogs_equal(exp_catalog, catalog_ref)
def test_get_catalog_ignores_endpoints_with_invalid_urls(self):
user_id = uuid.uuid4().hex
project_id = None

View File

@ -0,0 +1,8 @@
---
fixes:
- |
[`bug 1703666 <https://bugs.launchpad.net/keystone/+bug/1703666>`_]
Fixing multi-region support for the templated v3 catalog by making sure
that the catalog contains only one definition per endpoint, and that
each region is listed under that endpoint. Previously each region
and endpoint would have had its own definition.