From f335b9e040f0b7f20fba0fda5d6494ce0e97eca7 Mon Sep 17 00:00:00 2001 From: Alex Schultz Date: Fri, 27 Apr 2018 14:43:16 -0600 Subject: [PATCH] Move tests to tripleo deploy After refactoring the deployment command, the tests should be migrated to the tripleo deploy command tests. Additionally some of the utility functions have been moved to tripleoclient.utils. Change-Id: I48b77a4c91412a0fba9a78bc5fe5f81d8962ac18 Related-Blueprint: all-in-one --- .../tests/v1/tripleo/test_tripleo_deploy.py | 373 +++++++++++++- .../v1/undercloud/test_undercloud_deploy.py | 467 +----------------- tripleoclient/utils.py | 72 +++ tripleoclient/v1/tripleo_deploy.py | 50 +- 4 files changed, 454 insertions(+), 508 deletions(-) diff --git a/tripleoclient/tests/v1/tripleo/test_tripleo_deploy.py b/tripleoclient/tests/v1/tripleo/test_tripleo_deploy.py index 245049684..9326d41dc 100644 --- a/tripleoclient/tests/v1/tripleo/test_tripleo_deploy.py +++ b/tripleoclient/tests/v1/tripleo/test_tripleo_deploy.py @@ -14,7 +14,11 @@ # import mock +import os +import tempfile +import yaml +from heatclient import exc as hc_exc from tripleo_common.image import kolla_builder from tripleoclient import exceptions @@ -55,9 +59,372 @@ class TestDeployUndercloud(TestPluginV1): self.orc.stacks.create = mock.MagicMock( return_value={'stack': {'id': 'foo'}}) - def test_take_action_standalone(self): - """This is currently handled by undercloud_deploy tests""" - pass + @mock.patch('os.chmod') + @mock.patch('os.path.exists') + @mock.patch('tripleo_common.utils.passwords.generate_passwords') + @mock.patch('yaml.safe_dump') + def test_update_passwords_env_init(self, mock_dump, mock_pw, + mock_exists, mock_chmod): + pw_dict = {"GeneratedPassword": 123} + pw_conf_path = os.path.join(self.temp_homedir, + 'undercloud-passwords.conf') + t_pw_conf_path = os.path.join( + self.temp_homedir, 'tripleo-undercloud-passwords.yaml') + + mock_pw.return_value = pw_dict + mock_exists.return_value = False + + mock_open_context = mock.mock_open() + with mock.patch('six.moves.builtins.open', mock_open_context): + self.cmd._update_passwords_env(self.temp_homedir) + + mock_open_handle = mock_open_context() + mock_dump.assert_called_once_with({'parameter_defaults': pw_dict}, + mock_open_handle, + default_flow_style=False) + chmod_calls = [mock.call(t_pw_conf_path, 0o600), + mock.call(pw_conf_path, 0o600)] + mock_chmod.assert_has_calls(chmod_calls) + + @mock.patch('os.chmod') + @mock.patch('os.path.exists') + @mock.patch('tripleo_common.utils.passwords.generate_passwords') + @mock.patch('yaml.safe_dump') + def test_update_passwords_env_update(self, mock_dump, mock_pw, + mock_exists, mock_chmod): + pw_dict = {"GeneratedPassword": 123} + pw_conf_path = os.path.join(self.temp_homedir, + 'undercloud-passwords.conf') + t_pw_conf_path = os.path.join( + self.temp_homedir, 'tripleo-undercloud-passwords.yaml') + + mock_pw.return_value = pw_dict + mock_exists.return_value = True + with open(t_pw_conf_path, 'w') as t_pw: + t_pw.write('parameter_defaults: {ExistingKey: xyz}\n') + + with open(pw_conf_path, 'w') as t_pw: + t_pw.write('[auth]\nundercloud_db_password = abc\n') + + self.cmd._update_passwords_env(self.temp_homedir, + passwords={'ADefault': 456, + 'ExistingKey': + 'dontupdate'}) + expected_dict = {'parameter_defaults': {'GeneratedPassword': 123, + 'ExistingKey': 'xyz', + 'MysqlRootPassword': 'abc', + 'ADefault': 456}} + mock_dump.assert_called_once_with(expected_dict, + mock.ANY, + default_flow_style=False) + chmod_calls = [mock.call(t_pw_conf_path, 0o600), + mock.call(pw_conf_path, 0o600)] + mock_chmod.assert_has_calls(chmod_calls) + + @mock.patch('heatclient.common.template_utils.' + 'process_environment_and_files', return_value=({}, {}), + autospec=True) + @mock.patch('heatclient.common.template_utils.' + 'get_template_contents', return_value=({}, {}), + autospec=True) + @mock.patch('heatclient.common.environment_format.' + 'parse', autospec=True, return_value=dict()) + @mock.patch('heatclient.common.template_format.' + 'parse', autospec=True, return_value=dict()) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_setup_heat_environments', autospec=True) + @mock.patch('tripleo_common.image.kolla_builder.' + 'container_images_prepare_multi') + def test_deploy_tripleo_heat_templates_redir(self, + mock_cipm, + mock_setup_heat_envs, + mock_hc_templ_parse, + mock_hc_env_parse, + mock_hc_get_templ_cont, + mock_hc_process): + + with tempfile.NamedTemporaryFile(delete=False) as roles_file: + self.addCleanup(os.unlink, roles_file.name) + + mock_cipm.return_value = {} + + parsed_args = self.check_parser(self.cmd, + ['--local-ip', '127.0.0.1/8', + '--templates', '/tmp/thtroot', + '--roles-file', roles_file.name], []) + + mock_setup_heat_envs.return_value = [ + './inside.yaml', '/tmp/thtroot/abs.yaml', + '/tmp/thtroot/puppet/foo.yaml', + '/tmp/thtroot/environments/myenv.yaml', + '/tmp/thtroot42/notouch.yaml', + '../outside.yaml'] + + self.cmd._deploy_tripleo_heat_templates(self.orc, parsed_args) + + mock_hc_process.assert_has_calls([ + mock.call(env_path='./inside.yaml'), + mock.call(env_path='/twd/templates/abs.yaml'), + mock.call(env_path='/twd/templates/puppet/foo.yaml'), + mock.call(env_path='/twd/templates/environments/myenv.yaml'), + mock.call(env_path='/tmp/thtroot42/notouch.yaml'), + mock.call(env_path='../outside.yaml')]) + + @mock.patch('heatclient.common.template_utils.' + 'process_environment_and_files', return_value=({}, {}), + autospec=True) + @mock.patch('heatclient.common.template_utils.' + 'get_template_contents', return_value=({}, {}), + autospec=True) + @mock.patch('heatclient.common.environment_format.' + 'parse', autospec=True, return_value=dict()) + @mock.patch('heatclient.common.template_format.' + 'parse', autospec=True, return_value=dict()) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_setup_heat_environments', autospec=True) + @mock.patch('yaml.safe_dump', autospec=True) + @mock.patch('yaml.safe_load', autospec=True) + @mock.patch('six.moves.builtins.open') + @mock.patch('tempfile.NamedTemporaryFile', autospec=True) + @mock.patch('tripleo_common.image.kolla_builder.' + 'container_images_prepare_multi') + def test_deploy_tripleo_heat_templates_rewrite(self, + mock_cipm, + mock_temp, mock_open, + mock_yaml_load, + mock_yaml_dump, + mock_setup_heat_envs, + mock_hc_templ_parse, + mock_hc_env_parse, + mock_hc_get_templ_cont, + mock_hc_process): + def hc_process(*args, **kwargs): + if 'abs.yaml' in kwargs['env_path']: + raise hc_exc.CommandError + else: + return ({}, {}) + + mock_cipm.return_value = {} + + mock_hc_process.side_effect = hc_process + + parsed_args = self.check_parser(self.cmd, + ['--local-ip', '127.0.0.1/8', + '--templates', '/tmp/thtroot'], []) + + rewritten_env = {'resource_registry': { + 'OS::Foo::Bar': '/twd/outside.yaml', + 'OS::Foo::Baz': '/twd/templates/inside.yaml', + 'OS::Foo::Qux': '/twd/templates/abs.yaml', + 'OS::Foo::Quux': '/tmp/thtroot42/notouch.yaml', + 'OS::Foo::Corge': '/twd/templates/puppet/foo.yaml' + } + } + myenv = {'resource_registry': { + 'OS::Foo::Bar': '../outside.yaml', + 'OS::Foo::Baz': './inside.yaml', + 'OS::Foo::Qux': '/tmp/thtroot/abs.yaml', + 'OS::Foo::Quux': '/tmp/thtroot42/notouch.yaml', + 'OS::Foo::Corge': '/tmp/thtroot/puppet/foo.yaml' + } + } + mock_yaml_load.return_value = myenv + + mock_setup_heat_envs.return_value = [ + './inside.yaml', '/tmp/thtroot/abs.yaml', + '/tmp/thtroot/puppet/foo.yaml', + '/tmp/thtroot/environments/myenv.yaml', + '../outside.yaml'] + + self.cmd._deploy_tripleo_heat_templates(self.orc, parsed_args) + + mock_yaml_dump.assert_has_calls([mock.call(rewritten_env, + default_flow_style=False)]) + + @mock.patch('heatclient.common.template_utils.' + 'process_environment_and_files', return_value=({}, {}), + autospec=True) + @mock.patch('heatclient.common.template_utils.' + 'get_template_contents', return_value=({}, {}), + autospec=True) + @mock.patch('tripleoclient.utils.' + 'process_multiple_environments', autospec=True) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_process_hieradata_overrides', return_value='foo.yaml', + autospec=True) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_update_passwords_env', autospec=True) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_run_and_log_output', autospec=True) + @mock.patch('tempfile.mkdtemp', autospec=True, return_value='/twd') + @mock.patch('shutil.copytree', autospec=True) + def test_setup_heat_environments(self, + mock_copy, + mock_mktemp, + mock_run, + mock_update_pass_env, + mock_process_hiera, + mock_process_multiple_environments, + mock_hc_get_templ_cont, + mock_hc_process): + + mock_run.return_value = 0 + + parsed_args = self.check_parser(self.cmd, + ['--local-ip', '127.0.0.1/8', + '--templates', '/tmp/thtroot', + '--output-dir', '/my', + '--hieradata-override', + 'legacy.yaml', + '-e', + '/tmp/thtroot/puppet/foo.yaml', + '-e', + '/tmp/thtroot//docker/bar.yaml', + '-e', + '/tmp/thtroot42/notouch.yaml', + '-e', '~/custom.yaml', + '-e', 'something.yaml', + '-e', '../../../outside.yaml'], []) + expected_env = [ + '/twd/templates/overcloud-resource-registry-puppet.yaml', + mock.ANY, + '/twd/templates/environments/undercloud.yaml', + '/twd/templates/environments/config-download-environment.yaml', + '/twd/templates/environments/deployed-server-noop-ctlplane.yaml', + '/tmp/thtroot/puppet/foo.yaml', + '/tmp/thtroot//docker/bar.yaml', + '/tmp/thtroot42/notouch.yaml', + '~/custom.yaml', + 'something.yaml', + '../../../outside.yaml', + mock.ANY, 'foo.yaml'] + + environment = self.cmd._setup_heat_environments(parsed_args) + + self.assertEqual(environment, expected_env) + + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_create_working_dirs', autospec=True) + @mock.patch('tripleoclient.v1.tripleo_deploy.TripleoInventory', + autospec=True) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_launch_heat', autospec=True) + @mock.patch('tripleo_common.utils.config.Config', + autospec=True) + @mock.patch('tripleoclient.v1.tripleo_deploy.sys.stdout.flush') + @mock.patch('os.path.join', return_value='/twd/inventory.yaml') + def test_download_ansible_playbooks(self, mock_join, mock_flush, + mock_stack_config, mock_launch_heat, + mock_importInv, createdir_mock): + + fake_output_dir = '/twd' + extra_vars = {'Undercloud': {'ansible_connection': 'local'}} + mock_inventory = mock.Mock() + mock_importInv.return_value = mock_inventory + self.cmd.output_dir = fake_output_dir + self.cmd._download_ansible_playbooks(mock_launch_heat, + 'undercloud') + self.assertEqual(mock_flush.call_count, 2) + mock_inventory.write_static_inventory.assert_called_once_with( + fake_output_dir + '/inventory.yaml', extra_vars) + + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_run_and_log_output', autospec=True) + @mock.patch('os.chdir') + @mock.patch('os.execvp') + def test_launch_ansible(self, mock_execvp, mock_chdir, mock_run): + + self.cmd._launch_ansible('/tmp') + mock_chdir.assert_called_once() + mock_run.assert_called_once_with(self.cmd, [ + 'ansible-playbook', '-i', '/tmp/inventory.yaml', + 'deploy_steps_playbook.yaml', '-e', 'role_name=Undercloud', + '-e', 'deploy_server_id=undercloud', '-e', + 'bootstrap_server_id=undercloud']) + + @mock.patch('tripleo_common.image.kolla_builder.' + 'container_images_prepare_multi') + def test_prepare_container_images(self, mock_cipm): + env = {'parameter_defaults': {}} + mock_cipm.return_value = {'FooImage': 'foo/bar:baz'} + + with tempfile.NamedTemporaryFile(mode='w') as roles_file: + yaml.dump([{'name': 'Compute'}], roles_file) + self.cmd._prepare_container_images(env, roles_file.name) + + mock_cipm.assert_called_once_with( + env, + [{'name': 'Compute'}] + ) + self.assertEqual( + { + 'parameter_defaults': { + 'FooImage': 'foo/bar:baz' + } + }, + env + ) + + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_create_install_artifact', return_value='/tmp/foo.tar.bzip2') + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_launch_ansible') + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_cleanup_working_dirs') + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_create_working_dirs') + @mock.patch('tripleoclient.utils.wait_api_port_ready', + autospec=True) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_deploy_tripleo_heat_templates', autospec=True, + return_value='undercloud, 0') + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_download_ansible_playbooks', autospec=True, + return_value='/foo') + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_launch_heat') + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_kill_heat') + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_configure_puppet') + @mock.patch('os.geteuid', return_value=0) + @mock.patch('os.environ', return_value='CREATE_COMPLETE') + @mock.patch('tripleoclient.v1.tripleo_deploy.' + 'event_utils.poll_for_events', + return_value=('CREATE_COMPLETE', 0)) + def test_take_action_standalone(self, mock_poll, mock_environ, + mock_geteuid, mock_puppet, mock_killheat, + mock_launchheat, mock_download, mock_tht, + mock_wait_for_port, mock_createdirs, + mock_cleanupdirs, mock_launchansible, + mock_tarball): + + parsed_args = self.check_parser(self.cmd, + ['--local-ip', '127.0.0.1', + '--templates', '/tmp/thtroot', + '--stack', 'undercloud', + '--output-dir', '/my', + '-e', '/tmp/thtroot/puppet/foo.yaml', + '-e', '/tmp/thtroot//docker/bar.yaml', + '-e', '/tmp/thtroot42/notouch.yaml', + '-e', '~/custom.yaml', + '-e', 'something.yaml', + '-e', '../../../outside.yaml', + '--standalone'], []) + + fake_orchestration = mock_launchheat(parsed_args) + self.cmd.take_action(parsed_args) + mock_createdirs.assert_called_once() + mock_puppet.assert_called_once() + mock_launchheat.assert_called_with(parsed_args) + mock_tht.assert_called_once_with(self.cmd, fake_orchestration, + parsed_args) + mock_download.assert_called_with(self.cmd, fake_orchestration, + 'undercloud') + mock_launchansible.assert_called_once() + mock_tarball.assert_called_once() + mock_cleanupdirs.assert_called_once() + self.assertEqual(mock_killheat.call_count, 2) def test_take_action(self): parsed_args = self.check_parser(self.cmd, diff --git a/tripleoclient/tests/v1/undercloud/test_undercloud_deploy.py b/tripleoclient/tests/v1/undercloud/test_undercloud_deploy.py index 059232a6f..c1965eed2 100644 --- a/tripleoclient/tests/v1/undercloud/test_undercloud_deploy.py +++ b/tripleoclient/tests/v1/undercloud/test_undercloud_deploy.py @@ -14,31 +14,14 @@ # import mock -import os -import tempfile -import yaml -from heatclient import exc as hc_exc -from tripleo_common.image import kolla_builder - -from tripleoclient import exceptions -from tripleoclient.tests.v1.test_plugin import TestPluginV1 +from osc_lib.tests import utils # Load the plugin init module for the plugin list and show commands from tripleoclient.v1 import undercloud_deploy -# TODO(sbaker) Remove after a tripleo-common release contains this new function -if not hasattr(kolla_builder, 'container_images_prepare_multi'): - setattr(kolla_builder, 'container_images_prepare_multi', mock.Mock()) - -class FakePluginV1Client(object): - def __init__(self, **kwargs): - self.auth_token = kwargs['token'] - self.management_url = kwargs['endpoint'] - - -class TestDeployUndercloud(TestPluginV1): +class TestDeployUndercloud(utils.TestCommand): def setUp(self): super(TestDeployUndercloud, self).setUp() @@ -46,331 +29,9 @@ class TestDeployUndercloud(TestPluginV1): # Get the command object to test self.cmd = undercloud_deploy.DeployUndercloud(self.app, None) - undercloud_deploy.DeployUndercloud.heat_pid = mock.MagicMock( - return_value=False) - undercloud_deploy.DeployUndercloud.tht_render = '/twd/templates' - undercloud_deploy.DeployUndercloud.tmp_env_dir = '/twd' - undercloud_deploy.DeployUndercloud.tmp_env_file_name = 'tmp/foo' - undercloud_deploy.DeployUndercloud.heat_launch = mock.MagicMock( - side_effect=(lambda *x, **y: None)) - - self.tc = self.app.client_manager.tripleoclient = mock.MagicMock() - self.orc = self.tc.local_orchestration = mock.MagicMock() - self.orc.stacks.create = mock.MagicMock( - return_value={'stack': {'id': 'foo'}}) - - @mock.patch('os.chmod') - @mock.patch('os.path.exists') - @mock.patch('tripleo_common.utils.passwords.generate_passwords') - @mock.patch('yaml.safe_dump') - def test_update_passwords_env_init(self, mock_dump, mock_pw, - mock_exists, mock_chmod): - pw_dict = {"GeneratedPassword": 123} - pw_conf_path = os.path.join(self.temp_homedir, - 'undercloud-passwords.conf') - t_pw_conf_path = os.path.join( - self.temp_homedir, 'tripleo-undercloud-passwords.yaml') - - mock_pw.return_value = pw_dict - mock_exists.return_value = False - - mock_open_context = mock.mock_open() - with mock.patch('six.moves.builtins.open', mock_open_context): - self.cmd._update_passwords_env(self.temp_homedir) - - mock_open_handle = mock_open_context() - mock_dump.assert_called_once_with({'parameter_defaults': pw_dict}, - mock_open_handle, - default_flow_style=False) - chmod_calls = [mock.call(t_pw_conf_path, 0o600), - mock.call(pw_conf_path, 0o600)] - mock_chmod.assert_has_calls(chmod_calls) - - @mock.patch('os.chmod') - @mock.patch('os.path.exists') - @mock.patch('tripleo_common.utils.passwords.generate_passwords') - @mock.patch('yaml.safe_dump') - def test_update_passwords_env_update(self, mock_dump, mock_pw, - mock_exists, mock_chmod): - pw_dict = {"GeneratedPassword": 123} - pw_conf_path = os.path.join(self.temp_homedir, - 'undercloud-passwords.conf') - t_pw_conf_path = os.path.join( - self.temp_homedir, 'tripleo-undercloud-passwords.yaml') - - mock_pw.return_value = pw_dict - mock_exists.return_value = True - with open(t_pw_conf_path, 'w') as t_pw: - t_pw.write('parameter_defaults: {ExistingKey: xyz}\n') - - with open(pw_conf_path, 'w') as t_pw: - t_pw.write('[auth]\nundercloud_db_password = abc\n') - - self.cmd._update_passwords_env(self.temp_homedir, - passwords={'ADefault': 456, - 'ExistingKey': - 'dontupdate'}) - expected_dict = {'parameter_defaults': {'GeneratedPassword': 123, - 'ExistingKey': 'xyz', - 'MysqlRootPassword': 'abc', - 'ADefault': 456}} - mock_dump.assert_called_once_with(expected_dict, - mock.ANY, - default_flow_style=False) - chmod_calls = [mock.call(t_pw_conf_path, 0o600), - mock.call(pw_conf_path, 0o600)] - mock_chmod.assert_has_calls(chmod_calls) - - @mock.patch('heatclient.common.template_utils.' - 'process_environment_and_files', return_value=({}, {}), + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.take_action', autospec=True) - @mock.patch('heatclient.common.template_utils.' - 'get_template_contents', return_value=({}, {}), - autospec=True) - @mock.patch('heatclient.common.environment_format.' - 'parse', autospec=True, return_value=dict()) - @mock.patch('heatclient.common.template_format.' - 'parse', autospec=True, return_value=dict()) - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_setup_heat_environments', autospec=True) - @mock.patch('tripleo_common.image.kolla_builder.' - 'container_images_prepare_multi') - def test_deploy_tripleo_heat_templates_redir(self, - mock_cipm, - mock_setup_heat_envs, - mock_hc_templ_parse, - mock_hc_env_parse, - mock_hc_get_templ_cont, - mock_hc_process): - - with tempfile.NamedTemporaryFile(delete=False) as roles_file: - self.addCleanup(os.unlink, roles_file.name) - - mock_cipm.return_value = {} - - parsed_args = self.check_parser(self.cmd, - ['--local-ip', '127.0.0.1/8', - '--templates', '/tmp/thtroot', - '--roles-file', roles_file.name], []) - - mock_setup_heat_envs.return_value = [ - './inside.yaml', '/tmp/thtroot/abs.yaml', - '/tmp/thtroot/puppet/foo.yaml', - '/tmp/thtroot/environments/myenv.yaml', - '/tmp/thtroot42/notouch.yaml', - '../outside.yaml'] - - self.cmd._deploy_tripleo_heat_templates(self.orc, parsed_args) - - mock_hc_process.assert_has_calls([ - mock.call(env_path='./inside.yaml'), - mock.call(env_path='/twd/templates/abs.yaml'), - mock.call(env_path='/twd/templates/puppet/foo.yaml'), - mock.call(env_path='/twd/templates/environments/myenv.yaml'), - mock.call(env_path='/tmp/thtroot42/notouch.yaml'), - mock.call(env_path='../outside.yaml')]) - - @mock.patch('heatclient.common.template_utils.' - 'process_environment_and_files', return_value=({}, {}), - autospec=True) - @mock.patch('heatclient.common.template_utils.' - 'get_template_contents', return_value=({}, {}), - autospec=True) - @mock.patch('heatclient.common.environment_format.' - 'parse', autospec=True, return_value=dict()) - @mock.patch('heatclient.common.template_format.' - 'parse', autospec=True, return_value=dict()) - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_setup_heat_environments', autospec=True) - @mock.patch('yaml.safe_dump', autospec=True) - @mock.patch('yaml.safe_load', autospec=True) - @mock.patch('six.moves.builtins.open') - @mock.patch('tempfile.NamedTemporaryFile', autospec=True) - @mock.patch('tripleo_common.image.kolla_builder.' - 'container_images_prepare_multi') - def test_deploy_tripleo_heat_templates_rewrite(self, - mock_cipm, - mock_temp, mock_open, - mock_yaml_load, - mock_yaml_dump, - mock_setup_heat_envs, - mock_hc_templ_parse, - mock_hc_env_parse, - mock_hc_get_templ_cont, - mock_hc_process): - def hc_process(*args, **kwargs): - if 'abs.yaml' in kwargs['env_path']: - raise hc_exc.CommandError - else: - return ({}, {}) - - mock_cipm.return_value = {} - - mock_hc_process.side_effect = hc_process - - parsed_args = self.check_parser(self.cmd, - ['--local-ip', '127.0.0.1/8', - '--templates', '/tmp/thtroot'], []) - - rewritten_env = {'resource_registry': { - 'OS::Foo::Bar': '/twd/outside.yaml', - 'OS::Foo::Baz': '/twd/templates/inside.yaml', - 'OS::Foo::Qux': '/twd/templates/abs.yaml', - 'OS::Foo::Quux': '/tmp/thtroot42/notouch.yaml', - 'OS::Foo::Corge': '/twd/templates/puppet/foo.yaml' - } - } - myenv = {'resource_registry': { - 'OS::Foo::Bar': '../outside.yaml', - 'OS::Foo::Baz': './inside.yaml', - 'OS::Foo::Qux': '/tmp/thtroot/abs.yaml', - 'OS::Foo::Quux': '/tmp/thtroot42/notouch.yaml', - 'OS::Foo::Corge': '/tmp/thtroot/puppet/foo.yaml' - } - } - mock_yaml_load.return_value = myenv - - mock_setup_heat_envs.return_value = [ - './inside.yaml', '/tmp/thtroot/abs.yaml', - '/tmp/thtroot/puppet/foo.yaml', - '/tmp/thtroot/environments/myenv.yaml', - '../outside.yaml'] - - self.cmd._deploy_tripleo_heat_templates(self.orc, parsed_args) - - mock_yaml_dump.assert_has_calls([mock.call(rewritten_env, - default_flow_style=False)]) - - @mock.patch('heatclient.common.template_utils.' - 'process_environment_and_files', return_value=({}, {}), - autospec=True) - @mock.patch('heatclient.common.template_utils.' - 'get_template_contents', return_value=({}, {}), - autospec=True) - @mock.patch('tripleoclient.utils.' - 'process_multiple_environments', autospec=True) - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_process_hieradata_overrides', return_value='foo.yaml', - autospec=True) - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_update_passwords_env', autospec=True) - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_run_and_log_output', autospec=True) - @mock.patch('tempfile.mkdtemp', autospec=True, return_value='/twd') - @mock.patch('shutil.copytree', autospec=True) - def test_setup_heat_environments(self, - mock_copy, - mock_mktemp, - mock_run, - mock_update_pass_env, - mock_process_hiera, - mock_process_multiple_environments, - mock_hc_get_templ_cont, - mock_hc_process): - - mock_run.return_value = 0 - - parsed_args = self.check_parser(self.cmd, - ['--local-ip', '127.0.0.1/8', - '--templates', '/tmp/thtroot', - '--output-dir', '/my', - '--hieradata-override', 'legacy.yaml', - '-e', '/tmp/thtroot/puppet/foo.yaml', - '-e', '/tmp/thtroot//docker/bar.yaml', - '-e', '/tmp/thtroot42/notouch.yaml', - '-e', '~/custom.yaml', - '-e', 'something.yaml', - '-e', '../../../outside.yaml'], []) - expected_env = [ - '/twd/templates/overcloud-resource-registry-puppet.yaml', - mock.ANY, - '/twd/templates/environments/undercloud.yaml', - '/twd/templates/environments/config-download-environment.yaml', - '/twd/templates/environments/deployed-server-noop-ctlplane.yaml', - '/tmp/thtroot/puppet/foo.yaml', - '/tmp/thtroot//docker/bar.yaml', - '/tmp/thtroot42/notouch.yaml', - '~/custom.yaml', - 'something.yaml', - '../../../outside.yaml', - mock.ANY, 'foo.yaml'] - - environment = self.cmd._setup_heat_environments(parsed_args) - - self.assertEqual(environment, expected_env) - - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_create_working_dirs', autospec=True) - @mock.patch('tripleoclient.v1.tripleo_deploy.TripleoInventory', - autospec=True) - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_launch_heat', autospec=True) - @mock.patch('tripleo_common.utils.config.Config', - autospec=True) - @mock.patch('tripleoclient.v1.tripleo_deploy.sys.stdout.flush') - @mock.patch('os.path.join', return_value='/twd/inventory.yaml') - def test_download_ansible_playbooks(self, mock_join, mock_flush, - mock_stack_config, mock_launch_heat, - mock_importInv, createdir_mock): - - fake_output_dir = '/twd' - extra_vars = {'Undercloud': {'ansible_connection': 'local'}} - mock_inventory = mock.Mock() - mock_importInv.return_value = mock_inventory - self.cmd.output_dir = fake_output_dir - self.cmd._download_ansible_playbooks(mock_launch_heat, - 'undercloud') - self.assertEqual(mock_flush.call_count, 2) - mock_inventory.write_static_inventory.assert_called_once_with( - fake_output_dir + '/inventory.yaml', extra_vars) - - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_run_and_log_output', autospec=True) - @mock.patch('os.chdir') - @mock.patch('os.execvp') - def test_launch_ansible(self, mock_execvp, mock_chdir, mock_run): - - self.cmd._launch_ansible('/tmp') - mock_chdir.assert_called_once() - mock_run.assert_called_once_with(self.cmd, [ - 'ansible-playbook', '-i', '/tmp/inventory.yaml', - 'deploy_steps_playbook.yaml', '-e', 'role_name=Undercloud', - '-e', 'deploy_server_id=undercloud', '-e', - 'bootstrap_server_id=undercloud']) - - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_create_install_artifact', return_value='/tmp/foo.tar.bzip2') - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_launch_ansible') - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_cleanup_working_dirs') - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_create_working_dirs') - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_wait_local_port_ready', autospec=True) - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_deploy_tripleo_heat_templates', autospec=True, - return_value='undercloud, 0') - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_download_ansible_playbooks', autospec=True, - return_value='/foo') - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_launch_heat') - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_kill_heat') - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_configure_puppet') - @mock.patch('os.geteuid', return_value=0) - @mock.patch('os.environ', return_value='CREATE_COMPLETE') - @mock.patch('tripleoclient.v1.tripleo_deploy.' - 'event_utils.poll_for_events', - return_value=('CREATE_COMPLETE', 0)) - def test_take_action(self, mock_poll, mock_environ, mock_geteuid, - mock_puppet, mock_killheat, mock_launchheat, - mock_download, mock_tht, mock_wait_for_port, - mock_createdirs, mock_cleanupdirs, - mock_launchansible, mock_tarball): - + def test_take_action(self, mock_deploy): parsed_args = self.check_parser(self.cmd, ['--local-ip', '127.0.0.1', '--templates', '/tmp/thtroot', @@ -382,122 +43,6 @@ class TestDeployUndercloud(TestPluginV1): '-e', '~/custom.yaml', '-e', 'something.yaml', '-e', '../../../outside.yaml'], []) - - fake_orchestration = mock_launchheat(parsed_args) self.cmd.take_action(parsed_args) - mock_createdirs.assert_called_once() - mock_puppet.assert_called_once() - mock_launchheat.assert_called_with(parsed_args) - mock_tht.assert_called_once_with(self.cmd, fake_orchestration, - parsed_args) - mock_download.assert_called_with(self.cmd, fake_orchestration, - 'undercloud') - mock_launchansible.assert_called_once() - mock_tarball.assert_called_once() - mock_cleanupdirs.assert_called_once() - self.assertEqual(mock_killheat.call_count, 2) - - @mock.patch('tripleo_common.image.kolla_builder.' - 'container_images_prepare_multi') - def test_prepare_container_images(self, mock_cipm): - env = {'parameter_defaults': {}} - mock_cipm.return_value = {'FooImage': 'foo/bar:baz'} - - with tempfile.NamedTemporaryFile(mode='w') as roles_file: - yaml.dump([{'name': 'Compute'}], roles_file) - self.cmd._prepare_container_images(env, roles_file.name) - - mock_cipm.assert_called_once_with( - env, - [{'name': 'Compute'}] - ) - self.assertEqual( - { - 'parameter_defaults': { - 'FooImage': 'foo/bar:baz' - } - }, - env - ) - - @mock.patch('tempfile.NamedTemporaryFile', autospec=True) - @mock.patch('os.path.exists', return_value=True) - def test_process_hierdata_overrides(self, mock_exists, mock_tmpfile): - data = "foo: bar" - mock_open = mock.mock_open(read_data=data) - with mock.patch('tripleoclient.v1.tripleo_deploy.open', mock_open): - self.assertEqual(mock_tmpfile.return_value.__enter__().name, - self.cmd._process_hieradata_overrides('/foobar')) - - @mock.patch('os.path.exists', return_value=False) - def test_process_hierdata_overrides_bad_input(self, mock_exists): - self.assertRaises(exceptions.DeploymentError, - self.cmd._process_hieradata_overrides, - ['/tmp/foobar']) - - @mock.patch('tempfile.NamedTemporaryFile', autospec=True) - @mock.patch('os.path.exists', return_value=True) - def test_process_hierdata_overrides_tht(self, mock_exists, mock_tmpfile): - data = "parameter_defaults:\n UndercloudExtraConfig:\n foo: bar" - mock_open = mock.mock_open(read_data=data) - with mock.patch('tripleoclient.v1.tripleo_deploy.open', mock_open): - self.assertEqual('/foobar', - self.cmd._process_hieradata_overrides('/foobar')) - - @mock.patch('os.waitpid') - def test_kill_heat(self, wait_mock): - self.cmd.heat_pid = 1234 - wait_mock.return_value = [0, 0] - - parsed_args = self.check_parser(self.cmd, [], []) - self.cmd._kill_heat(parsed_args) - wait_mock.assert_called_once_with(1234, 0) - - @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' - '_get_tar_filename', - return_value='/tmp/undercloud-install-1.tar.bzip2') - @mock.patch('tarfile.open', autospec=True) - def test_create_install_artifact(self, mock_open, mock_filename): - self.cmd.output_dir = '/tmp' - self.cmd.tmp_env_dir = '/tmp/foo' - self.cmd.tmp_env_file_name = '/tmp/bar' - self.cmd.tmp_ansible_dir = '/tmp/baz' - name = self.cmd._create_install_artifact() - self.cmd.output_dir = None - self.cmd.tmp_env_dir = None - self.cmd.tmp_env_file_name = None - self.cmd.tmp_ansible_dir = None - self.assertEqual(name, '/tmp/undercloud-install-1.tar.bzip2') - - @mock.patch('tempfile.mkdtemp') - def test_create_working_dirs(self, mock_tempfile): - self.output_dir = None - self.cmd.tmp_ansible_dir = None - self.cmd.tmp_env_dir = None - self.cmd._create_working_dirs() - self.assertEqual(mock_tempfile.call_count, 2) - - @mock.patch('os.remove') - @mock.patch('shutil.rmtree') - @mock.patch('os.path.exists', return_value=True) - def test_cleanup_working_dirs(self, mock_exists, mock_rmtree, mock_remove): - self.cmd.tmp_env_dir = '/foo' - self.cmd.tmp_env_file_name = '/bar' - self.cmd.tmp_ansible_dir = '/baz' - self.cmd._cleanup_working_dirs() - self.assertEqual(mock_exists.call_count, 0) - mock_remove.assert_not_called() - self.assertEqual(mock_rmtree.call_count, 0) - - @mock.patch('os.remove') - @mock.patch('shutil.rmtree') - @mock.patch('os.path.exists', return_value=True) - def test_cleanup_working_dirs_cleanup(self, mock_exists, mock_rmtree, - mock_remove): - self.cmd.tmp_env_dir = '/foo' - self.cmd.tmp_env_file_name = '/bar' - self.cmd.tmp_ansible_dir = '/baz' - self.cmd._cleanup_working_dirs(True) - self.assertEqual(mock_exists.call_count, 2) - mock_remove.assert_called_once_with('/bar') - self.assertEqual(mock_rmtree.call_count, 2) + parsed_args.standlone = True + mock_deploy.assert_called_with(self.cmd, parsed_args) diff --git a/tripleoclient/utils.py b/tripleoclient/utils.py index fe852b7a2..7bc14d8a0 100644 --- a/tripleoclient/utils.py +++ b/tripleoclient/utils.py @@ -20,6 +20,8 @@ import getpass import glob import hashlib import logging +import shutil + import os import os.path import simplejson @@ -39,6 +41,10 @@ from oslo_concurrency import processutils from six.moves import configparser from heatclient import exc as hc_exc +from six.moves.urllib import error as url_error +from six.moves.urllib import request + + from tripleoclient import exceptions @@ -961,3 +967,69 @@ def load_container_registry(log, path): "to re-run this command and provide the registry file " "with: --container-registry-file option.") return registry + + +def get_short_hostname(): + """Returns the local short hostname + + :return string + """ + p = subprocess.Popen(["hostname", "-s"], stdout=subprocess.PIPE) + return p.communicate()[0].rstrip() + + +def wait_api_port_ready(api_port, host='127.0.0.1'): + """Wait until an http services becomes available + + :param api_port: api service port + :type api_port: integer + + :param host: host running the service (default: 127.0.0.1) + :type host: string + + :return boolean + """ + count = 0 + while count < 30: + time.sleep(1) + count += 1 + try: + request.urlopen("http://%s:%s/" % (host, api_port), timeout=1) + except url_error.HTTPError as he: + if he.code == 300: + return True + pass + except url_error.URLError: + pass + return False + + +def bulk_symlink(log, src, dst, tmpd='/tmp'): + """Create bulk symlinks from a directory + + :param log: logger instance for logging + :type log: Logger + + :param src: dir of directories to symlink + :type src: string + + :param dst: dir to create the symlinks + :type dst: string + + :param tmpd: temporary working directory to use + :type tmp: string + """ + log.debug("Symlinking %s to %s, via temp dir %s" % + (src, dst, tmpd)) + try: + tmp = tempfile.mkdtemp(dir=tmpd) + subprocess.check_call(['mkdir', '-p', dst]) + os.chmod(tmp, 0o755) + for obj in os.listdir(src): + tmpf = os.path.join(tmp, obj) + os.symlink(os.path.join(src, obj), tmpf) + os.rename(tmpf, os.path.join(dst, obj)) + except Exception: + raise + finally: + shutil.rmtree(tmp, ignore_errors=True) diff --git a/tripleoclient/v1/tripleo_deploy.py b/tripleoclient/v1/tripleo_deploy.py index d8f081bba..bb0ff0e3f 100644 --- a/tripleoclient/v1/tripleo_deploy.py +++ b/tripleoclient/v1/tripleo_deploy.py @@ -26,7 +26,6 @@ import subprocess import sys import tarfile import tempfile -import time import traceback import yaml @@ -36,8 +35,6 @@ from heatclient.common import event_utils from heatclient.common import template_utils from openstackclient.i18n import _ from six.moves import configparser -from six.moves.urllib import error as url_error -from six.moves.urllib import request from tripleoclient import constants from tripleoclient import exceptions @@ -90,22 +87,6 @@ class Deploy(command.Command): tmp_env_file_name = None tmp_ansible_dir = None - def _symlink(self, src, dst, tmpd='/tmp'): - self.log.debug("Symlinking %s to %s, via temp dir %s" % - (src, dst, tmpd)) - try: - tmp = tempfile.mkdtemp(dir=tmpd) - subprocess.check_call(['mkdir', '-p', dst]) - os.chmod(tmp, 0o755) - for obj in os.listdir(src): - tmpf = os.path.join(tmp, obj) - os.symlink(os.path.join(src, obj), tmpf) - os.rename(tmpf, os.path.join(dst, obj)) - except Exception: - raise - finally: - shutil.rmtree(tmp, ignore_errors=True) - def _get_tar_filename(self): """Return tarball name for the install artifacts""" return '%s/undercloud-install-%s.tar.bzip2' % \ @@ -179,30 +160,11 @@ class Deploy(command.Command): self.log.warning("Not cleaning ansible directory %s" % self.tmp_ansible_dir) - def _get_hostname(self): - p = subprocess.Popen(["hostname", "-s"], stdout=subprocess.PIPE) - return p.communicate()[0].rstrip() - def _configure_puppet(self): self.log.info('Configuring puppet modules symlinks ...') - self._symlink(constants.TRIPLEO_PUPPET_MODULES, - constants.PUPPET_MODULES, - constants.PUPPET_BASE) - - def _wait_local_port_ready(self, api_port): - count = 0 - while count < 30: - time.sleep(1) - count += 1 - try: - request.urlopen("http://127.0.0.1:%s/" % api_port, timeout=1) - except url_error.HTTPError as he: - if he.code == 300: - return True - pass - except url_error.URLError: - pass - return False + utils.bulk_symlink(self.log, constants.TRIPLEO_PUPPET_MODULES, + constants.PUPPET_MODULES, + constants.PUPPET_BASE) def _run_and_log_output(self, cmd, cwd=None): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, @@ -280,7 +242,7 @@ class Deploy(command.Command): return pw_file def _generate_hosts_parameters(self, parsed_args, p_ip): - hostname = self._get_hostname() + hostname = utils.get_short_hostname() domain = parsed_args.local_domain data = { @@ -296,7 +258,7 @@ class Deploy(command.Command): def _generate_portmap_parameters(self, ip_addr, cidr_prefixlen, ctlplane_vip_addr, public_vip_addr): - hostname = self._get_hostname() + hostname = utils.get_short_hostname() data = { 'ControlPlaneSubnetCidr': '%s' % cidr_prefixlen, @@ -767,7 +729,7 @@ class Deploy(command.Command): # Launch heat. orchestration_client = self._launch_heat(parsed_args) # Wait for heat to be ready. - self._wait_local_port_ready(parsed_args.heat_api_port) + utils.wait_api_port_ready(parsed_args.heat_api_port) # Deploy TripleO Heat templates. stack_id = \ self._deploy_tripleo_heat_templates(orchestration_client,