From ad733d3112e4300bd9e2b9b55b8eaa0547a94619 Mon Sep 17 00:00:00 2001 From: Adriano Petrich Date: Fri, 24 Nov 2017 14:53:10 +0000 Subject: [PATCH] Add executions yaql filter This allows for filtering or executions Implements: blueprint mistral-workflow-executions-yaql-function Change-Id: I8d41c662d8659375750b52f7510e9a35f8b42f93 --- doc/source/user/dsl_v2.rst | 62 +++++++ .../unit/expressions/test_jinja_expression.py | 155 ++++++++++++++++++ mistral/utils/expression_utils.py | 53 +++++- setup.cfg | 1 + 4 files changed, 270 insertions(+), 1 deletion(-) diff --git a/doc/source/user/dsl_v2.rst b/doc/source/user/dsl_v2.rst index 9173fe6d9..07841d865 100644 --- a/doc/source/user/dsl_v2.rst +++ b/doc/source/user/dsl_v2.rst @@ -1394,6 +1394,68 @@ Execution info is available by **execution()**. It contains information about execution itself such as **id**, **wf_spec**, **input** and **start_params**. +Executions function +''''''''''''''''''' + +Signature: + **executions(id=null, root_execution_id=null, state=null, + from_time=null, to_time=null)** + +Description: + + This function allows users to filter all executions by execution id, + root_execution_id ,state and/or created_at time. + +Parameters: + + #. **id** - If provided will return a list of executions with that id. + Otherwise it will return all executions that match the other + parameters. *Optional.* + #. **root_execution_id** - Similar to id above, if provided will return + a list of executions with that root_execution_id. Otherwise it will + return all executions that match the other parameters. *Optional.* + False by default. + #. **state** - If provided, the executions will be filtered by their + current state. If state isn't provided, all executions that match the + other parameters will be returned . *Optional.* + #. **from_time** - If provided, the executions will be filtered by their + created_at time being greater or equal to the from_time parameter. + If from_time isn't provided, all executions that match the + other parameters will be returned. from_time parameter can be provided + in the format *YYYY-MM-DD hh:mm:ss* + *Optional.* + #. **to_time** - If provided, the executions will be filtered by their + created_at time being less than to the from_time parameter (less than but + not less than equal as the from_time parameter does) + If to_time isn't provided, all executions that match the + other parameters will be returned. to_time parameter can be provided + in the format *YYYY-MM-DD hh:mm:ss* + *Optional.* + +Example: + +Workflow definition: + +.. code-block:: mistral + + --- + version: "v2.0" + wf: + tasks: + task: + action: std.noop + publish: + all_executions_yaql: <% executions() %> + all_child_executions_of_this_execution: "{{ executions(root_execution_id=execution().id) }}" + + all_executions_in_error_yaql: <% executions(null, null, ERROR) %> + all_executions_in_error_jinja: "{{ executions(None, None, 'ERROR') }}" + all_executions_in_error_yaql_with_kw: <% executions(state => ERROR) %> + all_executions_in_error_jinja_with_kw: "{{ executions(state='ERROR') }}" + + all_executions_filtered_date_jinja: "{{ executions(to_time="2016-12-01 15:01:00") }}" + + Environment ^^^^^^^^^^^ diff --git a/mistral/tests/unit/expressions/test_jinja_expression.py b/mistral/tests/unit/expressions/test_jinja_expression.py index fa24b93c6..c18acb026 100644 --- a/mistral/tests/unit/expressions/test_jinja_expression.py +++ b/mistral/tests/unit/expressions/test_jinja_expression.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime import mock +from mistral.db.v2.sqlalchemy import api as db_api from mistral import exceptions as exc from mistral.expressions import jinja_expression as expr from mistral.tests.unit import base @@ -36,6 +38,38 @@ SERVERS = { ] } +WF_EXECS = [ + { + 'spec': {}, + 'id': "one", + 'start_params': {'task': 'my_task1'}, + 'state': 'IDLE', + 'state_info': "Running...", + 'created_at': datetime.datetime(2016, 12, 1, 15, 0, 0), + 'updated_at': None, + 'context': None, + 'task_id': None, + 'trust_id': None, + 'description': None, + 'output': None + }, + { + 'spec': {}, + 'id': "two", + 'root_execution_id': "one", + 'start_params': {'task': 'my_task1'}, + 'state': 'RUNNING', + 'state_info': "Running...", + 'created_at': datetime.datetime(2016, 12, 1, 15, 1, 0), + 'updated_at': None, + 'context': {'image_id': '123123'}, + 'task_id': None, + 'trust_id': None, + 'description': None, + 'output': None + } +] + class JinjaEvaluatorTest(base.BaseTest): def setUp(self): @@ -314,6 +348,127 @@ class JinjaEvaluatorTest(base.BaseTest): 'updated_at': wf_ex.updated_at.isoformat(' ') }, result) + def test_executions(self): + with db_api.transaction(): + created0 = db_api.create_workflow_execution(WF_EXECS[0]) + created1 = db_api.create_workflow_execution(WF_EXECS[1]) + + ctx = { + '__execution': { + 'id': 'some' + } + } + + result = self._evaluator.evaluate('_|executions()', ctx) + + self.assertEqual([created0, created1], result) + db_api.rollback_tx() + + def test_executions_id_filter(self): + with db_api.transaction(): + created0 = db_api.create_workflow_execution(WF_EXECS[0]) + created1 = db_api.create_workflow_execution(WF_EXECS[1]) + + ctx = { + '__execution': { + 'id': 'some' + } + } + + result = self._evaluator.evaluate('_|executions("one")', ctx) + + self.assertEqual([created0], result) + + result = self._evaluator.evaluate( + 'executions(root_execution_id="one") ', ctx + ) + self.assertEqual([created1], result) + db_api.rollback_tx() + + def test_executions_state_filter(self): + with db_api.transaction(): + db_api.create_workflow_execution(WF_EXECS[0]) + created1 = db_api.create_workflow_execution(WF_EXECS[1]) + + ctx = { + '__execution': { + 'id': 'some' + } + } + + result = self._evaluator.evaluate( + '_|executions(state="RUNNING")', ctx + ) + + self.assertEqual([created1], result) + + result = self._evaluator.evaluate( + '_|executions(id="one", state="RUNNING")', ctx + ) + + self.assertEqual([], result) + db_api.rollback_tx() + + def test_executions_from_time_filter(self): + with db_api.transaction(): + created0 = db_api.create_workflow_execution(WF_EXECS[0]) + created1 = db_api.create_workflow_execution(WF_EXECS[1]) + + ctx = { + '__execution': { + 'id': 'some' + } + } + + result = self._evaluator.evaluate( + '_|executions(from_time="2000-01-01")', ctx + ) + + self.assertEqual([created0, created1], result) + + result = self._evaluator.evaluate( + '_|executions(from_time="2016-12-01 15:01:00")', ctx + ) + + self.assertEqual([created1], result) + + result = self._evaluator.evaluate( + '_|executions(id="one", from_time="2016-12-01 15:01:00")', ctx + ) + + self.assertEqual([], result) + db_api.rollback_tx() + + def test_executions_to_time_filter(self): + with db_api.transaction(): + created0 = db_api.create_workflow_execution(WF_EXECS[0]) + created1 = db_api.create_workflow_execution(WF_EXECS[1]) + + ctx = { + '__execution': { + 'id': 'some' + } + } + + result = self._evaluator.evaluate( + '_|executions(to_time="2020-01-01")', ctx + ) + + self.assertEqual([created0, created1], result) + + result = self._evaluator.evaluate( + '_|executions(to_time="2016-12-01 15:01:00")', ctx + ) + + self.assertEqual([created0], result) + + result = self._evaluator.evaluate( + '_|executions(id="two", to_time="2016-12-01 15:01:00")', ctx + ) + + self.assertEqual([], result) + db_api.rollback_tx() + @mock.patch('mistral.db.v2.api.get_workflow_execution') def test_function_execution(self, workflow_execution): wf_ex = mock.MagicMock(return_value={}) diff --git a/mistral/utils/expression_utils.py b/mistral/utils/expression_utils.py index f1d02fe3a..48b38d961 100644 --- a/mistral/utils/expression_utils.py +++ b/mistral/utils/expression_utils.py @@ -104,6 +104,58 @@ def env_(context): return context['__env'] +def executions_(context, + id=None, + root_execution_id=None, + state=None, + from_time=None, + to_time=None + ): + + filter = {} + if id is not None: + filter = utils.filter_utils.create_or_update_filter( + 'id', + id, + "eq", + filter + ) + if root_execution_id is not None: + filter = utils.filter_utils.create_or_update_filter( + 'root_execution_id', + root_execution_id, + "eq", + filter + ) + + if state is not None: + filter = utils.filter_utils.create_or_update_filter( + 'state', + state, + "eq", + filter + ) + + if from_time is not None: + filter = utils.filter_utils.create_or_update_filter( + 'created_at', + from_time, + "gte", + filter + ) + + if to_time is not None: + filter = utils.filter_utils.create_or_update_filter( + 'created_at', + to_time, + "lt", + filter + ) + + wf_executions = db_api.get_workflow_executions(**filter) + return wf_executions + + def execution_(context): wf_ex = db_api.get_workflow_execution(context['__execution']['id']) @@ -251,7 +303,6 @@ def _get_tasks_from_db(workflow_execution_id=None, recursive=False, state=None, def tasks_(context, workflow_execution_id=None, recursive=False, state=None, flat=False): - task_execs = _get_tasks_from_db( workflow_execution_id, recursive, diff --git a/setup.cfg b/setup.cfg index ff1bbcd2f..959bf5358 100644 --- a/setup.cfg +++ b/setup.cfg @@ -84,6 +84,7 @@ mistral.expression.functions = env = mistral.utils.expression_utils:env_ execution = mistral.utils.expression_utils:execution_ + executions = mistral.utils.expression_utils:executions_ global = mistral.utils.expression_utils:global_ json_parse = mistral.utils.expression_utils:json_parse_ json_dump = mistral.utils.expression_utils:json_dump_