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
|
# Add the task to be rolled back *immediately* so that even if
|
||||||
# the task fails while producing results it will be given a
|
# the task fails while producing results it will be given a
|
||||||
# chance to rollback.
|
# 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._accumulator.add(rb)
|
||||||
self.task_notifier.notify(states.STARTED, details={
|
self.task_notifier.notify(states.STARTED, details={
|
||||||
'context': context,
|
'context': context,
|
||||||
@@ -198,13 +198,7 @@ class Flow(flow.Flow):
|
|||||||
" object: %s", result)
|
" object: %s", result)
|
||||||
result = exc.InvalidStateException()
|
result = exc.InvalidStateException()
|
||||||
raise result
|
raise result
|
||||||
# Adjust the task result in the accumulator before
|
self.results[runner.uuid] = runner.result
|
||||||
# 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={
|
self.task_notifier.notify(states.SUCCESS, details={
|
||||||
'context': context,
|
'context': context,
|
||||||
'flow': self,
|
'flow': self,
|
||||||
|
|||||||
@@ -423,7 +423,8 @@ class Flow(flow.Flow):
|
|||||||
accum = utils.RollbackAccumulator()
|
accum = utils.RollbackAccumulator()
|
||||||
for r in self._graph.nodes_iter():
|
for r in self._graph.nodes_iter():
|
||||||
if r.has_ran():
|
if r.has_ran():
|
||||||
accum.add(utils.RollbackTask(context, r.task, r.result))
|
accum.add(utils.Rollback(context, r,
|
||||||
|
self, self.task_notifier))
|
||||||
try:
|
try:
|
||||||
self._change_state(context, states.REVERTING)
|
self._change_state(context, states.REVERTING)
|
||||||
accum.rollback(cause)
|
accum.rollback(cause)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ INTERRUPTED = 'INTERRUPTED'
|
|||||||
PENDING = 'PENDING'
|
PENDING = 'PENDING'
|
||||||
RESUMING = 'RESUMING'
|
RESUMING = 'RESUMING'
|
||||||
REVERTING = 'REVERTING'
|
REVERTING = 'REVERTING'
|
||||||
|
REVERTED = 'REVERTED'
|
||||||
RUNNING = RUNNING
|
RUNNING = RUNNING
|
||||||
STARTED = 'STARTED'
|
STARTED = 'STARTED'
|
||||||
SUCCESS = SUCCESS
|
SUCCESS = SUCCESS
|
||||||
@@ -42,3 +43,5 @@ STARTED = STARTED
|
|||||||
SUCCESS = SUCCESS
|
SUCCESS = SUCCESS
|
||||||
TIMED_OUT = 'TIMED_OUT'
|
TIMED_OUT = 'TIMED_OUT'
|
||||||
CANCELLED = CANCELLED
|
CANCELLED = CANCELLED
|
||||||
|
REVERTED = REVERTED
|
||||||
|
REVERTING = REVERTING
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
from taskflow import decorators
|
from taskflow import decorators
|
||||||
from taskflow import exceptions as exc
|
from taskflow import exceptions as exc
|
||||||
from taskflow import states
|
from taskflow import states
|
||||||
@@ -135,21 +137,43 @@ class LinearFlowTest(test.TestCase):
|
|||||||
wf.add(self.make_reverting_task(i))
|
wf.add(self.make_reverting_task(i))
|
||||||
|
|
||||||
run_context = {}
|
run_context = {}
|
||||||
|
capture_func, captured = self._capture_states()
|
||||||
|
wf.task_notifier.register('*', capture_func)
|
||||||
wf.run(run_context)
|
wf.run(run_context)
|
||||||
|
|
||||||
self.assertEquals(10, len(run_context))
|
self.assertEquals(10, len(run_context))
|
||||||
|
self.assertEquals(10, len(captured))
|
||||||
for _k, v in run_context.items():
|
for _k, v in run_context.items():
|
||||||
self.assertEquals('passed', v)
|
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):
|
def test_reverting_flow(self):
|
||||||
wf = lw.Flow("the-test-action")
|
wf = lw.Flow("the-test-action")
|
||||||
wf.add(self.make_reverting_task(1))
|
ok_uuid = wf.add(self.make_reverting_task(1))
|
||||||
wf.add(self.make_reverting_task(2, True))
|
broke_uuid = wf.add(self.make_reverting_task(2, True))
|
||||||
|
capture_func, captured = self._capture_states()
|
||||||
|
wf.task_notifier.register('*', capture_func)
|
||||||
|
|
||||||
run_context = {}
|
run_context = {}
|
||||||
self.assertRaises(Exception, wf.run, run_context)
|
self.assertRaises(Exception, wf.run, run_context)
|
||||||
self.assertEquals('reverted', run_context[1])
|
self.assertEquals('reverted', run_context[1])
|
||||||
self.assertEquals(1, len(run_context))
|
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):
|
def test_not_satisfied_inputs_previous(self):
|
||||||
wf = lw.Flow("the-test-action")
|
wf = lw.Flow("the-test-action")
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ class ThreadedFlowTest(test.TestCase):
|
|||||||
context = {}
|
context = {}
|
||||||
self.assertRaises(IOError, flo.run, context)
|
self.assertRaises(IOError, flo.run, context)
|
||||||
self.assertEquals(states.FAILURE, flo.state)
|
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'))
|
self.assertTrue(context.get('reverted'))
|
||||||
|
|
||||||
def test_failure_cancel_successors(self):
|
def test_failure_cancel_successors(self):
|
||||||
@@ -351,7 +351,7 @@ class ThreadedFlowTest(test.TestCase):
|
|||||||
context = {}
|
context = {}
|
||||||
self.assertRaises(IOError, flo.run, context)
|
self.assertRaises(IOError, flo.run, context)
|
||||||
self.assertEquals(states.FAILURE, flo.state)
|
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[af][-1])
|
||||||
self.assertEquals(states.CANCELLED, history[af2][-1])
|
self.assertEquals(states.CANCELLED, history[af2][-1])
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import types
|
import types
|
||||||
|
import weakref
|
||||||
|
|
||||||
import threading2
|
import threading2
|
||||||
|
|
||||||
from distutils import version
|
from distutils import version
|
||||||
|
|
||||||
from taskflow.openstack.common import uuidutils
|
from taskflow.openstack.common import uuidutils
|
||||||
|
from taskflow import states
|
||||||
|
|
||||||
TASK_FACTORY_ATTRIBUTE = '_TaskFlow_task_factory'
|
TASK_FACTORY_ATTRIBUTE = '_TaskFlow_task_factory'
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -263,32 +265,10 @@ class FlowFailure(object):
|
|||||||
return self.runner.exc_info[1]
|
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):
|
class Runner(object):
|
||||||
"""A helper class that wraps a task and can find the needed inputs for
|
"""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
|
the task to run, as well as providing a uuid and other useful functionality
|
||||||
for users of the task.
|
for users of the task.
|
||||||
|
|
||||||
TODO(harlowja): replace with the task details object or a subclass of
|
|
||||||
that???
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, task, uuid=None):
|
def __init__(self, task, uuid=None):
|
||||||
@@ -332,7 +312,9 @@ class Runner(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.task.name
|
if hasattr(self.task, 'name'):
|
||||||
|
return self.task.name
|
||||||
|
return '?'
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.result = None
|
self.result = None
|
||||||
@@ -452,6 +434,43 @@ class TransitionNotifier(object):
|
|||||||
break
|
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):
|
class RollbackAccumulator(object):
|
||||||
"""A utility class that can help in organizing 'undo' like code
|
"""A utility class that can help in organizing 'undo' like code
|
||||||
so that said code be rolled back on failure (automatically or manually)
|
so that said code be rolled back on failure (automatically or manually)
|
||||||
|
|||||||
Reference in New Issue
Block a user