Add a engine preparation stage
Move the final logic of the compilation stage (and pre-run) code into a new engine stage that is devoted to performing post-compile but pre-run actions. Do the final validation that was being done in compile() and run() in this stage instead of being split across those two stages. Breaking change: any user expecting storage to be ensured after compilation will have to adjust their code to call prepare (after which they can continue to expect storage to be ready). Change-Id: Ie97bc4a795266dda9d7ae8b395bcaadcd1ada737
This commit is contained in:
@@ -64,6 +64,7 @@ class ActionEngine(base.EngineBase):
|
||||
self._task_executor = None
|
||||
self._task_action = None
|
||||
self._retry_action = None
|
||||
self._storage_ensured = False
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %s" % (reflection.get_class_name(self), id(self))
|
||||
@@ -83,15 +84,7 @@ class ActionEngine(base.EngineBase):
|
||||
def run(self):
|
||||
"""Runs the flow in the engine to completion."""
|
||||
self.compile()
|
||||
external_provides = set(self.storage.fetch_all().keys())
|
||||
missing = self._flow.requires - external_provides
|
||||
if missing:
|
||||
raise exc.MissingDependencies(self._flow, sorted(missing))
|
||||
|
||||
if self.storage.get_flow_state() == states.REVERTED:
|
||||
self._root.reset_all()
|
||||
self._change_state(states.PENDING)
|
||||
|
||||
self.prepare()
|
||||
self._task_executor.start()
|
||||
try:
|
||||
self._run()
|
||||
@@ -145,6 +138,27 @@ class ActionEngine(base.EngineBase):
|
||||
self.storage.ensure_task(node.name, version, node.save_as)
|
||||
self._change_state(states.SUSPENDED) # does nothing in PENDING state
|
||||
|
||||
@lock_utils.locked
|
||||
def prepare(self):
|
||||
if not self._compiled:
|
||||
raise exc.InvalidState("Can not prepare an engine"
|
||||
" which has not been compiled")
|
||||
if not self._storage_ensured:
|
||||
self._ensure_storage_for(self.execution_graph)
|
||||
self._storage_ensured = True
|
||||
# At this point we can check to ensure all dependencies are either
|
||||
# flow/task provided or storage provided, if there are still missing
|
||||
# dependencies then this flow will fail at runtime (which we can avoid
|
||||
# by failing at preparation time).
|
||||
external_provides = set(self.storage.fetch_all().keys())
|
||||
missing = self._flow.requires - external_provides
|
||||
if missing:
|
||||
raise exc.MissingDependencies(self._flow, sorted(missing))
|
||||
# Reset everything back to pending (if we were previously reverted).
|
||||
if self.storage.get_flow_state() == states.REVERTED:
|
||||
self._root.reset_all()
|
||||
self._change_state(states.PENDING)
|
||||
|
||||
@lock_utils.locked
|
||||
def compile(self):
|
||||
if self._compiled:
|
||||
@@ -167,13 +181,8 @@ class ActionEngine(base.EngineBase):
|
||||
self.storage,
|
||||
self._task_action,
|
||||
self._retry_action)
|
||||
# NOTE(harlowja): Perform initial state manipulation and setup.
|
||||
#
|
||||
# TODO(harlowja): This doesn't seem like it should be in a compilation
|
||||
# function since compilation seems like it should not modify any
|
||||
# external state.
|
||||
self._ensure_storage_for(execution_graph)
|
||||
self._compiled = True
|
||||
return
|
||||
|
||||
|
||||
class SingleThreadedActionEngine(ActionEngine):
|
||||
|
||||
@@ -56,6 +56,16 @@ class EngineBase(object):
|
||||
indicating why this compilation could not be achieved.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def prepare(self):
|
||||
"""Performs any pre-run, but post-compilation actions.
|
||||
|
||||
NOTE(harlowja): During preparation it is currently assumed that the
|
||||
underlying storage will be initialized, all final dependencies
|
||||
will be verified, the tasks will be reset and the engine will enter
|
||||
the PENDING state.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def run(self):
|
||||
"""Runs the flow in the engine to completion (or die trying)."""
|
||||
|
||||
@@ -49,6 +49,7 @@ class TestProgress(test.TestCase):
|
||||
flow_detail=flow_detail,
|
||||
backend=backend)
|
||||
e.compile()
|
||||
e.prepare()
|
||||
return e
|
||||
|
||||
def tearDown(self):
|
||||
|
||||
@@ -394,6 +394,7 @@ class RetryTest(utils.EngineTestBase):
|
||||
)
|
||||
engine = self._make_engine(flow)
|
||||
engine.compile()
|
||||
engine.prepare()
|
||||
utils.register_notifiers(engine, self.values)
|
||||
engine.storage.set_atom_state('r1', st.RETRYING)
|
||||
engine.storage.set_atom_state('t1', st.PENDING)
|
||||
@@ -425,6 +426,7 @@ class RetryTest(utils.EngineTestBase):
|
||||
)
|
||||
engine = self._make_engine(flow)
|
||||
engine.compile()
|
||||
engine.prepare()
|
||||
utils.register_notifiers(engine, self.values)
|
||||
engine.storage.set_atom_intention('r1', st.RETRY)
|
||||
engine.storage.set_atom_state('r1', st.SUCCESS)
|
||||
@@ -551,6 +553,7 @@ class RetryTest(utils.EngineTestBase):
|
||||
utils.SaveOrderTask('task1'))
|
||||
engine = self._make_engine(flow)
|
||||
engine.compile()
|
||||
engine.prepare()
|
||||
# imagine we run engine
|
||||
engine.storage.set_flow_state(st.RUNNING)
|
||||
engine.storage.set_atom_intention('flow-1_retry', st.EXECUTE)
|
||||
@@ -662,6 +665,7 @@ class RetryTest(utils.EngineTestBase):
|
||||
utils.FailingTask('c')))
|
||||
engine = self._make_engine(flow)
|
||||
engine.compile()
|
||||
engine.prepare()
|
||||
engine.storage.save('test2_retry', 1)
|
||||
engine.storage.save('b', 11)
|
||||
engine.storage.save('a', 10)
|
||||
@@ -687,6 +691,7 @@ class RetryTest(utils.EngineTestBase):
|
||||
utils.SaveOrderTask('c')))
|
||||
engine = self._make_engine(flow)
|
||||
engine.compile()
|
||||
engine.prepare()
|
||||
engine.storage.save('test2_retry', 1)
|
||||
engine.storage.save('b', 11)
|
||||
# pretend that 'c' failed
|
||||
|
||||
Reference in New Issue
Block a user