Skip to content

Commit

Permalink
fix: add generic error msg for unknown source errors (#645)
Browse files Browse the repository at this point in the history
This is a little enhancement when the source plugin fails and raises an
exception without message.
Also extends the source plugin development guide. 
Related with https://issues.redhat.com/browse/AAP-19598
  • Loading branch information
Alex-Izquierdo authored and zkayyali812 committed Dec 9, 2024
1 parent 3396470 commit a554c80
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
## [Unreleased]
### Changed
### Added
- Add generic error message for unknown source errors
### Fixed
- Allow user to optionally include matching events
- Allow for fetching env and file contents from EDA server
Expand Down
17 changes: 15 additions & 2 deletions ansible_rulebook/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,22 @@ async def start_source(
)
logger.debug("Task cancelled " + shutdown_msg)
except BaseException as e:
logger.error("Source error %s", str(e))
error_msg = str(e)
# Get the name of the exception class
error_type = str(type(e)).split(".")[-1].replace("'>", "")
if not error_msg:
user_msg = (
f"Unknown error {error_type}: "
"source plugin failed with no error message."
)
else:
user_msg = (
f"{error_type}: Source plugin failed with error message: "
f"'{error_msg}'"
)
logger.error("Source error: %s", user_msg)
shutdown_msg = (
f"Shutting down source: {source.source_name} error : {e}"
f"Shutting down source: {source.source_name} error: {user_msg}"
)
logger.error(shutdown_msg)
raise
Expand Down
3 changes: 3 additions & 0 deletions docs/sources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ immediately after the ``put`` method.
The rulebook can contain it's own logic to finish the process through the ``shutdown`` action.
If your plugin needs to perform some cleanup before the process is terminated, you must catch the ``asyncio.CancelledError`` exception.

.. note::
Please, pay attention when handling errors in your plugin and ensure to raise an exception with a meaningful message so that ansible-rulebook
can log it correctly. Ansible-rulebook will not log the exception itself or print stack traces; it will only log the message you provide.

Distributing plugins
^^^^^^^^^^^^^^^^^^^^
Expand Down
12 changes: 12 additions & 0 deletions tests/examples/89_source_error_with_msg.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
- name: "89 source error with msg"
hosts: all
sources:
- name: range_fail_with_msg
source_with_exception:
error_msg: "range fail with msg"
rules:
- name: r1
condition: true
action:
print_event:
12 changes: 12 additions & 0 deletions tests/examples/90_source_error_without_msg.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
- name: "90 source error without msg"
hosts: all
sources:
- name: range_fail_without_msg
source_with_exception:
error_msg: ""
rules:
- name: r1
condition: true
action:
print_event:
34 changes: 34 additions & 0 deletions tests/sources/source_with_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2022 Red Hat, Inc.
#
# 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.

import asyncio
from typing import Any, Dict


class TestException(Exception):
pass


async def main(queue: asyncio.Queue, args: Dict[str, Any]):
error_msg = str(args.get("error_msg", ""))
raise TestException(error_msg)


if __name__ == "__main__":

class MockQueue:
async def put(self, event):
print(event)

asyncio.run(main(MockQueue(), dict(limit=5)))
44 changes: 44 additions & 0 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -2528,3 +2528,47 @@ async def test_include_events(
list(mocked_obj.call_args.args[2]["extra_vars"].keys())
== expected_keys
)


@pytest.mark.asyncio
async def test_89_source_error_with_msg(caplog):
ruleset_queues, event_log = load_rulebook(
"examples/89_source_error_with_msg.yml"
)

queue = ruleset_queues[0][1]
rs = ruleset_queues[0][0]
with SourceTask(rs.sources[0], "sources", {}, queue):
await run_rulesets(
event_log,
ruleset_queues,
dict(),
dict(),
)
expected_msg = (
"ERROR Source error: TestException: "
"Source plugin failed with error message: 'range fail with msg'"
)
assert expected_msg in caplog.text


@pytest.mark.asyncio
async def test_90_source_error_without_msg(caplog):
ruleset_queues, event_log = load_rulebook(
"examples/90_source_error_without_msg.yml"
)

queue = ruleset_queues[0][1]
rs = ruleset_queues[0][0]
with SourceTask(rs.sources[0], "sources", {}, queue):
await run_rulesets(
event_log,
ruleset_queues,
dict(),
dict(),
)
expected_msg = (
"ERROR Source error: Unknown error "
"TestException: source plugin failed with no error message."
)
assert expected_msg in caplog.text

0 comments on commit a554c80

Please sign in to comment.