Merge "Retain atom 'revert' result (or failure)"
This commit is contained in:
		
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB | 
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB | 
| @@ -136,19 +136,25 @@ method returns. | |||||||
|  |  | ||||||
| **SUCCESS** - The engine running the task transitions the task to this state | **SUCCESS** - The engine running the task transitions the task to this state | ||||||
| after the task has finished successfully (ie no exception/s were raised during | after the task has finished successfully (ie no exception/s were raised during | ||||||
| execution). | running its :py:meth:`~taskflow.task.BaseTask.execute` method). | ||||||
|  |  | ||||||
| **FAILURE** - The engine running the task transitions the task to this state | **FAILURE** - The engine running the task transitions the task to this state | ||||||
| after it has finished with an error. | after it has finished with an error (ie exception/s were raised during | ||||||
|  | running its :py:meth:`~taskflow.task.BaseTask.execute` method). | ||||||
|  |  | ||||||
|  | **REVERT_FAILURE** - The engine running the task transitions the task to this | ||||||
|  | state after it has finished with an error (ie exception/s were raised during | ||||||
|  | running its :py:meth:`~taskflow.task.BaseTask.revert` method). | ||||||
|  |  | ||||||
| **REVERTING** - The engine running a task transitions the task to this state | **REVERTING** - The engine running a task transitions the task to this state | ||||||
| when the containing flow the engine is running starts to revert and | when the containing flow the engine is running starts to revert and | ||||||
| its :py:meth:`~taskflow.task.BaseTask.revert` method is called. Only tasks in | its :py:meth:`~taskflow.task.BaseTask.revert` method is called. Only tasks in | ||||||
| the ``SUCCESS`` or ``FAILURE`` state can be reverted.  If this method fails (ie | the ``SUCCESS`` or ``FAILURE`` state can be reverted.  If this method fails (ie | ||||||
| raises an exception), the task goes to the ``FAILURE`` state (if it was already | raises an exception), the task goes to the ``REVERT_FAILURE`` state. | ||||||
| in the ``FAILURE`` state then this is a no-op). |  | ||||||
|  |  | ||||||
| **REVERTED** - A task that has been reverted appears in this state. | **REVERTED** - The engine running the task transitions the task to this state | ||||||
|  | after it has successfully reverted the task (ie no exception/s were raised | ||||||
|  | during running its :py:meth:`~taskflow.task.BaseTask.revert` method). | ||||||
|  |  | ||||||
| Retry | Retry | ||||||
| ===== | ===== | ||||||
| @@ -188,17 +194,23 @@ state until its :py:meth:`~taskflow.retry.Retry.execute` method returns. | |||||||
| it was finished successfully (ie no exception/s were raised during | it was finished successfully (ie no exception/s were raised during | ||||||
| execution). | execution). | ||||||
|  |  | ||||||
| **FAILURE** - The engine running the retry transitions it to this state after | **FAILURE** - The engine running the retry transitions the retry to this state | ||||||
| it has finished with an error. | after it has finished with an error (ie exception/s were raised during | ||||||
|  | running its :py:meth:`~taskflow.retry.Retry.execute` method). | ||||||
|  |  | ||||||
|  | **REVERT_FAILURE** - The engine running the retry transitions the retry to | ||||||
|  | this state after it has finished with an error (ie exception/s were raised | ||||||
|  | during its :py:meth:`~taskflow.retry.Retry.revert` method). | ||||||
|  |  | ||||||
| **REVERTING** - The engine running the retry transitions to this state when | **REVERTING** - The engine running the retry transitions to this state when | ||||||
| the associated flow the engine is running starts to revert it and its | the associated flow the engine is running starts to revert it and its | ||||||
| :py:meth:`~taskflow.retry.Retry.revert` method is called. Only retries | :py:meth:`~taskflow.retry.Retry.revert` method is called. Only retries | ||||||
| in ``SUCCESS`` or ``FAILURE`` state can be reverted. If this method fails (ie | in ``SUCCESS`` or ``FAILURE`` state can be reverted. If this method fails (ie | ||||||
| raises an exception), the retry goes to the ``FAILURE`` state (if it was | raises an exception), the retry goes to the ``REVERT_FAILURE`` state. | ||||||
| already in the ``FAILURE`` state then this is a no-op). |  | ||||||
|  |  | ||||||
| **REVERTED** - A retry that has been reverted appears in this state. | **REVERTED** - The engine running the retry transitions the retry to this state | ||||||
|  | after it has successfully reverted the retry (ie no exception/s were raised | ||||||
|  | during running its :py:meth:`~taskflow.retry.Retry.revert` method). | ||||||
|  |  | ||||||
| **RETRYING** - If flow that is associated with the current retry was failed and | **RETRYING** - If flow that is associated with the current retry was failed and | ||||||
| reverted, the engine prepares the flow for the next run and transitions the | reverted, the engine prepares the flow for the next run and transitions the | ||||||
|   | |||||||
| @@ -21,17 +21,19 @@ import six | |||||||
| from taskflow import states | from taskflow import states | ||||||
|  |  | ||||||
|  |  | ||||||
| #: Sentinel use to represent no-result (none can be a valid result...) |  | ||||||
| NO_RESULT = object() |  | ||||||
|  |  | ||||||
| #: States that are expected to/may have a result to save... |  | ||||||
| SAVE_RESULT_STATES = (states.SUCCESS, states.FAILURE) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @six.add_metaclass(abc.ABCMeta) | @six.add_metaclass(abc.ABCMeta) | ||||||
| class Action(object): | class Action(object): | ||||||
|     """An action that handles executing, state changes, ... of atoms.""" |     """An action that handles executing, state changes, ... of atoms.""" | ||||||
|  |  | ||||||
|  |     NO_RESULT = object() | ||||||
|  |     """ | ||||||
|  |     Sentinel use to represent lack of any result (none can be a valid result) | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     #: States that are expected to have a result to save... | ||||||
|  |     SAVE_RESULT_STATES = (states.SUCCESS, states.FAILURE, | ||||||
|  |                           states.REVERTED, states.REVERT_FAILURE) | ||||||
|  |  | ||||||
|     def __init__(self, storage, notifier): |     def __init__(self, storage, notifier): | ||||||
|         self._storage = storage |         self._storage = storage | ||||||
|         self._notifier = notifier |         self._notifier = notifier | ||||||
|   | |||||||
| @@ -61,19 +61,21 @@ class RetryAction(base.Action): | |||||||
|             arguments.update(addons) |             arguments.update(addons) | ||||||
|         return arguments |         return arguments | ||||||
|  |  | ||||||
|     def change_state(self, retry, state, result=base.NO_RESULT): |     def change_state(self, retry, state, result=base.Action.NO_RESULT): | ||||||
|         old_state = self._storage.get_atom_state(retry.name) |         old_state = self._storage.get_atom_state(retry.name) | ||||||
|         if state in base.SAVE_RESULT_STATES: |         if state in self.SAVE_RESULT_STATES: | ||||||
|             save_result = None |             save_result = None | ||||||
|             if result is not base.NO_RESULT: |             if result is not self.NO_RESULT: | ||||||
|                 save_result = result |                 save_result = result | ||||||
|             self._storage.save(retry.name, save_result, state) |             self._storage.save(retry.name, save_result, state) | ||||||
|         elif state == states.REVERTED: |             # TODO(harlowja): combine this with the save to avoid a call | ||||||
|  |             # back into the persistence layer... | ||||||
|  |             if state == states.REVERTED: | ||||||
|                 self._storage.cleanup_retry_history(retry.name, state) |                 self._storage.cleanup_retry_history(retry.name, state) | ||||||
|         else: |         else: | ||||||
|             if state == old_state: |             if state == old_state: | ||||||
|                 # NOTE(imelnikov): nothing really changed, so we should not |                 # NOTE(imelnikov): nothing really changed, so we should not | ||||||
|                 # write anything to storage and run notifications |                 # write anything to storage and run notifications. | ||||||
|                 return |                 return | ||||||
|             self._storage.set_atom_state(retry.name, state) |             self._storage.set_atom_state(retry.name, state) | ||||||
|         retry_uuid = self._storage.get_atom_uuid(retry.name) |         retry_uuid = self._storage.get_atom_uuid(retry.name) | ||||||
| @@ -82,7 +84,7 @@ class RetryAction(base.Action): | |||||||
|             'retry_uuid': retry_uuid, |             'retry_uuid': retry_uuid, | ||||||
|             'old_state': old_state, |             'old_state': old_state, | ||||||
|         } |         } | ||||||
|         if result is not base.NO_RESULT: |         if result is not self.NO_RESULT: | ||||||
|             details['result'] = result |             details['result'] = result | ||||||
|         self._notifier.notify(state, details) |         self._notifier.notify(state, details) | ||||||
|  |  | ||||||
| @@ -107,9 +109,9 @@ class RetryAction(base.Action): | |||||||
|         def _on_done_callback(fut): |         def _on_done_callback(fut): | ||||||
|             result = fut.result()[-1] |             result = fut.result()[-1] | ||||||
|             if isinstance(result, failure.Failure): |             if isinstance(result, failure.Failure): | ||||||
|                 self.change_state(retry, states.FAILURE) |                 self.change_state(retry, states.REVERT_FAILURE, result=result) | ||||||
|             else: |             else: | ||||||
|                 self.change_state(retry, states.REVERTED) |                 self.change_state(retry, states.REVERTED, result=result) | ||||||
|  |  | ||||||
|         self.change_state(retry, states.REVERTING) |         self.change_state(retry, states.REVERTING) | ||||||
|         arg_addons = { |         arg_addons = { | ||||||
|   | |||||||
| @@ -32,8 +32,8 @@ class TaskAction(base.Action): | |||||||
|         super(TaskAction, self).__init__(storage, notifier) |         super(TaskAction, self).__init__(storage, notifier) | ||||||
|         self._task_executor = task_executor |         self._task_executor = task_executor | ||||||
|  |  | ||||||
|     def _is_identity_transition(self, old_state, state, task, progress): |     def _is_identity_transition(self, old_state, state, task, progress=None): | ||||||
|         if state in base.SAVE_RESULT_STATES: |         if state in self.SAVE_RESULT_STATES: | ||||||
|             # saving result is never identity transition |             # saving result is never identity transition | ||||||
|             return False |             return False | ||||||
|         if state != old_state: |         if state != old_state: | ||||||
| @@ -50,16 +50,17 @@ class TaskAction(base.Action): | |||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     def change_state(self, task, state, |     def change_state(self, task, state, | ||||||
|                      result=base.NO_RESULT, progress=None): |                      progress=None, result=base.Action.NO_RESULT): | ||||||
|         old_state = self._storage.get_atom_state(task.name) |         old_state = self._storage.get_atom_state(task.name) | ||||||
|         if self._is_identity_transition(old_state, state, task, progress): |         if self._is_identity_transition(old_state, state, task, | ||||||
|  |                                         progress=progress): | ||||||
|             # NOTE(imelnikov): ignore identity transitions in order |             # NOTE(imelnikov): ignore identity transitions in order | ||||||
|             # to avoid extra write to storage backend and, what's |             # to avoid extra write to storage backend and, what's | ||||||
|             # more important, extra notifications |             # more important, extra notifications. | ||||||
|             return |             return | ||||||
|         if state in base.SAVE_RESULT_STATES: |         if state in self.SAVE_RESULT_STATES: | ||||||
|             save_result = None |             save_result = None | ||||||
|             if result is not base.NO_RESULT: |             if result is not self.NO_RESULT: | ||||||
|                 save_result = result |                 save_result = result | ||||||
|             self._storage.save(task.name, save_result, state) |             self._storage.save(task.name, save_result, state) | ||||||
|         else: |         else: | ||||||
| @@ -72,7 +73,7 @@ class TaskAction(base.Action): | |||||||
|             'task_uuid': task_uuid, |             'task_uuid': task_uuid, | ||||||
|             'old_state': old_state, |             'old_state': old_state, | ||||||
|         } |         } | ||||||
|         if result is not base.NO_RESULT: |         if result is not self.NO_RESULT: | ||||||
|             details['result'] = result |             details['result'] = result | ||||||
|         self._notifier.notify(state, details) |         self._notifier.notify(state, details) | ||||||
|         if progress is not None: |         if progress is not None: | ||||||
| @@ -140,9 +141,10 @@ class TaskAction(base.Action): | |||||||
|  |  | ||||||
|     def complete_reversion(self, task, result): |     def complete_reversion(self, task, result): | ||||||
|         if isinstance(result, failure.Failure): |         if isinstance(result, failure.Failure): | ||||||
|             self.change_state(task, states.FAILURE) |             self.change_state(task, states.REVERT_FAILURE, result=result) | ||||||
|         else: |         else: | ||||||
|             self.change_state(task, states.REVERTED, progress=1.0) |             self.change_state(task, states.REVERTED, progress=1.0, | ||||||
|  |                               result=result) | ||||||
|  |  | ||||||
|     def wait_for_any(self, fs, timeout): |     def wait_for_any(self, fs, timeout): | ||||||
|         return self._task_executor.wait_for_any(fs, timeout) |         return self._task_executor.wait_for_any(fs, timeout) | ||||||
|   | |||||||
| @@ -152,6 +152,7 @@ class Completer(object): | |||||||
|             if event == ex.EXECUTED: |             if event == ex.EXECUTED: | ||||||
|                 self._process_atom_failure(node, result) |                 self._process_atom_failure(node, result) | ||||||
|             else: |             else: | ||||||
|  |                 # Reverting failed, always retain the failure... | ||||||
|                 return True |                 return True | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ | |||||||
|  |  | ||||||
| import collections | import collections | ||||||
| import contextlib | import contextlib | ||||||
|  | import itertools | ||||||
| import threading | import threading | ||||||
|  |  | ||||||
| from concurrent import futures | from concurrent import futures | ||||||
| @@ -194,8 +195,10 @@ class ActionEngine(base.Engine): | |||||||
|                 if last_state and last_state not in ignorable_states: |                 if last_state and last_state not in ignorable_states: | ||||||
|                     self._change_state(last_state) |                     self._change_state(last_state) | ||||||
|                     if last_state not in self.NO_RERAISING_STATES: |                     if last_state not in self.NO_RERAISING_STATES: | ||||||
|                         failures = self.storage.get_failures() |                         it = itertools.chain( | ||||||
|                         failure.Failure.reraise_if_any(failures.values()) |                             six.itervalues(self.storage.get_failures()), | ||||||
|  |                             six.itervalues(self.storage.get_revert_failures())) | ||||||
|  |                         failure.Failure.reraise_if_any(it) | ||||||
|  |  | ||||||
|     def _change_state(self, state): |     def _change_state(self, state): | ||||||
|         with self._state_lock: |         with self._state_lock: | ||||||
|   | |||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | #    Copyright (C) 2015 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. | ||||||
|  |  | ||||||
|  | """Add 'revert_results' and 'revert_failure' atom detail column. | ||||||
|  |  | ||||||
|  | Revision ID: 3162c0f3f8e4 | ||||||
|  | Revises: 589dccdf2b6e | ||||||
|  | Create Date: 2015-06-17 15:52:56.575245 | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | # revision identifiers, used by Alembic. | ||||||
|  | revision = '3162c0f3f8e4' | ||||||
|  | down_revision = '589dccdf2b6e' | ||||||
|  |  | ||||||
|  | from alembic import op | ||||||
|  | import sqlalchemy as sa | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def upgrade(): | ||||||
|  |     op.add_column('atomdetails', | ||||||
|  |                   sa.Column('revert_results', sa.Text(), nullable=True)) | ||||||
|  |     op.add_column('atomdetails', | ||||||
|  |                   sa.Column('revert_failure', sa.Text(), nullable=True)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def downgrade(): | ||||||
|  |     op.drop_column('atomdetails', 'revert_results') | ||||||
|  |     op.drop_column('atomdetails', 'revert_failure') | ||||||
| @@ -92,6 +92,8 @@ def fetch(metadata): | |||||||
|                                default=uuidutils.generate_uuid), |                                default=uuidutils.generate_uuid), | ||||||
|                         Column('failure', Json), |                         Column('failure', Json), | ||||||
|                         Column('results', Json), |                         Column('results', Json), | ||||||
|  |                         Column('revert_results', Json), | ||||||
|  |                         Column('revert_failure', Json), | ||||||
|                         Column('atom_type', Enum(*models.ATOM_TYPES, |                         Column('atom_type', Enum(*models.ATOM_TYPES, | ||||||
|                                                  name='atom_types')), |                                                  name='atom_types')), | ||||||
|                         Column('intention', Enum(*states.INTENTIONS, |                         Column('intention', Enum(*states.INTENTIONS, | ||||||
|   | |||||||
| @@ -32,6 +32,14 @@ LOG = logging.getLogger(__name__) | |||||||
|  |  | ||||||
| # Internal helpers... | # Internal helpers... | ||||||
|  |  | ||||||
|  | def _is_all_none(arg, *args): | ||||||
|  |     if arg is not None: | ||||||
|  |         return False | ||||||
|  |     for more_arg in args: | ||||||
|  |         if more_arg is not None: | ||||||
|  |             return False | ||||||
|  |     return True | ||||||
|  |  | ||||||
|  |  | ||||||
| def _copy_function(deep_copy): | def _copy_function(deep_copy): | ||||||
|     if deep_copy: |     if deep_copy: | ||||||
| @@ -413,11 +421,18 @@ class AtomDetail(object): | |||||||
|                    strategies). |                    strategies). | ||||||
|     :ivar results: Any results the atom produced from either its |     :ivar results: Any results the atom produced from either its | ||||||
|                    ``execute`` method or from other sources. |                    ``execute`` method or from other sources. | ||||||
|     :ivar failure: If the atom failed (possibly due to its ``execute`` |     :ivar revert_results: Any results the atom produced from either its | ||||||
|                    method raising) this will be a |                           ``revert`` method or from other sources. | ||||||
|  |     :ivar failure: If the atom failed (due to its ``execute`` method | ||||||
|  |                    raising) this will be a | ||||||
|                    :py:class:`~taskflow.types.failure.Failure` object that |                    :py:class:`~taskflow.types.failure.Failure` object that | ||||||
|                    represents that failure (if there was no failure this |                    represents that failure (if there was no failure this | ||||||
|                    will be set to none). |                    will be set to none). | ||||||
|  |     :ivar revert_failure: If the atom failed (possibly due to its ``revert`` | ||||||
|  |                           method raising) this will be a | ||||||
|  |                           :py:class:`~taskflow.types.failure.Failure` object | ||||||
|  |                           that represents that failure (if there was no | ||||||
|  |                           failure this will be set to none). | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, name, uuid): |     def __init__(self, name, uuid): | ||||||
| @@ -427,6 +442,8 @@ class AtomDetail(object): | |||||||
|         self.intention = states.EXECUTE |         self.intention = states.EXECUTE | ||||||
|         self.results = None |         self.results = None | ||||||
|         self.failure = None |         self.failure = None | ||||||
|  |         self.revert_results = None | ||||||
|  |         self.revert_failure = None | ||||||
|         self.meta = {} |         self.meta = {} | ||||||
|         self.version = None |         self.version = None | ||||||
|  |  | ||||||
| @@ -465,6 +482,8 @@ class AtomDetail(object): | |||||||
|         self.meta = ad.meta |         self.meta = ad.meta | ||||||
|         self.failure = ad.failure |         self.failure = ad.failure | ||||||
|         self.results = ad.results |         self.results = ad.results | ||||||
|  |         self.revert_results = ad.revert_results | ||||||
|  |         self.revert_failure = ad.revert_failure | ||||||
|         self.version = ad.version |         self.version = ad.version | ||||||
|         return self |         return self | ||||||
|  |  | ||||||
| @@ -503,6 +522,16 @@ class AtomDetail(object): | |||||||
|                     self.failure = other.failure |                     self.failure = other.failure | ||||||
|             else: |             else: | ||||||
|                 self.failure = None |                 self.failure = None | ||||||
|  |         if self.revert_failure != other.revert_failure: | ||||||
|  |             # NOTE(imelnikov): we can't just deep copy Failures, as they | ||||||
|  |             # contain tracebacks, which are not copyable. | ||||||
|  |             if other.revert_failure: | ||||||
|  |                 if deep_copy: | ||||||
|  |                     self.revert_failure = other.revert_failure.copy() | ||||||
|  |                 else: | ||||||
|  |                     self.revert_failure = other.revert_failure | ||||||
|  |             else: | ||||||
|  |                 self.revert_failure = None | ||||||
|         if self.meta != other.meta: |         if self.meta != other.meta: | ||||||
|             self.meta = copy_fn(other.meta) |             self.meta = copy_fn(other.meta) | ||||||
|         if self.version != other.version: |         if self.version != other.version: | ||||||
| @@ -522,11 +551,17 @@ class AtomDetail(object): | |||||||
|             failure = self.failure.to_dict() |             failure = self.failure.to_dict() | ||||||
|         else: |         else: | ||||||
|             failure = None |             failure = None | ||||||
|  |         if self.revert_failure: | ||||||
|  |             revert_failure = self.revert_failure.to_dict() | ||||||
|  |         else: | ||||||
|  |             revert_failure = None | ||||||
|         return { |         return { | ||||||
|             'failure': failure, |             'failure': failure, | ||||||
|  |             'revert_failure': revert_failure, | ||||||
|             'meta': self.meta, |             'meta': self.meta, | ||||||
|             'name': self.name, |             'name': self.name, | ||||||
|             'results': self.results, |             'results': self.results, | ||||||
|  |             'revert_results': self.revert_results, | ||||||
|             'state': self.state, |             'state': self.state, | ||||||
|             'version': self.version, |             'version': self.version, | ||||||
|             'intention': self.intention, |             'intention': self.intention, | ||||||
| @@ -547,11 +582,15 @@ class AtomDetail(object): | |||||||
|         obj.state = data.get('state') |         obj.state = data.get('state') | ||||||
|         obj.intention = data.get('intention') |         obj.intention = data.get('intention') | ||||||
|         obj.results = data.get('results') |         obj.results = data.get('results') | ||||||
|  |         obj.revert_results = data.get('revert_results') | ||||||
|         obj.version = data.get('version') |         obj.version = data.get('version') | ||||||
|         obj.meta = _fix_meta(data) |         obj.meta = _fix_meta(data) | ||||||
|         failure = data.get('failure') |         failure = data.get('failure') | ||||||
|         if failure: |         if failure: | ||||||
|             obj.failure = ft.Failure.from_dict(failure) |             obj.failure = ft.Failure.from_dict(failure) | ||||||
|  |         revert_failure = data.get('revert_failure') | ||||||
|  |         if revert_failure: | ||||||
|  |             obj.revert_failure = ft.Failure.from_dict(revert_failure) | ||||||
|         return obj |         return obj | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
| @@ -582,47 +621,65 @@ class TaskDetail(AtomDetail): | |||||||
|     def reset(self, state): |     def reset(self, state): | ||||||
|         """Resets this task detail and sets ``state`` attribute value. |         """Resets this task detail and sets ``state`` attribute value. | ||||||
|  |  | ||||||
|         This sets any previously set ``results`` and ``failure`` attributes |         This sets any previously set ``results``, ``failure``, | ||||||
|         back to ``None`` and sets the state to the provided one, as well as |         and ``revert_results`` attributes back to ``None`` and sets the | ||||||
|         setting this task details ``intention`` attribute to ``EXECUTE``. |         state to the provided one, as well as setting this task | ||||||
|  |         details ``intention`` attribute to ``EXECUTE``. | ||||||
|         """ |         """ | ||||||
|         self.results = None |         self.results = None | ||||||
|         self.failure = None |         self.failure = None | ||||||
|  |         self.revert_results = None | ||||||
|  |         self.revert_failure = None | ||||||
|         self.state = state |         self.state = state | ||||||
|         self.intention = states.EXECUTE |         self.intention = states.EXECUTE | ||||||
|  |  | ||||||
|     def put(self, state, result): |     def put(self, state, result): | ||||||
|         """Puts a result (acquired in the given state) into this detail. |         """Puts a result (acquired in the given state) into this detail. | ||||||
|  |  | ||||||
|         If the result is a :py:class:`~taskflow.types.failure.Failure` object |         Returns whether this object was modified (or whether it was not). | ||||||
|         then the ``failure`` attribute will be set (and the ``results`` |  | ||||||
|         attribute will be set to ``None``); if the result is not a |  | ||||||
|         :py:class:`~taskflow.types.failure.Failure` object then the |  | ||||||
|         ``results`` attribute will be set (and the ``failure`` attribute |  | ||||||
|         will be set to ``None``). In either case the ``state`` |  | ||||||
|         attribute will be set to the provided state. |  | ||||||
|         """ |         """ | ||||||
|         was_altered = False |         was_altered = False | ||||||
|         if self.state != state: |         if state != self.state: | ||||||
|             self.state = state |             self.state = state | ||||||
|             was_altered = True |             was_altered = True | ||||||
|         if self._was_failure(state, result): |         if state == states.REVERT_FAILURE: | ||||||
|  |             if self.revert_failure != result: | ||||||
|  |                 self.revert_failure = result | ||||||
|  |                 was_altered = True | ||||||
|  |             if not _is_all_none(self.results, self.revert_results): | ||||||
|  |                 self.results = None | ||||||
|  |                 self.revert_results = None | ||||||
|  |                 was_altered = True | ||||||
|  |         elif state == states.FAILURE: | ||||||
|             if self.failure != result: |             if self.failure != result: | ||||||
|                 self.failure = result |                 self.failure = result | ||||||
|                 was_altered = True |                 was_altered = True | ||||||
|             if self.results is not None: |             if not _is_all_none(self.results, self.revert_results, | ||||||
|  |                                 self.revert_failure): | ||||||
|                 self.results = None |                 self.results = None | ||||||
|  |                 self.revert_results = None | ||||||
|  |                 self.revert_failure = None | ||||||
|  |                 was_altered = True | ||||||
|  |         elif state == states.SUCCESS: | ||||||
|  |             if not _is_all_none(self.revert_results, self.revert_failure, | ||||||
|  |                                 self.failure): | ||||||
|  |                 self.revert_results = None | ||||||
|  |                 self.revert_failure = None | ||||||
|  |                 self.failure = None | ||||||
|                 was_altered = True |                 was_altered = True | ||||||
|         else: |  | ||||||
|             # We don't really have the ability to determine equality of |             # We don't really have the ability to determine equality of | ||||||
|             # task (user) results at the current time, without making |             # task (user) results at the current time, without making | ||||||
|             # potentially bad guesses, so assume the task detail always needs |             # potentially bad guesses, so assume the task detail always needs | ||||||
|             # to be saved if they are not exactly equivalent... |             # to be saved if they are not exactly equivalent... | ||||||
|             if self.results is not result: |             if result is not self.results: | ||||||
|                 self.results = result |                 self.results = result | ||||||
|                 was_altered = True |                 was_altered = True | ||||||
|             if self.failure is not None: |         elif state == states.REVERTED: | ||||||
|                 self.failure = None |             if not _is_all_none(self.revert_failure): | ||||||
|  |                 self.revert_failure = None | ||||||
|  |                 was_altered = True | ||||||
|  |             if result is not self.revert_results: | ||||||
|  |                 self.revert_results = result | ||||||
|                 was_altered = True |                 was_altered = True | ||||||
|         return was_altered |         return was_altered | ||||||
|  |  | ||||||
| @@ -630,10 +687,11 @@ class TaskDetail(AtomDetail): | |||||||
|         """Merges the current task detail with the given one. |         """Merges the current task detail with the given one. | ||||||
|  |  | ||||||
|         NOTE(harlowja): This merge does **not** copy and replace |         NOTE(harlowja): This merge does **not** copy and replace | ||||||
|         the ``results`` attribute if it differs. Instead the current |         the ``results`` or ``revert_results`` if it differs. Instead the | ||||||
|         objects ``results`` attribute directly becomes (via assignment) the |         current objects ``results`` and ``revert_results`` attributes directly | ||||||
|         other objects ``results`` attribute. Also note that if the provided |         becomes (via assignment) the other objects attributes. Also note that | ||||||
|         object is this object itself then **no** merging is done. |         if the provided object is this object itself then **no** merging is | ||||||
|  |         done. | ||||||
|  |  | ||||||
|         See: https://bugs.launchpad.net/taskflow/+bug/1452978 for |         See: https://bugs.launchpad.net/taskflow/+bug/1452978 for | ||||||
|         what happens if this is copied at a deeper level (for example by |         what happens if this is copied at a deeper level (for example by | ||||||
| @@ -648,8 +706,8 @@ class TaskDetail(AtomDetail): | |||||||
|         if other is self: |         if other is self: | ||||||
|             return self |             return self | ||||||
|         super(TaskDetail, self).merge(other, deep_copy=deep_copy) |         super(TaskDetail, self).merge(other, deep_copy=deep_copy) | ||||||
|         if self.results != other.results: |  | ||||||
|         self.results = other.results |         self.results = other.results | ||||||
|  |         self.revert_results = other.revert_results | ||||||
|         return self |         return self | ||||||
|  |  | ||||||
|     def copy(self): |     def copy(self): | ||||||
| @@ -659,10 +717,10 @@ class TaskDetail(AtomDetail): | |||||||
|         version information that this object maintains is shallow |         version information that this object maintains is shallow | ||||||
|         copied via ``copy.copy``). |         copied via ``copy.copy``). | ||||||
|  |  | ||||||
|         NOTE(harlowja): This copy does **not** perform ``copy.copy`` on |         NOTE(harlowja): This copy does **not** copy and replace | ||||||
|         the ``results`` attribute of this object (before assigning to the |         the ``results`` or ``revert_results`` attribute if it differs. Instead | ||||||
|         copy). Instead the current objects ``results`` attribute directly |         the current objects ``results`` and ``revert_results`` attributes | ||||||
|         becomes (via assignment) the copied objects ``results`` attribute. |         directly becomes (via assignment) the cloned objects attributes. | ||||||
|  |  | ||||||
|         See: https://bugs.launchpad.net/taskflow/+bug/1452978 for |         See: https://bugs.launchpad.net/taskflow/+bug/1452978 for | ||||||
|         what happens if this is copied at a deeper level (for example by |         what happens if this is copied at a deeper level (for example by | ||||||
| @@ -673,6 +731,7 @@ class TaskDetail(AtomDetail): | |||||||
|         """ |         """ | ||||||
|         clone = copy.copy(self) |         clone = copy.copy(self) | ||||||
|         clone.results = self.results |         clone.results = self.results | ||||||
|  |         clone.revert_results = self.revert_results | ||||||
|         if self.meta: |         if self.meta: | ||||||
|             clone.meta = self.meta.copy() |             clone.meta = self.meta.copy() | ||||||
|         if self.version: |         if self.version: | ||||||
| @@ -694,12 +753,15 @@ class RetryDetail(AtomDetail): | |||||||
|         """Resets this retry detail and sets ``state`` attribute value. |         """Resets this retry detail and sets ``state`` attribute value. | ||||||
|  |  | ||||||
|         This sets any previously added ``results`` back to an empty list |         This sets any previously added ``results`` back to an empty list | ||||||
|         and resets the ``failure`` attribute back to ``None`` and sets the |         and resets the ``failure`` and ``revert_failure`` and | ||||||
|         state to the provided one, as well as setting this atom |         ``revert_results`` attributes back to ``None`` and sets the state | ||||||
|  |         to the provided one, as well as setting this retry | ||||||
|         details ``intention`` attribute to ``EXECUTE``. |         details ``intention`` attribute to ``EXECUTE``. | ||||||
|         """ |         """ | ||||||
|         self.results = [] |         self.results = [] | ||||||
|  |         self.revert_results = None | ||||||
|         self.failure = None |         self.failure = None | ||||||
|  |         self.revert_failure = None | ||||||
|         self.state = state |         self.state = state | ||||||
|         self.intention = states.EXECUTE |         self.intention = states.EXECUTE | ||||||
|  |  | ||||||
| @@ -711,14 +773,15 @@ class RetryDetail(AtomDetail): | |||||||
|         copied via ``copy.copy``). |         copied via ``copy.copy``). | ||||||
|  |  | ||||||
|         NOTE(harlowja): This copy does **not** copy |         NOTE(harlowja): This copy does **not** copy | ||||||
|         the incoming objects ``results`` attribute. Instead this |         the incoming objects ``results`` or ``revert_results`` attributes. | ||||||
|         objects ``results`` attribute list is iterated over and a new list |         Instead this objects ``results`` attribute list is iterated over and | ||||||
|         is constructed with each ``(data, failures)`` element in that list |         a new list is constructed with each ``(data, failures)`` element in | ||||||
|         having its ``failures`` (a dictionary of each named |         that list having its ``failures`` (a dictionary of each named | ||||||
|         :py:class:`~taskflow.types.failure.Failure` object that |         :py:class:`~taskflow.types.failure.Failure` object that | ||||||
|         occured) copied but its ``data`` is left untouched. After |         occured) copied but its ``data`` is left untouched. After | ||||||
|         this is done that new list becomes (via assignment) the cloned |         this is done that new list becomes (via assignment) the cloned | ||||||
|         objects ``results`` attribute. |         objects ``results`` attribute. The ``revert_results`` is directly | ||||||
|  |         assigned to the cloned objects ``revert_results`` attribute. | ||||||
|  |  | ||||||
|         See: https://bugs.launchpad.net/taskflow/+bug/1452978 for |         See: https://bugs.launchpad.net/taskflow/+bug/1452978 for | ||||||
|         what happens if the ``data`` in ``results`` is copied at a |         what happens if the ``data`` in ``results`` is copied at a | ||||||
| @@ -738,6 +801,7 @@ class RetryDetail(AtomDetail): | |||||||
|                 copied_failures[key] = failure |                 copied_failures[key] = failure | ||||||
|             results.append((data, copied_failures)) |             results.append((data, copied_failures)) | ||||||
|         clone.results = results |         clone.results = results | ||||||
|  |         clone.revert_results = self.revert_results | ||||||
|         if self.meta: |         if self.meta: | ||||||
|             clone.meta = self.meta.copy() |             clone.meta = self.meta.copy() | ||||||
|         if self.version: |         if self.version: | ||||||
| @@ -771,21 +835,50 @@ class RetryDetail(AtomDetail): | |||||||
|     def put(self, state, result): |     def put(self, state, result): | ||||||
|         """Puts a result (acquired in the given state) into this detail. |         """Puts a result (acquired in the given state) into this detail. | ||||||
|  |  | ||||||
|         If the result is a :py:class:`~taskflow.types.failure.Failure` object |         Returns whether this object was modified (or whether it was not). | ||||||
|         then the ``failure`` attribute will be set; if the result is not a |  | ||||||
|         :py:class:`~taskflow.types.failure.Failure` object then the |  | ||||||
|         ``results`` attribute will be appended to (and the ``failure`` |  | ||||||
|         attribute will be set to ``None``). In either case the ``state`` |  | ||||||
|         attribute will be set to the provided state. |  | ||||||
|         """ |         """ | ||||||
|         # Do not clean retry history (only on reset does this happen). |         # Do not clean retry history (only on reset does this happen). | ||||||
|  |         was_altered = False | ||||||
|  |         if state != self.state: | ||||||
|             self.state = state |             self.state = state | ||||||
|         if self._was_failure(state, result): |             was_altered = True | ||||||
|  |         if state == states.REVERT_FAILURE: | ||||||
|  |             if result != self.revert_failure: | ||||||
|  |                 self.revert_failure = result | ||||||
|  |                 was_altered = True | ||||||
|  |             if not _is_all_none(self.revert_results): | ||||||
|  |                 self.revert_results = None | ||||||
|  |                 was_altered = True | ||||||
|  |         elif state == states.FAILURE: | ||||||
|  |             if result != self.failure: | ||||||
|                 self.failure = result |                 self.failure = result | ||||||
|         else: |                 was_altered = True | ||||||
|             self.results.append((result, {})) |             if not _is_all_none(self.revert_results, self.revert_failure): | ||||||
|  |                 self.revert_results = None | ||||||
|  |                 self.revert_failure = None | ||||||
|  |                 was_altered = True | ||||||
|  |         elif state == states.SUCCESS: | ||||||
|  |             if not _is_all_none(self.failure, self.revert_failure, | ||||||
|  |                                 self.revert_results): | ||||||
|                 self.failure = None |                 self.failure = None | ||||||
|         return True |                 self.revert_failure = None | ||||||
|  |                 self.revert_results = None | ||||||
|  |             # Track what we produced, so that we can examine it (or avoid | ||||||
|  |             # using it again). | ||||||
|  |             self.results.append((result, {})) | ||||||
|  |             was_altered = True | ||||||
|  |         elif state == states.REVERTED: | ||||||
|  |             # We don't really have the ability to determine equality of | ||||||
|  |             # task (user) results at the current time, without making | ||||||
|  |             # potentially bad guesses, so assume the retry detail always needs | ||||||
|  |             # to be saved if they are not exactly equivalent... | ||||||
|  |             if result is not self.revert_results: | ||||||
|  |                 self.revert_results = result | ||||||
|  |                 was_altered = True | ||||||
|  |             if not _is_all_none(self.revert_failure): | ||||||
|  |                 self.revert_failure = None | ||||||
|  |                 was_altered = True | ||||||
|  |         return was_altered | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def from_dict(cls, data): |     def from_dict(cls, data): | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ SUCCESS = SUCCESS | |||||||
| RUNNING = RUNNING | RUNNING = RUNNING | ||||||
| RETRYING = 'RETRYING' | RETRYING = 'RETRYING' | ||||||
| IGNORE = 'IGNORE' | IGNORE = 'IGNORE' | ||||||
|  | REVERT_FAILURE = 'REVERT_FAILURE' | ||||||
|  |  | ||||||
| # Atom intentions. | # Atom intentions. | ||||||
| EXECUTE = 'EXECUTE' | EXECUTE = 'EXECUTE' | ||||||
| @@ -157,20 +158,20 @@ def check_flow_transition(old_state, new_state): | |||||||
|  |  | ||||||
|  |  | ||||||
| # Task state transitions | # Task state transitions | ||||||
| # See: http://docs.openstack.org/developer/taskflow/states.html | # See: http://docs.openstack.org/developer/taskflow/states.html#task | ||||||
|  |  | ||||||
| _ALLOWED_TASK_TRANSITIONS = frozenset(( | _ALLOWED_TASK_TRANSITIONS = frozenset(( | ||||||
|     (PENDING, RUNNING),       # run it! |     (PENDING, RUNNING),       # run it! | ||||||
|     (PENDING, IGNORE),        # skip it! |     (PENDING, IGNORE),        # skip it! | ||||||
|  |  | ||||||
|     (RUNNING, SUCCESS),       # the task finished successfully |     (RUNNING, SUCCESS),       # the task executed successfully | ||||||
|     (RUNNING, FAILURE),       # the task failed |     (RUNNING, FAILURE),       # the task execution failed | ||||||
|  |  | ||||||
|     (FAILURE, REVERTING),     # task failed, do cleanup now |     (FAILURE, REVERTING),     # task execution failed, try reverting... | ||||||
|     (SUCCESS, REVERTING),     # some other task failed, do cleanup now |     (SUCCESS, REVERTING),     # some other task failed, try reverting... | ||||||
|  |  | ||||||
|     (REVERTING, REVERTED),    # revert done |     (REVERTING, REVERTED),           # the task reverted successfully | ||||||
|     (REVERTING, FAILURE),     # revert failed |     (REVERTING, REVERT_FAILURE),     # the task failed reverting (terminal!) | ||||||
|  |  | ||||||
|     (REVERTED, PENDING),      # try again |     (REVERTED, PENDING),      # try again | ||||||
|     (IGNORE, PENDING),        # try again |     (IGNORE, PENDING),        # try again | ||||||
|   | |||||||
| @@ -28,15 +28,43 @@ from taskflow.persistence import models | |||||||
| from taskflow import retry | from taskflow import retry | ||||||
| from taskflow import states | from taskflow import states | ||||||
| from taskflow import task | from taskflow import task | ||||||
| from taskflow.types import failure |  | ||||||
| from taskflow.utils import misc | from taskflow.utils import misc | ||||||
|  |  | ||||||
| LOG = logging.getLogger(__name__) | LOG = logging.getLogger(__name__) | ||||||
| STATES_WITH_RESULTS = (states.SUCCESS, states.REVERTING, states.FAILURE) |  | ||||||
|  |  | ||||||
|  | _EXECUTE_STATES_WITH_RESULTS = ( | ||||||
|  |     # The atom ``execute`` worked out :) | ||||||
|  |     states.SUCCESS, | ||||||
|  |     # The atom ``execute`` didn't work out :( | ||||||
|  |     states.FAILURE, | ||||||
|  |     # In this state we will still have access to prior SUCCESS (or FAILURE) | ||||||
|  |     # results, so make sure extraction is still allowed in this state... | ||||||
|  |     states.REVERTING, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | _REVERT_STATES_WITH_RESULTS = ( | ||||||
|  |     # The atom ``revert`` worked out :) | ||||||
|  |     states.REVERTED, | ||||||
|  |     # The atom ``revert`` didn't work out :( | ||||||
|  |     states.REVERT_FAILURE, | ||||||
|  |     # In this state we will still have access to prior SUCCESS (or FAILURE) | ||||||
|  |     # results, so make sure extraction is still allowed in this state... | ||||||
|  |     states.REVERTING, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | # Atom states that may have results... | ||||||
|  | STATES_WITH_RESULTS = set() | ||||||
|  | STATES_WITH_RESULTS.update(_REVERT_STATES_WITH_RESULTS) | ||||||
|  | STATES_WITH_RESULTS.update(_EXECUTE_STATES_WITH_RESULTS) | ||||||
|  | STATES_WITH_RESULTS = tuple(sorted(STATES_WITH_RESULTS)) | ||||||
|  |  | ||||||
| # TODO(harlowja): do this better (via a singleton or something else...) | # TODO(harlowja): do this better (via a singleton or something else...) | ||||||
| _TRANSIENT_PROVIDER = object() | _TRANSIENT_PROVIDER = object() | ||||||
|  |  | ||||||
|  | # Only for these intentions will we cache any failures that happened... | ||||||
|  | _SAVE_FAILURE_INTENTIONS = (states.EXECUTE, states.REVERT) | ||||||
|  |  | ||||||
| # NOTE(harlowja): Perhaps the container is a dictionary-like object and that | # NOTE(harlowja): Perhaps the container is a dictionary-like object and that | ||||||
| # key does not exist (key error), or the container is a tuple/list and a | # key does not exist (key error), or the container is a tuple/list and a | ||||||
| # non-numeric key is being requested (index error), or there was no container | # non-numeric key is being requested (index error), or there was no container | ||||||
| @@ -164,8 +192,12 @@ class Storage(object): | |||||||
|         # so we cache failures here, in atom name -> failure mapping. |         # so we cache failures here, in atom name -> failure mapping. | ||||||
|         self._failures = {} |         self._failures = {} | ||||||
|         for ad in self._flowdetail: |         for ad in self._flowdetail: | ||||||
|  |             fail_cache = {} | ||||||
|             if ad.failure is not None: |             if ad.failure is not None: | ||||||
|                 self._failures[ad.name] = ad.failure |                 fail_cache[states.EXECUTE] = ad.failure | ||||||
|  |             if ad.revert_failure is not None: | ||||||
|  |                 fail_cache[states.REVERT] = ad.revert_failure | ||||||
|  |             self._failures[ad.name] = fail_cache | ||||||
|  |  | ||||||
|         self._atom_name_to_uuid = dict((ad.name, ad.uuid) |         self._atom_name_to_uuid = dict((ad.name, ad.uuid) | ||||||
|                                        for ad in self._flowdetail) |                                        for ad in self._flowdetail) | ||||||
| @@ -247,6 +279,7 @@ class Storage(object): | |||||||
|                 atom_ids[i] = ad.uuid |                 atom_ids[i] = ad.uuid | ||||||
|                 self._atom_name_to_uuid[atom_name] = ad.uuid |                 self._atom_name_to_uuid[atom_name] = ad.uuid | ||||||
|                 self._set_result_mapping(atom_name, atom.save_as) |                 self._set_result_mapping(atom_name, atom.save_as) | ||||||
|  |                 self._failures.setdefault(atom_name, {}) | ||||||
|         return atom_ids |         return atom_ids | ||||||
|  |  | ||||||
|     def ensure_atom(self, atom): |     def ensure_atom(self, atom): | ||||||
| @@ -448,21 +481,23 @@ class Storage(object): | |||||||
|                             "with index %r (name %s)", atom_name, index, name) |                             "with index %r (name %s)", atom_name, index, name) | ||||||
|  |  | ||||||
|     @fasteners.write_locked |     @fasteners.write_locked | ||||||
|     def save(self, atom_name, data, state=states.SUCCESS): |     def save(self, atom_name, result, state=states.SUCCESS): | ||||||
|         """Save result for named atom into storage with given state.""" |         """Put result for atom with provided name to storage.""" | ||||||
|         source, clone = self._atomdetail_by_name(atom_name, clone=True) |         source, clone = self._atomdetail_by_name(atom_name, clone=True) | ||||||
|         if clone.put(state, data): |         if clone.put(state, result): | ||||||
|             result = self._with_connection(self._save_atom_detail, |             self._with_connection(self._save_atom_detail, source, clone) | ||||||
|                                            source, clone) |         # We need to somehow place more of this responsibility on the atom | ||||||
|         else: |         # detail class itself, vs doing it here; since it ties those two | ||||||
|             result = clone |         # together (which is bad)... | ||||||
|         if state == states.FAILURE and isinstance(data, failure.Failure): |         if state in (states.FAILURE, states.REVERT_FAILURE): | ||||||
|             # NOTE(imelnikov): failure serialization looses information, |             # NOTE(imelnikov): failure serialization looses information, | ||||||
|             # so we cache failures here, in atom name -> failure mapping so |             # so we cache failures here, in atom name -> failure mapping so | ||||||
|             # that we can later use the better version on fetch/get. |             # that we can later use the better version on fetch/get. | ||||||
|             self._failures[result.name] = data |             if clone.intention in _SAVE_FAILURE_INTENTIONS: | ||||||
|         else: |                 fail_cache = self._failures[clone.name] | ||||||
|             self._check_all_results_provided(result.name, data) |                 fail_cache[clone.intention] = result | ||||||
|  |         if state == states.SUCCESS and clone.intention == states.EXECUTE: | ||||||
|  |             self._check_all_results_provided(clone.name, result) | ||||||
|  |  | ||||||
|     @fasteners.write_locked |     @fasteners.write_locked | ||||||
|     def save_retry_failure(self, retry_name, failed_atom_name, failure): |     def save_retry_failure(self, retry_name, failed_atom_name, failure): | ||||||
| @@ -491,39 +526,69 @@ class Storage(object): | |||||||
|         self._with_connection(self._save_atom_detail, source, clone) |         self._with_connection(self._save_atom_detail, source, clone) | ||||||
|  |  | ||||||
|     @fasteners.read_locked |     @fasteners.read_locked | ||||||
|     def _get(self, atom_name, only_last=False): |     def _get(self, atom_name, | ||||||
|  |              results_attr_name, fail_attr_name, | ||||||
|  |              allowed_states, fail_cache_key): | ||||||
|         source, _clone = self._atomdetail_by_name(atom_name) |         source, _clone = self._atomdetail_by_name(atom_name) | ||||||
|         if source.failure is not None: |         failure = getattr(source, fail_attr_name) | ||||||
|             cached = self._failures.get(atom_name) |         if failure is not None: | ||||||
|             if source.failure.matches(cached): |             fail_cache = self._failures[atom_name] | ||||||
|                 # Try to give the version back that should have the backtrace |             try: | ||||||
|                 # instead of one that has it stripped (since backtraces are not |                 fail = fail_cache[fail_cache_key] | ||||||
|                 # serializable). |                 if failure.matches(fail): | ||||||
|                 return cached |                     # Try to give the version back that should have the | ||||||
|             return source.failure |                     # backtrace instead of one that has it | ||||||
|         if source.state not in STATES_WITH_RESULTS: |                     # stripped (since backtraces are not serializable). | ||||||
|  |                     failure = fail | ||||||
|  |             except KeyError: | ||||||
|  |                 pass | ||||||
|  |             return failure | ||||||
|  |         # TODO(harlowja): this seems like it should be checked before fetching | ||||||
|  |         # the potential failure, instead of after, fix this soon... | ||||||
|  |         if source.state not in allowed_states: | ||||||
|             raise exceptions.NotFound("Result for atom %s is not currently" |             raise exceptions.NotFound("Result for atom %s is not currently" | ||||||
|                                       " known" % atom_name) |                                       " known" % atom_name) | ||||||
|         if only_last: |         return getattr(source, results_attr_name) | ||||||
|             return source.last_results |  | ||||||
|         else: |  | ||||||
|             return source.results |  | ||||||
|  |  | ||||||
|     def get(self, atom_name): |     def get_execute_result(self, atom_name): | ||||||
|         """Gets the results for an atom with a given name from storage.""" |         """Gets the ``execute`` results for an atom from storage.""" | ||||||
|         return self._get(atom_name) |         return self._get(atom_name, 'results', 'failure', | ||||||
|  |                          _EXECUTE_STATES_WITH_RESULTS, states.EXECUTE) | ||||||
|  |  | ||||||
|     @fasteners.read_locked |     @fasteners.read_locked | ||||||
|     def get_failures(self): |     def _get_failures(self, fail_cache_key): | ||||||
|         """Get list of failures that happened with this flow. |         failures = {} | ||||||
|  |         for atom_name, fail_cache in six.iteritems(self._failures): | ||||||
|  |             try: | ||||||
|  |                 failures[atom_name] = fail_cache[fail_cache_key] | ||||||
|  |             except KeyError: | ||||||
|  |                 pass | ||||||
|  |         return failures | ||||||
|  |  | ||||||
|         No order guaranteed. |     def get_execute_failures(self): | ||||||
|         """ |         """Get all ``execute`` failures that happened with this flow.""" | ||||||
|         return self._failures.copy() |         return self._get_failures(states.EXECUTE) | ||||||
|  |  | ||||||
|  |     # TODO(harlowja): remove these in the future? | ||||||
|  |     get = get_execute_result | ||||||
|  |     get_failures = get_execute_failures | ||||||
|  |  | ||||||
|  |     def get_revert_result(self, atom_name): | ||||||
|  |         """Gets the ``revert`` results for an atom from storage.""" | ||||||
|  |         return self._get(atom_name, 'revert_results', 'revert_failure', | ||||||
|  |                          _REVERT_STATES_WITH_RESULTS, states.REVERT) | ||||||
|  |  | ||||||
|  |     def get_revert_failures(self): | ||||||
|  |         """Get all ``revert`` failures that happened with this flow.""" | ||||||
|  |         return self._get_failures(states.REVERT) | ||||||
|  |  | ||||||
|  |     @fasteners.read_locked | ||||||
|     def has_failures(self): |     def has_failures(self): | ||||||
|         """Returns True if there are failed tasks in the storage.""" |         """Returns true if there are **any** failures in storage.""" | ||||||
|         return bool(self._failures) |         for fail_cache in six.itervalues(self._failures): | ||||||
|  |             if fail_cache: | ||||||
|  |                 return True | ||||||
|  |         return False | ||||||
|  |  | ||||||
|     @fasteners.write_locked |     @fasteners.write_locked | ||||||
|     def reset(self, atom_name, state=states.PENDING): |     def reset(self, atom_name, state=states.PENDING): | ||||||
| @@ -534,8 +599,8 @@ class Storage(object): | |||||||
|         if source.state == state: |         if source.state == state: | ||||||
|             return |             return | ||||||
|         clone.reset(state) |         clone.reset(state) | ||||||
|         result = self._with_connection(self._save_atom_detail, source, clone) |         self._with_connection(self._save_atom_detail, source, clone) | ||||||
|         self._failures.pop(result.name, None) |         self._failures[clone.name].clear() | ||||||
|  |  | ||||||
|     def inject_atom_args(self, atom_name, pairs, transient=True): |     def inject_atom_args(self, atom_name, pairs, transient=True): | ||||||
|         """Add values into storage for a specific atom only. |         """Add values into storage for a specific atom only. | ||||||
| @@ -681,7 +746,7 @@ class Storage(object): | |||||||
|  |  | ||||||
|     @fasteners.read_locked |     @fasteners.read_locked | ||||||
|     def fetch(self, name, many_handler=None): |     def fetch(self, name, many_handler=None): | ||||||
|         """Fetch a named result.""" |         """Fetch a named ``execute`` result.""" | ||||||
|         def _many_handler(values): |         def _many_handler(values): | ||||||
|             # By default we just return the first of many (unless provided |             # By default we just return the first of many (unless provided | ||||||
|             # a different callback that can translate many results into |             # a different callback that can translate many results into | ||||||
| @@ -702,7 +767,10 @@ class Storage(object): | |||||||
|                                                 self._transients, name)) |                                                 self._transients, name)) | ||||||
|             else: |             else: | ||||||
|                 try: |                 try: | ||||||
|                     container = self._get(provider.name, only_last=True) |                     container = self._get(provider.name, | ||||||
|  |                                           'last_results', 'failure', | ||||||
|  |                                           _EXECUTE_STATES_WITH_RESULTS, | ||||||
|  |                                           states.EXECUTE) | ||||||
|                 except exceptions.NotFound: |                 except exceptions.NotFound: | ||||||
|                     pass |                     pass | ||||||
|                 else: |                 else: | ||||||
| @@ -717,7 +785,7 @@ class Storage(object): | |||||||
|     @fasteners.read_locked |     @fasteners.read_locked | ||||||
|     def fetch_unsatisfied_args(self, atom_name, args_mapping, |     def fetch_unsatisfied_args(self, atom_name, args_mapping, | ||||||
|                                scope_walker=None, optional_args=None): |                                scope_walker=None, optional_args=None): | ||||||
|         """Fetch unsatisfied atom arguments using an atoms argument mapping. |         """Fetch unsatisfied ``execute`` arguments using an atoms args mapping. | ||||||
|  |  | ||||||
|         NOTE(harlowja): this takes into account the provided scope walker |         NOTE(harlowja): this takes into account the provided scope walker | ||||||
|         atoms who should produce the required value at runtime, as well as |         atoms who should produce the required value at runtime, as well as | ||||||
| @@ -756,7 +824,9 @@ class Storage(object): | |||||||
|                     results = self._transients |                     results = self._transients | ||||||
|                 else: |                 else: | ||||||
|                     try: |                     try: | ||||||
|                         results = self._get(p.name, only_last=True) |                         results = self._get(p.name, 'last_results', 'failure', | ||||||
|  |                                             _EXECUTE_STATES_WITH_RESULTS, | ||||||
|  |                                             states.EXECUTE) | ||||||
|                     except exceptions.NotFound: |                     except exceptions.NotFound: | ||||||
|                         results = {} |                         results = {} | ||||||
|                 try: |                 try: | ||||||
| @@ -802,7 +872,7 @@ class Storage(object): | |||||||
|  |  | ||||||
|     @fasteners.read_locked |     @fasteners.read_locked | ||||||
|     def fetch_all(self, many_handler=None): |     def fetch_all(self, many_handler=None): | ||||||
|         """Fetch all named results known so far.""" |         """Fetch all named ``execute`` results known so far.""" | ||||||
|         def _many_handler(values): |         def _many_handler(values): | ||||||
|             if len(values) > 1: |             if len(values) > 1: | ||||||
|                 return values |                 return values | ||||||
| @@ -821,7 +891,7 @@ class Storage(object): | |||||||
|     def fetch_mapped_args(self, args_mapping, |     def fetch_mapped_args(self, args_mapping, | ||||||
|                           atom_name=None, scope_walker=None, |                           atom_name=None, scope_walker=None, | ||||||
|                           optional_args=None): |                           optional_args=None): | ||||||
|         """Fetch arguments for an atom using an atoms argument mapping.""" |         """Fetch ``execute`` arguments for an atom using its args mapping.""" | ||||||
|  |  | ||||||
|         def _extract_first_from(name, sources): |         def _extract_first_from(name, sources): | ||||||
|             """Extracts/returns first occurence of key in list of dicts.""" |             """Extracts/returns first occurence of key in list of dicts.""" | ||||||
| @@ -835,7 +905,9 @@ class Storage(object): | |||||||
|         def _get_results(looking_for, provider): |         def _get_results(looking_for, provider): | ||||||
|             """Gets the results saved for a given provider.""" |             """Gets the results saved for a given provider.""" | ||||||
|             try: |             try: | ||||||
|                 return self._get(provider.name, only_last=True) |                 return self._get(provider.name, 'last_results', 'failure', | ||||||
|  |                                  _EXECUTE_STATES_WITH_RESULTS, | ||||||
|  |                                  states.EXECUTE) | ||||||
|             except exceptions.NotFound: |             except exceptions.NotFound: | ||||||
|                 exceptions.raise_with_cause(exceptions.NotFound, |                 exceptions.raise_with_cause(exceptions.NotFound, | ||||||
|                                             "Expected to be able to find" |                                             "Expected to be able to find" | ||||||
| @@ -963,11 +1035,14 @@ class Storage(object): | |||||||
|             # NOTE(harlowja): Try to use our local cache to get a more |             # NOTE(harlowja): Try to use our local cache to get a more | ||||||
|             # complete failure object that has a traceback (instead of the |             # complete failure object that has a traceback (instead of the | ||||||
|             # one that is saved which will *typically* not have one)... |             # one that is saved which will *typically* not have one)... | ||||||
|             cached = self._failures.get(ad.name) |  | ||||||
|             if ad.failure.matches(cached): |  | ||||||
|                 failure = cached |  | ||||||
|             else: |  | ||||||
|             failure = ad.failure |             failure = ad.failure | ||||||
|  |             fail_cache = self._failures[ad.name] | ||||||
|  |             try: | ||||||
|  |                 fail = fail_cache[states.EXECUTE] | ||||||
|  |                 if failure.matches(fail): | ||||||
|  |                     failure = fail | ||||||
|  |             except KeyError: | ||||||
|  |                 pass | ||||||
|         return retry.History(ad.results, failure=failure) |         return retry.History(ad.results, failure=failure) | ||||||
|  |  | ||||||
|     @fasteners.read_locked |     @fasteners.read_locked | ||||||
|   | |||||||
| @@ -126,7 +126,8 @@ class RunnerTest(test.TestCase, _RunnerTestMixin): | |||||||
|         failure = failures[0] |         failure = failures[0] | ||||||
|         self.assertTrue(failure.check(RuntimeError)) |         self.assertTrue(failure.check(RuntimeError)) | ||||||
|  |  | ||||||
|         self.assertEqual(st.FAILURE, rt.storage.get_atom_state(tasks[0].name)) |         self.assertEqual(st.REVERT_FAILURE, | ||||||
|  |                          rt.storage.get_atom_state(tasks[0].name)) | ||||||
|  |  | ||||||
|     def test_run_iterations_suspended(self): |     def test_run_iterations_suspended(self): | ||||||
|         flow = lf.Flow("root") |         flow = lf.Flow("root") | ||||||
|   | |||||||
| @@ -21,11 +21,16 @@ from taskflow import test | |||||||
|  |  | ||||||
| class TransitionTest(test.TestCase): | class TransitionTest(test.TestCase): | ||||||
|  |  | ||||||
|  |     _DISALLOWED_TPL = "Transition from '%s' to '%s' was found to be disallowed" | ||||||
|  |     _NOT_IGNORED_TPL = "Transition from '%s' to '%s' was not ignored" | ||||||
|  |  | ||||||
|     def assertTransitionAllowed(self, from_state, to_state): |     def assertTransitionAllowed(self, from_state, to_state): | ||||||
|         self.assertTrue(self.check_transition(from_state, to_state)) |         msg = self._DISALLOWED_TPL % (from_state, to_state) | ||||||
|  |         self.assertTrue(self.check_transition(from_state, to_state), msg=msg) | ||||||
|  |  | ||||||
|     def assertTransitionIgnored(self, from_state, to_state): |     def assertTransitionIgnored(self, from_state, to_state): | ||||||
|         self.assertFalse(self.check_transition(from_state, to_state)) |         msg = self._NOT_IGNORED_TPL % (from_state, to_state) | ||||||
|  |         self.assertFalse(self.check_transition(from_state, to_state), msg=msg) | ||||||
|  |  | ||||||
|     def assertTransitionForbidden(self, from_state, to_state): |     def assertTransitionForbidden(self, from_state, to_state): | ||||||
|         self.assertRaisesRegexp(exc.InvalidState, |         self.assertRaisesRegexp(exc.InvalidState, | ||||||
| @@ -101,7 +106,8 @@ class CheckTaskTransitionTest(TransitionTest): | |||||||
|  |  | ||||||
|     def test_from_reverting_state(self): |     def test_from_reverting_state(self): | ||||||
|         self.assertTransitions(from_state=states.REVERTING, |         self.assertTransitions(from_state=states.REVERTING, | ||||||
|                                allowed=(states.FAILURE, states.REVERTED), |                                allowed=(states.REVERT_FAILURE, | ||||||
|  |                                         states.REVERTED), | ||||||
|                                ignored=(states.RUNNING, states.REVERTING, |                                ignored=(states.RUNNING, states.REVERTING, | ||||||
|                                         states.PENDING, states.SUCCESS)) |                                         states.PENDING, states.SUCCESS)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ class EngineTaskTest(object): | |||||||
|         engine = self._make_engine(flow) |         engine = self._make_engine(flow) | ||||||
|         expected = ['fail.f RUNNING', 'fail.t RUNNING', |         expected = ['fail.f RUNNING', 'fail.t RUNNING', | ||||||
|                     'fail.t FAILURE(Failure: RuntimeError: Woot!)', |                     'fail.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'fail.t REVERTING', 'fail.t REVERTED', |                     'fail.t REVERTING', 'fail.t REVERTED(None)', | ||||||
|                     'fail.f REVERTED'] |                     'fail.f REVERTED'] | ||||||
|         with utils.CaptureListener(engine, values=values) as capturer: |         with utils.CaptureListener(engine, values=values) as capturer: | ||||||
|             self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) |             self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) | ||||||
| @@ -374,6 +374,29 @@ class EngineLinearFlowTest(utils.EngineTestBase): | |||||||
|         self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) |         self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) | ||||||
|         self.assertEqual(engine.storage.fetch_all(), {}) |         self.assertEqual(engine.storage.fetch_all(), {}) | ||||||
|  |  | ||||||
|  |     def test_revert_provided(self): | ||||||
|  |         flow = lf.Flow('revert').add( | ||||||
|  |             utils.GiveBackRevert('giver'), | ||||||
|  |             utils.FailingTask(name='fail') | ||||||
|  |         ) | ||||||
|  |         engine = self._make_engine(flow, store={'value': 0}) | ||||||
|  |         self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) | ||||||
|  |         self.assertEqual(engine.storage.get_revert_result('giver'), 2) | ||||||
|  |  | ||||||
|  |     def test_nasty_revert(self): | ||||||
|  |         flow = lf.Flow('revert').add( | ||||||
|  |             utils.NastyTask('nasty'), | ||||||
|  |             utils.FailingTask(name='fail') | ||||||
|  |         ) | ||||||
|  |         engine = self._make_engine(flow) | ||||||
|  |         self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run) | ||||||
|  |         fail = engine.storage.get_revert_result('nasty') | ||||||
|  |         self.assertIsNotNone(fail.check(RuntimeError)) | ||||||
|  |         exec_failures = engine.storage.get_execute_failures() | ||||||
|  |         self.assertIn('fail', exec_failures) | ||||||
|  |         rev_failures = engine.storage.get_revert_failures() | ||||||
|  |         self.assertIn('nasty', rev_failures) | ||||||
|  |  | ||||||
|     def test_sequential_flow_nested_blocks(self): |     def test_sequential_flow_nested_blocks(self): | ||||||
|         flow = lf.Flow('nested-1').add( |         flow = lf.Flow('nested-1').add( | ||||||
|             utils.ProgressingTask('task1'), |             utils.ProgressingTask('task1'), | ||||||
| @@ -406,7 +429,7 @@ class EngineLinearFlowTest(utils.EngineTestBase): | |||||||
|             self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) |             self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) | ||||||
|         expected = ['fail.t RUNNING', |         expected = ['fail.t RUNNING', | ||||||
|                     'fail.t FAILURE(Failure: RuntimeError: Woot!)', |                     'fail.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'fail.t REVERTING', 'fail.t REVERTED'] |                     'fail.t REVERTING', 'fail.t REVERTED(None)'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
|     def test_correctly_reverts_children(self): |     def test_correctly_reverts_children(self): | ||||||
| @@ -424,9 +447,9 @@ class EngineLinearFlowTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', 'task2.t SUCCESS(5)', |                     'task2.t RUNNING', 'task2.t SUCCESS(5)', | ||||||
|                     'fail.t RUNNING', |                     'fail.t RUNNING', | ||||||
|                     'fail.t FAILURE(Failure: RuntimeError: Woot!)', |                     'fail.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'fail.t REVERTING', 'fail.t REVERTED', |                     'fail.t REVERTING', 'fail.t REVERTED(None)', | ||||||
|                     'task2.t REVERTING', 'task2.t REVERTED', |                     'task2.t REVERTING', 'task2.t REVERTED(None)', | ||||||
|                     'task1.t REVERTING', 'task1.t REVERTED'] |                     'task1.t REVERTING', 'task1.t REVERTED(None)'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -529,18 +552,19 @@ class EngineLinearAndUnorderedExceptionsTest(utils.EngineTestBase): | |||||||
|             self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) |             self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run) | ||||||
|  |  | ||||||
|         # NOTE(imelnikov): we don't know if task 3 was run, but if it was, |         # NOTE(imelnikov): we don't know if task 3 was run, but if it was, | ||||||
|         # it should have been reverted in correct order. |         # it should have been REVERTED(None) in correct order. | ||||||
|         possible_values_no_task3 = [ |         possible_values_no_task3 = [ | ||||||
|             'task1.t RUNNING', 'task2.t RUNNING', |             'task1.t RUNNING', 'task2.t RUNNING', | ||||||
|             'fail.t FAILURE(Failure: RuntimeError: Woot!)', |             'fail.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|             'task2.t REVERTED', 'task1.t REVERTED' |             'task2.t REVERTED(None)', 'task1.t REVERTED(None)' | ||||||
|         ] |         ] | ||||||
|         self.assertIsSuperAndSubsequence(capturer.values, |         self.assertIsSuperAndSubsequence(capturer.values, | ||||||
|                                          possible_values_no_task3) |                                          possible_values_no_task3) | ||||||
|         if 'task3' in capturer.values: |         if 'task3' in capturer.values: | ||||||
|             possible_values_task3 = [ |             possible_values_task3 = [ | ||||||
|                 'task1.t RUNNING', 'task2.t RUNNING', 'task3.t RUNNING', |                 'task1.t RUNNING', 'task2.t RUNNING', 'task3.t RUNNING', | ||||||
|                 'task3.t REVERTED', 'task2.t REVERTED', 'task1.t REVERTED' |                 'task3.t REVERTED(None)', 'task2.t REVERTED(None)', | ||||||
|  |                 'task1.t REVERTED(None)' | ||||||
|             ] |             ] | ||||||
|             self.assertIsSuperAndSubsequence(capturer.values, |             self.assertIsSuperAndSubsequence(capturer.values, | ||||||
|                                              possible_values_task3) |                                              possible_values_task3) | ||||||
| @@ -561,12 +585,12 @@ class EngineLinearAndUnorderedExceptionsTest(utils.EngineTestBase): | |||||||
|             self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run) |             self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run) | ||||||
|  |  | ||||||
|         # NOTE(imelnikov): we don't know if task 3 was run, but if it was, |         # NOTE(imelnikov): we don't know if task 3 was run, but if it was, | ||||||
|         # it should have been reverted in correct order. |         # it should have been REVERTED(None) in correct order. | ||||||
|         possible_values = ['task1.t RUNNING', 'task1.t SUCCESS(5)', |         possible_values = ['task1.t RUNNING', 'task1.t SUCCESS(5)', | ||||||
|                            'task2.t RUNNING', 'task2.t SUCCESS(5)', |                            'task2.t RUNNING', 'task2.t SUCCESS(5)', | ||||||
|                            'task3.t RUNNING', 'task3.t SUCCESS(5)', |                            'task3.t RUNNING', 'task3.t SUCCESS(5)', | ||||||
|                            'task3.t REVERTING', |                            'task3.t REVERTING', | ||||||
|                            'task3.t REVERTED'] |                            'task3.t REVERTED(None)'] | ||||||
|         self.assertIsSuperAndSubsequence(possible_values, capturer.values) |         self.assertIsSuperAndSubsequence(possible_values, capturer.values) | ||||||
|         possible_values_no_task3 = ['task1.t RUNNING', 'task2.t RUNNING'] |         possible_values_no_task3 = ['task1.t RUNNING', 'task2.t RUNNING'] | ||||||
|         self.assertIsSuperAndSubsequence(capturer.values, |         self.assertIsSuperAndSubsequence(capturer.values, | ||||||
| @@ -589,12 +613,12 @@ class EngineLinearAndUnorderedExceptionsTest(utils.EngineTestBase): | |||||||
|         # NOTE(imelnikov): if task1 was run, it should have been reverted. |         # NOTE(imelnikov): if task1 was run, it should have been reverted. | ||||||
|         if 'task1' in capturer.values: |         if 'task1' in capturer.values: | ||||||
|             task1_story = ['task1.t RUNNING', 'task1.t SUCCESS(5)', |             task1_story = ['task1.t RUNNING', 'task1.t SUCCESS(5)', | ||||||
|                            'task1.t REVERTED'] |                            'task1.t REVERTED(None)'] | ||||||
|             self.assertIsSuperAndSubsequence(capturer.values, task1_story) |             self.assertIsSuperAndSubsequence(capturer.values, task1_story) | ||||||
|  |  | ||||||
|         # NOTE(imelnikov): task2 should have been run and reverted |         # NOTE(imelnikov): task2 should have been run and reverted | ||||||
|         task2_story = ['task2.t RUNNING', 'task2.t SUCCESS(5)', |         task2_story = ['task2.t RUNNING', 'task2.t SUCCESS(5)', | ||||||
|                        'task2.t REVERTED'] |                        'task2.t REVERTED(None)'] | ||||||
|         self.assertIsSuperAndSubsequence(capturer.values, task2_story) |         self.assertIsSuperAndSubsequence(capturer.values, task2_story) | ||||||
|  |  | ||||||
|     def test_revert_raises_for_linear_in_unordered(self): |     def test_revert_raises_for_linear_in_unordered(self): | ||||||
| @@ -608,7 +632,7 @@ class EngineLinearAndUnorderedExceptionsTest(utils.EngineTestBase): | |||||||
|         engine = self._make_engine(flow) |         engine = self._make_engine(flow) | ||||||
|         with utils.CaptureListener(engine, capture_flow=False) as capturer: |         with utils.CaptureListener(engine, capture_flow=False) as capturer: | ||||||
|             self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run) |             self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run) | ||||||
|         self.assertNotIn('task2.t REVERTED', capturer.values) |         self.assertNotIn('task2.t REVERTED(None)', capturer.values) | ||||||
|  |  | ||||||
|  |  | ||||||
| class EngineGraphFlowTest(utils.EngineTestBase): | class EngineGraphFlowTest(utils.EngineTestBase): | ||||||
| @@ -697,11 +721,11 @@ class EngineGraphFlowTest(utils.EngineTestBase): | |||||||
|                     'task3.t RUNNING', |                     'task3.t RUNNING', | ||||||
|                     'task3.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task3.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task3.t REVERTING', |                     'task3.t REVERTING', | ||||||
|                     'task3.t REVERTED', |                     'task3.t REVERTED(None)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED'] |                     'task1.t REVERTED(None)'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|         self.assertEqual(engine.storage.get_flow_state(), states.REVERTED) |         self.assertEqual(engine.storage.get_flow_state(), states.REVERTED) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -82,8 +82,8 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task1.t RUNNING', 'task1.t SUCCESS(5)', |                     'task1.t RUNNING', 'task1.t SUCCESS(5)', | ||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', 'task2.t REVERTED', |                     'task2.t REVERTING', 'task2.t REVERTED(None)', | ||||||
|                     'task1.t REVERTING', 'task1.t REVERTED', |                     'task1.t REVERTING', 'task1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
| @@ -114,9 +114,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
| @@ -127,11 +127,11 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -153,9 +153,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t FAILURE', |                     'task1.t REVERT_FAILURE(Failure: RuntimeError: Gotcha!)', | ||||||
|                     'flow-1.f FAILURE'] |                     'flow-1.f FAILURE'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -185,9 +185,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task3.t RUNNING', |                     'task3.t RUNNING', | ||||||
|                     'task3.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task3.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task3.t REVERTING', |                     'task3.t REVERTING', | ||||||
|                     'task3.t REVERTED', |                     'task3.t REVERTED(None)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r2.r RETRYING', |                     'r2.r RETRYING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
|                     'task3.t PENDING', |                     'task3.t PENDING', | ||||||
| @@ -231,15 +231,15 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task4.t RUNNING', |                     'task4.t RUNNING', | ||||||
|                     'task4.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task4.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task4.t REVERTING', |                     'task4.t REVERTING', | ||||||
|                     'task4.t REVERTED', |                     'task4.t REVERTED(None)', | ||||||
|                     'task3.t REVERTING', |                     'task3.t REVERTING', | ||||||
|                     'task3.t REVERTED', |                     'task3.t REVERTED(None)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r2.r REVERTING', |                     'r2.r REVERTING', | ||||||
|                     'r2.r REVERTED', |                     'r2.r REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'r2.r PENDING', |                     'r2.r PENDING', | ||||||
| @@ -280,8 +280,8 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'r.r RETRYING', |                     'r.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
| @@ -316,11 +316,11 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r2.r REVERTING', |                     'r2.r REVERTING', | ||||||
|                     'r2.r REVERTED', |                     'r2.r REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'r2.r PENDING', |                     'r2.r PENDING', | ||||||
| @@ -359,9 +359,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -388,11 +388,11 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -417,13 +417,13 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r2.r REVERTING', |                     'r2.r REVERTING', | ||||||
|                     'r2.r REVERTED', |                     'r2.r REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -515,7 +515,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'c.t RUNNING', |                     'c.t RUNNING', | ||||||
|                     'c.t FAILURE(Failure: RuntimeError: Woot!)', |                     'c.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'c.t REVERTING', |                     'c.t REVERTING', | ||||||
|                     'c.t REVERTED', |                     'c.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'c.t PENDING', |                     'c.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -542,9 +542,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't2.t RUNNING', |                     't2.t RUNNING', | ||||||
|                     't2.t FAILURE(Failure: RuntimeError: Woot!)', |                     't2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     't2.t REVERTING', |                     't2.t REVERTING', | ||||||
|                     't2.t REVERTED', |                     't2.t REVERTED(None)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     't2.t PENDING', |                     't2.t PENDING', | ||||||
| @@ -555,9 +555,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't2.t RUNNING', |                     't2.t RUNNING', | ||||||
|                     't2.t FAILURE(Failure: RuntimeError: Woot!)', |                     't2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     't2.t REVERTING', |                     't2.t REVERTING', | ||||||
|                     't2.t REVERTED', |                     't2.t REVERTED(None)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     't2.t PENDING', |                     't2.t PENDING', | ||||||
| @@ -568,11 +568,11 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't2.t RUNNING', |                     't2.t RUNNING', | ||||||
|                     't2.t FAILURE(Failure: RuntimeError: Woot!)', |                     't2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     't2.t REVERTING', |                     't2.t REVERTING', | ||||||
|                     't2.t REVERTED', |                     't2.t REVERTED(None)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -589,7 +589,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -597,7 +597,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 2)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 2)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -605,7 +605,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -613,9 +613,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 5)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 5)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -632,7 +632,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 2)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 2)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -640,7 +640,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -648,9 +648,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 5)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 5)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertItemsEqual(capturer.values, expected) |         self.assertItemsEqual(capturer.values, expected) | ||||||
|  |  | ||||||
| @@ -674,7 +674,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -682,7 +682,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot with 2)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot with 2)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -690,7 +690,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -698,9 +698,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot with 5)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot with 5)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -724,7 +724,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -732,7 +732,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot with 2)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot with 2)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -740,7 +740,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -748,11 +748,11 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot with 5)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot with 5)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -778,7 +778,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -786,7 +786,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 2)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 2)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -794,9 +794,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 5)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 5)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -814,7 +814,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -822,7 +822,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 2)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 2)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     't1.t PENDING', |                     't1.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -830,9 +830,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     't1.t RUNNING', |                     't1.t RUNNING', | ||||||
|                     't1.t FAILURE(Failure: RuntimeError: Woot with 5)', |                     't1.t FAILURE(Failure: RuntimeError: Woot with 5)', | ||||||
|                     't1.t REVERTING', |                     't1.t REVERTING', | ||||||
|                     't1.t REVERTED', |                     't1.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertItemsEqual(capturer.values, expected) |         self.assertItemsEqual(capturer.values, expected) | ||||||
|  |  | ||||||
| @@ -857,7 +857,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task-2.t RUNNING', |                     'task-2.t RUNNING', | ||||||
|                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     'task-2.t REVERTING', |                     'task-2.t REVERTING', | ||||||
|                     'task-2.t REVERTED', |                     'task-2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task-2.t PENDING', |                     'task-2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -865,7 +865,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task-2.t RUNNING', |                     'task-2.t RUNNING', | ||||||
|                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 2)', |                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 2)', | ||||||
|                     'task-2.t REVERTING', |                     'task-2.t REVERTING', | ||||||
|                     'task-2.t REVERTED', |                     'task-2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task-2.t PENDING', |                     'task-2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -873,9 +873,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task-2.t RUNNING', |                     'task-2.t RUNNING', | ||||||
|                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 5)', |                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 5)', | ||||||
|                     'task-2.t REVERTING', |                     'task-2.t REVERTING', | ||||||
|                     'task-2.t REVERTED', |                     'task-2.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -901,7 +901,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task-2.t RUNNING', |                     'task-2.t RUNNING', | ||||||
|                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 3)', |                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 3)', | ||||||
|                     'task-2.t REVERTING', |                     'task-2.t REVERTING', | ||||||
|                     'task-2.t REVERTED', |                     'task-2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task-2.t PENDING', |                     'task-2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -909,7 +909,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task-2.t RUNNING', |                     'task-2.t RUNNING', | ||||||
|                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 2)', |                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 2)', | ||||||
|                     'task-2.t REVERTING', |                     'task-2.t REVERTING', | ||||||
|                     'task-2.t REVERTED', |                     'task-2.t REVERTED(None)', | ||||||
|                     'r1.r RETRYING', |                     'r1.r RETRYING', | ||||||
|                     'task-2.t PENDING', |                     'task-2.t PENDING', | ||||||
|                     'r1.r RUNNING', |                     'r1.r RUNNING', | ||||||
| @@ -917,11 +917,11 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'task-2.t RUNNING', |                     'task-2.t RUNNING', | ||||||
|                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 5)', |                     'task-2.t FAILURE(Failure: RuntimeError: Woot with 5)', | ||||||
|                     'task-2.t REVERTING', |                     'task-2.t REVERTING', | ||||||
|                     'task-2.t REVERTED', |                     'task-2.t REVERTED(None)', | ||||||
|                     'r1.r REVERTING', |                     'r1.r REVERTING', | ||||||
|                     'r1.r REVERTED', |                     'r1.r REVERTED(None)', | ||||||
|                     'task-1.t REVERTING', |                     'task-1.t REVERTING', | ||||||
|                     'task-1.t REVERTED', |                     'task-1.t REVERTED(None)', | ||||||
|                     'flow-1.f REVERTED'] |                     'flow-1.f REVERTED'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
| @@ -973,7 +973,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|         with utils.CaptureListener(engine) as capturer: |         with utils.CaptureListener(engine) as capturer: | ||||||
|             engine.run() |             engine.run() | ||||||
|         expected = ['task1.t REVERTING', |         expected = ['task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'flow-1_retry.r RETRYING', |                     'flow-1_retry.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'flow-1_retry.r RUNNING', |                     'flow-1_retry.r RUNNING', | ||||||
| @@ -988,7 +988,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|         with utils.CaptureListener(engine) as capturer: |         with utils.CaptureListener(engine) as capturer: | ||||||
|             engine.run() |             engine.run() | ||||||
|         expected = ['task1.t REVERTING', |         expected = ['task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'flow-1_retry.r RETRYING', |                     'flow-1_retry.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'flow-1_retry.r RUNNING', |                     'flow-1_retry.r RUNNING', | ||||||
| @@ -1003,7 +1003,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|         with utils.CaptureListener(engine) as capturer: |         with utils.CaptureListener(engine) as capturer: | ||||||
|             engine.run() |             engine.run() | ||||||
|         expected = ['task1.t REVERTING', |         expected = ['task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'flow-1_retry.r RETRYING', |                     'flow-1_retry.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'flow-1_retry.r RUNNING', |                     'flow-1_retry.r RUNNING', | ||||||
| @@ -1018,7 +1018,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|         with utils.CaptureListener(engine) as capturer: |         with utils.CaptureListener(engine) as capturer: | ||||||
|             engine.run() |             engine.run() | ||||||
|         expected = ['task1.t REVERTING', |         expected = ['task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'flow-1_retry.r RETRYING', |                     'flow-1_retry.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'flow-1_retry.r RUNNING', |                     'flow-1_retry.r RUNNING', | ||||||
| @@ -1032,7 +1032,7 @@ class RetryTest(utils.EngineTestBase): | |||||||
|         engine = self._pretend_to_run_a_flow_and_crash('revert scheduled') |         engine = self._pretend_to_run_a_flow_and_crash('revert scheduled') | ||||||
|         with utils.CaptureListener(engine) as capturer: |         with utils.CaptureListener(engine) as capturer: | ||||||
|             engine.run() |             engine.run() | ||||||
|         expected = ['task1.t REVERTED', |         expected = ['task1.t REVERTED(None)', | ||||||
|                     'flow-1_retry.r RETRYING', |                     'flow-1_retry.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'flow-1_retry.r RUNNING', |                     'flow-1_retry.r RUNNING', | ||||||
| @@ -1077,16 +1077,16 @@ class RetryTest(utils.EngineTestBase): | |||||||
|                     'c.t FAILURE(Failure: RuntimeError: Woot!)', |                     'c.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'a.t REVERTING', |                     'a.t REVERTING', | ||||||
|                     'c.t REVERTING', |                     'c.t REVERTING', | ||||||
|                     'a.t REVERTED', |                     'a.t REVERTED(None)', | ||||||
|                     'c.t REVERTED', |                     'c.t REVERTED(None)', | ||||||
|                     'b.t REVERTING', |                     'b.t REVERTING', | ||||||
|                     'b.t REVERTED'] |                     'b.t REVERTED(None)'] | ||||||
|         self.assertItemsEqual(capturer.values[:8], expected) |         self.assertItemsEqual(capturer.values[:8], expected) | ||||||
|         # Task 'a' was or was not executed again, both cases are ok. |         # Task 'a' was or was not executed again, both cases are ok. | ||||||
|         self.assertIsSuperAndSubsequence(capturer.values[8:], [ |         self.assertIsSuperAndSubsequence(capturer.values[8:], [ | ||||||
|             'b.t RUNNING', |             'b.t RUNNING', | ||||||
|             'c.t FAILURE(Failure: RuntimeError: Woot!)', |             'c.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|             'b.t REVERTED', |             'b.t REVERTED(None)', | ||||||
|         ]) |         ]) | ||||||
|         self.assertEqual(engine.storage.get_flow_state(), st.REVERTED) |         self.assertEqual(engine.storage.get_flow_state(), st.REVERTED) | ||||||
|  |  | ||||||
| @@ -1107,9 +1107,9 @@ class RetryTest(utils.EngineTestBase): | |||||||
|         with utils.CaptureListener(engine, capture_flow=False) as capturer: |         with utils.CaptureListener(engine, capture_flow=False) as capturer: | ||||||
|             engine.run() |             engine.run() | ||||||
|         expected = ['c.t REVERTING', |         expected = ['c.t REVERTING', | ||||||
|                     'c.t REVERTED', |                     'c.t REVERTED(None)', | ||||||
|                     'b.t REVERTING', |                     'b.t REVERTING', | ||||||
|                     'b.t REVERTED'] |                     'b.t REVERTED(None)'] | ||||||
|         self.assertItemsEqual(capturer.values[:4], expected) |         self.assertItemsEqual(capturer.values[:4], expected) | ||||||
|         expected = ['test2_retry.r RETRYING', |         expected = ['test2_retry.r RETRYING', | ||||||
|                     'b.t PENDING', |                     'b.t PENDING', | ||||||
| @@ -1149,10 +1149,10 @@ class RetryParallelExecutionTest(utils.EngineTestBase): | |||||||
|                     'task2.t RUNNING', |                     'task2.t RUNNING', | ||||||
|                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task2.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'task1.t SUCCESS(5)', |                     'task1.t SUCCESS(5)', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'r.r RETRYING', |                     'r.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
| @@ -1189,10 +1189,10 @@ class RetryParallelExecutionTest(utils.EngineTestBase): | |||||||
|                     'task3.t FAILURE(Failure: RuntimeError: Woot!)', |                     'task3.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'task3.t REVERTING', |                     'task3.t REVERTING', | ||||||
|                     'task1.t REVERTING', |                     'task1.t REVERTING', | ||||||
|                     'task3.t REVERTED', |                     'task3.t REVERTED(None)', | ||||||
|                     'task1.t REVERTED', |                     'task1.t REVERTED(None)', | ||||||
|                     'task2.t REVERTING', |                     'task2.t REVERTING', | ||||||
|                     'task2.t REVERTED', |                     'task2.t REVERTED(None)', | ||||||
|                     'r.r RETRYING', |                     'r.r RETRYING', | ||||||
|                     'task1.t PENDING', |                     'task1.t PENDING', | ||||||
|                     'task2.t PENDING', |                     'task2.t PENDING', | ||||||
|   | |||||||
| @@ -118,13 +118,6 @@ class StorageTestMixin(object): | |||||||
|         self.assertEqual(s.fetch_all(), {}) |         self.assertEqual(s.fetch_all(), {}) | ||||||
|         self.assertEqual(s.get_atom_state('my task'), states.SUCCESS) |         self.assertEqual(s.get_atom_state('my task'), states.SUCCESS) | ||||||
|  |  | ||||||
|     def test_save_and_get_other_state(self): |  | ||||||
|         s = self._get_storage() |  | ||||||
|         s.ensure_atom(test_utils.NoopTask('my task')) |  | ||||||
|         s.save('my task', 5, states.FAILURE) |  | ||||||
|         self.assertEqual(s.get('my task'), 5) |  | ||||||
|         self.assertEqual(s.get_atom_state('my task'), states.FAILURE) |  | ||||||
|  |  | ||||||
|     def test_save_and_get_cached_failure(self): |     def test_save_and_get_cached_failure(self): | ||||||
|         a_failure = failure.Failure.from_exception(RuntimeError('Woot!')) |         a_failure = failure.Failure.from_exception(RuntimeError('Woot!')) | ||||||
|         s = self._get_storage() |         s = self._get_storage() | ||||||
| @@ -141,7 +134,7 @@ class StorageTestMixin(object): | |||||||
|         s.ensure_atom(test_utils.NoopTask('my task')) |         s.ensure_atom(test_utils.NoopTask('my task')) | ||||||
|         s.save('my task', a_failure, states.FAILURE) |         s.save('my task', a_failure, states.FAILURE) | ||||||
|         self.assertEqual(s.get('my task'), a_failure) |         self.assertEqual(s.get('my task'), a_failure) | ||||||
|         s._failures['my task'] = None |         s._failures['my task'] = {} | ||||||
|         self.assertTrue(a_failure.matches(s.get('my task'))) |         self.assertTrue(a_failure.matches(s.get('my task'))) | ||||||
|  |  | ||||||
|     def test_get_failure_from_reverted_task(self): |     def test_get_failure_from_reverted_task(self): | ||||||
| @@ -564,6 +557,33 @@ class StorageTestMixin(object): | |||||||
|         args = s.fetch_mapped_args(t.rebind, atom_name=t.name) |         args = s.fetch_mapped_args(t.rebind, atom_name=t.name) | ||||||
|         self.assertEqual(3, args['x']) |         self.assertEqual(3, args['x']) | ||||||
|  |  | ||||||
|  |     def test_save_fetch(self): | ||||||
|  |         t = test_utils.GiveBackRevert('my task') | ||||||
|  |         s = self._get_storage() | ||||||
|  |         s.ensure_atom(t) | ||||||
|  |         s.save('my task', 2) | ||||||
|  |         self.assertEqual(2, s.get('my task')) | ||||||
|  |         self.assertRaises(exceptions.NotFound, | ||||||
|  |                           s.get_revert_result, 'my task') | ||||||
|  |  | ||||||
|  |     def test_save_fetch_revert(self): | ||||||
|  |         t = test_utils.GiveBackRevert('my task') | ||||||
|  |         s = self._get_storage() | ||||||
|  |         s.ensure_atom(t) | ||||||
|  |         s.set_atom_intention('my task', states.REVERT) | ||||||
|  |         s.save('my task', 2, state=states.REVERTED) | ||||||
|  |         self.assertRaises(exceptions.NotFound, s.get, 'my task') | ||||||
|  |         self.assertEqual(2, s.get_revert_result('my task')) | ||||||
|  |  | ||||||
|  |     def test_save_fail_fetch_revert(self): | ||||||
|  |         t = test_utils.GiveBackRevert('my task') | ||||||
|  |         s = self._get_storage() | ||||||
|  |         s.ensure_atom(t) | ||||||
|  |         s.set_atom_intention('my task', states.REVERT) | ||||||
|  |         a_failure = failure.Failure.from_exception(RuntimeError('Woot!')) | ||||||
|  |         s.save('my task', a_failure, state=states.REVERT_FAILURE) | ||||||
|  |         self.assertEqual(a_failure, s.get_revert_result('my task')) | ||||||
|  |  | ||||||
|  |  | ||||||
| class StorageMemoryTest(StorageTestMixin, test.TestCase): | class StorageMemoryTest(StorageTestMixin, test.TestCase): | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|   | |||||||
| @@ -97,14 +97,14 @@ class SuspendTest(utils.EngineTestBase): | |||||||
|                     'c.t RUNNING', |                     'c.t RUNNING', | ||||||
|                     'c.t FAILURE(Failure: RuntimeError: Woot!)', |                     'c.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'c.t REVERTING', |                     'c.t REVERTING', | ||||||
|                     'c.t REVERTED', |                     'c.t REVERTED(None)', | ||||||
|                     'b.t REVERTING', |                     'b.t REVERTING', | ||||||
|                     'b.t REVERTED'] |                     'b.t REVERTED(None)'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|         with utils.CaptureListener(engine, capture_flow=False) as capturer: |         with utils.CaptureListener(engine, capture_flow=False) as capturer: | ||||||
|             self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) |             self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) | ||||||
|         self.assertEqual(engine.storage.get_flow_state(), states.REVERTED) |         self.assertEqual(engine.storage.get_flow_state(), states.REVERTED) | ||||||
|         expected = ['a.t REVERTING', 'a.t REVERTED'] |         expected = ['a.t REVERTING', 'a.t REVERTED(None)'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
|     def test_suspend_and_resume_linear_flow_on_revert(self): |     def test_suspend_and_resume_linear_flow_on_revert(self): | ||||||
| @@ -124,9 +124,9 @@ class SuspendTest(utils.EngineTestBase): | |||||||
|                     'c.t RUNNING', |                     'c.t RUNNING', | ||||||
|                     'c.t FAILURE(Failure: RuntimeError: Woot!)', |                     'c.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'c.t REVERTING', |                     'c.t REVERTING', | ||||||
|                     'c.t REVERTED', |                     'c.t REVERTED(None)', | ||||||
|                     'b.t REVERTING', |                     'b.t REVERTING', | ||||||
|                     'b.t REVERTED'] |                     'b.t REVERTED(None)'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
|         # pretend we are resuming |         # pretend we are resuming | ||||||
| @@ -135,7 +135,7 @@ class SuspendTest(utils.EngineTestBase): | |||||||
|             self.assertRaisesRegexp(RuntimeError, '^Woot', engine2.run) |             self.assertRaisesRegexp(RuntimeError, '^Woot', engine2.run) | ||||||
|         self.assertEqual(engine2.storage.get_flow_state(), states.REVERTED) |         self.assertEqual(engine2.storage.get_flow_state(), states.REVERTED) | ||||||
|         expected = ['a.t REVERTING', |         expected = ['a.t REVERTING', | ||||||
|                     'a.t REVERTED'] |                     'a.t REVERTED(None)'] | ||||||
|         self.assertEqual(expected, capturer2.values) |         self.assertEqual(expected, capturer2.values) | ||||||
|  |  | ||||||
|     def test_suspend_and_revert_even_if_task_is_gone(self): |     def test_suspend_and_revert_even_if_task_is_gone(self): | ||||||
| @@ -157,9 +157,9 @@ class SuspendTest(utils.EngineTestBase): | |||||||
|                     'c.t RUNNING', |                     'c.t RUNNING', | ||||||
|                     'c.t FAILURE(Failure: RuntimeError: Woot!)', |                     'c.t FAILURE(Failure: RuntimeError: Woot!)', | ||||||
|                     'c.t REVERTING', |                     'c.t REVERTING', | ||||||
|                     'c.t REVERTED', |                     'c.t REVERTED(None)', | ||||||
|                     'b.t REVERTING', |                     'b.t REVERTING', | ||||||
|                     'b.t REVERTED'] |                     'b.t REVERTED(None)'] | ||||||
|         self.assertEqual(expected, capturer.values) |         self.assertEqual(expected, capturer.values) | ||||||
|  |  | ||||||
|         # pretend we are resuming, but task 'c' gone when flow got updated |         # pretend we are resuming, but task 'c' gone when flow got updated | ||||||
| @@ -171,7 +171,7 @@ class SuspendTest(utils.EngineTestBase): | |||||||
|         with utils.CaptureListener(engine2, capture_flow=False) as capturer2: |         with utils.CaptureListener(engine2, capture_flow=False) as capturer2: | ||||||
|             self.assertRaisesRegexp(RuntimeError, '^Woot', engine2.run) |             self.assertRaisesRegexp(RuntimeError, '^Woot', engine2.run) | ||||||
|         self.assertEqual(engine2.storage.get_flow_state(), states.REVERTED) |         self.assertEqual(engine2.storage.get_flow_state(), states.REVERTED) | ||||||
|         expected = ['a.t REVERTING', 'a.t REVERTED'] |         expected = ['a.t REVERTING', 'a.t REVERTED(None)'] | ||||||
|         self.assertEqual(capturer2.values, expected) |         self.assertEqual(capturer2.values, expected) | ||||||
|  |  | ||||||
|     def test_storage_is_rechecked(self): |     def test_storage_is_rechecked(self): | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ class TestWorker(test.MockTestCase): | |||||||
|         self.broker_url = 'test-url' |         self.broker_url = 'test-url' | ||||||
|         self.exchange = 'test-exchange' |         self.exchange = 'test-exchange' | ||||||
|         self.topic = 'test-topic' |         self.topic = 'test-topic' | ||||||
|         self.endpoint_count = 25 |         self.endpoint_count = 26 | ||||||
|  |  | ||||||
|         # patch classes |         # patch classes | ||||||
|         self.executor_mock, self.executor_inst_mock = self.patchClass( |         self.executor_mock, self.executor_inst_mock = self.patchClass( | ||||||
|   | |||||||
| @@ -117,6 +117,15 @@ class AddOne(task.Task): | |||||||
|         return source + 1 |         return source + 1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GiveBackRevert(task.Task): | ||||||
|  |  | ||||||
|  |     def execute(self, value): | ||||||
|  |         return value + 1 | ||||||
|  |  | ||||||
|  |     def revert(self, *args, **kwargs): | ||||||
|  |         return kwargs.get('result') + 1 | ||||||
|  |  | ||||||
|  |  | ||||||
| class FakeTask(object): | class FakeTask(object): | ||||||
|  |  | ||||||
|     def execute(self, **kwargs): |     def execute(self, **kwargs): | ||||||
|   | |||||||
| @@ -291,12 +291,14 @@ class Failure(object): | |||||||
|     def reraise_if_any(failures): |     def reraise_if_any(failures): | ||||||
|         """Re-raise exceptions if argument is not empty. |         """Re-raise exceptions if argument is not empty. | ||||||
|  |  | ||||||
|         If argument is empty list, this method returns None. If |         If argument is empty list/tuple/iterator, this method returns | ||||||
|         argument is a list with a single ``Failure`` object in it, |         None. If argument is coverted into a list with a | ||||||
|         that failure is reraised. Else, a |         single ``Failure`` object in it, that failure is reraised. Else, a | ||||||
|         :class:`~taskflow.exceptions.WrappedFailure` exception |         :class:`~taskflow.exceptions.WrappedFailure` exception | ||||||
|         is raised with a failure list as causes. |         is raised with the failure list as causes. | ||||||
|         """ |         """ | ||||||
|  |         if not isinstance(failures, (list, tuple)): | ||||||
|  |             # Convert generators/other into a list... | ||||||
|             failures = list(failures) |             failures = list(failures) | ||||||
|         if len(failures) == 1: |         if len(failures) == 1: | ||||||
|             failures[0].reraise() |             failures[0].reraise() | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ def make_machine(start_state, transitions): | |||||||
| def map_color(internal_states, state): | def map_color(internal_states, state): | ||||||
|     if state in internal_states: |     if state in internal_states: | ||||||
|         return 'blue' |         return 'blue' | ||||||
|     if state == states.FAILURE: |     if state in (states.FAILURE, states.REVERT_FAILURE): | ||||||
|         return 'red' |         return 'red' | ||||||
|     if state == states.REVERTED: |     if state == states.REVERTED: | ||||||
|         return 'darkorange' |         return 'darkorange' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jenkins
					Jenkins