diff --git a/mistral/services/action_execution_checker.py b/mistral/services/action_execution_checker.py index af4bb0cff..1deccfe90 100644 --- a/mistral/services/action_execution_checker.py +++ b/mistral/services/action_execution_checker.py @@ -16,6 +16,7 @@ import datetime import eventlet import sys +from mistral import context as auth_ctx from mistral.db import utils as db_utils from mistral.db.v2 import api as db_api from mistral.engine import action_handler @@ -71,6 +72,17 @@ def handle_expired_actions(): def _loop(): global _stopped + # This is an administrative thread so we need to set an admin + # security context. + auth_ctx.set_ctx( + auth_ctx.MistralContext( + user=None, + tenant=None, + auth_token=None, + is_admin=True + ) + ) + while not _stopped: try: handle_expired_actions() diff --git a/mistral/tests/unit/engine/test_action_heartbeat.py b/mistral/tests/unit/engine/test_action_heartbeat.py index 87a45119f..8bc9a84cc 100644 --- a/mistral/tests/unit/engine/test_action_heartbeat.py +++ b/mistral/tests/unit/engine/test_action_heartbeat.py @@ -10,6 +10,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import cachetools import mock from oslo_config import cfg @@ -77,3 +78,51 @@ class ActionHeartbeatTest(base.EngineTestCase): name='std.noop', state=states.ERROR ) + + # Make sure actions are not sent to an executor. + @mock.patch.object( + rpc_clients.ExecutorClient, + 'run_action', + mock.MagicMock() + ) + @mock.patch.object( + cachetools.LRUCache, + '__getitem__', + mock.MagicMock(side_effect=KeyError) + ) + def test_fail_action_with_missing_heartbeats_wf_spec_not_cached(self): + wf_text = """--- + version: '2.0' + + wf: + tasks: + task1: + action: std.noop + """ + + wf_service.create_workflows(wf_text) + + wf_ex = self.engine.start_workflow('wf') + + # The workflow should fail because the action of "task1" should be + # failed automatically by the action execution heartbeat checker. + self.await_workflow_error(wf_ex.id) + + with db_api.transaction(): + wf_ex = db_api.get_workflow_execution(wf_ex.id) + + t_execs = wf_ex.task_executions + + t_ex = self._assert_single_item( + t_execs, + name='task1', + state=states.ERROR + ) + + a_execs = db_api.get_action_executions(task_execution_id=t_ex.id) + + self._assert_single_item( + a_execs, + name='std.noop', + state=states.ERROR + ) diff --git a/releasenotes/notes/set_security_context_for_action_execution_checker-eee7fb697fb213d1.yaml b/releasenotes/notes/set_security_context_for_action_execution_checker-eee7fb697fb213d1.yaml new file mode 100644 index 000000000..33e829e56 --- /dev/null +++ b/releasenotes/notes/set_security_context_for_action_execution_checker-eee7fb697fb213d1.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Action execution checker didn't set a security context before failing + expired action executions. It caused ApplicationContextNotFoundException + in case if corresponding workflow specification was not in the cache and + Mistral had to load a DB object. The DB operation in turn was trying + to access a security context which wasn't set. It's now fixed by setting + an admin context in the action execution checker thread.