# Copyright 2015 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from mistral.db.v2 import api as db_api from mistral.db.v2.sqlalchemy import models from mistral import exceptions as exc from mistral.lang import parser as spec_parser from mistral.services import workflows as wf_service from mistral.tests.unit import base from mistral.workflow import direct_workflow as d_wf from mistral.workflow import states class DirectWorkflowControllerTest(base.DbTestCase): def _prepare_test(self, wf_text): wfs = wf_service.create_workflows(wf_text) wf_spec = spec_parser.get_workflow_spec_by_definition_id( wfs[0].id, wfs[0].updated_at ) wf_ex = models.WorkflowExecution( id='1-2-3-4', spec=wf_spec.to_dict(), state=states.RUNNING, workflow_id=wfs[0].id, input={}, params={}, context={} ) self.wf_ex = wf_ex self.wf_spec = wf_spec return wf_ex def _create_task_execution(self, name, state): tasks_spec = self.wf_spec.get_tasks() task_ex = models.TaskExecution( id=self.getUniqueString('id'), name=name, spec=tasks_spec[name].to_dict(), state=state ) self.wf_ex.task_executions.append(task_ex) return task_ex @mock.patch.object(db_api, 'get_workflow_execution') @mock.patch.object(db_api, 'get_task_execution') def test_continue_workflow(self, get_task_execution, get_workflow_execution): wf_text = """--- version: '2.0' wf: type: direct tasks: task1: action: std.echo output="Hey" publish: res1: <% $.task1 %> on-complete: - task2: <% $.res1 = 'Hey' %> - task3: <% $.res1 = 'Not Hey' %> task2: action: std.echo output="Hi" task3: action: std.echo output="Hoy" """ wf_ex = self._prepare_test(wf_text) get_workflow_execution.return_value = wf_ex wf_ctrl = d_wf.DirectWorkflowController(wf_ex) # Workflow execution is in initial step. No running tasks. cmds = wf_ctrl.continue_workflow() self.assertEqual(1, len(cmds)) cmd = cmds[0] self.assertIs(wf_ctrl.wf_ex, cmd.wf_ex) self.assertIsNotNone(cmd.task_spec) self.assertEqual('task1', cmd.task_spec.get_name()) self.assertEqual(states.RUNNING, self.wf_ex.state) # Assume that 'task1' completed successfully. task1_ex = self._create_task_execution('task1', states.SUCCESS) task1_ex.published = {'res1': 'Hey'} get_task_execution.return_value = task1_ex task1_ex.action_executions.append( models.ActionExecution( name='std.echo', workflow_name='wf', state=states.SUCCESS, output={'result': 'Hey'}, accepted=True, runtime_context={'index': 0} ) ) cmds = wf_ctrl.continue_workflow() task1_ex.processed = True self.assertEqual(1, len(cmds)) self.assertEqual('task2', cmds[0].task_spec.get_name()) self.assertEqual(states.RUNNING, self.wf_ex.state) self.assertEqual(states.SUCCESS, task1_ex.state) # Now assume that 'task2' completed successfully. task2_ex = self._create_task_execution('task2', states.SUCCESS) task2_ex.action_executions.append( models.ActionExecution( name='std.echo', workflow_name='wf', state=states.SUCCESS, output={'result': 'Hi'}, accepted=True ) ) cmds = wf_ctrl.continue_workflow() task2_ex.processed = True self.assertEqual(0, len(cmds)) def test_continue_workflow_no_start_tasks(self): wf_text = """--- version: '2.0' wf: description: > Invalid workflow that doesn't have start tasks (tasks with no inbound connections). type: direct tasks: task1: on-complete: task2 task2: on-complete: task1 """ self.assertRaises(exc.DSLParsingException, self._prepare_test, wf_text)