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 <jslagle@redhat.com> Implements: blueprint ephemeral-heat-overcloud Change-Id: I8fb6ca088b1052488ff4f9ada4d3ab29c0be4735
This commit is contained in:
parent
7a1c4e2b0b
commit
e8f53ae778
@ -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.
|
@ -54,6 +54,7 @@ DEFAULT_HEAT_ENGINE_CONTAINER = ('{}/{}/openstack-heat-engine:{}'.format(
|
|||||||
|
|
||||||
|
|
||||||
USER_PARAMETERS = 'user-environments/tripleoclient-parameters.yaml'
|
USER_PARAMETERS = 'user-environments/tripleoclient-parameters.yaml'
|
||||||
|
PASSWORDS_ENV_FORMAT = '{}-passwords.yaml'
|
||||||
|
|
||||||
# This directory may contain additional environments to use during deploy
|
# This directory may contain additional environments to use during deploy
|
||||||
DEFAULT_ENV_DIRECTORY = os.path.join(os.environ.get('HOME', '~/'),
|
DEFAULT_ENV_DIRECTORY = os.path.join(os.environ.get('HOME', '~/'),
|
||||||
|
@ -125,6 +125,7 @@ class HeatBaseLauncher(object):
|
|||||||
user='heat',
|
user='heat',
|
||||||
heat_dir='/var/log/heat-launcher',
|
heat_dir='/var/log/heat-launcher',
|
||||||
use_tmp_dir=True,
|
use_tmp_dir=True,
|
||||||
|
use_root=False,
|
||||||
rm_heat=False,
|
rm_heat=False,
|
||||||
skip_heat_pull=False):
|
skip_heat_pull=False):
|
||||||
self.api_port = api_port
|
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_fake_keystone_token(self.api_port, self.token_file)
|
||||||
self._write_heat_config()
|
self._write_heat_config()
|
||||||
self._write_api_paste_config()
|
self._write_api_paste_config()
|
||||||
uid = int(self.get_heat_uid())
|
if use_root:
|
||||||
gid = int(self.get_heat_gid())
|
uid = int(self.get_heat_uid())
|
||||||
os.chown(self.install_dir, uid, gid)
|
gid = int(self.get_heat_gid())
|
||||||
os.chown(self.config_file, uid, gid)
|
os.chown(self.install_dir, uid, gid)
|
||||||
os.chown(self.paste_file, uid, gid)
|
os.chown(self.config_file, uid, gid)
|
||||||
|
os.chown(self.paste_file, uid, gid)
|
||||||
|
|
||||||
def _write_heat_config(self):
|
def _write_heat_config(self):
|
||||||
# TODO(ksambor) It will be nice to have possibilities to configure heat
|
# TODO(ksambor) It will be nice to have possibilities to configure heat
|
||||||
|
@ -259,10 +259,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'_validate_args')
|
'_validate_args')
|
||||||
@mock.patch('heatclient.common.template_utils.get_template_contents',
|
@mock.patch('heatclient.common.template_utils.get_template_contents',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
|
@mock.patch('os.chmod', autospec=True)
|
||||||
@mock.patch('os.chdir', autospec=True)
|
@mock.patch('os.chdir', autospec=True)
|
||||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||||
@mock.patch('tripleoclient.utils.makedirs')
|
@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_get_template_contents, mock_validate_args,
|
||||||
mock_breakpoints_cleanup, mock_postconfig,
|
mock_breakpoints_cleanup, mock_postconfig,
|
||||||
mock_invoke_plan_env_wf,
|
mock_invoke_plan_env_wf,
|
||||||
@ -505,8 +506,10 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
mock_copy.assert_called_once()
|
mock_copy.assert_called_once()
|
||||||
|
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
||||||
'_deploy_tripleo_heat_templates', autospec=True)
|
'create_params_and_env_files', autospec=True)
|
||||||
def test_jinja2_env_path(self, mock_deploy_tht):
|
@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',
|
arglist = ['--templates', '-e', 'bad_path.j2.yaml', '-e', 'other.yaml',
|
||||||
'-e', 'bad_path2.j2.yaml']
|
'-e', 'bad_path2.j2.yaml']
|
||||||
@ -533,9 +536,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'_deploy_postconfig', autospec=True)
|
'_deploy_postconfig', autospec=True)
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
||||||
'_update_parameters', autospec=True)
|
'_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.'
|
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
||||||
'_heat_deploy', autospec=True)
|
'_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_update_parameters, mock_post_config,
|
||||||
mock_stack_network_check, mock_ceph_fsid,
|
mock_stack_network_check, mock_ceph_fsid,
|
||||||
mock_copy, mock_nic_ansible,
|
mock_copy, mock_nic_ansible,
|
||||||
@ -593,6 +598,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
mock_rc_params.return_value = {'password': 'password',
|
mock_rc_params.return_value = {'password': 'password',
|
||||||
'region': 'region1'}
|
'region': 'region1'}
|
||||||
mock_deploy_heat.side_effect = _fake_heat_deploy
|
mock_deploy_heat.side_effect = _fake_heat_deploy
|
||||||
|
mock_create_env.return_value = ({}, [])
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
mock_copy.assert_called_once()
|
mock_copy.assert_called_once()
|
||||||
@ -700,8 +706,10 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'_get_undercloud_host_entry', autospec=True,
|
'_get_undercloud_host_entry', autospec=True,
|
||||||
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
||||||
'_deploy_tripleo_heat_templates', autospec=True)
|
'create_params_and_env_files', autospec=True)
|
||||||
def test_dry_run(self, mock_create_tempest_deployer_input,
|
@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):
|
mock_get_undercloud_host_entry):
|
||||||
utils_fixture = deployment.UtilsOvercloudFixture()
|
utils_fixture = deployment.UtilsOvercloudFixture()
|
||||||
self.useFixture(utils_fixture)
|
self.useFixture(utils_fixture)
|
||||||
@ -715,10 +723,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
('dry_run', True),
|
('dry_run', True),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
mock_create_env.return_value = ({}, [])
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
self.assertFalse(utils_fixture.mock_deploy_tht.called)
|
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.check_service_vips_migrated_to_service')
|
||||||
@mock.patch('tripleoclient.utils.get_rc_params', autospec=True)
|
@mock.patch('tripleoclient.utils.get_rc_params', autospec=True)
|
||||||
@ -1017,8 +1026,10 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'_get_undercloud_host_entry', autospec=True,
|
'_get_undercloud_host_entry', autospec=True,
|
||||||
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
||||||
'_deploy_tripleo_heat_templates_tmpdir', autospec=True)
|
'create_params_and_env_files', autospec=True)
|
||||||
def test_deployed_server(self, mock_deploy_tmpdir,
|
@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_get_undercloud_host_entry,
|
||||||
mock_copy, mock_rc_params):
|
mock_copy, mock_rc_params):
|
||||||
fixture = deployment.DeploymentWorkflowFixture()
|
fixture = deployment.DeploymentWorkflowFixture()
|
||||||
@ -1039,9 +1050,10 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
clients.compute = mock.Mock()
|
clients.compute = mock.Mock()
|
||||||
orchestration_client = clients.orchestration
|
orchestration_client = clients.orchestration
|
||||||
orchestration_client.stacks.get.return_value = fakes.create_tht_stack()
|
orchestration_client.stacks.get.return_value = fakes.create_tht_stack()
|
||||||
|
mock_create_env.return_value = ({}, [])
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
self.assertTrue(mock_deploy_tmpdir.called)
|
self.assertTrue(mock_deploy.called)
|
||||||
self.assertNotCalled(clients.baremetal)
|
self.assertNotCalled(clients.baremetal)
|
||||||
self.assertNotCalled(clients.compute)
|
self.assertNotCalled(clients.compute)
|
||||||
self.assertTrue(utils_fixture.mock_deploy_tht.called)
|
self.assertTrue(utils_fixture.mock_deploy_tht.called)
|
||||||
@ -1053,9 +1065,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'_get_undercloud_host_entry', autospec=True,
|
'_get_undercloud_host_entry', autospec=True,
|
||||||
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@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(
|
def test_config_download(
|
||||||
self, mock_deploy_tmpdir,
|
self, mock_deploy, mock_create_env,
|
||||||
mock_get_undercloud_host_entry,
|
mock_get_undercloud_host_entry,
|
||||||
mock_copy, mock_rc_params):
|
mock_copy, mock_rc_params):
|
||||||
fixture = deployment.DeploymentWorkflowFixture()
|
fixture = deployment.DeploymentWorkflowFixture()
|
||||||
@ -1074,8 +1088,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
mock_rc_params.return_value = {'password': 'password',
|
mock_rc_params.return_value = {'password': 'password',
|
||||||
'region': 'region1'}
|
'region': 'region1'}
|
||||||
|
mock_create_env.return_value = ({}, [])
|
||||||
self.cmd.take_action(parsed_args)
|
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_get_hosts_and_enable_ssh_admin.called)
|
||||||
self.assertTrue(fixture.mock_config_download.called)
|
self.assertTrue(fixture.mock_config_download.called)
|
||||||
self.assertTrue(fixture.mock_set_deployment_status.called)
|
self.assertTrue(fixture.mock_set_deployment_status.called)
|
||||||
@ -1091,9 +1106,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'_get_undercloud_host_entry', autospec=True,
|
'_get_undercloud_host_entry', autospec=True,
|
||||||
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@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(
|
def test_config_download_setup_only(
|
||||||
self, mock_deploy_tmpdir,
|
self, mock_deploy, mock_create_env,
|
||||||
mock_get_undercloud_host_entry,
|
mock_get_undercloud_host_entry,
|
||||||
mock_copy, mock_rc_params):
|
mock_copy, mock_rc_params):
|
||||||
fixture = deployment.DeploymentWorkflowFixture()
|
fixture = deployment.DeploymentWorkflowFixture()
|
||||||
@ -1103,6 +1120,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
clients = self.app.client_manager
|
clients = self.app.client_manager
|
||||||
orchestration_client = clients.orchestration
|
orchestration_client = clients.orchestration
|
||||||
orchestration_client.stacks.get.return_value = fakes.create_tht_stack()
|
orchestration_client.stacks.get.return_value = fakes.create_tht_stack()
|
||||||
|
mock_create_env.return_value = ({}, [])
|
||||||
|
|
||||||
arglist = ['--templates', '--config-download', '--setup-only']
|
arglist = ['--templates', '--config-download', '--setup-only']
|
||||||
verifylist = [
|
verifylist = [
|
||||||
@ -1114,7 +1132,6 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
mock_rc_params.return_value = {'password': 'password',
|
mock_rc_params.return_value = {'password': 'password',
|
||||||
'region': 'region1'}
|
'region': 'region1'}
|
||||||
self.cmd.take_action(parsed_args)
|
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_get_hosts_and_enable_ssh_admin.called)
|
||||||
self.assertTrue(fixture.mock_config_download.called)
|
self.assertTrue(fixture.mock_config_download.called)
|
||||||
self.assertTrue(fixture.mock_set_deployment_status.called)
|
self.assertTrue(fixture.mock_set_deployment_status.called)
|
||||||
@ -1124,17 +1141,20 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
)
|
)
|
||||||
mock_copy.assert_called_once()
|
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.get_rc_params', autospec=True)
|
||||||
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
|
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
||||||
'_get_undercloud_host_entry', autospec=True,
|
'_get_undercloud_host_entry', autospec=True,
|
||||||
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@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(
|
def test_config_download_only(
|
||||||
self, mock_deploy_tmpdir,
|
self, mock_deploy,
|
||||||
mock_get_undercloud_host_entry,
|
mock_get_undercloud_host_entry,
|
||||||
mock_copy, mock_rc_params):
|
mock_copy, mock_rc_params,
|
||||||
|
mock_create_parameters_env):
|
||||||
fixture = deployment.DeploymentWorkflowFixture()
|
fixture = deployment.DeploymentWorkflowFixture()
|
||||||
self.useFixture(fixture)
|
self.useFixture(fixture)
|
||||||
utils_fixture = deployment.UtilsOvercloudFixture()
|
utils_fixture = deployment.UtilsOvercloudFixture()
|
||||||
@ -1142,6 +1162,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
clients = self.app.client_manager
|
clients = self.app.client_manager
|
||||||
orchestration_client = clients.orchestration
|
orchestration_client = clients.orchestration
|
||||||
orchestration_client.stacks.get.return_value = fakes.create_tht_stack()
|
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']
|
arglist = ['--templates', '--config-download-only']
|
||||||
verifylist = [
|
verifylist = [
|
||||||
@ -1153,7 +1174,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'region': 'region1'}
|
'region': 'region1'}
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
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.assertFalse(fixture.mock_get_hosts_and_enable_ssh_admin.called)
|
||||||
self.assertTrue(fixture.mock_config_download.called)
|
self.assertTrue(fixture.mock_config_download.called)
|
||||||
self.assertTrue(fixture.mock_set_deployment_status.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'])
|
fixture.mock_set_deployment_status.call_args[-1]['status'])
|
||||||
mock_copy.assert_called_once()
|
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.get_rc_params', autospec=True)
|
||||||
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
|
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
||||||
@ -1171,18 +1194,20 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch('tripleoclient.utils.get_overcloud_endpoint', autospec=True)
|
@mock.patch('tripleoclient.utils.get_overcloud_endpoint', autospec=True)
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@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(
|
def test_config_download_fails(
|
||||||
self, mock_deploy_tmpdir,
|
self, mock_deploy,
|
||||||
mock_overcloud_endpoint,
|
mock_overcloud_endpoint,
|
||||||
mock_create_tempest_deployer_input,
|
mock_create_tempest_deployer_input,
|
||||||
mock_get_undercloud_host_entry,
|
mock_get_undercloud_host_entry,
|
||||||
mock_copy, mock_rc_params):
|
mock_copy, mock_rc_params,
|
||||||
|
mock_create_parameters_env):
|
||||||
fixture = deployment.DeploymentWorkflowFixture()
|
fixture = deployment.DeploymentWorkflowFixture()
|
||||||
self.useFixture(fixture)
|
self.useFixture(fixture)
|
||||||
clients = self.app.client_manager
|
clients = self.app.client_manager
|
||||||
orchestration_client = clients.orchestration
|
orchestration_client = clients.orchestration
|
||||||
orchestration_client.stacks.get.return_value = fakes.create_tht_stack()
|
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']
|
arglist = ['--templates', '--config-download-only']
|
||||||
verifylist = [
|
verifylist = [
|
||||||
@ -1199,7 +1224,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
exceptions.DeploymentError,
|
exceptions.DeploymentError,
|
||||||
self.cmd.take_action,
|
self.cmd.take_action,
|
||||||
parsed_args)
|
parsed_args)
|
||||||
self.assertFalse(mock_deploy_tmpdir.called)
|
self.assertFalse(mock_deploy.called)
|
||||||
self.assertTrue(fixture.mock_config_download.called)
|
self.assertTrue(fixture.mock_config_download.called)
|
||||||
self.assertTrue(fixture.mock_set_deployment_status.called)
|
self.assertTrue(fixture.mock_set_deployment_status.called)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -1212,9 +1237,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'_get_undercloud_host_entry', autospec=True,
|
'_get_undercloud_host_entry', autospec=True,
|
||||||
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@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(
|
def test_override_ansible_cfg(
|
||||||
self, mock_deploy_tmpdir,
|
self, mock_deploy, mock_create_env,
|
||||||
mock_get_undercloud_host_entry,
|
mock_get_undercloud_host_entry,
|
||||||
mock_copy, mock_rc_params):
|
mock_copy, mock_rc_params):
|
||||||
fixture = deployment.DeploymentWorkflowFixture()
|
fixture = deployment.DeploymentWorkflowFixture()
|
||||||
@ -1235,11 +1262,14 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
mock_rc_params.return_value = {'password': 'password',
|
mock_rc_params.return_value = {'password': 'password',
|
||||||
'region': 'region1'}
|
'region': 'region1'}
|
||||||
|
|
||||||
|
mock_create_env.return_value = ({}, [])
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
self.assertTrue(fixture.mock_get_hosts_and_enable_ssh_admin.called)
|
self.assertTrue(fixture.mock_get_hosts_and_enable_ssh_admin.called)
|
||||||
self.assertTrue(fixture.mock_config_download.called)
|
self.assertTrue(fixture.mock_config_download.called)
|
||||||
mock_copy.assert_called_once()
|
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('tripleoclient.utils.check_service_vips_migrated_to_service')
|
||||||
@mock.patch('tripleo_common.utils.plan.default_image_params',
|
@mock.patch('tripleo_common.utils.plan.default_image_params',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -1273,7 +1303,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
mock_process_env, mock_roles_data,
|
mock_process_env, mock_roles_data,
|
||||||
mock_container_prepare, mock_generate_password,
|
mock_container_prepare, mock_generate_password,
|
||||||
mock_rc_params, mock_default_image_params,
|
mock_rc_params, mock_default_image_params,
|
||||||
mock_check_service_vip_migr):
|
mock_check_service_vip_migr,
|
||||||
|
mock_create_parameters_env):
|
||||||
fixture = deployment.DeploymentWorkflowFixture()
|
fixture = deployment.DeploymentWorkflowFixture()
|
||||||
self.useFixture(fixture)
|
self.useFixture(fixture)
|
||||||
utils_fixture = deployment.UtilsOvercloudFixture()
|
utils_fixture = deployment.UtilsOvercloudFixture()
|
||||||
@ -1281,6 +1312,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
clients = self.app.client_manager
|
clients = self.app.client_manager
|
||||||
orchestration_client = clients.orchestration
|
orchestration_client = clients.orchestration
|
||||||
orchestration_client.stacks.get.return_value = fakes.create_tht_stack()
|
orchestration_client.stacks.get.return_value = fakes.create_tht_stack()
|
||||||
|
mock_create_parameters_env.return_value = ({}, [])
|
||||||
|
|
||||||
arglist = ['--templates', '--overcloud-ssh-port-timeout', '42',
|
arglist = ['--templates', '--overcloud-ssh-port-timeout', '42',
|
||||||
'--timeout', '451']
|
'--timeout', '451']
|
||||||
@ -1294,18 +1326,13 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
mock_rc_params.return_value = {'password': 'password',
|
mock_rc_params.return_value = {'password': 'password',
|
||||||
'region': 'region1'}
|
'region': 'region1'}
|
||||||
# assuming heat deploy consumed a 3m out of total 451m timeout
|
# assuming heat deploy consumed a 3m out of total 451m timeout
|
||||||
with mock.patch('time.time', side_effect=[0, 1585820346,
|
with mock.patch('time.time', side_effect=[1585820346,
|
||||||
0, 12345678, 0,
|
12345678, 0, 0,
|
||||||
1585820526]):
|
1585820526]):
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
[mock.call(mock.ANY, mock.ANY, 'overcloud', mock.ANY,
|
[mock.call(mock.ANY, mock.ANY, 'overcloud', mock.ANY,
|
||||||
{'StackAction': 'UPDATE',
|
{'StackAction': 'UPDATE'}, mock.ANY,
|
||||||
'DeployIdentifier': 12345678,
|
|
||||||
'RootStackName': 'overcloud',
|
|
||||||
'UndercloudHostsEntries':
|
|
||||||
['192.168.0.1 uc.ctlplane.localhost uc.ctlplane'],
|
|
||||||
'CtlplaneNetworkAttributes': {}}, mock.ANY,
|
|
||||||
451, mock.ANY, mock.ANY, False, None,
|
451, mock.ANY, mock.ANY, False, None,
|
||||||
deployment_options={}, env_files_tracker=mock.ANY)],
|
deployment_options={}, env_files_tracker=mock.ANY)],
|
||||||
mock_hd.mock_calls)
|
mock_hd.mock_calls)
|
||||||
@ -1333,9 +1360,11 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
'_get_undercloud_host_entry', autospec=True,
|
'_get_undercloud_host_entry', autospec=True,
|
||||||
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
|
||||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
|
@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(
|
def test_config_download_only_timeout(
|
||||||
self, mock_deploy_tmpdir,
|
self, mock_deploy, mock_create_env,
|
||||||
mock_get_undercloud_host_entry, mock_update,
|
mock_get_undercloud_host_entry, mock_update,
|
||||||
mock_copyi, mock_rc_params, mock_cd_dir):
|
mock_copyi, mock_rc_params, mock_cd_dir):
|
||||||
utils_fixture = deployment.UtilsOvercloudFixture()
|
utils_fixture = deployment.UtilsOvercloudFixture()
|
||||||
@ -1362,6 +1391,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
|
|||||||
mock_rc_params.return_value = {'password': 'password',
|
mock_rc_params.return_value = {'password': 'password',
|
||||||
'region': 'region1'}
|
'region': 'region1'}
|
||||||
|
|
||||||
|
mock_create_env.return_value = ({}, [])
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
playbook = os.path.join(os.environ.get(
|
playbook = os.path.join(os.environ.get(
|
||||||
'HOME'), self.cmd.working_dir,
|
'HOME'), self.cmd.working_dir,
|
||||||
|
@ -52,7 +52,6 @@ from heatclient.common import template_utils
|
|||||||
from heatclient.common import utils as heat_utils
|
from heatclient.common import utils as heat_utils
|
||||||
from heatclient.exc import HTTPNotFound
|
from heatclient.exc import HTTPNotFound
|
||||||
from osc_lib import exceptions as oscexc
|
from osc_lib import exceptions as oscexc
|
||||||
from osc_lib import utils as osc_lib_utils
|
|
||||||
from osc_lib.i18n import _
|
from osc_lib.i18n import _
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from six.moves import configparser
|
from six.moves import configparser
|
||||||
@ -71,6 +70,14 @@ from tripleoclient import constants
|
|||||||
from tripleoclient import exceptions
|
from tripleoclient import exceptions
|
||||||
from tripleoclient import heat_launcher
|
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")
|
LOG = logging.getLogger(__name__ + ".utils")
|
||||||
_local_orchestration_client = None
|
_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):
|
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 _local_orchestration_client
|
||||||
global _heat_pid
|
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)
|
heat_api_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
test_heat_api_port(heat_api_socket, launcher.host, int(launcher.api_port))
|
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)
|
launcher.host, launcher.api_port)
|
||||||
return _local_orchestration_client
|
return _local_orchestration_client
|
||||||
|
|
||||||
@ -2609,41 +2621,6 @@ def get_heat_launcher(heat_type, *args, **kwargs):
|
|||||||
return heat_launcher.HeatPodLauncher(*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):
|
def kill_heat(launcher, backup_db=True):
|
||||||
global _heat_pid
|
global _heat_pid
|
||||||
if _heat_pid:
|
if _heat_pid:
|
||||||
|
@ -77,59 +77,44 @@ class DeployOvercloud(command.Command):
|
|||||||
answers['environments'].extend(args.environment_files)
|
answers['environments'].extend(args.environment_files)
|
||||||
args.environment_files = answers['environments']
|
args.environment_files = answers['environments']
|
||||||
|
|
||||||
def _update_parameters(self, args, stack, tht_root, user_tht_root):
|
def _update_parameters(self, args, parameters,
|
||||||
parameters = {}
|
tht_root, user_tht_root):
|
||||||
|
|
||||||
stack_is_new = stack is None
|
|
||||||
|
|
||||||
parameters['RootStackName'] = args.stack
|
parameters['RootStackName'] = args.stack
|
||||||
if not args.skip_deploy_identifier:
|
if not args.skip_deploy_identifier:
|
||||||
parameters['DeployIdentifier'] = int(time.time())
|
parameters['DeployIdentifier'] = int(time.time())
|
||||||
else:
|
else:
|
||||||
parameters['DeployIdentifier'] = ''
|
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
|
# Check for existing passwords file
|
||||||
env_files = []
|
password_params_path = os.path.join(
|
||||||
env_files.append(
|
self.working_dir,
|
||||||
os.path.join(tht_root, constants.DEFAULT_RESOURCE_REGISTRY))
|
constants.PASSWORDS_ENV_FORMAT.format(args.stack))
|
||||||
if args.environment_directories:
|
if os.path.exists(password_params_path):
|
||||||
env_files.extend(utils.load_environment_directories(
|
with open(password_params_path, 'r') as f:
|
||||||
args.environment_directories))
|
passwords_env = yaml.safe_load(f.read())
|
||||||
if args.environment_files:
|
else:
|
||||||
env_files.extend(args.environment_files)
|
passwords_env = None
|
||||||
|
|
||||||
_, 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)
|
|
||||||
|
|
||||||
password_params = plan_utils.generate_passwords(
|
password_params = plan_utils.generate_passwords(
|
||||||
None, self.orchestration_client,
|
None, heat, args.stack, passwords_env=passwords_env)
|
||||||
args.stack)
|
|
||||||
|
# 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)
|
parameters.update(password_params)
|
||||||
|
|
||||||
param_args = (
|
param_args = (
|
||||||
('NtpServer', 'ntp_server'),
|
('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
|
# Update parameters from commandline
|
||||||
for param, arg in param_args:
|
for param, arg in param_args:
|
||||||
if getattr(args, arg, None) is not None:
|
if getattr(args, arg, None) is not None:
|
||||||
@ -245,7 +230,7 @@ class DeployOvercloud(command.Command):
|
|||||||
template, files, env_files_tracker,
|
template, files, env_files_tracker,
|
||||||
self.log, self.working_dir)
|
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)
|
tht_root = os.path.abspath(parsed_args.templates)
|
||||||
new_tht_root = "%s/tripleo-heat-templates" % self.working_dir
|
new_tht_root = "%s/tripleo-heat-templates" % self.working_dir
|
||||||
self.log.debug("Creating working templates tree in %s"
|
self.log.debug("Creating working templates tree in %s"
|
||||||
@ -257,38 +242,61 @@ class DeployOvercloud(command.Command):
|
|||||||
parsed_args.roles_file,
|
parsed_args.roles_file,
|
||||||
parsed_args.networks_file,
|
parsed_args.networks_file,
|
||||||
new_tht_root)
|
new_tht_root)
|
||||||
self._deploy_tripleo_heat_templates(stack, parsed_args,
|
return new_tht_root, tht_root
|
||||||
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)))
|
|
||||||
|
|
||||||
|
def create_params_and_env_files(self, new_tht_root, user_tht_root,
|
||||||
|
parsed_args):
|
||||||
self.log.debug("Creating Environment files")
|
self.log.debug("Creating Environment files")
|
||||||
created_env_files = []
|
created_env_files = []
|
||||||
|
|
||||||
created_env_files.append(
|
created_env_files.append(
|
||||||
os.path.join(tht_root, constants.DEFAULT_RESOURCE_REGISTRY))
|
os.path.join(new_tht_root, constants.DEFAULT_RESOURCE_REGISTRY))
|
||||||
created_env_files.extend(
|
|
||||||
self._provision_baremetal(parsed_args, tht_root))
|
|
||||||
|
|
||||||
if parsed_args.environment_directories:
|
if parsed_args.environment_directories:
|
||||||
created_env_files.extend(utils.load_environment_directories(
|
created_env_files.extend(utils.load_environment_directories(
|
||||||
parsed_args.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 = {}
|
||||||
parameters.update(self._update_parameters(
|
if image_params:
|
||||||
parsed_args, stack, tht_root, user_tht_root))
|
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(
|
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)
|
created_env_files.extend(param_env)
|
||||||
|
|
||||||
if parsed_args.deployed_server:
|
if parsed_args.deployed_server:
|
||||||
created_env_files.append(
|
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:
|
if parsed_args.environment_files:
|
||||||
created_env_files.extend(parsed_args.environment_files)
|
created_env_files.extend(parsed_args.environment_files)
|
||||||
@ -300,22 +308,22 @@ class DeployOvercloud(command.Command):
|
|||||||
|
|
||||||
if stack:
|
if stack:
|
||||||
env_path = utils.create_breakpoint_cleanup_env(
|
env_path = utils.create_breakpoint_cleanup_env(
|
||||||
tht_root, parsed_args.stack)
|
new_tht_root, parsed_args.stack)
|
||||||
created_env_files.extend(env_path)
|
created_env_files.extend(env_path)
|
||||||
|
|
||||||
self.log.debug("Processing environment files %s" % created_env_files)
|
self.log.debug("Processing environment files %s" % created_env_files)
|
||||||
env_files_tracker = []
|
env_files_tracker = []
|
||||||
env_files, env = utils.process_multiple_environments(
|
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,
|
env_files_tracker=env_files_tracker,
|
||||||
cleanup=(not parsed_args.no_cleanup))
|
cleanup=(not parsed_args.no_cleanup))
|
||||||
|
|
||||||
# Invokes the workflows specified in plan environment file
|
# Invokes the workflows specified in plan environment file
|
||||||
if parsed_args.plan_environment_file:
|
if parsed_args.plan_environment_file:
|
||||||
output_path = utils.build_user_env_path(
|
output_path = utils.build_user_env_path(
|
||||||
'derived_parameters.yaml', tht_root)
|
'derived_parameters.yaml', new_tht_root)
|
||||||
workflow_params.build_derived_params_environment(
|
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,
|
env_files_tracker, parsed_args.roles_file,
|
||||||
parsed_args.plan_environment_file,
|
parsed_args.plan_environment_file,
|
||||||
output_path, utils.playbook_verbosity(self=self))
|
output_path, utils.playbook_verbosity(self=self))
|
||||||
@ -323,12 +331,12 @@ class DeployOvercloud(command.Command):
|
|||||||
created_env_files.append(output_path)
|
created_env_files.append(output_path)
|
||||||
env_files_tracker = []
|
env_files_tracker = []
|
||||||
env_files, env = utils.process_multiple_environments(
|
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,
|
env_files_tracker=env_files_tracker,
|
||||||
cleanup=(not parsed_args.no_cleanup))
|
cleanup=(not parsed_args.no_cleanup))
|
||||||
|
|
||||||
# Copy the env_files to tmp folder for archiving
|
# 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:
|
if parsed_args.limit:
|
||||||
# check if skip list is defined while using --limit and throw a
|
# check if skip list is defined while using --limit and throw a
|
||||||
@ -365,7 +373,7 @@ class DeployOvercloud(command.Command):
|
|||||||
'(with HA).')
|
'(with HA).')
|
||||||
|
|
||||||
self._try_overcloud_deploy_with_compat_yaml(
|
self._try_overcloud_deploy_with_compat_yaml(
|
||||||
tht_root, stack,
|
new_tht_root, stack,
|
||||||
parsed_args.stack, parameters, env_files,
|
parsed_args.stack, parameters, env_files,
|
||||||
parsed_args.timeout, env,
|
parsed_args.timeout, env,
|
||||||
parsed_args.run_validations,
|
parsed_args.run_validations,
|
||||||
@ -911,6 +919,35 @@ class DeployOvercloud(command.Command):
|
|||||||
'input, output, and generated files will be stored.\n'
|
'input, output, and generated files will be stored.\n'
|
||||||
'Defaults to "$HOME/overcloud-deploy/<stack>"')
|
'Defaults to "$HOME/overcloud-deploy/<stack>"')
|
||||||
)
|
)
|
||||||
|
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
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -950,21 +987,64 @@ class DeployOvercloud(command.Command):
|
|||||||
parsed_args.environment_files)
|
parsed_args.environment_files)
|
||||||
|
|
||||||
self._update_args_from_answers_file(parsed_args)
|
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:
|
if parsed_args.dry_run:
|
||||||
self.log.info("Validation Finished")
|
self.log.info("Validation Finished")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.heat_launcher = None
|
||||||
|
stack = None
|
||||||
|
stack_create = None
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
if not parsed_args.config_download_only:
|
new_tht_root, user_tht_root = \
|
||||||
self._deploy_tripleo_heat_templates_tmpdir(stack, parsed_args)
|
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
|
# Get a new copy of the stack after stack update/create. If it was
|
||||||
# a create then the previous stack object would be None.
|
# a create then the previous stack object would be None.
|
||||||
@ -980,7 +1060,8 @@ class DeployOvercloud(command.Command):
|
|||||||
stack.get()
|
stack.get()
|
||||||
overcloud_endpoint = utils.get_overcloud_endpoint(stack)
|
overcloud_endpoint = utils.get_overcloud_endpoint(stack)
|
||||||
horizon_url = deployment.get_horizon_url(
|
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(
|
rc_params = utils.get_rc_params(
|
||||||
self.orchestration_client,
|
self.orchestration_client,
|
||||||
parsed_args.stack)
|
parsed_args.stack)
|
||||||
@ -993,6 +1074,9 @@ class DeployOvercloud(command.Command):
|
|||||||
stack, rc_params, parsed_args.no_proxy,
|
stack, rc_params, parsed_args.no_proxy,
|
||||||
self.working_dir)
|
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:
|
if parsed_args.config_download or parsed_args.setup_only:
|
||||||
self.log.info("Deploying overcloud configuration")
|
self.log.info("Deploying overcloud configuration")
|
||||||
deployment.set_deployment_status(
|
deployment.set_deployment_status(
|
||||||
@ -1009,7 +1093,8 @@ class DeployOvercloud(command.Command):
|
|||||||
parsed_args.overcloud_ssh_user,
|
parsed_args.overcloud_ssh_user,
|
||||||
self.get_key_pair(parsed_args),
|
self.get_key_pair(parsed_args),
|
||||||
parsed_args.overcloud_ssh_port_timeout,
|
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:
|
if parsed_args.config_download_timeout:
|
||||||
@ -1026,8 +1111,6 @@ class DeployOvercloud(command.Command):
|
|||||||
deployment_options['ansible_python_interpreter'] = \
|
deployment_options['ansible_python_interpreter'] = \
|
||||||
parsed_args.deployment_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,
|
deployment.make_config_download_dir(config_download_dir,
|
||||||
parsed_args.stack)
|
parsed_args.stack)
|
||||||
|
|
||||||
@ -1082,7 +1165,8 @@ class DeployOvercloud(command.Command):
|
|||||||
parsed_args.overcloud_ssh_user,
|
parsed_args.overcloud_ssh_user,
|
||||||
self.get_key_pair(parsed_args),
|
self.get_key_pair(parsed_args),
|
||||||
parsed_args.overcloud_ssh_port_timeout,
|
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(
|
deployment.set_deployment_status(
|
||||||
@ -1117,6 +1201,11 @@ class DeployOvercloud(command.Command):
|
|||||||
rcpath, old_rcpath))
|
rcpath, old_rcpath))
|
||||||
print("Overcloud Deployed {0}".format(deploy_message))
|
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:
|
if parsed_args.output_dir:
|
||||||
ansible_dir = config_download_dir
|
ansible_dir = config_download_dir
|
||||||
else:
|
else:
|
||||||
|
@ -477,7 +477,8 @@ class Deploy(command.Command):
|
|||||||
self.heat_launch = heat_launcher.HeatNativeLauncher(
|
self.heat_launch = heat_launcher.HeatNativeLauncher(
|
||||||
parsed_args.heat_api_port,
|
parsed_args.heat_api_port,
|
||||||
parsed_args.heat_container_image,
|
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
|
# NOTE(dprince): we launch heat with fork exec because
|
||||||
# we don't want it to inherit our args. Launching heat
|
# we don't want it to inherit our args. Launching heat
|
||||||
|
@ -28,6 +28,13 @@ from tripleoclient.constants import DEFAULT_WORK_DIR
|
|||||||
from tripleoclient import exceptions
|
from tripleoclient import exceptions
|
||||||
from tripleoclient import utils
|
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
|
_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,
|
def get_hosts_and_enable_ssh_admin(stack, overcloud_ssh_network,
|
||||||
overcloud_ssh_user, overcloud_ssh_key,
|
overcloud_ssh_user, overcloud_ssh_key,
|
||||||
overcloud_ssh_port_timeout,
|
overcloud_ssh_port_timeout,
|
||||||
verbosity=0):
|
verbosity=0, heat_type='installed'):
|
||||||
"""Enable ssh admin access.
|
"""Enable ssh admin access.
|
||||||
|
|
||||||
Get a list of hosts from a given stack and enable admin ssh across all of
|
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_user,
|
||||||
overcloud_ssh_key,
|
overcloud_ssh_key,
|
||||||
overcloud_ssh_port_timeout,
|
overcloud_ssh_port_timeout,
|
||||||
verbosity=verbosity
|
verbosity=verbosity,
|
||||||
|
heat_type=heat_type
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise exceptions.DeploymentError(
|
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,
|
def enable_ssh_admin(stack, hosts, ssh_user, ssh_key, timeout,
|
||||||
verbosity=0):
|
verbosity=0, heat_type='installed'):
|
||||||
"""Run enable ssh admin access playbook.
|
"""Run enable ssh admin access playbook.
|
||||||
|
|
||||||
:param stack: Stack data.
|
:param stack: Stack data.
|
||||||
@ -213,22 +221,28 @@ def enable_ssh_admin(stack, hosts, ssh_user, ssh_key, timeout,
|
|||||||
ssh_key
|
ssh_key
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
with utils.TempDirs() as tmp:
|
try:
|
||||||
utils.run_ansible_playbook(
|
if heat_type != 'installed' and tc_heat_utils.heatclient:
|
||||||
playbook='cli-enable-ssh-admin.yaml',
|
tc_heat_utils.heatclient.save_environment()
|
||||||
inventory=','.join(hosts),
|
with utils.TempDirs() as tmp:
|
||||||
workdir=tmp,
|
utils.run_ansible_playbook(
|
||||||
playbook_dir=ANSIBLE_TRIPLEO_PLAYBOOKS,
|
playbook='cli-enable-ssh-admin.yaml',
|
||||||
key=ssh_key,
|
inventory=','.join(hosts),
|
||||||
ssh_user=ssh_user,
|
workdir=tmp,
|
||||||
verbosity=verbosity,
|
playbook_dir=ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||||
extra_vars={
|
key=ssh_key,
|
||||||
"ssh_user": ssh_user,
|
ssh_user=ssh_user,
|
||||||
"ssh_servers": hosts,
|
verbosity=verbosity,
|
||||||
'tripleo_cloud_name': stack.stack_name
|
extra_vars={
|
||||||
},
|
"ssh_user": ssh_user,
|
||||||
ansible_timeout=timeout
|
"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.")
|
print("Enabling ssh admin - COMPLETE.")
|
||||||
|
|
||||||
|
|
||||||
@ -467,7 +481,8 @@ def config_download(log, clients, stack, ssh_network='ctlplane',
|
|||||||
repo.git.commit("--amend", "--no-edit")
|
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.
|
"""Return horizon URL string.
|
||||||
|
|
||||||
:params stack: Stack name
|
:params stack: Stack name
|
||||||
@ -475,22 +490,28 @@ def get_horizon_url(stack, verbosity=0):
|
|||||||
:returns: string
|
:returns: string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with utils.TempDirs() as tmp:
|
try:
|
||||||
horizon_tmp_file = os.path.join(tmp, 'horizon_url')
|
if heat_type != 'installed' and tc_heat_utils.heatclient:
|
||||||
utils.run_ansible_playbook(
|
tc_heat_utils.heatclient.save_environment()
|
||||||
playbook='cli-undercloud-get-horizon-url.yaml',
|
with utils.TempDirs() as tmp:
|
||||||
inventory='localhost,',
|
horizon_tmp_file = os.path.join(tmp, 'horizon_url')
|
||||||
workdir=tmp,
|
utils.run_ansible_playbook(
|
||||||
playbook_dir=ANSIBLE_TRIPLEO_PLAYBOOKS,
|
playbook='cli-undercloud-get-horizon-url.yaml',
|
||||||
verbosity=verbosity,
|
inventory='localhost,',
|
||||||
extra_vars={
|
workdir=tmp,
|
||||||
'stack_name': stack,
|
playbook_dir=ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||||
'horizon_url_output_file': horizon_tmp_file
|
verbosity=verbosity,
|
||||||
}
|
extra_vars={
|
||||||
)
|
'stack_name': stack,
|
||||||
|
'horizon_url_output_file': horizon_tmp_file
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
with open(horizon_tmp_file) as f:
|
with open(horizon_tmp_file) as f:
|
||||||
return f.read().strip()
|
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):
|
def get_deployment_status(clients, stack_name, working_dir):
|
||||||
|
Loading…
Reference in New Issue
Block a user