From df78d323c0643fa5c7f04e20de2ff55776218df4 Mon Sep 17 00:00:00 2001 From: Troy Dawson Date: Mon, 26 Oct 2015 10:12:09 -0500 Subject: Add atomic-openshift-utils add atomic-openshift-utils to openshift-ansible.spec file no need for redundent spec file remove dependencies that are not needed --- utils/test/__init__.py | 0 utils/test/cli_installer_tests.py | 463 ++++++++++++++++++++++++++++++++++++++ utils/test/oo_config_tests.py | 154 +++++++++++++ 3 files changed, 617 insertions(+) create mode 100644 utils/test/__init__.py create mode 100644 utils/test/cli_installer_tests.py create mode 100644 utils/test/oo_config_tests.py (limited to 'utils/test') diff --git a/utils/test/__init__.py b/utils/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/test/cli_installer_tests.py b/utils/test/cli_installer_tests.py new file mode 100644 index 000000000..78e3ab3bf --- /dev/null +++ b/utils/test/cli_installer_tests.py @@ -0,0 +1,463 @@ +import sys +import copy +import os +import ConfigParser +import yaml + +import ooinstall.cli_installer as cli + +from click.testing import CliRunner +from oo_config_tests import OOInstallFixture +from mock import patch + + +MOCK_FACTS = { + '10.0.0.1': { + 'common': { + 'ip': '10.0.0.1', + 'public_ip': '10.0.0.1', + 'hostname': 'master-private.example.com', + 'public_hostname': 'master.example.com' + } + }, + '10.0.0.2': { + 'common': { + 'ip': '10.0.0.2', + 'public_ip': '10.0.0.2', + 'hostname': 'node1-private.example.com', + 'public_hostname': 'node1.example.com' + } + }, + '10.0.0.3': { + 'common': { + 'ip': '10.0.0.3', + 'public_ip': '10.0.0.3', + 'hostname': 'node2-private.example.com', + 'public_hostname': 'node2.example.com' + } + }, +} + +# Substitute in a product name before use: +SAMPLE_CONFIG = """ +variant: %s +ansible_ssh_user: root +hosts: + - ip: 10.0.0.1 + hostname: master-private.example.com + public_ip: 24.222.0.1 + public_hostname: master.example.com + master: true + node: true + - ip: 10.0.0.2 + hostname: node1-private.example.com + public_ip: 24.222.0.2 + public_hostname: node1.example.com + node: true + - ip: 10.0.0.3 + hostname: node2-private.example.com + public_ip: 24.222.0.3 + public_hostname: node2.example.com + node: true +""" + + +class OOCliFixture(OOInstallFixture): + + def setUp(self): + OOInstallFixture.setUp(self) + self.runner = CliRunner() + + # Add any arguments you would like to test here, the defaults ensure + # we only do unattended invocations here, and using temporary files/dirs. + self.cli_args = ["-a", self.work_dir] + + def run_cli(self): + return self.runner.invoke(cli.main, self.cli_args) + + def assert_result(self, result, exit_code): + if result.exception is not None or result.exit_code != exit_code: + print("Unexpected result from CLI execution") + print("Exit code: %s" % result.exit_code) + print("Exception: %s" % result.exception) + print result.exc_info + import traceback + traceback.print_exception(*result.exc_info) + print("Output:\n%s" % result.output) + self.assertTrue("Exception during CLI execution", False) + + def _read_yaml(self, config_file_path): + f = open(config_file_path, 'r') + config = yaml.safe_load(f.read()) + f.close() + return config + + +class UnattendedCliTests(OOCliFixture): + + def setUp(self): + OOCliFixture.setUp(self) + self.cli_args.append("-u") + + @patch('ooinstall.install_transactions.run_main_playbook') + @patch('ooinstall.install_transactions.load_system_facts') + def test_cfg_full_run(self, load_facts_mock, run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_playbook_mock.return_value = 0 + + config_file = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), SAMPLE_CONFIG % 'openshift-enterprise') + + self.cli_args.extend(["-c", config_file]) + result = self.runner.invoke(cli.main, self.cli_args) + self.assert_result(result, 0) + + load_facts_args = load_facts_mock.call_args[0] + self.assertEquals(os.path.join(self.work_dir, ".ansible/hosts"), + load_facts_args[0]) + self.assertEquals(os.path.join(self.work_dir, + "playbooks/byo/openshift_facts.yml"), load_facts_args[1]) + env_vars = load_facts_args[2] + self.assertEquals(os.path.join(self.work_dir, + '.ansible/callback_facts.yaml'), + env_vars['OO_INSTALL_CALLBACK_FACTS_YAML']) + self.assertEqual('/tmp/ansible.log', env_vars['ANSIBLE_LOG_PATH']) + self.assertTrue('ANSIBLE_CONFIG' not in env_vars) + + # Make sure we ran on the expected masters and nodes: + hosts = run_playbook_mock.call_args[0][0] + hosts_to_run_on = run_playbook_mock.call_args[0][1] + self.assertEquals(3, len(hosts)) + self.assertEquals(3, len(hosts_to_run_on)) + + @patch('ooinstall.install_transactions.run_main_playbook') + @patch('ooinstall.install_transactions.load_system_facts') + def test_inventory_write(self, load_facts_mock, run_playbook_mock): + + # Add an ssh user so we can verify it makes it to the inventory file: + merged_config = "%s\n%s" % (SAMPLE_CONFIG % 'openshift-enterprise', + "ansible_ssh_user: bob") + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_playbook_mock.return_value = 0 + + config_file = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), merged_config) + + self.cli_args.extend(["-c", config_file]) + result = self.runner.invoke(cli.main, self.cli_args) + self.assert_result(result, 0) + + # Check the inventory file looks as we would expect: + inventory = ConfigParser.ConfigParser(allow_no_value=True) + inventory.read(os.path.join(self.work_dir, '.ansible/hosts')) + self.assertEquals('bob', + inventory.get('OSEv3:vars', 'ansible_ssh_user')) + self.assertEquals('openshift-enterprise', + inventory.get('OSEv3:vars', 'deployment_type')) + + # Check the masters: + self.assertEquals(1, len(inventory.items('masters'))) + self.assertEquals(3, len(inventory.items('nodes'))) + + for item in inventory.items('masters'): + # ansible host lines do NOT parse nicely: + master_line = item[0] + if item[1] is not None: + master_line = "%s=%s" % (master_line, item[1]) + self.assertTrue('openshift_ip' in master_line) + self.assertTrue('openshift_public_ip' in master_line) + self.assertTrue('openshift_hostname' in master_line) + self.assertTrue('openshift_public_hostname' in master_line) + + @patch('ooinstall.install_transactions.run_main_playbook') + @patch('ooinstall.install_transactions.load_system_facts') + def test_variant_version_latest_assumed(self, load_facts_mock, + run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_playbook_mock.return_value = 0 + + config_file = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), SAMPLE_CONFIG % 'openshift-enterprise') + + self.cli_args.extend(["-c", config_file]) + result = self.runner.invoke(cli.main, self.cli_args) + self.assert_result(result, 0) + + written_config = self._read_yaml(config_file) + + self.assertEquals('openshift-enterprise', written_config['variant']) + # We didn't specify a version so the latest should have been assumed, + # and written to disk: + self.assertEquals('3.1', written_config['variant_version']) + + # Make sure the correct value was passed to ansible: + inventory = ConfigParser.ConfigParser(allow_no_value=True) + inventory.read(os.path.join(self.work_dir, '.ansible/hosts')) + self.assertEquals('openshift-enterprise', + inventory.get('OSEv3:vars', 'deployment_type')) + + @patch('ooinstall.install_transactions.run_main_playbook') + @patch('ooinstall.install_transactions.load_system_facts') + def test_variant_version_preserved(self, load_facts_mock, + run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_playbook_mock.return_value = 0 + + config = SAMPLE_CONFIG % 'openshift-enterprise' + config = '%s\n%s' % (config, 'variant_version: 3.0') + config_file = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), config) + + self.cli_args.extend(["-c", config_file]) + result = self.runner.invoke(cli.main, self.cli_args) + self.assert_result(result, 0) + + written_config = self._read_yaml(config_file) + + self.assertEquals('openshift-enterprise', written_config['variant']) + # Make sure our older version was preserved: + # and written to disk: + self.assertEquals('3.0', written_config['variant_version']) + + inventory = ConfigParser.ConfigParser(allow_no_value=True) + inventory.read(os.path.join(self.work_dir, '.ansible/hosts')) + self.assertEquals('enterprise', + inventory.get('OSEv3:vars', 'deployment_type')) + + @patch('ooinstall.install_transactions.run_ansible') + @patch('ooinstall.install_transactions.load_system_facts') + def test_no_ansible_config_specified(self, load_facts_mock, run_ansible_mock): + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_ansible_mock.return_value = 0 + + config = SAMPLE_CONFIG % 'openshift-enterprise' + + self._ansible_config_test(load_facts_mock, run_ansible_mock, + config, None, None) + + @patch('ooinstall.install_transactions.run_ansible') + @patch('ooinstall.install_transactions.load_system_facts') + def test_ansible_config_specified_cli(self, load_facts_mock, run_ansible_mock): + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_ansible_mock.return_value = 0 + + config = SAMPLE_CONFIG % 'openshift-enterprise' + ansible_config = os.path.join(self.work_dir, 'ansible.cfg') + + self._ansible_config_test(load_facts_mock, run_ansible_mock, + config, ansible_config, ansible_config) + + @patch('ooinstall.install_transactions.run_ansible') + @patch('ooinstall.install_transactions.load_system_facts') + def test_ansible_config_specified_in_installer_config(self, + load_facts_mock, run_ansible_mock): + + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_ansible_mock.return_value = 0 + + ansible_config = os.path.join(self.work_dir, 'ansible.cfg') + config = SAMPLE_CONFIG % 'openshift-enterprise' + config = "%s\nansible_config: %s" % (config, ansible_config) + self._ansible_config_test(load_facts_mock, run_ansible_mock, + config, None, ansible_config) + + def _ansible_config_test(self, load_facts_mock, run_ansible_mock, + installer_config, ansible_config_cli=None, expected_result=None): + """ + Utility method for testing the ways you can specify the ansible config. + """ + + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_ansible_mock.return_value = 0 + + config_file = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), installer_config) + + self.cli_args.extend(["-c", config_file]) + if ansible_config_cli: + self.cli_args.extend(["--ansible-config", ansible_config_cli]) + result = self.runner.invoke(cli.main, self.cli_args) + self.assert_result(result, 0) + + # Test the env vars for facts playbook: + facts_env_vars = load_facts_mock.call_args[0][2] + if expected_result: + self.assertEquals(expected_result, facts_env_vars['ANSIBLE_CONFIG']) + else: + self.assertFalse('ANSIBLE_CONFIG' in facts_env_vars) + + # Test the env vars for main playbook: + playbook, inventory, env_vars = run_ansible_mock.call_args[0] + if expected_result: + self.assertEquals(expected_result, env_vars['ANSIBLE_CONFIG']) + else: + self.assertFalse('ANSIBLE_CONFIG' in env_vars) + + +class AttendedCliTests(OOCliFixture): + + def setUp(self): + OOCliFixture.setUp(self) + # Doesn't exist but keeps us from reading the local users config: + self.config_file = os.path.join(self.work_dir, 'config.yml') + self.cli_args.extend(["-c", self.config_file]) + + def _build_input(self, ssh_user=None, hosts=None, variant_num=None, add_nodes=None, confirm_facts=None): + """ + Builds a CLI input string with newline characters to simulate + the full run. + This gives us only one place to update when the input prompts change. + """ + + inputs = [ + 'y', # let's proceed + ] + if ssh_user: + inputs.append(ssh_user) + + if hosts: + i = 0 + for (host, is_master) in hosts: + inputs.append(host) + inputs.append('y' if is_master else 'n') + inputs.append('rpm') + if i < len(hosts) - 1: + inputs.append('y') # Add more hosts + else: + inputs.append('n') # Done adding hosts + i += 1 + + if variant_num: + inputs.append(str(variant_num)) # Choose variant + version + + # TODO: support option 2, fresh install + if add_nodes: + inputs.append('1') # Add more nodes + i = 0 + for (host, is_master) in add_nodes: + inputs.append(host) + inputs.append('y' if is_master else 'n') + inputs.append('rpm') + if i < len(add_nodes) - 1: + inputs.append('y') # Add more hosts + else: + inputs.append('n') # Done adding hosts + i += 1 + + inputs.extend([ + confirm_facts, + 'y', # lets do this + ]) + + return '\n'.join(inputs) + + def _verify_load_facts(self, load_facts_mock): + """ Check that we ran load facts with expected inputs. """ + load_facts_args = load_facts_mock.call_args[0] + self.assertEquals(os.path.join(self.work_dir, ".ansible/hosts"), + load_facts_args[0]) + self.assertEquals(os.path.join(self.work_dir, + "playbooks/byo/openshift_facts.yml"), load_facts_args[1]) + env_vars = load_facts_args[2] + self.assertEquals(os.path.join(self.work_dir, + '.ansible/callback_facts.yaml'), + env_vars['OO_INSTALL_CALLBACK_FACTS_YAML']) + self.assertEqual('/tmp/ansible.log', env_vars['ANSIBLE_LOG_PATH']) + + def _verify_run_playbook(self, run_playbook_mock, exp_hosts_len, exp_hosts_to_run_on_len): + """ Check that we ran playbook with expected inputs. """ + hosts = run_playbook_mock.call_args[0][0] + hosts_to_run_on = run_playbook_mock.call_args[0][1] + self.assertEquals(exp_hosts_len, len(hosts)) + self.assertEquals(exp_hosts_to_run_on_len, len(hosts_to_run_on)) + + def _verify_config_hosts(self, written_config, host_count): + self.assertEquals(host_count, len(written_config['hosts'])) + for h in written_config['hosts']: + self.assertTrue(h['node']) + self.assertTrue('ip' in h) + self.assertTrue('hostname' in h) + self.assertTrue('public_ip' in h) + self.assertTrue('public_hostname' in h) + + @patch('ooinstall.install_transactions.run_main_playbook') + @patch('ooinstall.install_transactions.load_system_facts') + def test_full_run(self, load_facts_mock, run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_playbook_mock.return_value = 0 + + cli_input = self._build_input(hosts=[ + ('10.0.0.1', True), + ('10.0.0.2', False), + ('10.0.0.3', False)], + ssh_user='root', + variant_num=1, + confirm_facts='y') + result = self.runner.invoke(cli.main, self.cli_args, + input=cli_input) + self.assert_result(result, 0) + + self._verify_load_facts(load_facts_mock) + self._verify_run_playbook(run_playbook_mock, 3, 3) + + written_config = self._read_yaml(self.config_file) + self._verify_config_hosts(written_config, 3) + + @patch('ooinstall.install_transactions.run_main_playbook') + @patch('ooinstall.install_transactions.load_system_facts') + def test_add_nodes(self, load_facts_mock, run_playbook_mock): + + # Modify the mock facts to return a version indicating OpenShift + # is already installed on our master, and the first node. + mock_facts = copy.deepcopy(MOCK_FACTS) + mock_facts['10.0.0.1']['common']['version'] = "3.0.0" + mock_facts['10.0.0.2']['common']['version'] = "3.0.0" + + load_facts_mock.return_value = (mock_facts, 0) + run_playbook_mock.return_value = 0 + + cli_input = self._build_input(hosts=[ + ('10.0.0.1', True), + ('10.0.0.2', False), + ], + add_nodes=[('10.0.0.3', False)], + ssh_user='root', + variant_num=1, + confirm_facts='y') + result = self.runner.invoke(cli.main, + self.cli_args, + input=cli_input) + self.assert_result(result, 0) + + self._verify_load_facts(load_facts_mock) + self._verify_run_playbook(run_playbook_mock, 3, 2) + + written_config = self._read_yaml(self.config_file) + self._verify_config_hosts(written_config, 3) + + @patch('ooinstall.install_transactions.run_main_playbook') + @patch('ooinstall.install_transactions.load_system_facts') + def test_fresh_install_with_config(self, load_facts_mock, run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_playbook_mock.return_value = 0 + + config_file = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), + SAMPLE_CONFIG % 'openshift-enterprise') + cli_input = self._build_input(confirm_facts='y') + self.cli_args.extend(["-c", config_file]) + result = self.runner.invoke(cli.main, + self.cli_args, + input=cli_input) + self.assert_result(result, 0) + + self._verify_load_facts(load_facts_mock) + self._verify_run_playbook(run_playbook_mock, 3, 3) + + written_config = self._read_yaml(config_file) + self._verify_config_hosts(written_config, 3) + +# TODO: test with config file, attended add node +# TODO: test with config file, attended new node already in config file +# TODO: test with config file, attended new node already in config file, plus manually added nodes +# TODO: test with config file, attended reject facts diff --git a/utils/test/oo_config_tests.py b/utils/test/oo_config_tests.py new file mode 100644 index 000000000..8459b7c39 --- /dev/null +++ b/utils/test/oo_config_tests.py @@ -0,0 +1,154 @@ +import os +import unittest +import tempfile +import shutil +import yaml + +from ooinstall.oo_config import OOConfig, Host, OOConfigInvalidHostError + +SAMPLE_CONFIG = """ +variant: openshift-enterprise +ansible_ssh_user: root +hosts: + - ip: 10.0.0.1 + hostname: master-private.example.com + public_ip: 24.222.0.1 + public_hostname: master.example.com + master: true + node: true + - ip: 10.0.0.2 + hostname: node1-private.example.com + public_ip: 24.222.0.2 + public_hostname: node1.example.com + node: true + - ip: 10.0.0.3 + hostname: node2-private.example.com + public_ip: 24.222.0.3 + public_hostname: node2.example.com + node: true +""" + +CONFIG_INCOMPLETE_FACTS = """ +hosts: + - ip: 10.0.0.1 + hostname: master-private.example.com + public_ip: 24.222.0.1 + public_hostname: master.example.com + master: true + - ip: 10.0.0.2 + hostname: node1-private.example.com + public_ip: 24.222.0.2 + node: true + - ip: 10.0.0.3 + node: true +""" + + +class OOInstallFixture(unittest.TestCase): + + def setUp(self): + self.tempfiles = [] + self.work_dir = tempfile.mkdtemp(prefix='ooconfigtests') + self.tempfiles.append(self.work_dir) + + def tearDown(self): + for path in self.tempfiles: + if os.path.isdir(path): + shutil.rmtree(path) + else: + os.remove(path) + + def write_config(self, path, config_str): + """ + Write given config to a temporary file which will be cleaned + up in teardown. + Returns full path to the file. + """ + f = open(path, 'w') + f.write(config_str) + f.close() + return path + + +class OOConfigTests(OOInstallFixture): + + def test_load_config(self): + + cfg_path = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), SAMPLE_CONFIG) + ooconfig = OOConfig(cfg_path) + + self.assertEquals(3, len(ooconfig.hosts)) + self.assertEquals("10.0.0.1", ooconfig.hosts[0].name) + self.assertEquals("10.0.0.1", ooconfig.hosts[0].ip) + self.assertEquals("master-private.example.com", ooconfig.hosts[0].hostname) + + self.assertEquals(["10.0.0.1", "10.0.0.2", "10.0.0.3"], + [host['ip'] for host in ooconfig.settings['hosts']]) + + self.assertEquals('openshift-enterprise', ooconfig.settings['variant']) + + def test_load_complete_validated_facts(self): + cfg_path = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), SAMPLE_CONFIG) + ooconfig = OOConfig(cfg_path) + missing_host_facts = ooconfig.calc_missing_facts() + self.assertEquals(0, len(missing_host_facts)) + + # Test missing optional facts the user must confirm: + def test_load_host_incomplete_facts(self): + cfg_path = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), CONFIG_INCOMPLETE_FACTS) + ooconfig = OOConfig(cfg_path) + missing_host_facts = ooconfig.calc_missing_facts() + self.assertEquals(2, len(missing_host_facts)) + self.assertEquals(1, len(missing_host_facts['10.0.0.2'])) + self.assertEquals(3, len(missing_host_facts['10.0.0.3'])) + + def test_write_config(self): + cfg_path = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), SAMPLE_CONFIG) + ooconfig = OOConfig(cfg_path) + ooconfig.save_to_disk() + + f = open(cfg_path, 'r') + written_config = yaml.safe_load(f.read()) + f.close() + + self.assertEquals(3, len(written_config['hosts'])) + for h in written_config['hosts']: + self.assertTrue('ip' in h) + self.assertTrue('public_ip' in h) + self.assertTrue('hostname' in h) + self.assertTrue('public_hostname' in h) + + self.assertTrue('ansible_ssh_user' in written_config) + self.assertTrue('variant' in written_config) + + # Some advanced settings should not get written out if they + # were not specified by the user: + self.assertFalse('ansible_inventory_directory' in written_config) + + +class HostTests(OOInstallFixture): + + def test_load_host_no_ip_or_hostname(self): + yaml_props = { + 'public_ip': '192.168.0.1', + 'public_hostname': 'a.example.com', + 'master': True + } + self.assertRaises(OOConfigInvalidHostError, Host, **yaml_props) + + def test_load_host_no_master_or_node_specified(self): + yaml_props = { + 'ip': '192.168.0.1', + 'hostname': 'a.example.com', + 'public_ip': '192.168.0.1', + 'public_hostname': 'a.example.com', + } + self.assertRaises(OOConfigInvalidHostError, Host, **yaml_props) + + + + -- cgit v1.2.3