# Copyright 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import inventory from io import StringIO import unittest from unittest import mock from collections import OrderedDict import sys path = "./contrib/inventory_builder/" if path not in sys.path: sys.path.append(path) import inventory # noqa class TestInventoryPrintHostnames(unittest.TestCase): @mock.patch('ruamel.yaml.YAML.load') def test_print_hostnames(self, load_mock): mock_io = mock.mock_open(read_data='') load_mock.return_value = OrderedDict({'all': {'hosts': { 'node1': {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}, 'node2': {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'}}}}) with mock.patch('builtins.open', mock_io): with self.assertRaises(SystemExit) as cm: with mock.patch('sys.stdout', new_callable=StringIO) as stdout: inventory.KubesprayInventory( changed_hosts=["print_hostnames"], config_file="file") self.assertEqual("node1 node2\n", stdout.getvalue()) self.assertEqual(cm.exception.code, 0) class TestInventory(unittest.TestCase): @mock.patch('inventory.sys') def setUp(self, sys_mock): sys_mock.exit = mock.Mock() super(TestInventory, self).setUp() self.data = ['10.90.3.2', '10.90.3.3', '10.90.3.4'] self.inv = inventory.KubesprayInventory() def test_get_ip_from_opts(self): optstring = {'ansible_host': '10.90.3.2', 'ip': '10.90.3.2', 'access_ip': '10.90.3.2'} expected = "10.90.3.2" result = self.inv.get_ip_from_opts(optstring) self.assertEqual(expected, result) def test_get_ip_from_opts_invalid(self): optstring = "notanaddr=value something random!chars:D" self.assertRaisesRegex(ValueError, "IP parameter not found", self.inv.get_ip_from_opts, optstring) def test_ensure_required_groups(self): groups = ['group1', 'group2'] self.inv.ensure_required_groups(groups) for group in groups: self.assertIn(group, self.inv.yaml_config['all']['children']) def test_get_host_id(self): hostnames = ['node99', 'no99de01', '01node01', 'node1.domain', 'node3.xyz123.aaa'] expected = [99, 1, 1, 1, 3] for hostname, expected in zip(hostnames, expected): result = self.inv.get_host_id(hostname) self.assertEqual(expected, result) def test_get_host_id_invalid(self): bad_hostnames = ['node', 'no99de', '01node', 'node.111111'] for hostname in bad_hostnames: self.assertRaisesRegex(ValueError, "Host name must end in an", self.inv.get_host_id, hostname) def test_build_hostnames_add_duplicate(self): changed_hosts = ['10.90.0.2'] expected = OrderedDict([('node3', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'})]) self.inv.yaml_config['all']['hosts'] = expected result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result) def test_build_hostnames_add_two(self): changed_hosts = ['10.90.0.2', '10.90.0.3'] expected = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) self.inv.yaml_config['all']['hosts'] = OrderedDict() result = self.inv.build_hostnames(changed_hosts) self.assertEqual(expected, result) def test_build_hostnames_add_three(self): changed_hosts = ['10.90.0.2', '10.90.0.3', '10.90.0.4'] expected = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'}), ('node3', {'ansible_host': '10.90.0.4', 'ip': '10.90.0.4', 'access_ip': '10.90.0.4'})]) result = self.inv.build_hostnames(changed_hosts) self.assertEqual(expected, result) def test_build_hostnames_add_one(self): changed_hosts = ['10.90.0.2'] expected = OrderedDict([('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'})]) result = self.inv.build_hostnames(changed_hosts) self.assertEqual(expected, result) def test_build_hostnames_delete_first(self): changed_hosts = ['-10.90.0.2'] existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) self.inv.yaml_config['all']['hosts'] = existing_hosts expected = OrderedDict([ ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result) def test_build_hostnames_delete_by_hostname(self): changed_hosts = ['-node1'] existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) self.inv.yaml_config['all']['hosts'] = existing_hosts expected = OrderedDict([ ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result) def test_exists_hostname_positive(self): hostname = 'node1' expected = True existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) result = self.inv.exists_hostname(existing_hosts, hostname) self.assertEqual(expected, result) def test_exists_hostname_negative(self): hostname = 'node99' expected = False existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) result = self.inv.exists_hostname(existing_hosts, hostname) self.assertEqual(expected, result) def test_exists_ip_positive(self): ip = '10.90.0.2' expected = True existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) result = self.inv.exists_ip(existing_hosts, ip) self.assertEqual(expected, result) def test_exists_ip_negative(self): ip = '10.90.0.200' expected = False existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) result = self.inv.exists_ip(existing_hosts, ip) self.assertEqual(expected, result) def test_delete_host_by_ip_positive(self): ip = '10.90.0.2' expected = OrderedDict([ ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) self.inv.delete_host_by_ip(existing_hosts, ip) self.assertEqual(expected, existing_hosts) def test_delete_host_by_ip_negative(self): ip = '10.90.0.200' existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'})]) self.assertRaisesRegex(ValueError, "Unable to find host", self.inv.delete_host_by_ip, existing_hosts, ip) def test_purge_invalid_hosts(self): proper_hostnames = ['node1', 'node2'] bad_host = 'doesnotbelong2' existing_hosts = OrderedDict([ ('node1', {'ansible_host': '10.90.0.2', 'ip': '10.90.0.2', 'access_ip': '10.90.0.2'}), ('node2', {'ansible_host': '10.90.0.3', 'ip': '10.90.0.3', 'access_ip': '10.90.0.3'}), ('doesnotbelong2', {'whateveropts=ilike'})]) self.inv.yaml_config['all']['hosts'] = existing_hosts self.inv.purge_invalid_hosts(proper_hostnames) self.assertNotIn( bad_host, self.inv.yaml_config['all']['hosts'].keys()) def test_add_host_to_group(self): group = 'etcd' host = 'node1' opts = {'ip': '10.90.0.2'} self.inv.add_host_to_group(group, host, opts) self.assertEqual( self.inv.yaml_config['all']['children'][group]['hosts'].get(host), None) def test_set_kube_control_plane(self): group = 'kube_control_plane' host = 'node1' self.inv.set_kube_control_plane([host]) self.assertIn( host, self.inv.yaml_config['all']['children'][group]['hosts']) def test_set_all(self): hosts = OrderedDict([ ('node1', 'opt1'), ('node2', 'opt2')]) self.inv.set_all(hosts) for host, opt in hosts.items(): self.assertEqual( self.inv.yaml_config['all']['hosts'].get(host), opt) def test_set_k8s_cluster(self): group = 'k8s_cluster' expected_hosts = ['kube_node', 'kube_control_plane'] self.inv.set_k8s_cluster() for host in expected_hosts: self.assertIn( host, self.inv.yaml_config['all']['children'][group]['children']) def test_set_kube_node(self): group = 'kube_node' host = 'node1' self.inv.set_kube_node([host]) self.assertIn( host, self.inv.yaml_config['all']['children'][group]['hosts']) def test_set_etcd(self): group = 'etcd' host = 'node1' self.inv.set_etcd([host]) self.assertIn( host, self.inv.yaml_config['all']['children'][group]['hosts']) def test_scale_scenario_one(self): num_nodes = 50 hosts = OrderedDict() for hostid in range(1, num_nodes+1): hosts["node" + str(hostid)] = "" self.inv.set_all(hosts) self.inv.set_etcd(list(hosts.keys())[0:3]) self.inv.set_kube_control_plane(list(hosts.keys())[0:2]) self.inv.set_kube_node(hosts.keys()) for h in range(3): self.assertFalse( list(hosts.keys())[h] in self.inv.yaml_config['all']['children']['kube_node']['hosts']) def test_scale_scenario_two(self): num_nodes = 500 hosts = OrderedDict() for hostid in range(1, num_nodes+1): hosts["node" + str(hostid)] = "" self.inv.set_all(hosts) self.inv.set_etcd(list(hosts.keys())[0:3]) self.inv.set_kube_control_plane(list(hosts.keys())[3:5]) self.inv.set_kube_node(hosts.keys()) for h in range(5): self.assertFalse( list(hosts.keys())[h] in self.inv.yaml_config['all']['children']['kube_node']['hosts']) def test_range2ips_range(self): changed_hosts = ['10.90.0.2', '10.90.0.4-10.90.0.6', '10.90.0.8'] expected = ['10.90.0.2', '10.90.0.4', '10.90.0.5', '10.90.0.6', '10.90.0.8'] result = self.inv.range2ips(changed_hosts) self.assertEqual(expected, result) def test_range2ips_incorrect_range(self): host_range = ['10.90.0.4-a.9b.c.e'] self.assertRaisesRegex(Exception, "Range of ip_addresses isn't valid", self.inv.range2ips, host_range) def test_build_hostnames_create_with_one_different_ips(self): changed_hosts = ['10.90.0.2,192.168.0.2'] expected = OrderedDict([('node1', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'})]) result = self.inv.build_hostnames(changed_hosts) self.assertEqual(expected, result) def test_build_hostnames_create_with_two_different_ips(self): changed_hosts = ['10.90.0.2,192.168.0.2', '10.90.0.3,192.168.0.3'] expected = OrderedDict([ ('node1', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node2', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'})]) result = self.inv.build_hostnames(changed_hosts) self.assertEqual(expected, result) def test_build_hostnames_create_with_three_different_ips(self): changed_hosts = ['10.90.0.2,192.168.0.2', '10.90.0.3,192.168.0.3', '10.90.0.4,192.168.0.4'] expected = OrderedDict([ ('node1', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node2', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node3', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'})]) result = self.inv.build_hostnames(changed_hosts) self.assertEqual(expected, result) def test_build_hostnames_overwrite_one_with_different_ips(self): changed_hosts = ['10.90.0.2,192.168.0.2'] expected = OrderedDict([('node1', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'})]) existing = OrderedDict([('node5', {'ansible_host': '192.168.0.5', 'ip': '10.90.0.5', 'access_ip': '192.168.0.5'})]) self.inv.yaml_config['all']['hosts'] = existing result = self.inv.build_hostnames(changed_hosts) self.assertEqual(expected, result) def test_build_hostnames_overwrite_three_with_different_ips(self): changed_hosts = ['10.90.0.2,192.168.0.2'] expected = OrderedDict([('node1', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'})]) existing = OrderedDict([ ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'}), ('node5', {'ansible_host': '192.168.0.5', 'ip': '10.90.0.5', 'access_ip': '192.168.0.5'})]) self.inv.yaml_config['all']['hosts'] = existing result = self.inv.build_hostnames(changed_hosts) self.assertEqual(expected, result) def test_build_hostnames_different_ips_add_duplicate(self): changed_hosts = ['10.90.0.2,192.168.0.2'] expected = OrderedDict([('node3', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'})]) existing = expected self.inv.yaml_config['all']['hosts'] = existing result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result) def test_build_hostnames_add_two_different_ips_into_one_existing(self): changed_hosts = ['10.90.0.3,192.168.0.3', '10.90.0.4,192.168.0.4'] expected = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'})]) existing = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'})]) self.inv.yaml_config['all']['hosts'] = existing result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result) def test_build_hostnames_add_two_different_ips_into_two_existing(self): changed_hosts = ['10.90.0.4,192.168.0.4', '10.90.0.5,192.168.0.5'] expected = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'}), ('node5', {'ansible_host': '192.168.0.5', 'ip': '10.90.0.5', 'access_ip': '192.168.0.5'})]) existing = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'})]) self.inv.yaml_config['all']['hosts'] = existing result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result) def test_build_hostnames_add_two_different_ips_into_three_existing(self): changed_hosts = ['10.90.0.5,192.168.0.5', '10.90.0.6,192.168.0.6'] expected = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'}), ('node5', {'ansible_host': '192.168.0.5', 'ip': '10.90.0.5', 'access_ip': '192.168.0.5'}), ('node6', {'ansible_host': '192.168.0.6', 'ip': '10.90.0.6', 'access_ip': '192.168.0.6'})]) existing = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'})]) self.inv.yaml_config['all']['hosts'] = existing result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result) # Add two IP addresses into a config that has # three already defined IP addresses. One of the IP addresses # is a duplicate. def test_build_hostnames_add_two_duplicate_one_overlap(self): changed_hosts = ['10.90.0.4,192.168.0.4', '10.90.0.5,192.168.0.5'] expected = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'}), ('node5', {'ansible_host': '192.168.0.5', 'ip': '10.90.0.5', 'access_ip': '192.168.0.5'})]) existing = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'})]) self.inv.yaml_config['all']['hosts'] = existing result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result) # Add two duplicate IP addresses into a config that has # three already defined IP addresses def test_build_hostnames_add_two_duplicate_two_overlap(self): changed_hosts = ['10.90.0.3,192.168.0.3', '10.90.0.4,192.168.0.4'] expected = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'})]) existing = OrderedDict([ ('node2', {'ansible_host': '192.168.0.2', 'ip': '10.90.0.2', 'access_ip': '192.168.0.2'}), ('node3', {'ansible_host': '192.168.0.3', 'ip': '10.90.0.3', 'access_ip': '192.168.0.3'}), ('node4', {'ansible_host': '192.168.0.4', 'ip': '10.90.0.4', 'access_ip': '192.168.0.4'})]) self.inv.yaml_config['all']['hosts'] = existing result = self.inv.build_hostnames(changed_hosts, True) self.assertEqual(expected, result)