[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.

Additionally fixed unit test which invoked an apt-get update.

Closes-Bug: #1398182
This commit is contained in:
Billy Olsen
2015-06-02 17:06:30 -07:00
parent d25249a67a
commit 62f6dc2eb3
4 changed files with 101 additions and 68 deletions

View File

@@ -230,6 +230,17 @@ 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 provided by the
nova-cloud-controller in the keystone identity provider.
.
This value will be used for public endpoints. For example, an
endpoint-public-name set to 'ncc.example.com' with ssl enabled will
create public endpoints such as
https://ncc.example.com:8775/v2/$(tenant_id)s
service-guard:
type: boolean
default: false

View File

@@ -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)

View File

@@ -90,11 +90,13 @@ class NovaComputeContextTests(CharmTestCase):
self.assertEqual({'memcached_servers': "%s:11211" % (formated_ip, )},
instance_console())
@mock.patch('charmhelpers.contrib.openstack.neutron.os_release')
@mock.patch.object(context, 'use_local_neutron_api')
@mock.patch('charmhelpers.contrib.openstack.ip.config')
@mock.patch('charmhelpers.contrib.openstack.ip.is_clustered')
def test_neutron_context_single_vip(self, mock_is_clustered, mock_config,
mock_use_local_neutron_api):
mock_use_local_neutron_api,
_os_release):
mock_use_local_neutron_api.return_value = True
self.https.return_value = False
mock_is_clustered.return_value = True
@@ -102,7 +104,7 @@ class NovaComputeContextTests(CharmTestCase):
'os-internal-network': '10.0.0.1/24',
'os-admin-network': '10.0.1.0/24',
'os-public-network': '10.0.2.0/24'}
mock_config.side_effect = lambda key: config[key]
mock_config.side_effect = lambda key: config.get(key)
mock_use_local_neutron_api.return_value = False
ctxt = context.NeutronCCContext()()
@@ -114,18 +116,20 @@ class NovaComputeContextTests(CharmTestCase):
self.assertEqual(ctxt['nova_url'], 'http://10.0.0.1:8774/v2')
self.assertEqual(ctxt['neutron_url'], 'http://10.0.0.1:9696')
@mock.patch('charmhelpers.contrib.openstack.neutron.os_release')
@mock.patch.object(context, 'use_local_neutron_api')
@mock.patch('charmhelpers.contrib.openstack.ip.config')
@mock.patch('charmhelpers.contrib.openstack.ip.is_clustered')
def test_neutron_context_multi_vip(self, mock_is_clustered, mock_config,
mock_use_local_neutron_api):
mock_use_local_neutron_api,
_os_release):
self.https.return_value = False
mock_is_clustered.return_value = True
config = {'vip': '10.0.0.1 10.0.1.1 10.0.2.1',
'os-internal-network': '10.0.1.0/24',
'os-admin-network': '10.0.0.0/24',
'os-public-network': '10.0.2.0/24'}
mock_config.side_effect = lambda key: config[key]
mock_config.side_effect = lambda key: config.get(key)
mock_use_local_neutron_api.return_value = False
ctxt = context.NeutronCCContext()()

View File

