From 3f601c58243c4dce9dcade58ad99c7e570c3e7a0 Mon Sep 17 00:00:00 2001 From: Billy Olsen Date: Tue, 2 Jun 2015 14:49:10 -0700 Subject: [PATCH] [wolsen,r=] Add support for overriding public endpoint addresses. Adds in the config option for overriding public endpoint addresses and introduces a unit tests to ensure that the override for the public address is functioning correctly. Closes-Bug: #1398182 --- config.yaml | 12 ++++ hooks/charmhelpers/contrib/openstack/ip.py | 75 +++++++++------------- unit_tests/test_neutron_api_hooks.py | 42 ++++++++++-- 3 files changed, 79 insertions(+), 50 deletions(-) diff --git a/config.yaml b/config.yaml index 8157f7c9..10e853dd 100644 --- a/config.yaml +++ b/config.yaml @@ -235,6 +235,18 @@ options: 192.168.0.0/24) . This network will be used for public endpoints. + endpoint-public-name: + type: string + default: + description: | + The hostname or address of the public endpoints created for neutron-api + in the keystone identity provider. + . + This value will be used for public endpoints. For example, an + endpoint-public-name set to 'neutron-api.example.com' with ssl enabled + will create the following endpoint for neutron-api: + . + https://neutron-api.example.com:9696/ ssl_cert: type: string default: diff --git a/hooks/charmhelpers/contrib/openstack/ip.py b/hooks/charmhelpers/contrib/openstack/ip.py index 29bbddcb..45531b57 100644 --- a/hooks/charmhelpers/contrib/openstack/ip.py +++ b/hooks/charmhelpers/contrib/openstack/ip.py @@ -26,8 +26,6 @@ from charmhelpers.contrib.network.ip import ( ) from charmhelpers.contrib.hahelpers.cluster import is_clustered -from functools import partial - PUBLIC = 'public' INTERNAL = 'int' ADMIN = 'admin' @@ -35,15 +33,18 @@ ADMIN = 'admin' ADDRESS_MAP = { PUBLIC: { 'config': 'os-public-network', - 'fallback': 'public-address' + 'fallback': 'public-address', + 'override': 'endpoint-public-name', }, INTERNAL: { 'config': 'os-internal-network', - 'fallback': 'private-address' + 'fallback': 'private-address', + 'override': 'endpoint-internal-name', }, ADMIN: { 'config': 'os-admin-network', - 'fallback': 'private-address' + 'fallback': 'private-address', + 'override': 'endpoint-admin-name', } } @@ -57,15 +58,30 @@ def canonical_url(configs, endpoint_type=PUBLIC): :param endpoint_type: str endpoint type to resolve. :param returns: str base URL for services on the current service unit. """ - scheme = 'http' - if 'https' in configs.complete_contexts(): - scheme = 'https' + scheme = _get_scheme(configs) + address = resolve_address(endpoint_type) if is_ipv6(address): address = "[{}]".format(address) + return '%s://%s' % (scheme, address) +def _get_scheme(configs): + """Returns the scheme to use for the url (either http or https) + depending upon whether https is in the configs value. + + :param configs: OSTemplateRenderer config templating object to inspect + for a complete https context. + :returns: either 'http' or 'https' depending on whether https is + configured within the configs context. + """ + scheme = 'http' + if configs and 'https' in configs.complete_contexts(): + scheme = 'https' + return scheme + + def resolve_address(endpoint_type=PUBLIC): """Return unit address depending on net config. @@ -78,6 +94,14 @@ def resolve_address(endpoint_type=PUBLIC): :param endpoint_type: Network endpoing type """ resolved_address = None + + # Allow the user to override the address which is used. This is + # useful for proxy services or exposing a public endpoint url, etc. + override_key = ADDRESS_MAP[endpoint_type]['override'] + addr_override = config(override_key) + if addr_override: + return addr_override + vips = config('vip') if vips: vips = vips.split() @@ -109,38 +133,3 @@ def resolve_address(endpoint_type=PUBLIC): "clustered=%s)" % (net_type, clustered)) return resolved_address - - -def endpoint_url(configs, url_template, port, endpoint_type=PUBLIC, - override=None): - """Returns the correct endpoint URL to advertise to Keystone. - - This method provides the correct endpoint URL which should be advertised to - the keystone charm for endpoint creation. This method allows for the url to - be overridden to force a keystone endpoint to have specific URL for any of - the defined scopes (admin, internal, public). - - :param configs: OSTemplateRenderer config templating object to inspect - for a complete https context. - :param url_template: str format string for creating the url template. Only - two values will be passed - the scheme+hostname - returned by the canonical_url and the port. - :param endpoint_type: str endpoint type to resolve. - :param override: str the name of the config option which overrides the - endpoint URL defined by the charm itself. None will - disable any overrides (default). - """ - if override: - # Return any user-defined overrides for the keystone endpoint URL. - user_value = config(override) - if user_value: - return user_value.strip() - - return url_template % (canonical_url(configs, endpoint_type), port) - - -public_endpoint = partial(endpoint_url, endpoint_type=PUBLIC) - -internal_endpoint = partial(endpoint_url, endpoint_type=INTERNAL) - -admin_endpoint = partial(endpoint_url, endpoint_type=ADMIN) diff --git a/unit_tests/test_neutron_api_hooks.py b/unit_tests/test_neutron_api_hooks.py index 70ee610c..92913fe9 100644 --- a/unit_tests/test_neutron_api_hooks.py +++ b/unit_tests/test_neutron_api_hooks.py @@ -25,7 +25,6 @@ TO_PATCH = [ 'api_port', 'apt_update', 'apt_install', - 'canonical_url', 'config', 'CONFIGS', 'check_call', @@ -319,8 +318,9 @@ class NeutronAPIHooksTests(CharmTestCase): self._call_hook('amqp-relation-broken') self.assertTrue(self.CONFIGS.write_all.called) - def test_identity_joined(self): - self.canonical_url.return_value = 'http://127.0.0.1' + @patch.object(hooks, 'canonical_url') + def test_identity_joined(self, _canonical_url): + _canonical_url.return_value = 'http://127.0.0.1' self.api_port.return_value = '9696' self.test_config.set('region', 'region1') _neutron_url = 'http://127.0.0.1:9696' @@ -337,6 +337,32 @@ class NeutronAPIHooksTests(CharmTestCase): relation_settings=_endpoints ) + @patch('charmhelpers.contrib.openstack.ip.unit_get') + @patch('charmhelpers.contrib.openstack.ip.is_clustered') + @patch('charmhelpers.contrib.openstack.ip.config') + def test_identity_changed_public_name(self, _config, _is_clustered, + _unit_get): + _unit_get.return_value = '127.0.0.1' + _is_clustered.return_value = False + _config.side_effect = self.test_config.get + self.api_port.return_value = '9696' + self.test_config.set('region', 'region1') + self.test_config.set('endpoint-public-name', + 'neutron-api.example.com') + self._call_hook('identity-service-relation-joined') + _neutron_url = 'http://127.0.0.1:9696' + _endpoints = { + 'quantum_service': 'quantum', + 'quantum_region': 'region1', + 'quantum_public_url': 'http://neutron-api.example.com:9696', + 'quantum_admin_url': _neutron_url, + 'quantum_internal_url': _neutron_url, + } + self.relation_set.assert_called_with( + relation_id=None, + relation_settings=_endpoints + ) + def test_identity_changed_partial_ctxt(self): self.CONFIGS.complete_contexts.return_value = [] _api_rel_joined = self.patch('neutron_api_relation_joined') @@ -353,12 +379,13 @@ class NeutronAPIHooksTests(CharmTestCase): self.assertTrue(self.CONFIGS.write.called_with(NEUTRON_CONF)) self.assertTrue(_api_rel_joined.called) - def test_neutron_api_relation_no_id_joined(self): + @patch.object(hooks, 'canonical_url') + def test_neutron_api_relation_no_id_joined(self, _canonical_url): host = 'http://127.0.0.1' port = 1234 _id_rel_joined = self.patch('identity_joined') self.relation_ids.side_effect = self._fake_relids - self.canonical_url.return_value = host + _canonical_url.return_value = host self.api_port.return_value = port self.is_relation_made = False neutron_url = '%s:%s' % (host, port) @@ -381,10 +408,11 @@ class NeutronAPIHooksTests(CharmTestCase): **_relation_data ) - def test_neutron_api_relation_joined(self): + @patch.object(hooks, 'canonical_url') + def test_neutron_api_relation_joined(self, _canonical_url): host = 'http://127.0.0.1' port = 1234 - self.canonical_url.return_value = host + _canonical_url.return_value = host self.api_port.return_value = port self.is_relation_made = True neutron_url = '%s:%s' % (host, port)