def run_it(runner, failed=False, result=None, simulate_run=False): try: # Add the task to be rolled back *immediately* so that even if # the task fails while producing results it will be given a # chance to rollback. rb = utils.RollbackTask(context, runner.task, result=None) self._accumulator.add(rb) self.task_notifier.notify(states.STARTED, details={ 'context': context, 'flow': self, 'runner': runner, }) if not simulate_run: result = runner(context, *args, **kwargs) else: if failed: # TODO(harlowja): make this configurable?? # If we previously failed, we want to fail again at # the same place. if not result: # If no exception or exception message was provided # or captured from the previous run then we need to # form one for this task. result = "%s failed running." % (runner.task) if isinstance(result, basestring): result = exc.InvalidStateException(result) if not isinstance(result, Exception): LOG.warn( "Can not raise a non-exception" " object: %s", result) result = exc.InvalidStateException() raise result # Adjust the task result in the accumulator before # notifying others that the task has finished to # avoid the case where a listener might throw an # exception. rb.result = result runner.result = result self.results[runner.uuid] = result self.task_notifier.notify(states.SUCCESS, details={ 'context': context, 'flow': self, 'runner': runner, }) except Exception as e: runner.result = e cause = utils.FlowFailure(runner, self, e) with excutils.save_and_reraise_exception(): # Notify any listeners that the task has errored. self.task_notifier.notify(states.FAILURE, details={ 'context': context, 'flow': self, 'runner': runner, }) self.rollback(context, cause)
def soft_reset(self): """Partially resets the internal state of this flow, allowing for the flow to be ran again from an interrupted state only. """ if self.state not in self.SOFT_RESETTABLE_STATES: raise exc.InvalidStateException(("Can not soft reset when" " in state %s") % (self.state)) self._change_state(None, states.PENDING)
def reset(self): """Fully resets the internal state of this flow, allowing for the flow to be ran again. Note: Listeners are also reset. """ if self.state not in self.RESETTABLE_STATES: raise exc.InvalidStateException(("Can not reset when" " in state %s") % (self.state)) self.notifier.reset() self.task_notifier.reset() self._change_state(None, states.PENDING)
def interrupt(self): """Attempts to interrupt the current flow and any tasks that are currently not running in the flow. Returns how many tasks were interrupted (if any). """ if self.state in self.UNINTERRUPTIBLE_STATES: raise exc.InvalidStateException(("Can not interrupt when" " in state %s") % (self.state)) # Note(harlowja): Do *not* acquire the lock here so that the flow may # be interrupted while running. This does mean the the above check may # not be valid but we can worry about that if it becomes an issue. old_state = self.state if old_state != states.INTERRUPTED: self._state = states.INTERRUPTED self.notifier.notify(self.state, details={ 'context': None, 'flow': self, 'old_state': old_state, }) return 0
def run(self, context, *args, **kwargs): """Executes the workflow.""" if self.state not in self.RUNNABLE_STATES: raise exc.InvalidStateException("Unable to run flow when " "in state %s" % (self.state))