diff --git a/hooks/hooks.py b/hooks/hooks.py
index 83e3916..7f9b5ea 100755
--- a/hooks/hooks.py
+++ b/hooks/hooks.py
@@ -69,6 +69,7 @@ from utils import (
disable_lsb_services,
disable_upstart_services,
get_ipv6_addr,
+ get_ip_addr_from_resource_params,
validate_dns_ha,
setup_maas_api,
setup_ocf_files,
@@ -77,6 +78,8 @@ from utils import (
kill_legacy_ocf_daemon_process,
try_pcmk_wait,
maintenance_mode,
+ needs_maas_dns_migration,
+ write_maas_dns_address,
)
from charmhelpers.contrib.charmsupport import nrpe
@@ -174,10 +177,34 @@ def config_changed():
maintenance_mode(cfg['maintenance-mode'])
+def migrate_maas_dns():
+ """
+ Migrates the MAAS DNS HA configuration to write local IP address
+ information to files.
+ """
+ if not needs_maas_dns_migration():
+ log("MAAS DNS migration is not necessary.", INFO)
+ return
+
+ for relid in relation_ids('ha'):
+ for unit in related_units(relid):
+ resources = parse_data(relid, unit, 'resources')
+ resource_params = parse_data(relid, unit, 'resource_params')
+
+ if True in [ra.startswith('ocf:maas')
+ for ra in resources.values()]:
+ for resource in resource_params.keys():
+ if resource.endswith("_hostname"):
+ res_ipaddr = get_ip_addr_from_resource_params(
+ resource_params[resource])
+ log("Migrating MAAS DNS resource %s" % resource, INFO)
+ write_maas_dns_address(resource, res_ipaddr)
+
+
@hooks.hook()
def upgrade_charm():
install()
-
+ migrate_maas_dns()
update_nrpe_config()
@@ -258,10 +285,13 @@ def ha_relation_changed():
# credentials
for resource in resource_params.keys():
if resource.endswith("_hostname"):
+ res_ipaddr = get_ip_addr_from_resource_params(
+ resource_params[resource])
resource_params[resource] += (
' maas_url="{}" maas_credentials="{}"'
''.format(config('maas_url'),
config('maas_credentials')))
+ write_maas_dns_address(resource, res_ipaddr)
else:
msg = ("DNS HA is requested but maas_url "
"or maas_credentials are not set")
diff --git a/hooks/utils.py b/hooks/utils.py
index 80dffbb..ed57d08 100644
--- a/hooks/utils.py
+++ b/hooks/utils.py
@@ -110,6 +110,9 @@ SYSTEMD_OVERRIDES_DIR = '/etc/systemd/system/{}.service.d'
SYSTEMD_OVERRIDES_FILE = '{}/overrides.conf'
+MAAS_DNS_CONF_DIR = '/etc/maas_dns'
+
+
class MAASConfigIncomplete(Exception):
pass
@@ -561,6 +564,16 @@ def configure_cluster_global():
pcmk.commit(cmd)
+def get_ip_addr_from_resource_params(params):
+ """Returns the IP address in the resource params provided
+
+ :return: the IP address in the params or None if not found
+ """
+ reg_ex = r'.* ip_address="([a-fA-F\d\:\.]+)".*'
+ res = re.search(reg_ex, params)
+ return res.group(1) if res else None
+
+
def restart_corosync_on_change():
"""Simple decorator to restart corosync if any of its config changes"""
def wrap(f):
@@ -710,6 +723,33 @@ def setup_ocf_files():
rsync('ocf/maas/maasclient/', '/usr/lib/heartbeat/maasclient/')
+def write_maas_dns_address(resource_name, resource_addr):
+ """Writes the specified IP address to the resource file for MAAS dns.
+
+ :param resource_name: the name of the resource the address belongs to.
+ This is the name of the file that will be written in /etc/maas_dns.
+ :param resource_addr: the IP address for the resource. This will be
+ written to the resource_name file.
+ """
+ mkdir(MAAS_DNS_CONF_DIR)
+ write_file(os.path.join(MAAS_DNS_CONF_DIR, resource_name),
+ content=resource_addr)
+
+
+def needs_maas_dns_migration():
+ """Determines if the MAAS DNS ocf resources need migration.
+
+ :return: True if migration is necessary, False otherwise.
+ """
+ try:
+ subprocess.check_call(['grep', 'OCF_RESOURCE_INSTANCE',
+ '/usr/lib/ocf/resource.d/maas/dns'])
+ return True
+ except subprocess.CalledProcessError:
+ # check_call will raise an exception if grep doesn't find the string
+ return False
+
+
def is_in_standby_mode(node_name):
"""Check if node is in standby mode in pacemaker
diff --git a/ocf/maas/dns b/ocf/maas/dns
index 842c668..c55b961 100755
--- a/ocf/maas/dns
+++ b/ocf/maas/dns
@@ -42,6 +42,7 @@
# OCF_RESKEY_ttl
# OCF_RESKEY_maas_url
# OCF_RESKEY_maas_credentials
+# OCF_RESKEY_cfg_dir
# Defaults
@@ -53,6 +54,28 @@ Expects to have a fully populated OCF RA-compliant environment set.
END
}
+# Retrieve the local IP for the current resource
+#
+# returns:
+# ip address contained in $OCF_RESKEY_cfg_dir/$OCF_RESOURCE_INSTANCE
+# no = nothing or no file
+my_ip() {
+ if [ ! -r $ipaddrfile ]
+ then
+ echo "no"
+ return 0
+ fi
+
+ ip_addr=`cat $OCF_RESKEY_cfg_dir/$OCF_RESOURCE_INSTANCE`
+ if [ "x$ip_addr" != "x" ]
+ then
+ echo $ip_addr
+ return 0
+ else
+ echo "no"
+ return 0
+ fi
+}
# Do we already serve this IP address on the given $NIC?
#
@@ -66,7 +89,17 @@ dns_served() {
target=`dig +short $OCF_RESKEY_fqdn`
if [ "x$target" != "x" ]
then
- if test "$OCF_RESKEY_ip_address" = "$target"
+ ip_address=`my_ip`
+ # The $ip_address should be set as it is ensured in the validate
+ # function, but this is a sanity check. The maas_dns.log file should
+ # contain the error that the $ip_address file is not found.
+ if test "$ip_address" = "no"
+ then
+ echo "no"
+ return 0
+ fi
+
+ if test "$ip_address" = "$target"
then
echo "ok"
return 0
@@ -95,8 +128,13 @@ maas_dns_start() {
if [ "$dns_status" = "ok" ]; then
exit $OCF_SUCCESS
fi
+ local myipaddr=`my_ip`
+ if [ "$myipaddr" = "no" ]; then
+ ocf_log err "No ip address found in $ipaddrfile"
+ exit $OCF_ERR_GENERIC
+ fi
- cmd="python3 $binfile --fqdn=$OCF_RESKEY_fqdn --ip_address=$OCF_RESKEY_ip_address --maas_server=$OCF_RESKEY_maas_url --maas_credentials=$OCF_RESKEY_maas_credentials "
+ cmd="python3 $binfile --fqdn=$OCF_RESKEY_fqdn --ip_address=$myipaddr --maas_server=$OCF_RESKEY_maas_url --maas_credentials=$OCF_RESKEY_maas_credentials "
if [ -n "$OCF_RESKEY_ttl" ]; then
cmd="$cmd --ttl=$OCF_RESKEY_ttl"
fi
@@ -133,7 +171,6 @@ maas_dns_monitor() {
return $OCF_SUCCESS
;;
no)
- exit 7
exit $OCF_NOT_RUNNING
;;
*)
@@ -148,6 +185,7 @@ binfile="$HA_BIN/maas_dns.py"
logfile="$OCF_RESKEY_logfile"
errlogfile="$OCF_RESKEY_errlogfile"
user="$OCF_RESKEY_user"
+ipaddrfile="$OCF_RESKEY_cfg_dir/$OCF_RESOURCE_INSTANCE"
[ -z "$user" ] && user=root
maas_dns_validate() {
@@ -171,6 +209,16 @@ maas_dns_validate() {
}
fi
done
+ if [ ! -r $ipaddrfile ]
+ then
+ ocf_log err "$ipaddrfile does not exist or cannot be read"
+ exit $OCF_ERR_INSTALLED
+ fi
+ if [ `my_ip` = "no" ]
+ then
+ ocf_log err "IP address is not found in $ipaddrfile"
+ exit $OCF_ERR_INSTALLED
+ fi
return $OCF_SUCCESS
}
@@ -192,9 +240,9 @@ The fully qualified domain name for the DNS entry.
Fully qualified domain name
-
+
-The IP address for the DNS entry
+The IP address for the DNS entry. Deprecated option, do not use.
IP Address
@@ -227,6 +275,14 @@ File to write STDERR to
File to write STDERR to
+
+
+Directory containing resource config files containing IP address information
+for the resource running on the local server.
+
+IP address config file directory
+
+
diff --git a/unit_tests/test_hacluster_hooks.py b/unit_tests/test_hacluster_hooks.py
index 609401b..f9d9eb0 100644
--- a/unit_tests/test_hacluster_hooks.py
+++ b/unit_tests/test_hacluster_hooks.py
@@ -32,6 +32,7 @@ class TestCorosyncConf(unittest.TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
+ @mock.patch.object(hooks, 'write_maas_dns_address')
@mock.patch('pcmk.wait_for_pcmk')
@mock.patch.object(hooks, 'peer_units')
@mock.patch('pcmk.crm_opt_exists')
@@ -54,7 +55,7 @@ class TestCorosyncConf(unittest.TestCase):
configure_stonith, configure_monitor_host,
configure_cluster_global, configure_corosync,
oldest_peer, crm_opt_exists, peer_units,
- wait_for_pcmk):
+ wait_for_pcmk, write_maas_dns_address):
crm_opt_exists.return_value = False
oldest_peer.return_value = True
related_units.return_value = ['ha/0', 'ha/1', 'ha/2']
@@ -90,6 +91,7 @@ class TestCorosyncConf(unittest.TestCase):
configure_monitor_host.assert_called_with()
configure_cluster_global.assert_called_with()
configure_corosync.assert_called_with()
+ write_maas_dns_address.assert_not_called()
for kw, key in [('location', 'locations'),
('clone', 'clones'),
@@ -108,6 +110,7 @@ class TestCorosyncConf(unittest.TestCase):
commit.assert_any_call(
'crm -w -F configure %s %s %s' % (kw, name, params))
+ @mock.patch.object(hooks, 'write_maas_dns_address')
@mock.patch.object(hooks, 'setup_maas_api')
@mock.patch.object(hooks, 'validate_dns_ha')
@mock.patch('pcmk.wait_for_pcmk')
@@ -135,7 +138,7 @@ class TestCorosyncConf(unittest.TestCase):
configure_corosync, oldest_peer,
crm_opt_exists, peer_units,
wait_for_pcmk, validate_dns_ha,
- setup_maas_api):
+ setup_maas_api, write_maas_dns_addr):
validate_dns_ha.return_value = True
crm_opt_exists.return_value = False
oldest_peer.return_value = True
@@ -158,7 +161,9 @@ class TestCorosyncConf(unittest.TestCase):
'groups': {'grp_foo': 'res_foo'},
'colocations': {'co_foo': 'inf: grp_foo cl_foo'},
'resources': {'res_foo_hostname': 'ocf:maas:dns'},
- 'resource_params': {'res_foo_hostname': 'params bar'},
+ 'resource_params': {
+ 'res_foo_hostname': 'params bar '
+ 'ip_address="172.16.0.1"'},
'ms': {'ms_foo': 'res_foo meta notify=true'},
'orders': {'foo_after': 'inf: res_foo ms_foo'}}
@@ -170,10 +175,12 @@ class TestCorosyncConf(unittest.TestCase):
hooks.ha_relation_changed()
self.assertTrue(validate_dns_ha.called)
self.assertTrue(setup_maas_api.called)
+ write_maas_dns_addr.assert_called_with('res_foo_hostname',
+ '172.16.0.1')
# Validate maas_credentials and maas_url are added to params
commit.assert_any_call(
'crm -w -F configure primitive res_foo_hostname ocf:maas:dns '
- 'params bar maas_url="http://maas/MAAAS/" '
+ 'params bar ip_address="172.16.0.1" maas_url="http://maas/MAAAS/" '
'maas_credentials="secret"')
@mock.patch.object(hooks, 'setup_maas_api')
@@ -279,3 +286,37 @@ class TestHooks(test_utils.CharmTestCase):
self.test_config.set('maintenance-mode', False)
hooks.config_changed()
mock_maintenance_mode.assert_called_with(False)
+
+ @mock.patch.object(hooks, 'needs_maas_dns_migration')
+ @mock.patch.object(hooks, 'relation_ids')
+ def test_migrate_maas_dns_no_migration(self, relation_ids,
+ needs_maas_dns_migration):
+ needs_maas_dns_migration.return_value = False
+ hooks.migrate_maas_dns()
+ relation_ids.assert_not_called()
+
+ @mock.patch.object(hooks, 'needs_maas_dns_migration')
+ @mock.patch.object(hooks, 'write_maas_dns_address')
+ @mock.patch.object(hooks, 'relation_ids')
+ @mock.patch.object(hooks, 'related_units')
+ @mock.patch.object(hooks, 'parse_data')
+ def test_migrate_maas_dns_(self, parse_data, related_units, relation_ids,
+ write_maas_dns_address,
+ needs_maas_dns_migration):
+ needs_maas_dns_migration.return_value = True
+ related_units.return_value = 'keystone/0'
+ relation_ids.return_value = 'ha:4'
+
+ def mock_parse_data(relid, unit, key):
+ if key == 'resources':
+ return {'res_keystone_public_hostname': 'ocf:maas:dns'}
+ elif key == 'resource_params':
+ return {'res_keystone_public_hostname':
+ 'params fqdn="keystone.maas" ip_address="172.16.0.1"'}
+ else:
+ raise KeyError("unexpected key {}".format(key))
+
+ parse_data.side_effect = mock_parse_data
+ hooks.migrate_maas_dns()
+ write_maas_dns_address.assert_called_with(
+ "res_keystone_public_hostname", "172.16.0.1")
diff --git a/unit_tests/test_hacluster_utils.py b/unit_tests/test_hacluster_utils.py
index 4cbfb79..701a810 100644
--- a/unit_tests/test_hacluster_utils.py
+++ b/unit_tests/test_hacluster_utils.py
@@ -381,3 +381,30 @@ class UtilsTestCase(unittest.TestCase):
utils.maintenance_mode(False)
mock_get_property.assert_called_with('maintenance-mode')
mock_set_property.assert_not_called()
+
+ @mock.patch('subprocess.check_call')
+ def test_needs_maas_dns_migration(self, check_call):
+ ret = utils.needs_maas_dns_migration()
+ self.assertEqual(True, ret)
+
+ check_call.side_effect = subprocess.CalledProcessError(1, '')
+ ret = utils.needs_maas_dns_migration()
+ self.assertEqual(False, ret)
+
+ def test_get_ip_addr_from_resource_params(self):
+ param_str = 'params fqdn="keystone.maas" ip_address="{}" '
+ for addr in ("172.16.0.4", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"):
+ ip = utils.get_ip_addr_from_resource_params(param_str.format(addr))
+ self.assertEqual(addr, ip)
+
+ ip = utils.get_ip_addr_from_resource_params("no_ip_addr")
+ self.assertEqual(None, ip)
+
+ @mock.patch.object(utils, 'write_file')
+ @mock.patch.object(utils, 'mkdir')
+ def test_write_maas_dns_address(self, mkdir, write_file):
+ utils.write_maas_dns_address("res_keystone_public_hostname",
+ "172.16.0.1")
+ mkdir.assert_called_once_with("/etc/maas_dns")
+ write_file.assert_called_once_with(
+ "/etc/maas_dns/res_keystone_public_hostname", content="172.16.0.1")