From 9ba02840ac3e49433be9554563e02c7d702a3f95 Mon Sep 17 00:00:00 2001 From: vitthalmagadum <122079046+vitthalmagadum@users.noreply.github.com> Date: Tue, 25 Feb 2025 21:05:13 +0530 Subject: [PATCH] refactor(anta.tests): Nicer result failure messages MLAG test module (#1039) --- anta/tests/mlag.py | 116 +++++++++++++++++----------- tests/units/anta_tests/test_mlag.py | 87 +++++++++++++-------- 2 files changed, 124 insertions(+), 79 deletions(-) diff --git a/anta/tests/mlag.py b/anta/tests/mlag.py index 215630c91..708271ca7 100644 --- a/anta/tests/mlag.py +++ b/anta/tests/mlag.py @@ -22,10 +22,8 @@ class VerifyMlagStatus(AntaTest): Expected Results ---------------- - * Success: The test will pass if the MLAG state is 'active', negotiation status is 'connected', - peer-link status and local interface status are 'up'. - * Failure: The test will fail if the MLAG state is not 'active', negotiation status is not 'connected', - peer-link status or local interface status are not 'up'. + * Success: The test will pass if the MLAG state is 'active', negotiation status is 'connected', peer-link status and local interface status are 'up'. + * Failure: The test will fail if the MLAG state is not 'active', negotiation status is not 'connected', peer-link status or local interface status are not 'up'. * Skipped: The test will be skipped if MLAG is 'disabled'. Examples @@ -42,21 +40,25 @@ class VerifyMlagStatus(AntaTest): @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyMlagStatus.""" + self.result.is_success() command_output = self.instance_commands[0].json_output + + # Skipping the test if MLAG is disabled if command_output["state"] == "disabled": self.result.is_skipped("MLAG is disabled") return - keys_to_verify = ["state", "negStatus", "localIntfStatus", "peerLinkStatus"] - verified_output = {key: get_value(command_output, key) for key in keys_to_verify} - if ( - verified_output["state"] == "active" - and verified_output["negStatus"] == "connected" - and verified_output["localIntfStatus"] == "up" - and verified_output["peerLinkStatus"] == "up" - ): - self.result.is_success() - else: - self.result.is_failure(f"MLAG status is not OK: {verified_output}") + + # Verifies the negotiation status + if (neg_status := command_output["negStatus"]) != "connected": + self.result.is_failure(f"MLAG negotiation status mismatch - Expected: connected Actual: {neg_status}") + + # Verifies the local interface interface status + if (intf_state := command_output["localIntfStatus"]) != "up": + self.result.is_failure(f"Operational state of the MLAG local interface is not correct - Expected: up Actual: {intf_state}") + + # Verifies the peerLinkStatus + if (peer_link_state := command_output["peerLinkStatus"]) != "up": + self.result.is_failure(f"Operational state of the MLAG peer link is not correct - Expected: up Actual: {peer_link_state}") class VerifyMlagInterfaces(AntaTest): @@ -82,14 +84,19 @@ class VerifyMlagInterfaces(AntaTest): @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyMlagInterfaces.""" + self.result.is_success() command_output = self.instance_commands[0].json_output + + # Skipping the test if MLAG is disabled if command_output["state"] == "disabled": self.result.is_skipped("MLAG is disabled") return - if command_output["mlagPorts"]["Inactive"] == 0 and command_output["mlagPorts"]["Active-partial"] == 0: - self.result.is_success() - else: - self.result.is_failure(f"MLAG status is not OK: {command_output['mlagPorts']}") + + # Verifies the Inactive and Active-partial ports + inactive_ports = command_output["mlagPorts"]["Inactive"] + partial_active_ports = command_output["mlagPorts"]["Active-partial"] + if inactive_ports != 0 or partial_active_ports != 0: + self.result.is_failure(f"MLAG status is not ok - Inactive Ports: {inactive_ports} Partial Active Ports: {partial_active_ports}") class VerifyMlagConfigSanity(AntaTest): @@ -116,16 +123,21 @@ class VerifyMlagConfigSanity(AntaTest): @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyMlagConfigSanity.""" + self.result.is_success() command_output = self.instance_commands[0].json_output + + # Skipping the test if MLAG is disabled if command_output["mlagActive"] is False: self.result.is_skipped("MLAG is disabled") return - keys_to_verify = ["globalConfiguration", "interfaceConfiguration"] - verified_output = {key: get_value(command_output, key) for key in keys_to_verify} - if not any(verified_output.values()): - self.result.is_success() - else: - self.result.is_failure(f"MLAG config-sanity returned inconsistencies: {verified_output}") + + # Verifies the globalConfiguration config-sanity + if get_value(command_output, "globalConfiguration"): + self.result.is_failure("MLAG config-sanity found in global configuration") + + # Verifies the interfaceConfiguration config-sanity + if get_value(command_output, "interfaceConfiguration"): + self.result.is_failure("MLAG config-sanity found in interface configuration") class VerifyMlagReloadDelay(AntaTest): @@ -161,17 +173,21 @@ class Input(AntaTest.Input): @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyMlagReloadDelay.""" + self.result.is_success() command_output = self.instance_commands[0].json_output + + # Skipping the test if MLAG is disabled if command_output["state"] == "disabled": self.result.is_skipped("MLAG is disabled") return - keys_to_verify = ["reloadDelay", "reloadDelayNonMlag"] - verified_output = {key: get_value(command_output, key) for key in keys_to_verify} - if verified_output["reloadDelay"] == self.inputs.reload_delay and verified_output["reloadDelayNonMlag"] == self.inputs.reload_delay_non_mlag: - self.result.is_success() - else: - self.result.is_failure(f"The reload-delay parameters are not configured properly: {verified_output}") + # Verifies the reloadDelay + if (reload_delay := get_value(command_output, "reloadDelay")) != self.inputs.reload_delay: + self.result.is_failure(f"MLAG reload-delay mismatch - Expected: {self.inputs.reload_delay}s Actual: {reload_delay}s") + + # Verifies the reloadDelayNonMlag + if (non_mlag_reload_delay := get_value(command_output, "reloadDelayNonMlag")) != self.inputs.reload_delay_non_mlag: + self.result.is_failure(f"Delay for non-MLAG ports mismatch - Expected: {self.inputs.reload_delay_non_mlag}s Actual: {non_mlag_reload_delay}s") class VerifyMlagDualPrimary(AntaTest): @@ -214,25 +230,37 @@ class Input(AntaTest.Input): @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyMlagDualPrimary.""" + self.result.is_success() errdisabled_action = "errdisableAllInterfaces" if self.inputs.errdisabled else "none" command_output = self.instance_commands[0].json_output + + # Skipping the test if MLAG is disabled if command_output["state"] == "disabled": self.result.is_skipped("MLAG is disabled") return + + # Verifies the dualPrimaryDetectionState if command_output["dualPrimaryDetectionState"] == "disabled": self.result.is_failure("Dual-primary detection is disabled") return - keys_to_verify = ["detail.dualPrimaryDetectionDelay", "detail.dualPrimaryAction", "dualPrimaryMlagRecoveryDelay", "dualPrimaryNonMlagRecoveryDelay"] - verified_output = {key: get_value(command_output, key) for key in keys_to_verify} - if ( - verified_output["detail.dualPrimaryDetectionDelay"] == self.inputs.detection_delay - and verified_output["detail.dualPrimaryAction"] == errdisabled_action - and verified_output["dualPrimaryMlagRecoveryDelay"] == self.inputs.recovery_delay - and verified_output["dualPrimaryNonMlagRecoveryDelay"] == self.inputs.recovery_delay_non_mlag - ): - self.result.is_success() - else: - self.result.is_failure(f"The dual-primary parameters are not configured properly: {verified_output}") + + # Verifies the dualPrimaryAction + if (primary_action := get_value(command_output, "detail.dualPrimaryAction")) != errdisabled_action: + self.result.is_failure(f"Dual-primary action mismatch - Expected: {errdisabled_action} Actual: {primary_action}") + + # Verifies the dualPrimaryDetectionDelay + if (detection_delay := get_value(command_output, "detail.dualPrimaryDetectionDelay")) != self.inputs.detection_delay: + self.result.is_failure(f"Dual-primary detection delay mismatch - Expected: {self.inputs.detection_delay} Actual: {detection_delay}") + + # Verifies the dualPrimaryMlagRecoveryDelay + if (recovery_delay := get_value(command_output, "dualPrimaryMlagRecoveryDelay")) != self.inputs.recovery_delay: + self.result.is_failure(f"Dual-primary MLAG recovery delay mismatch - Expected: {self.inputs.recovery_delay} Actual: {recovery_delay}") + + # Verifies the dualPrimaryNonMlagRecoveryDelay + if (recovery_delay_non_mlag := get_value(command_output, "dualPrimaryNonMlagRecoveryDelay")) != self.inputs.recovery_delay_non_mlag: + self.result.is_failure( + f"Dual-primary non MLAG recovery delay mismatch - Expected: {self.inputs.recovery_delay_non_mlag} Actual: {recovery_delay_non_mlag}" + ) class VerifyMlagPrimaryPriority(AntaTest): @@ -282,6 +310,4 @@ def test(self) -> None: # Check primary priority if primary_priority != self.inputs.primary_priority: - self.result.is_failure( - f"The primary priority does not match expected. Expected `{self.inputs.primary_priority}`, but found `{primary_priority}` instead.", - ) + self.result.is_failure(f"MLAG primary priority mismatch - Expected: {self.inputs.primary_priority} Actual: {primary_priority}") diff --git a/tests/units/anta_tests/test_mlag.py b/tests/units/anta_tests/test_mlag.py index 387c88979..2a0cd25dc 100644 --- a/tests/units/anta_tests/test_mlag.py +++ b/tests/units/anta_tests/test_mlag.py @@ -30,13 +30,33 @@ "expected": {"result": "skipped", "messages": ["MLAG is disabled"]}, }, { - "name": "failure", + "name": "failure-negotiation-status", + "test": VerifyMlagStatus, + "eos_data": [{"state": "active", "negStatus": "connecting", "peerLinkStatus": "up", "localIntfStatus": "up"}], + "inputs": None, + "expected": { + "result": "failure", + "messages": ["MLAG negotiation status mismatch - Expected: connected Actual: connecting"], + }, + }, + { + "name": "failure-local-interface", + "test": VerifyMlagStatus, + "eos_data": [{"state": "active", "negStatus": "connected", "peerLinkStatus": "up", "localIntfStatus": "down"}], + "inputs": None, + "expected": { + "result": "failure", + "messages": ["Operational state of the MLAG local interface is not correct - Expected: up Actual: down"], + }, + }, + { + "name": "failure-peer-link", "test": VerifyMlagStatus, "eos_data": [{"state": "active", "negStatus": "connected", "peerLinkStatus": "down", "localIntfStatus": "up"}], "inputs": None, "expected": { "result": "failure", - "messages": ["MLAG status is not OK: {'state': 'active', 'negStatus': 'connected', 'localIntfStatus': 'up', 'peerLinkStatus': 'down'}"], + "messages": ["Operational state of the MLAG peer link is not correct - Expected: up Actual: down"], }, }, { @@ -74,7 +94,7 @@ "inputs": None, "expected": { "result": "failure", - "messages": ["MLAG status is not OK: {'Disabled': 0, 'Configured': 0, 'Inactive': 0, 'Active-partial': 1, 'Active-full': 1}"], + "messages": ["MLAG status is not ok - Inactive Ports: 0 Partial Active Ports: 1"], }, }, { @@ -89,7 +109,7 @@ "inputs": None, "expected": { "result": "failure", - "messages": ["MLAG status is not OK: {'Disabled': 0, 'Configured': 0, 'Inactive': 1, 'Active-partial': 1, 'Active-full': 1}"], + "messages": ["MLAG status is not ok - Inactive Ports: 1 Partial Active Ports: 1"], }, }, { @@ -124,12 +144,7 @@ "inputs": None, "expected": { "result": "failure", - "messages": [ - "MLAG config-sanity returned inconsistencies: " - "{'globalConfiguration': {'mlag': {'globalParameters': " - "{'dual-primary-detection-delay': {'localValue': '0', 'peerValue': '200'}}}}, " - "'interfaceConfiguration': {}}", - ], + "messages": ["MLAG config-sanity found in global configuration"], }, }, { @@ -146,12 +161,7 @@ "inputs": None, "expected": { "result": "failure", - "messages": [ - "MLAG config-sanity returned inconsistencies: " - "{'globalConfiguration': {}, " - "'interfaceConfiguration': {'trunk-native-vlan mlag30': " - "{'interface': {'Port-Channel30': {'localValue': '123', 'peerValue': '3700'}}}}}", - ], + "messages": ["MLAG config-sanity found in interface configuration"], }, }, { @@ -177,7 +187,10 @@ "test": VerifyMlagReloadDelay, "eos_data": [{"state": "active", "reloadDelay": 400, "reloadDelayNonMlag": 430}], "inputs": {"reload_delay": 300, "reload_delay_non_mlag": 330}, - "expected": {"result": "failure", "messages": ["The reload-delay parameters are not configured properly: {'reloadDelay': 400, 'reloadDelayNonMlag': 430}"]}, + "expected": { + "result": "failure", + "messages": ["MLAG reload-delay mismatch - Expected: 300s Actual: 400s", "Delay for non-MLAG ports mismatch - Expected: 330s Actual: 430s"], + }, }, { "name": "success", @@ -236,13 +249,8 @@ "expected": { "result": "failure", "messages": [ - ( - "The dual-primary parameters are not configured properly: " - "{'detail.dualPrimaryDetectionDelay': 300, " - "'detail.dualPrimaryAction': 'none', " - "'dualPrimaryMlagRecoveryDelay': 160, " - "'dualPrimaryNonMlagRecoveryDelay': 0}" - ), + "Dual-primary detection delay mismatch - Expected: 200 Actual: 300", + "Dual-primary MLAG recovery delay mismatch - Expected: 60 Actual: 160", ], }, }, @@ -262,15 +270,26 @@ "inputs": {"detection_delay": 200, "errdisabled": True, "recovery_delay": 60, "recovery_delay_non_mlag": 0}, "expected": { "result": "failure", - "messages": [ - ( - "The dual-primary parameters are not configured properly: " - "{'detail.dualPrimaryDetectionDelay': 200, " - "'detail.dualPrimaryAction': 'none', " - "'dualPrimaryMlagRecoveryDelay': 60, " - "'dualPrimaryNonMlagRecoveryDelay': 0}" - ), - ], + "messages": ["Dual-primary action mismatch - Expected: errdisableAllInterfaces Actual: none"], + }, + }, + { + "name": "failure-wrong-non-mlag-delay", + "test": VerifyMlagDualPrimary, + "eos_data": [ + { + "state": "active", + "dualPrimaryDetectionState": "configured", + "dualPrimaryPortsErrdisabled": False, + "dualPrimaryMlagRecoveryDelay": 60, + "dualPrimaryNonMlagRecoveryDelay": 120, + "detail": {"dualPrimaryDetectionDelay": 200, "dualPrimaryAction": "errdisableAllInterfaces"}, + }, + ], + "inputs": {"detection_delay": 200, "errdisabled": True, "recovery_delay": 60, "recovery_delay_non_mlag": 60}, + "expected": { + "result": "failure", + "messages": ["Dual-primary non MLAG recovery delay mismatch - Expected: 60 Actual: 120"], }, }, { @@ -325,7 +344,7 @@ "inputs": {"primary_priority": 1}, "expected": { "result": "failure", - "messages": ["The device is not set as MLAG primary.", "The primary priority does not match expected. Expected `1`, but found `32767` instead."], + "messages": ["The device is not set as MLAG primary.", "MLAG primary priority mismatch - Expected: 1 Actual: 32767"], }, }, ]