diff --git a/anta/tests/system.py b/anta/tests/system.py index 9ec719180..c48ec1467 100644 --- a/anta/tests/system.py +++ b/anta/tests/system.py @@ -345,3 +345,58 @@ 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 or entering 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"): + units_under_maintenance = [] + units_entering_maintenance = [] + 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": + units_under_maintenance.append(unit) + elif info["state"] == "maintenanceModeEnter": + units_entering_maintenance.append(unit) + if info["adminState"] == "underMaintenance" and "Quiesce is configured" not in causes: + causes.append("Quiesce is configured") + if info["onBootMaintenance"] and "On-boot maintenance is configured" not in causes: + causes.append("On-boot maintenance is configured") + if info["intfsViolatingTrafficThreshold"] and "Interface traffic threshold violation" not in causes: + causes.append("Interface traffic threshold violation") + + # Declare success if maintenance is configured but no unit is configured with 'quiesce'. + if not units_under_maintenance and not units_entering_maintenance and not causes: + return + + # Building the error message. + if units_under_maintenance: + self.result.is_failure(f"Units under maintenance: '{', '.join(units_under_maintenance)}'.") + if units_entering_maintenance: + self.result.is_failure(f"Units entering maintenance: '{', '.join(units_entering_maintenance)}'.") + if causes: + self.result.is_failure(f"Possible causes: '{', '.join(causes)}'.") diff --git a/examples/tests.yaml b/examples/tests.yaml index 6e5d88c66..5210f3d02 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 or entering 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 5fe0fbad1..6ce3920c4 100644 --- a/tests/units/anta_tests/test_system.py +++ b/tests/units/anta_tests/test_system.py @@ -12,6 +12,7 @@ VerifyCoredump, VerifyCPUUtilization, VerifyFileSystemUtilization, + VerifyMaintenance, VerifyMemoryUtilization, VerifyNTP, VerifyNTPAssociations, @@ -496,4 +497,208 @@ ], }, }, + { + "name": "success-no-maintenance-configured", + "test": VerifyMaintenance, + "eos_data": [ + { + "units": {}, + "interfaces": {}, + "vrfs": {}, + "warnings": ["Maintenance Mode is disabled."], + }, + ], + "inputs": None, + "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": {}, + }, + ], + "inputs": None, + "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": {}, + }, + ], + "inputs": None, + "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": {}, + }, + ], + "inputs": None, + "expected": { + "result": "failure", + "messages": [ + "Units 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": {}, + }, + ], + "inputs": None, + "expected": { + "result": "failure", + "messages": [ + "Units under maintenance: 'mlag'.", + "Units entering maintenance: 'System'.", + "Possible causes: '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": {}, + }, + ], + "inputs": None, + "expected": { + "result": "failure", + "messages": [ + "Units under maintenance: 'System'.", + "Possible causes: 'Quiesce is configured, 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": {}, + }, + ], + "inputs": None, + "expected": { + "result": "failure", + "messages": [ + "Units entering maintenance: 'System'.", + "Possible causes: 'Quiesce is configured, Interface traffic threshold violation'.", + ], + }, + }, ]