@@ -25,13 +25,13 @@ TO_PATCH = [
'api_port',
'apt_update',
'apt_install',
'canonical_url',
'configure_installation_source',
'charm_dir',
'do_openstack_upgrade',
'openstack_upgrade_available',
'cmd_all_services',
'config',
'determine_endpoints',
'determine_packages',
'determine_ports',
'disable_services',
@@ -188,9 +188,11 @@ class NovaCCHooksTests(CharmTestCase):
self.assertEquals(sorted(self.relation_set.call_args_list),
sorted(expected_relations))
@patch.object(hooks, 'canonical_url')
@patch.object(utils, 'config')
@patch.object(hooks, '_auth_config')
def test_compute_joined_neutron(self, auth_config, _util_config):
def test_compute_joined_neutron(self, auth_config, _util_config,
_canonical_url):
_util_config.return_value = None
self.is_relation_made.return_value = False
self.network_manager.return_value = 'neutron'
@@ -198,7 +200,7 @@ class NovaCCHooksTests(CharmTestCase):
self.keystone_ca_cert_b64.return_value = 'foocert64'
self.volume_service.return_value = 'cinder'
self.unit_get.return_value = 'nova-cc-host1'
self.canonical_url.return_value = 'http://nova-cc-host1'
_canonical_url.return_value = 'http://nova-cc-host1'
self.api_port.return_value = '9696'
self.neutron_plugin.return_value = 'nvp'
auth_config.return_value = FAKE_KS_AUTH_CFG
@@ -217,11 +219,12 @@ class NovaCCHooksTests(CharmTestCase):
quantum_plugin='nvp',
network_manager='neutron', **FAKE_KS_AUTH_CFG)
@patch.object(hooks, 'canonical_url')
@patch.object(utils, 'config')
@patch.object(hooks, 'NeutronAPIContext')
@patch.object(hooks, '_auth_config')
def test_compute_joined_neutron_api_rel(self, auth_config, napi,
_util_config):
_util_config, _canonical_url):
def mock_NeutronAPIContext():
return {
'neutron_plugin': 'bob',
@@ -236,7 +239,7 @@ class NovaCCHooksTests(CharmTestCase):
self.keystone_ca_cert_b64.return_value = 'foocert64'
self.volume_service.return_value = 'cinder'
self.unit_get.return_value = 'nova-cc-host1'
self.canonical_url.return_value = 'http://nova-cc-host1'
_canonical_url.return_value = 'http://nova-cc-host1'
self.api_port.return_value = '9696'
self.neutron_plugin.return_value = 'nvp'
auth_config.return_value = FAKE_KS_AUTH_CFG
@@ -254,13 +257,14 @@ class NovaCCHooksTests(CharmTestCase):
quantum_plugin='bob',
network_manager='neutron', **FAKE_KS_AUTH_CFG)
@patch.object(hooks, 'canonical_url')
@patch.object(hooks, '_auth_config')
def test_nova_vmware_joined(self, auth_config):
def test_nova_vmware_joined(self, auth_config, _canonical_url):
auth_config.return_value = FAKE_KS_AUTH_CFG
# quantum-security-groups, plugin
self.neutron_plugin.return_value = 'nvp'
self.network_manager.return_value = 'neutron'
self.canonical_url.return_value = 'http://nova-cc-host1'
_canonical_url.return_value = 'http://nova-cc-host1'
self.api_port.return_value = '9696'
hooks.nova_vmware_relation_joined()
self.relation_set.assert_called_with(
@@ -278,6 +282,23 @@ class NovaCCHooksTests(CharmTestCase):
nova_hostname='nova.foohost.com')
self.unit_get.assert_called_with('private-address')
@patch('charmhelpers.contrib.openstack.ip.unit_get')
@patch('charmhelpers.contrib.openstack.ip.is_clustered')
@patch('charmhelpers.contrib.openstack.ip.config')
def test_identity_joined(self, _ip_config, _is_clustered, _unit_get):
_is_clustered.return_value = False
_unit_get.return_value = '127.0.0.1'
_ip_config.side_effect = self.test_config.get
self.test_config.set('endpoint-public-name', 'ncc.example.com')
hooks.identity_joined()
self.determine_endpoints.asssert_called_with(
public_url='http://ncc.example.com',
internal_url='http://127.0.0.1',
admin_url='http://127.0.0.1'
)
def test_postgresql_nova_db_joined(self):
self.is_relation_made.return_value = False
hooks.pgsql_nova_db_joined()
@@ -442,9 +463,10 @@ class NovaCCHooksTests(CharmTestCase):
call('/etc/neutron/neutron.conf')])
cell_joined.assert_called_with(rid='nova-cell-api/0')
def test_nova_cell_relation_joined(self):
@patch.object(hooks, 'canonical_url')
def test_nova_cell_relation_joined(self, _canonical_url):
self.uuid.uuid4.return_value = 'bob'
self.canonical_url.return_value = 'http://novaurl'
_canonical_url.return_value = 'http://novaurl'
hooks.nova_cell_relation_joined(rid='rid',
remote_restart=True)
self.relation_set.assert_called_with(restart_trigger='bob',
@@ -463,19 +485,20 @@ class NovaCCHooksTests(CharmTestCase):
}
self.assertEquals(hooks.get_cell_type(), 'parent')
@patch.object(hooks, 'canonical_url')
@patch.object(os, 'rename')
@patch.object(os.path, 'isfile')
@patch.object(hooks, 'CONFIGS')
@patch.object(hooks, 'get_cell_type')
def test_neutron_api_relation_joined(self, get_cell_type, configs, isfile,
rename):
rename, _canonical_url):
neutron_conf = '/etc/neutron/neutron.conf'
nova_url = 'http://novaurl:8774/v2'
isfile.return_value = True
self.service_running.return_value = True
_identity_joined = self.patch('identity_joined')
self.relation_ids.return_value = ['relid']
self.canonical_url.return_value = 'http://novaurl'
_canonical_url.return_value = 'http://novaurl'
get_cell_type.return_value = 'parent'
self.uuid.uuid4.return_value = 'bob'
with patch_open() as (_open, _file):
@@ -512,11 +535,12 @@ class NovaCCHooksTests(CharmTestCase):
self.assertTrue(_compute_joined.called)
self.assertTrue(_quantum_joined.called)
@patch.object(hooks, 'canonical_url')
@patch.object(utils, 'config')
def test_console_settings_vnc(self, _utils_config):
def test_console_settings_vnc(self, _utils_config, _canonical_url):
_utils_config.return_value = 'vnc'
_cc_host = "nova-cc-host1"
self.canonical_url.return_value = 'http://' + _cc_host
_canonical_url.return_value = 'http://' + _cc_host
_con_sets = hooks.console_settings()
console_settings = {
'console_proxy_novnc_address': 'http://%s:6080/vnc_auto.html' %
@@ -532,11 +556,12 @@ class NovaCCHooksTests(CharmTestCase):
}
self.assertEqual(_con_sets, console_settings)
@patch.object(hooks, 'canonical_url')
@patch.object(utils, 'config')
def test_console_settings_xvpvnc(self, _utils_config):
def test_console_settings_xvpvnc(self, _utils_config, _canonical_url):
_utils_config.return_value = 'xvpvnc'
_cc_host = "nova-cc-host1"
self.canonical_url.return_value = 'http://' + _cc_host
_canonical_url.return_value = 'http://' + _cc_host
_con_sets = hooks.console_settings()
console_settings = {
'console_access_protocol': 'xvpvnc',
@@ -548,11 +573,12 @@ class NovaCCHooksTests(CharmTestCase):
}
self.assertEqual(_con_sets, console_settings)
@patch.object(hooks, 'canonical_url')
@patch.object(utils, 'config')
def test_console_settings_novnc(self, _utils_config):
def test_console_settings_novnc(self, _utils_config, _canonical_url):
_utils_config.return_value = 'novnc'
_cc_host = "nova-cc-host1"
self.canonical_url.return_value = 'http://' + _cc_host
_canonical_url.return_value = 'http://' + _cc_host
_con_sets = hooks.console_settings()
console_settings = {
'console_proxy_novnc_address': 'http://%s:6080/vnc_auto.html' %
@@ -564,11 +590,12 @@ class NovaCCHooksTests(CharmTestCase):
}
self.assertEqual(_con_sets, console_settings)
@patch.object(hooks, 'canonical_url')
@patch.object(utils, 'config')
def test_console_settings_spice(self, _utils_config):
def test_console_settings_spice(self, _utils_config, _canonical_url):
_utils_config.return_value = 'spice'
_cc_host = "nova-cc-host1"
self.canonical_url.return_value = 'http://' + _cc_host
_canonical_url.return_value = 'http://' + _cc_host
_con_sets = hooks.console_settings()
console_settings = {
'console_proxy_spice_address': 'http://%s:6082/spice_auto.html' %
@@ -580,14 +607,16 @@ class NovaCCHooksTests(CharmTestCase):
}
self.assertEqual(_con_sets, console_settings)
@patch.object(hooks, 'canonical_url')
@patch.object(utils, 'config')
def test_console_settings_explicit_ip(self, _utils_config):
def test_console_settings_explicit_ip(self, _utils_config,
_canonical_url):
_utils_config.return_value = 'spice'
_cc_public_host = "public-host"
_cc_private_host = "private-host"
self.test_config.set('console-proxy-ip', _cc_public_host)
_con_sets = hooks.console_settings()
self.canonical_url.return_value = 'http://' + _cc_private_host
_canonical_url.return_value = 'http://' + _cc_private_host
console_settings = {
'console_proxy_spice_address': 'http://%s:6082/spice_auto.html' %
(_cc_public_host),