Engine, task, linear_flow unification
In order to move away from the existing flows having their own implementation of running, start moving the existing flows to be patterns that only structure tasks (and impose constraints about how the group of tasks can run) in useful ways. Let the concept of running those patterns be handled by an engine instead of being handled by the flow itself. This will allow for varying engines to be able to run flows in whichever way the engine chooses (as long as the constraints set up by the flow are observed). Currently threaded flow and graph flow are broken by this commit, since they have not been converted to being a structure of tasks + constraints. The existing engine has not yet been modified to run those structures either, work is underway to remediate this. Part of: blueprint patterns-and-engines Followup bugs that must be addressed: Bug: 1221448 Bug: 1221505 Change-Id: I3a8b96179f336d1defe269728ebae0caa3d832d7
This commit is contained in:
185
taskflow/flow.py
185
taskflow/flow.py
@@ -17,82 +17,35 @@
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
import threading
|
||||
|
||||
from taskflow.openstack.common import uuidutils
|
||||
from taskflow import task
|
||||
from taskflow import utils
|
||||
|
||||
from taskflow import exceptions as exc
|
||||
from taskflow import states
|
||||
from taskflow.utils import flow_utils
|
||||
|
||||
def _class_name(obj):
|
||||
return ".".join([obj.__class__.__module__, obj.__class__.__name__])
|
||||
|
||||
|
||||
class Flow(object):
|
||||
"""The base abstract class of all flow implementations.
|
||||
|
||||
It provides a set of parents to flows that have a concept of parent flows
|
||||
as well as a state and state utility functions to the deriving classes. It
|
||||
also provides a name and an identifier (uuid or other) to the flow so that
|
||||
It provides a name and an identifier (uuid or other) to the flow so that
|
||||
it can be uniquely identifed among many flows.
|
||||
|
||||
Flows are expected to provide (if desired) the following methods:
|
||||
Flows are expected to provide the following methods:
|
||||
- add
|
||||
- add_many
|
||||
- interrupt
|
||||
- reset
|
||||
- rollback
|
||||
- run
|
||||
- soft_reset
|
||||
- __len__
|
||||
"""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
# Common states that certain actions can be performed in. If the flow
|
||||
# is not in these sets of states then it is likely that the flow operation
|
||||
# can not succeed.
|
||||
RESETTABLE_STATES = set([
|
||||
states.INTERRUPTED,
|
||||
states.SUCCESS,
|
||||
states.PENDING,
|
||||
states.FAILURE,
|
||||
])
|
||||
SOFT_RESETTABLE_STATES = set([
|
||||
states.INTERRUPTED,
|
||||
])
|
||||
UNINTERRUPTIBLE_STATES = set([
|
||||
states.FAILURE,
|
||||
states.SUCCESS,
|
||||
states.PENDING,
|
||||
])
|
||||
RUNNABLE_STATES = set([
|
||||
states.PENDING,
|
||||
])
|
||||
|
||||
def __init__(self, name, parents=None, uuid=None):
|
||||
def __init__(self, name, uuid=None):
|
||||
self._name = str(name)
|
||||
# The state of this flow.
|
||||
self._state = states.PENDING
|
||||
# If this flow has a parent flow/s which need to be reverted if
|
||||
# this flow fails then please include them here to allow this child
|
||||
# to call the parents...
|
||||
if parents:
|
||||
self.parents = tuple(parents)
|
||||
else:
|
||||
self.parents = tuple([])
|
||||
# Any objects that want to listen when a wf/task starts/stops/completes
|
||||
# or errors should be registered here. This can be used to monitor
|
||||
# progress and record tasks finishing (so that it becomes possible to
|
||||
# store the result of a task in some persistent or semi-persistent
|
||||
# storage backend).
|
||||
self.notifier = flow_utils.TransitionNotifier()
|
||||
self.task_notifier = flow_utils.TransitionNotifier()
|
||||
# Assign this flow a unique identifer.
|
||||
if uuid:
|
||||
self._id = str(uuid)
|
||||
else:
|
||||
self._id = uuidutils.generate_uuid()
|
||||
# Ensure we can not change the state at the same time in 2 different
|
||||
# threads.
|
||||
self._state_lock = threading.RLock()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -103,114 +56,28 @@ class Flow(object):
|
||||
def uuid(self):
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Provides a read-only view of the flow state."""
|
||||
return self._state
|
||||
|
||||
def _change_state(self, context, new_state, check_func=None, notify=True):
|
||||
old_state = None
|
||||
changed = False
|
||||
with self._state_lock:
|
||||
if self.state != new_state:
|
||||
if (not check_func or
|
||||
(check_func and check_func(self.state))):
|
||||
changed = True
|
||||
old_state = self.state
|
||||
self._state = new_state
|
||||
# Don't notify while holding the lock so that the reciever of said
|
||||
# notifications can actually perform operations on the given flow
|
||||
# without getting into deadlock.
|
||||
if notify and changed:
|
||||
self.notifier.notify(self.state, details={
|
||||
'context': context,
|
||||
'flow': self,
|
||||
'old_state': old_state,
|
||||
})
|
||||
return changed
|
||||
@abc.abstractmethod
|
||||
def __len__(self):
|
||||
"""Returns how many items are in this flow."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def __str__(self):
|
||||
lines = ["Flow: %s" % (self.name)]
|
||||
lines = ["%s: %s" % (_class_name(self), self.name)]
|
||||
lines.append("%s" % (self.uuid))
|
||||
lines.append("%s" % (len(self.parents)))
|
||||
lines.append("%s" % (self.state))
|
||||
lines.append("%s" % (len(self)))
|
||||
return "; ".join(lines)
|
||||
|
||||
@abc.abstractmethod
|
||||
def add(self, task):
|
||||
"""Adds a given task to this flow.
|
||||
|
||||
Returns the uuid that is associated with the task for later operations
|
||||
before and after it is ran.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_many(self, tasks):
|
||||
"""Adds many tasks to this flow.
|
||||
|
||||
Returns a list of uuids (one for each task added).
|
||||
"""
|
||||
uuids = []
|
||||
for t in tasks:
|
||||
uuids.append(self.add(t))
|
||||
return uuids
|
||||
|
||||
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).
|
||||
"""
|
||||
def check():
|
||||
if self.state in self.UNINTERRUPTIBLE_STATES:
|
||||
raise exc.InvalidStateException(("Can not interrupt when"
|
||||
" in state %s") % self.state)
|
||||
|
||||
check()
|
||||
with self._state_lock:
|
||||
check()
|
||||
self._change_state(None, states.INTERRUPTED)
|
||||
return 0
|
||||
|
||||
def reset(self):
|
||||
"""Fully resets the internal state of this flow, allowing for the flow
|
||||
to be ran again.
|
||||
|
||||
Note: Listeners are also reset.
|
||||
"""
|
||||
def check():
|
||||
if self.state not in self.RESETTABLE_STATES:
|
||||
raise exc.InvalidStateException(("Can not reset when"
|
||||
" in state %s") % self.state)
|
||||
|
||||
check()
|
||||
with self._state_lock:
|
||||
check()
|
||||
self.notifier.reset()
|
||||
self.task_notifier.reset()
|
||||
self._change_state(None, states.PENDING)
|
||||
|
||||
def soft_reset(self):
|
||||
"""Partially resets the internal state of this flow, allowing for the
|
||||
flow to be ran again from an interrupted state.
|
||||
"""
|
||||
def check():
|
||||
if self.state not in self.SOFT_RESETTABLE_STATES:
|
||||
raise exc.InvalidStateException(("Can not soft reset when"
|
||||
" in state %s") % self.state)
|
||||
|
||||
check()
|
||||
with self._state_lock:
|
||||
check()
|
||||
self._change_state(None, states.PENDING)
|
||||
def _extract_item(self, item):
|
||||
if isinstance(item, (task.BaseTask, Flow)):
|
||||
return item
|
||||
if issubclass(item, task.BaseTask):
|
||||
return item()
|
||||
task_factory = getattr(item, utils.TASK_FACTORY_ATTRIBUTE, None)
|
||||
if task_factory:
|
||||
return self._extract_item(task_factory(item))
|
||||
raise TypeError("Invalid item %r: it's not task and not flow" % item)
|
||||
|
||||
@abc.abstractmethod
|
||||
def run(self, context, *args, **kwargs):
|
||||
"""Executes the workflow."""
|
||||
def add(self, *items):
|
||||
"""Adds a given item/items to this flow."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def rollback(self, context, cause):
|
||||
"""Performs rollback of this workflow and any attached parent workflows
|
||||
if present.
|
||||
"""
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user