From 8217d0796e3719080cf421da99572c79683da5ef Mon Sep 17 00:00:00 2001 From: Geetanjali G Mane Date: Fri, 13 Dec 2024 13:34:32 +0530 Subject: [PATCH 1/8] Updated VerifySpecificPath testcase with the latest changes --- anta/input_models/path_selection.py | 28 +++ anta/tests/path_selection.py | 93 +++++----- examples/tests.yaml | 2 +- tests/units/anta_tests/test_path_selection.py | 170 ++++++++++++------ 4 files changed, 186 insertions(+), 107 deletions(-) create mode 100644 anta/input_models/path_selection.py diff --git a/anta/input_models/path_selection.py b/anta/input_models/path_selection.py new file mode 100644 index 000000000..bf843fd75 --- /dev/null +++ b/anta/input_models/path_selection.py @@ -0,0 +1,28 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Module containing input models for path selection tests.""" + +from __future__ import annotations + +from ipaddress import IPv4Address + +from pydantic import BaseModel, ConfigDict + + +class RouterPath(BaseModel): + """Model for a list of router path entries.""" + + model_config = ConfigDict(extra="forbid") + peer: IPv4Address + """Static peer IPv4 address.""" + path_group: str + """Router path group name.""" + source_address: IPv4Address + """Source IPv4 address of path.""" + destination_address: IPv4Address + """Destination IPv4 address of path.""" + + def __str__(self) -> str: + """Return a human-readable string representation of the router path for reporting.""" + return f"Peer: {self.peer} PathGroup: {self.path_group} Source: {self.source_address} Destination: {self.destination_address}" diff --git a/anta/tests/path_selection.py b/anta/tests/path_selection.py index 58b86860d..a1b5ec614 100644 --- a/anta/tests/path_selection.py +++ b/anta/tests/path_selection.py @@ -7,12 +7,10 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations -from ipaddress import IPv4Address from typing import ClassVar -from pydantic import BaseModel - from anta.decorators import skip_on_platforms +from anta.input_models.path_selection import RouterPath from anta.models import AntaCommand, AntaTemplate, AntaTest from anta.tools import get_value @@ -70,13 +68,18 @@ def test(self) -> None: class VerifySpecificPath(AntaTest): - """Verifies the path and telemetry state of a specific path for an IPv4 peer under router path-selection. + """Verifies the path and telemetry state of a specific path for an IPv4 peer. - The expected states are 'IPsec established', 'Resolved' for path and 'active' for telemetry. + This test performs the following checks for each specified routerpath: + 1. Verifies that the specified peer is configured. + 2. Verifies that the specified path group is found. + 3. Verifies that the expected source and destination address are matched for the specified path group. + 4. Verifies that the state of the path is `ipsecEstablished` or `routeResolved`. + 5. Verifies that the the telemetry state is `active`. Expected Results ---------------- - * Success: The test will pass if the path state under router path-selection is either 'IPsec established' or 'Resolved' + * Success: The test will pass if the path state under router path-selection is either 'IPsecEstablished' or 'Resolved' and telemetry state as 'active'. * Failure: The test will fail if router path-selection is not configured or if the path state is not 'IPsec established' or 'Resolved', or if the telemetry state is 'inactive'. @@ -95,36 +98,14 @@ class VerifySpecificPath(AntaTest): """ categories: ClassVar[list[str]] = ["path-selection"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [ - AntaTemplate(template="show path-selection paths peer {peer} path-group {group} source {source} destination {destination}", revision=1) - ] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show path-selection paths", revision=1)] class Input(AntaTest.Input): """Input model for the VerifySpecificPath test.""" paths: list[RouterPath] """List of router paths to verify.""" - - class RouterPath(BaseModel): - """Detail of a router path.""" - - peer: IPv4Address - """Static peer IPv4 address.""" - - path_group: str - """Router path group name.""" - - source_address: IPv4Address - """Source IPv4 address of path.""" - - destination_address: IPv4Address - """Destination IPv4 address of path.""" - - def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each router path.""" - return [ - template.render(peer=path.peer, group=path.path_group, source=path.source_address, destination=path.destination_address) for path in self.inputs.paths - ] + RouterPath: ClassVar[type[RouterPath]] = RouterPath @skip_on_platforms(["cEOSLab", "vEOS-lab"]) @AntaTest.anta_test @@ -132,28 +113,46 @@ def test(self) -> None: """Main test function for VerifySpecificPath.""" self.result.is_success() - # Check the state of each path - for command in self.instance_commands: - peer = command.params.peer - path_group = command.params.group - source = command.params.source - destination = command.params.destination - command_output = command.json_output.get("dpsPeers", []) + command_output = self.instance_commands[0].json_output + + # If the dpsPeers details are not found in the command output, the test fails. + if not (dps_peers_details := get_value(command_output, "dpsPeers")): + self.result.is_failure("No Router path configured") + + return + + # Iterating on each router path mentioned in the inputs. + for router_path in self.inputs.paths: + peer = str(router_path.peer) + path_group = router_path.path_group + source = str(router_path.source_address) + destination = str(router_path.destination_address) + peer_details = dps_peers_details.get(peer, {}) # If the peer is not configured for the path group, the test fails - if not command_output: - self.result.is_failure(f"Path `peer: {peer} source: {source} destination: {destination}` is not configured for path-group `{path_group}`.") + if not peer_details: + self.result.is_failure(f"{peer} - peer not configured") continue - # Extract the state of the path - path_output = get_value(command_output, f"{peer}..dpsGroups..{path_group}..dpsPaths", separator="..") - path_state = next(iter(path_output.values())).get("state") - session = get_value(next(iter(path_output.values())), "dpsSessions.0.active") + path_group_details = get_value(peer_details, f"dpsGroups..{path_group}..dpsPaths", separator="..") + # If the expected pathgroup is not found for the peer, the test fails. + if not path_group_details: + self.result.is_failure(f"{peer} - path-group {path_group} not found") + continue + path_data = next((path for path in path_group_details.values() if (path.get("source") == source and path.get("destination") == destination)), None) + # If the expected and actual source and destion address of the pathgroup are not matched, test fails. + if not path_data: + self.result.is_failure(f"{peer} - source: {source} destination: {destination} is not configured for path-group {path_group}") + continue + + path_state = path_data.get("state") + session = get_value(path_data, "dpsSessions.0.active") + expected_state = ["ipsecEstablished", "routeResolved"] # If the state of the path is not 'ipsecEstablished' or 'routeResolved', or the telemetry state is 'inactive', the test fails - if path_state not in ["ipsecEstablished", "routeResolved"]: - self.result.is_failure(f"Path state for `peer: {peer} source: {source} destination: {destination}` in path-group {path_group} is `{path_state}`.") - elif not session: + if path_state not in expected_state: self.result.is_failure( - f"Telemetry state for path `peer: {peer} source: {source} destination: {destination}` in path-group {path_group} is `inactive`." + f"{peer} - in path-group {path_group} - Incorrect path state - Expected: is {' or '.join(expected_state)} Actual: {path_state}" ) + elif not session: + self.result.is_failure(f"{peer} - in path-group {path_group} Incorrect telemetry state - Expected: active Actual: inactive") diff --git a/examples/tests.yaml b/examples/tests.yaml index ce3b851a5..3660a7dac 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -342,7 +342,7 @@ anta.tests.path_selection: - VerifyPathsHealth: # Verifies the path and telemetry state of all paths under router path-selection. - VerifySpecificPath: - # Verifies the path and telemetry state of a specific path for an IPv4 peer under router path-selection. + # Verifies the path and telemetry state of a specific path for an IPv4 peer. paths: - peer: 10.255.0.1 path_group: internet diff --git a/tests/units/anta_tests/test_path_selection.py b/tests/units/anta_tests/test_path_selection.py index 08377e675..26fe5e7c7 100644 --- a/tests/units/anta_tests/test_path_selection.py +++ b/tests/units/anta_tests/test_path_selection.py @@ -160,105 +160,166 @@ "eos_data": [ { "dpsPeers": { - "10.255.0.1": { + "10.255.0.2": { "dpsGroups": { - "internet": { + "mpls": { "dpsPaths": { - "path3": { - "state": "ipsecEstablished", + "path7": {}, + "path8": { "source": "172.18.13.2", "destination": "172.18.15.2", + "state": "ipsecEstablished", "dpsSessions": {"0": {"active": True}}, - } + }, } - } + }, + "internet": {}, } - } - } - }, - { - "dpsPeers": { - "10.255.0.2": { + }, + "10.255.0.1": { "dpsGroups": { - "mpls": { + "internet": { "dpsPaths": { - "path2": { + "path6": { + "source": "100.64.3.2", + "destination": "100.64.1.2", "state": "ipsecEstablished", - "source": "172.18.3.2", - "destination": "172.18.5.2", "dpsSessions": {"0": {"active": True}}, } } - } + }, + "mpls": {}, } - } + }, } - }, + } ], "inputs": { "paths": [ - {"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}, + {"peer": "10.255.0.1", "path_group": "internet", "source_address": "100.64.3.2", "destination_address": "100.64.1.2"}, {"peer": "10.255.0.2", "path_group": "mpls", "source_address": "172.18.13.2", "destination_address": "172.18.15.2"}, ] }, "expected": {"result": "success"}, }, { - "name": "failure-no-peer", + "name": "failure-expected-path-group-not-found", "test": VerifySpecificPath, "eos_data": [ - {"dpsPeers": {}}, - {"dpsPeers": {}}, + { + "dpsPeers": { + "10.255.0.2": { + "dpsGroups": {"internet": {}}, + }, + "10.255.0.1": {"peerName": "", "dpsGroups": {"mpls": {}}}, + } + } ], "inputs": { "paths": [ - {"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}, + {"peer": "10.255.0.1", "path_group": "internet", "source_address": "100.64.3.2", "destination_address": "100.64.1.2"}, {"peer": "10.255.0.2", "path_group": "mpls", "source_address": "172.18.13.2", "destination_address": "172.18.15.2"}, ] }, "expected": { "result": "failure", - "messages": [ - "Path `peer: 10.255.0.1 source: 172.18.3.2 destination: 172.18.5.2` is not configured for path-group `internet`.", - "Path `peer: 10.255.0.2 source: 172.18.13.2 destination: 172.18.15.2` is not configured for path-group `mpls`.", - ], + "messages": ["10.255.0.1 - path-group internet not found", "10.255.0.2 - path-group mpls not found"], }, }, + { + "name": "failure-no-router-path-configured", + "test": VerifySpecificPath, + "eos_data": [{"dpsPeers": {}}], + "inputs": {"paths": [{"peer": "10.255.0.1", "path_group": "internet", "source_address": "100.64.3.2", "destination_address": "100.64.1.2"}]}, + "expected": {"result": "failure", "messages": ["No Router path configured"]}, + }, + { + "name": "failure-no-specific-peer-configured", + "test": VerifySpecificPath, + "eos_data": [{"dpsPeers": {"10.255.0.2": {}}}], + "inputs": {"paths": [{"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}]}, + "expected": {"result": "failure", "messages": ["10.255.0.1 - peer not configured"]}, + }, { "name": "failure-not-established", "test": VerifySpecificPath, "eos_data": [ { "dpsPeers": { + "10.255.0.2": { + "dpsGroups": { + "mpls": { + "dpsPaths": { + "path7": {}, + "path8": { + "source": "172.18.13.2", + "destination": "172.18.15.2", + "state": "ipsecPending", + "dpsSessions": {"0": {"active": True}}, + }, + } + }, + "internet": {"dpsPaths": {}}, + } + }, "10.255.0.1": { "dpsGroups": { "internet": { "dpsPaths": { - "path3": {"state": "ipsecPending", "source": "172.18.3.2", "destination": "172.18.5.2", "dpsSessions": {"0": {"active": True}}} + "path6": {"source": "172.18.3.2", "destination": "172.18.5.2", "state": "ipsecPending", "dpsSessions": {"0": {"active": True}}} } - } + }, + "mpls": {"dpsPaths": {}}, } - } + }, } - }, + } + ], + "inputs": { + "paths": [ + {"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}, + {"peer": "10.255.0.2", "path_group": "mpls", "source_address": "172.18.13.2", "destination_address": "172.18.15.2"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "10.255.0.1 - in path-group internet - Incorrect path state - Expected: is ipsecEstablished or routeResolved Actual: ipsecPending", + "10.255.0.2 - in path-group mpls - Incorrect path state - Expected: is ipsecEstablished or routeResolved Actual: ipsecPending", + ], + }, + }, + { + "name": "failure-inactive", + "test": VerifySpecificPath, + "eos_data": [ { "dpsPeers": { "10.255.0.2": { "dpsGroups": { "mpls": { "dpsPaths": { - "path4": { - "state": "ipsecPending", + "path8": { "source": "172.18.13.2", "destination": "172.18.15.2", + "state": "routeResolved", "dpsSessions": {"0": {"active": False}}, } } } } - } + }, + "10.255.0.1": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path6": {"source": "172.18.3.2", "destination": "172.18.5.2", "state": "routeResolved", "dpsSessions": {"0": {"active": False}}} + } + } + } + }, } - }, + } ], "inputs": { "paths": [ @@ -269,46 +330,37 @@ "expected": { "result": "failure", "messages": [ - "Path state for `peer: 10.255.0.1 source: 172.18.3.2 destination: 172.18.5.2` in path-group internet is `ipsecPending`.", - "Path state for `peer: 10.255.0.2 source: 172.18.13.2 destination: 172.18.15.2` in path-group mpls is `ipsecPending`.", + "10.255.0.1 - in path-group internet Incorrect telemetry state - Expected: active Actual: inactive", + "10.255.0.2 - in path-group mpls Incorrect telemetry state - Expected: active Actual: inactive", ], }, }, { - "name": "failure-inactive", + "name": "failure-source-destination-not-configured", "test": VerifySpecificPath, "eos_data": [ { "dpsPeers": { - "10.255.0.1": { + "10.255.0.2": { "dpsGroups": { - "internet": { + "mpls": { "dpsPaths": { - "path3": {"state": "routeResolved", "source": "172.18.3.2", "destination": "172.18.5.2", "dpsSessions": {"0": {"active": False}}} + "path8": {"source": "172.18.3.2", "destination": "172.8.15.2", "state": "routeResolved", "dpsSessions": {"0": {"active": False}}} } } } - } - } - }, - { - "dpsPeers": { - "10.255.0.2": { + }, + "10.255.0.1": { "dpsGroups": { - "mpls": { + "internet": { "dpsPaths": { - "path4": { - "state": "routeResolved", - "source": "172.18.13.2", - "destination": "172.18.15.2", - "dpsSessions": {"0": {"active": False}}, - } + "path6": {"source": "172.8.3.2", "destination": "172.8.5.2", "state": "routeResolved", "dpsSessions": {"0": {"active": False}}} } } } - } + }, } - }, + } ], "inputs": { "paths": [ @@ -319,8 +371,8 @@ "expected": { "result": "failure", "messages": [ - "Telemetry state for path `peer: 10.255.0.1 source: 172.18.3.2 destination: 172.18.5.2` in path-group internet is `inactive`.", - "Telemetry state for path `peer: 10.255.0.2 source: 172.18.13.2 destination: 172.18.15.2` in path-group mpls is `inactive`.", + "10.255.0.1 - source: 172.18.3.2 destination: 172.18.5.2 is not configured for path-group internet", + "10.255.0.2 - source: 172.18.13.2 destination: 172.18.15.2 is not configured for path-group mpls", ], }, }, From 2c5bb7d28154177d8b4b4a445da235ff768a4462 Mon Sep 17 00:00:00 2001 From: Geetanjali G Mane Date: Fri, 13 Dec 2024 16:52:19 +0530 Subject: [PATCH 2/8] Updated test failure messages, documenation --- anta/input_models/path_selection.py | 4 +-- anta/tests/path_selection.py | 31 +++++++++---------- docs/api/tests.path_selection.md | 15 +++++++++ tests/units/anta_tests/test_path_selection.py | 23 ++++++++------ 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/anta/input_models/path_selection.py b/anta/input_models/path_selection.py index bf843fd75..f74b8931f 100644 --- a/anta/input_models/path_selection.py +++ b/anta/input_models/path_selection.py @@ -1,7 +1,7 @@ # Copyright (c) 2023-2024 Arista Networks, Inc. # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. -"""Module containing input models for path selection tests.""" +"""Module containing input models for path-selection tests.""" from __future__ import annotations @@ -24,5 +24,5 @@ class RouterPath(BaseModel): """Destination IPv4 address of path.""" def __str__(self) -> str: - """Return a human-readable string representation of the router path for reporting.""" + """Return a human-readable string representation of the RouterPath for reporting.""" return f"Peer: {self.peer} PathGroup: {self.path_group} Source: {self.source_address} Destination: {self.destination_address}" diff --git a/anta/tests/path_selection.py b/anta/tests/path_selection.py index a1b5ec614..2dc65e7b6 100644 --- a/anta/tests/path_selection.py +++ b/anta/tests/path_selection.py @@ -71,18 +71,18 @@ class VerifySpecificPath(AntaTest): """Verifies the path and telemetry state of a specific path for an IPv4 peer. This test performs the following checks for each specified routerpath: - 1. Verifies that the specified peer is configured. - 2. Verifies that the specified path group is found. - 3. Verifies that the expected source and destination address are matched for the specified path group. - 4. Verifies that the state of the path is `ipsecEstablished` or `routeResolved`. - 5. Verifies that the the telemetry state is `active`. + 1. Verifies that the specified peer is configured. + 2. Verifies that the specified path group is found. + 3. Verifies that the expected source and destination address match the path group. + 4. Verifies that the state of the path is `ipsecEstablished` or `routeResolved`. + 5. Verifies that the telemetry state is `active`. Expected Results ---------------- - * Success: The test will pass if the path state under router path-selection is either 'IPsecEstablished' or 'Resolved' + * Success: The test will pass if the path state under router path selection is either 'IPsecEstablished' or 'Resolved' and telemetry state as 'active'. - * Failure: The test will fail if router path-selection is not configured or if the path state is not 'IPsec established' or 'Resolved', - or if the telemetry state is 'inactive'. + * Failure: The test will fail if router path selection is not configured, the path state is not 'IPsec established' or 'Resolved', + or the telemetry state is 'inactive'. Examples -------- @@ -117,8 +117,7 @@ def test(self) -> None: # If the dpsPeers details are not found in the command output, the test fails. if not (dps_peers_details := get_value(command_output, "dpsPeers")): - self.result.is_failure("No Router path configured") - + self.result.is_failure("Router path not configured") return # Iterating on each router path mentioned in the inputs. @@ -131,19 +130,19 @@ def test(self) -> None: # If the peer is not configured for the path group, the test fails if not peer_details: - self.result.is_failure(f"{peer} - peer not configured") + self.result.is_failure(f"{router_path} - Peer not found") continue path_group_details = get_value(peer_details, f"dpsGroups..{path_group}..dpsPaths", separator="..") # If the expected pathgroup is not found for the peer, the test fails. if not path_group_details: - self.result.is_failure(f"{peer} - path-group {path_group} not found") + self.result.is_failure(f"{router_path} - Path-group not found") continue path_data = next((path for path in path_group_details.values() if (path.get("source") == source and path.get("destination") == destination)), None) # If the expected and actual source and destion address of the pathgroup are not matched, test fails. if not path_data: - self.result.is_failure(f"{peer} - source: {source} destination: {destination} is not configured for path-group {path_group}") + self.result.is_failure(f"{router_path} - Source and/or Destination address not found") continue path_state = path_data.get("state") @@ -151,8 +150,6 @@ def test(self) -> None: expected_state = ["ipsecEstablished", "routeResolved"] # If the state of the path is not 'ipsecEstablished' or 'routeResolved', or the telemetry state is 'inactive', the test fails if path_state not in expected_state: - self.result.is_failure( - f"{peer} - in path-group {path_group} - Incorrect path state - Expected: is {' or '.join(expected_state)} Actual: {path_state}" - ) + self.result.is_failure(f"{router_path} - Incorrect path state - Expected: {' or '.join(expected_state)} Actual: {path_state}") elif not session: - self.result.is_failure(f"{peer} - in path-group {path_group} Incorrect telemetry state - Expected: active Actual: inactive") + self.result.is_failure(f"{router_path} - Incorrect telemetry state - Expected: active Actual: inactive") diff --git a/docs/api/tests.path_selection.md b/docs/api/tests.path_selection.md index f4d41d6f2..68488b664 100644 --- a/docs/api/tests.path_selection.md +++ b/docs/api/tests.path_selection.md @@ -7,6 +7,8 @@ anta_title: ANTA catalog for Router path-selection tests ~ that can be found in the LICENSE file. --> +# Tests + ::: anta.tests.path_selection options: show_root_heading: false @@ -18,3 +20,16 @@ anta_title: ANTA catalog for Router path-selection tests filters: - "!test" - "!render" + +# Input models + +::: anta.input_models.path_selection + + options: + show_root_heading: false + show_root_toc_entry: false + show_bases: false + merge_init_into_class: false + anta_hide_test_module_description: true + show_labels: true + filters: ["!^__str__"] diff --git a/tests/units/anta_tests/test_path_selection.py b/tests/units/anta_tests/test_path_selection.py index 26fe5e7c7..9aca0ee38 100644 --- a/tests/units/anta_tests/test_path_selection.py +++ b/tests/units/anta_tests/test_path_selection.py @@ -223,7 +223,10 @@ }, "expected": { "result": "failure", - "messages": ["10.255.0.1 - path-group internet not found", "10.255.0.2 - path-group mpls not found"], + "messages": [ + "Peer: 10.255.0.1 PathGroup: internet Source: 100.64.3.2 Destination: 100.64.1.2 - Path-group not found", + "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Path-group not found", + ], }, }, { @@ -231,14 +234,14 @@ "test": VerifySpecificPath, "eos_data": [{"dpsPeers": {}}], "inputs": {"paths": [{"peer": "10.255.0.1", "path_group": "internet", "source_address": "100.64.3.2", "destination_address": "100.64.1.2"}]}, - "expected": {"result": "failure", "messages": ["No Router path configured"]}, + "expected": {"result": "failure", "messages": ["Router path not configured"]}, }, { "name": "failure-no-specific-peer-configured", "test": VerifySpecificPath, "eos_data": [{"dpsPeers": {"10.255.0.2": {}}}], "inputs": {"paths": [{"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}]}, - "expected": {"result": "failure", "messages": ["10.255.0.1 - peer not configured"]}, + "expected": {"result": "failure", "messages": ["Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Peer not found"]}, }, { "name": "failure-not-established", @@ -284,8 +287,10 @@ "expected": { "result": "failure", "messages": [ - "10.255.0.1 - in path-group internet - Incorrect path state - Expected: is ipsecEstablished or routeResolved Actual: ipsecPending", - "10.255.0.2 - in path-group mpls - Incorrect path state - Expected: is ipsecEstablished or routeResolved Actual: ipsecPending", + "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Incorrect path state - Expected:" + " ipsecEstablished or routeResolved Actual: ipsecPending", + "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Incorrect path state - Expected:" + " ipsecEstablished or routeResolved Actual: ipsecPending", ], }, }, @@ -330,8 +335,8 @@ "expected": { "result": "failure", "messages": [ - "10.255.0.1 - in path-group internet Incorrect telemetry state - Expected: active Actual: inactive", - "10.255.0.2 - in path-group mpls Incorrect telemetry state - Expected: active Actual: inactive", + "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Incorrect telemetry state - Expected: active Actual: inactive", + "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Incorrect telemetry state - Expected: active Actual: inactive", ], }, }, @@ -371,8 +376,8 @@ "expected": { "result": "failure", "messages": [ - "10.255.0.1 - source: 172.18.3.2 destination: 172.18.5.2 is not configured for path-group internet", - "10.255.0.2 - source: 172.18.13.2 destination: 172.18.15.2 is not configured for path-group mpls", + "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Source and/or Destination address not found", + "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Source and/or Destination address not found", ], }, }, From 50a4abf8682d8a2b1bee422975a9db719beeb1db Mon Sep 17 00:00:00 2001 From: Geetanjali Mane Date: Thu, 26 Dec 2024 16:49:02 +0530 Subject: [PATCH 3/8] Updated test failure messages --- anta/tests/path_selection.py | 14 ++++++------- tests/units/anta_tests/test_path_selection.py | 20 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/anta/tests/path_selection.py b/anta/tests/path_selection.py index 2dc65e7b6..2e1c82445 100644 --- a/anta/tests/path_selection.py +++ b/anta/tests/path_selection.py @@ -70,7 +70,7 @@ def test(self) -> None: class VerifySpecificPath(AntaTest): """Verifies the path and telemetry state of a specific path for an IPv4 peer. - This test performs the following checks for each specified routerpath: + This test performs the following checks for each specified router path: 1. Verifies that the specified peer is configured. 2. Verifies that the specified path group is found. 3. Verifies that the expected source and destination address match the path group. @@ -134,15 +134,15 @@ def test(self) -> None: continue path_group_details = get_value(peer_details, f"dpsGroups..{path_group}..dpsPaths", separator="..") - # If the expected pathgroup is not found for the peer, the test fails. + # If the expected path group is not found for the peer, the test fails. if not path_group_details: - self.result.is_failure(f"{router_path} - Path-group not found") + self.result.is_failure(f"{router_path} - DPS paths not found") continue path_data = next((path for path in path_group_details.values() if (path.get("source") == source and path.get("destination") == destination)), None) - # If the expected and actual source and destion address of the pathgroup are not matched, test fails. + # If the expected and actual source and destination address of the path group are not matched, test fails. if not path_data: - self.result.is_failure(f"{router_path} - Source and/or Destination address not found") + self.result.is_failure(f"{router_path} - Path not found") continue path_state = path_data.get("state") @@ -150,6 +150,6 @@ def test(self) -> None: expected_state = ["ipsecEstablished", "routeResolved"] # If the state of the path is not 'ipsecEstablished' or 'routeResolved', or the telemetry state is 'inactive', the test fails if path_state not in expected_state: - self.result.is_failure(f"{router_path} - Incorrect path state - Expected: {' or '.join(expected_state)} Actual: {path_state}") + self.result.is_failure(f"{router_path} - State is not in ipsecEstablished, routeResolved - Actual: {path_state}") elif not session: - self.result.is_failure(f"{router_path} - Incorrect telemetry state - Expected: active Actual: inactive") + self.result.is_failure(f"{router_path} - Telemetry state inactive") diff --git a/tests/units/anta_tests/test_path_selection.py b/tests/units/anta_tests/test_path_selection.py index 9aca0ee38..30f445047 100644 --- a/tests/units/anta_tests/test_path_selection.py +++ b/tests/units/anta_tests/test_path_selection.py @@ -224,8 +224,8 @@ "expected": { "result": "failure", "messages": [ - "Peer: 10.255.0.1 PathGroup: internet Source: 100.64.3.2 Destination: 100.64.1.2 - Path-group not found", - "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Path-group not found", + "Peer: 10.255.0.1 PathGroup: internet Source: 100.64.3.2 Destination: 100.64.1.2 - DPS paths not found", + "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - DPS paths not found", ], }, }, @@ -287,10 +287,10 @@ "expected": { "result": "failure", "messages": [ - "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Incorrect path state - Expected:" - " ipsecEstablished or routeResolved Actual: ipsecPending", - "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Incorrect path state - Expected:" - " ipsecEstablished or routeResolved Actual: ipsecPending", + "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - State is not in ipsecEstablished, routeResolved -" + " Actual: ipsecPending", + "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - State is not in ipsecEstablished, routeResolved -" + " Actual: ipsecPending", ], }, }, @@ -335,8 +335,8 @@ "expected": { "result": "failure", "messages": [ - "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Incorrect telemetry state - Expected: active Actual: inactive", - "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Incorrect telemetry state - Expected: active Actual: inactive", + "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Telemetry state inactive", + "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Telemetry state inactive", ], }, }, @@ -376,8 +376,8 @@ "expected": { "result": "failure", "messages": [ - "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Source and/or Destination address not found", - "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Source and/or Destination address not found", + "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Path not found", + "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Path not found", ], }, }, From d96a749fc5d3253d9707c2056d87e44af6ff9952 Mon Sep 17 00:00:00 2001 From: Geetanjali Mane Date: Thu, 26 Dec 2024 16:52:06 +0530 Subject: [PATCH 4/8] Updated test.yaml file --- anta/tests/path_selection.py | 2 +- examples/tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/anta/tests/path_selection.py b/anta/tests/path_selection.py index 2e1c82445..e6aec1ff9 100644 --- a/anta/tests/path_selection.py +++ b/anta/tests/path_selection.py @@ -68,7 +68,7 @@ def test(self) -> None: class VerifySpecificPath(AntaTest): - """Verifies the path and telemetry state of a specific path for an IPv4 peer. + """Verifies the DPS path and telemetry state of an IPv4 peer. This test performs the following checks for each specified router path: 1. Verifies that the specified peer is configured. diff --git a/examples/tests.yaml b/examples/tests.yaml index 3660a7dac..e5ef81cb5 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -342,7 +342,7 @@ anta.tests.path_selection: - VerifyPathsHealth: # Verifies the path and telemetry state of all paths under router path-selection. - VerifySpecificPath: - # Verifies the path and telemetry state of a specific path for an IPv4 peer. + # Verifies the DPS path and telemetry state of an IPv4 peer. paths: - peer: 10.255.0.1 path_group: internet From 111bf9c0cb1c0f1d2b1db7bdb198361572d48caf Mon Sep 17 00:00:00 2001 From: Geetanjali Mane Date: Thu, 26 Dec 2024 16:58:29 +0530 Subject: [PATCH 5/8] Fixed documenation issue --- anta/tests/path_selection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/anta/tests/path_selection.py b/anta/tests/path_selection.py index e6aec1ff9..bad2ee3ea 100644 --- a/anta/tests/path_selection.py +++ b/anta/tests/path_selection.py @@ -71,6 +71,7 @@ class VerifySpecificPath(AntaTest): """Verifies the DPS path and telemetry state of an IPv4 peer. This test performs the following checks for each specified router path: + 1. Verifies that the specified peer is configured. 2. Verifies that the specified path group is found. 3. Verifies that the expected source and destination address match the path group. From d1351c3b8bb79992aa9e804b52d01cbb87f4dbe3 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Wed, 15 Jan 2025 15:30:16 +0000 Subject: [PATCH 6/8] Updated docstrings and test failures messages --- anta/input_models/path_selection.py | 10 ++-- anta/tests/path_selection.py | 48 +++++++++---------- tests/units/anta_tests/test_path_selection.py | 20 ++++---- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/anta/input_models/path_selection.py b/anta/input_models/path_selection.py index f74b8931f..1c8e977c2 100644 --- a/anta/input_models/path_selection.py +++ b/anta/input_models/path_selection.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023-2024 Arista Networks, Inc. +# Copyright (c) 2023-2025 Arista Networks, Inc. # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. """Module containing input models for path-selection tests.""" @@ -10,8 +10,8 @@ from pydantic import BaseModel, ConfigDict -class RouterPath(BaseModel): - """Model for a list of router path entries.""" +class DpsPath(BaseModel): + """Model for a list of DPS path entries.""" model_config = ConfigDict(extra="forbid") peer: IPv4Address @@ -24,5 +24,5 @@ class RouterPath(BaseModel): """Destination IPv4 address of path.""" def __str__(self) -> str: - """Return a human-readable string representation of the RouterPath for reporting.""" - return f"Peer: {self.peer} PathGroup: {self.path_group} Source: {self.source_address} Destination: {self.destination_address}" + """Return a human-readable string representation of the DpsPath for reporting.""" + return f"Peer: {self.peer}, PathGroup: {self.path_group}, Source: {self.source_address}, Destination: {self.destination_address}" diff --git a/anta/tests/path_selection.py b/anta/tests/path_selection.py index bad2ee3ea..c637d430d 100644 --- a/anta/tests/path_selection.py +++ b/anta/tests/path_selection.py @@ -10,7 +10,7 @@ from typing import ClassVar from anta.decorators import skip_on_platforms -from anta.input_models.path_selection import RouterPath +from anta.input_models.path_selection import DpsPath from anta.models import AntaCommand, AntaTemplate, AntaTest from anta.tools import get_value @@ -70,13 +70,14 @@ def test(self) -> None: class VerifySpecificPath(AntaTest): """Verifies the DPS path and telemetry state of an IPv4 peer. - This test performs the following checks for each specified router path: + This test performs the following checks: - 1. Verifies that the specified peer is configured. - 2. Verifies that the specified path group is found. - 3. Verifies that the expected source and destination address match the path group. - 4. Verifies that the state of the path is `ipsecEstablished` or `routeResolved`. - 5. Verifies that the telemetry state is `active`. + 1. Verifies that the specified peer is configured. + 2. Verifies that the specified path group is found. + 3. For each specified DPS path: + - Verifies that the expected source and destination address matches the expected. + - Verifies that the state is `ipsecEstablished` or `routeResolved`. + - Verifies that the telemetry state is `active`. Expected Results ---------------- @@ -104,9 +105,9 @@ class VerifySpecificPath(AntaTest): class Input(AntaTest.Input): """Input model for the VerifySpecificPath test.""" - paths: list[RouterPath] + paths: list[DpsPath] """List of router paths to verify.""" - RouterPath: ClassVar[type[RouterPath]] = RouterPath + DpsPath: ClassVar[type[DpsPath]] = DpsPath @skip_on_platforms(["cEOSLab", "vEOS-lab"]) @AntaTest.anta_test @@ -118,39 +119,38 @@ def test(self) -> None: # If the dpsPeers details are not found in the command output, the test fails. if not (dps_peers_details := get_value(command_output, "dpsPeers")): - self.result.is_failure("Router path not configured") + self.result.is_failure("Router path-selection not configured") return - # Iterating on each router path mentioned in the inputs. - for router_path in self.inputs.paths: - peer = str(router_path.peer) - path_group = router_path.path_group - source = str(router_path.source_address) - destination = str(router_path.destination_address) + # Iterating on each DPS peer mentioned in the inputs. + for dps_path in self.inputs.paths: + peer = str(dps_path.peer) peer_details = dps_peers_details.get(peer, {}) - # If the peer is not configured for the path group, the test fails if not peer_details: - self.result.is_failure(f"{router_path} - Peer not found") + self.result.is_failure(f"{dps_path} - Peer not found") continue + path_group = dps_path.path_group + source = str(dps_path.source_address) + destination = str(dps_path.destination_address) path_group_details = get_value(peer_details, f"dpsGroups..{path_group}..dpsPaths", separator="..") # If the expected path group is not found for the peer, the test fails. if not path_group_details: - self.result.is_failure(f"{router_path} - DPS paths not found") + self.result.is_failure(f"{dps_path} - No DPS path found for this peer and path group.") continue path_data = next((path for path in path_group_details.values() if (path.get("source") == source and path.get("destination") == destination)), None) # If the expected and actual source and destination address of the path group are not matched, test fails. if not path_data: - self.result.is_failure(f"{router_path} - Path not found") + self.result.is_failure(f"{dps_path} - No path matching the source and destination found") continue path_state = path_data.get("state") session = get_value(path_data, "dpsSessions.0.active") - expected_state = ["ipsecEstablished", "routeResolved"] + # If the state of the path is not 'ipsecEstablished' or 'routeResolved', or the telemetry state is 'inactive', the test fails - if path_state not in expected_state: - self.result.is_failure(f"{router_path} - State is not in ipsecEstablished, routeResolved - Actual: {path_state}") + if path_state not in ["ipsecEstablished", "routeResolved"]: + self.result.is_failure(f"{dps_path} - State is not in ipsecEstablished, routeResolved. Actual: {path_state}") elif not session: - self.result.is_failure(f"{router_path} - Telemetry state inactive") + self.result.is_failure(f"{dps_path} - Telemetry state inactive for this path") diff --git a/tests/units/anta_tests/test_path_selection.py b/tests/units/anta_tests/test_path_selection.py index 30f445047..f003df959 100644 --- a/tests/units/anta_tests/test_path_selection.py +++ b/tests/units/anta_tests/test_path_selection.py @@ -224,8 +224,8 @@ "expected": { "result": "failure", "messages": [ - "Peer: 10.255.0.1 PathGroup: internet Source: 100.64.3.2 Destination: 100.64.1.2 - DPS paths not found", - "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - DPS paths not found", + "Peer: 10.255.0.1, PathGroup: internet, Source: 100.64.3.2, Destination: 100.64.1.2 - No DPS path found for this peer and path group", + "Peer: 10.255.0.2, PathGroup: mpls, Source: 172.18.13.2, Destination: 172.18.15.2 - No DPS path found for this peer and path group.", ], }, }, @@ -234,14 +234,14 @@ "test": VerifySpecificPath, "eos_data": [{"dpsPeers": {}}], "inputs": {"paths": [{"peer": "10.255.0.1", "path_group": "internet", "source_address": "100.64.3.2", "destination_address": "100.64.1.2"}]}, - "expected": {"result": "failure", "messages": ["Router path not configured"]}, + "expected": {"result": "failure", "messages": ["Router path-selection not configured"]}, }, { "name": "failure-no-specific-peer-configured", "test": VerifySpecificPath, "eos_data": [{"dpsPeers": {"10.255.0.2": {}}}], "inputs": {"paths": [{"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}]}, - "expected": {"result": "failure", "messages": ["Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Peer not found"]}, + "expected": {"result": "failure", "messages": ["Peer: 10.255.0.1, PathGroup: internet, Source: 172.18.3.2, Destination: 172.18.5.2 - Peer not found"]}, }, { "name": "failure-not-established", @@ -287,9 +287,9 @@ "expected": { "result": "failure", "messages": [ - "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - State is not in ipsecEstablished, routeResolved -" + "Peer: 10.255.0.1, PathGroup: internet, Source: 172.18.3.2, Destination: 172.18.5.2 - State is not in ipsecEstablished, routeResolved." " Actual: ipsecPending", - "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - State is not in ipsecEstablished, routeResolved -" + "Peer: 10.255.0.2, PathGroup: mpls, Source: 172.18.13.2, Destination: 172.18.15.2 - State is not in ipsecEstablished, routeResolved." " Actual: ipsecPending", ], }, @@ -335,8 +335,8 @@ "expected": { "result": "failure", "messages": [ - "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Telemetry state inactive", - "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Telemetry state inactive", + "Peer: 10.255.0.1, PathGroup: internet, Source: 172.18.3.2, Destination: 172.18.5.2 - Telemetry state inactive for this path", + "Peer: 10.255.0.2, PathGroup: mpls, Source: 172.18.13.2, Destination: 172.18.15.2 - Telemetry state inactive for this path", ], }, }, @@ -376,8 +376,8 @@ "expected": { "result": "failure", "messages": [ - "Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Path not found", - "Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Path not found", + "Peer: 10.255.0.1, PathGroup: internet, Source: 172.18.3.2, Destination: 172.18.5.2 - No path matching the source and destination found", + "Peer: 10.255.0.2, PathGroup: mpls, Source: 172.18.13.2, Destination: 172.18.15.2 - No path matching the source and destination found", ], }, }, From ce300b8f54081096bf1de546a81d91c37fcb6d04 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Wed, 22 Jan 2025 11:55:19 +0000 Subject: [PATCH 7/8] Updated docstrings --- anta/tests/path_selection.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/anta/tests/path_selection.py b/anta/tests/path_selection.py index c637d430d..4acdf01af 100644 --- a/anta/tests/path_selection.py +++ b/anta/tests/path_selection.py @@ -81,9 +81,9 @@ class VerifySpecificPath(AntaTest): Expected Results ---------------- - * Success: The test will pass if the path state under router path selection is either 'IPsecEstablished' or 'Resolved' + * Success: The test will pass if the path state under router path-selection is either 'IPsecEstablished' or 'Resolved' and telemetry state as 'active'. - * Failure: The test will fail if router path selection is not configured, the path state is not 'IPsec established' or 'Resolved', + * Failure: The test will fail if router path selection or the peer is not configured or if the path state is not 'IPsec established' or 'Resolved', or the telemetry state is 'inactive'. Examples @@ -107,7 +107,8 @@ class Input(AntaTest.Input): paths: list[DpsPath] """List of router paths to verify.""" - DpsPath: ClassVar[type[DpsPath]] = DpsPath + RouterPath: ClassVar[type[DpsPath]] = DpsPath + """To maintain backward compatibility.""" @skip_on_platforms(["cEOSLab", "vEOS-lab"]) @AntaTest.anta_test From 7c3e8ac8298373a1fb239b4af44d438013224d40 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Tue, 28 Jan 2025 15:04:13 +0100 Subject: [PATCH 8/8] Update anta/tests/path_selection.py --- anta/tests/path_selection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anta/tests/path_selection.py b/anta/tests/path_selection.py index 4acdf01af..762c9d6ee 100644 --- a/anta/tests/path_selection.py +++ b/anta/tests/path_selection.py @@ -142,7 +142,7 @@ def test(self) -> None: continue path_data = next((path for path in path_group_details.values() if (path.get("source") == source and path.get("destination") == destination)), None) - # If the expected and actual source and destination address of the path group are not matched, test fails. + # Source and destination address do not match, the test fails. if not path_data: self.result.is_failure(f"{dps_path} - No path matching the source and destination found") continue