657 lines
26 KiB
Python
657 lines
26 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from taskflow.patterns import graph_flow as gf
|
|
from taskflow.patterns import linear_flow as lf
|
|
from taskflow.patterns import unordered_flow as uf
|
|
|
|
import taskflow.engines
|
|
|
|
from taskflow import exceptions as exc
|
|
from taskflow import retry
|
|
from taskflow import states as st
|
|
from taskflow import test
|
|
from taskflow.tests import utils
|
|
from taskflow.utils import misc
|
|
|
|
|
|
class RetryTest(utils.EngineTestBase):
|
|
|
|
def test_run_empty_linear_flow(self):
|
|
flow = lf.Flow('flow-1', utils.OneReturnRetry(provides='x'))
|
|
engine = self._make_engine(flow)
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'x': 1})
|
|
|
|
def test_run_empty_unordered_flow(self):
|
|
flow = uf.Flow('flow-1', utils.OneReturnRetry(provides='x'))
|
|
engine = self._make_engine(flow)
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'x': 1})
|
|
|
|
def test_run_empty_graph_flow(self):
|
|
flow = gf.Flow('flow-1', utils.OneReturnRetry(provides='x'))
|
|
engine = self._make_engine(flow)
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'x': 1})
|
|
|
|
def test_states_retry_success_linear_flow(self):
|
|
flow = lf.Flow('flow-1', retry.Times(4, 'r1', provides='x')).add(
|
|
utils.SaveOrderTask("task1"),
|
|
utils.ConditionalTask("task2")
|
|
)
|
|
engine = self._make_engine(flow)
|
|
utils.register_notifiers(engine, self.values)
|
|
engine.storage.inject({'y': 2})
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 2, 'x': 2})
|
|
expected = ['flow RUNNING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1',
|
|
'task1 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2',
|
|
'task2 FAILURE',
|
|
'task2 REVERTING',
|
|
u'task2 reverted(Failure: RuntimeError: Woot!)',
|
|
'task2 REVERTED',
|
|
'task1 REVERTING',
|
|
'task1 reverted(5)',
|
|
'task1 REVERTED',
|
|
'r1 RETRYING',
|
|
'task1 PENDING',
|
|
'task2 PENDING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1',
|
|
'task1 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2',
|
|
'task2 SUCCESS',
|
|
'flow SUCCESS']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_states_retry_reverted_linear_flow(self):
|
|
flow = lf.Flow('flow-1', retry.Times(2, 'r1', provides='x')).add(
|
|
utils.SaveOrderTask("task1"),
|
|
utils.ConditionalTask("task2")
|
|
)
|
|
engine = self._make_engine(flow)
|
|
utils.register_notifiers(engine, self.values)
|
|
engine.storage.inject({'y': 4})
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 4})
|
|
expected = ['flow RUNNING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1',
|
|
'task1 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2',
|
|
'task2 FAILURE',
|
|
'task2 REVERTING',
|
|
u'task2 reverted(Failure: RuntimeError: Woot!)',
|
|
'task2 REVERTED',
|
|
'task1 REVERTING',
|
|
'task1 reverted(5)',
|
|
'task1 REVERTED',
|
|
'r1 RETRYING',
|
|
'task1 PENDING',
|
|
'task2 PENDING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1',
|
|
'task1 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2',
|
|
'task2 FAILURE',
|
|
'task2 REVERTING',
|
|
u'task2 reverted(Failure: RuntimeError: Woot!)',
|
|
'task2 REVERTED',
|
|
'task1 REVERTING',
|
|
'task1 reverted(5)',
|
|
'task1 REVERTED',
|
|
'r1 REVERTING',
|
|
'r1 REVERTED',
|
|
'flow REVERTED']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_states_retry_failure_linear_flow(self):
|
|
flow = lf.Flow('flow-1', retry.Times(2, 'r1', provides='x')).add(
|
|
utils.NastyTask("task1"),
|
|
utils.ConditionalTask("task2")
|
|
)
|
|
engine = self._make_engine(flow)
|
|
utils.register_notifiers(engine, self.values)
|
|
engine.storage.inject({'y': 4})
|
|
self.assertRaisesRegexp(RuntimeError, '^Gotcha', engine.run)
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 4, 'x': 1})
|
|
expected = ['flow RUNNING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2',
|
|
'task2 FAILURE',
|
|
'task2 REVERTING',
|
|
u'task2 reverted(Failure: RuntimeError: Woot!)',
|
|
'task2 REVERTED',
|
|
'task1 REVERTING',
|
|
'task1 FAILURE',
|
|
'flow FAILURE']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_states_retry_failure_nested_flow_fails(self):
|
|
flow = lf.Flow('flow-1', utils.retry.AlwaysRevert('r1')).add(
|
|
utils.TaskNoRequiresNoReturns("task1"),
|
|
lf.Flow('flow-2', retry.Times(3, 'r2', provides='x')).add(
|
|
utils.TaskNoRequiresNoReturns("task2"),
|
|
utils.ConditionalTask("task3")
|
|
),
|
|
utils.TaskNoRequiresNoReturns("task4")
|
|
)
|
|
engine = self._make_engine(flow)
|
|
utils.register_notifiers(engine, self.values)
|
|
engine.storage.inject({'y': 2})
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 2, 'x': 2})
|
|
expected = ['flow RUNNING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1 SUCCESS',
|
|
'r2 RUNNING',
|
|
'r2 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2 SUCCESS',
|
|
'task3 RUNNING',
|
|
'task3',
|
|
'task3 FAILURE',
|
|
'task3 REVERTING',
|
|
u'task3 reverted(Failure: RuntimeError: Woot!)',
|
|
'task3 REVERTED',
|
|
'task2 REVERTING',
|
|
'task2 REVERTED',
|
|
'r2 RETRYING',
|
|
'task2 PENDING',
|
|
'task3 PENDING',
|
|
'r2 RUNNING',
|
|
'r2 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2 SUCCESS',
|
|
'task3 RUNNING',
|
|
'task3',
|
|
'task3 SUCCESS',
|
|
'task4 RUNNING',
|
|
'task4 SUCCESS',
|
|
'flow SUCCESS']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_states_retry_failure_parent_flow_fails(self):
|
|
flow = lf.Flow('flow-1', retry.Times(3, 'r1', provides='x1')).add(
|
|
utils.TaskNoRequiresNoReturns("task1"),
|
|
lf.Flow('flow-2', retry.Times(3, 'r2', provides='x2')).add(
|
|
utils.TaskNoRequiresNoReturns("task2"),
|
|
utils.TaskNoRequiresNoReturns("task3")
|
|
),
|
|
utils.ConditionalTask("task4", rebind={'x': 'x1'})
|
|
)
|
|
engine = self._make_engine(flow)
|
|
utils.register_notifiers(engine, self.values)
|
|
engine.storage.inject({'y': 2})
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 2, 'x1': 2,
|
|
'x2': 1})
|
|
expected = ['flow RUNNING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1 SUCCESS',
|
|
'r2 RUNNING',
|
|
'r2 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2 SUCCESS',
|
|
'task3 RUNNING',
|
|
'task3 SUCCESS',
|
|
'task4 RUNNING',
|
|
'task4',
|
|
'task4 FAILURE',
|
|
'task4 REVERTING',
|
|
u'task4 reverted(Failure: RuntimeError: Woot!)',
|
|
'task4 REVERTED',
|
|
'task3 REVERTING',
|
|
'task3 REVERTED',
|
|
'task2 REVERTING',
|
|
'task2 REVERTED',
|
|
'r2 REVERTING',
|
|
'r2 REVERTED',
|
|
'task1 REVERTING',
|
|
'task1 REVERTED',
|
|
'r1 RETRYING',
|
|
'task1 PENDING',
|
|
'r2 PENDING',
|
|
'task2 PENDING',
|
|
'task3 PENDING',
|
|
'task4 PENDING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1 SUCCESS',
|
|
'r2 RUNNING',
|
|
'r2 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2 SUCCESS',
|
|
'task3 RUNNING',
|
|
'task3 SUCCESS',
|
|
'task4 RUNNING',
|
|
'task4',
|
|
'task4 SUCCESS',
|
|
'flow SUCCESS']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_unordered_flow_task_fails_parallel_tasks_should_be_reverted(self):
|
|
flow = uf.Flow('flow-1', retry.Times(3, 'r', provides='x')).add(
|
|
utils.SaveOrderTask("task1"),
|
|
utils.ConditionalTask("task2")
|
|
)
|
|
engine = self._make_engine(flow)
|
|
engine.storage.inject({'y': 2})
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 2, 'x': 2})
|
|
expected = ['task2',
|
|
'task1',
|
|
u'task2 reverted(Failure: RuntimeError: Woot!)',
|
|
'task1 reverted(5)',
|
|
'task2',
|
|
'task1']
|
|
self.assertIsContainsSameElements(self.values, expected)
|
|
|
|
def test_nested_flow_reverts_parent_retries(self):
|
|
retry1 = retry.Times(3, 'r1', provides='x')
|
|
retry2 = retry.Times(0, 'r2', provides='x2')
|
|
|
|
flow = lf.Flow('flow-1', retry1).add(
|
|
utils.SaveOrderTask("task1"),
|
|
lf.Flow('flow-2', retry2).add(utils.ConditionalTask("task2"))
|
|
)
|
|
engine = self._make_engine(flow)
|
|
engine.storage.inject({'y': 2})
|
|
utils.register_notifiers(engine, self.values)
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 2, 'x': 2, 'x2': 1})
|
|
expected = ['flow RUNNING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1',
|
|
'task1 SUCCESS',
|
|
'r2 RUNNING',
|
|
'r2 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2',
|
|
'task2 FAILURE',
|
|
'task2 REVERTING',
|
|
u'task2 reverted(Failure: RuntimeError: Woot!)',
|
|
'task2 REVERTED',
|
|
'r2 REVERTING',
|
|
'r2 REVERTED',
|
|
'task1 REVERTING',
|
|
'task1 reverted(5)',
|
|
'task1 REVERTED',
|
|
'r1 RETRYING',
|
|
'task1 PENDING',
|
|
'r2 PENDING',
|
|
'task2 PENDING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1',
|
|
'task1 SUCCESS',
|
|
'r2 RUNNING',
|
|
'r2 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2',
|
|
'task2 SUCCESS',
|
|
'flow SUCCESS']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_revert_all_retry(self):
|
|
flow = lf.Flow('flow-1', retry.Times(3, 'r1', provides='x')).add(
|
|
utils.SaveOrderTask("task1"),
|
|
lf.Flow('flow-2', retry.AlwaysRevertAll('r2')).add(
|
|
utils.ConditionalTask("task2"))
|
|
)
|
|
engine = self._make_engine(flow)
|
|
engine.storage.inject({'y': 2})
|
|
utils.register_notifiers(engine, self.values)
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 2})
|
|
expected = ['flow RUNNING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
'task1 RUNNING',
|
|
'task1',
|
|
'task1 SUCCESS',
|
|
'r2 RUNNING',
|
|
'r2 SUCCESS',
|
|
'task2 RUNNING',
|
|
'task2',
|
|
'task2 FAILURE',
|
|
'task2 REVERTING',
|
|
u'task2 reverted(Failure: RuntimeError: Woot!)',
|
|
'task2 REVERTED',
|
|
'r2 REVERTING',
|
|
'r2 REVERTED',
|
|
'task1 REVERTING',
|
|
'task1 reverted(5)',
|
|
'task1 REVERTED',
|
|
'r1 REVERTING',
|
|
'r1 REVERTED',
|
|
'flow REVERTED']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_restart_reverted_flow_with_retry(self):
|
|
flow = lf.Flow('test', retry=utils.OneReturnRetry(provides='x')).add(
|
|
utils.FailingTask('fail'))
|
|
engine = self._make_engine(flow)
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
|
|
def test_run_just_retry(self):
|
|
flow = utils.OneReturnRetry(provides='x')
|
|
engine = self._make_engine(flow)
|
|
self.assertRaisesRegexp(TypeError, 'Retry controller', engine.run)
|
|
|
|
def test_use_retry_as_a_task(self):
|
|
flow = lf.Flow('test').add(utils.OneReturnRetry(provides='x'))
|
|
engine = self._make_engine(flow)
|
|
self.assertRaisesRegexp(TypeError, 'Retry controller', engine.run)
|
|
|
|
def test_resume_flow_that_had_been_interrupted_during_retrying(self):
|
|
flow = lf.Flow('flow-1', retry.Times(3, 'r1')).add(
|
|
utils.SaveOrderTask('t1'),
|
|
utils.SaveOrderTask('t2'),
|
|
utils.SaveOrderTask('t3')
|
|
)
|
|
engine = self._make_engine(flow)
|
|
engine.compile()
|
|
utils.register_notifiers(engine, self.values)
|
|
engine.storage.set_task_state('r1', st.RETRYING)
|
|
engine.storage.set_task_state('t1', st.PENDING)
|
|
engine.storage.set_task_state('t2', st.REVERTED)
|
|
engine.storage.set_task_state('t3', st.REVERTED)
|
|
|
|
engine.run()
|
|
expected = ['flow RUNNING',
|
|
't2 PENDING',
|
|
't3 PENDING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
't1 RUNNING',
|
|
't1',
|
|
't1 SUCCESS',
|
|
't2 RUNNING',
|
|
't2',
|
|
't2 SUCCESS',
|
|
't3 RUNNING',
|
|
't3',
|
|
't3 SUCCESS',
|
|
'flow SUCCESS']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_resume_flow_that_should_be_retried(self):
|
|
flow = lf.Flow('flow-1', retry.Times(3, 'r1')).add(
|
|
utils.SaveOrderTask('t1'),
|
|
utils.SaveOrderTask('t2')
|
|
)
|
|
engine = self._make_engine(flow)
|
|
engine.compile()
|
|
utils.register_notifiers(engine, self.values)
|
|
engine.storage.set_atom_intention('r1', st.RETRY)
|
|
engine.storage.set_task_state('r1', st.SUCCESS)
|
|
engine.storage.set_task_state('t1', st.REVERTED)
|
|
engine.storage.set_task_state('t2', st.REVERTED)
|
|
|
|
engine.run()
|
|
expected = ['flow RUNNING',
|
|
'r1 RETRYING',
|
|
't1 PENDING',
|
|
't2 PENDING',
|
|
'r1 RUNNING',
|
|
'r1 SUCCESS',
|
|
't1 RUNNING',
|
|
't1',
|
|
't1 SUCCESS',
|
|
't2 RUNNING',
|
|
't2',
|
|
't2 SUCCESS',
|
|
'flow SUCCESS']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_retry_tasks_that_has_not_been_reverted(self):
|
|
flow = lf.Flow('flow-1', retry.Times(3, 'r1', provides='x')).add(
|
|
utils.ConditionalTask('c'),
|
|
utils.SaveOrderTask('t1')
|
|
)
|
|
engine = self._make_engine(flow)
|
|
engine.storage.inject({'y': 2})
|
|
engine.run()
|
|
expected = ['c',
|
|
u'c reverted(Failure: RuntimeError: Woot!)',
|
|
'c',
|
|
't1']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_default_times_retry(self):
|
|
flow = lf.Flow('flow-1', retry.Times(3, 'r1')).add(
|
|
utils.SaveOrderTask('t1'),
|
|
utils.FailingTask('t2'))
|
|
engine = self._make_engine(flow)
|
|
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
expected = ['t1',
|
|
u't2 reverted(Failure: RuntimeError: Woot!)',
|
|
't1 reverted(5)',
|
|
't1',
|
|
u't2 reverted(Failure: RuntimeError: Woot!)',
|
|
't1 reverted(5)',
|
|
't1',
|
|
u't2 reverted(Failure: RuntimeError: Woot!)',
|
|
't1 reverted(5)']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_for_each_with_list(self):
|
|
collection = [3, 2, 3, 5]
|
|
retry1 = retry.ForEach(collection, 'r1', provides='x')
|
|
flow = lf.Flow('flow-1', retry1).add(utils.FailingTaskWithOneArg('t1'))
|
|
engine = self._make_engine(flow)
|
|
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
expected = [u't1 reverted(Failure: RuntimeError: Woot with 3)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 2)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 3)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 5)']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_for_each_with_set(self):
|
|
collection = ([3, 2, 5])
|
|
retry1 = retry.ForEach(collection, 'r1', provides='x')
|
|
flow = lf.Flow('flow-1', retry1).add(utils.FailingTaskWithOneArg('t1'))
|
|
engine = self._make_engine(flow)
|
|
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
expected = [u't1 reverted(Failure: RuntimeError: Woot with 3)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 2)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 5)']
|
|
self.assertIsContainsSameElements(self.values, expected)
|
|
|
|
def test_for_each_empty_collection(self):
|
|
values = []
|
|
retry1 = retry.ForEach(values, 'r1', provides='x')
|
|
flow = lf.Flow('flow-1', retry1).add(utils.ConditionalTask('t1'))
|
|
engine = self._make_engine(flow)
|
|
engine.storage.inject({'y': 1})
|
|
self.assertRaisesRegexp(exc.NotFound, '^No elements left', engine.run)
|
|
|
|
def test_parameterized_for_each_with_list(self):
|
|
values = [3, 2, 5]
|
|
retry1 = retry.ParameterizedForEach('r1', provides='x')
|
|
flow = lf.Flow('flow-1', retry1).add(utils.FailingTaskWithOneArg('t1'))
|
|
engine = self._make_engine(flow)
|
|
engine.storage.inject({'values': values, 'y': 1})
|
|
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
expected = [u't1 reverted(Failure: RuntimeError: Woot with 3)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 2)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 5)']
|
|
self.assertEqual(self.values, expected)
|
|
|
|
def test_parameterized_for_each_with_set(self):
|
|
values = ([3, 2, 5])
|
|
retry1 = retry.ParameterizedForEach('r1', provides='x')
|
|
flow = lf.Flow('flow-1', retry1).add(utils.FailingTaskWithOneArg('t1'))
|
|
engine = self._make_engine(flow)
|
|
engine.storage.inject({'values': values, 'y': 1})
|
|
|
|
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
|
|
expected = [u't1 reverted(Failure: RuntimeError: Woot with 3)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 2)',
|
|
u't1 reverted(Failure: RuntimeError: Woot with 5)']
|
|
self.assertIsContainsSameElements(self.values, expected)
|
|
|
|
def test_parameterized_for_each_empty_collection(self):
|
|
values = []
|
|
retry1 = retry.ParameterizedForEach('r1', provides='x')
|
|
flow = lf.Flow('flow-1', retry1).add(utils.ConditionalTask('t1'))
|
|
engine = self._make_engine(flow)
|
|
engine.storage.inject({'values': values, 'y': 1})
|
|
self.assertRaisesRegexp(exc.NotFound, '^No elements left', engine.run)
|
|
|
|
def test_retry_after_failure_before_processig_failure(self):
|
|
flow = uf.Flow('flow-1', retry.Times(3, provides='x')).add(
|
|
utils.SaveOrderTask('task1'))
|
|
engine = self._make_engine(flow)
|
|
engine.compile()
|
|
# imagine we run engine
|
|
engine.storage.set_flow_state(st.RUNNING)
|
|
engine.storage.set_atom_intention('flow-1_retry', st.EXECUTE)
|
|
engine.storage.set_atom_intention('task1', st.EXECUTE)
|
|
# we execute retry
|
|
engine.storage.save('flow-1_retry', 1)
|
|
# task fails (if we comment it out, it works)
|
|
engine.storage.save('task1',
|
|
misc.Failure.from_exception(RuntimeError('foo')),
|
|
state=st.FAILURE)
|
|
# then process die and we resume engine
|
|
engine.run()
|
|
|
|
def test_retry_fails(self):
|
|
|
|
class FailingRetry(retry.Retry):
|
|
|
|
def execute(self, **kwargs):
|
|
raise ValueError('OMG I FAILED')
|
|
|
|
def revert(self, history, **kwargs):
|
|
self.history = history
|
|
|
|
def on_failure(self, **kwargs):
|
|
return retry.REVERT
|
|
|
|
r = FailingRetry()
|
|
flow = lf.Flow('testflow', r)
|
|
self.assertRaisesRegexp(ValueError, '^OMG',
|
|
self._make_engine(flow).run)
|
|
self.assertEqual(len(r.history), 1)
|
|
self.assertEqual(r.history[0][1], {})
|
|
self.assertEqual(isinstance(r.history[0][0], misc.Failure), True)
|
|
|
|
|
|
class RetryParallelExecutionTest(utils.EngineTestBase):
|
|
|
|
def test_when_subflow_fails_revert_running_tasks(self):
|
|
waiting_task = utils.WaitForOneFromTask('task1', 'task2',
|
|
[st.SUCCESS, st.FAILURE])
|
|
flow = uf.Flow('flow-1', retry.Times(3, 'r', provides='x')).add(
|
|
waiting_task,
|
|
utils.ConditionalTask('task2')
|
|
)
|
|
engine = self._make_engine(flow)
|
|
engine.task_notifier.register('*', waiting_task.callback)
|
|
engine.storage.inject({'y': 2})
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 2, 'x': 2})
|
|
expected = ['task2',
|
|
'task1',
|
|
u'task2 reverted(Failure: RuntimeError: Woot!)',
|
|
'task1 reverted(5)',
|
|
'task2',
|
|
'task1']
|
|
self.assertIsContainsSameElements(self.values, expected)
|
|
|
|
def test_when_subflow_fails_revert_success_tasks(self):
|
|
waiting_task = utils.WaitForOneFromTask('task2', 'task1',
|
|
[st.SUCCESS, st.FAILURE])
|
|
flow = uf.Flow('flow-1', retry.Times(3, 'r', provides='x')).add(
|
|
utils.SaveOrderTask('task1'),
|
|
lf.Flow('flow-2').add(
|
|
waiting_task,
|
|
utils.ConditionalTask('task3'))
|
|
)
|
|
engine = self._make_engine(flow)
|
|
engine.task_notifier.register('*', waiting_task.callback)
|
|
engine.storage.inject({'y': 2})
|
|
engine.run()
|
|
self.assertEqual(engine.storage.fetch_all(), {'y': 2, 'x': 2})
|
|
expected = ['task1',
|
|
'task2',
|
|
'task3',
|
|
u'task3 reverted(Failure: RuntimeError: Woot!)',
|
|
'task1 reverted(5)',
|
|
'task2 reverted(5)',
|
|
'task1',
|
|
'task2',
|
|
'task3']
|
|
self.assertIsContainsSameElements(self.values, expected)
|
|
|
|
|
|
class SingleThreadedEngineTest(RetryTest,
|
|
test.TestCase):
|
|
def _make_engine(self, flow, flow_detail=None):
|
|
return taskflow.engines.load(flow,
|
|
flow_detail=flow_detail,
|
|
engine_conf='serial',
|
|
backend=self.backend)
|
|
|
|
|
|
class MultiThreadedEngineTest(RetryTest,
|
|
RetryParallelExecutionTest,
|
|
test.TestCase):
|
|
def _make_engine(self, flow, flow_detail=None, executor=None):
|
|
engine_conf = dict(engine='parallel',
|
|
executor=executor)
|
|
return taskflow.engines.load(flow, flow_detail=flow_detail,
|
|
engine_conf=engine_conf,
|
|
backend=self.backend)
|