diff --git a/config.yaml b/config.yaml index 1e39182..f556882 100644 --- a/config.yaml +++ b/config.yaml @@ -62,4 +62,16 @@ options: default: description: | SSL CA to use with the certificate and key provided - this is only - required if you are providing a privately signed ssl_cert and ssl_key. \ No newline at end of file + required if you are providing a privately signed ssl_cert and ssl_key. + os-public-hostname: + type: string + default: + description: | + The hostname or address of the public endpoints created for heat + in the keystone identity provider. + . + This value will be used for public endpoints. For example, an + os-public-hostname set to 'heat.example.com' with ssl enabled will + create the following public endpoints for ceilometer: + . + https://ceilometer.example.com:8777/ diff --git a/hooks/charmhelpers/contrib/openstack/ip.py b/hooks/charmhelpers/contrib/openstack/ip.py index 29bbddc..3dca6dc 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/hooks/heat_relations.py b/hooks/heat_relations.py index 8926312..66828cd 100755 --- a/hooks/heat_relations.py +++ b/hooks/heat_relations.py @@ -41,8 +41,11 @@ from charmhelpers.contrib.openstack.utils import ( openstack_upgrade_available ) -from charmhelpers.contrib.hahelpers.cluster import ( - canonical_url +from charmhelpers.contrib.openstack.ip import ( + canonical_url, + ADMIN, + INTERNAL, + PUBLIC, ) from heat_utils import ( @@ -144,20 +147,31 @@ def configure_https(): @hooks.hook('identity-service-relation-joined') def identity_joined(rid=None): - base_url = canonical_url(CONFIGS) - api_url = '%s:8004/v1/$(tenant_id)s' % base_url - cfn_url = '%s:8000/v1' % base_url + public_url_base = canonical_url(CONFIGS, PUBLIC) + internal_url_base = canonical_url(CONFIGS, INTERNAL) + admin_url_base = canonical_url(CONFIGS, ADMIN) + + api_url_template = '%s:8004/v1/$(tenant_id)s' + public_api_endpoint = (api_url_template % public_url_base) + internal_api_endpoint = (api_url_template % internal_url_base) + admin_api_endpoint = (api_url_template % admin_url_base) + + cfn_url_template = '%s:8000/v1' + public_cfn_endpoint = (cfn_url_template % public_url_base) + internal_cfn_endpoint = (cfn_url_template % internal_url_base) + admin_cfn_endpoint = (cfn_url_template % admin_url_base) + relation_data = { 'heat_service': 'heat', 'heat_region': config('region'), - 'heat_public_url': api_url, - 'heat_admin_url': api_url, - 'heat_internal_url': api_url, + 'heat_public_url': public_api_endpoint, + 'heat_admin_url': admin_api_endpoint, + 'heat_internal_url': internal_api_endpoint, 'heat-cfn_service': 'heat-cfn', 'heat-cfn_region': config('region'), - 'heat-cfn_public_url': cfn_url, - 'heat-cfn_admin_url': cfn_url, - 'heat-cfn_internal_url': cfn_url + 'heat-cfn_public_url': public_cfn_endpoint, + 'heat-cfn_admin_url': admin_cfn_endpoint, + 'heat-cfn_internal_url': internal_cfn_endpoint, } relation_set(relation_id=rid, **relation_data) diff --git a/unit_tests/test_heat_relations.py b/unit_tests/test_heat_relations.py index 6b266ef..85d3ca3 100644 --- a/unit_tests/test_heat_relations.py +++ b/unit_tests/test_heat_relations.py @@ -18,7 +18,6 @@ utils.restart_map = _map TO_PATCH = [ # charmhelpers.core.hookenv 'Hooks', - 'canonical_url', 'config', 'open_port', 'relation_set', @@ -141,10 +140,11 @@ class HeatRelationTests(CharmTestCase): relations.relation_broken() self.assertTrue(configs.write_all.called) - def test_identity_service_joined(self): + @patch.object(relations, 'canonical_url') + def test_identity_service_joined(self, _canonical_url): "It properly requests unclustered endpoint via identity-service" self.unit_get.return_value = 'heatnode1' - self.canonical_url.return_value = 'http://heatnode1' + _canonical_url.return_value = 'http://heatnode1' relations.identity_joined() expected = { 'heat_service': 'heat', @@ -161,8 +161,9 @@ class HeatRelationTests(CharmTestCase): } self.relation_set.assert_called_with(**expected) - def test_identity_service_joined_with_relation_id(self): - self.canonical_url.return_value = 'http://heatnode1' + @patch.object(relations, 'canonical_url') + def test_identity_service_joined_with_relation_id(self, _canonical_url): + _canonical_url.return_value = 'http://heatnode1' relations.identity_joined(rid='identity-service:0') ex = { 'heat_service': 'heat', @@ -179,6 +180,33 @@ class HeatRelationTests(CharmTestCase): } self.relation_set.assert_called_with(**ex) + @patch('charmhelpers.contrib.openstack.ip.unit_get', + lambda *args: 'heatnode1') + @patch('charmhelpers.contrib.openstack.ip.is_clustered', + lambda *args: False) + @patch('charmhelpers.contrib.openstack.ip.service_name') + @patch('charmhelpers.contrib.openstack.ip.config') + def test_identity_service_public_address_override(self, ip_config, + _service_name): + ip_config.side_effect = self.test_config.get + _service_name.return_value = 'heat' + self.test_config.set('os-public-hostname', 'heat.example.org') + relations.identity_joined(rid='identity-service:0') + exp = { + 'heat_service': 'heat', + 'heat_region': 'RegionOne', + 'heat_public_url': 'http://heat.example.org:8004/v1/$(tenant_id)s', + 'heat_admin_url': 'http://heatnode1:8004/v1/$(tenant_id)s', + 'heat_internal_url': 'http://heatnode1:8004/v1/$(tenant_id)s', + 'heat-cfn_service': 'heat-cfn', + 'heat-cfn_region': 'RegionOne', + 'heat-cfn_public_url': 'http://heat.example.org:8000/v1', + 'heat-cfn_admin_url': 'http://heatnode1:8000/v1', + 'heat-cfn_internal_url': 'http://heatnode1:8000/v1', + 'relation_id': 'identity-service:0', + } + self.relation_set.assert_called_with(**exp) + @patch.object(relations, 'configure_https') @patch.object(relations, 'CONFIGS') def test_identity_changed(self, configs, mock_configure_https):