diff --git a/agents/sap/sap.cfg b/agents/sap/sap.cfg index 74501737e4a..19cfa093d6e 100644 --- a/agents/sap/sap.cfg +++ b/agents/sap/sap.cfg @@ -49,3 +49,22 @@ monitor_paths += [ #'SAP CCMS Monitor Templates/Operating System/OperatingSystem/CPU/CPU_Utilization', #'*', ] + +# A list of strings using the same unix shell pattern syntax as +# monitor_paths (see above). All monitoring objects whose path matches at +# least one of these patterns are excluded from monitoring, even if the +# path is matched by one of the monitor_paths patterns. This can be used +# to skip single objects below a monitor_paths wildcard, e.g. the alert +# container of a client which only holds purely technical number range +# objects rolling over by design. +# Anchoring the pattern to the affected subtree is recommended to avoid +# accidentally excluding objects in other monitors. +# Note: The path segments are built from the SAP field MTNAMESHRT, which +# is limited to 40 characters. Long node names are therefore truncated +# (e.g. "Client 000 Alert Messages for Number Ran") and patterns have to +# match the truncated name. To verify the exact paths, temporarily enable +# the debug output line in the plugin (search for 'node["PATH"]'). +exclude_paths += [ + #'SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/Critical Number Ranges All Clients/Client 000 Alert Messages for Number Ran*', + #'SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/*/Client 066*', +] diff --git a/cmk/plugins/sap/agents/mk_sap.py b/cmk/plugins/sap/agents/mk_sap.py index 54976fdd344..2fd17247f6b 100755 --- a/cmk/plugins/sap/agents/mk_sap.py +++ b/cmk/plugins/sap/agents/mk_sap.py @@ -136,6 +136,13 @@ "SAP CCMS Monitor Templates/Dialog Overview/*", ] monitor_types = [] # type: list[str] + +# A list of strings using the same unix shell pattern syntax as +# monitor_paths (see above). All monitoring objects whose path matches at +# least one of these patterns are excluded from monitoring, even if the +# path is matched by one of the monitor_paths patterns. This can be used +# to skip single objects below a monitor_paths wildcard. +exclude_paths = [] # type: list[str] config_file = MK_CONFDIR + "/sap.cfg" cfg = {} # type: list[dict[Any, Any]] | dict[Any, Any] @@ -208,6 +215,15 @@ class SapError(Exception): def to_be_monitored(path, toplevel_match=False): + # Exclude rules are always matched against the full path. They are + # intentionally not shortened during toplevel matching: Shortening a + # rule which targets a single subtree node would exclude the whole + # monitor instead. A rule matching the toplevel path itself still + # skips the whole monitor and saves the RFC calls for its tree. + for rule in exclude_paths: + if fnmatch.fnmatch(path, rule): + return False + for rule in monitor_paths: if toplevel_match and rule.count("/") > 1: rule = "/".join(rule.split("/")[:2]) diff --git a/tests/unit/cmk/plugins/sap/agents/test_mk_sap.py b/tests/unit/cmk/plugins/sap/agents/test_mk_sap.py index c497f94f9e4..d7e4eac4a34 100644 --- a/tests/unit/cmk/plugins/sap/agents/test_mk_sap.py +++ b/tests/unit/cmk/plugins/sap/agents/test_mk_sap.py @@ -112,6 +112,59 @@ def test_recursion(monkeypatch): } +@pytest.mark.parametrize( + "monitor, exclude, path, toplevel_match, expected", + [ + # default behaviour without exclude_paths stays untouched + (["SAP CCMS Monitor Templates/Dialog Overview/*"], [], "SAP CCMS Monitor Templates/Dialog Overview/ResponseTime", False, True), + (["SAP CCMS Monitor Templates/Dialog Overview/*"], [], "SAP CCMS Monitor Templates/Other Monitor/Foo", False, False), + # toplevel matching shortens monitor rules to the first two segments + (["SAP CCMS Monitor Templates/Dialog Overview/ResponseTime"], [], "SAP CCMS Monitor Templates/Dialog Overview", True, True), + # a matching exclude rule wins over a matching monitor rule. + # Note the truncated last path segment: segments come from the + # 40 character SAP field MTNAMESHRT, patterns must match the + # truncated name (pattern anchored to the subtree as recommended + # in sap.cfg) + ( + ["SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/*"], + ["SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/Critical Number Ranges All Clients/Client 000 Alert Messages for Number Ran*"], + "SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/Critical Number Ranges All Clients/Client 000 Alert Messages for Number Ran", + False, + False, + ), + # sibling nodes not matching the exclude rule are still monitored + ( + ["SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/*"], + ["SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/Critical Number Ranges All Clients/Client 000 Alert Messages for Number Ran*"], + "SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/Critical Number Ranges All Clients/Client 100 Alert Messages for Number Ran", + False, + True, + ), + # an exclude rule targeting a subtree node must not skip the whole + # monitor during toplevel matching (exclude rules are not shortened) + ( + ["SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/*"], + ["SAP CCMS Technical Expert Monitors/All Monitoring Contexts/Critical Number Ranges/Critical Number Ranges All Clients/Client 000 Alert Messages for Number Ran*"], + "SAP CCMS Technical Expert Monitors/All Monitoring Contexts", + True, + True, + ), + # an exclude rule matching the toplevel path skips the whole monitor + ( + ["*"], + ["SAP CCMS Monitor Templates/Dialog Overview*"], + "SAP CCMS Monitor Templates/Dialog Overview", + True, + False, + ), + ], +) +def test_to_be_monitored(monkeypatch, monitor, exclude, path, toplevel_match, expected): + monkeypatch.setattr(mk_sap, "monitor_paths", monitor) + monkeypatch.setattr(mk_sap, "exclude_paths", exclude) + assert mk_sap.to_be_monitored(path, toplevel_match) is expected + + def test_state_file_round_trip(monkeypatch, tmp_path): state_file = tmp_path / "sap.state" state_file.write_text(mk_sap.serialize_states(STATES))