diff --git a/releasenotes/notes/skip-deploy-identifier-f7eb0d3ff5126f62.yaml b/releasenotes/notes/skip-deploy-identifier-f7eb0d3ff5126f62.yaml new file mode 100644 index 000000000..bd34ae4db --- /dev/null +++ b/releasenotes/notes/skip-deploy-identifier-f7eb0d3ff5126f62.yaml @@ -0,0 +1,10 @@ +--- +features: + - Add a new cli argument, --skip-deploy-identifier. The argument will disable + setting a unique value for the DeployIdentifier parameter, which means the + SoftwareDeployment resources in the templates will only be triggered if + there is an actual change to their configuration. This argument can be used + to avoid always applying configuration, such as during node scale out. + This option should be used with Caution, and only if there is confidence + that the software configuration does not need to be run, such as when + scaling out certain roles. diff --git a/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py b/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py index b85d02282..3f29dacd1 100644 --- a/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py +++ b/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py @@ -285,6 +285,101 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_tarball.tarball_extract_to_swift_container.assert_called_with( clients.tripleoclient.object_store, mock.ANY, 'overcloud') + @mock.patch('tripleoclient.utils.get_overcloud_endpoint', autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_deploy_postconfig', autospec=True) + @mock.patch('tripleoclient.workflows.plan_management.tarball', + autospec=True) + @mock.patch('tripleo_common.update.add_breakpoints_cleanup_into_env', + autospec=True) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_validate_args') + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_create_parameters_env', autospec=True) + @mock.patch('tripleoclient.utils.create_tempest_deployer_input', + autospec=True) + @mock.patch('tripleoclient.utils.write_overcloudrc', autospec=True) + @mock.patch('tripleoclient.utils.remove_known_hosts', autospec=True) + @mock.patch('tripleoclient.utils.wait_for_stack_ready', + autospec=True) + @mock.patch('heatclient.common.template_utils.get_template_contents', + autospec=True) + @mock.patch('tripleoclient.utils.check_hypervisor_stats', + autospec=True) + @mock.patch('uuid.uuid1', autospec=True) + @mock.patch('time.time', autospec=True) + @mock.patch('shutil.rmtree', autospec=True) + @mock.patch('shutil.copytree', autospec=True) + @mock.patch('tempfile.mkdtemp', autospec=True) + def test_tht_deploy_skip_deploy_identifier( + self, mock_tmpdir, mock_copy, mock_rm, mock_time, + mock_uuid1, + mock_check_hypervisor_stats, + mock_get_template_contents, + wait_for_stack_ready_mock, + mock_remove_known_hosts, + mock_write_overcloudrc, + mock_create_tempest_deployer_input, + mock_create_parameters_env, mock_validate_args, + mock_breakpoints_cleanup, mock_tarball, + mock_postconfig, mock_get_overcloud_endpoint): + + arglist = ['--templates', '--skip-deploy-identifier'] + verifylist = [ + ('templates', '/usr/share/openstack-tripleo-heat-templates/'), + ('skip_deploy_identifier', True) + ] + + mock_tmpdir.return_value = "/tmp/tht" + mock_uuid1.return_value = "uuid" + mock_time.return_value = 123456789 + + clients = self.app.client_manager + orchestration_client = clients.orchestration + mock_stack = fakes.create_tht_stack() + orchestration_client.stacks.get.side_effect = [None, mock.Mock()] + workflow_client = clients.workflow_engine + workflow_client.environments.get.return_value = mock.MagicMock( + variables={'environments': []}) + workflow_client.action_executions.create.return_value = mock.MagicMock( + output='{"result":[]}') + + def _orch_clt_create(**kwargs): + orchestration_client.stacks.get.return_value = mock_stack + + orchestration_client.stacks.create.side_effect = _orch_clt_create + + mock_check_hypervisor_stats.return_value = { + 'count': 4, + 'memory_mb': 4096, + 'vcpus': 8, + } + clients.network.api.find_attr.return_value = { + "id": "network id" + } + mock_get_template_contents.return_value = [{}, "template"] + wait_for_stack_ready_mock.return_value = True + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + baremetal = clients.baremetal + baremetal.node.list.return_value = range(10) + + testcase = self + + def _custom_create_params_env(self, parameters): + testcase.assertTrue('DeployIdentifier' not in parameters) + parameter_defaults = {"parameter_defaults": parameters} + return parameter_defaults + + mock_create_parameters_env.side_effect = _custom_create_params_env + + self.cmd.take_action(parsed_args) + execution_calls = workflow_client.executions.create.call_args_list + deploy_plan_call = execution_calls[1] + deploy_plan_call_input = deploy_plan_call[1]['workflow_input'] + self.assertTrue(deploy_plan_call_input['skip_deploy_identifier']) + @mock.patch('tripleoclient.workflows.plan_management.tarball', autospec=True) @mock.patch("heatclient.common.event_utils.get_events", autospec=True) @@ -445,7 +540,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): def _fake_heat_deploy(self, stack, stack_name, template_path, parameters, environments, timeout, tht_root, - env, update_plan_only, run_validations): + env, update_plan_only, run_validations, + skip_deploy_identifier): assertEqual( {'parameter_defaults': {}, 'resource_registry': { @@ -506,7 +602,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): def _fake_heat_deploy(self, stack, stack_name, template_path, parameters, environments, timeout, tht_root, - env, update_plan_only, run_validations): + env, update_plan_only, run_validations, + skip_deploy_identifier): # Should be no breakpoint cleanup because utils.get_stack = None assertEqual( {'parameter_defaults': {}, @@ -741,13 +838,13 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): self, mock_heat_deploy_func): result = self.cmd._try_overcloud_deploy_with_compat_yaml( '/fake/path', {}, 'overcloud', {}, ['~/overcloud-env.json'], 1, - {}, False, True) + {}, False, True, False) # If it returns None it succeeded self.assertIsNone(result) mock_heat_deploy_func.assert_called_once_with( self.cmd, {}, 'overcloud', '/fake/path/' + constants.OVERCLOUD_YAML_NAME, {}, - ['~/overcloud-env.json'], 1, '/fake/path', {}, False, True) + ['~/overcloud-env.json'], 1, '/fake/path', {}, False, True, False) @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' '_heat_deploy', autospec=True) @@ -757,7 +854,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): self.assertRaises(ValueError, self.cmd._try_overcloud_deploy_with_compat_yaml, '/fake/path', mock.ANY, mock.ANY, mock.ANY, - mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY) + mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, + mock.ANY) @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' '_heat_deploy', autospec=True) @@ -768,7 +866,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): try: self.cmd._try_overcloud_deploy_with_compat_yaml( '/fake/path', mock.ANY, mock.ANY, mock.ANY, - mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY) + mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY) except ValueError as value_error: self.assertIn('/fake/path', str(value_error)) @@ -1120,7 +1218,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_get_template_contents.return_value = [{}, {}] self.cmd._heat_deploy(mock_stack, 'mock_stack', '/tmp', {}, - {}, 1, '/tmp', {}, True, False) + {}, 1, '/tmp', {}, True, False, False) self.assertFalse(mock_deploy_and_wait.called) diff --git a/tripleoclient/tests/v1/test_overcloud_plan.py b/tripleoclient/tests/v1/test_overcloud_plan.py index d45768e1c..a9a93c22f 100644 --- a/tripleoclient/tests/v1/test_overcloud_plan.py +++ b/tripleoclient/tests/v1/test_overcloud_plan.py @@ -356,6 +356,7 @@ class TestOvercloudDeployPlan(utils.TestCommand): workflow_input={ 'container': 'overcast', 'run_validations': True, - 'queue_name': 'UUID4' + 'queue_name': 'UUID4', + 'skip_deploy_identifier': False } ) diff --git a/tripleoclient/v1/overcloud_deploy.py b/tripleoclient/v1/overcloud_deploy.py index 00e296781..cae9dde20 100644 --- a/tripleoclient/v1/overcloud_deploy.py +++ b/tripleoclient/v1/overcloud_deploy.py @@ -23,7 +23,6 @@ import re import shutil import six import tempfile -import time import uuid import yaml @@ -68,8 +67,6 @@ class DeployOvercloud(command.Command): stack_is_new = stack is None - timestamp = int(time.time()) - parameters['DeployIdentifier'] = timestamp parameters['UpdateIdentifier'] = '' parameters['StackAction'] = 'CREATE' if stack_is_new else 'UPDATE' @@ -199,7 +196,7 @@ class DeployOvercloud(command.Command): def _heat_deploy(self, stack, stack_name, template_path, parameters, env_files, timeout, tht_root, env, update_plan_only, - run_validations): + run_validations, skip_deploy_identifier): """Verify the Baremetal nodes are available and do a stack update""" self.log.debug("Getting template contents from plan %s" % stack_name) @@ -227,10 +224,12 @@ class DeployOvercloud(command.Command): stack_name, env, moved_files, tht_root) if not update_plan_only: - deployment.deploy_and_wait(self.log, self.clients, stack, - stack_name, self.app_args.verbose_level, - timeout=timeout, - run_validations=run_validations) + deployment.deploy_and_wait( + self.log, self.clients, stack, + stack_name, self.app_args.verbose_level, + timeout=timeout, + run_validations=run_validations, + skip_deploy_identifier=skip_deploy_identifier) def _load_environment_directories(self, directories): if os.environ.get('TRIPLEO_ENVIRONMENT_DIRECTORY'): @@ -439,19 +438,20 @@ class DeployOvercloud(command.Command): self._try_overcloud_deploy_with_compat_yaml( tht_root, stack, parsed_args.stack, parameters, env_files, parsed_args.timeout, env, parsed_args.update_plan_only, - parsed_args.run_validations) + parsed_args.run_validations, parsed_args.skip_deploy_identifier) def _try_overcloud_deploy_with_compat_yaml(self, tht_root, stack, stack_name, parameters, env_files, timeout, env, update_plan_only, - run_validations): + run_validations, + skip_deploy_identifier): overcloud_yaml = os.path.join(tht_root, constants.OVERCLOUD_YAML_NAME) try: self._heat_deploy(stack, stack_name, overcloud_yaml, parameters, env_files, timeout, tht_root, env, update_plan_only, - run_validations) + run_validations, skip_deploy_identifier) except ClientException as e: messages = 'Failed to deploy: %s' % str(e) raise ValueError(messages) @@ -743,6 +743,18 @@ class DeployOvercloud(command.Command): default=False, help=_('Force the overcloud post-deployment configuration.') ) + parser.add_argument( + '--skip-deploy-identifier', + action='store_true', + default=False, + help=_('Skip generation of a unique identifier for the ' + 'DeployIdentifier parameter. The software configuration ' + 'deployment steps will only be triggered if there is an ' + 'actual change to the configuration. This option should ' + 'be used with Caution, and only if there is confidence ' + 'that the software configuration does not need to be ' + 'run, such as when scaling out certain roles.') + ) reg_group = parser.add_argument_group('Registration Parameters') reg_group.add_argument( '--rhel-reg', diff --git a/tripleoclient/workflows/deployment.py b/tripleoclient/workflows/deployment.py index 53c0f2963..7ec4be719 100644 --- a/tripleoclient/workflows/deployment.py +++ b/tripleoclient/workflows/deployment.py @@ -46,12 +46,14 @@ def deploy(clients, **workflow_input): def deploy_and_wait(log, clients, stack, plan_name, verbose_level, - timeout=None, run_validations=False): + timeout=None, run_validations=False, + skip_deploy_identifier=False): """Start the deploy and wait for it to finish""" workflow_input = { "container": plan_name, "run_validations": run_validations, + "skip_deploy_identifier": skip_deploy_identifier, "queue_name": str(uuid.uuid4()), }