Notify on task reversion
When a runner and its associated task are reverted we should fire off notifications that this is occuring so that others can perform various activities when this occurs. Change-Id: I3fc797255e5cacd0d5cff9cb6ec444ccb9e7ac2e
This commit is contained in:
@@ -172,7 +172,7 @@ class Flow(flow.Flow):
|
||||
# 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)
|
||||
rb = utils.Rollback(context, runner, self, self.task_notifier)
|
||||
self._accumulator.add(rb)
|
||||
self.task_notifier.notify(states.STARTED, details={
|
||||
'context': context,
|
||||
@@ -198,13 +198,7 @@ class Flow(flow.Flow):
|
||||
" 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.results[runner.uuid] = runner.result
|
||||
self.task_notifier.notify(states.SUCCESS, details={
|
||||
'context': context,
|
||||
'flow': self,
|
||||
|
||||
@@ -423,7 +423,8 @@ class Flow(flow.Flow):
|
||||
accum = utils.RollbackAccumulator()
|
||||
for r in self._graph.nodes_iter():
|
||||
if r.has_ran():
|
||||
accum.add(utils.RollbackTask(context, r.task, r.result))
|
||||
accum.add(utils.Rollback(context, r,
|
||||
self, self.task_notifier))
|
||||
try:
|
||||
self._change_state(context, states.REVERTING)
|
||||
accum.rollback(cause)
|
||||
|
||||
@@ -30,6 +30,7 @@ INTERRUPTED = 'INTERRUPTED'
|
||||
PENDING = 'PENDING'
|
||||
RESUMING = 'RESUMING'
|
||||
REVERTING = 'REVERTING'
|
||||
REVERTED = 'REVERTED'
|
||||
RUNNING = RUNNING
|
||||
STARTED = 'STARTED'
|
||||
SUCCESS = SUCCESS
|
||||
@@ -42,3 +43,5 @@ STARTED = STARTED
|
||||
SUCCESS = SUCCESS
|
||||
TIMED_OUT = 'TIMED_OUT'
|
||||
CANCELLED = CANCELLED
|
||||
REVERTED = REVERTED
|
||||
REVERTING = REVERTING
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
|
||||
from taskflow import decorators
|
||||
from taskflow import exceptions as exc
|
||||
from taskflow import states
|
||||
@@ -135,21 +137,43 @@ class LinearFlowTest(test.TestCase):
|
||||
wf.add(self.make_reverting_task(i))
|
||||
|
||||
run_context = {}
|
||||
capture_func, captured = self._capture_states()
|
||||
wf.task_notifier.register('*', capture_func)
|
||||
wf.run(run_context)
|
||||
|
||||
self.assertEquals(10, len(run_context))
|
||||
self.assertEquals(10, len(captured))
|
||||
for _k, v in run_context.items():
|
||||
self.assertEquals('passed', v)
|
||||
for _uuid, u_states in captured.items():
|
||||
self.assertEquals([states.STARTED, states.SUCCESS], u_states)
|
||||
|
||||
def _capture_states(self):
|
||||
capture_where = collections.defaultdict(list)
|
||||
|
||||
def do_capture(state, details):
|
||||
runner = details.get('runner')
|
||||
if not runner:
|
||||
return
|
||||
capture_where[runner.uuid].append(state)
|
||||
|
||||
return (do_capture, capture_where)
|
||||
|
||||
def test_reverting_flow(self):
|
||||
wf = lw.Flow("the-test-action")
|
||||
wf.add(self.make_reverting_task(1))
|
||||
wf.add(self.make_reverting_task(2, True))
|
||||
ok_uuid = wf.add(self.make_reverting_task(1))
|
||||
broke_uuid = wf.add(self.make_reverting_task(2, True))
|
||||
capture_func, captured = self._capture_states()
|
||||
wf.task_notifier.register('*', capture_func)
|
||||
|
||||
run_context = {}
|
||||
self.assertRaises(Exception, wf.run, run_context)
|
||||
self.assertEquals('reverted', run_context[1])
|
||||
self.assertEquals(1, len(run_context))
|
||||
self.assertEquals([states.STARTED, states.SUCCESS, states.REVERTING,
|
||||
states.REVERTED], captured[ok_uuid])
|
||||
self.assertEquals([states.STARTED, states.FAILURE, states.REVERTING,
|
||||
states.REVERTED], captured[broke_uuid])
|
||||
|
||||
def test_not_satisfied_inputs_previous(self):
|
||||
wf = lw.Flow("the-test-action")
|
||||
|
||||
@@ -327,7 +327,7 @@ class ThreadedFlowTest(test.TestCase):
|
||||
context = {}
|
||||
self.assertRaises(IOError, flo.run, context)
|
||||
self.assertEquals(states.FAILURE, flo.state)
|
||||
self.assertEquals(states.FAILURE, history[f_uuid][-1])
|
||||
self.assertEquals(states.REVERTED, history[f_uuid][-1])
|
||||
self.assertTrue(context.get('reverted'))
|
||||
|
||||
def test_failure_cancel_successors(self):
|
||||
@@ -351,7 +351,7 @@ class ThreadedFlowTest(test.TestCase):
|
||||
context = {}
|
||||
self.assertRaises(IOError, flo.run, context)
|
||||
self.assertEquals(states.FAILURE, flo.state)
|
||||
self.assertEquals(states.FAILURE, history[fq][-1])
|
||||
self.assertEquals(states.REVERTED, history[fq][-1])
|
||||
self.assertEquals(states.CANCELLED, history[af][-1])
|
||||
self.assertEquals(states.CANCELLED, history[af2][-1])
|
||||
|
||||
|
||||
@@ -25,12 +25,14 @@ import logging
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
import weakref
|
||||
|
||||
import threading2
|
||||
|
||||
from distutils import version
|
||||
|
||||
from taskflow.openstack.common import uuidutils
|
||||
from taskflow import states
|
||||
|
||||
TASK_FACTORY_ATTRIBUTE = '_TaskFlow_task_factory'
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -263,32 +265,10 @@ class FlowFailure(object):
|
||||
return self.runner.exc_info[1]
|
||||
|
||||
|
||||
class RollbackTask(object):
|
||||
"""A helper task that on being called will call the underlying callable
|
||||
tasks revert method (if said method exists).
|
||||
"""
|
||||
|
||||
def __init__(self, context, task, result):
|
||||
self.task = task
|
||||
self.result = result
|
||||
self.context = context
|
||||
|
||||
def __str__(self):
|
||||
return str(self.task)
|
||||
|
||||
def __call__(self, cause):
|
||||
if ((hasattr(self.task, "revert") and
|
||||
isinstance(self.task.revert, collections.Callable))):
|
||||
self.task.revert(self.context, self.result, cause)
|
||||
|
||||
|
||||
class Runner(object):
|
||||
"""A helper class that wraps a task and can find the needed inputs for
|
||||
the task to run, as well as providing a uuid and other useful functionality
|
||||
for users of the task.
|
||||
|
||||
TODO(harlowja): replace with the task details object or a subclass of
|
||||
that???
|
||||
"""
|
||||
|
||||
def __init__(self, task, uuid=None):
|
||||
@@ -332,7 +312,9 @@ class Runner(object):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.task.name
|
||||
if hasattr(self.task, 'name'):
|
||||
return self.task.name
|
||||
return '?'
|
||||
|
||||
def reset(self):
|
||||
self.result = None
|
||||
@@ -452,6 +434,43 @@ class TransitionNotifier(object):
|
||||
break
|
||||
|
||||
|
||||
class Rollback(object):
|
||||
"""A helper functor object that on being called will call the underlying
|
||||
runners tasks revert method (if said method exists) and do the appropriate
|
||||
notification to signal to others that the reverting is underway.
|
||||
"""
|
||||
|
||||
def __init__(self, context, runner, flow, notifier):
|
||||
self.runner = runner
|
||||
self.context = context
|
||||
self.notifier = notifier
|
||||
# Use weak references to give the GC a break.
|
||||
self.flow = weakref.proxy(flow)
|
||||
|
||||
def __str__(self):
|
||||
return "Rollback: %s" % (self.runner)
|
||||
|
||||
def _fire_notify(self, has_reverted):
|
||||
if self.notifier:
|
||||
if has_reverted:
|
||||
state = states.REVERTED
|
||||
else:
|
||||
state = states.REVERTING
|
||||
self.notifier.notify(state, details={
|
||||
'context': self.context,
|
||||
'flow': self.flow,
|
||||
'runner': self.runner,
|
||||
})
|
||||
|
||||
def __call__(self, cause):
|
||||
self._fire_notify(False)
|
||||
task = self.runner.task
|
||||
if ((hasattr(task, "revert") and
|
||||
isinstance(task.revert, collections.Callable))):
|
||||
task.revert(self.context, self.runner.result, cause)
|
||||
self._fire_notify(True)
|
||||
|
||||
|
||||
class RollbackAccumulator(object):
|
||||
"""A utility class that can help in organizing 'undo' like code
|
||||
so that said code be rolled back on failure (automatically or manually)
|
||||
|
||||
Reference in New Issue
Block a user