def cancel(self): mistral_ctx = self.context.get('mistral', dict()) if not mistral_ctx.get('execution_id'): raise Exception( 'Unable to cancel because mistral execution_id is missing.') # Cancels the main workflow execution. Any non-workflow tasks that are still # running will be allowed to complete gracefully. self._client.executions.update(mistral_ctx.get('execution_id'), 'CANCELLED') # If workflow is executed under another parent workflow, cancel the corresponding # action execution for the task in the parent workflow. if 'parent' in getattr(self, 'context', {}) and mistral_ctx.get('action_execution_id'): mistral_action_ex_id = mistral_ctx.get('action_execution_id') self._client.action_executions.update(mistral_action_ex_id, 'CANCELLED') # Identify the list of action executions that are workflows and still running. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id) if (child_exec.runner['name'] in action_constants.WORKFLOW_RUNNER_TYPES and child_exec.status in action_constants.LIVEACTION_CANCELABLE_STATES): action_service.request_cancellation( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None)) return (action_constants.LIVEACTION_STATUS_CANCELING, self.liveaction.result, self.liveaction.context)
def test_execution_creation_action_triggered_by_rule(self): # Wait for the action execution to complete and then confirm outcome. trigger_type = self.MODELS['triggertypes']['triggertype2.yaml'] trigger = self.MODELS['triggers']['trigger2.yaml'] trigger_instance = self.MODELS['triggerinstances']['trigger_instance_1.yaml'] test_liveaction = self.FIXTURES['liveactions']['liveaction3.yaml'] rule = self.MODELS['rules']['rule3.yaml'] # Setup LiveAction to point to right rule and trigger_instance. # XXX: We need support for dynamic fixtures. test_liveaction['context']['rule']['id'] = str(rule.id) test_liveaction['context']['trigger_instance']['id'] = str(trigger_instance.id) test_liveaction_api = LiveActionAPI(**test_liveaction) test_liveaction = LiveAction.add_or_update(LiveActionAPI.to_model(test_liveaction_api)) liveaction = LiveAction.get(context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(liveaction) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_REQUESTED) executions_util.create_execution_object(liveaction) execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(execution.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual(execution.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance))) self.assertDictEqual(execution.rule, vars(RuleAPI.from_model(rule))) action = action_utils.get_action_by_ref(liveaction.action) self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEquals(execution.liveaction['id'], str(liveaction.id))
def resume(self): mistral_ctx = self.context.get('mistral', dict()) if not mistral_ctx.get('execution_id'): raise Exception('Unable to resume because mistral execution_id is missing.') # If workflow is executed under another parent workflow, resume the corresponding # action execution for the task in the parent workflow. if 'parent' in getattr(self, 'context', {}) and mistral_ctx.get('action_execution_id'): mistral_action_ex_id = mistral_ctx.get('action_execution_id') self._client.action_executions.update(mistral_action_ex_id, 'RUNNING') # Pause the main workflow execution. Any non-workflow tasks that are still # running will be allowed to complete gracefully. self._client.executions.update(mistral_ctx.get('execution_id'), 'RUNNING') # Identify the list of action executions that are workflows and cascade resume. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id, raise_exception=True) if (child_exec.runner['name'] in action_constants.WORKFLOW_RUNNER_TYPES and child_exec.status == action_constants.LIVEACTION_STATUS_PAUSED): action_service.request_resume( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None) ) return ( action_constants.LIVEACTION_STATUS_RUNNING, self.execution.result, self.execution.context )
def purge_orphaned_workflow_executions(logger): """ Purge workflow executions that are idled and identified as orphans. """ # Cancel workflow executions that are identified as orphans. The workflow executions are # marked as canceled instead of failed because error handling during normal operation # failed and the system does not know what state the workflow execution is in. A failed # workflow execution can be rerun from failed task(s). Since we do not know what state # the workflow execution is in because correct data may not be recorded in the database # as a result of the original failure, the garbage collection routine here cancels # the workflow execution so it cannot be rerun from failed task(s). for ac_ex_db in workflow_service.identify_orphaned_workflows(): lv_ac_db = LiveAction.get(id=ac_ex_db.liveaction['id']) action_service.request_cancellation(lv_ac_db, None)
def test_triggered_execution(self): docs = { 'trigger_type': copy.deepcopy(fixture.ARTIFACTS['trigger_type']), 'trigger': copy.deepcopy(fixture.ARTIFACTS['trigger']), 'rule': copy.deepcopy(fixture.ARTIFACTS['rule']), 'trigger_instance': copy.deepcopy(fixture.ARTIFACTS['trigger_instance'])} # Trigger an action execution. trigger_type = TriggerType.add_or_update( TriggerTypeAPI.to_model(TriggerTypeAPI(**docs['trigger_type']))) trigger = Trigger.add_or_update(TriggerAPI.to_model(TriggerAPI(**docs['trigger']))) rule = RuleAPI.to_model(RuleAPI(**docs['rule'])) rule.trigger = reference.get_str_resource_ref_from_model(trigger) rule = Rule.add_or_update(rule) trigger_instance = TriggerInstance.add_or_update( TriggerInstanceAPI.to_model(TriggerInstanceAPI(**docs['trigger_instance']))) trace_service.add_or_update_given_trace_context( trace_context={'trace_tag': 'test_triggered_execution_trace'}, trigger_instances=[str(trigger_instance.id)]) enforcer = RuleEnforcer(trigger_instance, rule) enforcer.enforce() # Wait for the action execution to complete and then confirm outcome. liveaction = LiveAction.get(context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(liveaction) liveaction = self._wait_on_status(liveaction, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution( liveaction__id=str(liveaction.id), raise_exception=True ) self.assertDictEqual(execution.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(execution.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual(execution.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance))) self.assertDictEqual(execution.rule, vars(RuleAPI.from_model(rule))) action = action_utils.get_action_by_ref(liveaction.action) self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) self.assertEqual(execution.end_timestamp, liveaction.end_timestamp) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction['callback'], liveaction.callback) self.assertEqual(execution.liveaction['action'], liveaction.action)
def pause(self): # Identify the list of action executions that are workflows and cascade pause. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id, raise_exception=True) if (child_exec.runner['name'] in action_constants.WORKFLOW_RUNNER_TYPES and child_exec.status == action_constants.LIVEACTION_STATUS_RUNNING): action_service.request_pause( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None)) return (action_constants.LIVEACTION_STATUS_PAUSING, self.liveaction.result, self.liveaction.context)
def pause(self): # Identify the list of action executions that are workflows and cascade pause. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id, raise_exception=True) if (child_exec.runner['name'] in action_constants.WORKFLOW_RUNNER_TYPES and child_exec.status == action_constants.LIVEACTION_STATUS_RUNNING): action_service.request_pause( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None) ) return ( action_constants.LIVEACTION_STATUS_PAUSING, self.liveaction.result, self.liveaction.context )
def cancel(self): mistral_ctx = self.context.get('mistral', dict()) if not mistral_ctx.get('execution_id'): raise Exception('Unable to cancel because mistral execution_id is missing.') # Cancels the main workflow execution. Any non-workflow tasks that are still # running will be allowed to complete gracefully. self._client.executions.update(mistral_ctx.get('execution_id'), 'CANCELLED') # Identify the list of action executions that are workflows and still running. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id) if (child_exec.runner['name'] == self.runner_type_db.name and child_exec.status in action_constants.LIVEACTION_CANCELABLE_STATES): action_service.request_cancellation( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None) )
def get_parent_liveaction(liveaction_db): """Get the liveaction for the parent workflow Useful for finding the parent workflow. Pass in any LiveActionDB instance, and this function will return the liveaction of the parent workflow. :param liveaction_db: The LiveActionDB instance for which to find the parent. :rtype: LiveActionDB """ parent = liveaction_db.context.get('parent') if not parent: return None parent_execution_db = ActionExecution.get(id=parent['execution_id']) parent_liveaction_db = LiveAction.get(id=parent_execution_db.liveaction['id']) return parent_liveaction_db
def get_parent_liveaction(liveaction_db): """Get the liveaction for the parent workflow Useful for finding the parent workflow. Pass in any LiveActionDB instance, and this function will return the liveaction of the parent workflow. :param liveaction_db: The LiveActionDB instance for which to find the parent. :rtype: LiveActionDB """ parent = liveaction_db.context.get("parent") if not parent: return None parent_execution_db = ActionExecution.get(id=parent["execution_id"]) parent_liveaction_db = LiveAction.get(id=parent_execution_db.liveaction["id"]) return parent_liveaction_db
def get_root_liveaction(liveaction_db): """Recursively ascends until the root liveaction is found Useful for finding an original parent workflow. Pass in any LiveActionDB instance, and this function will eventually return the top-most liveaction, even if the two are one and the same. :param liveaction_db: The LiveActionDB instance for which to find the root parent. :rtype: LiveActionDB """ parent = liveaction_db.context.get('parent') if not parent: return liveaction_db parent_execution = ActionExecution.get(id=parent['execution_id']) parent_liveaction = LiveAction.get(id=parent_execution.liveaction['id']) return get_root_liveaction(parent_liveaction)
def cancel(self): # Identify the list of action executions that are workflows and cascade pause. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id, raise_exception=True) if (child_exec.runner["name"] in action_constants.WORKFLOW_RUNNER_TYPES and child_exec.status in action_constants.LIVEACTION_CANCELABLE_STATES): action_service.request_cancellation( LiveAction.get(id=child_exec.liveaction["id"]), self.context.get("user", None), ) return ( action_constants.LIVEACTION_STATUS_CANCELING, self.liveaction.result, self.liveaction.context, )
def cancel(self): mistral_ctx = self.context.get('mistral', dict()) if not mistral_ctx.get('execution_id'): raise Exception('Unable to cancel because mistral execution_id is missing.') # Cancels the main workflow execution. Any non-workflow tasks that are still # running will be allowed to complete gracefully. self._client.executions.update(mistral_ctx.get('execution_id'), 'CANCELLED') # If workflow is executed under another parent workflow, cancel the corresponding # action execution for the task in the parent workflow. if 'parent' in getattr(self, 'context', {}) and mistral_ctx.get('action_execution_id'): mistral_action_ex_id = mistral_ctx.get('action_execution_id') self._client.action_executions.update(mistral_action_ex_id, 'CANCELLED') # Identify the list of action executions that are workflows and still running. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id) if (child_exec.runner['name'] in action_constants.WORKFLOW_RUNNER_TYPES and child_exec.status in action_constants.LIVEACTION_CANCELABLE_STATES): action_service.request_cancellation( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None) ) status = ( action_constants.LIVEACTION_STATUS_CANCELING if action_service.is_children_active(self.liveaction.id) else action_constants.LIVEACTION_STATUS_CANCELED ) return ( status, self.liveaction.result, self.liveaction.context )
def pause(self): mistral_ctx = self.context.get('mistral', dict()) if not mistral_ctx.get('execution_id'): raise Exception('Unable to pause because mistral execution_id is missing.') # Pause the main workflow execution. Any non-workflow tasks that are still # running will be allowed to complete gracefully. self._client.executions.update(mistral_ctx.get('execution_id'), 'PAUSED') # If workflow is executed under another parent workflow, pause the corresponding # action execution for the task in the parent workflow. if 'parent' in getattr(self, 'context', {}) and mistral_ctx.get('action_execution_id'): mistral_action_ex_id = mistral_ctx.get('action_execution_id') self._client.action_executions.update(mistral_action_ex_id, 'PAUSED') # Identify the list of action executions that are workflows and cascade pause. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id, raise_exception=True) if (child_exec.runner['name'] in action_constants.WORKFLOW_RUNNER_TYPES and child_exec.status == action_constants.LIVEACTION_STATUS_RUNNING): action_service.request_pause( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None) ) status = ( action_constants.LIVEACTION_STATUS_PAUSING if action_service.is_children_active(self.liveaction.id) else action_constants.LIVEACTION_STATUS_PAUSED ) return ( status, self.liveaction.result, self.liveaction.context )
def test_triggered_execution(self): docs = { "trigger_type": copy.deepcopy(fixture.ARTIFACTS["trigger_type"]), "trigger": copy.deepcopy(fixture.ARTIFACTS["trigger"]), "rule": copy.deepcopy(fixture.ARTIFACTS["rule"]), "trigger_instance": copy.deepcopy(fixture.ARTIFACTS["trigger_instance"]), } # Trigger an action execution. trigger_type = TriggerType.add_or_update( TriggerTypeAPI.to_model(TriggerTypeAPI(**docs["trigger_type"]))) trigger = Trigger.add_or_update( TriggerAPI.to_model(TriggerAPI(**docs["trigger"]))) rule = RuleAPI.to_model(RuleAPI(**docs["rule"])) rule.trigger = reference.get_str_resource_ref_from_model(trigger) rule = Rule.add_or_update(rule) trigger_instance = TriggerInstance.add_or_update( TriggerInstanceAPI.to_model( TriggerInstanceAPI(**docs["trigger_instance"]))) trace_service.add_or_update_given_trace_context( trace_context={"trace_tag": "test_triggered_execution_trace"}, trigger_instances=[str(trigger_instance.id)], ) enforcer = RuleEnforcer(trigger_instance, rule) enforcer.enforce() # Wait for the action execution to complete and then confirm outcome. liveaction = LiveAction.get( context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(liveaction) liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str( liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(execution.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual( execution.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance)), ) self.assertDictEqual(execution.rule, vars(RuleAPI.from_model(rule))) action = action_utils.get_action_by_ref(liveaction.action) self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type["name"]) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) # NOTE: Timestamp of liveaction and execution may be a bit different, depending on how long # it takes to persist each object in the database self.assertEqual( execution.end_timestamp.replace(microsecond=0), liveaction.end_timestamp.replace(microsecond=0), ) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction["callback"], liveaction.callback) self.assertEqual(execution.liveaction["action"], liveaction.action)