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 8d11b1497..7057180a7 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 @@ -973,3 +979,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,