def test_get_task(self): inputs = {'a': 123} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) task_name = 'task1' current_task = {'id': task_name, 'name': task_name} expected_ctx = {'a': 123, 'b': False, '__current_task': current_task} task = conductor.get_task(task_name) self.assertEqual(task['id'], task_name) self.assertEqual(task['name'], task_name) self.assertDictEqual(task['ctx'], expected_ctx) conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) task_name = 'task2' current_task = {'id': task_name, 'name': task_name} expected_ctx = { 'a': 123, 'b': False, 'c': 'xyz', '__current_task': current_task } task = conductor.get_task(task_name) self.assertEqual(task['id'], task_name) self.assertEqual(task['name'], task_name) self.assertDictEqual(task['ctx'], expected_ctx)
def test_update_task_flow(self): conductor = self._prep_conductor(state=states.RUNNING) conductor.update_task_flow('task1', events.ActionExecutionEvent(states.RUNNING)) expected_task_flow_item = {'id': 'task1', 'state': 'running', 'ctx': 0} self.assertEqual(conductor._get_task_flow_idx('task1'), 0) self.assertDictEqual(conductor.get_task_flow_entry('task1'), expected_task_flow_item) ac_ex_event = events.ActionExecutionEvent(states.SUCCEEDED, result='foobar') conductor.update_task_flow('task1', ac_ex_event) expected_task_flow_item = { 'id': 'task1', 'state': 'succeeded', 'task2__0': True, 'task5__0': True, 'ctx': 0 } self.assertEqual(conductor._get_task_flow_idx('task1'), 0) self.assertDictEqual(conductor.get_task_flow_entry('task1'), expected_task_flow_item)
def test_update_invalid_state_to_task_flow_item(self): conductor = self._prep_conductor(state=states.RUNNING) self.assertRaises(exc.InvalidState, events.ActionExecutionEvent, 'foobar') self.assertRaises(TypeError, conductor.update_task_flow, 'task1', 'foobar') # When transition is not valid, the task state is not changed. For the test case below, # the state change from requested to succeeded is not a valid transition. conductor.update_task_flow( 'task1', events.ActionExecutionEvent(states.REQUESTED)) expected_task_flow_item = { 'id': 'task1', 'state': 'requested', 'ctx': 0 } self.assertEqual(conductor._get_task_flow_idx('task1'), 0) self.assertDictEqual(conductor.get_task_flow_entry('task1'), expected_task_flow_item) conductor.update_task_flow( 'task1', events.ActionExecutionEvent(states.SUCCEEDED)) expected_task_flow_item = { 'id': 'task1', 'state': 'requested', 'ctx': 0 } self.assertEqual(conductor._get_task_flow_idx('task1'), 0) self.assertDictEqual(conductor.get_task_flow_entry('task1'), expected_task_flow_item)
def test_get_next_tasks_from_staged(self): inputs = {'a': 123} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) next_task_name = 'task1' next_task_spec = conductor.spec.tasks.get_task(next_task_name) expected_ctx_val = {'a': 123, 'b': False} expected_task = self.format_task_item(next_task_name, expected_ctx_val, next_task_spec) self.assert_task_list(conductor.get_next_tasks(), [expected_task]) for i in range(1, 5): task_name = 'task' + str(i) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) next_task_name = 'task' + str(i + 1) next_task_spec = conductor.spec.tasks.get_task(next_task_name) expected_ctx_val = {'a': 123, 'b': False, 'c': 'xyz'} expected_task = self.format_task_item(next_task_name, expected_ctx_val, next_task_spec) self.assert_task_list(conductor.get_next_tasks(), [expected_task])
def test_get_next_tasks_when_graph_abended(self): inputs = {'a': 123} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) task_name = 'task1' next_task_name = 'task2' next_task_spec = conductor.spec.tasks.get_task(next_task_name) ctx_value = {'a': 123, 'b': False, 'c': 'xyz'} conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) expected_tasks = [ self.format_task_item(next_task_name, ctx_value, next_task_spec) ] self.assert_task_list(conductor.get_next_tasks(task_name), expected_tasks) expected_tasks = [ self.format_task_item(next_task_name, ctx_value, next_task_spec) ] self.assert_task_list(conductor.get_next_tasks(task_name), expected_tasks) conductor.request_workflow_state(states.FAILED) self.assertListEqual(conductor.get_next_tasks(task_name), [])
def test_get_task_transition_contexts(self): inputs = {'a': 123, 'b': True} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) # Use task1 to get context for task2 that is staged by not yet running. conductor.update_task_flow('task1', events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( 'task1', events.ActionExecutionEvent(states.SUCCEEDED)) task2_in_ctx = { 'srcs': [0], 'value': dx.merge_dicts(copy.deepcopy(inputs), {'c': 'xyz'}) } expected_contexts = {'task2__0': task2_in_ctx} self.assertDictEqual(conductor.get_task_transition_contexts('task1'), expected_contexts) # Use task1 to get context for task2 that is alstaged running. conductor.update_task_flow('task2', events.ActionExecutionEvent(states.RUNNING)) task2_in_ctx = { 'srcs': [0], 'value': dx.merge_dicts(copy.deepcopy(inputs), {'c': 'xyz'}) } expected_contexts = {'task2__0': task2_in_ctx} self.assertDictEqual(conductor.get_task_transition_contexts('task1'), expected_contexts) # Use task2 to get context for task3 that is not staged yet. self.assertDictEqual(conductor.get_task_transition_contexts('task2'), {}) # Use task3 that is not yet staged to get context. self.assertRaises(exc.InvalidTaskFlowEntry, conductor.get_task_transition_contexts, 'task3')
def test_task_transition_publish_seq_ref_error(self): wf_def = """ version: 1.0 description: A basic branching workflow. vars: - foobar: fubar tasks: task1: action: core.noop next: - when: <% succeeded() %> publish: - x: 123 - y: <% ctx().x %> - z: <% ctx().y.value %> do: task2 task2: action: core.noop next: - when: <% succeeded() %> publish: - var2: 123 """ expected_errors = [ { 'type': 'error', 'message': ( 'YaqlEvaluationException: Unable to evaluate expression ' '\'<% ctx().y.value %>\'. NoFunctionRegisteredException: ' 'Unknown function "#property#value"' ), 'task_id': 'task1', 'task_transition_id': 'task2__0' } ] spec = specs.WorkflowSpec(wf_def) self.assertDictEqual(spec.inspect(), {}) conductor = conducting.WorkflowConductor(spec) conductor.request_workflow_state(states.RUNNING) # The workflow should fail on completion of task1 while evaluating task transition. task_name = 'task1' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.SUCCEEDED)) # The workflow should fail with the expected errors. self.assertEqual(conductor.get_workflow_state(), states.FAILED) actual_errors = sorted(conductor.errors, key=lambda x: x.get('task_id', None)) self.assertListEqual(actual_errors, expected_errors) self.assertNotIn('task2', conductor.flow.staged) self.assertListEqual(conductor.get_next_tasks(), [])
def test_set_workflow_paused_when_no_active_tasks(self): inputs = {'a': 123} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) task_name = 'task1' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) conductor.request_workflow_state(states.PAUSED) self.assertEqual(conductor.get_workflow_state(), states.PAUSED)
def test_runtime_function_of_graph_size(self): num_tasks = 100 conductor = self._prep_conductor(num_tasks, state=states.RUNNING) for i in range(1, num_tasks + 1): task_name = 't' + str(i) conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.SUCCEEDED)) self.assertEqual(conductor.get_workflow_state(), states.SUCCEEDED)
def test_get_workflow_output_when_workflow_incomplete(self): inputs = {'a': 123, 'b': True} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) for i in range(1, 5): task_name = 'task' + str(i) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) self.assertEqual(conductor.get_workflow_state(), states.RUNNING) self.assertIsNone(conductor.get_workflow_output())
def test_init_task_with_no_action(self): wf_def = """ version: 1.0 tasks: task1: next: - publish: xyz=123 do: task2 task2: action: core.noop """ spec = specs.WorkflowSpec(wf_def) conductor = conducting.WorkflowConductor(spec) conductor.request_workflow_state(states.RUNNING) # Process task1. next_task_name = 'task1' next_task_spec = conductor.spec.tasks.get_task(next_task_name) expected_ctx_value = {} expected_tasks = [ self.format_task_item(next_task_name, expected_ctx_value, next_task_spec) ] self.assert_task_list(conductor.get_next_tasks(), expected_tasks) task_name = 'task1' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) # Process task2. next_task_name = 'task2' next_task_spec = conductor.spec.tasks.get_task(next_task_name) expected_ctx_value = {'xyz': 123} expected_tasks = [ self.format_task_item(next_task_name, expected_ctx_value, next_task_spec) ] self.assert_task_list(conductor.get_next_tasks(task_name), expected_tasks) task_name = 'task2' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) self.assertEqual(conductor.get_workflow_state(), states.SUCCEEDED)
def test_get_next_tasks_with_task_input_error(self): wf_def = """ version: 1.0 description: A basic branching workflow. vars: - foobar: fubar tasks: task1: action: core.noop next: - when: <% succeeded() %> do: task2 task2: action: core.noop input: var_x: <% ctx().foobar.fubar %> """ expected_errors = [ { 'type': 'error', 'message': ( 'YaqlEvaluationException: Unable to evaluate expression ' '\'<% ctx().foobar.fubar %>\'. NoFunctionRegisteredException: ' 'Unknown function "#property#fubar"' ), 'task_id': 'task2' } ] spec = specs.WorkflowSpec(wf_def) self.assertDictEqual(spec.inspect(), {}) conductor = conducting.WorkflowConductor(spec) conductor.request_workflow_state(states.RUNNING) # Manually complete task1. task_name = 'task1' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.SUCCEEDED)) # The get_next_tasks method should not return any tasks. self.assertListEqual(conductor.get_next_tasks(), []) # The workflow should fail with the expected errors. self.assertEqual(conductor.get_workflow_state(), states.FAILED) actual_errors = sorted(conductor.errors, key=lambda x: x.get('task_id', None)) self.assertListEqual(actual_errors, expected_errors)
def request_next_tasks(task_ex_id): task_ex_db = wf_db_access.TaskExecution.get_by_id(task_ex_id) # Return if task execution is not complete.. if task_ex_db.status not in states.COMPLETED_STATES: return # Update task flow if task execution is completed. conductor, wf_ex_db = refresh_conductor(task_ex_db.workflow_execution) ac_ex_event = events.ActionExecutionEvent(task_ex_db.status, result=task_ex_db.result) conductor.update_task_flow(task_ex_db.task_id, ac_ex_event) # Identify the list of next set of tasks. next_tasks = conductor.get_next_tasks(task_ex_db.task_id) # If there is no new tasks, update execution records to handle possible completion. if not next_tasks: # Update workflow execution and related liveaction and action execution. update_execution_records(wf_ex_db, conductor) # Iterate while there are next tasks identified for processing. In the case for # task with no action execution defined, the task execution will complete # immediately with a new set of tasks available. while next_tasks: # Mark the tasks as running in the task flow before actual task execution. for task in next_tasks: ac_ex_event = events.ActionExecutionEvent(states.RUNNING) conductor.update_task_flow(task['id'], ac_ex_event) # Update workflow execution and related liveaction and action execution. update_execution_records(wf_ex_db, conductor) # Request task execution for the tasks. for task in next_tasks: try: task_id, task_spec, task_ctx = task['id'], task['spec'], task[ 'ctx'] st2_ctx = {'execution_id': wf_ex_db.action_execution} request_task_execution(wf_ex_db, task_id, task_spec, task_ctx, st2_ctx) except Exception as e: fail_workflow_execution(str(wf_ex_db.id), e, task_id=task['id']) return # Identify the next set of tasks to execute. conductor, wf_ex_db = refresh_conductor(str(wf_ex_db.id)) next_tasks = conductor.get_next_tasks()
def test_get_workflow_output_when_workflow_succeeded(self): inputs = {'a': 123, 'b': True} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) for i in range(1, 6): task_name = 'task' + str(i) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) expected_output = {'data': {'a': 123, 'b': True, 'c': 'xyz'}} self.assertEqual(conductor.get_workflow_state(), states.SUCCEEDED) self.assertDictEqual(conductor.get_workflow_output(), expected_output)
def assert_data_flow(self, input_value): inputs = {'a1': input_value} expected_output = {'a5': inputs['a1'], 'b5': inputs['a1']} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) for i in range(1, len(conductor.spec.tasks) + 1): task_name = 'task' + str(i) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) self.assertEqual(conductor.get_workflow_state(), states.SUCCEEDED) self.assertDictEqual(conductor.get_workflow_output(), expected_output)
def update_task_state(task_ex_id, ac_ex_status, ac_ex_result=None, ac_ex_ctx=None, publish=True): # Return if action execution status is not in the list of statuses to process. statuses_to_process = (copy.copy(ac_const.LIVEACTION_COMPLETED_STATES) + [ ac_const.LIVEACTION_STATUS_PAUSED, ac_const.LIVEACTION_STATUS_PENDING ]) if ac_ex_status not in statuses_to_process: return # Refresh records task_ex_db = wf_db_access.TaskExecution.get_by_id(task_ex_id) conductor, wf_ex_db = refresh_conductor(task_ex_db.workflow_execution) wf_ac_ex_id = wf_ex_db.action_execution # Update task flow if task execution is completed or paused. msg = '[%s] Publish task "%s", route "%s", with status "%s" to conductor.' LOG.info(msg, wf_ac_ex_id, task_ex_db.task_id, str(task_ex_db.task_route), task_ex_db.status) ac_ex_event = events.ActionExecutionEvent(ac_ex_status, result=ac_ex_result, context=ac_ex_ctx) LOG.debug('[%s] %s', wf_ac_ex_id, conductor.serialize()) conductor.update_task_state(task_ex_db.task_id, task_ex_db.task_route, ac_ex_event) # Update workflow execution and related liveaction and action execution. update_execution_records(wf_ex_db, conductor, update_lv_ac_on_statuses=statuses_to_process, pub_lv_ac=publish, pub_ac_ex=publish)
def assert_workflow_state(self, wf_name, mock_flow, expected_wf_states, conductor=None): if not conductor: wf_def = self.get_wf_def(wf_name) wf_spec = self.spec_module.instantiate(wf_def) conductor = conducting.WorkflowConductor(wf_spec) conductor.request_workflow_state(states.RUNNING) for task_flow_entry, expected_wf_state in zip(mock_flow, expected_wf_states): task_id = task_flow_entry['id'] task_state = task_flow_entry['state'] ac_ex_event = events.ActionExecutionEvent(task_state) conductor.update_task_flow(task_id, ac_ex_event) err_ctx = ('Workflow state "%s" is not the expected state "%s". ' 'Updated task "%s" with state "%s".' % (conductor.get_workflow_state(), expected_wf_state, task_id, task_state)) self.assertEqual(conductor.get_workflow_state(), expected_wf_state, err_ctx) return conductor
def forward_task_statuses(self, conductor, task_id, statuses, item_ids=None, results=None, accumulated_results=None, route=0): if item_ids is None: item_ids = [] if results is None: results = [] if accumulated_results is None: accumulated_results = [] status_changes = six.moves.zip_longest(statuses, item_ids, results, accumulated_results) for status, item_id, result, accumulated_result in status_changes: ac_ex_event = (events.ActionExecutionEvent(status) if item_id is None or item_id < 0 else events.TaskItemActionExecutionEvent( item_id, status)) if item_id and item_id >= 0 and accumulated_result is not None: ac_ex_event.accumulated_result = accumulated_result if result: ac_ex_event.result = result conductor.update_task_state(task_id, route, ac_ex_event)
def test_bad_current_task_state(self): task_flow_entry = {'id': 'task1', 'ctx': 0, 'state': 'mock'} ac_ex_event = events.ActionExecutionEvent(states.SUCCEEDED) self.assertRaises(exc.InvalidTaskStateTransition, machines.TaskStateMachine.process_event, None, task_flow_entry, ac_ex_event)
def prep_wf_ex(self, wf_ex_db): data = { 'spec': wf_ex_db.spec, 'graph': wf_ex_db.graph, 'state': wf_ex_db.status, 'flow': wf_ex_db.flow, 'context': wf_ex_db.context, 'input': wf_ex_db.input, 'output': wf_ex_db.output, 'errors': wf_ex_db.errors } conductor = conducting.WorkflowConductor.deserialize(data) conductor.request_workflow_state(wf_lib_states.RUNNING) for task in conductor.get_next_tasks(): ac_ex_event = events.ActionExecutionEvent(wf_lib_states.RUNNING) conductor.update_task_flow(task['id'], ac_ex_event) wf_ex_db.status = conductor.get_workflow_state() wf_ex_db.flow = conductor.flow.serialize() wf_ex_db = wf_db_access.WorkflowExecution.update(wf_ex_db, publish=False) return wf_ex_db
def forward_task_statuses(self, conductor, task_id, status_changes, route=0, result=None): for status in status_changes: ac_ex_event = events.ActionExecutionEvent(status) if result is not None and status in statuses.COMPLETED_STATUSES: ac_ex_event.result = result conductor.update_task_state(task_id, route, ac_ex_event)
def test_app_ctx_references(self): app_ctx = {'x': 'foobar', 'y': 'fubar', 'z': 'phobar'} wf_def = """ version: 1.0 input: - a: <% ctx().x %> vars: - b: <% ctx().y %> output: - x: <% ctx().a %> - y: <% ctx().b %> - z: <% ctx().z %> tasks: task1: action: core.noop """ expected_output = app_ctx expected_errors = [] spec = specs.WorkflowSpec(wf_def) self.assertDictEqual(spec.inspect(app_ctx=app_ctx), {}) # Run the workflow. conductor = conducting.WorkflowConductor(spec, context=app_ctx) conductor.request_workflow_state(states.RUNNING) self.assertEqual(conductor.get_workflow_state(), states.RUNNING) self.assertListEqual(conductor.errors, expected_errors) # Complete tasks task_name = 'task1' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) # Check workflow status and output. self.assertEqual(conductor.get_workflow_state(), states.SUCCEEDED) self.assertListEqual(conductor.errors, expected_errors) self.assertDictEqual(conductor.get_workflow_output(), expected_output)
def test_get_next_tasks_repeat_by_task_name(self): inputs = {'a': 123} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) self.assertEqual(len(conductor.get_next_tasks()), 1) conductor.update_task_flow('task1', events.ActionExecutionEvent(states.RUNNING)) self.assertEqual(len(conductor.get_next_tasks('task1')), 0) conductor.update_task_flow( 'task1', events.ActionExecutionEvent(states.SUCCEEDED)) next_tasks = conductor.get_next_tasks('task1') self.assertEqual(len(next_tasks), 1) self.assertEqual(next_tasks[0]['name'], 'task2') conductor.update_task_flow('task2', events.ActionExecutionEvent(states.RUNNING)) self.assertEqual(len(conductor.get_next_tasks('task2')), 0)
def test_set_workflow_canceled_when_has_active_tasks(self): inputs = {'a': 123} conductor = self._prep_conductor(inputs=inputs, state=states.RUNNING) task_name = 'task1' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.request_workflow_state(states.CANCELED) self.assertEqual(conductor.get_workflow_state(), states.CANCELING)
def test_workflow_output_seq_ref_error(self): wf_def = """ version: 1.0 output: - x: 123 - y: <% ctx().x %> - z: <% ctx().y.value %> tasks: task1: action: core.noop """ expected_output = { 'x': 123, 'y': 123 } expected_errors = [ { 'type': 'error', 'message': ( 'YaqlEvaluationException: Unable to evaluate expression ' '\'<% ctx().y.value %>\'. NoFunctionRegisteredException: ' 'Unknown function "#property#value"' ) } ] spec = specs.WorkflowSpec(wf_def) self.assertDictEqual(spec.inspect(), {}) conductor = conducting.WorkflowConductor(spec) conductor.request_workflow_state(states.RUNNING) # Manually complete task1. task_name = 'task1' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.SUCCEEDED)) self.assertEqual(conductor.get_workflow_state(), states.FAILED) self.assertListEqual(conductor.errors, expected_errors) self.assertDictEqual(conductor.get_workflow_output(), expected_output)
def test_retry_given_condition_not_satisfied(self): wf_def = """ version: 1.0 description: A basic sequential workflow. tasks: task1: action: core.noop retry: when: <% result().status_code = 400 %> count: 3 next: - when: <% succeeded() %> do: task2 task2: action: core.noop """ # Setup workflow conductor. spec = native_specs.WorkflowSpec(wf_def) conductor = conducting.WorkflowConductor(spec) conductor.request_workflow_status(statuses.RUNNING) # Forward task1 from running to succeeded. task_id = "task1" route = 0 task_result = {"status_code": 200} ac_ex_event = events.ActionExecutionEvent(statuses.RUNNING) conductor.update_task_state(task_id, route, ac_ex_event) ac_ex_event = events.ActionExecutionEvent(statuses.SUCCEEDED, result=task_result) conductor.update_task_state(task_id, route, ac_ex_event) # Get task state for task1. task_state_entry = conductor.get_task_state_entry(task_id, route) # Set context for task1. current_task_ctx = conductor.make_task_context(task_state_entry, task_result=task_result) # Assert retry is false for task1. self.assertFalse( conductor._evaluate_task_retry(task_state_entry, current_task_ctx))
def test_workflow_output_with_error(self): wf_def = """ version: 1.0 output: - x: 123 - y: <% ctx().x %> - z: <% ctx().y.value %> tasks: task1: action: core.noop """ expected_output = {'x': 123, 'y': 123} expected_errors = [{ 'type': 'error', 'message': ('YaqlEvaluationException: Unable to evaluate expression ' '\'<% ctx().y.value %>\'. NoFunctionRegisteredException: ' 'Unknown function "#property#value"') }] spec = specs.WorkflowSpec(wf_def) self.assertDictEqual(spec.inspect(), {}) # Run the workflow and keep it running. conductor = conducting.WorkflowConductor(spec) conductor.request_workflow_state(states.RUNNING) task_name = 'task1' conductor.update_task_flow(task_name, events.ActionExecutionEvent(states.RUNNING)) # Cancels the workflow and complete task1. conductor.request_workflow_state(states.CANCELING) conductor.update_task_flow( task_name, events.ActionExecutionEvent(states.SUCCEEDED)) # Check workflow status is not changed to failed given the output error. self.assertEqual(conductor.get_workflow_state(), states.CANCELED) self.assertListEqual(conductor.errors, expected_errors) self.assertDictEqual(conductor.get_workflow_output(), expected_output)
def test_retry_default_condition_satisfied(self): wf_def = """ version: 1.0 description: A basic sequential workflow. tasks: task1: action: core.noop retry: count: 3 next: - when: <% succeeded() %> do: task2 task2: action: core.noop """ # Setup workflow conductor. spec = native_specs.WorkflowSpec(wf_def) conductor = conducting.WorkflowConductor(spec) conductor.request_workflow_status(statuses.RUNNING) # Forward task1 from running to failed. task_id = "task1" route = 0 ac_ex_event = events.ActionExecutionEvent(statuses.RUNNING) conductor.update_task_state(task_id, route, ac_ex_event) ac_ex_event = events.ActionExecutionEvent(statuses.FAILED) conductor.update_task_state(task_id, route, ac_ex_event) # Get task state for task1. task_state_entry = conductor.get_task_state_entry(task_id, route) # Mock the task status. task_state_entry["status"] = statuses.FAILED # Set context for task1. current_task_ctx = conductor.make_task_context(task_state_entry, task_result=None) # Assert retry is true for task1. self.assertTrue( conductor._evaluate_task_retry(task_state_entry, current_task_ctx))
def test_update_task_state_for_not_ready_task(self): conductor = self._prep_conductor(status=statuses.RUNNING) self.assertRaises( exc.InvalidTaskStateEntry, conductor.update_task_state, "task2", 0, events.ActionExecutionEvent(statuses.RUNNING), )
def test_update_task_state_for_nonexistent_task(self): conductor = self._prep_conductor(status=statuses.RUNNING) self.assertRaises( exc.InvalidTask, conductor.update_task_state, "task999", 0, events.ActionExecutionEvent(statuses.RUNNING), )