Check for two IP addresses assigned to same host
When parsing the openstack_user_config.yml file, the code would not account for user mistakes when multiple IP addresses were specified for the same hostname. When multiple IP addresses were specified, the last one parsed would be written to the inventory. This change instead throws a runtime error when this situation arises, so that the invalid config cannot be written. The tox.ini configuration is modified to make sure that the insert order on the configuration dictionary is the same on every run of the tests. Were this not set, the insertion order may well change dependon on the hash seed, which would cause test failures because the assertions would not match. An OrderedDict is also used to ensure platform differences don't affect testing order. The behavior of this class shouldn't differ from normal dictionaries in a way that invalidates the test cases. Change-Id: I7c724b1dd668a8372bf2dafaf3461e0a3cb1a557
This commit is contained in:
parent
2a3c695972
commit
8399965ed0
@ -68,6 +68,24 @@ class MultipleHostsWithOneIPError(Exception):
|
|||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleIpForHostError(Exception):
|
||||||
|
def __init__(self, hostname, current_ip, new_ip):
|
||||||
|
self.hostname = hostname
|
||||||
|
self.current_ip = current_ip
|
||||||
|
self.new_ip = new_ip
|
||||||
|
|
||||||
|
# Sort the IPs for our error message so we're always consistent.
|
||||||
|
ips = [current_ip, new_ip]
|
||||||
|
ips.sort()
|
||||||
|
|
||||||
|
error_msg = "Host {hostname} has both {ips[0]} and {ips[1]} assigned"
|
||||||
|
|
||||||
|
self.message = error_msg.format(hostname=hostname, ips=ips)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
def args():
|
def args():
|
||||||
"""Setup argument Parsing."""
|
"""Setup argument Parsing."""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@ -389,8 +407,8 @@ def user_defined_setup(config, inventory):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for _key, _value in value.iteritems():
|
for _key, _value in value.iteritems():
|
||||||
if _key not in inventory['_meta']['hostvars']:
|
if _key not in hvs:
|
||||||
inventory['_meta']['hostvars'][_key] = {}
|
hvs[_key] = {}
|
||||||
|
|
||||||
hvs[_key].update({
|
hvs[_key].update({
|
||||||
'ansible_ssh_host': _value['ip'],
|
'ansible_ssh_host': _value['ip'],
|
||||||
@ -894,6 +912,28 @@ def _check_same_ip_to_multiple_host(config):
|
|||||||
raise MultipleHostsWithOneIPError(*info)
|
raise MultipleHostsWithOneIPError(*info)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_multiple_ips_to_host(config):
|
||||||
|
"""Check for multiple IPs assigned to a single hostname
|
||||||
|
|
||||||
|
:param: config: ``dict`` User provided configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Extract only the dictionaries in the host groups.
|
||||||
|
host_ip_map = {}
|
||||||
|
for groupnames, group in config.items():
|
||||||
|
if '_hosts' in groupnames:
|
||||||
|
for hostname, entries in group.items():
|
||||||
|
if hostname not in host_ip_map:
|
||||||
|
host_ip_map[hostname] = entries['ip']
|
||||||
|
else:
|
||||||
|
current_ip = host_ip_map[hostname]
|
||||||
|
new_ip = entries['ip']
|
||||||
|
if not current_ip == new_ip:
|
||||||
|
raise MultipleIpForHostError(hostname, current_ip,
|
||||||
|
new_ip)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _check_config_settings(cidr_networks, config, container_skel):
|
def _check_config_settings(cidr_networks, config, container_skel):
|
||||||
"""check preciseness of config settings
|
"""check preciseness of config settings
|
||||||
|
|
||||||
@ -938,6 +978,8 @@ def _check_config_settings(cidr_networks, config, container_skel):
|
|||||||
# look for same ip address assigned to different hosts
|
# look for same ip address assigned to different hosts
|
||||||
_check_same_ip_to_multiple_host(config)
|
_check_same_ip_to_multiple_host(config)
|
||||||
|
|
||||||
|
_check_multiple_ips_to_host(config)
|
||||||
|
|
||||||
|
|
||||||
def load_environment(config_path):
|
def load_environment(config_path):
|
||||||
"""Create an environment dictionary from config files
|
"""Create an environment dictionary from config files
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- Assigning multiple IP addresses to the same host name will now result in
|
||||||
|
an inventory error before running any playbooks.
|
@ -390,6 +390,11 @@ class TestConfigChecks(unittest.TestCase):
|
|||||||
user_defined_config[group][new_hostname] = old_hostname_settings
|
user_defined_config[group][new_hostname] = old_hostname_settings
|
||||||
self.write_config()
|
self.write_config()
|
||||||
|
|
||||||
|
def set_new_ip(self, user_defined_config, group, hostname, ip):
|
||||||
|
# Sets an IP address for a specified host.
|
||||||
|
user_defined_config[group][hostname]['ip'] = ip
|
||||||
|
self.write_config()
|
||||||
|
|
||||||
def test_provider_networks_check(self):
|
def test_provider_networks_check(self):
|
||||||
# create config file without provider networks
|
# create config file without provider networks
|
||||||
self.delete_config_key(self.user_defined_config, 'provider_networks')
|
self.delete_config_key(self.user_defined_config, 'provider_networks')
|
||||||
@ -440,6 +445,55 @@ class TestConfigChecks(unittest.TestCase):
|
|||||||
"assign same ip to both hosts")
|
"assign same ip to both hosts")
|
||||||
self.assertEqual(context.exception.message, expectedLog)
|
self.assertEqual(context.exception.message, expectedLog)
|
||||||
|
|
||||||
|
def test_one_host_two_ips_externally(self):
|
||||||
|
# haproxy chosen because it was last in the config file as of
|
||||||
|
# writing
|
||||||
|
self.set_new_ip(self.user_defined_config, 'haproxy_hosts', 'aio1',
|
||||||
|
'172.29.236.101')
|
||||||
|
with self.assertRaises(di.MultipleIpForHostError) as context:
|
||||||
|
get_inventory()
|
||||||
|
expectedLog = ("Host aio1 has both 172.29.236.100 and 172.29.236.101 "
|
||||||
|
"assigned")
|
||||||
|
self.assertEqual(context.exception.message, expectedLog)
|
||||||
|
|
||||||
|
def test_two_ips(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'] = {
|
||||||
|
'host1': {
|
||||||
|
'ip': '192.168.1.2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with self.assertRaises(di.MultipleIpForHostError) as context:
|
||||||
|
di._check_multiple_ips_to_host(config)
|
||||||
|
self.assertEqual(context.exception.current_ip, '192.168.1.1')
|
||||||
|
self.assertEqual(context.exception.new_ip, '192.168.1.2')
|
||||||
|
self.assertEqual(context.exception.hostname, 'host1')
|
||||||
|
|
||||||
|
def test_correct_hostname_ip_map(self):
|
||||||
|
config = {
|
||||||
|
'infra_hosts': {
|
||||||
|
'host1': {
|
||||||
|
'ip': '192.168.1.1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'compute_hosts': {
|
||||||
|
'host2': {
|
||||||
|
'ip': '192.168.1.2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ret = di._check_multiple_ips_to_host(config)
|
||||||
|
self.assertTrue(ret)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
if self.config_changed:
|
if self.config_changed:
|
||||||
# get back our initial user config file
|
# get back our initial user config file
|
||||||
|
4
tox.ini
4
tox.ini
@ -90,6 +90,10 @@ commands =
|
|||||||
{toxinidir}/playbooks/*.yml"
|
{toxinidir}/playbooks/*.yml"
|
||||||
|
|
||||||
[testenv:inventory]
|
[testenv:inventory]
|
||||||
|
# Use a fixed seed since some inventory tests rely on specific ordering
|
||||||
|
setenv =
|
||||||
|
{[testenv]setenv}
|
||||||
|
PYTHONHASHSEED = 100
|
||||||
commands =
|
commands =
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run {toxinidir}/tests/test_inventory.py
|
coverage run {toxinidir}/tests/test_inventory.py
|
||||||
|
Loading…
Reference in New Issue
Block a user