From f99e19bd079fddd45ffa86bcc4cad5eaa72cf457 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Mon, 5 May 2025 08:20:21 +0000 Subject: [PATCH 1/6] feat(anta.tests): Updated test VerifyInterfaceErrors and VerifyInterfaceDiscards to support all interface errors with expected threshold --- anta/tests/interfaces.py | 55 ++++- examples/tests.yaml | 2 +- tests/units/anta_tests/test_interfaces.py | 262 +++++++++++++++++++--- 3 files changed, 281 insertions(+), 38 deletions(-) diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py index 282b9456f..2c1e703f7 100644 --- a/anta/tests/interfaces.py +++ b/anta/tests/interfaces.py @@ -98,12 +98,14 @@ def test(self) -> None: class VerifyInterfaceErrors(AntaTest): - """Verifies that the interfaces error counters are equal to zero. + """Verifies that the interface error counters are below the expected error threshold. Expected Results ---------------- - * Success: The test will pass if all interfaces have error counters equal to zero. - * Failure: The test will fail if one or more interfaces have non-zero error counters. + * Success: The test will pass if all interfaces have error counters below the expected threshold and the link status changes field matches the expected value, + if provided. + * Failure: The test will fail if one or more interfaces have error counters below the expected threshold, + or if the link status changes field does not match the expected value (if provided). Examples -------- @@ -114,17 +116,46 @@ class VerifyInterfaceErrors(AntaTest): """ categories: ClassVar[list[str]] = ["interfaces"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show interfaces counters errors", revision=1)] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show interfaces", revision=1)] + + class Input(AntaTest.Input): + """Input model for the VerifyInterfaceErrors test.""" + + error_threshold: PositiveInteger = 0 + """The low value for error threshold above which the test will fail.""" + link_status_changes: PositiveInteger | None = None + """The low value for link status changes above which the test will fail.""" @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyInterfaceErrors.""" self.result.is_success() + # TODO: Do we need to validate the rxPause and txPause error counters from input and output errors, respectively? command_output = self.instance_commands[0].json_output - for interface, counters in command_output["interfaceErrorCounters"].items(): - counters_data = [f"{counter}: {value}" for counter, value in counters.items() if value > 0] - if counters_data: - self.result.is_failure(f"Interface: {interface} - Non-zero error counter(s) - {', '.join(counters_data)}") + error_threshold = self.inputs.error_threshold + for interface, data in command_output["interfaces"].items(): + error_counters = data.get("interfaceCounters", {}) + input_counters_data = [f"{counter}: {value}" for counter, value in error_counters.get("inputErrorsDetail", {}).items() if value > error_threshold] + if input_counters_data: + self.result.is_failure(f"Interface: {interface} - Non-zero input error counter(s) - {', '.join(input_counters_data)}") + output_counters_data = [f"{counter}: {value}" for counter, value in error_counters.get("outputErrorsDetail", {}).items() if value > error_threshold] + if output_counters_data: + self.result.is_failure(f"Interface: {interface} - Non-zero output error counter(s) - {', '.join(output_counters_data)}") + if error_counters.get("totalInErrors") > error_threshold: + self.result.is_failure( + f"Interface: {interface} - Total input error counter(s) mismatch - Expected: {error_threshold} Actual: {error_counters['totalInErrors']}" + ) + if error_counters.get("totalOutErrors") > error_threshold: + self.result.is_failure( + f"Interface: {interface} - Total output error counter(s) mismatch - Expected: {error_threshold} Actual: {error_counters['totalOutErrors']}" + ) + if ( + self.inputs.link_status_changes and error_counters.get("linkStatusChanges") > self.inputs.link_status_changes + ): # TODO: Need to check for the default value for linkStatusChanges + self.result.is_failure( + f"Interface: {interface} - Link Status changes mismatch - Expected: {self.inputs.link_status_changes} " + "Actual: {error_counters['linkStatusChanges']}" + ) class VerifyInterfaceDiscards(AntaTest): @@ -146,13 +177,19 @@ class VerifyInterfaceDiscards(AntaTest): categories: ClassVar[list[str]] = ["interfaces"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show interfaces counters discards", revision=1)] + class Input(AntaTest.Input): + """Input model for the VerifyInterfaceDiscards test.""" + + error_threshold: PositiveInteger = 0 + """The low value for error threshold above which the test will fail.""" + @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyInterfaceDiscards.""" self.result.is_success() command_output = self.instance_commands[0].json_output for interface, interface_data in command_output["interfaces"].items(): - counters_data = [f"{counter}: {value}" for counter, value in interface_data.items() if value > 0] + counters_data = [f"{counter}: {value}" for counter, value in interface_data.items() if value > self.inputs.error_threshold] if counters_data: self.result.is_failure(f"Interface: {interface} - Non-zero discard counter(s): {', '.join(counters_data)}") diff --git a/examples/tests.yaml b/examples/tests.yaml index afc14d904..d99e10c0f 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -252,7 +252,7 @@ anta.tests.interfaces: - VerifyInterfaceErrDisabled: # Verifies there are no interfaces in the errdisabled state. - VerifyInterfaceErrors: - # Verifies that the interfaces error counters are equal to zero. + # Verifies that the interface error counters are below the expected error threshold. - VerifyInterfaceIPv4: # Verifies the interface IPv4 addresses. interfaces: diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py index 778133b24..c36188a44 100644 --- a/tests/units/anta_tests/test_interfaces.py +++ b/tests/units/anta_tests/test_interfaces.py @@ -814,69 +814,237 @@ "test": VerifyInterfaceErrors, "eos_data": [ { - "interfaceErrorCounters": { - "Ethernet1": {"inErrors": 0, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, - "Ethernet6": {"inErrors": 0, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, - }, - }, + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + } + } ], "inputs": None, "expected": {"result": "success"}, }, { - "name": "failure-multiple-intfs", + "name": "success-error-threshold", "test": VerifyInterfaceErrors, "eos_data": [ { - "interfaceErrorCounters": { - "Ethernet1": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, - "Ethernet6": {"inErrors": 0, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 666, "symbolErrors": 0}, - }, - }, + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 2, + "inputErrorsDetail": {"runtFrames": 5, "giantFrames": 5, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 3, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 5, "deferredTransmissions": 5, "txPause": 0}, + }, + }, + } + } + ], + "inputs": {"error_threshold": 5}, + "expected": {"result": "success"}, + }, + { + "name": "success-error-threshold-linkstatus", + "test": VerifyInterfaceErrors, + "eos_data": [ + { + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 2, + "inputErrorsDetail": {"runtFrames": 5, "giantFrames": 5, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 3, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 5, "deferredTransmissions": 5, "txPause": 0}, + }, + }, + } + } + ], + "inputs": {"error_threshold": 5, "link_status_changes": 2}, + "expected": {"result": "success"}, + }, + { + "name": "failure-multiple-intfs-input-errors", + "test": VerifyInterfaceErrors, + "eos_data": [ + { + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 10, + "inputErrorsDetail": {"runtFrames": 10, "giantFrames": 10, "fcsErrors": 10, "alignmentErrors": 10, "symbolErrors": 10, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 20, + "inputErrorsDetail": {"runtFrames": 20, "giantFrames": 20, "fcsErrors": 20, "alignmentErrors": 20, "symbolErrors": 20, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + } + } ], "inputs": None, "expected": { "result": "failure", "messages": [ - "Interface: Ethernet1 - Non-zero error counter(s) - inErrors: 42", - "Interface: Ethernet6 - Non-zero error counter(s) - alignmentErrors: 666", + "Interface: Ethernet2 - Non-zero input error counter(s) - runtFrames: 10, giantFrames: 10, fcsErrors: 10, alignmentErrors: 10, symbolErrors: 10", + "Interface: Ethernet2 - Total input error counter(s) mismatch - Expected: 0 Actual: 10", + "Interface: Ethernet4 - Non-zero input error counter(s) - runtFrames: 20, giantFrames: 20, fcsErrors: 20, alignmentErrors: 20, symbolErrors: 20", + "Interface: Ethernet4 - Total input error counter(s) mismatch - Expected: 0 Actual: 20", ], }, }, { - "name": "failure-multiple-intfs-multiple-errors", + "name": "failure-intfs-output-errors", "test": VerifyInterfaceErrors, "eos_data": [ { - "interfaceErrorCounters": { - "Ethernet1": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 10, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, - "Ethernet6": {"inErrors": 0, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 6, "symbolErrors": 10}, - }, - }, + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 10, + "outputErrorsDetail": {"collisions": 20, "lateCollisions": 20, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + } + } ], "inputs": None, "expected": { "result": "failure", "messages": [ - "Interface: Ethernet1 - Non-zero error counter(s) - inErrors: 42, outErrors: 10", - "Interface: Ethernet6 - Non-zero error counter(s) - alignmentErrors: 6, symbolErrors: 10", + "Interface: Ethernet2 - Non-zero output error counter(s) - collisions: 20, lateCollisions: 20", + "Interface: Ethernet2 - Total output error counter(s) mismatch - Expected: 0 Actual: 10", ], }, }, { - "name": "failure-single-intf-multiple-errors", + "name": "failure-intf-multiple-errors", "test": VerifyInterfaceErrors, "eos_data": [ { - "interfaceErrorCounters": { - "Ethernet1": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 2, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, - }, - }, + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 32, + "totalInErrors": 20, + "inputErrorsDetail": {"runtFrames": 30, "giantFrames": 34, "fcsErrors": 20, "alignmentErrors": 53, "symbolErrors": 30, "rxPause": 4}, + "totalOutErrors": 10, + "outputErrorsDetail": {"collisions": 20, "lateCollisions": 20, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 32, + "totalInErrors": 20, + "inputErrorsDetail": {"runtFrames": 30, "giantFrames": 0, "fcsErrors": 30, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 30}, + "totalOutErrors": 42, + "outputErrorsDetail": {"collisions": 30, "lateCollisions": 43, "deferredTransmissions": 0, "txPause": 20}, + }, + }, + } + } ], - "inputs": None, + "inputs": {"error_threshold": 5, "link_status_changes": 2}, "expected": { "result": "failure", - "messages": ["Interface: Ethernet1 - Non-zero error counter(s) - inErrors: 42, outErrors: 2"], + "messages": [ + "Interface: Ethernet2 - Non-zero input error counter(s) - runtFrames: 30, giantFrames: 34, fcsErrors: 20, alignmentErrors: 53, symbolErrors: 30", + "Interface: Ethernet2 - Non-zero output error counter(s) - collisions: 20, lateCollisions: 20", + "Interface: Ethernet2 - Total input error counter(s) mismatch - Expected: 5 Actual: 20", + "Interface: Ethernet2 - Total output error counter(s) mismatch - Expected: 5 Actual: 10", + "Interface: Ethernet2 - Link Status changes mismatch - Expected: 2 Actual: 32", + "Interface: Ethernet4 - Non-zero input error counter(s) - runtFrames: 30, fcsErrors: 30, rxPause: 30", + "Interface: Ethernet4 - Non-zero output error counter(s) - collisions: 30, lateCollisions: 43, txPause: 20", + "Interface: Ethernet4 - Total input error counter(s) mismatch - Expected: 5 Actual: 20", + "Interface: Ethernet4 - Total output error counter(s) mismatch - Expected: 5 Actual: 42", + "Interface: Ethernet4 - Link Status changes mismatch - Expected: 2 Actual: 32", + ], }, }, { @@ -895,6 +1063,22 @@ "inputs": None, "expected": {"result": "success"}, }, + { + "name": "success-error-threshold", + "test": VerifyInterfaceDiscards, + "eos_data": [ + { + "inDiscardsTotal": 0, + "interfaces": { + "Ethernet2": {"outDiscards": 0, "inDiscards": 0}, + "Ethernet1": {"outDiscards": 0, "inDiscards": 0}, + }, + "outDiscardsTotal": 0, + }, + ], + "inputs": {"error_threshold": 3}, + "expected": {"result": "success"}, + }, { "name": "failure", "test": VerifyInterfaceDiscards, @@ -917,6 +1101,28 @@ ], }, }, + { + "name": "failure-error-threshold", + "test": VerifyInterfaceDiscards, + "eos_data": [ + { + "inDiscardsTotal": 0, + "interfaces": { + "Ethernet2": {"outDiscards": 10, "inDiscards": 10}, + "Ethernet1": {"outDiscards": 0, "inDiscards": 10}, + }, + "outDiscardsTotal": 0, + }, + ], + "inputs": {"error_threshold": 3}, + "expected": { + "result": "failure", + "messages": [ + "Interface: Ethernet2 - Non-zero discard counter(s): outDiscards: 10, inDiscards: 10", + "Interface: Ethernet1 - Non-zero discard counter(s): inDiscards: 10", + ], + }, + }, { "name": "success", "test": VerifyInterfaceErrDisabled, From 3f916ee204bf4095fc423c41ee676c223cbb9b47 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Mon, 5 May 2025 08:46:10 +0000 Subject: [PATCH 2/6] fixed unit testcases --- anta/tests/interfaces.py | 4 ++-- tests/units/anta_tests/test_interfaces.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py index 2c1e703f7..020aeb5a1 100644 --- a/anta/tests/interfaces.py +++ b/anta/tests/interfaces.py @@ -153,8 +153,8 @@ def test(self) -> None: self.inputs.link_status_changes and error_counters.get("linkStatusChanges") > self.inputs.link_status_changes ): # TODO: Need to check for the default value for linkStatusChanges self.result.is_failure( - f"Interface: {interface} - Link Status changes mismatch - Expected: {self.inputs.link_status_changes} " - "Actual: {error_counters['linkStatusChanges']}" + f"Interface: {interface} - Link status changes mismatch - Expected: {self.inputs.link_status_changes} " + f"Actual: {error_counters['linkStatusChanges']}" ) diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py index c36188a44..9a30645a8 100644 --- a/tests/units/anta_tests/test_interfaces.py +++ b/tests/units/anta_tests/test_interfaces.py @@ -1038,12 +1038,12 @@ "Interface: Ethernet2 - Non-zero output error counter(s) - collisions: 20, lateCollisions: 20", "Interface: Ethernet2 - Total input error counter(s) mismatch - Expected: 5 Actual: 20", "Interface: Ethernet2 - Total output error counter(s) mismatch - Expected: 5 Actual: 10", - "Interface: Ethernet2 - Link Status changes mismatch - Expected: 2 Actual: 32", + "Interface: Ethernet2 - Link status changes mismatch - Expected: 2 Actual: 32", "Interface: Ethernet4 - Non-zero input error counter(s) - runtFrames: 30, fcsErrors: 30, rxPause: 30", "Interface: Ethernet4 - Non-zero output error counter(s) - collisions: 30, lateCollisions: 43, txPause: 20", "Interface: Ethernet4 - Total input error counter(s) mismatch - Expected: 5 Actual: 20", "Interface: Ethernet4 - Total output error counter(s) mismatch - Expected: 5 Actual: 42", - "Interface: Ethernet4 - Link Status changes mismatch - Expected: 2 Actual: 32", + "Interface: Ethernet4 - Link status changes mismatch - Expected: 2 Actual: 32", ], }, }, From ed8618af5defaa6229bbeb4a2465f0e52a5f60d3 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Tue, 13 May 2025 09:38:49 +0000 Subject: [PATCH 3/6] Updated testcase with resolving conflicts and inline comments --- anta/tests/interfaces.py | 11 +++ tests/units/anta_tests/test_interfaces.py | 112 +++++++++++++++++----- 2 files changed, 97 insertions(+), 26 deletions(-) diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py index eb077e67c..169c668f7 100644 --- a/anta/tests/interfaces.py +++ b/anta/tests/interfaces.py @@ -183,26 +183,37 @@ def test(self) -> None: self.result.is_success() # TODO: Do we need to validate the rxPause and txPause error counters from input and output errors, respectively? command_output = self.instance_commands[0].json_output + error_threshold = self.inputs.error_threshold for interface, data in command_output["interfaces"].items(): # Verification is skipped if the interface is in the ignored interfaces list. if _is_interface_ignored(interface, self.inputs.ignored_interfaces): continue + error_counters = data.get("interfaceCounters", {}) input_counters_data = [f"{counter}: {value}" for counter, value in error_counters.get("inputErrorsDetail", {}).items() if value > error_threshold] + # Verify that input error counters are non-zero if input_counters_data: self.result.is_failure(f"Interface: {interface} - Non-zero input error counter(s) - {', '.join(input_counters_data)}") + output_counters_data = [f"{counter}: {value}" for counter, value in error_counters.get("outputErrorsDetail", {}).items() if value > error_threshold] + # Verify that output error counters are non-zero if output_counters_data: self.result.is_failure(f"Interface: {interface} - Non-zero output error counter(s) - {', '.join(output_counters_data)}") + + # Verify that total input error counters are non-zero if error_counters.get("totalInErrors") > error_threshold: self.result.is_failure( f"Interface: {interface} - Total input error counter(s) mismatch - Expected: {error_threshold} Actual: {error_counters['totalInErrors']}" ) + + # Verify that total output error counters are non-zero if error_counters.get("totalOutErrors") > error_threshold: self.result.is_failure( f"Interface: {interface} - Total output error counter(s) mismatch - Expected: {error_threshold} Actual: {error_counters['totalOutErrors']}" ) + + # Verify that link status changes are within the expected range if ( self.inputs.link_status_changes and error_counters.get("linkStatusChanges") > self.inputs.link_status_changes ): # TODO: Need to check for the default value for linkStatusChanges diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py index 6d2e848fd..28b03e6dd 100644 --- a/tests/units/anta_tests/test_interfaces.py +++ b/tests/units/anta_tests/test_interfaces.py @@ -1170,19 +1170,32 @@ "test": VerifyInterfaceErrors, "eos_data": [ { - "interfaceErrorCounters": { - "Ethernet1": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 30, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 30, "deferredTransmissions": 0, "txPause": 0}, + }, + }, "Management0": { - "inErrors": 0, - "frameTooLongs": 0, - "outErrors": 0, - "frameTooShorts": 0, - "fcsErrors": 0, - "alignmentErrors": 666, - "symbolErrors": 0, + "name": "Management0", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 10, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 30, "txPause": 0}, + }, }, - }, - }, + } + } ], "inputs": {"ignored_interfaces": ["Ethernet", "Management0"]}, "expected": { @@ -1194,23 +1207,54 @@ "test": VerifyInterfaceErrors, "eos_data": [ { - "interfaceErrorCounters": { - "Ethernet1": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 12, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, "Management0": { - "inErrors": 0, - "frameTooLongs": 0, - "outErrors": 0, - "frameTooShorts": 0, - "fcsErrors": 0, - "alignmentErrors": 666, - "symbolErrors": 0, - }, - "Ethernet10": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, - }, - }, + "name": "Management0", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 12, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 10, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 10, "deferredTransmissions": 0, "txPause": 10}, + }, + }, + "Ethernet10": { + "name": "Ethernet10", + "interfaceStatistics": {}, + "interfaceCounters": { + "linkStatusChanges": 12, + "totalInErrors": 10, + "inputErrorsDetail": {"runtFrames": 10, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 20, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + } + } ], - "inputs": {"ignored_interfaces": ["Ethernet1", "Management0"]}, - "expected": {"result": "failure", "messages": ["Interface: Ethernet10 - Non-zero error counter(s) - inErrors: 42"]}, + "inputs": {"ignored_interfaces": ["Ethernet2", "Management0"], "link_status_changes": 2}, + "expected": { + "result": "failure", + "messages": [ + "Interface: Ethernet10 - Non-zero input error counter(s) - runtFrames: 10", + "Interface: Ethernet10 - Non-zero output error counter(s) - lateCollisions: 20", + "Interface: Ethernet10 - Total input error counter(s) mismatch - Expected: 0 Actual: 10", + "Interface: Ethernet10 - Link status changes mismatch - Expected: 2 Actual: 12", + ], + }, }, { "name": "failure-multiple-intfs", @@ -1474,6 +1518,22 @@ ], }, }, + { + "name": "success-error-threshold", + "test": VerifyInterfaceDiscards, + "eos_data": [ + { + "inDiscardsTotal": 0, + "interfaces": { + "Ethernet2": {"outDiscards": 0, "inDiscards": 0}, + "Ethernet1": {"outDiscards": 0, "inDiscards": 0}, + }, + "outDiscardsTotal": 0, + }, + ], + "inputs": {"error_threshold": 3}, + "expected": {"result": "success"}, + }, { "name": "failure-error-threshold", "test": VerifyInterfaceDiscards, From b79fb1c907474d4e96afb194d347d40fa8ca50ad Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Tue, 20 May 2025 06:06:50 +0000 Subject: [PATCH 4/6] Reverted the changes --- anta/tests/interfaces.py | 2 +- tests/units/anta_tests/test_interfaces.py | 54 ++++++++++------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py index 08e1877c7..93b7cab73 100644 --- a/anta/tests/interfaces.py +++ b/anta/tests/interfaces.py @@ -225,7 +225,7 @@ def test(self) -> None: if _is_interface_ignored(interface, self.inputs.ignored_interfaces): continue - counters_data = [f"{counter}: {value}" for counter, value in interface_data.items() if value > self.inputs.error_threshold] + counters_data = [f"{counter}: {value}" for counter, value in interface_data.items() if value > 0] if counters_data: self.result.is_failure(f"Interface: {interface} - Non-zero discard counter(s): {', '.join(counters_data)}") diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py index 1a6507d40..572928321 100644 --- a/tests/units/anta_tests/test_interfaces.py +++ b/tests/units/anta_tests/test_interfaces.py @@ -1140,18 +1140,16 @@ (VerifyInterfaceErrors, "success-ignore-interface"): { "eos_data": [ { - "interfaces": { - "Ethernet2": { - "name": "Ethernet2", - "interfaceAddress": [], - "interfaceStatistics": {}, - "interfaceCounters": { - "linkStatusChanges": 2, - "totalInErrors": 0, - "inputErrorsDetail": {"runtFrames": 30, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, - "totalOutErrors": 0, - "outputErrorsDetail": {"collisions": 0, "lateCollisions": 30, "deferredTransmissions": 0, "txPause": 0}, - }, + "interfaceErrorCounters": { + "Ethernet1": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, + "Management0": { + "inErrors": 0, + "frameTooLongs": 0, + "outErrors": 0, + "frameTooShorts": 0, + "fcsErrors": 0, + "alignmentErrors": 666, + "symbolErrors": 0, }, } } @@ -1162,18 +1160,16 @@ (VerifyInterfaceErrors, "failure-ignore-interface"): { "eos_data": [ { - "interfaces": { - "Ethernet2": { - "name": "Ethernet2", - "interfaceAddress": [], - "interfaceStatistics": {}, - "interfaceCounters": { - "linkStatusChanges": 12, - "totalInErrors": 0, - "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, - "totalOutErrors": 0, - "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, - }, + "interfaceErrorCounters": { + "Ethernet1": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, + "Management0": { + "inErrors": 0, + "frameTooLongs": 0, + "outErrors": 0, + "frameTooShorts": 0, + "fcsErrors": 0, + "alignmentErrors": 666, + "symbolErrors": 0, }, "Ethernet10": {"inErrors": 42, "frameTooLongs": 0, "outErrors": 0, "frameTooShorts": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0}, } @@ -1194,10 +1190,8 @@ "expected": { "result": AntaTestStatus.FAILURE, "messages": [ - "Interface: Ethernet2 - Non-zero input error counter(s) - runtFrames: 10, giantFrames: 10, fcsErrors: 10, alignmentErrors: 10, symbolErrors: 10", - "Interface: Ethernet2 - Total input error counter(s) mismatch - Expected: 0 Actual: 10", - "Interface: Ethernet4 - Non-zero input error counter(s) - runtFrames: 20, giantFrames: 20, fcsErrors: 20, alignmentErrors: 20, symbolErrors: 20", - "Interface: Ethernet4 - Total input error counter(s) mismatch - Expected: 0 Actual: 20", + "Interface: Ethernet1 - Non-zero error counter(s) - inErrors: 42", + "Interface: Ethernet6 - Non-zero error counter(s) - alignmentErrors: 666", ], }, }, @@ -1213,8 +1207,8 @@ "expected": { "result": AntaTestStatus.FAILURE, "messages": [ - "Interface: Ethernet2 - Non-zero output error counter(s) - collisions: 20, lateCollisions: 20", - "Interface: Ethernet2 - Total output error counter(s) mismatch - Expected: 0 Actual: 10", + "Interface: Ethernet1 - Non-zero error counter(s) - inErrors: 42, outErrors: 10", + "Interface: Ethernet6 - Non-zero error counter(s) - alignmentErrors: 6, symbolErrors: 10", ], }, }, From 3eddc1ba5855725eba72b4fa04d634eb151936e5 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Tue, 20 May 2025 10:49:07 +0000 Subject: [PATCH 5/6] Added new testcase VerifyInterfacesCounters --- anta/tests/interfaces.py | 131 ++++++++ examples/tests.yaml | 8 + tests/units/anta_tests/test_interfaces.py | 363 ++++++++++++++++++++++ 3 files changed, 502 insertions(+) diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py index 93b7cab73..c20da5541 100644 --- a/anta/tests/interfaces.py +++ b/anta/tests/interfaces.py @@ -8,6 +8,7 @@ from __future__ import annotations import re +from datetime import datetime, timezone from typing import ClassVar, TypeVar from pydantic import Field, field_validator @@ -996,3 +997,133 @@ def test(self) -> None: if (part_port_details := actual_interface_output["partner_port_details"]) != expected_details: self.result.is_failure(f"{interface} - Partner port details mismatch - {format_data(part_port_details)}") + + +class VerifyInterfacesCounters(AntaTest): + """Verifies the interfaces counter details. + + Expected Results + ---------------- + * Success: The test will pass if all interfaces have error counters below the expected threshold and the link status changes field matches the expected value, + * Failure: The test will fail if one or more interfaces have error counters below the expected threshold, + or if the link status changes field does not match the expected value (if provided). + + Examples + -------- + ```yaml + anta.tests.interfaces: + - VerifyInterfacesCounters: + ignored_interfaces: + - Management # Ignore all Management interfaces + - Ethernet2.100 + - Ethernet1/1 + errors_threshold: 10 + link_status_changes_threshold: 100 + ``` + """ + + categories: ClassVar[list[str]] = ["interfaces"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show interfaces", revision=1)] + + class Input(AntaTest.Input): + """Input model for the VerifyInterfacesCounters test.""" + + ignored_interfaces: list[InterfaceType | Interface] = Field(default=["Dps", "Fabric", "Loopback", "Management", "Recirc-Channel", "Tunnel", "Vxlan"]) + """A list of L3 interfaces or interfaces types like Loopback, Tunnel which will ignore all Loopback and Tunnel interfaces.""" + errors_threshold: PositiveInteger = 0 + """The max value for error threshold above which the test will fail.""" + link_status_changes_threshold: PositiveInteger + """The max value for link status changes above which the test will fail.""" + + def _get_last_change_time_stamp(self, interface_time_stamp: float) -> float | None: + """Return the last change time stamp.""" + now = datetime.now(timezone.utc) + if interface_time_stamp: + then = datetime.fromtimestamp(interface_time_stamp, tz=timezone.utc) + delta = now - then + return delta.days * 24 if delta.days < 1 else delta.days + return None + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyInterfacesCounters.""" + self.result.is_success() + command_output = self.instance_commands[0].json_output + + for interface, data in command_output["interfaces"].items(): + # Verify is skipped if the interface is in the ignored interfaces list + if _is_interface_ignored(interface, self.inputs.ignored_interfaces): + continue + + # TODO: Need to skip subinterfaces + + last_state_timestamp = self._get_last_change_time_stamp(data.get("lastStatusChangeTimestamp")) + int_desc = data.get("description") if data.get("description") else None + error_counters = data.get("interfaceCounters", {}) + errors_threshold = self.inputs.errors_threshold + # Verify the interface status + if data["lineProtocolStatus"] == "down" or data["lineProtocolStatus"] == "notPresent": # TODO: downtime need to be confirm + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Downtime: {last_state_timestamp} - Incorrect state - Expected: up" + f" Actual: {data['lineProtocolStatus']}" + ) + continue + + input_counters_data = [ + f"{counter}: {value}" + for counter, value in error_counters.qget("inputErrorsDetail", {}).items() + if all([value > errors_threshold, counter != "rxPause"]) + ] + # Verify that input error counters are non-zero + if input_counters_data: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Non-zero input error counter(s) - " + f"{', '.join(input_counters_data)}" + ) + + output_counters_data = [ + f"{counter}: {value}" + for counter, value in error_counters.get("outputErrorsDetail", {}).items() + if all([value > errors_threshold, counter != "txPause"]) + ] + # Verify that output error counters are non-zero + if output_counters_data: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Non-zero output error counter(s) - " + f"{', '.join(output_counters_data)}" + ) + + # Verify that total input error counters are non-zero + if error_counters.get("totalInErrors") > errors_threshold: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Total input error counter(s) mismatch -" + f" Expected: < {errors_threshold} Actual: {error_counters['totalInErrors']}" + ) + + # Verify that total output error counters are non-zero + if error_counters.get("totalOutErrors") > errors_threshold: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Total output error counter(s) mismatch -" + f" Expected: < {errors_threshold} Actual: {error_counters['totalOutErrors']}" + ) + + # Verify that link status changes are within the expected range + if error_counters.get("linkStatusChanges") > self.inputs.link_status_changes_threshold: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Link status changes mismatch -" + f" Expected: < {self.inputs.link_status_changes_threshold} Actual: {error_counters['linkStatusChanges']}" + ) + + # Verify that interfaces input packet discard counters are non-zero + if error_counters.get("inDiscards") > self.inputs.link_status_changes_threshold: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Input packet discards counter(s) mismatch -" + f" Expected: < {errors_threshold} Actual: {error_counters['inDiscards']}" + ) + + # Verify that interfaces output packet discard counters are non-zero + if error_counters.get("outDiscards") > self.inputs.link_status_changes_threshold: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Output packet discards counter(s) mismatch -" + f" Expected: < {errors_threshold} Actual: {error_counters['outDiscards']}" + ) diff --git a/examples/tests.yaml b/examples/tests.yaml index a25957f0f..d3ad03a5f 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -273,6 +273,14 @@ anta.tests.interfaces: ignored_interfaces: - Ethernet1 - Port-Channel1 + - VerifyInterfacesCounters: + # Verifies the interfaces counter details. + ignored_interfaces: + - Management # Ignore all Management interfaces + - Ethernet2.100 + - Ethernet1/1 + errors_threshold: 10 + link_status_changes_threshold: 100 - VerifyInterfacesSpeed: # Verifies the speed, lanes, auto-negotiation status, and mode as full duplex for interfaces. interfaces: diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py index 572928321..ec6fe54b7 100644 --- a/tests/units/anta_tests/test_interfaces.py +++ b/tests/units/anta_tests/test_interfaces.py @@ -17,6 +17,7 @@ VerifyInterfaceErrDisabled, VerifyInterfaceErrors, VerifyInterfaceIPv4, + VerifyInterfacesCounters, VerifyInterfacesSpeed, VerifyInterfacesStatus, VerifyInterfaceUtilization, @@ -3031,4 +3032,366 @@ ], }, }, + (VerifyInterfacesCounters, "success"): { + "eos_data": [ + { + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "interfaceAddress": [], + "description": "", + "lastStatusChangeTimestamp": 1747729843.2728114, + "interfaceCounters": { + "inOctets": 53910, + "inUcastPkts": 0, + "inMulticastPkts": 415, + "inBroadcastPkts": 0, + "inDiscards": 0, + "inTotalPkts": 415, + "outOctets": 0, + "outUcastPkts": 0, + "outMulticastPkts": 0, + "outBroadcastPkts": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "hardware": "ethernet", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2721722, + "interfaceCounters": { + "inOctets": 6769, + "inUcastPkts": 0, + "inMulticastPkts": 33, + "inBroadcastPkts": 0, + "inDiscards": 0, + "inTotalPkts": 33, + "outOctets": 0, + "outUcastPkts": 0, + "outMulticastPkts": 0, + "outBroadcastPkts": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + "counterRefreshTime": 1747730615.124224, + }, + }, + "Ethernet1": { + "name": "Ethernet1", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "hardware": "ethernet", + "lastStatusChangeTimestamp": 1747729843.2710755, + "interfaceCounters": { + "inOctets": 6541, + "inUcastPkts": 0, + "inMulticastPkts": 31, + "inBroadcastPkts": 0, + "inDiscards": 0, + "inTotalPkts": 31, + "outOctets": 0, + "outUcastPkts": 0, + "outMulticastPkts": 0, + "outBroadcastPkts": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + "counterRefreshTime": 1747730615.136472, + }, + }, + "Ethernet3": { + "name": "Ethernet3", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "hardware": "ethernet", + "interfaceAddress": [], + "description": "", + "lastStatusChangeTimestamp": 1747729843.271714, + "interfaceCounters": { + "inOctets": 54194, + "inUcastPkts": 0, + "inMulticastPkts": 418, + "inBroadcastPkts": 0, + "inDiscards": 0, + "inTotalPkts": 418, + "outOctets": 0, + "outUcastPkts": 0, + "outMulticastPkts": 0, + "outBroadcastPkts": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + "counterRefreshTime": 1747730615.150881, + }, + }, + "Ethernet6": { + "name": "Ethernet6", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "hardware": "ethernet", + "interfaceAddress": [], + "physicalAddress": "02:bf:5a:d0:f0:14", + "burnedInAddress": "02:bf:5a:d0:f0:14", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2703886, + "interfaceCounters": { + "inOctets": 6541, + "inUcastPkts": 0, + "inMulticastPkts": 31, + "inBroadcastPkts": 0, + "inDiscards": 0, + "inTotalPkts": 31, + "outOctets": 0, + "outUcastPkts": 0, + "outMulticastPkts": 0, + "outBroadcastPkts": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + "counterRefreshTime": 1747730615.152845, + }, + }, + } + }, + ], + "inputs": {"ignored_interfaces": ["Management2", "Ethernet2.100"], "errors_threshold": 0, "link_status_changes_threshold": 100}, + "expected": { + "result": AntaTestStatus.SUCCESS, + }, + }, + (VerifyInterfacesCounters, "failure-int-not-present"): { + "eos_data": [ + { + "interfaces": { + "Ethernet52/1": { + "name": "Ethernet52/1", + "forwardingModel": "routed", + "lineProtocolStatus": "notPresent", + "interfaceStatus": "disabled", + "interfaceStatistics": {"updateInterval": 300.0, "inBitsRate": 0.0, "inPktsRate": 0.0, "outBitsRate": 0.0, "outPktsRate": 0.0}, + "interfaceCounters": { + "inOctets": 0, + "inUcastPkts": 0, + "inMulticastPkts": 0, + "inBroadcastPkts": 0, + "inDiscards": 0, + "inTotalPkts": 0, + "outOctets": 0, + "outUcastPkts": 0, + "outMulticastPkts": 0, + "outBroadcastPkts": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 1, + "lastClear": 1730142608.033686, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + "counterRefreshTime": 1747715134.395665, + }, + }, + "Ethernet26": { + "name": "Ethernet26", + "forwardingModel": "routed", + "lineProtocolStatus": "notPresent", + "interfaceStatus": "disabled", + "interfaceStatistics": {"updateInterval": 300.0, "inBitsRate": 0.0, "inPktsRate": 0.0, "outBitsRate": 0.0, "outPktsRate": 0.0}, + "interfaceCounters": { + "inOctets": 0, + "inUcastPkts": 0, + "inMulticastPkts": 0, + "inBroadcastPkts": 0, + "inDiscards": 0, + "inTotalPkts": 0, + "outOctets": 0, + "outUcastPkts": 0, + "outMulticastPkts": 0, + "outBroadcastPkts": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 1, + "lastClear": 1730142608.0336852, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet27": { + "name": "Ethernet27", + "forwardingModel": "routed", + "lineProtocolStatus": "down", + "interfaceStatus": "disabled", + "description": "", + "lastStatusChangeTimestamp": 1730142955.8185458, + "interfaceCounters": { + "inOctets": 0, + "inUcastPkts": 0, + "inMulticastPkts": 0, + "inBroadcastPkts": 0, + "inDiscards": 0, + "inTotalPkts": 0, + "outOctets": 0, + "outUcastPkts": 0, + "outMulticastPkts": 0, + "outBroadcastPkts": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 1, + "lastClear": 1730142608.0336852, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + "counterRefreshTime": 1747715134.399435, + }, + }, + "Management1": { + "name": "Management1", + "forwardingModel": "routed", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "hardware": "ethernet", + "description": "OOB_MANAGEMENT", + "lastStatusChangeTimestamp": 1730142907.9199438, + "interfaceCounters": { + "inOctets": 175413533766, + "inUcastPkts": 1132843440, + "inMulticastPkts": 585751, + "inBroadcastPkts": 779498672, + "inDiscards": 0, + "inTotalPkts": 1912927863, + "outOctets": 319556443723, + "outUcastPkts": 1361993047, + "outMulticastPkts": 585704, + "outBroadcastPkts": 24, + "outDiscards": 0, + "outTotalPkts": 1362578775, + "linkStatusChanges": 4, + "lastClear": 1730142608.0336847, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + "counterRefreshTime": 1747715134.404214, + }, + }, + } + }, + ], + "inputs": {"ignored_interfaces": ["Ethernet26"], "errors_threshold": 0, "link_status_changes_threshold": 100}, + "expected": { + "result": AntaTestStatus.FAILURE, + "messages": [ + "Interface: Ethernet52/1 Description: None Downtime: None - Incorrect state - Expected: up Actual: notPresent", + "Interface: Ethernet27 Description: None Downtime: 203 - Incorrect state - Expected: up Actual: down", + ], + }, + }, + (VerifyInterfacesCounters, "failure-multiple-issues"): { + "eos_data": [ + { + "interfaces": { + "Ethernet2": { + "name": "Ethernet2", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1730142955.8185458, + "interfaceAddress": [], + "interfaceStatistics": {}, + "interfaceCounters": { + "inDiscards": 10, + "outDiscards": 0, + "linkStatusChanges": 12, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Management0": { + "name": "Management0", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "hardware": "ethernet", + "description": "OOB_MANAGEMENT", + "lastStatusChangeTimestamp": 1730142907.9199438, + "interfaceCounters": { + "inDiscards": 20, + "outDiscards": 0, + "linkStatusChanges": 1, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 10, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 10}, + }, + }, + "Ethernet10": { + "name": "Ethernet10", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "hardware": "ethernet", + "description": "", + "lastStatusChangeTimestamp": 1730142907.9199438, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 10, + "linkStatusChanges": 12, + "totalInErrors": 10, + "inputErrorsDetail": {"runtFrames": 10, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 20, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + } + } + ], + "inputs": {"ignored_interfaces": ["Ethernet2"], "link_status_changes_threshold": 2}, + "expected": { + "result": AntaTestStatus.FAILURE, + "messages": [ + "Interface: Management0 Description: OOB_MANAGEMENT Uptime: 203 - Total output error counter(s) mismatch - Expected: < 0 Actual: 10", + "Interface: Management0 Description: OOB_MANAGEMENT Uptime: 203 - Input packet discards counter(s) mismatch - Expected: < 0 Actual: 20", + "Interface: Ethernet10 Description: None Uptime: 203 - Non-zero input error counter(s) - runtFrames: 10", + "Interface: Ethernet10 Description: None Uptime: 203 - Non-zero output error counter(s) - lateCollisions: 20", + "Interface: Ethernet10 Description: None Uptime: 203 - Total input error counter(s) mismatch - Expected: < 0 Actual: 10", + "Interface: Ethernet10 Description: None Uptime: 203 - Link status changes mismatch - Expected: < 2 Actual: 12", + "Interface: Ethernet10 Description: None Uptime: 203 - Output packet discards counter(s) mismatch - Expected: < 0 Actual: 10", + ], + }, + }, } From 5361931d9ab8cba741712fc3a6fe2bdb00222916 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Wed, 21 May 2025 08:54:29 +0000 Subject: [PATCH 6/6] Added unit testcases and fixed linting issues --- anta/tests/interfaces.py | 109 +++--- tests/units/anta_tests/test_interfaces.py | 438 +++++++++++++++------- 2 files changed, 361 insertions(+), 186 deletions(-) diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py index c20da5541..ee9bbade8 100644 --- a/anta/tests/interfaces.py +++ b/anta/tests/interfaces.py @@ -9,7 +9,7 @@ import re from datetime import datetime, timezone -from typing import ClassVar, TypeVar +from typing import Any, ClassVar, TypeVar from pydantic import Field, field_validator from pydantic_extra_types.mac_address import MacAddress @@ -1035,7 +1035,7 @@ class Input(AntaTest.Input): link_status_changes_threshold: PositiveInteger """The max value for link status changes above which the test will fail.""" - def _get_last_change_time_stamp(self, interface_time_stamp: float) -> float | None: + def _get_last_change_time_stamp(self, interface_time_stamp: float) -> int | None: """Return the last change time stamp.""" now = datetime.now(timezone.utc) if interface_time_stamp: @@ -1051,62 +1051,22 @@ def test(self) -> None: command_output = self.instance_commands[0].json_output for interface, data in command_output["interfaces"].items(): - # Verify is skipped if the interface is in the ignored interfaces list - if _is_interface_ignored(interface, self.inputs.ignored_interfaces): + # if the interface is in the ignored interfaces list or interface is sub-interface ignored the verification + if _is_interface_ignored(interface, self.inputs.ignored_interfaces) or "." in interface: continue - # TODO: Need to skip subinterfaces - last_state_timestamp = self._get_last_change_time_stamp(data.get("lastStatusChangeTimestamp")) int_desc = data.get("description") if data.get("description") else None error_counters = data.get("interfaceCounters", {}) - errors_threshold = self.inputs.errors_threshold + # Verify the interface status - if data["lineProtocolStatus"] == "down" or data["lineProtocolStatus"] == "notPresent": # TODO: downtime need to be confirm + if data["lineProtocolStatus"] == "down" or data["lineProtocolStatus"] == "notPresent": self.result.is_failure( f"Interface: {interface} Description: {int_desc} Downtime: {last_state_timestamp} - Incorrect state - Expected: up" f" Actual: {data['lineProtocolStatus']}" ) continue - input_counters_data = [ - f"{counter}: {value}" - for counter, value in error_counters.qget("inputErrorsDetail", {}).items() - if all([value > errors_threshold, counter != "rxPause"]) - ] - # Verify that input error counters are non-zero - if input_counters_data: - self.result.is_failure( - f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Non-zero input error counter(s) - " - f"{', '.join(input_counters_data)}" - ) - - output_counters_data = [ - f"{counter}: {value}" - for counter, value in error_counters.get("outputErrorsDetail", {}).items() - if all([value > errors_threshold, counter != "txPause"]) - ] - # Verify that output error counters are non-zero - if output_counters_data: - self.result.is_failure( - f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Non-zero output error counter(s) - " - f"{', '.join(output_counters_data)}" - ) - - # Verify that total input error counters are non-zero - if error_counters.get("totalInErrors") > errors_threshold: - self.result.is_failure( - f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Total input error counter(s) mismatch -" - f" Expected: < {errors_threshold} Actual: {error_counters['totalInErrors']}" - ) - - # Verify that total output error counters are non-zero - if error_counters.get("totalOutErrors") > errors_threshold: - self.result.is_failure( - f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Total output error counter(s) mismatch -" - f" Expected: < {errors_threshold} Actual: {error_counters['totalOutErrors']}" - ) - # Verify that link status changes are within the expected range if error_counters.get("linkStatusChanges") > self.inputs.link_status_changes_threshold: self.result.is_failure( @@ -1115,15 +1075,64 @@ def test(self) -> None: ) # Verify that interfaces input packet discard counters are non-zero - if error_counters.get("inDiscards") > self.inputs.link_status_changes_threshold: + if error_counters.get("inDiscards") > self.inputs.errors_threshold: self.result.is_failure( f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Input packet discards counter(s) mismatch -" - f" Expected: < {errors_threshold} Actual: {error_counters['inDiscards']}" + f" Expected: < {self.inputs.errors_threshold} Actual: {error_counters['inDiscards']}" ) # Verify that interfaces output packet discard counters are non-zero - if error_counters.get("outDiscards") > self.inputs.link_status_changes_threshold: + if error_counters.get("outDiscards") > self.inputs.errors_threshold: self.result.is_failure( f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Output packet discards counter(s) mismatch -" - f" Expected: < {errors_threshold} Actual: {error_counters['outDiscards']}" + f" Expected: < {self.inputs.errors_threshold} Actual: {error_counters['outDiscards']}" ) + + # Verify interface error counter details + self.verify_interface_error_counter_details(interface, error_counters, int_desc, last_state_timestamp) + + def verify_interface_error_counter_details(self, interface: str, error_counters: dict[str, Any], int_desc: str | None, last_state_timestamp: int | None) -> None: + """Verify interface error counter details.""" + # Delegate verification logic for the interface error counters + self.verify_input_output_error_counter_details(interface, error_counters, int_desc, last_state_timestamp) + + def verify_input_output_error_counter_details( + self, interface: str, error_counters: dict[str, Any], int_desc: str | None, last_state_timestamp: int | None + ) -> None: + """Verify interface input and output error counter details.""" + # Verify that input error counters are non-zero + input_counters_data = [ + f"{counter}: {value}" + for counter, value in error_counters.get("inputErrorsDetail", {}).items() + if all([value > self.inputs.errors_threshold, counter != "rxPause"]) + ] + if input_counters_data: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Non-zero input error counter(s) - {', '.join(input_counters_data)}" + ) + + # Verify that output error counters are non-zero + output_counters_data = [ + f"{counter}: {value}" + for counter, value in error_counters.get("outputErrorsDetail", {}).items() + if all([value > self.inputs.errors_threshold, counter != "txPause"]) + ] + if output_counters_data: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Non-zero output error counter(s) - " + f"{', '.join(output_counters_data)}" + ) + + # Verify that total input error counters are non-zero + if error_counters.get("totalInErrors") > self.inputs.errors_threshold: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Total input error counter(s) mismatch -" + f" Expected: < {self.inputs.errors_threshold} Actual: {error_counters['totalInErrors']}" + ) + + # Verify that total output error counters are non-zero + if error_counters.get("totalOutErrors") > self.inputs.errors_threshold: + self.result.is_failure( + f"Interface: {interface} Description: {int_desc} Uptime: {last_state_timestamp} - Total output error counter(s) mismatch -" + f" Expected: < {self.inputs.errors_threshold} Actual: {error_counters['totalOutErrors']}" + ) diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py index ec6fe54b7..d91ff5f59 100644 --- a/tests/units/anta_tests/test_interfaces.py +++ b/tests/units/anta_tests/test_interfaces.py @@ -3041,20 +3041,10 @@ "forwardingModel": "bridged", "lineProtocolStatus": "up", "interfaceStatus": "connected", - "interfaceAddress": [], "description": "", "lastStatusChangeTimestamp": 1747729843.2728114, "interfaceCounters": { - "inOctets": 53910, - "inUcastPkts": 0, - "inMulticastPkts": 415, - "inBroadcastPkts": 0, "inDiscards": 0, - "inTotalPkts": 415, - "outOctets": 0, - "outUcastPkts": 0, - "outMulticastPkts": 0, - "outBroadcastPkts": 0, "outDiscards": 0, "outTotalPkts": 0, "linkStatusChanges": 2, @@ -3069,28 +3059,16 @@ "forwardingModel": "bridged", "lineProtocolStatus": "up", "interfaceStatus": "connected", - "hardware": "ethernet", "description": "", "lastStatusChangeTimestamp": 1747729843.2721722, "interfaceCounters": { - "inOctets": 6769, - "inUcastPkts": 0, - "inMulticastPkts": 33, - "inBroadcastPkts": 0, "inDiscards": 0, - "inTotalPkts": 33, - "outOctets": 0, - "outUcastPkts": 0, - "outMulticastPkts": 0, - "outBroadcastPkts": 0, "outDiscards": 0, - "outTotalPkts": 0, "linkStatusChanges": 2, "totalInErrors": 0, "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, "totalOutErrors": 0, "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, - "counterRefreshTime": 1747730615.124224, }, }, "Ethernet1": { @@ -3098,27 +3076,15 @@ "forwardingModel": "bridged", "lineProtocolStatus": "up", "interfaceStatus": "connected", - "hardware": "ethernet", "lastStatusChangeTimestamp": 1747729843.2710755, "interfaceCounters": { - "inOctets": 6541, - "inUcastPkts": 0, - "inMulticastPkts": 31, - "inBroadcastPkts": 0, "inDiscards": 0, - "inTotalPkts": 31, - "outOctets": 0, - "outUcastPkts": 0, - "outMulticastPkts": 0, - "outBroadcastPkts": 0, "outDiscards": 0, - "outTotalPkts": 0, "linkStatusChanges": 2, "totalInErrors": 0, "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, "totalOutErrors": 0, "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, - "counterRefreshTime": 1747730615.136472, }, }, "Ethernet3": { @@ -3126,53 +3092,10 @@ "forwardingModel": "bridged", "lineProtocolStatus": "up", "interfaceStatus": "connected", - "hardware": "ethernet", - "interfaceAddress": [], "description": "", "lastStatusChangeTimestamp": 1747729843.271714, "interfaceCounters": { - "inOctets": 54194, - "inUcastPkts": 0, - "inMulticastPkts": 418, - "inBroadcastPkts": 0, - "inDiscards": 0, - "inTotalPkts": 418, - "outOctets": 0, - "outUcastPkts": 0, - "outMulticastPkts": 0, - "outBroadcastPkts": 0, - "outDiscards": 0, - "outTotalPkts": 0, - "linkStatusChanges": 2, - "totalInErrors": 0, - "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, - "totalOutErrors": 0, - "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, - "counterRefreshTime": 1747730615.150881, - }, - }, - "Ethernet6": { - "name": "Ethernet6", - "forwardingModel": "bridged", - "lineProtocolStatus": "up", - "interfaceStatus": "connected", - "hardware": "ethernet", - "interfaceAddress": [], - "physicalAddress": "02:bf:5a:d0:f0:14", - "burnedInAddress": "02:bf:5a:d0:f0:14", - "description": "", - "lastStatusChangeTimestamp": 1747729843.2703886, - "interfaceCounters": { - "inOctets": 6541, - "inUcastPkts": 0, - "inMulticastPkts": 31, - "inBroadcastPkts": 0, "inDiscards": 0, - "inTotalPkts": 31, - "outOctets": 0, - "outUcastPkts": 0, - "outMulticastPkts": 0, - "outBroadcastPkts": 0, "outDiscards": 0, "outTotalPkts": 0, "linkStatusChanges": 2, @@ -3180,13 +3103,12 @@ "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, "totalOutErrors": 0, "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, - "counterRefreshTime": 1747730615.152845, }, }, } }, ], - "inputs": {"ignored_interfaces": ["Management2", "Ethernet2.100"], "errors_threshold": 0, "link_status_changes_threshold": 100}, + "inputs": {"ignored_interfaces": ["Management2", "Ethernet3.100"], "errors_threshold": 0, "link_status_changes_threshold": 100}, "expected": { "result": AntaTestStatus.SUCCESS, }, @@ -3202,25 +3124,13 @@ "interfaceStatus": "disabled", "interfaceStatistics": {"updateInterval": 300.0, "inBitsRate": 0.0, "inPktsRate": 0.0, "outBitsRate": 0.0, "outPktsRate": 0.0}, "interfaceCounters": { - "inOctets": 0, - "inUcastPkts": 0, - "inMulticastPkts": 0, - "inBroadcastPkts": 0, "inDiscards": 0, - "inTotalPkts": 0, - "outOctets": 0, - "outUcastPkts": 0, - "outMulticastPkts": 0, - "outBroadcastPkts": 0, "outDiscards": 0, - "outTotalPkts": 0, "linkStatusChanges": 1, - "lastClear": 1730142608.033686, "totalInErrors": 0, "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, "totalOutErrors": 0, "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, - "counterRefreshTime": 1747715134.395665, }, }, "Ethernet26": { @@ -3230,20 +3140,10 @@ "interfaceStatus": "disabled", "interfaceStatistics": {"updateInterval": 300.0, "inBitsRate": 0.0, "inPktsRate": 0.0, "outBitsRate": 0.0, "outPktsRate": 0.0}, "interfaceCounters": { - "inOctets": 0, - "inUcastPkts": 0, - "inMulticastPkts": 0, - "inBroadcastPkts": 0, "inDiscards": 0, - "inTotalPkts": 0, - "outOctets": 0, - "outUcastPkts": 0, - "outMulticastPkts": 0, - "outBroadcastPkts": 0, "outDiscards": 0, "outTotalPkts": 0, "linkStatusChanges": 1, - "lastClear": 1730142608.0336852, "totalInErrors": 0, "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, "totalOutErrors": 0, @@ -3258,25 +3158,14 @@ "description": "", "lastStatusChangeTimestamp": 1730142955.8185458, "interfaceCounters": { - "inOctets": 0, - "inUcastPkts": 0, - "inMulticastPkts": 0, - "inBroadcastPkts": 0, "inDiscards": 0, - "inTotalPkts": 0, - "outOctets": 0, - "outUcastPkts": 0, - "outMulticastPkts": 0, - "outBroadcastPkts": 0, "outDiscards": 0, "outTotalPkts": 0, "linkStatusChanges": 1, - "lastClear": 1730142608.0336852, "totalInErrors": 0, "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, "totalOutErrors": 0, "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, - "counterRefreshTime": 1747715134.399435, }, }, "Management1": { @@ -3284,29 +3173,16 @@ "forwardingModel": "routed", "lineProtocolStatus": "up", "interfaceStatus": "connected", - "hardware": "ethernet", "description": "OOB_MANAGEMENT", "lastStatusChangeTimestamp": 1730142907.9199438, "interfaceCounters": { - "inOctets": 175413533766, - "inUcastPkts": 1132843440, - "inMulticastPkts": 585751, - "inBroadcastPkts": 779498672, "inDiscards": 0, - "inTotalPkts": 1912927863, - "outOctets": 319556443723, - "outUcastPkts": 1361993047, - "outMulticastPkts": 585704, - "outBroadcastPkts": 24, "outDiscards": 0, - "outTotalPkts": 1362578775, "linkStatusChanges": 4, - "lastClear": 1730142608.0336847, "totalInErrors": 0, "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, "totalOutErrors": 0, "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, - "counterRefreshTime": 1747715134.404214, }, }, } @@ -3317,7 +3193,7 @@ "result": AntaTestStatus.FAILURE, "messages": [ "Interface: Ethernet52/1 Description: None Downtime: None - Incorrect state - Expected: up Actual: notPresent", - "Interface: Ethernet27 Description: None Downtime: 203 - Incorrect state - Expected: up Actual: down", + "Interface: Ethernet27 Description: None Downtime: 204 - Incorrect state - Expected: up Actual: down", ], }, }, @@ -3331,8 +3207,6 @@ "interfaceStatus": "connected", "description": "", "lastStatusChangeTimestamp": 1730142955.8185458, - "interfaceAddress": [], - "interfaceStatistics": {}, "interfaceCounters": { "inDiscards": 10, "outDiscards": 0, @@ -3347,7 +3221,6 @@ "name": "Management0", "lineProtocolStatus": "up", "interfaceStatus": "connected", - "hardware": "ethernet", "description": "OOB_MANAGEMENT", "lastStatusChangeTimestamp": 1730142907.9199438, "interfaceCounters": { @@ -3384,14 +3257,307 @@ "expected": { "result": AntaTestStatus.FAILURE, "messages": [ - "Interface: Management0 Description: OOB_MANAGEMENT Uptime: 203 - Total output error counter(s) mismatch - Expected: < 0 Actual: 10", - "Interface: Management0 Description: OOB_MANAGEMENT Uptime: 203 - Input packet discards counter(s) mismatch - Expected: < 0 Actual: 20", - "Interface: Ethernet10 Description: None Uptime: 203 - Non-zero input error counter(s) - runtFrames: 10", - "Interface: Ethernet10 Description: None Uptime: 203 - Non-zero output error counter(s) - lateCollisions: 20", - "Interface: Ethernet10 Description: None Uptime: 203 - Total input error counter(s) mismatch - Expected: < 0 Actual: 10", - "Interface: Ethernet10 Description: None Uptime: 203 - Link status changes mismatch - Expected: < 2 Actual: 12", - "Interface: Ethernet10 Description: None Uptime: 203 - Output packet discards counter(s) mismatch - Expected: < 0 Actual: 10", + "Interface: Management0 Description: OOB_MANAGEMENT Uptime: 204 - Input packet discards counter(s) mismatch - Expected: < 0 Actual: 20", + "Interface: Management0 Description: OOB_MANAGEMENT Uptime: 204 - Total output error counter(s) mismatch - Expected: < 0 Actual: 10", + "Interface: Ethernet10 Description: None Uptime: 204 - Link status changes mismatch - Expected: < 2 Actual: 12", + "Interface: Ethernet10 Description: None Uptime: 204 - Output packet discards counter(s) mismatch - Expected: < 0 Actual: 10", + "Interface: Ethernet10 Description: None Uptime: 204 - Non-zero input error counter(s) - runtFrames: 10", + "Interface: Ethernet10 Description: None Uptime: 204 - Non-zero output error counter(s) - lateCollisions: 20", + "Interface: Ethernet10 Description: None Uptime: 204 - Total input error counter(s) mismatch - Expected: < 0 Actual: 10", + ], + }, + }, + (VerifyInterfacesCounters, "ignored-sub-interface"): { + "eos_data": [ + { + "interfaces": { + "Management1": { + "name": "Management1", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2728114, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 30}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4.100": { + "name": "Ethernet4", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2721722, + "interfaceCounters": { + "inDiscards": 20, + "outDiscards": 20, + "linkStatusChanges": 200, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 30, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 30, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + } + }, + ], + "inputs": {"ignored_interfaces": ["Ethernet1"], "errors_threshold": 0, "link_status_changes_threshold": 10}, + "expected": {"result": AntaTestStatus.SUCCESS}, + }, + (VerifyInterfacesCounters, "failure-input-error"): { + "eos_data": [ + { + "interfaces": { + "Management1": { + "name": "Management1", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2728114, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 30, "giantFrames": 0, "fcsErrors": 10, "alignmentErrors": 55, "symbolErrors": 20, "rxPause": 30}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 10, "lateCollisions": 10, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2721722, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 30, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 30, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + } + }, + ], + "inputs": {"ignored_interfaces": ["Management0", "Ethernet"], "errors_threshold": 10, "link_status_changes_threshold": 10}, + "expected": { + "result": AntaTestStatus.FAILURE, + "messages": [ + "Interface: Management1 Description: None Uptime: 1 - Non-zero input error counter(s) - runtFrames: 30, alignmentErrors: 55, symbolErrors: 20" + ], + }, + }, + (VerifyInterfacesCounters, "failure-output-error"): { + "eos_data": [ + { + "interfaces": { + "Management1": { + "name": "Management1", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2728114, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 10, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 10, "symbolErrors": 20, "rxPause": 30}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2721722, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 20, "lateCollisions": 30, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + } + }, + ], + "inputs": {"ignored_interfaces": ["Management", "Ethernet2"], "errors_threshold": 0, "link_status_changes_threshold": 20}, + "expected": { + "result": AntaTestStatus.FAILURE, + "messages": ["Interface: Ethernet4 Description: None Uptime: 1 - Non-zero output error counter(s) - collisions: 20, lateCollisions: 30"], + }, + }, + (VerifyInterfacesCounters, "failure-total-int-out-error"): { + "eos_data": [ + { + "interfaces": { + "Management1": { + "name": "Management1", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2728114, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 0, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 10, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 30}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2721722, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 0, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 20}, + "totalOutErrors": 30, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + } + }, + ], + "inputs": {"ignored_interfaces": ["Ethernet2"], "errors_threshold": 0, "link_status_changes_threshold": 20}, + "expected": { + "result": AntaTestStatus.FAILURE, + "messages": [ + "Interface: Management1 Description: None Uptime: 1 - Total input error counter(s) mismatch - Expected: < 0 Actual: 10", + "Interface: Ethernet4 Description: None Uptime: 1 - Total output error counter(s) mismatch - Expected: < 0 Actual: 30", ], }, }, + (VerifyInterfacesCounters, "failure-int-out-packet-discard"): { + "eos_data": [ + { + "interfaces": { + "Management1": { + "name": "Management1", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2728114, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 30, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 10, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 10, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 30}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2721722, + "interfaceCounters": { + "inDiscards": 30, + "outDiscards": 10, + "linkStatusChanges": 2, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 10, "symbolErrors": 0, "rxPause": 20}, + "totalOutErrors": 10, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 10, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + } + }, + ], + "inputs": {"ignored_interfaces": ["Ethernet2"], "errors_threshold": 10, "link_status_changes_threshold": 20}, + "expected": { + "result": AntaTestStatus.FAILURE, + "messages": [ + "Interface: Management1 Description: None Uptime: 1 - Output packet discards counter(s) mismatch - Expected: < 10 Actual: 30", + "Interface: Ethernet4 Description: None Uptime: 1 - Input packet discards counter(s) mismatch - Expected: < 10 Actual: 30", + ], + }, + }, + (VerifyInterfacesCounters, "failure-link-status-changes"): { + "eos_data": [ + { + "interfaces": { + "Management1": { + "name": "Management1", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2728114, + "interfaceCounters": { + "inDiscards": 0, + "outDiscards": 30, + "outTotalPkts": 0, + "linkStatusChanges": 2, + "totalInErrors": 10, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 10, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 30}, + "totalOutErrors": 0, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + "Ethernet4": { + "name": "Ethernet4", + "forwardingModel": "bridged", + "lineProtocolStatus": "up", + "interfaceStatus": "connected", + "description": "", + "lastStatusChangeTimestamp": 1747729843.2721722, + "interfaceCounters": { + "inDiscards": 30, + "outDiscards": 10, + "linkStatusChanges": 40, + "totalInErrors": 0, + "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 10, "symbolErrors": 0, "rxPause": 20}, + "totalOutErrors": 10, + "outputErrorsDetail": {"collisions": 0, "lateCollisions": 10, "deferredTransmissions": 0, "txPause": 30}, + }, + }, + } + }, + ], + "inputs": {"ignored_interfaces": ["Ethernet2"], "errors_threshold": 40, "link_status_changes_threshold": 20}, + "expected": { + "result": AntaTestStatus.FAILURE, + "messages": ["Interface: Ethernet4 Description: None Uptime: 1 - Link status changes mismatch - Expected: < 20 Actual: 40"], + }, + }, }