def execute(self, *args, **kwargs): def _do_execute_action(*args, **kwargs): try: db_action = self.do_execute(*args, **kwargs) notifications.action.send_execution_notification( self.engine.context, db_action, fields.NotificationAction.EXECUTION, fields.NotificationPhase.END) except Exception as e: LOG.exception(e) LOG.error( 'The workflow engine has failed' 'to execute the action: %s', self.name) db_action = self.engine.notify(self._db_action, objects.action.State.FAILED) notifications.action.send_execution_notification( self.engine.context, db_action, fields.NotificationAction.EXECUTION, fields.NotificationPhase.ERROR, priority=fields.NotificationPriority.ERROR) raise # NOTE: spawn a new thread for action execution, so that if action plan # is cancelled workflow engine will not wait to finish action execution et = eventlet.spawn(_do_execute_action, *args, **kwargs) # NOTE: check for the state of action plan periodically,so that if # action is finished or action plan is cancelled we can exit from here. while True: action_object = objects.Action.get_by_uuid(self.engine.context, self._db_action.uuid, eager=True) action_plan_object = objects.ActionPlan.get_by_id( self.engine.context, action_object.action_plan_id) if (action_object.state in [ objects.action.State.SUCCEEDED, objects.action.State.FAILED ] or action_plan_object.state in CANCEL_STATE): break time.sleep(1) try: # NOTE: kill the action execution thread, if action plan is # cancelled for all other cases wait for the result from action # execution thread. # Not all actions support abort operations, kill only those action # which support abort operations abort = self.action.check_abort() if (action_plan_object.state in CANCEL_STATE and abort): et.kill() et.wait() # NOTE: catch the greenlet exit exception due to thread kill, # taskflow will call revert for the action, # we will redirect it to abort. except eventlet.greenlet.GreenletExit: self.engine.notify_cancel_start(action_plan_object.uuid) raise exception.ActionPlanCancelled(uuid=action_plan_object.uuid) except Exception as e: LOG.exception(e) raise
def pre_execute(self): try: # NOTE(adisky): check the state of action plan before starting # next action, if action plan is cancelled raise the exceptions # so that taskflow does not schedule further actions. action_plan = objects.ActionPlan.get_by_id( self.engine.context, self._db_action.action_plan_id) if action_plan.state in CANCEL_STATE: raise exception.ActionPlanCancelled(uuid=action_plan.uuid) db_action = self.do_pre_execute() notifications.action.send_execution_notification( self.engine.context, db_action, fields.NotificationAction.EXECUTION, fields.NotificationPhase.START) except exception.ActionPlanCancelled as e: LOG.exception(e) self.engine.notify_cancel_start(action_plan.uuid) raise except Exception as e: LOG.exception(e) db_action = self.engine.notify(self._db_action, objects.action.State.FAILED) notifications.action.send_execution_notification( self.engine.context, db_action, fields.NotificationAction.EXECUTION, fields.NotificationPhase.ERROR, priority=fields.NotificationPriority.ERROR)
def test_cancel_action_plan_with_exception(self, m_get_action_plan, m_execute): m_get_action_plan.return_value = self.action_plan m_execute.side_effect = exception.ActionPlanCancelled( self.action_plan.uuid) command = default.DefaultActionPlanHandler( self.context, mock.MagicMock(), self.action_plan.uuid) command.execute() self.assertEqual(ap_objects.State.CANCELLED, self.action_plan.state)