def test_run_action_async(self): action_ex = self.engine.start_action('std.async_noop', {}) is_action_ex_running = (lambda: db_api.get_action_execution( action_ex.id).state == states.RUNNING) self._await(is_action_ex_running) action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.RUNNING, action_ex.state)
def test_run_action_async_invoke_failure(self): action_ex = self.engine.start_action('std.async_noop', {}) is_action_ex_error = (lambda: db_api.get_action_execution(action_ex.id) .state == states.ERROR) self._await(is_action_ex_error) action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.ERROR, action_ex.state) self.assertIn('Invoke failed.', action_ex.output.get('result', ''))
def on_action_complete(self, action_ex_id, result): wf_ex_id = None try: with db_api.transaction(): action_ex = db_api.get_action_execution(action_ex_id) # In case of single action execution there is no # assigned task execution. if not action_ex.task_execution: return action_handler.store_action_result( action_ex, result).get_clone() wf_ex_id = action_ex.task_execution.workflow_execution_id wf_ex = wf_handler.lock_workflow_execution(wf_ex_id) task_ex = task_handler.on_action_complete(action_ex, result) # If workflow is on pause or completed then there's no # need to continue workflow. if states.is_paused_or_completed(wf_ex.state): return action_ex.get_clone() prev_task_state = task_ex.state # Separate the task transition in a separate transaction. The task # has already completed for better or worst. The task state should # not be affected by errors during transition on conditions such as # on-success and on-error. with db_api.transaction(): wf_ex = wf_handler.lock_workflow_execution(wf_ex_id) action_ex = db_api.get_action_execution(action_ex_id) task_ex = action_ex.task_execution self._on_task_state_change(task_ex, wf_ex, task_state=prev_task_state) return action_ex.get_clone() except Exception as e: # TODO(dzimine): try to find out which command caused failure. # TODO(rakhmerov): Need to refactor logging in a more elegant way. LOG.error( "Failed to handle action execution result [id=%s]: %s\n%s", action_ex_id, e, traceback.format_exc()) # If an exception was thrown after we got the wf_ex_id if wf_ex_id: self._fail_workflow(wf_ex_id, e) raise e
def test_run_action_save_result(self): # Start action. action_ex = self.engine.start_action('std.echo', {'output': 'Hello!'}, save_result=True) is_action_ex_success = (lambda: db_api.get_action_execution( action_ex.id).state == states.SUCCESS) self._await(is_action_ex_success) action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.SUCCESS, action_ex.state) self.assertEqual({'result': 'Hello!'}, action_ex.output)
def delete(self, id): """Delete the specified action_execution. :param id: UUID of action execution to delete """ acl.enforce('action_executions:delete', context.ctx()) LOG.debug("Delete action_execution [id=%s]", id) if not cfg.CONF.api.allow_action_execution_deletion: raise exc.NotAllowedException("Action execution deletion is not " "allowed.") with db_api.transaction(): action_ex = db_api.get_action_execution(id) if action_ex.task_execution_id: raise exc.NotAllowedException( "Only ad-hoc action execution can be deleted.") if not states.is_completed(action_ex.state): raise exc.NotAllowedException( "Only completed action execution can be deleted.") return db_api.delete_action_execution(id)
def _fail_workflow(wf_ex_id, err, action_ex_id=None): """Private helper to fail workflow on exceptions.""" err_msg = str(err) with db_api.transaction(): wf_ex = db_api.load_workflow_execution(wf_ex_id) if wf_ex is None: LOG.error( "Cant fail workflow execution with id='%s': not found.", wf_ex_id) return wf_handler.set_execution_state(wf_ex, states.ERROR, err_msg) if action_ex_id: # Note(dzimine): Don't call self.engine_client: # 1) to avoid computing and triggering next tasks # 2) to avoid a loop in case of error in transport action_ex = db_api.get_action_execution(action_ex_id) task_handler.on_action_complete(action_ex, wf_utils.Result(error=err_msg)) return wf_ex
def test_report_running_actions(self): wf_input = {'param1': 'Hey', 'param2': 'Hi'} # Start workflow. wf_ex = self.engine.start_workflow('wb.wf', '', wf_input=wf_input, description='my execution', task_name='task2') with db_api.transaction(): wf_ex = db_api.get_workflow_execution(wf_ex.id) task_execs = wf_ex.task_executions self.assertEqual(1, len(task_execs)) task_ex = task_execs[0] action_execs = db_api.get_action_executions( task_execution_id=task_ex.id) task_action_ex = action_execs[0] self.engine.report_running_actions([]) self.engine.report_running_actions([None, None]) self.engine.report_running_actions([None, task_action_ex.id]) task_action_ex = db_api.get_action_execution(task_action_ex.id) self.assertIsNotNone(task_action_ex.last_heartbeat)
def _scheduled_on_action_update(action_ex_id, wf_action): with db_api.transaction(): if wf_action: action_ex = db_api.get_workflow_execution(action_ex_id) else: action_ex = db_api.get_action_execution(action_ex_id) _on_action_update(action_ex)
def test_run_action_async(self): action_ex = self.engine.start_action('std.async_noop', {}) self.await_action_state(action_ex.id, states.RUNNING) action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.RUNNING, action_ex.state)
def test_run_action_async_invoke_with_error(self): action_ex = self.engine.start_action('std.async_noop', {}) self.await_action_error(action_ex.id) action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.ERROR, action_ex.state) self.assertIn('Invoke erred.', action_ex.output.get('result', ''))
def run_existing_action(action_ex_id, target): action_ex = db_api.get_action_execution(action_ex_id) action_def = db_api.get_action_definition(action_ex.name) return run_action( action_def, action_ex.input, action_ex_id, target )
def _run_existing_action(action_ex_id, target): action_ex = db_api.get_action_execution(action_ex_id) action_def = db_api.get_action_definition(action_ex.name) result = rpc.get_executor_client().run_action(action_ex_id, action_def.action_class, action_def.attributes or {}, action_ex.input, target) return _get_action_output(result) if result else None
def on_action_complete(self, action_ex_id, result, wf_action=False): with db_api.transaction(): if wf_action: action_ex = db_api.get_workflow_execution(action_ex_id) else: action_ex = db_api.get_action_execution(action_ex_id) action_handler.on_action_complete(action_ex, result) return action_ex.get_clone()
def on_action_update(self, action_ex_id, state, wf_action=False, async_=False): with db_api.transaction(): if wf_action: action_ex = db_api.get_workflow_execution(action_ex_id) else: action_ex = db_api.get_action_execution(action_ex_id) action_handler.on_action_update(action_ex, state) return action_ex.get_clone()
def test_run_action_save_result_and_run_sync(self): # Start action. action_ex = self.engine.start_action('std.echo', {'output': 'Hello!'}, save_result=True, run_sync=True) self.assertEqual('Hello!', action_ex.output['result']) self.assertEqual(states.SUCCESS, action_ex.state) db_action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.SUCCESS, db_action_ex.state) self.assertEqual({'result': 'Hello!'}, db_action_ex.output)
def test_run_action_save_result(self): # Start action. action_ex = self.engine.start_action('std.echo', {'output': 'Hello!'}, save_result=True) self.await_action_success(action_ex.id) with db_api.transaction(): action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.SUCCESS, action_ex.state) self.assertEqual({'result': 'Hello!'}, action_ex.output)
def on_action_complete(self, action_ex_id, result): with db_api.transaction(): action_ex = db_api.get_action_execution(action_ex_id) task_ex = action_ex.task_execution if task_ex: wf_handler.lock_workflow_execution( task_ex.workflow_execution_id) action_handler.on_action_complete(action_ex, result) return action_ex.get_clone()
def _run_existing_action(action_ex_id, target): action_ex = db_api.get_action_execution(action_ex_id) action_def = db_api.get_action_definition(action_ex.name) result = rpc.get_executor_client().run_action( action_ex_id, action_def.action_class, action_def.attributes or {}, action_ex.input, target, safe_rerun=action_ex.runtime_context.get('safe_rerun', False)) return _get_action_output(result) if result else None
def on_action_complete(self, action_ex_id, result, wf_action=False, async_=False): with db_api.transaction(): if wf_action: action_ex = db_api.get_workflow_execution(action_ex_id) # If result is None it means that it's a normal subworkflow # output and we just need to fetch it from the model. # This is just an optimization to not send data over RPC if result is None: result = ml_actions.Result(data=action_ex.output) else: action_ex = db_api.get_action_execution(action_ex_id) action_handler.on_action_complete(action_ex, result) return action_ex.get_clone()
def on_action_complete(self, action_ex_id, result): wf_ex_id = None try: with db_api.transaction(): action_ex = db_api.get_action_execution(action_ex_id) # In case of single action execution there is no # assigned task execution. if not action_ex.task_execution: return action_handler.store_action_result( action_ex, result ).get_clone() wf_ex_id = action_ex.task_execution.workflow_execution_id wf_ex = wf_handler.lock_workflow_execution(wf_ex_id) wf_spec = spec_parser.get_workflow_spec(wf_ex.spec) task_ex = task_handler.on_action_complete( action_ex, wf_spec, result ) # If workflow is on pause or completed then there's no # need to continue workflow. if states.is_paused_or_completed(wf_ex.state): return action_ex.get_clone() self._on_task_state_change(task_ex, wf_ex, wf_spec) return action_ex.get_clone() except Exception as e: # TODO(rakhmerov): Need to refactor logging in a more elegant way. LOG.error( 'Failed to handle action execution result [id=%s]: %s\n%s', action_ex_id, e, traceback.format_exc() ) # If an exception was thrown after we got the wf_ex_id if wf_ex_id: self._fail_workflow(wf_ex_id, e) raise e
def test_adhoc_async_action(self): wb_text = """--- version: '2.0' name: my_wb1 actions: my_action: input: - my_param base: std.async_noop output: (((<% $ %>))) workflows: my_wf: tasks: task1: action: my_action my_param="asdfasdf" """ wb_service.create_workbook_v2(wb_text) wf_ex = self.engine.start_workflow('my_wb1.my_wf') self.await_workflow_running(wf_ex.id, timeout=4) with db_api.transaction(read_only=True): wf_ex = db_api.get_workflow_execution(wf_ex.id) wf_ex_id = wf_ex.id a_ex_id = wf_ex.task_executions[0].action_executions[0].id self.engine.on_action_complete(a_ex_id, ml_actions.Result(data='Hi!')) self.await_action_success(a_ex_id) self.await_workflow_success(wf_ex_id) with db_api.transaction(read_only=True): a_ex = db_api.get_action_execution(a_ex_id) self.assertEqual('(((Hi!)))', a_ex.output.get('result'))
def delete(self, id): """Delete the specified action_execution.""" LOG.info("Delete action_execution [id=%s]" % id) if not cfg.CONF.api.allow_action_execution_deletion: raise exc.NotAllowedException("Action execution deletion is not " "allowed.") action_ex = db_api.get_action_execution(id) if action_ex.task_execution_id: raise exc.NotAllowedException("Only ad-hoc action execution can " "be deleted.") if not states.is_completed(action_ex.state): raise exc.NotAllowedException("Only completed action execution " "can be deleted.") return db_api.delete_action_execution(id)
def on_action_complete(self, action_ex_id, result): wf_ex_id = None try: with db_api.transaction(): action_ex = db_api.get_action_execution(action_ex_id) # In case of single action execution there is no # assigned task execution. if not action_ex.task_execution: return action_handler.store_action_result( action_ex, result).get_clone() wf_ex_id = action_ex.task_execution.workflow_execution_id # Must be before loading the object itself (see method doc). self._lock_workflow_execution(wf_ex_id) wf_ex = action_ex.task_execution.workflow_execution task_ex = task_handler.on_action_complete(action_ex, result) # If workflow is on pause or completed then there's no # need to continue workflow. if states.is_paused_or_completed(wf_ex.state): return action_ex self._on_task_state_change(task_ex, wf_ex, action_ex) return action_ex.get_clone() except Exception as e: # TODO(dzimine): try to find out which command caused failure. # TODO(rakhmerov): Need to refactor logging in a more elegant way. LOG.error( "Failed to handle action execution result [id=%s]: %s\n%s", action_ex_id, e, traceback.format_exc()) self._fail_workflow(wf_ex_id, e) raise e
def test_run_action_with_namespace(self): namespace = 'test_ns' action_text = """--- version: '2.0' concat1: base: std.echo base-input: output: <% $.left %><% $.right %> input: - left - right concat2: base: concat1 base-input: left: <% $.left %><% $.center %> right: <% $.right %> input: - left - center - right """ actions.create_actions(action_text, namespace=namespace) self.assertRaises(exc.InvalidActionException, self.engine.start_action, 'concat1', { 'left': 'Hello, ', 'right': 'John Doe!' }, save_result=True, namespace='') action_ex = self.engine.start_action('concat1', { 'left': 'Hello, ', 'right': 'John Doe!' }, save_result=True, namespace=namespace) self.assertEqual(namespace, action_ex.workflow_namespace) self.await_action_success(action_ex.id) with db_api.transaction(): action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.SUCCESS, action_ex.state) self.assertEqual({'result': u'Hello, John Doe!'}, action_ex.output) action_ex = self.engine.start_action('concat2', { 'left': 'Hello, ', 'center': 'John', 'right': ' Doe!' }, save_result=True, namespace=namespace) self.assertEqual(namespace, action_ex.workflow_namespace) self.await_action_success(action_ex.id) with db_api.transaction(): action_ex = db_api.get_action_execution(action_ex.id) self.assertEqual(states.SUCCESS, action_ex.state) self.assertEqual('Hello, John Doe!', action_ex.output['result'])
def _action_result_equals(action_ex_id, output): with db_api.transaction(): a_ex = db_api.get_action_execution(action_ex_id) return a_ex.output == output
def is_action_in_state(self, ex_id, state): return db_api.get_action_execution(ex_id).state == state
class DefaultEngine(base.Engine): @action_queue.process @u.log_exec(LOG) @profiler.trace('engine-start-workflow') def start_workflow(self, wf_identifier, wf_input, description='', **params): with db_api.transaction(): wf_ex = wf_handler.start_workflow(wf_identifier, wf_input, description, params) return wf_ex.get_clone() @action_queue.process @u.log_exec(LOG) def start_action(self, action_name, action_input, description=None, **params): with db_api.transaction(): action = action_handler.build_action_by_name(action_name) action.validate_input(action_input) sync = params.get('run_sync') save = params.get('save_result') target = params.get('target') is_action_sync = action.is_sync(action_input) if sync and not is_action_sync: raise exceptions.InputException( "Action does not support synchronous execution.") if not sync and (save or not is_action_sync): action.schedule(action_input, target) return action.action_ex.get_clone() output = action.run(action_input, target, save=False) state = states.SUCCESS if output.is_success() else states.ERROR if not save: # Action execution is not created but we need to return similar # object to a client anyway. return db_models.ActionExecution(name=action_name, description=description, input=action_input, output=output.to_dict(), state=state) action_ex_id = u.generate_unicode_uuid() values = { 'id': action_ex_id, 'name': action_name, 'description': description, 'input': action_input, 'output': output.to_dict(), 'state': state, } return db_api.create_action_execution(values) @db_utils.retry_on_deadlock @action_queue.process @u.log_exec(LOG) @profiler.trace('engine-on-action-complete') def on_action_complete(self, action_ex_id, result, wf_action=False, async=False): with db_api.transaction(): if wf_action: action_ex = db_api.get_workflow_execution(action_ex_id) else: action_ex = db_api.get_action_execution(action_ex_id) action_handler.on_action_complete(action_ex, result) return action_ex.get_clone()
def _get_action_execution(id): with db_api.transaction(): return _get_action_execution_resource(db_api.get_action_execution(id))
def _get_action_execution(id): action_ex = db_api.get_action_execution(id) return _get_action_execution_resource(action_ex)