def prepare_tasks(tasks, context, workbook): results = [] for task in tasks: # TODO(rakhmerov): Inbound context should be a merge of # outbound contexts of task dependencies, if any. action_params = evaluate_task_parameters(task, context) db_api.task_update(task['id'], {'state': states.RUNNING, 'in_context': context, 'parameters': action_params}) # Get action name. Unwrap ad-hoc and reevaluate params if # necessary. action_name = wb_task.TaskSpec(task['task_spec'])\ .get_full_action_name() openstack_ctx = context.get('openstack') if not a_f.get_action_class(action_name): # If action is not found in registered actions try to find # ad-hoc action definition. if openstack_ctx is not None: action_params.update({'openstack': openstack_ctx}) action = a_f.resolve_adhoc_action_name(workbook, action_name) if not action: msg = 'Unknown action [workbook=%s, action=%s]' % \ (workbook, action_name) raise exc.ActionException(msg) action_params = a_f.convert_adhoc_action_params(workbook, action_name, action_params) action_name = action if _has_action_context_param(a_f.get_action_class(action_name)): action_params[_ACTION_CTX_PARAM] = \ _get_action_context(task, openstack_ctx) results.append((task['id'], action_name, action_params)) return results
def handle_task(self, cntx, task_id, action_name, params={}): """Handle the execution of the workbook task. :param task_id: task identifier :type task_id: str :param action_name: a name of the action to run :type action_name: str :param params: a dict of action parameters """ action_cls = a_f.get_action_class(action_name) # TODO(dzimine): on failure, convey failure details back try: action = action_cls(**params) except Exception as e: raise exc.ActionException("Failed to create action" "[action_name=%s, params=%s]: %s" % (action_name, params, e)) if action.is_sync(): try: state, result = states.SUCCESS, action.run() except exc.ActionException as ex: self._log_action_exception("Action failed", task_id, action_name, params, ex) state, result = states.ERROR, None self.engine.convey_task_result(task_id, state, result) else: try: action.run() except exc.ActionException as ex: self._log_action_exception("Action failed", task_id, action_name, params, ex) self.engine.convey_task_result(task_id, states.ERROR, None)
def convey_task_result(self, cntx, **kwargs): """Conveys task result to Mistral Engine. This method should be used by clients of Mistral Engine to update state of a task once task action has been performed. One of the clients of this method is Mistral REST API server that receives task result from the outside action handlers. Note: calling this method serves an event notifying Mistral that it possibly needs to move the workflow on, i.e. run other workflow tasks for which all dependencies are satisfied. :param cntx: a request context dict :type cntx: dict :param kwargs: a dict of method arguments :type kwargs: dict :return: Task. """ task_id = kwargs.get('task_id') state = kwargs.get('state') result = kwargs.get('result') db_api.start_tx() try: # TODO(rakhmerov): validate state transition task = db_api.task_get(task_id) workbook = self._get_workbook(task['workbook_name']) wf_trace_msg = "Task '%s' [%s -> %s" % \ (task['name'], task['state'], state) wf_trace_msg += ']' if state == states.ERROR \ else ", result = %s]" % result WORKFLOW_TRACE.info(wf_trace_msg) action_name = wb_task.TaskSpec(task['task_spec'])\ .get_full_action_name() if not a_f.get_action_class(action_name): action = a_f.resolve_adhoc_action_name(workbook, action_name) if not action: msg = 'Unknown action [workbook=%s, action=%s]' % \ (workbook, action_name) raise exc.ActionException(msg) result = a_f.convert_adhoc_action_result(workbook, action_name, result) task_output = data_flow.get_task_output(task, result) # Update task state. task, context = self._update_task(workbook, task, state, task_output) execution = db_api.execution_get(task['execution_id']) self._create_next_tasks(task, workbook) # Determine what tasks need to be started. tasks = db_api.tasks_get(execution_id=task['execution_id']) new_exec_state = self._determine_execution_state(execution, tasks) if execution['state'] != new_exec_state: wf_trace_msg = \ "Execution '%s' [%s -> %s]" % \ (execution['id'], execution['state'], new_exec_state) WORKFLOW_TRACE.info(wf_trace_msg) execution = db_api.execution_update(execution['id'], { "state": new_exec_state }) LOG.info("Changed execution state: %s" % execution) # Create a list of tasks that can be executed immediately (have # their requirements satisfied) along with the list of tasks that # require some delay before they'll be executed. tasks_to_start, delayed_tasks = workflow.find_resolved_tasks(tasks) # Populate context with special variables such as `openstack` and # `__execution`. self._add_variables_to_data_flow_context(context, execution) # Update task with new context and params. executables = data_flow.prepare_tasks(tasks_to_start, context, workbook) db_api.commit_tx() except Exception as e: msg = "Failed to create necessary DB objects: %s" % e LOG.exception(msg) raise exc.EngineException(msg) finally: db_api.end_tx() if states.is_stopped_or_finished(execution['state']): return task for task in delayed_tasks: self._schedule_run(workbook, task, context) for task_id, action_name, action_params in executables: self._run_task(task_id, action_name, action_params) return task
def test_get_action_class(self): self.assertEqual(std.EchoAction, a_f.get_action_class("std.echo")) self.assertEqual(std.HTTPAction, a_f.get_action_class("std.http")) self.assertEqual(std.SendEmailAction, a_f.get_action_class("std.email"))
def test_get_action_class_failure(self): self.assertEqual(std.EchoAction, a_f.get_action_class("echo"))