diff --git a/config.yaml b/config.yaml index 8157f7c9..05d366cd 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. + os-public-hostname: + 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 + os-public-hostname 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..3dca6dc1 100644 --- a/hooks/charmhelpers/contrib/openstack/ip.py +++ b/hooks/charmhelpers/contrib/openstack/ip.py @@ -17,6 +17,7 @@ from charmhelpers.core.hookenv import ( config, unit_get, + service_name, ) from charmhelpers.contrib.network.ip import ( get_address_in_network, @@ -26,8 +27,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 +34,18 @@ ADMIN = 'admin' ADDRESS_MAP = { PUBLIC: { 'config': 'os-public-network', - 'fallback': 'public-address' + 'fallback': 'public-address', + 'override': 'os-public-hostname', }, INTERNAL: { 'config': 'os-internal-network', - 'fallback': 'private-address' + 'fallback': 'private-address', + 'override': 'os-internal-hostname', }, ADMIN: { 'config': 'os-admin-network', - 'fallback': 'private-address' + 'fallback': 'private-address', + 'override': 'os-admin-hostname', } } @@ -57,15 +59,50 @@ 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 _get_address_override(endpoint_type=PUBLIC): + """Returns any address overrides that the user has defined based on the + endpoint type. + + Note: this function allows for the service name to be inserted into the + address if the user specifies {service_name}.somehost.org. + + :param endpoint_type: the type of endpoint to retrieve the override + value for. + :returns: any endpoint address or hostname that the user has overridden + or None if an override is not present. + """ + override_key = ADDRESS_MAP[endpoint_type]['override'] + addr_override = config(override_key) + if not addr_override: + return None + else: + return addr_override.format(service_name=service_name()) + + def resolve_address(endpoint_type=PUBLIC): """Return unit address depending on net config. @@ -77,7 +114,10 @@ def resolve_address(endpoint_type=PUBLIC): :param endpoint_type: Network endpoing type """ - resolved_address = None + resolved_address = _get_address_override(endpoint_type) + if resolved_address: + return resolved_address + vips = config('vip') if vips: vips = vips.split() @@ -109,38 +149,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..377f1483 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,34 @@ class NeutronAPIHooksTests(CharmTestCase): relation_settings=_endpoints ) + @patch('charmhelpers.contrib.openstack.ip.service_name', + lambda *args: 'neutron-api') + @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('os-public-hostname', + '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 +381,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 +410,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)