From 589052809a74b6f7f408706e43555a8e6d35e7d4 Mon Sep 17 00:00:00 2001 From: emil Date: Thu, 6 Mar 2025 12:07:42 +0100 Subject: [PATCH 1/5] Add new test for maintenance and unit-tests --- anta/tests/system.py | 62 ++++++++ tests/units/anta_tests/test_system.py | 195 ++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) diff --git a/anta/tests/system.py b/anta/tests/system.py index 9ec719180..cdd03ca4d 100644 --- a/anta/tests/system.py +++ b/anta/tests/system.py @@ -345,3 +345,65 @@ def test(self) -> None: if act_condition != exp_condition or act_stratum != exp_stratum: self.result.is_failure(f"{ntp_server} - Bad association - Condition: {act_condition}, Stratum: {act_stratum}") + + +class VerifyMaintenance(AntaTest): + """Verifies that the device is not currently under maintenance. + + Expected Results + ---------------- + * Success: The test will pass if the device is not under or entering maintenance. + * Failure: The test will fail if the device is under or entering maintenance. + + Examples + -------- + ```yaml + anta.tests.system: + - VerifyMaintenance: + ``` + """ + + categories: ClassVar[list[str]] = ["Maintenance"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show maintenance", revision=1)] + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyMaintenance.""" + self.result.is_success() + + # If units is not empty we have to examine the output for details. + if (units := get_value(self.instance_commands[0].json_output, "units")): + unitsundermaintenance = [] + unitsenteringmaintenance = [] + under = False + entering = False + causes = [] + # Iterate over units, check for units under or entering maintenance, and examine the causes. + for unit, info in units.items(): + if info["state"] == "underMaintenance": + unitsundermaintenance.append(unit) + under = True + elif info["state"] == "maintenanceModeEnter": + unitsenteringmaintenance.append(unit) + entering = True + if info["adminState"] == "underMaintenance": + causes.append("quiesce is configured") + if info["onBootMaintenance"]: + causes.append("On-boot maintenance is configured") + if info["intfsViolatingTrafficThreshold"]: + causes.append("Interface traffic threshold violation") + + # This can occur if maintenance is configured but no unit is configured with 'quiesce'. + if not under and not entering and not causes: + self.result.is_success() + + # Building the error message. + else: + message = "Some units are under or entering maintenance." + if under: + message += f" The following units are currently under maintenance: '{unitsundermaintenance}'." + if entering: + message += f" The following units are currently entering maintenance: '{unitsenteringmaintenance}'." + if len(causes) > 0: + message += f" Possible causes: '{causes}'" + self.result.is_failure(message) diff --git a/tests/units/anta_tests/test_system.py b/tests/units/anta_tests/test_system.py index 5fe0fbad1..cbc030b37 100644 --- a/tests/units/anta_tests/test_system.py +++ b/tests/units/anta_tests/test_system.py @@ -17,6 +17,7 @@ VerifyNTPAssociations, VerifyReloadCause, VerifyUptime, + VerifyMaintenance ) from tests.units.anta_tests import test @@ -496,4 +497,198 @@ ], }, }, + { + "name": "success-no-maintenance-configured", + "test": VerifyMaintenance, + "eos_data": [ + { + "units": {}, + "interfaces": {}, + "vrfs": {}, + "warnings": [ + "Maintenance Mode is disabled." + ], + }, + ], + "expected": {"result": "success"}, + }, + { + "name": "success-maintenance-configured-but-not-enabled", + "test": VerifyMaintenance, + "eos_data": [ + { + "units": { + "System": { + "state": "active", + "adminState": "active", + "stateChangeTime": 0.0, + "onBootMaintenance": False, + "intfsViolatingTrafficThreshold": False, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + } + }, + "interfaces": {}, + "vrfs": {}, + }, + ], + "expected": {"result": "success"}, + }, + { + "name": "success-multiple-units-but-not-enabled", + "test": VerifyMaintenance, + "eos_data": [ + { + "units": { + "mlag": { + "state": "active", + "adminState": "active", + "stateChangeTime": 0.0, + "onBootMaintenance": False, + "intfsViolatingTrafficThreshold": False, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + }, + "System": { + "state": "active", + "adminState": "active", + "stateChangeTime": 0.0, + "onBootMaintenance": False, + "intfsViolatingTrafficThreshold": False, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + }, + }, + "interfaces": {}, + "vrfs": {}, + }, + ], + "expected": {"result": "success"}, + }, + { + "name": "failure-maintenance-enabled", + "test": VerifyMaintenance, + "eos_data": [ + { + "units": { + "mlag": { + "state": "underMaintenance", + "adminState": "underMaintenance", + "stateChangeTime": 1741257120.9532886, + "onBootMaintenance": False, + "intfsViolatingTrafficThreshold": False, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + }, + "System": { + "state": "active", + "adminState": "active", + "stateChangeTime": 0.0, + "onBootMaintenance": False, + "intfsViolatingTrafficThreshold": False, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + } + }, + "interfaces": {}, + "vrfs": {}, + }, + ], + "expected": { + "result": "failure", + "messages": [ + "Some units are under or entering maintenance. The following units are currently under maintenance: '['mlag']'. Possible causes: ['quiesce is configured']", + ], + }, + }, + { + "name": "failure-multiple-reasons", + "test": VerifyMaintenance, + "eos_data": [ + { + "units": { + "mlag": { + "state": "underMaintenance", + "adminState": "underMaintenance", + "stateChangeTime": 1741257120.9532895, + "onBootMaintenance": False, + "intfsViolatingTrafficThreshold": False, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + }, + "System": { + "state": "maintenanceModeEnter", + "adminState": "underMaintenance", + "stateChangeTime": 1741257669.7231765, + "onBootMaintenance": False, + "intfsViolatingTrafficThreshold": False, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + } + }, + "interfaces": {}, + "vrfs": {}, + }, + ], + "expected": { + "result": "failure", + "messages": [ + "Some units are under or entering maintenance. The following units are currently under maintenance: '['mlag']'. The following units are currently entering maintenance: '['System']' Possible causes: ['quiesce is configured','quiesce is configured']", + ], + }, + }, + { + "name": "failure-onboot-maintenance", + "test": VerifyMaintenance, + "eos_data": [ + { + "units": { + "System": { + "state": "underMaintenance", + "adminState": "underMaintenance", + "stateChangeTime": 1741258774.3756502, + "onBootMaintenance": True, + "intfsViolatingTrafficThreshold": False, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + } + }, + "interfaces": {}, + "vrfs": {} + }, + ], + "expected": { + "result": "failure", + "messages": [ + "Some units are under or entering maintenance. The following units are currently under maintenance: '['System']'. Possible causes: ['On-boot maintenance is configured']", + ], + }, + }, + { + "name": "failure-entering-maintenance-interface-violation", + "test": VerifyMaintenance, + "eos_data": [ + { + "units": { + "System": { + "state": "maintenanceModeEnter", + "adminState": "underMaintenance", + "stateChangeTime": 1741257669.7231765, + "onBootMaintenance": False, + "intfsViolatingTrafficThreshold": True, + "aggInBpsRate": 0, + "aggOutBpsRate": 0 + } + }, + "interfaces": {}, + "vrfs": {} + }, + ], + "expected": { + "result": "failure", + "messages": [ + "Some units are under or entering maintenance. The following units are currently entering maintenance: '['System']'. Possible causes: ['Interface traffic threshold violation']", + ], + }, + }, ] From d81bcd26ad28266a140c107941bd5cca9deca2b0 Mon Sep 17 00:00:00 2001 From: emil Date: Thu, 6 Mar 2025 12:14:05 +0100 Subject: [PATCH 2/5] Abbreviated fail messages --- anta/tests/system.py | 10 +++---- examples/tests.yaml | 2 ++ tests/units/anta_tests/test_system.py | 40 +++++++++++++-------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/anta/tests/system.py b/anta/tests/system.py index cdd03ca4d..a4c717d09 100644 --- a/anta/tests/system.py +++ b/anta/tests/system.py @@ -372,7 +372,7 @@ def test(self) -> None: self.result.is_success() # If units is not empty we have to examine the output for details. - if (units := get_value(self.instance_commands[0].json_output, "units")): + if units := get_value(self.instance_commands[0].json_output, "units"): unitsundermaintenance = [] unitsenteringmaintenance = [] under = False @@ -399,11 +399,11 @@ def test(self) -> None: # Building the error message. else: - message = "Some units are under or entering maintenance." + message = "" if under: - message += f" The following units are currently under maintenance: '{unitsundermaintenance}'." + message += f"Units under maintenance: '{unitsundermaintenance}'. " if entering: - message += f" The following units are currently entering maintenance: '{unitsenteringmaintenance}'." + message += f"Units entering maintenance: '{unitsenteringmaintenance}'. " if len(causes) > 0: - message += f" Possible causes: '{causes}'" + message += f"Possible causes: '{causes}'" self.result.is_failure(message) diff --git a/examples/tests.yaml b/examples/tests.yaml index 6e5d88c66..6aee43ee0 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -951,6 +951,8 @@ anta.tests.system: # Verifies there are no core dump files. - VerifyFileSystemUtilization: # Verifies that no partition is utilizing more than 75% of its disk space. + - VerifyMaintenance: + # Verifies that the device is not currently under maintenance. - VerifyMemoryUtilization: # Verifies whether the memory utilization is below 75%. - VerifyNTP: diff --git a/tests/units/anta_tests/test_system.py b/tests/units/anta_tests/test_system.py index cbc030b37..60d4f1574 100644 --- a/tests/units/anta_tests/test_system.py +++ b/tests/units/anta_tests/test_system.py @@ -12,12 +12,12 @@ VerifyCoredump, VerifyCPUUtilization, VerifyFileSystemUtilization, + VerifyMaintenance, VerifyMemoryUtilization, VerifyNTP, VerifyNTPAssociations, VerifyReloadCause, VerifyUptime, - VerifyMaintenance ) from tests.units.anta_tests import test @@ -505,9 +505,7 @@ "units": {}, "interfaces": {}, "vrfs": {}, - "warnings": [ - "Maintenance Mode is disabled." - ], + "warnings": ["Maintenance Mode is disabled."], }, ], "expected": {"result": "success"}, @@ -525,7 +523,7 @@ "onBootMaintenance": False, "intfsViolatingTrafficThreshold": False, "aggInBpsRate": 0, - "aggOutBpsRate": 0 + "aggOutBpsRate": 0, } }, "interfaces": {}, @@ -547,7 +545,7 @@ "onBootMaintenance": False, "intfsViolatingTrafficThreshold": False, "aggInBpsRate": 0, - "aggOutBpsRate": 0 + "aggOutBpsRate": 0, }, "System": { "state": "active", @@ -556,7 +554,7 @@ "onBootMaintenance": False, "intfsViolatingTrafficThreshold": False, "aggInBpsRate": 0, - "aggOutBpsRate": 0 + "aggOutBpsRate": 0, }, }, "interfaces": {}, @@ -578,7 +576,7 @@ "onBootMaintenance": False, "intfsViolatingTrafficThreshold": False, "aggInBpsRate": 0, - "aggOutBpsRate": 0 + "aggOutBpsRate": 0, }, "System": { "state": "active", @@ -587,8 +585,8 @@ "onBootMaintenance": False, "intfsViolatingTrafficThreshold": False, "aggInBpsRate": 0, - "aggOutBpsRate": 0 - } + "aggOutBpsRate": 0, + }, }, "interfaces": {}, "vrfs": {}, @@ -597,7 +595,7 @@ "expected": { "result": "failure", "messages": [ - "Some units are under or entering maintenance. The following units are currently under maintenance: '['mlag']'. Possible causes: ['quiesce is configured']", + "The following units are currently under maintenance: '['mlag']'. Possible causes: ['quiesce is configured']", ], }, }, @@ -614,7 +612,7 @@ "onBootMaintenance": False, "intfsViolatingTrafficThreshold": False, "aggInBpsRate": 0, - "aggOutBpsRate": 0 + "aggOutBpsRate": 0, }, "System": { "state": "maintenanceModeEnter", @@ -623,8 +621,8 @@ "onBootMaintenance": False, "intfsViolatingTrafficThreshold": False, "aggInBpsRate": 0, - "aggOutBpsRate": 0 - } + "aggOutBpsRate": 0, + }, }, "interfaces": {}, "vrfs": {}, @@ -633,7 +631,7 @@ "expected": { "result": "failure", "messages": [ - "Some units are under or entering maintenance. The following units are currently under maintenance: '['mlag']'. The following units are currently entering maintenance: '['System']' Possible causes: ['quiesce is configured','quiesce is configured']", + "Units under maintenance: '['mlag']'. Units entering maintenance: '['System']' Possible causes: ['quiesce is configured','quiesce is configured']", ], }, }, @@ -650,17 +648,17 @@ "onBootMaintenance": True, "intfsViolatingTrafficThreshold": False, "aggInBpsRate": 0, - "aggOutBpsRate": 0 + "aggOutBpsRate": 0, } }, "interfaces": {}, - "vrfs": {} + "vrfs": {}, }, ], "expected": { "result": "failure", "messages": [ - "Some units are under or entering maintenance. The following units are currently under maintenance: '['System']'. Possible causes: ['On-boot maintenance is configured']", + "Units under maintenance: '['System']'. Possible causes: ['On-boot maintenance is configured']", ], }, }, @@ -677,17 +675,17 @@ "onBootMaintenance": False, "intfsViolatingTrafficThreshold": True, "aggInBpsRate": 0, - "aggOutBpsRate": 0 + "aggOutBpsRate": 0, } }, "interfaces": {}, - "vrfs": {} + "vrfs": {}, }, ], "expected": { "result": "failure", "messages": [ - "Some units are under or entering maintenance. The following units are currently entering maintenance: '['System']'. Possible causes: ['Interface traffic threshold violation']", + "Units entering maintenance: '['System']'. Possible causes: ['Interface traffic threshold violation']", ], }, }, From 5b5b51c3106460df30b7ba6432eb0263b6ee2437 Mon Sep 17 00:00:00 2001 From: emil Date: Thu, 6 Mar 2025 12:21:00 +0100 Subject: [PATCH 3/5] update test description --- anta/tests/system.py | 2 +- examples/tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/anta/tests/system.py b/anta/tests/system.py index a4c717d09..d671574b1 100644 --- a/anta/tests/system.py +++ b/anta/tests/system.py @@ -348,7 +348,7 @@ def test(self) -> None: class VerifyMaintenance(AntaTest): - """Verifies that the device is not currently under maintenance. + """Verifies that the device is not currently under or entering maintenance. Expected Results ---------------- diff --git a/examples/tests.yaml b/examples/tests.yaml index 6aee43ee0..5210f3d02 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -952,7 +952,7 @@ anta.tests.system: - VerifyFileSystemUtilization: # Verifies that no partition is utilizing more than 75% of its disk space. - VerifyMaintenance: - # Verifies that the device is not currently under maintenance. + # Verifies that the device is not currently under or entering maintenance. - VerifyMemoryUtilization: # Verifies whether the memory utilization is below 75%. - VerifyNTP: From c3aee915983ae69a756d42732782b15889e4c0c0 Mon Sep 17 00:00:00 2001 From: emil Date: Thu, 6 Mar 2025 15:41:00 +0100 Subject: [PATCH 4/5] fix pytest test cases --- anta/tests/system.py | 7 +++++-- tests/units/anta_tests/test_system.py | 15 +++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/anta/tests/system.py b/anta/tests/system.py index d671574b1..be258449e 100644 --- a/anta/tests/system.py +++ b/anta/tests/system.py @@ -387,7 +387,7 @@ def test(self) -> None: unitsenteringmaintenance.append(unit) entering = True if info["adminState"] == "underMaintenance": - causes.append("quiesce is configured") + causes.append("Quiesce is configured") if info["onBootMaintenance"]: causes.append("On-boot maintenance is configured") if info["intfsViolatingTrafficThreshold"]: @@ -400,10 +400,13 @@ def test(self) -> None: # Building the error message. else: message = "" + unitsundermaintenance = ", ".join(unitsundermaintenance) + unitsenteringmaintenance = " ".join(unitsenteringmaintenance) + causes = ", ".join(causes) if under: message += f"Units under maintenance: '{unitsundermaintenance}'. " if entering: message += f"Units entering maintenance: '{unitsenteringmaintenance}'. " if len(causes) > 0: - message += f"Possible causes: '{causes}'" + message += f"Possible causes: {causes}" self.result.is_failure(message) diff --git a/tests/units/anta_tests/test_system.py b/tests/units/anta_tests/test_system.py index 60d4f1574..6a4993443 100644 --- a/tests/units/anta_tests/test_system.py +++ b/tests/units/anta_tests/test_system.py @@ -508,6 +508,7 @@ "warnings": ["Maintenance Mode is disabled."], }, ], + "inputs": None, "expected": {"result": "success"}, }, { @@ -530,6 +531,7 @@ "vrfs": {}, }, ], + "inputs": None, "expected": {"result": "success"}, }, { @@ -561,6 +563,7 @@ "vrfs": {}, }, ], + "inputs": None, "expected": {"result": "success"}, }, { @@ -592,10 +595,11 @@ "vrfs": {}, }, ], + "inputs": None, "expected": { "result": "failure", "messages": [ - "The following units are currently under maintenance: '['mlag']'. Possible causes: ['quiesce is configured']", + "Units under maintenance: 'mlag'. Possible causes: Quiesce is configured", ], }, }, @@ -628,10 +632,11 @@ "vrfs": {}, }, ], + "inputs": None, "expected": { "result": "failure", "messages": [ - "Units under maintenance: '['mlag']'. Units entering maintenance: '['System']' Possible causes: ['quiesce is configured','quiesce is configured']", + "Units under maintenance: 'mlag'. Units entering maintenance: 'System'. Possible causes: Quiesce is configured, Quiesce is configured", ], }, }, @@ -655,10 +660,11 @@ "vrfs": {}, }, ], + "inputs": None, "expected": { "result": "failure", "messages": [ - "Units under maintenance: '['System']'. Possible causes: ['On-boot maintenance is configured']", + "Units under maintenance: 'System'. Possible causes: Quiesce is configured, On-boot maintenance is configured", ], }, }, @@ -682,10 +688,11 @@ "vrfs": {}, }, ], + "inputs": None, "expected": { "result": "failure", "messages": [ - "Units entering maintenance: '['System']'. Possible causes: ['Interface traffic threshold violation']", + "Units entering maintenance: 'System'. Possible causes: Quiesce is configured, Interface traffic threshold violation", ], }, }, From e1a31b00d39e33e92d991db86f3729a54eb1ed9a Mon Sep 17 00:00:00 2001 From: emil Date: Thu, 6 Mar 2025 15:47:05 +0100 Subject: [PATCH 5/5] update test to not add duplicate reasons --- anta/tests/system.py | 6 +++--- tests/units/anta_tests/test_system.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/anta/tests/system.py b/anta/tests/system.py index be258449e..358308550 100644 --- a/anta/tests/system.py +++ b/anta/tests/system.py @@ -386,11 +386,11 @@ def test(self) -> None: elif info["state"] == "maintenanceModeEnter": unitsenteringmaintenance.append(unit) entering = True - if info["adminState"] == "underMaintenance": + if info["adminState"] == "underMaintenance" and "Quiesce is configured" not in causes: causes.append("Quiesce is configured") - if info["onBootMaintenance"]: + if info["onBootMaintenance"] and "On-boot maintenance is configured" not in causes: causes.append("On-boot maintenance is configured") - if info["intfsViolatingTrafficThreshold"]: + if info["intfsViolatingTrafficThreshold"] and "Interface traffic threshold violation" not in causes: causes.append("Interface traffic threshold violation") # This can occur if maintenance is configured but no unit is configured with 'quiesce'. diff --git a/tests/units/anta_tests/test_system.py b/tests/units/anta_tests/test_system.py index 6a4993443..b5b0b7f3f 100644 --- a/tests/units/anta_tests/test_system.py +++ b/tests/units/anta_tests/test_system.py @@ -636,7 +636,7 @@ "expected": { "result": "failure", "messages": [ - "Units under maintenance: 'mlag'. Units entering maintenance: 'System'. Possible causes: Quiesce is configured, Quiesce is configured", + "Units under maintenance: 'mlag'. Units entering maintenance: 'System'. Possible causes: Quiesce is configured", ], }, },