From e8f53ae778921f42714881d90d18ef28608bc354 Mon Sep 17 00:00:00 2001 From: James Slagle Date: Tue, 20 Apr 2021 09:37:05 -0400 Subject: [PATCH] Support using ephemeral Heat This patch begins to add support for using the ephemeral Heat process to the overcloud deploy command. The functionality is enabled with the newly added --heat-type cli arg, which can be used to select the heat type to be used by the deployment playbooks and modules. The options are: installed: current behavior using Heat installed on the undercloud pod: ephemeral Heat podman pod (heat-api/engine) container: ephemeral Heat podman container (heat-all) native: ephemeral Heat native heat-all process The default is "installed" initially to preserve the existing functionality. Signed-off-by: James Slagle Implements: blueprint ephemeral-heat-overcloud Change-Id: I8fb6ca088b1052488ff4f9ada4d3ab29c0be4735 --- ...dd-heat-type-cli-arg-2fa4f47a835aafea.yaml | 7 + tripleoclient/constants.py | 1 + tripleoclient/heat_launcher.py | 12 +- .../overcloud_deploy/test_overcloud_deploy.py | 104 +++++--- tripleoclient/utils.py | 51 +--- tripleoclient/v1/overcloud_deploy.py | 241 ++++++++++++------ tripleoclient/v1/tripleo_deploy.py | 3 +- tripleoclient/workflows/deployment.py | 91 ++++--- 8 files changed, 319 insertions(+), 191 deletions(-) create mode 100644 releasenotes/notes/add-heat-type-cli-arg-2fa4f47a835aafea.yaml diff --git a/releasenotes/notes/add-heat-type-cli-arg-2fa4f47a835aafea.yaml b/releasenotes/notes/add-heat-type-cli-arg-2fa4f47a835aafea.yaml new file mode 100644 index 000000000..274a79b47 --- /dev/null +++ b/releasenotes/notes/add-heat-type-cli-arg-2fa4f47a835aafea.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + A new cli argument, --heat-type is added to openstack overcloud deploy. + Available options are "installed", "pod", "container", and "native". The + default is "installed". The argument specifies the type of Heat process to + use for the deployment. diff --git a/tripleoclient/constants.py b/tripleoclient/constants.py index 2f309e5ff..ac6ceb136 100644 --- a/tripleoclient/constants.py +++ b/tripleoclient/constants.py @@ -54,6 +54,7 @@ DEFAULT_HEAT_ENGINE_CONTAINER = ('{}/{}/openstack-heat-engine:{}'.format( USER_PARAMETERS = 'user-environments/tripleoclient-parameters.yaml' +PASSWORDS_ENV_FORMAT = '{}-passwords.yaml' # This directory may contain additional environments to use during deploy DEFAULT_ENV_DIRECTORY = os.path.join(os.environ.get('HOME', '~/'), diff --git a/tripleoclient/heat_launcher.py b/tripleoclient/heat_launcher.py index ded3e7df6..eb59d8fa3 100644 --- a/tripleoclient/heat_launcher.py +++ b/tripleoclient/heat_launcher.py @@ -125,6 +125,7 @@ class HeatBaseLauncher(object): user='heat', heat_dir='/var/log/heat-launcher', use_tmp_dir=True, + use_root=False, rm_heat=False, skip_heat_pull=False): self.api_port = api_port @@ -197,11 +198,12 @@ class HeatBaseLauncher(object): self._write_fake_keystone_token(self.api_port, self.token_file) self._write_heat_config() self._write_api_paste_config() - uid = int(self.get_heat_uid()) - gid = int(self.get_heat_gid()) - os.chown(self.install_dir, uid, gid) - os.chown(self.config_file, uid, gid) - os.chown(self.paste_file, uid, gid) + if use_root: + uid = int(self.get_heat_uid()) + gid = int(self.get_heat_gid()) + os.chown(self.install_dir, uid, gid) + os.chown(self.config_file, uid, gid) + os.chown(self.paste_file, uid, gid) def _write_heat_config(self): # TODO(ksambor) It will be nice to have possibilities to configure heat diff --git a/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py b/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py index 43c9b2076..8df6a8b6c 100644 --- a/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py +++ b/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py @@ -259,10 +259,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): '_validate_args') @mock.patch('heatclient.common.template_utils.get_template_contents', autospec=True) + @mock.patch('os.chmod', autospec=True) @mock.patch('os.chdir', autospec=True) @mock.patch('tempfile.mkdtemp', autospec=True) @mock.patch('tripleoclient.utils.makedirs') - def test_tht_deploy(self, mock_md, mock_tmpdir, mock_cd, + def test_tht_deploy(self, mock_md, mock_tmpdir, mock_cd, mock_chmod, mock_get_template_contents, mock_validate_args, mock_breakpoints_cleanup, mock_postconfig, mock_invoke_plan_env_wf, @@ -505,8 +506,10 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_copy.assert_called_once() @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates', autospec=True) - def test_jinja2_env_path(self, mock_deploy_tht): + 'create_params_and_env_files', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'deploy_tripleo_heat_templates', autospec=True) + def test_jinja2_env_path(self, mock_deploy_tht, mock_create_env): arglist = ['--templates', '-e', 'bad_path.j2.yaml', '-e', 'other.yaml', '-e', 'bad_path2.j2.yaml'] @@ -533,9 +536,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): '_deploy_postconfig', autospec=True) @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' '_update_parameters', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'create_params_and_env_files', autospec=True) @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' '_heat_deploy', autospec=True) - def test_environment_dirs(self, mock_deploy_heat, + def test_environment_dirs(self, mock_deploy_heat, mock_create_env, mock_update_parameters, mock_post_config, mock_stack_network_check, mock_ceph_fsid, mock_copy, mock_nic_ansible, @@ -593,6 +598,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_rc_params.return_value = {'password': 'password', 'region': 'region1'} mock_deploy_heat.side_effect = _fake_heat_deploy + mock_create_env.return_value = ({}, []) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) mock_copy.assert_called_once() @@ -700,8 +706,10 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): '_get_undercloud_host_entry', autospec=True, return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates', autospec=True) - def test_dry_run(self, mock_create_tempest_deployer_input, + 'create_params_and_env_files', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'deploy_tripleo_heat_templates', autospec=True) + def test_dry_run(self, mock_deploy, mock_create_env, mock_get_undercloud_host_entry): utils_fixture = deployment.UtilsOvercloudFixture() self.useFixture(utils_fixture) @@ -715,10 +723,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): ('dry_run', True), ] + mock_create_env.return_value = ({}, []) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(utils_fixture.mock_deploy_tht.called) - self.assertFalse(mock_create_tempest_deployer_input.called) + self.assertFalse(mock_deploy.called) @mock.patch('tripleoclient.utils.check_service_vips_migrated_to_service') @mock.patch('tripleoclient.utils.get_rc_params', autospec=True) @@ -1017,8 +1026,10 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): '_get_undercloud_host_entry', autospec=True, return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates_tmpdir', autospec=True) - def test_deployed_server(self, mock_deploy_tmpdir, + 'create_params_and_env_files', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'deploy_tripleo_heat_templates', autospec=True) + def test_deployed_server(self, mock_deploy, mock_create_env, mock_get_undercloud_host_entry, mock_copy, mock_rc_params): fixture = deployment.DeploymentWorkflowFixture() @@ -1039,9 +1050,10 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): clients.compute = mock.Mock() orchestration_client = clients.orchestration orchestration_client.stacks.get.return_value = fakes.create_tht_stack() + mock_create_env.return_value = ({}, []) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.assertTrue(mock_deploy_tmpdir.called) + self.assertTrue(mock_deploy.called) self.assertNotCalled(clients.baremetal) self.assertNotCalled(clients.compute) self.assertTrue(utils_fixture.mock_deploy_tht.called) @@ -1053,9 +1065,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): '_get_undercloud_host_entry', autospec=True, return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates_tmpdir', autospec=True) + 'create_params_and_env_files', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'deploy_tripleo_heat_templates', autospec=True) def test_config_download( - self, mock_deploy_tmpdir, + self, mock_deploy, mock_create_env, mock_get_undercloud_host_entry, mock_copy, mock_rc_params): fixture = deployment.DeploymentWorkflowFixture() @@ -1074,8 +1088,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): parsed_args = self.check_parser(self.cmd, arglist, verifylist) mock_rc_params.return_value = {'password': 'password', 'region': 'region1'} + mock_create_env.return_value = ({}, []) self.cmd.take_action(parsed_args) - self.assertTrue(mock_deploy_tmpdir.called) + self.assertTrue(mock_deploy.called) self.assertTrue(fixture.mock_get_hosts_and_enable_ssh_admin.called) self.assertTrue(fixture.mock_config_download.called) self.assertTrue(fixture.mock_set_deployment_status.called) @@ -1091,9 +1106,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): '_get_undercloud_host_entry', autospec=True, return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates_tmpdir', autospec=True) + 'create_params_and_env_files', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'deploy_tripleo_heat_templates', autospec=True) def test_config_download_setup_only( - self, mock_deploy_tmpdir, + self, mock_deploy, mock_create_env, mock_get_undercloud_host_entry, mock_copy, mock_rc_params): fixture = deployment.DeploymentWorkflowFixture() @@ -1103,6 +1120,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): clients = self.app.client_manager orchestration_client = clients.orchestration orchestration_client.stacks.get.return_value = fakes.create_tht_stack() + mock_create_env.return_value = ({}, []) arglist = ['--templates', '--config-download', '--setup-only'] verifylist = [ @@ -1114,7 +1132,6 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_rc_params.return_value = {'password': 'password', 'region': 'region1'} self.cmd.take_action(parsed_args) - self.assertTrue(mock_deploy_tmpdir.called) self.assertTrue(fixture.mock_get_hosts_and_enable_ssh_admin.called) self.assertTrue(fixture.mock_config_download.called) self.assertTrue(fixture.mock_set_deployment_status.called) @@ -1124,17 +1141,20 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): ) mock_copy.assert_called_once() + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'create_params_and_env_files', autospec=True) @mock.patch('tripleoclient.utils.get_rc_params', autospec=True) @mock.patch('tripleoclient.utils.copy_clouds_yaml') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' '_get_undercloud_host_entry', autospec=True, return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates_tmpdir', autospec=True) + 'deploy_tripleo_heat_templates', autospec=True) def test_config_download_only( - self, mock_deploy_tmpdir, + self, mock_deploy, mock_get_undercloud_host_entry, - mock_copy, mock_rc_params): + mock_copy, mock_rc_params, + mock_create_parameters_env): fixture = deployment.DeploymentWorkflowFixture() self.useFixture(fixture) utils_fixture = deployment.UtilsOvercloudFixture() @@ -1142,6 +1162,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): clients = self.app.client_manager orchestration_client = clients.orchestration orchestration_client.stacks.get.return_value = fakes.create_tht_stack() + mock_create_parameters_env.return_value = (mock.Mock(), mock.Mock()) arglist = ['--templates', '--config-download-only'] verifylist = [ @@ -1153,7 +1174,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): 'region': 'region1'} self.cmd.take_action(parsed_args) - self.assertFalse(mock_deploy_tmpdir.called) + self.assertFalse(mock_deploy.called) self.assertFalse(fixture.mock_get_hosts_and_enable_ssh_admin.called) self.assertTrue(fixture.mock_config_download.called) self.assertTrue(fixture.mock_set_deployment_status.called) @@ -1162,6 +1183,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): fixture.mock_set_deployment_status.call_args[-1]['status']) mock_copy.assert_called_once() + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'create_params_and_env_files', autospec=True) @mock.patch('tripleoclient.utils.get_rc_params', autospec=True) @mock.patch('tripleoclient.utils.copy_clouds_yaml') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' @@ -1171,18 +1194,20 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): autospec=True) @mock.patch('tripleoclient.utils.get_overcloud_endpoint', autospec=True) @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates_tmpdir', autospec=True) + 'deploy_tripleo_heat_templates', autospec=True) def test_config_download_fails( - self, mock_deploy_tmpdir, + self, mock_deploy, mock_overcloud_endpoint, mock_create_tempest_deployer_input, mock_get_undercloud_host_entry, - mock_copy, mock_rc_params): + mock_copy, mock_rc_params, + mock_create_parameters_env): fixture = deployment.DeploymentWorkflowFixture() self.useFixture(fixture) clients = self.app.client_manager orchestration_client = clients.orchestration orchestration_client.stacks.get.return_value = fakes.create_tht_stack() + mock_create_parameters_env.return_value = (mock.Mock(), mock.Mock()) arglist = ['--templates', '--config-download-only'] verifylist = [ @@ -1199,7 +1224,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): exceptions.DeploymentError, self.cmd.take_action, parsed_args) - self.assertFalse(mock_deploy_tmpdir.called) + self.assertFalse(mock_deploy.called) self.assertTrue(fixture.mock_config_download.called) self.assertTrue(fixture.mock_set_deployment_status.called) self.assertEqual( @@ -1212,9 +1237,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): '_get_undercloud_host_entry', autospec=True, return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates_tmpdir', autospec=True) + 'create_params_and_env_files', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'deploy_tripleo_heat_templates', autospec=True) def test_override_ansible_cfg( - self, mock_deploy_tmpdir, + self, mock_deploy, mock_create_env, mock_get_undercloud_host_entry, mock_copy, mock_rc_params): fixture = deployment.DeploymentWorkflowFixture() @@ -1235,11 +1262,14 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_rc_params.return_value = {'password': 'password', 'region': 'region1'} + mock_create_env.return_value = ({}, []) self.cmd.take_action(parsed_args) self.assertTrue(fixture.mock_get_hosts_and_enable_ssh_admin.called) self.assertTrue(fixture.mock_config_download.called) mock_copy.assert_called_once() + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'create_params_and_env_files', autospec=True) @mock.patch('tripleoclient.utils.check_service_vips_migrated_to_service') @mock.patch('tripleo_common.utils.plan.default_image_params', autospec=True) @@ -1273,7 +1303,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_process_env, mock_roles_data, mock_container_prepare, mock_generate_password, mock_rc_params, mock_default_image_params, - mock_check_service_vip_migr): + mock_check_service_vip_migr, + mock_create_parameters_env): fixture = deployment.DeploymentWorkflowFixture() self.useFixture(fixture) utils_fixture = deployment.UtilsOvercloudFixture() @@ -1281,6 +1312,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): clients = self.app.client_manager orchestration_client = clients.orchestration orchestration_client.stacks.get.return_value = fakes.create_tht_stack() + mock_create_parameters_env.return_value = ({}, []) arglist = ['--templates', '--overcloud-ssh-port-timeout', '42', '--timeout', '451'] @@ -1294,18 +1326,13 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_rc_params.return_value = {'password': 'password', 'region': 'region1'} # assuming heat deploy consumed a 3m out of total 451m timeout - with mock.patch('time.time', side_effect=[0, 1585820346, - 0, 12345678, 0, + with mock.patch('time.time', side_effect=[1585820346, + 12345678, 0, 0, 1585820526]): self.cmd.take_action(parsed_args) self.assertIn( [mock.call(mock.ANY, mock.ANY, 'overcloud', mock.ANY, - {'StackAction': 'UPDATE', - 'DeployIdentifier': 12345678, - 'RootStackName': 'overcloud', - 'UndercloudHostsEntries': - ['192.168.0.1 uc.ctlplane.localhost uc.ctlplane'], - 'CtlplaneNetworkAttributes': {}}, mock.ANY, + {'StackAction': 'UPDATE'}, mock.ANY, 451, mock.ANY, mock.ANY, False, None, deployment_options={}, env_files_tracker=mock.ANY)], mock_hd.mock_calls) @@ -1333,9 +1360,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): '_get_undercloud_host_entry', autospec=True, return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' - '_deploy_tripleo_heat_templates_tmpdir', autospec=True) + 'create_params_and_env_files', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + 'deploy_tripleo_heat_templates', autospec=True) def test_config_download_only_timeout( - self, mock_deploy_tmpdir, + self, mock_deploy, mock_create_env, mock_get_undercloud_host_entry, mock_update, mock_copyi, mock_rc_params, mock_cd_dir): utils_fixture = deployment.UtilsOvercloudFixture() @@ -1362,6 +1391,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_rc_params.return_value = {'password': 'password', 'region': 'region1'} + mock_create_env.return_value = ({}, []) self.cmd.take_action(parsed_args) playbook = os.path.join(os.environ.get( 'HOME'), self.cmd.working_dir, diff --git a/tripleoclient/utils.py b/tripleoclient/utils.py index 937d8add3..0c0702f79 100644 --- a/tripleoclient/utils.py +++ b/tripleoclient/utils.py @@ -52,7 +52,6 @@ from heatclient.common import template_utils from heatclient.common import utils as heat_utils from heatclient.exc import HTTPNotFound from osc_lib import exceptions as oscexc -from osc_lib import utils as osc_lib_utils from osc_lib.i18n import _ from oslo_concurrency import processutils from six.moves import configparser @@ -71,6 +70,14 @@ from tripleoclient import constants from tripleoclient import exceptions from tripleoclient import heat_launcher +try: + # TODO(slagle): the try/except can be removed once tripleo_common is + # released with + # https://review.opendev.org/c/openstack/tripleo-common/+/787819 + from tripleo_common.utils import heat as tc_heat_utils +except ImportError: + tc_heat_utils = None + LOG = logging.getLogger(__name__ + ".utils") _local_orchestration_client = None @@ -2566,6 +2573,11 @@ def write_user_environment(env_map, abs_env_path, tht_root, def launch_heat(launcher=None, restore_db=False): + if not tc_heat_utils: + msg = "tripleo-common too old to use ephemeral Heat" + LOG.error(msg) + raise Exception(msg) + global _local_orchestration_client global _heat_pid @@ -2589,7 +2601,7 @@ def launch_heat(launcher=None, restore_db=False): heat_api_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) test_heat_api_port(heat_api_socket, launcher.host, int(launcher.api_port)) - _local_orchestration_client = local_orchestration_client( + _local_orchestration_client = tc_heat_utils.local_orchestration_client( launcher.host, launcher.api_port) return _local_orchestration_client @@ -2609,41 +2621,6 @@ def get_heat_launcher(heat_type, *args, **kwargs): return heat_launcher.HeatPodLauncher(*args, **kwargs) -def local_orchestration_client(host="127.0.0.1", api_port=8006): - """Returns a local orchestration service client""" - - API_VERSIONS = { - '1': 'heatclient.v1.client.Client', - } - - heat_client = osc_lib_utils.get_client_class( - 'tripleoclient', - '1', - API_VERSIONS) - LOG.debug('Instantiating local_orchestration client for ' - 'host %s, port %s: %s', - host, api_port, heat_client) - - endpoint = 'http://%s:%s/v1/admin' % (host, api_port) - client = heat_client( - endpoint=endpoint, - username='admin', - password='fake', - region_name='regionOne', - token='fake', - ) - - for v in ('OS_USER_DOMAIN_NAME', - 'OS_PROJECT_DOMAIN_NAME', - 'OS_PROJECT_NAME'): - os.environ.pop(v, None) - - os.environ['OS_AUTH_TYPE'] = "none" - os.environ['OS_ENDPOINT'] = endpoint - - return client - - def kill_heat(launcher, backup_db=True): global _heat_pid if _heat_pid: diff --git a/tripleoclient/v1/overcloud_deploy.py b/tripleoclient/v1/overcloud_deploy.py index a316bc8b4..8e9d454a7 100644 --- a/tripleoclient/v1/overcloud_deploy.py +++ b/tripleoclient/v1/overcloud_deploy.py @@ -77,59 +77,44 @@ class DeployOvercloud(command.Command): answers['environments'].extend(args.environment_files) args.environment_files = answers['environments'] - def _update_parameters(self, args, stack, tht_root, user_tht_root): - parameters = {} - - stack_is_new = stack is None - + def _update_parameters(self, args, parameters, + tht_root, user_tht_root): parameters['RootStackName'] = args.stack if not args.skip_deploy_identifier: parameters['DeployIdentifier'] = int(time.time()) else: parameters['DeployIdentifier'] = '' - parameters['StackAction'] = 'CREATE' if stack_is_new else 'UPDATE' + if args.heat_type != 'installed': + heat = None + else: + heat = self.orchestration_client - # We need the processed env for the image parameters atm - env_files = [] - env_files.append( - os.path.join(tht_root, constants.DEFAULT_RESOURCE_REGISTRY)) - if args.environment_directories: - env_files.extend(utils.load_environment_directories( - args.environment_directories)) - if args.environment_files: - env_files.extend(args.environment_files) - - _, env = utils.process_multiple_environments( - env_files, tht_root, user_tht_root, - cleanup=(not args.no_cleanup)) - - default_image_params = plan_utils.default_image_params() - image_params = {} - if not args.disable_container_prepare: - image_params = kolla_builder.container_images_prepare_multi( - env, roles.get_roles_data(args.roles_file, - tht_root), dry_run=True) - if image_params: - default_image_params.update(image_params) - parameters.update(default_image_params) + # Check for existing passwords file + password_params_path = os.path.join( + self.working_dir, + constants.PASSWORDS_ENV_FORMAT.format(args.stack)) + if os.path.exists(password_params_path): + with open(password_params_path, 'r') as f: + passwords_env = yaml.safe_load(f.read()) + else: + passwords_env = None password_params = plan_utils.generate_passwords( - None, self.orchestration_client, - args.stack) + None, heat, args.stack, passwords_env=passwords_env) + + # Save generated passwords file + with open(password_params_path, 'w') as f: + f.write(yaml.safe_dump(dict(parameter_defaults=password_params))) + os.chmod(password_params_path, 0o600) parameters.update(password_params) param_args = ( ('NtpServer', 'ntp_server'), + ('NovaComputeLibvirtType', 'libvirt_type'), ) - if stack_is_new: - new_stack_args = ( - ('NovaComputeLibvirtType', 'libvirt_type'), - ) - param_args = param_args + new_stack_args - # Update parameters from commandline for param, arg in param_args: if getattr(args, arg, None) is not None: @@ -245,7 +230,7 @@ class DeployOvercloud(command.Command): template, files, env_files_tracker, self.log, self.working_dir) - def _deploy_tripleo_heat_templates_tmpdir(self, stack, parsed_args): + def create_template_dirs(self, parsed_args): tht_root = os.path.abspath(parsed_args.templates) new_tht_root = "%s/tripleo-heat-templates" % self.working_dir self.log.debug("Creating working templates tree in %s" @@ -257,38 +242,61 @@ class DeployOvercloud(command.Command): parsed_args.roles_file, parsed_args.networks_file, new_tht_root) - self._deploy_tripleo_heat_templates(stack, parsed_args, - new_tht_root, tht_root) - - def _deploy_tripleo_heat_templates(self, stack, parsed_args, - tht_root, user_tht_root): - """Deploy the fixed templates in TripleO Heat Templates""" - - self.log.info("Processing templates in the directory {0}".format( - os.path.abspath(tht_root))) + return new_tht_root, tht_root + def create_params_and_env_files(self, new_tht_root, user_tht_root, + parsed_args): self.log.debug("Creating Environment files") created_env_files = [] - created_env_files.append( - os.path.join(tht_root, constants.DEFAULT_RESOURCE_REGISTRY)) - created_env_files.extend( - self._provision_baremetal(parsed_args, tht_root)) - + os.path.join(new_tht_root, constants.DEFAULT_RESOURCE_REGISTRY)) if parsed_args.environment_directories: created_env_files.extend(utils.load_environment_directories( parsed_args.environment_directories)) + created_env_files.extend( + self._provision_baremetal(parsed_args, new_tht_root)) + + _, env = utils.process_multiple_environments( + created_env_files, new_tht_root, user_tht_root, + cleanup=(not parsed_args.no_cleanup)) + + default_image_params = plan_utils.default_image_params() + image_params = {} + if not parsed_args.disable_container_prepare: + image_params = kolla_builder.container_images_prepare_multi( + env, roles.get_roles_data(parsed_args.roles_file, + new_tht_root), dry_run=True) parameters = {} - parameters.update(self._update_parameters( - parsed_args, stack, tht_root, user_tht_root)) + if image_params: + default_image_params.update(image_params) + parameters.update(default_image_params) + + self._update_parameters( + parsed_args, parameters, new_tht_root, user_tht_root) + + return parameters, created_env_files + + def deploy_tripleo_heat_templates(self, stack, parsed_args, + new_tht_root, user_tht_root, + parameters, created_env_files): + """Deploy the fixed templates in TripleO Heat Templates""" + + self.log.info("Processing templates in the directory {0}".format( + os.path.abspath(new_tht_root))) + + stack_is_new = stack is None + parameters['StackAction'] = 'CREATE' if stack_is_new else 'UPDATE' + param_env = utils.create_parameters_env( - parameters, tht_root, parsed_args.stack) + parameters, new_tht_root, parsed_args.stack) created_env_files.extend(param_env) if parsed_args.deployed_server: created_env_files.append( - os.path.join(tht_root, constants.DEPLOYED_SERVER_ENVIRONMENT)) + os.path.join( + new_tht_root, + constants.DEPLOYED_SERVER_ENVIRONMENT)) if parsed_args.environment_files: created_env_files.extend(parsed_args.environment_files) @@ -300,22 +308,22 @@ class DeployOvercloud(command.Command): if stack: env_path = utils.create_breakpoint_cleanup_env( - tht_root, parsed_args.stack) + new_tht_root, parsed_args.stack) created_env_files.extend(env_path) self.log.debug("Processing environment files %s" % created_env_files) env_files_tracker = [] env_files, env = utils.process_multiple_environments( - created_env_files, tht_root, user_tht_root, + created_env_files, new_tht_root, user_tht_root, env_files_tracker=env_files_tracker, cleanup=(not parsed_args.no_cleanup)) # Invokes the workflows specified in plan environment file if parsed_args.plan_environment_file: output_path = utils.build_user_env_path( - 'derived_parameters.yaml', tht_root) + 'derived_parameters.yaml', new_tht_root) workflow_params.build_derived_params_environment( - self.clients, parsed_args.stack, tht_root, env_files, + self.clients, parsed_args.stack, new_tht_root, env_files, env_files_tracker, parsed_args.roles_file, parsed_args.plan_environment_file, output_path, utils.playbook_verbosity(self=self)) @@ -323,12 +331,12 @@ class DeployOvercloud(command.Command): created_env_files.append(output_path) env_files_tracker = [] env_files, env = utils.process_multiple_environments( - created_env_files, tht_root, user_tht_root, + created_env_files, new_tht_root, user_tht_root, env_files_tracker=env_files_tracker, cleanup=(not parsed_args.no_cleanup)) # Copy the env_files to tmp folder for archiving - self._copy_env_files(env_files, tht_root) + self._copy_env_files(env_files, new_tht_root) if parsed_args.limit: # check if skip list is defined while using --limit and throw a @@ -365,7 +373,7 @@ class DeployOvercloud(command.Command): '(with HA).') self._try_overcloud_deploy_with_compat_yaml( - tht_root, stack, + new_tht_root, stack, parsed_args.stack, parameters, env_files, parsed_args.timeout, env, parsed_args.run_validations, @@ -911,6 +919,35 @@ class DeployOvercloud(command.Command): 'input, output, and generated files will be stored.\n' 'Defaults to "$HOME/overcloud-deploy/"') ) + parser.add_argument( + '--heat-type', + action='store', + default='installed', + choices=['system', 'pod', 'container', 'native'], + help=_('The type of Heat process to use to execute ' + 'the deployment.\n' + 'installed (Default): Use the system installed ' + 'Heat.\n' + 'pod: Use an ephemeral Heat pod.\n' + 'container: Use an ephemeral Heat container.\n' + 'native: Use an ephemeral Heat process.') + ) + parser.add_argument( + '--rm-heat', + action='store_true', + default=False, + help=_('If specified and --heat-type is container or pod ' + 'any existing container or pod of a previous ' + 'ephemeral Heat process will be deleted first. ' + 'Ignored if --heat-type is native.') + ) + parser.add_argument( + '--skip-heat-pull', + action='store_true', + default=False, + help=_('When --heat-type is pod or container, assume ' + 'the container image has already been pulled ') + ) return parser def take_action(self, parsed_args): @@ -950,21 +987,64 @@ class DeployOvercloud(command.Command): parsed_args.environment_files) self._update_args_from_answers_file(parsed_args) - stack = utils.get_stack(self.orchestration_client, parsed_args.stack) - stack_create = stack is None - if stack_create: - self.log.info("No stack found, will be doing a stack create") - else: - self.log.info("Stack found, will be doing a stack update") if parsed_args.dry_run: self.log.info("Validation Finished") return + self.heat_launcher = None + stack = None + stack_create = None start = time.time() - if not parsed_args.config_download_only: - self._deploy_tripleo_heat_templates_tmpdir(stack, parsed_args) + new_tht_root, user_tht_root = \ + self.create_template_dirs(parsed_args) + parameters, created_env_files = \ + self.create_params_and_env_files( + new_tht_root, user_tht_root, parsed_args) + + if parsed_args.heat_type != 'installed': + self.log.info("Using tripleo-deploy with " + "ephemeral heat-all for stack operation") + api_container_image = parameters['ContainerHeatApiImage'] + engine_container_image = \ + parameters['ContainerHeatEngineImage'] + self.heat_launcher = utils.get_heat_launcher( + parsed_args.heat_type, + api_container_image=api_container_image, + engine_container_image=engine_container_image, + heat_dir=os.path.join(self.working_dir, + 'heat-launcher'), + use_tmp_dir=False, + rm_heat=parsed_args.rm_heat, + skip_heat_pull=parsed_args.skip_heat_pull) + self.orchestration_client = \ + utils.launch_heat(self.heat_launcher) + self.clients.orchestration = self.orchestration_client + + try: + if parsed_args.heat_type == 'installed': + stack = utils.get_stack(self.orchestration_client, + parsed_args.stack) + + stack_create = stack is None + if stack_create: + self.log.info("No stack found, " + "will be doing a stack create") + else: + self.log.info("Stack found, " + "will be doing a stack update") + if not (parsed_args.config_download_only or + parsed_args.setup_only): + self.deploy_tripleo_heat_templates( + stack, parsed_args, new_tht_root, user_tht_root, + parameters, created_env_files) + except Exception: + if parsed_args.heat_type != 'installed' and self.heat_launcher: + self.log.info("Stopping ephemeral heat.") + utils.kill_heat(self.heat_launcher) + utils.rm_heat(self.heat_launcher) + raise # Get a new copy of the stack after stack update/create. If it was # a create then the previous stack object would be None. @@ -980,7 +1060,8 @@ class DeployOvercloud(command.Command): stack.get() overcloud_endpoint = utils.get_overcloud_endpoint(stack) horizon_url = deployment.get_horizon_url( - stack=stack.stack_name) + stack=stack.stack_name, + heat_type=parsed_args.heat_type) rc_params = utils.get_rc_params( self.orchestration_client, parsed_args.stack) @@ -993,6 +1074,9 @@ class DeployOvercloud(command.Command): stack, rc_params, parsed_args.no_proxy, self.working_dir) + config_download_dir = parsed_args.output_dir or \ + os.path.join(self.working_dir, "config-download") + if parsed_args.config_download or parsed_args.setup_only: self.log.info("Deploying overcloud configuration") deployment.set_deployment_status( @@ -1009,7 +1093,8 @@ class DeployOvercloud(command.Command): parsed_args.overcloud_ssh_user, self.get_key_pair(parsed_args), parsed_args.overcloud_ssh_port_timeout, - verbosity=utils.playbook_verbosity(self=self) + verbosity=utils.playbook_verbosity(self=self), + heat_type=parsed_args.heat_type ) if parsed_args.config_download_timeout: @@ -1026,8 +1111,6 @@ class DeployOvercloud(command.Command): deployment_options['ansible_python_interpreter'] = \ parsed_args.deployment_python_interpreter - config_download_dir = parsed_args.output_dir or \ - os.path.join(self.working_dir, "config-download") deployment.make_config_download_dir(config_download_dir, parsed_args.stack) @@ -1082,7 +1165,8 @@ class DeployOvercloud(command.Command): parsed_args.overcloud_ssh_user, self.get_key_pair(parsed_args), parsed_args.overcloud_ssh_port_timeout, - verbosity=utils.playbook_verbosity(self=self) + verbosity=utils.playbook_verbosity(self=self), + heat_type=parsed_args.heat_type ) deployment.set_deployment_status( @@ -1117,6 +1201,11 @@ class DeployOvercloud(command.Command): rcpath, old_rcpath)) print("Overcloud Deployed {0}".format(deploy_message)) + if parsed_args.heat_type != 'installed': + self.log.info("Stopping ephemeral heat.") + utils.kill_heat(self.heat_launcher) + utils.rm_heat(self.heat_launcher) + if parsed_args.output_dir: ansible_dir = config_download_dir else: diff --git a/tripleoclient/v1/tripleo_deploy.py b/tripleoclient/v1/tripleo_deploy.py index 9e2644d7b..7bcb43e81 100644 --- a/tripleoclient/v1/tripleo_deploy.py +++ b/tripleoclient/v1/tripleo_deploy.py @@ -477,7 +477,8 @@ class Deploy(command.Command): self.heat_launch = heat_launcher.HeatNativeLauncher( parsed_args.heat_api_port, parsed_args.heat_container_image, - parsed_args.heat_user) + parsed_args.heat_user, + use_root=True) # NOTE(dprince): we launch heat with fork exec because # we don't want it to inherit our args. Launching heat diff --git a/tripleoclient/workflows/deployment.py b/tripleoclient/workflows/deployment.py index f0ccabca3..2ce575379 100644 --- a/tripleoclient/workflows/deployment.py +++ b/tripleoclient/workflows/deployment.py @@ -28,6 +28,13 @@ from tripleoclient.constants import DEFAULT_WORK_DIR from tripleoclient import exceptions from tripleoclient import utils +try: + # TODO(slagle): the try/except can be removed once tripleo_common is + # released with + # https://review.opendev.org/c/openstack/tripleo-common/+/787819 + from tripleo_common.utils import heat as tc_heat_utils +except ImportError: + tc_heat_utils = None _WORKFLOW_TIMEOUT = 360 # 6 * 60 seconds @@ -136,7 +143,7 @@ def get_overcloud_hosts(stack, ssh_network): def get_hosts_and_enable_ssh_admin(stack, overcloud_ssh_network, overcloud_ssh_user, overcloud_ssh_key, overcloud_ssh_port_timeout, - verbosity=0): + verbosity=0, heat_type='installed'): """Enable ssh admin access. Get a list of hosts from a given stack and enable admin ssh across all of @@ -169,7 +176,8 @@ def get_hosts_and_enable_ssh_admin(stack, overcloud_ssh_network, overcloud_ssh_user, overcloud_ssh_key, overcloud_ssh_port_timeout, - verbosity=verbosity + verbosity=verbosity, + heat_type=heat_type ) else: raise exceptions.DeploymentError( @@ -181,7 +189,7 @@ def get_hosts_and_enable_ssh_admin(stack, overcloud_ssh_network, def enable_ssh_admin(stack, hosts, ssh_user, ssh_key, timeout, - verbosity=0): + verbosity=0, heat_type='installed'): """Run enable ssh admin access playbook. :param stack: Stack data. @@ -213,22 +221,28 @@ def enable_ssh_admin(stack, hosts, ssh_user, ssh_key, timeout, ssh_key ) ) - with utils.TempDirs() as tmp: - utils.run_ansible_playbook( - playbook='cli-enable-ssh-admin.yaml', - inventory=','.join(hosts), - workdir=tmp, - playbook_dir=ANSIBLE_TRIPLEO_PLAYBOOKS, - key=ssh_key, - ssh_user=ssh_user, - verbosity=verbosity, - extra_vars={ - "ssh_user": ssh_user, - "ssh_servers": hosts, - 'tripleo_cloud_name': stack.stack_name - }, - ansible_timeout=timeout - ) + try: + if heat_type != 'installed' and tc_heat_utils.heatclient: + tc_heat_utils.heatclient.save_environment() + with utils.TempDirs() as tmp: + utils.run_ansible_playbook( + playbook='cli-enable-ssh-admin.yaml', + inventory=','.join(hosts), + workdir=tmp, + playbook_dir=ANSIBLE_TRIPLEO_PLAYBOOKS, + key=ssh_key, + ssh_user=ssh_user, + verbosity=verbosity, + extra_vars={ + "ssh_user": ssh_user, + "ssh_servers": hosts, + 'tripleo_cloud_name': stack.stack_name + }, + ansible_timeout=timeout + ) + finally: + if heat_type != 'installed' and tc_heat_utils.heatclient: + tc_heat_utils.heatclient.restore_environment() print("Enabling ssh admin - COMPLETE.") @@ -467,7 +481,8 @@ def config_download(log, clients, stack, ssh_network='ctlplane', repo.git.commit("--amend", "--no-edit") -def get_horizon_url(stack, verbosity=0): +def get_horizon_url(stack, verbosity=0, + heat_type='installed'): """Return horizon URL string. :params stack: Stack name @@ -475,22 +490,28 @@ def get_horizon_url(stack, verbosity=0): :returns: string """ - with utils.TempDirs() as tmp: - horizon_tmp_file = os.path.join(tmp, 'horizon_url') - utils.run_ansible_playbook( - playbook='cli-undercloud-get-horizon-url.yaml', - inventory='localhost,', - workdir=tmp, - playbook_dir=ANSIBLE_TRIPLEO_PLAYBOOKS, - verbosity=verbosity, - extra_vars={ - 'stack_name': stack, - 'horizon_url_output_file': horizon_tmp_file - } - ) + try: + if heat_type != 'installed' and tc_heat_utils.heatclient: + tc_heat_utils.heatclient.save_environment() + with utils.TempDirs() as tmp: + horizon_tmp_file = os.path.join(tmp, 'horizon_url') + utils.run_ansible_playbook( + playbook='cli-undercloud-get-horizon-url.yaml', + inventory='localhost,', + workdir=tmp, + playbook_dir=ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=verbosity, + extra_vars={ + 'stack_name': stack, + 'horizon_url_output_file': horizon_tmp_file + } + ) - with open(horizon_tmp_file) as f: - return f.read().strip() + with open(horizon_tmp_file) as f: + return f.read().strip() + finally: + if heat_type != 'installed' and tc_heat_utils.heatclient: + tc_heat_utils.heatclient.restore_environment() def get_deployment_status(clients, stack_name, working_dir):