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:
Joshua Harlow
2013-09-04 12:47:26 -07:00
parent d6d4a93719
commit 23dfff4105
36 changed files with 899 additions and 1428 deletions

View File

@@ -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