def respond(inquiry, response, requester=None): # Set requester to system user is not provided. if not requester: requester = cfg.CONF.system_user.user # Retrieve the liveaction from the database. liveaction_db = lv_db_access.LiveAction.get_by_id( inquiry.liveaction.get("id")) # Resume the parent workflow first. If the action execution for the inquiry is updated first, # it triggers handling of the action execution completion which will interact with the paused # parent workflow. The resuming logic that is executed here will then race with the completion # of the inquiry action execution, which will randomly result in the parent workflow stuck in # paused state. if liveaction_db.context.get("parent"): LOG.debug('Resuming workflow parent(s) for inquiry "%s".' % str(inquiry.id)) # For action execution under Action Chain workflows, request the entire # workflow to resume. Orquesta handles resume differently and so does not require root # to resume. Orquesta allows for specifc branches to resume while other is paused. When # there is no other paused branches, the conductor will resume the rest of the workflow. resume_target = ( action_service.get_parent_liveaction(liveaction_db) if workflow_service.is_action_execution_under_workflow_context( liveaction_db) else action_service.get_root_liveaction(liveaction_db)) if resume_target.status in action_constants.LIVEACTION_PAUSE_STATES: action_service.request_resume(resume_target, requester) # Succeed the liveaction and update result with the inquiry response. LOG.debug('Updating response for inquiry "%s".' % str(inquiry.id)) result = fast_deepcopy_dict(inquiry.result) result["response"] = response liveaction_db = action_utils.update_liveaction_status( status=action_constants.LIVEACTION_STATUS_SUCCEEDED, end_timestamp=date_utils.get_datetime_utc_now(), runner_info=sys_info_utils.get_process_info(), result=result, liveaction_id=str(liveaction_db.id), ) # Sync the liveaction with the corresponding action execution. execution_service.update_execution(liveaction_db) # Invoke inquiry post run to trigger a callback to parent workflow. LOG.debug('Invoking post run for inquiry "%s".' % str(inquiry.id)) runner_container = container.get_runner_container() action_db = action_utils.get_action_by_ref(liveaction_db.action) runnertype_db = action_utils.get_runnertype_by_name( action_db.runner_type["name"]) runner = runner_container._get_runner(runnertype_db, action_db, liveaction_db) runner.post_run(status=action_constants.LIVEACTION_STATUS_SUCCEEDED, result=result) return liveaction_db
def process(self, execution_db): execution_id = str(execution_db.id) extra = {'execution': execution_db} LOG.debug('Processing action execution "%s".', execution_id, extra=extra) if execution_db.status not in LIVEACTION_COMPLETED_STATES: msg = 'Skip action execution "%s" because state "%s" is not in a completed state.' LOG.debug(msg % (str(execution_db.id), execution_db.status), extra=extra) return # Get the corresponding liveaction record. liveaction_db = LiveAction.get_by_id(execution_db.liveaction['id']) # If the action execution is executed under an orquesta workflow, policies for the # action execution will be applied by the workflow engine. A policy may affect the # final state of the action execution thereby impacting the state of the workflow. if not workflow_service.is_action_execution_under_workflow_context( execution_db): policy_service.apply_post_run_policies(liveaction_db) if liveaction_db.notify is not None: self._post_notify_triggers(liveaction_db=liveaction_db, execution_db=execution_db) self._post_generic_trigger(liveaction_db=liveaction_db, execution_db=execution_db)
def test_run_workflow_action_config_context(self): wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, 'config-context.yaml') wf_input = {} lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta['name'], parameters=wf_input) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_db = wf_db_access.WorkflowExecution.query(action_execution=str(ac_ex_db.id))[0] self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) # Assert task1 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task1'} tk1_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk1_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk1_ex_db.id))[0] tk1_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk1_ac_ex_db.liveaction['id']) self.assertEqual(tk1_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk1_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk1_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.SUCCEEDED) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) # Verify config_context works self.assertEqual(wf_ex_db.output, {'msg': 'value of config key a'})
def test_run_workflow_action_config_context(self): wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, 'config-context.yaml') wf_input = {} lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta['name'], parameters=wf_input) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_db = wf_db_access.WorkflowExecution.query(action_execution=str(ac_ex_db.id))[0] self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) # Assert task1 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task1'} tk1_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk1_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk1_ex_db.id))[0] tk1_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk1_ac_ex_db.liveaction['id']) self.assertEqual(tk1_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk1_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk1_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.SUCCEEDED) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) # Verify config_context works self.assertEqual(wf_ex_db.output, {'msg': 'value of config key a'})
def handle_action_execution_with_instrumentation(ac_ex_db): # Ignore non orquesta workflow executions if not wf_svc.is_action_execution_under_workflow_context(ac_ex_db): return with metrics.CounterWithTimer(key='orquesta.action.executions'): return self.handle_action_execution(ac_ex_db=ac_ex_db)
def process(self, execution_db): execution_id = str(execution_db.id) extra = {'execution': execution_db} LOG.debug('Processing action execution "%s".', execution_id, extra=extra) # Get the corresponding liveaction record. liveaction_db = LiveAction.get_by_id(execution_db.liveaction['id']) if execution_db.status in LIVEACTION_COMPLETED_STATES: # If the action execution is executed under an orquesta workflow, policies for the # action execution will be applied by the workflow engine. A policy may affect the # final state of the action execution thereby impacting the state of the workflow. if not workflow_service.is_action_execution_under_workflow_context( execution_db): with CounterWithTimer(key='notifier.apply_post_run_policies'): policy_service.apply_post_run_policies(liveaction_db) if liveaction_db.notify: with CounterWithTimer(key='notifier.notify_trigger.post'): self._post_notify_triggers(liveaction_db=liveaction_db, execution_db=execution_db) self._post_generic_trigger(liveaction_db=liveaction_db, execution_db=execution_db)
def _execute_workflow(self, wf_name, expected_task_sequence, expected_output, expected_status=wf_states.SUCCEEDED, expected_errors=None): wf_file = wf_name + '.yaml' wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, wf_file) lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta['name']) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_db = wf_db_access.WorkflowExecution.query( action_execution=str(ac_ex_db.id))[0] self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) for task_id in expected_task_sequence: query_filters = { 'workflow_execution': str(wf_ex_db.id), 'task_id': task_id } tk_ex_dbs = wf_db_access.TaskExecution.query(**query_filters) tk_ex_db = sorted( tk_ex_dbs, key=lambda x: x.start_timestamp)[len(tk_ex_dbs) - 1] tk_ac_ex_db = ex_db_access.ActionExecution.query( task_execution=str(tk_ex_db.id))[0] tk_lv_ac_db = lv_db_access.LiveAction.get_by_id( tk_ac_ex_db.liveaction['id']) self.assertEqual(tk_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue( wf_svc.is_action_execution_under_workflow_context(tk_ac_ex_db)) wf_svc.handle_action_execution_completion(tk_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, expected_status) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, expected_status) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, expected_status) # Check workflow output, liveaction result, and action execution result. expected_result = {'output': expected_output} if expected_errors is not None: expected_result['errors'] = expected_errors if expected_output is not None: self.assertDictEqual(wf_ex_db.output, expected_output) self.assertDictEqual(lv_ac_db.result, expected_result) self.assertDictEqual(ac_ex_db.result, expected_result)
def handle_action_execution(self, ac_ex_db): # Exit if action execution is not executed under an orquesta workflow. if not wf_svc.is_action_execution_under_workflow_context(ac_ex_db): return # Get related record identifiers. wf_ex_id = ac_ex_db.context["orquesta"]["workflow_execution_id"] task_ex_id = ac_ex_db.context["orquesta"]["task_execution_id"] # Get execution records for logging purposes. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_id) task_ex_db = wf_db_access.TaskExecution.get_by_id(task_ex_id) msg = 'Action execution "%s" for task "%s" is updated and in "%s" state.' % ( str(ac_ex_db.id), task_ex_db.task_id, ac_ex_db.status, ) wf_svc.update_progress(wf_ex_db, msg) # Skip if task execution is already in completed state. if task_ex_db.status in statuses.COMPLETED_STATUSES: msg = ( 'Action execution "%s" for task "%s", route "%s", is not processed ' 'because task execution "%s" is already in completed state "%s".' % ( str(ac_ex_db.id), task_ex_db.task_id, str(task_ex_db.task_route), str(task_ex_db.id), task_ex_db.status, ) ) wf_svc.update_progress(wf_ex_db, msg) return # Process pending request on the action execution. if ac_ex_db.status == ac_const.LIVEACTION_STATUS_PENDING: wf_svc.handle_action_execution_pending(ac_ex_db) return # Process pause request on the action execution. if ac_ex_db.status == ac_const.LIVEACTION_STATUS_PAUSED: wf_svc.handle_action_execution_pause(ac_ex_db) return # Exit if action execution has not completed yet. if ac_ex_db.status not in ac_const.LIVEACTION_COMPLETED_STATES: return # Apply post run policies. lv_ac_db = lv_db_access.LiveAction.get_by_id(ac_ex_db.liveaction["id"]) pc_svc.apply_post_run_policies(lv_ac_db) # Process completion of the action execution. wf_svc.handle_action_execution_completion(ac_ex_db)
def respond(inquiry, response, requester=None): # Set requester to system user is not provided. if not requester: requester = cfg.CONF.system_user.user # Retrieve the liveaction from the database. liveaction_db = lv_db_access.LiveAction.get_by_id(inquiry.liveaction.get('id')) # Resume the parent workflow first. If the action execution for the inquiry is updated first, # it triggers handling of the action execution completion which will interact with the paused # parent workflow. The resuming logic that is executed here will then race with the completion # of the inquiry action execution, which will randomly result in the parent workflow stuck in # paused state. if liveaction_db.context.get('parent'): LOG.debug('Resuming workflow parent(s) for inquiry "%s".' % str(inquiry.id)) # For action execution under Action Chain and Mistral workflows, request the entire # workflow to resume. Orquesta handles resume differently and so does not require root # to resume. Orquesta allows for specifc branches to resume while other is paused. When # there is no other paused branches, the conductor will resume the rest of the workflow. resume_target = ( action_service.get_parent_liveaction(liveaction_db) if workflow_service.is_action_execution_under_workflow_context(liveaction_db) else action_service.get_root_liveaction(liveaction_db) ) if resume_target.status in action_constants.LIVEACTION_PAUSE_STATES: action_service.request_resume(resume_target, requester) # Succeed the liveaction and update result with the inquiry response. LOG.debug('Updating response for inquiry "%s".' % str(inquiry.id)) result = copy.deepcopy(inquiry.result) result['response'] = response liveaction_db = action_utils.update_liveaction_status( status=action_constants.LIVEACTION_STATUS_SUCCEEDED, end_timestamp=date_utils.get_datetime_utc_now(), runner_info=sys_info_utils.get_process_info(), result=result, liveaction_id=str(liveaction_db.id) ) # Sync the liveaction with the corresponding action execution. execution_service.update_execution(liveaction_db) # Invoke inquiry post run to trigger a callback to parent workflow. LOG.debug('Invoking post run for inquiry "%s".' % str(inquiry.id)) runner_container = container.get_runner_container() action_db = action_utils.get_action_by_ref(liveaction_db.action) runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) runner = runner_container._get_runner(runnertype_db, action_db, liveaction_db) runner.post_run(status=action_constants.LIVEACTION_STATUS_SUCCEEDED, result=result) return liveaction_db
def _execute_workflow(self, wf_name, expected_task_sequence, expected_output, expected_status=wf_statuses.SUCCEEDED, expected_errors=None): wf_file = wf_name + '.yaml' wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, wf_file) lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta['name']) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_db = wf_db_access.WorkflowExecution.query(action_execution=str(ac_ex_db.id))[0] self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) for task_id, route in expected_task_sequence: tk_ex_dbs = wf_db_access.TaskExecution.query( workflow_execution=str(wf_ex_db.id), task_id=task_id, task_route=route ) if len(tk_ex_dbs) <= 0: break tk_ex_db = sorted(tk_ex_dbs, key=lambda x: x.start_timestamp)[len(tk_ex_dbs) - 1] tk_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk_ex_db.id))[0] tk_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk_ac_ex_db.liveaction['id']) self.assertEqual(tk_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk_ac_ex_db)) wf_svc.handle_action_execution_completion(tk_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, expected_status) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, expected_status) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, expected_status) # Check workflow output, liveaction result, and action execution result. expected_result = {'output': expected_output} if expected_errors is not None: expected_result['errors'] = expected_errors if expected_output is not None: self.assertDictEqual(wf_ex_db.output, expected_output) self.assertDictEqual(lv_ac_db.result, expected_result) self.assertDictEqual(ac_ex_db.result, expected_result)
def handle_action_execution(self, ac_ex_db): # Exit if action execution is not executed under an orquesta workflow. if not wf_svc.is_action_execution_under_workflow_context(ac_ex_db): return # Get related record identifiers. wf_ex_id = ac_ex_db.context['orquesta']['workflow_execution_id'] task_ex_id = ac_ex_db.context['orquesta']['task_execution_id'] # Get execution records for logging purposes. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_id) task_ex_db = wf_db_access.TaskExecution.get_by_id(task_ex_id) wf_ac_ex_id = wf_ex_db.action_execution msg = '[%s] Action execution "%s" for task "%s" is updated and in "%s" state.' LOG.info(msg, wf_ac_ex_id, str(ac_ex_db.id), task_ex_db.task_id, ac_ex_db.status) # Skip if task execution is already in completed state. if task_ex_db.status in states.COMPLETED_STATES: LOG.info( '[%s] Action execution "%s" for task "%s" is not processed because ' 'task execution "%s" is already in completed state "%s".', wf_ac_ex_id, str(ac_ex_db.id), task_ex_db.task_id, str(task_ex_db.id), task_ex_db.status) return # Process pending request on the action execution. if ac_ex_db.status == ac_const.LIVEACTION_STATUS_PENDING: wf_svc.handle_action_execution_pending(ac_ex_db) return # Process pause request on the action execution. if ac_ex_db.status == ac_const.LIVEACTION_STATUS_PAUSED: wf_svc.handle_action_execution_pause(ac_ex_db) return # Exit if action execution has not completed yet. if ac_ex_db.status not in ac_const.LIVEACTION_COMPLETED_STATES: return # Apply post run policies. lv_ac_db = lv_db_access.LiveAction.get_by_id(ac_ex_db.liveaction['id']) pc_svc.apply_post_run_policies(lv_ac_db) # Process completion of the action execution. wf_svc.handle_action_execution_completion(ac_ex_db)
def _execute_workflow(self, wf_name, expected_output): wf_file = wf_name + '.yaml' wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, wf_file) lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta['name']) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_db = wf_db_access.WorkflowExecution.query( action_execution=str(ac_ex_db.id))[0] self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) # Assert task1 is already completed. query_filters = { 'workflow_execution': str(wf_ex_db.id), 'task_id': 'task1' } tk1_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk1_ac_ex_db = ex_db_access.ActionExecution.query( task_execution=str(tk1_ex_db.id))[0] tk1_lv_ac_db = lv_db_access.LiveAction.get_by_id( tk1_ac_ex_db.liveaction['id']) self.assertEqual(tk1_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue( wf_svc.is_action_execution_under_workflow_context(tk1_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk1_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.SUCCEEDED) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) # Check workflow output, liveaction result, and action execution result. expected_result = {'output': expected_output} self.assertDictEqual(wf_ex_db.output, expected_output) self.assertDictEqual(lv_ac_db.result, expected_result) self.assertDictEqual(ac_ex_db.result, expected_result)
def handle_action_execution(self, ac_ex_db): # Exit if action execution is not executed under an orquesta workflow. if not wf_svc.is_action_execution_under_workflow_context(ac_ex_db): return # Get related record identifiers. wf_ex_id = ac_ex_db.context['orquesta']['workflow_execution_id'] task_ex_id = ac_ex_db.context['orquesta']['task_execution_id'] # Get execution records for logging purposes. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_id) task_ex_db = wf_db_access.TaskExecution.get_by_id(task_ex_id) wf_ac_ex_id = wf_ex_db.action_execution msg = '[%s] Action execution "%s" for task "%s" is updated and in "%s" state.' LOG.info(msg, wf_ac_ex_id, str(ac_ex_db.id), task_ex_db.task_id, ac_ex_db.status) # Skip if task execution is already in completed state. if task_ex_db.status in statuses.COMPLETED_STATUSES: msg = ('[%s] Action execution "%s" for task "%s (%s)", route "%s", is not processed ' 'because task execution "%s" is already in completed state "%s".') LOG.info(msg, wf_ac_ex_id, str(ac_ex_db.id), task_ex_db.task_id, str(task_ex_db.task_route), str(task_ex_db.id), task_ex_db.status) return # Process pending request on the action execution. if ac_ex_db.status == ac_const.LIVEACTION_STATUS_PENDING: wf_svc.handle_action_execution_pending(ac_ex_db) return # Process pause request on the action execution. if ac_ex_db.status == ac_const.LIVEACTION_STATUS_PAUSED: wf_svc.handle_action_execution_pause(ac_ex_db) return # Exit if action execution has not completed yet. if ac_ex_db.status not in ac_const.LIVEACTION_COMPLETED_STATES: return # Apply post run policies. lv_ac_db = lv_db_access.LiveAction.get_by_id(ac_ex_db.liveaction['id']) pc_svc.apply_post_run_policies(lv_ac_db) # Process completion of the action execution. wf_svc.handle_action_execution_completion(ac_ex_db)
def _is_notify_skipped(liveaction): """ notification is skipped if action execution is under workflow context and task is not specified under wf_ex_db.notify["tasks"]. """ is_under_workflow_context = ( workflow_service.is_action_execution_under_workflow_context(liveaction) ) is_under_action_chain_context = is_action_execution_under_action_chain_context( liveaction ) if is_under_workflow_context: wf_ex_db = WorkflowExecution.get( id=liveaction.workflow_execution, only_fields=["notify"] ) task_ex_db = TaskExecution.get( id=liveaction.task_execution, only_fields=["task_name"] ) return not wf_ex_db.notify or task_ex_db.task_name not in wf_ex_db.notify.get( "tasks", {} ) if is_under_action_chain_context: task_name = liveaction.context["chain"]["name"] parent = liveaction.context.get("parent") if parent: parent_execution_db = ActionExecution.get( id=parent["execution_id"], only_fields=["action.parameters", "parameters"], ) skip_notify_tasks = parent_execution_db["parameters"].get("skip_notify", []) default_skip_notify_tasks = parent_execution_db["action"]["parameters"].get( "skip_notify", {} ) if skip_notify_tasks: if task_name in skip_notify_tasks: return True # If skip_notify parameter is specified, but task is not skipped. return False # If skip_notify parameter is not specified, check the task in default list. return task_name in default_skip_notify_tasks.get("default", []) return False
def post_run(self, status, result): # If the action execution goes into pending state at the onstart of the inquiry, # then paused the parent/root workflow in the post run. Previously, the pause request # is made in the run method, but because the liveaction hasn't update to pending status # yet, there is a race condition where the pause request is mishandled. if status == action_constants.LIVEACTION_STATUS_PENDING: pause_parent = ( self.liveaction.context.get("parent") and not workflow_service.is_action_execution_under_workflow_context(self.liveaction) ) # For action execution under Action Chain workflows, request the entire # workflow to pause. Orquesta handles pause differently and so does not require parent # to pause. Orquesta allows for other branches to keep running. When there is no other # active branches, the conductor will see there is only the pending task and will know # to pause the workflow. if pause_parent: root_liveaction = action_service.get_root_liveaction(self.liveaction) action_service.request_pause(root_liveaction, self.context.get('user', None)) # Invoke post run of parent for common post run related work. super(Inquirer, self).post_run(status, result)
def process(self, execution_db): execution_id = str(execution_db.id) extra = {'execution': execution_db} LOG.debug('Processing action execution "%s".', execution_id, extra=extra) # Get the corresponding liveaction record. liveaction_db = LiveAction.get_by_id(execution_db.liveaction['id']) if execution_db.status in LIVEACTION_COMPLETED_STATES: # If the action execution is executed under an orquesta workflow, policies for the # action execution will be applied by the workflow engine. A policy may affect the # final state of the action execution thereby impacting the state of the workflow. if not workflow_service.is_action_execution_under_workflow_context(execution_db): with CounterWithTimer(key='notifier.apply_post_run_policies'): policy_service.apply_post_run_policies(liveaction_db) if liveaction_db.notify: with CounterWithTimer(key='notifier.notify_trigger.post'): self._post_notify_triggers(liveaction_db=liveaction_db, execution_db=execution_db) self._post_generic_trigger(liveaction_db=liveaction_db, execution_db=execution_db)
def post_run(self, status, result): # If the action execution goes into pending state at the onstart of the inquiry, # then paused the parent/root workflow in the post run. Previously, the pause request # is made in the run method, but because the liveaction hasn't update to pending status # yet, there is a race condition where the pause request is mishandled. if status == action_constants.LIVEACTION_STATUS_PENDING: pause_parent = ( self.liveaction.context.get("parent") and not workflow_service.is_action_execution_under_workflow_context(self.liveaction) ) # For action execution under Action Chain and Mistral workflows, request the entire # workflow to pause. Orquesta handles pause differently and so does not require parent # to pause. Orquesta allows for other branches to keep running. When there is no other # active branches, the conductor will see there is only the pending task and will know # to pause the workflow. if pause_parent: root_liveaction = action_service.get_root_liveaction(self.liveaction) action_service.request_pause(root_liveaction, self.context.get('user', None)) # Invoke post run of parent for common post run related work. super(Inquirer, self).post_run(status, result)
def _execute_workflow(self, wf_name, expected_output): wf_file = wf_name + '.yaml' wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, wf_file) lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta['name']) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_db = wf_db_access.WorkflowExecution.query(action_execution=str(ac_ex_db.id))[0] self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) # Assert task1 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task1'} tk1_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk1_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk1_ex_db.id))[0] tk1_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk1_ac_ex_db.liveaction['id']) self.assertEqual(tk1_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk1_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk1_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.SUCCEEDED) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) # Check workflow output, liveaction result, and action execution result. expected_result = {'output': expected_output} self.assertDictEqual(wf_ex_db.output, expected_output) self.assertDictEqual(lv_ac_db.result, expected_result) self.assertDictEqual(ac_ex_db.result, expected_result)
def test_run_workflow(self): wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, 'sequential.yaml') wf_input = {'who': 'Thanos'} lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta['name'], parameters=wf_input) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # The main action execution for this workflow is not under the context of another workflow. self.assertFalse(wf_svc.is_action_execution_under_workflow_context(ac_ex_db)) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertTrue(lv_ac_db.action_is_workflow) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_dbs = wf_db_access.WorkflowExecution.query(action_execution=str(ac_ex_db.id)) wf_ex_db = wf_ex_dbs[0] # Check required attributes. self.assertEqual(len(wf_ex_dbs), 1) self.assertIsNotNone(wf_ex_db.id) self.assertGreater(wf_ex_db.rev, 0) self.assertEqual(wf_ex_db.action_execution, str(ac_ex_db.id)) self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) # Check context in the workflow execution. expected_wf_ex_ctx = { 'st2': { 'workflow_execution_id': str(wf_ex_db.id), 'action_execution_id': str(ac_ex_db.id), 'api_url': 'http://127.0.0.1/v1', 'user': '******' } } self.assertDictEqual(wf_ex_db.context, expected_wf_ex_ctx) # Check context in the liveaction. expected_lv_ac_ctx = { 'workflow_execution': str(wf_ex_db.id), 'pack': 'orquesta_tests' } self.assertDictEqual(lv_ac_db.context, expected_lv_ac_ctx) # Check graph. self.assertIsNotNone(wf_ex_db.graph) self.assertTrue(isinstance(wf_ex_db.graph, dict)) self.assertIn('nodes', wf_ex_db.graph) self.assertIn('adjacency', wf_ex_db.graph) # Check task flow. self.assertIsNotNone(wf_ex_db.flow) self.assertTrue(isinstance(wf_ex_db.flow, dict)) self.assertIn('tasks', wf_ex_db.flow) self.assertIn('sequence', wf_ex_db.flow) # Check input. self.assertDictEqual(wf_ex_db.input, wf_input) # Assert task1 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task1'} tk1_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk1_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk1_ex_db.id))[0] tk1_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk1_ac_ex_db.liveaction['id']) self.assertEqual(tk1_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk1_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk1_ac_ex_db) # Assert task1 succeeded and workflow is still running. tk1_ex_db = wf_db_access.TaskExecution.get_by_id(tk1_ex_db.id) self.assertEqual(tk1_ex_db.status, wf_states.SUCCEEDED) wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_states.RUNNING) # Assert task2 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task2'} tk2_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk2_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk2_ex_db.id))[0] tk2_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk2_ac_ex_db.liveaction['id']) self.assertEqual(tk2_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk2_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk2_ac_ex_db) # Assert task2 succeeded and workflow is still running. tk2_ex_db = wf_db_access.TaskExecution.get_by_id(tk2_ex_db.id) self.assertEqual(tk2_ex_db.status, wf_states.SUCCEEDED) wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_states.RUNNING) # Assert task3 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task3'} tk3_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk3_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk3_ex_db.id))[0] tk3_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk3_ac_ex_db.liveaction['id']) self.assertEqual(tk3_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk3_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk3_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_states.SUCCEEDED) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) # Check workflow output. expected_output = {'msg': '%s, All your base are belong to us!' % wf_input['who']} self.assertDictEqual(wf_ex_db.output, expected_output) # Check liveaction and action execution result. expected_result = {'output': expected_output} self.assertDictEqual(lv_ac_db.result, expected_result) self.assertDictEqual(ac_ex_db.result, expected_result)
def test_run_workflow(self): username = "******" wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, "sequential.yaml") wf_input = {"who": "Thanos"} lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta["name"], parameters=wf_input) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # The main action execution for this workflow is not under the context of another workflow. self.assertFalse( wf_svc.is_action_execution_under_workflow_context(ac_ex_db)) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertTrue(lv_ac_db.action_is_workflow) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_dbs = wf_db_access.WorkflowExecution.query( action_execution=str(ac_ex_db.id)) wf_ex_db = wf_ex_dbs[0] # Check required attributes. self.assertEqual(len(wf_ex_dbs), 1) self.assertIsNotNone(wf_ex_db.id) self.assertGreater(wf_ex_db.rev, 0) self.assertEqual(wf_ex_db.action_execution, str(ac_ex_db.id)) self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) # Check context in the workflow execution. expected_wf_ex_ctx = { "st2": { "workflow_execution_id": str(wf_ex_db.id), "action_execution_id": str(ac_ex_db.id), "api_url": "http://127.0.0.1/v1", "user": username, "pack": "orquesta_tests", "action": "orquesta_tests.sequential", "runner": "orquesta", }, "parent": { "pack": "orquesta_tests" }, } self.assertDictEqual(wf_ex_db.context, expected_wf_ex_ctx) # Check context in the liveaction. expected_lv_ac_ctx = { "workflow_execution": str(wf_ex_db.id), "pack": "orquesta_tests", } self.assertDictEqual(lv_ac_db.context, expected_lv_ac_ctx) # Check graph. self.assertIsNotNone(wf_ex_db.graph) self.assertIsInstance(wf_ex_db.graph, dict) self.assertIn("nodes", wf_ex_db.graph) self.assertIn("adjacency", wf_ex_db.graph) # Check task states. self.assertIsNotNone(wf_ex_db.state) self.assertIsInstance(wf_ex_db.state, dict) self.assertIn("tasks", wf_ex_db.state) self.assertIn("sequence", wf_ex_db.state) # Check input. self.assertDictEqual(wf_ex_db.input, wf_input) # Assert task1 is already completed. query_filters = { "workflow_execution": str(wf_ex_db.id), "task_id": "task1" } tk1_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk1_ac_ex_db = ex_db_access.ActionExecution.query( task_execution=str(tk1_ex_db.id))[0] tk1_lv_ac_db = lv_db_access.LiveAction.get_by_id( tk1_ac_ex_db.liveaction["id"]) self.assertEqual(tk1_lv_ac_db.context.get("user"), username) self.assertEqual(tk1_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue( wf_svc.is_action_execution_under_workflow_context(tk1_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk1_ac_ex_db) # Assert task1 succeeded and workflow is still running. tk1_ex_db = wf_db_access.TaskExecution.get_by_id(tk1_ex_db.id) self.assertEqual(tk1_ex_db.status, wf_statuses.SUCCEEDED) wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.RUNNING) # Assert task2 is already completed. query_filters = { "workflow_execution": str(wf_ex_db.id), "task_id": "task2" } tk2_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk2_ac_ex_db = ex_db_access.ActionExecution.query( task_execution=str(tk2_ex_db.id))[0] tk2_lv_ac_db = lv_db_access.LiveAction.get_by_id( tk2_ac_ex_db.liveaction["id"]) self.assertEqual(tk2_lv_ac_db.context.get("user"), username) self.assertEqual(tk2_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue( wf_svc.is_action_execution_under_workflow_context(tk2_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk2_ac_ex_db) # Assert task2 succeeded and workflow is still running. tk2_ex_db = wf_db_access.TaskExecution.get_by_id(tk2_ex_db.id) self.assertEqual(tk2_ex_db.status, wf_statuses.SUCCEEDED) wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.RUNNING) # Assert task3 is already completed. query_filters = { "workflow_execution": str(wf_ex_db.id), "task_id": "task3" } tk3_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk3_ac_ex_db = ex_db_access.ActionExecution.query( task_execution=str(tk3_ex_db.id))[0] tk3_lv_ac_db = lv_db_access.LiveAction.get_by_id( tk3_ac_ex_db.liveaction["id"]) self.assertEqual(tk3_lv_ac_db.context.get("user"), username) self.assertEqual(tk3_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue( wf_svc.is_action_execution_under_workflow_context(tk3_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk3_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.SUCCEEDED) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) # Check post run is invoked for the liveaction. self.assertTrue(runners_utils.invoke_post_run.called) self.assertEqual(runners_utils.invoke_post_run.call_count, 1) # Check workflow output. expected_output = { "msg": "%s, All your base are belong to us!" % wf_input["who"] } self.assertDictEqual(wf_ex_db.output, expected_output) # Check liveaction and action execution result. expected_result = {"output": expected_output} self.assertDictEqual(lv_ac_db.result, expected_result) self.assertDictEqual(ac_ex_db.result, expected_result)
def test_run_workflow(self): username = '******' wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, 'sequential.yaml') wf_input = {'who': 'Thanos'} lv_ac_db = lv_db_models.LiveActionDB(action=wf_meta['name'], parameters=wf_input) lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) # The main action execution for this workflow is not under the context of another workflow. self.assertFalse(wf_svc.is_action_execution_under_workflow_context(ac_ex_db)) # Assert action execution is running. lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertTrue(lv_ac_db.action_is_workflow) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_RUNNING, lv_ac_db.result) wf_ex_dbs = wf_db_access.WorkflowExecution.query(action_execution=str(ac_ex_db.id)) wf_ex_db = wf_ex_dbs[0] # Check required attributes. self.assertEqual(len(wf_ex_dbs), 1) self.assertIsNotNone(wf_ex_db.id) self.assertGreater(wf_ex_db.rev, 0) self.assertEqual(wf_ex_db.action_execution, str(ac_ex_db.id)) self.assertEqual(wf_ex_db.status, ac_const.LIVEACTION_STATUS_RUNNING) # Check context in the workflow execution. expected_wf_ex_ctx = { 'st2': { 'workflow_execution_id': str(wf_ex_db.id), 'action_execution_id': str(ac_ex_db.id), 'api_url': 'http://127.0.0.1/v1', 'user': username, 'pack': 'orquesta_tests' }, 'parent': { 'pack': 'orquesta_tests' } } self.assertDictEqual(wf_ex_db.context, expected_wf_ex_ctx) # Check context in the liveaction. expected_lv_ac_ctx = { 'workflow_execution': str(wf_ex_db.id), 'pack': 'orquesta_tests' } self.assertDictEqual(lv_ac_db.context, expected_lv_ac_ctx) # Check graph. self.assertIsNotNone(wf_ex_db.graph) self.assertTrue(isinstance(wf_ex_db.graph, dict)) self.assertIn('nodes', wf_ex_db.graph) self.assertIn('adjacency', wf_ex_db.graph) # Check task states. self.assertIsNotNone(wf_ex_db.state) self.assertTrue(isinstance(wf_ex_db.state, dict)) self.assertIn('tasks', wf_ex_db.state) self.assertIn('sequence', wf_ex_db.state) # Check input. self.assertDictEqual(wf_ex_db.input, wf_input) # Assert task1 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task1'} tk1_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk1_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk1_ex_db.id))[0] tk1_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk1_ac_ex_db.liveaction['id']) self.assertEqual(tk1_lv_ac_db.context.get('user'), username) self.assertEqual(tk1_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk1_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk1_ac_ex_db) # Assert task1 succeeded and workflow is still running. tk1_ex_db = wf_db_access.TaskExecution.get_by_id(tk1_ex_db.id) self.assertEqual(tk1_ex_db.status, wf_statuses.SUCCEEDED) wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.RUNNING) # Assert task2 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task2'} tk2_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk2_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk2_ex_db.id))[0] tk2_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk2_ac_ex_db.liveaction['id']) self.assertEqual(tk2_lv_ac_db.context.get('user'), username) self.assertEqual(tk2_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk2_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk2_ac_ex_db) # Assert task2 succeeded and workflow is still running. tk2_ex_db = wf_db_access.TaskExecution.get_by_id(tk2_ex_db.id) self.assertEqual(tk2_ex_db.status, wf_statuses.SUCCEEDED) wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.RUNNING) # Assert task3 is already completed. query_filters = {'workflow_execution': str(wf_ex_db.id), 'task_id': 'task3'} tk3_ex_db = wf_db_access.TaskExecution.query(**query_filters)[0] tk3_ac_ex_db = ex_db_access.ActionExecution.query(task_execution=str(tk3_ex_db.id))[0] tk3_lv_ac_db = lv_db_access.LiveAction.get_by_id(tk3_ac_ex_db.liveaction['id']) self.assertEqual(tk3_lv_ac_db.context.get('user'), username) self.assertEqual(tk3_lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) self.assertTrue(wf_svc.is_action_execution_under_workflow_context(tk3_ac_ex_db)) # Manually handle action execution completion. wf_svc.handle_action_execution_completion(tk3_ac_ex_db) # Assert workflow is completed. wf_ex_db = wf_db_access.WorkflowExecution.get_by_id(wf_ex_db.id) self.assertEqual(wf_ex_db.status, wf_statuses.SUCCEEDED) lv_ac_db = lv_db_access.LiveAction.get_by_id(str(lv_ac_db.id)) self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) ac_ex_db = ex_db_access.ActionExecution.get_by_id(str(ac_ex_db.id)) self.assertEqual(ac_ex_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED) # Check post run is invoked for the liveaction. self.assertTrue(runners_utils.invoke_post_run.called) self.assertEqual(runners_utils.invoke_post_run.call_count, 1) # Check workflow output. expected_output = {'msg': '%s, All your base are belong to us!' % wf_input['who']} self.assertDictEqual(wf_ex_db.output, expected_output) # Check liveaction and action execution result. expected_result = {'output': expected_output} self.assertDictEqual(lv_ac_db.result, expected_result) self.assertDictEqual(ac_ex_db.result, expected_result)