From cf41e2c3759832998f32abf54552e1d04442b919 Mon Sep 17 00:00:00 2001 From: Bill Wei Date: Fri, 5 May 2023 14:04:26 -0400 Subject: [PATCH] Do not place the whole exception object in event_log Exception object cannot be serialized and sent through websocket. Catch JobTemplateNotFoundException explicitly Fixes AAP-11969: ansible-rulebook worker mode fails to handle JobTemplateNotFoundException --- ansible_rulebook/builtin.py | 43 +++++++++++++++-------------- ansible_rulebook/exception.py | 5 ++++ ansible_rulebook/rule_set_runner.py | 23 +++++++++------ tests/test_examples.py | 40 ++++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 30 deletions(-) diff --git a/ansible_rulebook/builtin.py b/ansible_rulebook/builtin.py index 5bc3364d..a84c9280 100644 --- a/ansible_rulebook/builtin.py +++ b/ansible_rulebook/builtin.py @@ -38,6 +38,7 @@ from .event_filter.insert_meta_info import main as insert_meta from .exception import ( ControllerApiException, + JobTemplateNotFoundException, PlaybookNotFoundException, PlaybookStatusNotFoundException, ShutdownException, @@ -813,32 +814,34 @@ async def event_callback(event: dict) -> None: ) if controller_job["status"] != "failed": break - except ControllerApiException as ex: + except (ControllerApiException, JobTemplateNotFoundException) as ex: logger.error(ex) controller_job = {} controller_job["status"] = "failed" controller_job["created"] = run_at() + controller_job["error"] = str(ex) - await event_log.put( - dict( - type="Action", - action="run_job_template", - action_uuid=str(uuid.uuid4()), - activation_id=settings.identifier, - job_template_name=name, - organization=organization, - job_id=job_id, - ruleset=ruleset, - ruleset_uuid=source_ruleset_uuid, - rule=source_rule_name, - rule_uuid=source_rule_uuid, - status=controller_job["status"], - run_at=controller_job["created"], - url=_controller_job_url(controller_job), - matching_events=_get_events(variables), - rule_run_at=rule_run_at, - ) + a_log = dict( + type="Action", + action="run_job_template", + action_uuid=str(uuid.uuid4()), + activation_id=settings.identifier, + job_template_name=name, + organization=organization, + job_id=job_id, + ruleset=ruleset, + ruleset_uuid=source_ruleset_uuid, + rule=source_rule_name, + rule_uuid=source_rule_uuid, + status=controller_job["status"], + run_at=controller_job["created"], + url=_controller_job_url(controller_job), + matching_events=_get_events(variables), + rule_run_at=rule_run_at, ) + if "error" in controller_job: + a_log["reason"] = dict(error=controller_job["error"]) + await event_log.put(a_log) if set_facts or post_events: logger.debug("set_facts") diff --git a/ansible_rulebook/exception.py b/ansible_rulebook/exception.py index 1c0d771d..d5437597 100644 --- a/ansible_rulebook/exception.py +++ b/ansible_rulebook/exception.py @@ -141,3 +141,8 @@ class JobTemplateNotFoundException(Exception): class WebSocketExchangeException(Exception): pass + + +class UnsupportedActionException(Exception): + + pass diff --git a/ansible_rulebook/rule_set_runner.py b/ansible_rulebook/rule_set_runner.py index 0ee1ca4f..1d1c7717 100644 --- a/ansible_rulebook/rule_set_runner.py +++ b/ansible_rulebook/rule_set_runner.py @@ -28,7 +28,10 @@ from ansible_rulebook.builtin import actions as builtin_actions from ansible_rulebook.conf import settings -from ansible_rulebook.exception import ShutdownException +from ansible_rulebook.exception import ( + ShutdownException, + UnsupportedActionException, +) from ansible_rulebook.messages import Shutdown from ansible_rulebook.rule_types import ( Action, @@ -310,7 +313,7 @@ async def _call_action( logger.info("call_action %s", action) - result = None + error = None if action in builtin_actions: try: if action == "run_job_template": @@ -397,15 +400,15 @@ async def _call_action( str(e), pformat(variables_copy), ) - result = dict(error=e) + error = e except MessageNotHandledException as e: logger.error( "Message cannot be handled: %s err %s", action_args, str(e) ) - result = dict(error=e) + error = e except MessageObservedException as e: logger.info("MessageObservedException: %s", action_args) - result = dict(error=e) + error = e except ShutdownException as e: if self.shutdown: logger.info( @@ -418,15 +421,17 @@ async def _call_action( raise except Exception as e: logger.error("Error calling action %s, err %s", action, str(e)) - result = dict(error=e) + error = e except BaseException as e: logger.error(e) raise else: logger.error("Action %s not supported", action) - result = dict(error=f"Action {action} not supported") + error = UnsupportedActionException( + f"Action {action} not supported" + ) - if result: + if error: await self.event_log.put( dict( type="Action", @@ -435,7 +440,7 @@ async def _call_action( playbook_name=action_args.get("name"), status="failed", run_at=str(datetime.utcnow()), - reason=result, + reason=dict(error=str(error)), ) ) diff --git a/tests/test_examples.py b/tests/test_examples.py index 811d1961..24295ade 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -19,7 +19,11 @@ from freezegun import freeze_time from ansible_rulebook.engine import run_rulesets, start_source -from ansible_rulebook.exception import VarsKeyMissingException +from ansible_rulebook.exception import ( + ControllerApiException, + JobTemplateNotFoundException, + VarsKeyMissingException, +) from ansible_rulebook.job_template_runner import job_template_runner from ansible_rulebook.messages import Shutdown from ansible_rulebook.util import load_inventory @@ -2105,6 +2109,40 @@ async def test_46_job_template(): assert action["action"] == "run_job_template" +JOB_TEMPLATE_ERRORS = [ + ("api error", ControllerApiException("api error")), + ("jt does not exist", JobTemplateNotFoundException("jt does not exist")), +] + + +@pytest.mark.parametrize("err_msg,err", JOB_TEMPLATE_ERRORS) +@pytest.mark.asyncio +async def test_46_job_template_exception(err_msg, err): + ruleset_queues, event_log = load_rulebook("examples/46_job_template.yml") + + queue = ruleset_queues[0][1] + rs = ruleset_queues[0][0] + with SourceTask(rs.sources[0], "sources", {}, queue): + with patch( + "ansible_rulebook.builtin.job_template_runner.run_job_template", + side_effect=err, + ): + await run_rulesets( + event_log, + ruleset_queues, + dict(), + load_inventory("playbooks/inventory.yml"), + ) + + while not event_log.empty(): + event = event_log.get_nowait() + if event["type"] == "Action": + action = event + + assert action["action"] == "run_job_template" + assert action["reason"] == {"error": err_msg} + + @pytest.mark.asyncio async def test_77_default_events_ttl(): ruleset_queues, event_log = load_rulebook(