diff --git a/doc/source/user/wf_lang_v2.rst b/doc/source/user/wf_lang_v2.rst index 25a8299f6..0e91224bc 100644 --- a/doc/source/user/wf_lang_v2.rst +++ b/doc/source/user/wf_lang_v2.rst @@ -160,6 +160,7 @@ Common workflow attributes - **timeout** - Configures timeout policy. *Optional*. - **retry** - Configures retry policy. *Optional*. - **concurrency** - Configures concurrency policy. *Optional*. + - **safe-rerun** - Configures safe-rerun policy. *Optional*. - **tasks** - Dictionary containing workflow tasks. See below for more details. *Required*. diff --git a/mistral/engine/tasks.py b/mistral/engine/tasks.py index c747eaa8a..420a3de25 100644 --- a/mistral/engine/tasks.py +++ b/mistral/engine/tasks.py @@ -336,6 +336,22 @@ class Task(object): self.created = True + def _get_safe_rerun(self): + safe_rerun = self.task_spec.get_safe_rerun() + + if safe_rerun is not None: + return safe_rerun + + task_defaults = self.wf_spec.get_task_defaults() + + if task_defaults: + default_safe_rerun = task_defaults.get_safe_rerun() + + if default_safe_rerun is not None: + return default_safe_rerun + + return False + def _get_action_defaults(self): action_name = self.task_spec.get_action_name() @@ -474,7 +490,7 @@ class RegularTask(Task): action.schedule( input_dict, target, - safe_rerun=self.task_spec.get_safe_rerun(), + safe_rerun=self._get_safe_rerun(), timeout=self._get_timeout() ) @@ -655,7 +671,7 @@ class WithItemsTask(RegularTask): input_dict, target, index=i, - safe_rerun=self.task_spec.get_safe_rerun(), + safe_rerun=self._get_safe_rerun(), timeout=self._get_timeout() ) diff --git a/mistral/lang/v2/task_defaults.py b/mistral/lang/v2/task_defaults.py index 7cb3bcb13..acb591c39 100644 --- a/mistral/lang/v2/task_defaults.py +++ b/mistral/lang/v2/task_defaults.py @@ -40,6 +40,7 @@ class TaskDefaultsSpec(base.BaseSpec): "on-complete": types.ANY, "on-success": types.ANY, "on-error": types.ANY, + "safe-rerun": types.EXPRESSION_OR_BOOLEAN, "requires": { "oneOf": [types.NONEMPTY_STRING, types.UNIQUE_STRING_LIST] } @@ -70,10 +71,17 @@ class TaskDefaultsSpec(base.BaseSpec): self._on_success = self._spec_property('on-success', on_spec_cls) self._on_error = self._spec_property('on-error', on_spec_cls) + self._safe_rerun = data.get('safe-rerun') + # TODO(rakhmerov): 'requires' should reside in a different spec for # reverse workflows. self._requires = data.get('requires', []) + def validate_schema(self): + super(TaskDefaultsSpec, self).validate_schema() + + self.validate_expr(self._data.get('safe-rerun', {})) + def validate_semantics(self): # Validate YAQL expressions. self._validate_transitions(self._on_complete) @@ -101,6 +109,9 @@ class TaskDefaultsSpec(base.BaseSpec): def get_on_error(self): return self._on_error + def get_safe_rerun(self): + return self._safe_rerun + def get_requires(self): if isinstance(self._requires, six.string_types): return [self._requires] diff --git a/mistral/lang/v2/tasks.py b/mistral/lang/v2/tasks.py index ff0b86991..13c7f6ba8 100644 --- a/mistral/lang/v2/tasks.py +++ b/mistral/lang/v2/tasks.py @@ -128,7 +128,7 @@ class TaskSpec(base.BaseSpec): ) self._target = data.get('target') self._keep_result = data.get('keep-result', True) - self._safe_rerun = data.get('safe-rerun', False) + self._safe_rerun = data.get('safe-rerun') self._process_action_and_workflow() diff --git a/mistral/tests/unit/engine/test_safe_rerun.py b/mistral/tests/unit/engine/test_safe_rerun.py index 6e0439e62..b1da423b3 100644 --- a/mistral/tests/unit/engine/test_safe_rerun.py +++ b/mistral/tests/unit/engine/test_safe_rerun.py @@ -176,3 +176,68 @@ class TestSafeRerun(base.EngineTestCase): self.assertIn(1, result) self.assertIn(2, result) self.assertIn(3, result) + + @mock.patch.object(r_exe.RemoteExecutor, 'run_action', MOCK_RUN_AT_TARGET) + def test_safe_rerun_in_task_defaults(self): + wf_text = """--- + version: '2.0' + + wf: + task-defaults: + safe-rerun: true + tasks: + task1: + safe-rerun: false + on-error: + - task2 + + task2: + action: std.noop + """ + + wf_service.create_workflows(wf_text) + + wf_ex = self.engine.start_workflow('wf') + + self.await_workflow_success(wf_ex.id) + + with db_api.transaction(): + wf_ex = db_api.get_workflow_execution(wf_ex.id) + + tasks = wf_ex.task_executions + + self.assertEqual(len(tasks), 2) + + task1 = self._assert_single_item(tasks, name='task1') + task2 = self._assert_single_item(tasks, name='task2') + + self.assertEqual(task1.state, states.ERROR) + self.assertEqual(task2.state, states.SUCCESS) + + @mock.patch.object(r_exe.RemoteExecutor, 'run_action', MOCK_RUN_AT_TARGET) + def test_default_value_of_safe_rerun(self): + wf_text = """--- + version: '2.0' + + wf: + tasks: + task1: + action: std.noop + """ + + wf_service.create_workflows(wf_text) + + wf_ex = self.engine.start_workflow('wf') + + self.await_workflow_error(wf_ex.id) + + with db_api.transaction(): + wf_ex = db_api.get_workflow_execution(wf_ex.id) + + tasks = wf_ex.task_executions + + self.assertEqual(len(tasks), 1) + + task1 = self._assert_single_item(tasks, name='task1') + + self.assertEqual(task1.state, states.ERROR) diff --git a/mistral/tests/unit/lang/v2/test_tasks.py b/mistral/tests/unit/lang/v2/test_tasks.py index e52a343d0..962b53c83 100644 --- a/mistral/tests/unit/lang/v2/test_tasks.py +++ b/mistral/tests/unit/lang/v2/test_tasks.py @@ -713,3 +713,24 @@ class TaskSpecValidation(v2_base.WorkflowSpecValidationTestCase): changes=overlay, expect_error=expect_error ) + + def test_safe_rerurn(self): + tests = [ + ({'safe-rerun': True}, False), + ({'safe-rerun': False}, False), + ({'safe-rerun': '<% false %>'}, False), + ({'safe-rerun': '<% true %>'}, False), + ({'safe-rerun': '<% * %>'}, True), + ({'safe-rerun': None}, True) + ] + + for default, expect_error in tests: + overlay = {'test': {'task-defaults': {}}} + + utils.merge_dicts(overlay['test']['task-defaults'], default) + + self._parse_dsl_spec( + add_tasks=True, + changes=overlay, + expect_error=expect_error + ) diff --git a/mistral/tests/unit/lang/v2/test_workflows.py b/mistral/tests/unit/lang/v2/test_workflows.py index e5027ab70..48019f5e4 100644 --- a/mistral/tests/unit/lang/v2/test_workflows.py +++ b/mistral/tests/unit/lang/v2/test_workflows.py @@ -383,7 +383,13 @@ class WorkflowSpecValidation(base.WorkflowSpecValidationTestCase): ({'concurrency': '{{ * }}'}, True), ({'concurrency': -10}, True), ({'concurrency': 10.0}, True), - ({'concurrency': '10'}, True) + ({'concurrency': '10'}, True), + ({'safe-rerun': True}, False), + ({'safe-rerun': False}, False), + ({'safe-rerun': '<% false %>'}, False), + ({'safe-rerun': '<% true %>'}, False), + ({'safe-rerun': '<% * %>'}, True), + ({'safe-rerun': None}, True) ] for default, expect_error in tests: diff --git a/releasenotes/notes/safe-rerun-in-task-defaults-87a4cbe12558bc6d.yaml b/releasenotes/notes/safe-rerun-in-task-defaults-87a4cbe12558bc6d.yaml new file mode 100644 index 000000000..66439ff1e --- /dev/null +++ b/releasenotes/notes/safe-rerun-in-task-defaults-87a4cbe12558bc6d.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added 'safe-rerun' policy to task-defaults section