diff --git a/playbooks/inventory/dynamic_inventory.py b/playbooks/inventory/dynamic_inventory.py index 17a40b6ab7..b36b10268f 100755 --- a/playbooks/inventory/dynamic_inventory.py +++ b/playbooks/inventory/dynamic_inventory.py @@ -49,6 +49,22 @@ REQUIRED_HOSTVARS = [ ] +class MultipleHostsWithOneIPError(Exception): + def __init__(self, ip, assigned_host, new_host): + self.ip = ip + self.assigned_host = assigned_host + self.new_host = new_host + + error_msg = ("ip address:{} has already been " + "assigned to host:{}, cannot " + "assign same ip to host:{}") + + self.message = error_msg.format(ip, assigned_host, new_host) + + def __str__(self): + return self.message + + def args(): """Setup argument Parsing.""" parser = argparse.ArgumentParser( @@ -855,6 +871,26 @@ def _extra_config(user_defined_config, base_dir): ) +def _check_same_ip_to_multiple_host(config): + """Check for IPs assigned to multiple hosts + + : param: config: ``dict`` User provided configuration + """ + + ips_to_hostnames_mapping = dict() + for key, value in config.iteritems(): + if key.endswith('hosts'): + for _key, _value in value.iteritems(): + hostname = _key + ip = _value['ip'] + if not (ip in ips_to_hostnames_mapping): + ips_to_hostnames_mapping[ip] = hostname + else: + if ips_to_hostnames_mapping[ip] != hostname: + info = (ip, ips_to_hostnames_mapping[ip], hostname) + raise MultipleHostsWithOneIPError(*info) + + def _check_config_settings(cidr_networks, config, container_skel): """check preciseness of config settings @@ -896,6 +932,8 @@ def _check_config_settings(cidr_networks, config, container_skel): raise SystemExit( "can't find " + q_name + " in cidr_networks" ) + # look for same ip address assigned to different hosts + _check_same_ip_to_multiple_host(config) def load_environment(config_path): diff --git a/tests/test_inventory.py b/tests/test_inventory.py index 1e83a45906..95ed5a5c6c 100644 --- a/tests/test_inventory.py +++ b/tests/test_inventory.py @@ -329,8 +329,8 @@ class TestIps(unittest.TestCase): class TestConfigChecks(unittest.TestCase): - def setUp(self): + self.config_changed = False self.user_defined_config = dict() with open(USER_CONFIG_FILE, 'rb') as f: self.user_defined_config.update(yaml.safe_load(f.read()) or {}) @@ -351,6 +351,7 @@ class TestConfigChecks(unittest.TestCase): self.write_config() def write_config(self): + self.config_changed = True # rename temporarily our user_config_file so we can use the new one os.rename(USER_CONFIG_FILE, USER_CONFIG_FILE + ".tmp") # Save new user_config_file @@ -380,6 +381,14 @@ class TestConfigChecks(unittest.TestCase): expectedLog = "No container CIDR specified in user config" self.assertEqual(context.exception.message, expectedLog) + def set_new_hostname(self, user_defined_config, group, + old_hostname, new_hostname): + self.config_changed = True + # set a new name for the specified hostname + old_hostname_settings = user_defined_config[group].pop(old_hostname) + user_defined_config[group][new_hostname] = old_hostname_settings + self.write_config() + def test_provider_networks_check(self): # create config file without provider networks self.delete_config_key(self.user_defined_config, 'provider_networks') @@ -398,10 +407,42 @@ class TestConfigChecks(unittest.TestCase): expectedLog = "global_overrides can't be found in user config" self.assertEqual(context.exception.message, expectedLog) + def test_two_hosts_same_ip(self): + # Use an OrderedDict to be certain our testing order is preserved + # Even with the same hash seed, different OSes get different results, + # eg. local OS X vs gate's Linux + config = collections.OrderedDict() + config['infra_hosts'] = { + 'host1': { + 'ip': '192.168.1.1' + } + } + config['compute_hosts'] = { + 'host2': { + 'ip': '192.168.1.1' + } + } + + with self.assertRaises(di.MultipleHostsWithOneIPError) as context: + di._check_same_ip_to_multiple_host(config) + self.assertEqual(context.exception.ip, '192.168.1.1') + self.assertEqual(context.exception.assigned_host, 'host1') + self.assertEqual(context.exception.new_host, 'host2') + + def test_two_hosts_same_ip_externally(self): + self.set_new_hostname(self.user_defined_config, "haproxy_hosts", + "aio1", "hap") + with self.assertRaises(di.MultipleHostsWithOneIPError) as context: + get_inventory() + expectedLog = ("ip address:172.29.236.100 has already been assigned" + " to host:aio1, cannot assign same ip to host:hap") + self.assertEqual(context.exception.message, expectedLog) + def tearDown(self): - # get back our initial user config file - os.remove(USER_CONFIG_FILE) - os.rename(USER_CONFIG_FILE + ".tmp", USER_CONFIG_FILE) + if self.config_changed: + # get back our initial user config file + os.remove(USER_CONFIG_FILE) + os.rename(USER_CONFIG_FILE + ".tmp", USER_CONFIG_FILE) if __name__ == '__main__':