From cc03973a35a80e3196508d34a33d10f4a914189d Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 26 Oct 2017 10:15:34 +0100 Subject: [PATCH] Add function for adding DNS HA resources Add ability for charms to request DNS entries to be managed by hacluster via the hacluster interface. Change-Id: Id51f7553c806a62bbcb49c114fe48c6471642c10 Partial-Bug: #1727376 Depends-On: I1a6cdeffa3aa8657b957ba68cd09face27f93b27 --- charms_openstack/charm/classes.py | 43 +++++++++- unit_tests/__init__.py | 2 + .../charms_openstack/charm/test_classes.py | 83 +++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/charms_openstack/charm/classes.py b/charms_openstack/charm/classes.py index 2fe9d1b..fe0882d 100644 --- a/charms_openstack/charm/classes.py +++ b/charms_openstack/charm/classes.py @@ -2,12 +2,14 @@ import base64 import contextlib import os import random +import re import shutil import string import subprocess import charmhelpers.contrib.network.ip as ch_ip import charmhelpers.contrib.openstack.utils as os_utils +import charmhelpers.contrib.openstack.ha as os_ha import charmhelpers.core.hookenv as hookenv import charmhelpers.core.host as ch_host import charmhelpers.fetch as fetch @@ -29,6 +31,7 @@ import charms_openstack.ip as os_ip VIP_KEY = "vip" CIDR_KEY = "vip_cidr" IFACE_KEY = "vip_iface" +DNSHA_KEY = "dns-ha" APACHE_SSL_VHOST = '/etc/apache2/sites-available/openstack_https_frontend.conf' SYSTEM_CA_CERTS = '/etc/ssl/certs/ca-certificates.crt' SNAP_CA_CERTS = '/var/snap/{}/common/etc/ssl/certs/ca-certificates.crt' @@ -421,6 +424,7 @@ class HAOpenStackCharm(OpenStackAPICharm): RESOURCE_TYPES = { 'vips': self._add_ha_vips_config, 'haproxy': self._add_ha_haproxy_config, + 'dnsha': self._add_dnsha_config, } if self.ha_resources: for res_type in self.ha_resources: @@ -432,7 +436,9 @@ class HAOpenStackCharm(OpenStackAPICharm): @param hacluster instance of interface class HAClusterRequires """ - for vip in self.config.get(VIP_KEY, '').split(): + if not self.config.get(VIP_KEY): + return + for vip in self.config[VIP_KEY].split(): iface = (ch_ip.get_iface_for_address(vip) or self.config.get(IFACE_KEY)) netmask = (ch_ip.get_netmask_for_address(vip) or @@ -447,6 +453,41 @@ class HAOpenStackCharm(OpenStackAPICharm): """ hacluster.add_init_service(self.name, 'haproxy') + def _add_dnsha_config(self, hacluster): + """Add a DNSHA object to self.resources + + @param hacluster instance of interface class HAClusterRequires + """ + if not self.config.get(DNSHA_KEY): + return + settings = ['os-admin-hostname', 'os-internal-hostname', + 'os-public-hostname', 'os-access-hostname'] + + for setting in settings: + hostname = self.config.get(setting) + if hostname is None: + hookenv.log( + 'DNS HA: Hostname setting {} is None. Ignoring.'.format( + setting), + hookenv.DEBUG) + continue + m = re.search('os-(.+?)-hostname', setting) + if m: + endpoint_type = m.group(1) + # resolve_address's ADDRESS_MAP uses 'int' not 'internal' + if endpoint_type == 'internal': + endpoint_type = 'int' + else: + msg = ( + 'Unexpected DNS hostname setting: {}. Cannot determine ' + 'endpoint_type name'.format(setting)) + hookenv.status_set('blocked', msg) + raise os_ha.DNSHAException(msg) + ip = os_ip.resolve_address( + endpoint_type=endpoint_type, + override=False) + hacluster.add_dnsha(self.name, ip, hostname, endpoint_type) + def set_haproxy_stat_password(self): """Set a stats password for accessing haproxy statistics""" if not self.get_state('haproxy.stat.password'): diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 4a6d193..541fe4a 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -28,6 +28,8 @@ sys.modules['charmhelpers.core.templating'] = charmhelpers.core.templating sys.modules['charmhelpers.core.unitdata'] = charmhelpers.core.unitdata sys.modules['charmhelpers.contrib'] = charmhelpers.contrib sys.modules['charmhelpers.contrib.openstack'] = charmhelpers.contrib.openstack +sys.modules['charmhelpers.contrib.openstack.ha'] = ( + charmhelpers.contrib.openstack.ha) sys.modules['charmhelpers.contrib.openstack.utils'] = ( charmhelpers.contrib.openstack.utils) sys.modules['charmhelpers.contrib.openstack.templating'] = ( diff --git a/unit_tests/charms_openstack/charm/test_classes.py b/unit_tests/charms_openstack/charm/test_classes.py index 3e47000..fe12bdb 100644 --- a/unit_tests/charms_openstack/charm/test_classes.py +++ b/unit_tests/charms_openstack/charm/test_classes.py @@ -479,6 +479,13 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest): mock.call('myservice', 'vip2', 'user_iface', 'user_cidr')] interface_mock.add_vip.assert_has_calls(calls) + def test__add_ha_vips_config_novip(self): + config = {'vip': None} + self.patch_target('config', new=config) + interface_mock = mock.Mock() + self.target._add_ha_vips_config(interface_mock) + self.assertFalse(interface_mock.add_vip.called) + def test__add_ha_haproxy_config(self): self.patch_target('name', new='myservice') interface_mock = mock.Mock() @@ -487,6 +494,82 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest): 'myservice', 'haproxy') + def test__add_dnsha_config_single_dns_entry(self): + config = { + 'dns-ha': True, + 'os-admin-hostname': 'myservice-admin.maas'} + self.patch_target('config', new=config) + self.patch_target('name', new='myservice') + self.patch_object(chm.os_ip, 'resolve_address', '10.0.0.10') + interface_mock = mock.Mock() + self.target._add_dnsha_config(interface_mock) + interface_mock.add_dnsha.assert_called_once_with( + 'myservice', + '10.0.0.10', + 'myservice-admin.maas', + 'admin') + + def test__add_dnsha_config_multi_dns_entries(self): + config = { + 'dns-ha': True, + 'os-public-hostname': 'myservice-public.maas', + 'os-admin-hostname': 'myservice-admin.maas'} + addr = { + 'public': '10.10.0.10', + 'admin': '10.0.0.10'} + self.patch_target('config', new=config) + self.patch_target('name', new='myservice') + self.patch_object( + chm.os_ip, + 'resolve_address', + new=lambda endpoint_type, override=False: addr[endpoint_type]) + interface_mock = mock.Mock() + self.target._add_dnsha_config(interface_mock) + calls = [ + mock.call( + 'myservice', + '10.0.0.10', + 'myservice-admin.maas', + 'admin'), + mock.call( + 'myservice', + '10.10.0.10', + 'myservice-public.maas', + 'public')] + interface_mock.add_dnsha.assert_has_calls(calls) + + def test__add_dnsha_config_single_internal_dns_entry(self): + config = { + 'dns-ha': True, + 'os-internal-hostname': 'myservice-internal.maas'} + self.patch_target('config', new=config) + self.patch_target('name', new='myservice') + self.patch_object(chm.os_ip, 'resolve_address', '10.0.0.10') + interface_mock = mock.Mock() + self.target._add_dnsha_config(interface_mock) + interface_mock.add_dnsha.assert_called_once_with( + 'myservice', + '10.0.0.10', + 'myservice-internal.maas', + 'int') + + def test__add_dnsha_config_dns_ha_false(self): + config = { + 'os-internal-hostname': 'myservice-internal.maas' + } + self.patch_target('config', new=config) + interface_mock = mock.Mock() + self.target._add_dnsha_config(interface_mock) + self.assertFalse(interface_mock.add_dnsha.called) + config['dns-ha'] = None + interface_mock.reset_mock() + self.target._add_dnsha_config(interface_mock) + self.assertFalse(interface_mock.add_dnsha.called) + config['dns-ha'] = False + interface_mock.reset_mock() + self.target._add_dnsha_config(interface_mock) + self.assertFalse(interface_mock.add_dnsha.called) + def test_set_haproxy_stat_password(self): self.patch_object(chm.reactive.bus, 'get_state') self.patch_object(chm.reactive.bus, 'set_state')