From a52988d95f5d4df5e36c1beaafd864233c6bc9e7 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Mon, 6 Jan 2025 15:45:54 +0000 Subject: [PATCH 01/18] Added testcase to verify the BGP Redistributed Routes --- anta/custom_types.py | 1 + anta/input_models/routing/bgp.py | 26 ++- anta/tests/routing/bgp.py | 91 ++++++++++ examples/tests.yaml | 27 +++ tests/units/anta_tests/routing/test_bgp.py | 191 +++++++++++++++++++++ 5 files changed, 334 insertions(+), 2 deletions(-) diff --git a/anta/custom_types.py b/anta/custom_types.py index 3af27db97..c79e003c6 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -263,3 +263,4 @@ def validate_regex(value: str) -> str: SnmpHashingAlgorithm = Literal["MD5", "SHA", "SHA-224", "SHA-256", "SHA-384", "SHA-512"] SnmpEncryptionAlgorithm = Literal["AES-128", "AES-192", "AES-256", "DES"] DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"] +Redistributed_Protocol = Literal["AttachedHost", "Bgp", "Connected", "Dynamic", "IS-IS", "OSPF Internal", "OSPFv3 Internal", "RIP", "Static", "User"] diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index e51e0e11a..e1cfb828c 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator from pydantic_extra_types.mac_address import MacAddress -from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, Safi, Vni +from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, Redistributed_Protocol, Safi, Vni if TYPE_CHECKING: import sys @@ -40,6 +40,15 @@ } """Dictionary mapping AFI/SAFI to EOS key representation.""" +replaced_afi_safi_keys = { + "ipv4Unicast": "v4u", + "ipv4Multicast": "v4m", + "ipv6Unicast": "v6u", + "ipv6Multicast": "v6m", +} + +"""Dictionary mapping to replace a few keys of AFI/SAFI for EOS key representation in redistributed routes.""" + class BgpAddressFamily(BaseModel): """Model for a BGP address family.""" @@ -68,7 +77,16 @@ class BgpAddressFamily(BaseModel): check_peer_state: bool = False """Flag to check if the peers are established with negotiated AFI/SAFI. Defaults to `False`. - Can be enabled in the `VerifyBGPPeerCount` tests. + Can be enabled in the `VerifyBGPPeerCount` tests.""" + redistributed_route_protocol: Redistributed_Protocol | None = None + """Specify redistributed route protocol.""" + route_map: str | None = None + """Specify redistributed route protocol route map.""" + afi_safi_keys_replaced: bool = False + """Flag to check if the AFI_SAFI_EOS_KEYs values are replaced using replaced_afi_safi_keys dictionary. Defaults to `False`. + + Can be enabled in the `VerifyBGPRedistributedRoutes` tests. + """ @model_validator(mode="after") @@ -94,6 +112,10 @@ def validate_inputs(self) -> Self: @property def eos_key(self) -> str: """AFI/SAFI EOS key representation.""" + if self.afi_safi_keys_replaced: + afi_safi_keys = {key: replaced_afi_safi_keys.get(value, value) for key, value in AFI_SAFI_EOS_KEY.items()} + return afi_safi_keys[(self.afi, self.safi)] + # Pydantic handles the validation of the AFI/SAFI combination, so we can ignore error handling here. return AFI_SAFI_EOS_KEY[(self.afi, self.safi)] diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 89ad386ac..35face5e2 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1544,3 +1544,94 @@ def test(self) -> None: outq = peer["peerTcpInfo"]["outputQueueLength"] if self.inputs.check_tcp_queues and (inq != 0 or outq != 0): self.result.is_failure(f"Peer: {peer['peerAddress']} VRF: {vrf} - Session has non-empty message queues - InQ: {inq}, OutQ: {outq}") + + +class VerifyBGPRedistributedRoutes(AntaTest): + """Verifies BGP redistributed routes protocol and route-map. + + This test performs the following checks for each specified route: + + 1. Confirms the redistributed route protocol and route map, match the expected value for a route. + + Expected Results + ---------------- + * Success: If all of the following conditions are met: + - The redistributed route protocol and route map match the expected value for a route. + * Failure: If any of the following occur: + - The redistributed route protocol or route map do not match the expected value for a route. + + Examples + -------- + ```yaml + anta.tests.routing: + bgp: + - VerifyBGPRedistributedRoutes: + address_families: + - vrf: default + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP + afi: "ipv4" + safi: "unicast" + afi_safi_keys_replaced: True + - vrf: default + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP + afi: "ipv6" + safi: "unicast" + afi_safi_keys_replaced: True + - vrf: test + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP + afi: "ipv4" + safi: "multicast" + afi_safi_keys_replaced: True + - vrf: test + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP + afi: "ipv6" + safi: "multicast" + afi_safi_keys_replaced: True + ``` + """ + + categories: ClassVar[list[str]] = ["bgp"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp instance vrf all", revision=4)] + + class Input(AntaTest.Input): + """Input model for the VerifyBGPRedistributedRoutes test.""" + + address_families: list[BgpAddressFamily] + """List of BGP address families.""" + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyBGPRedistributedRoutes.""" + self.result.is_success() + cmd_output = self.instance_commands[0].json_output + + # If BGP is not configured on the device, test fails. + if cmd_output.get("errors"): + self.result.is_failure("BGP is not configured") + return + + # If specified VRF, afi safi details not found or redistributed route protocol or route map do not match the expected value, test fails. + for address_family in self.inputs.address_families: + vrf = address_family.vrf + redistributed_route_protocol = address_family.redistributed_route_protocol + route_map = address_family.route_map + afi_safi_key = address_family.eos_key + + if not (afi_safi_configs := get_value(cmd_output, f"vrfs.{vrf}.afiSafiConfig.{afi_safi_key}")): + self.result.is_failure(f"{address_family} - Not found") + continue + + redistributed_routes = afi_safi_configs.get("redistributedRoutes") + route_data = [route_data for route_data in redistributed_routes if route_data.get("proto") == redistributed_route_protocol] + if route_data: + if route_data[0].get("routeMap") != route_map: + self.result.is_failure(f"{address_family} - Redistributed route map mismatch - Expected: {route_map} Actual: {route_data[0].get('routeMap')}") + else: + routes_proto = [route_data["proto"] for route_data in redistributed_routes] + self.result.is_failure( + f"{address_family} - Redistributed route protocol mismatch - Expected: {redistributed_route_protocol} Actual: {', '.join(routes_proto)}" + ) diff --git a/examples/tests.yaml b/examples/tests.yaml index ce3b851a5..603b5b353 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -499,6 +499,33 @@ anta.tests.routing.bgp: - VerifyBGPPeersHealthRibd: # Verifies the health of all the BGP IPv4 peer(s). check_tcp_queues: True + - VerifyBGPRedistributedRoutes: + # Verifies BGP redistributed routes protocol and route-map. + address_families: + - vrf: default + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP + afi: "ipv4" + safi: "unicast" + afi_safi_keys_replaced: True + - vrf: default + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP + afi: "ipv6" + safi: "unicast" + afi_safi_keys_replaced: True + - vrf: test + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP + afi: "ipv4" + safi: "multicast" + afi_safi_keys_replaced: True + - vrf: test + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP + afi: "ipv6" + safi: "multicast" + afi_safi_keys_replaced: True - VerifyBGPSpecificPeers: # Verifies the health of specific BGP peer(s) for given address families. address_families: diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index c0de32932..c53dfaa3c 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -27,6 +27,7 @@ VerifyBGPPeersHealth, VerifyBGPPeersHealthRibd, VerifyBGPPeerUpdateErrors, + VerifyBGPRedistributedRoutes, VerifyBgpRouteMaps, VerifyBGPSpecificPeers, VerifyBGPTimers, @@ -4609,4 +4610,194 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], }, }, + { + "name": "success", + "test": VerifyBGPRedistributedRoutes, + "eos_data": [ + { + "vrfs": { + "default": {"afiSafiConfig": {"v4u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, + "test": {"afiSafiConfig": {"v6u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, + } + } + ], + "inputs": { + "address_families": [ + { + "vrf": "default", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv4", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + { + "vrf": "test", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv6", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "failure-afi-safi-config-not-found", + "test": VerifyBGPRedistributedRoutes, + "eos_data": [ + { + "vrfs": { + "default": {"afiSafiConfig": {"v4u": {}}}, + "test": {"afiSafiConfig": {"v6u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, + } + } + ], + "inputs": { + "address_families": [ + { + "vrf": "default", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv4", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + { + "vrf": "test", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv6", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + ] + }, + "expected": {"result": "failure", "messages": ["AFI: ipv4 SAFI: unicast VRF: default - Not found"]}, + }, + { + "name": "failure-expected-proto-not-found", + "test": VerifyBGPRedistributedRoutes, + "eos_data": [ + { + "vrfs": { + "default": { + "afiSafiConfig": { + "v4u": {"redistributedRoutes": [{"proto": "RIP", "routeMap": "RM-CONN-2-BGP"}, {"proto": "IS-IS", "routeMap": "RM-MLAG-PEER-IN"}]} + } + }, + "test": { + "afiSafiConfig": { + "v6u": { + "redistributedRoutes": [{"proto": "Static", "routeMap": "RM-CONN-2-BGP"}], + } + } + }, + } + } + ], + "inputs": { + "address_families": [ + { + "vrf": "default", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv4", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + { + "vrf": "test", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv6", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "AFI: ipv4 SAFI: unicast VRF: default - Redistributed route protocol mismatch - Expected: Connected Actual: RIP, IS-IS", + "AFI: ipv6 SAFI: unicast VRF: test - Redistributed route protocol mismatch - Expected: Connected Actual: Static", + ], + }, + }, + { + "name": "failure-route-map-not-found", + "test": VerifyBGPRedistributedRoutes, + "eos_data": [ + { + "vrfs": { + "default": { + "afiSafiConfig": { + "v4u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-10-BGP"}, {"proto": "IS-IS", "routeMap": "RM-MLAG-PEER-IN"}]} + } + }, + "test": { + "afiSafiConfig": { + "v6u": { + "redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-MLAG-PEER-IN"}], + } + } + }, + } + } + ], + "inputs": { + "address_families": [ + { + "vrf": "default", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv4", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + { + "vrf": "test", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv6", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "AFI: ipv4 SAFI: unicast VRF: default - Redistributed route map mismatch - Expected: RM-CONN-2-BGP Actual: RM-CONN-10-BGP", + "AFI: ipv6 SAFI: unicast VRF: test - Redistributed route map mismatch - Expected: RM-CONN-2-BGP Actual: RM-MLAG-PEER-IN", + ], + }, + }, + { + "name": "failure-bgp-inactive", + "test": VerifyBGPRedistributedRoutes, + "eos_data": [{"errors": ["BGP inactive"]}], + "inputs": { + "address_families": [ + { + "vrf": "default", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv4", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + { + "vrf": "test", + "redistributed_route_protocol": "Connected", + "route_map": "RM-CONN-2-BGP", + "afi": "ipv6", + "safi": "unicast", + "afi_safi_keys_replaced": True, + }, + ] + }, + "expected": {"result": "failure", "messages": ["BGP is not configured"]}, + }, ] From 9215731172a0e0c825a0a31939b919fd126961ff Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Tue, 7 Jan 2025 07:04:58 +0000 Subject: [PATCH 02/18] Removed redundant testcase and updated customtypes --- anta/input_models/routing/bgp.py | 25 +++++----------- anta/tests/routing/bgp.py | 14 ++------- examples/tests.yaml | 4 --- tests/units/anta_tests/routing/test_bgp.py | 34 ---------------------- 4 files changed, 11 insertions(+), 66 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index e1cfb828c..9760dce6e 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -40,14 +40,9 @@ } """Dictionary mapping AFI/SAFI to EOS key representation.""" -replaced_afi_safi_keys = { - "ipv4Unicast": "v4u", - "ipv4Multicast": "v4m", - "ipv6Unicast": "v6u", - "ipv6Multicast": "v6m", -} +AFI_SAFI_REDISTRIBUTED_ROUTE_KEY = {"ipv4Unicast": "v4u", "ipv4Multicast": "v4m", "ipv6Unicast": "v6u", "ipv6Multicast": "v6m"} -"""Dictionary mapping to replace a few keys of AFI/SAFI for EOS key representation in redistributed routes.""" +"""Dictionary mapping of AFI/SAFI to redistributed routes key representation.""" class BgpAddressFamily(BaseModel): @@ -82,12 +77,6 @@ class BgpAddressFamily(BaseModel): """Specify redistributed route protocol.""" route_map: str | None = None """Specify redistributed route protocol route map.""" - afi_safi_keys_replaced: bool = False - """Flag to check if the AFI_SAFI_EOS_KEYs values are replaced using replaced_afi_safi_keys dictionary. Defaults to `False`. - - Can be enabled in the `VerifyBGPRedistributedRoutes` tests. - - """ @model_validator(mode="after") def validate_inputs(self) -> Self: @@ -112,13 +101,15 @@ def validate_inputs(self) -> Self: @property def eos_key(self) -> str: """AFI/SAFI EOS key representation.""" - if self.afi_safi_keys_replaced: - afi_safi_keys = {key: replaced_afi_safi_keys.get(value, value) for key, value in AFI_SAFI_EOS_KEY.items()} - return afi_safi_keys[(self.afi, self.safi)] - # Pydantic handles the validation of the AFI/SAFI combination, so we can ignore error handling here. return AFI_SAFI_EOS_KEY[(self.afi, self.safi)] + @property + def redistributed_route_key(self) -> str: + """AFI/SAFI Redistributed route key representation.""" + afi_safi_keys = {key: AFI_SAFI_REDISTRIBUTED_ROUTE_KEY.get(value, value) for key, value in AFI_SAFI_EOS_KEY.items()} + return afi_safi_keys[(self.afi, self.safi)] + def __str__(self) -> str: """Return a human-readable string representation of the BgpAddressFamily for reporting. diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 35face5e2..fe545775f 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1572,28 +1572,25 @@ class VerifyBGPRedistributedRoutes(AntaTest): route_map: RM-CONN-2-BGP afi: "ipv4" safi: "unicast" - afi_safi_keys_replaced: True - vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP afi: "ipv6" safi: "unicast" - afi_safi_keys_replaced: True - vrf: test redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP afi: "ipv4" safi: "multicast" - afi_safi_keys_replaced: True - vrf: test redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP afi: "ipv6" safi: "multicast" - afi_safi_keys_replaced: True ``` """ - + # Note: For thr proto User on the device, Update the redistributed_route_protocol as EOS SDK. + categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp instance vrf all", revision=4)] @@ -1609,17 +1606,12 @@ def test(self) -> None: self.result.is_success() cmd_output = self.instance_commands[0].json_output - # If BGP is not configured on the device, test fails. - if cmd_output.get("errors"): - self.result.is_failure("BGP is not configured") - return - # If specified VRF, afi safi details not found or redistributed route protocol or route map do not match the expected value, test fails. for address_family in self.inputs.address_families: vrf = address_family.vrf redistributed_route_protocol = address_family.redistributed_route_protocol route_map = address_family.route_map - afi_safi_key = address_family.eos_key + afi_safi_key = address_family.redistributed_route_key if not (afi_safi_configs := get_value(cmd_output, f"vrfs.{vrf}.afiSafiConfig.{afi_safi_key}")): self.result.is_failure(f"{address_family} - Not found") diff --git a/examples/tests.yaml b/examples/tests.yaml index 603b5b353..6b555fcd5 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -507,25 +507,21 @@ anta.tests.routing.bgp: route_map: RM-CONN-2-BGP afi: "ipv4" safi: "unicast" - afi_safi_keys_replaced: True - vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP afi: "ipv6" safi: "unicast" - afi_safi_keys_replaced: True - vrf: test redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP afi: "ipv4" safi: "multicast" - afi_safi_keys_replaced: True - vrf: test redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP afi: "ipv6" safi: "multicast" - afi_safi_keys_replaced: True - VerifyBGPSpecificPeers: # Verifies the health of specific BGP peer(s) for given address families. address_families: diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index c53dfaa3c..8058a79bb 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -4629,7 +4629,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "route_map": "RM-CONN-2-BGP", "afi": "ipv4", "safi": "unicast", - "afi_safi_keys_replaced": True, }, { "vrf": "test", @@ -4637,7 +4636,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "route_map": "RM-CONN-2-BGP", "afi": "ipv6", "safi": "unicast", - "afi_safi_keys_replaced": True, }, ] }, @@ -4662,7 +4660,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "route_map": "RM-CONN-2-BGP", "afi": "ipv4", "safi": "unicast", - "afi_safi_keys_replaced": True, }, { "vrf": "test", @@ -4670,7 +4667,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "route_map": "RM-CONN-2-BGP", "afi": "ipv6", "safi": "unicast", - "afi_safi_keys_replaced": True, }, ] }, @@ -4705,7 +4701,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "route_map": "RM-CONN-2-BGP", "afi": "ipv4", "safi": "unicast", - "afi_safi_keys_replaced": True, }, { "vrf": "test", @@ -4713,7 +4708,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "route_map": "RM-CONN-2-BGP", "afi": "ipv6", "safi": "unicast", - "afi_safi_keys_replaced": True, }, ] }, @@ -4754,7 +4748,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "route_map": "RM-CONN-2-BGP", "afi": "ipv4", "safi": "unicast", - "afi_safi_keys_replaced": True, }, { "vrf": "test", @@ -4762,7 +4755,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "route_map": "RM-CONN-2-BGP", "afi": "ipv6", "safi": "unicast", - "afi_safi_keys_replaced": True, }, ] }, @@ -4774,30 +4766,4 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], }, }, - { - "name": "failure-bgp-inactive", - "test": VerifyBGPRedistributedRoutes, - "eos_data": [{"errors": ["BGP inactive"]}], - "inputs": { - "address_families": [ - { - "vrf": "default", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv4", - "safi": "unicast", - "afi_safi_keys_replaced": True, - }, - { - "vrf": "test", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv6", - "safi": "unicast", - "afi_safi_keys_replaced": True, - }, - ] - }, - "expected": {"result": "failure", "messages": ["BGP is not configured"]}, - }, ] From ddcf15c07ff715edec45226ef0480ec2c3305248 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Tue, 7 Jan 2025 07:12:22 +0000 Subject: [PATCH 03/18] Fixed pre-commit issue --- anta/tests/routing/bgp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index fe545775f..5f103b7c4 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1589,8 +1589,9 @@ class VerifyBGPRedistributedRoutes(AntaTest): safi: "multicast" ``` """ + # Note: For thr proto User on the device, Update the redistributed_route_protocol as EOS SDK. - + categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp instance vrf all", revision=4)] From 3a4eed815603dc5261d453c7af50e252f8b78f6f Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Tue, 14 Jan 2025 01:50:30 +0000 Subject: [PATCH 04/18] Updated test inputs with validator and added UT for the same. --- anta/custom_types.py | 2 +- anta/input_models/routing/bgp.py | 11 ++- anta/tests/routing/bgp.py | 72 +++++++++++++------- examples/tests.yaml | 20 +++--- tests/units/anta_tests/routing/test_bgp.py | 26 +++---- tests/units/input_models/routing/test_bgp.py | 43 ++++++++++++ 6 files changed, 119 insertions(+), 55 deletions(-) diff --git a/anta/custom_types.py b/anta/custom_types.py index c79e003c6..9843e0765 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -263,4 +263,4 @@ def validate_regex(value: str) -> str: SnmpHashingAlgorithm = Literal["MD5", "SHA", "SHA-224", "SHA-256", "SHA-384", "SHA-512"] SnmpEncryptionAlgorithm = Literal["AES-128", "AES-192", "AES-256", "DES"] DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"] -Redistributed_Protocol = Literal["AttachedHost", "Bgp", "Connected", "Dynamic", "IS-IS", "OSPF Internal", "OSPFv3 Internal", "RIP", "Static", "User"] +RedistributedProtocol = Literal["AttachedHost", "Bgp", "Connected", "Dynamic", "IS-IS", "OSPF Internal", "OSPFv3 Internal", "RIP", "Static", "User"] diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 9760dce6e..65f46eec6 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator from pydantic_extra_types.mac_address import MacAddress -from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, Redistributed_Protocol, Safi, Vni +from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, RedistributedProtocol, Safi, Vni if TYPE_CHECKING: import sys @@ -73,10 +73,10 @@ class BgpAddressFamily(BaseModel): """Flag to check if the peers are established with negotiated AFI/SAFI. Defaults to `False`. Can be enabled in the `VerifyBGPPeerCount` tests.""" - redistributed_route_protocol: Redistributed_Protocol | None = None - """Specify redistributed route protocol.""" + redistributed_route_protocol: RedistributedProtocol | None = None + """Specify redistributed route protocol. Required field in the `VerifyBGPRedistributedRoutes` test.""" route_map: str | None = None - """Specify redistributed route protocol route map.""" + """Specify redistributed route protocol route map. Required field in the `VerifyBGPRedistributedRoutes` test.""" @model_validator(mode="after") def validate_inputs(self) -> Self: @@ -107,8 +107,7 @@ def eos_key(self) -> str: @property def redistributed_route_key(self) -> str: """AFI/SAFI Redistributed route key representation.""" - afi_safi_keys = {key: AFI_SAFI_REDISTRIBUTED_ROUTE_KEY.get(value, value) for key, value in AFI_SAFI_EOS_KEY.items()} - return afi_safi_keys[(self.afi, self.safi)] + return AFI_SAFI_REDISTRIBUTED_ROUTE_KEY[self.eos_key] def __str__(self) -> str: """Return a human-readable string representation of the BgpAddressFamily for reporting. diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 5f103b7c4..e7f201560 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1551,14 +1551,20 @@ class VerifyBGPRedistributedRoutes(AntaTest): This test performs the following checks for each specified route: - 1. Confirms the redistributed route protocol and route map, match the expected value for a route. + 1. Ensures that the expected afi-safi is configured on the device. + 2. Confirms that the redistributed route protocol and route map match the expected values for a route. + + Note: The 'User' field in the redistributed route protocol has been updated to 'EOS SDK'. Expected Results ---------------- * Success: If all of the following conditions are met: - - The redistributed route protocol and route map match the expected value for a route. + - The expected afi-safi is configured on the device. + - The redistributed route protocol and route map align with the expected values for the route. * Failure: If any of the following occur: - - The redistributed route protocol or route map do not match the expected value for a route. + - The expected afi-safi is not configured on device. + - The redistributed route protocol or route map does not match the expected value for a route. + Examples -------- @@ -1567,31 +1573,29 @@ class VerifyBGPRedistributedRoutes(AntaTest): bgp: - VerifyBGPRedistributedRoutes: address_families: - - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP - afi: "ipv4" + - afi: "ipv4" safi: "unicast" - - vrf: default + vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP - afi: "ipv6" + - afi: "ipv6" safi: "unicast" - - vrf: test + vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP - afi: "ipv4" + - afi: "ipv4" safi: "multicast" - - vrf: test + vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP - afi: "ipv6" + - afi: "ipv6" safi: "multicast" + vrf: default + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP ``` """ - # Note: For thr proto User on the device, Update the redistributed_route_protocol as EOS SDK. - categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp instance vrf all", revision=4)] @@ -1601,16 +1605,36 @@ class Input(AntaTest.Input): address_families: list[BgpAddressFamily] """List of BGP address families.""" + @field_validator("address_families") + @classmethod + def validate_address_families(cls, address_families: list[BgpAddressFamily]) -> list[BgpAddressFamily]: + """Validate that all required fields are provided in each address family.""" + for address_family in address_families: + if address_family.afi not in ["ipv4", "ipv6"]: + msg = f"{address_family}; redistributed route protocol is not supported for address family `{address_family.afi}`" + raise ValueError(msg) + if address_family.safi not in ["unicast", "multicast"]: + msg = f"{address_family}; redistributed route protocol is not supported for subsequent address family `{address_family.safi}`" + raise ValueError(msg) + if address_family.redistributed_route_protocol is None: + msg = f"{address_family}; 'redistributed_route_protocol' field missing in the input" + raise ValueError(msg) + if address_family.route_map is None: + msg = f"{address_family}; 'route_map' field missing in the input" + raise ValueError(msg) + + return address_families + @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPRedistributedRoutes.""" self.result.is_success() cmd_output = self.instance_commands[0].json_output - # If specified VRF, afi safi details not found or redistributed route protocol or route map do not match the expected value, test fails. + # If the specified VRF, AFI-SAFI details are not found, or if the redistributed route protocol or route map do not match the expected values, the test fails. for address_family in self.inputs.address_families: vrf = address_family.vrf - redistributed_route_protocol = address_family.redistributed_route_protocol + redistributed_route_protocol = "EOS SDK" if address_family.redistributed_route_protocol == "User" else address_family.redistributed_route_protocol route_map = address_family.route_map afi_safi_key = address_family.redistributed_route_key @@ -1618,13 +1642,11 @@ def test(self) -> None: self.result.is_failure(f"{address_family} - Not found") continue - redistributed_routes = afi_safi_configs.get("redistributedRoutes") - route_data = [route_data for route_data in redistributed_routes if route_data.get("proto") == redistributed_route_protocol] - if route_data: - if route_data[0].get("routeMap") != route_map: - self.result.is_failure(f"{address_family} - Redistributed route map mismatch - Expected: {route_map} Actual: {route_data[0].get('routeMap')}") - else: - routes_proto = [route_data["proto"] for route_data in redistributed_routes] + if not (route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", redistributed_route_protocol)): + self.result.is_failure(f"{address_family} Protocol: {redistributed_route_protocol} - Not Found") + continue + + if (actual_route_map := route.get("routeMap", "Not Found")) != route_map: self.result.is_failure( - f"{address_family} - Redistributed route protocol mismatch - Expected: {redistributed_route_protocol} Actual: {', '.join(routes_proto)}" + f"{address_family} Protocol: {redistributed_route_protocol} - Route map mismatch - Expected: {route_map} Actual: {actual_route_map}" ) diff --git a/examples/tests.yaml b/examples/tests.yaml index 6b555fcd5..ae2ec903d 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -502,26 +502,26 @@ anta.tests.routing.bgp: - VerifyBGPRedistributedRoutes: # Verifies BGP redistributed routes protocol and route-map. address_families: - - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP - afi: "ipv4" + - afi: "ipv4" safi: "unicast" - - vrf: default + vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP - afi: "ipv6" + - afi: "ipv6" safi: "unicast" - - vrf: test + vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP - afi: "ipv4" + - afi: "ipv4" safi: "multicast" - - vrf: test + vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP - afi: "ipv6" + - afi: "ipv6" safi: "multicast" + vrf: default + redistributed_route_protocol: Connected + route_map: RM-CONN-2-BGP - VerifyBGPSpecificPeers: # Verifies the health of specific BGP peer(s) for given address families. address_families: diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 8058a79bb..ae9412e3f 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -4648,7 +4648,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "vrfs": { "default": {"afiSafiConfig": {"v4u": {}}}, - "test": {"afiSafiConfig": {"v6u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, + "test": {"afiSafiConfig": {"v6m": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, } } ], @@ -4666,7 +4666,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP", "afi": "ipv6", - "safi": "unicast", + "safi": "multicast", }, ] }, @@ -4680,12 +4680,12 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "vrfs": { "default": { "afiSafiConfig": { - "v4u": {"redistributedRoutes": [{"proto": "RIP", "routeMap": "RM-CONN-2-BGP"}, {"proto": "IS-IS", "routeMap": "RM-MLAG-PEER-IN"}]} + "v4m": {"redistributedRoutes": [{"proto": "RIP", "routeMap": "RM-CONN-2-BGP"}, {"proto": "IS-IS", "routeMap": "RM-MLAG-PEER-IN"}]} } }, "test": { "afiSafiConfig": { - "v6u": { + "v6m": { "redistributedRoutes": [{"proto": "Static", "routeMap": "RM-CONN-2-BGP"}], } } @@ -4700,22 +4700,22 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP", "afi": "ipv4", - "safi": "unicast", + "safi": "multicast", }, { "vrf": "test", - "redistributed_route_protocol": "Connected", + "redistributed_route_protocol": "User", "route_map": "RM-CONN-2-BGP", "afi": "ipv6", - "safi": "unicast", + "safi": "multicast", }, ] }, "expected": { "result": "failure", "messages": [ - "AFI: ipv4 SAFI: unicast VRF: default - Redistributed route protocol mismatch - Expected: Connected Actual: RIP, IS-IS", - "AFI: ipv6 SAFI: unicast VRF: test - Redistributed route protocol mismatch - Expected: Connected Actual: Static", + "AFI: ipv4 SAFI: multicast VRF: default Protocol: Connected - Not Found", + "AFI: ipv6 SAFI: multicast VRF: test Protocol: EOS SDK - Not Found", ], }, }, @@ -4727,7 +4727,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "vrfs": { "default": { "afiSafiConfig": { - "v4u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-10-BGP"}, {"proto": "IS-IS", "routeMap": "RM-MLAG-PEER-IN"}]} + "v4m": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-10-BGP"}, {"proto": "IS-IS", "routeMap": "RM-MLAG-PEER-IN"}]} } }, "test": { @@ -4747,7 +4747,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP", "afi": "ipv4", - "safi": "unicast", + "safi": "multicast", }, { "vrf": "test", @@ -4761,8 +4761,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI: ipv4 SAFI: unicast VRF: default - Redistributed route map mismatch - Expected: RM-CONN-2-BGP Actual: RM-CONN-10-BGP", - "AFI: ipv6 SAFI: unicast VRF: test - Redistributed route map mismatch - Expected: RM-CONN-2-BGP Actual: RM-MLAG-PEER-IN", + "AFI: ipv4 SAFI: multicast VRF: default Protocol: Connected - Route map mismatch - Expected: RM-CONN-2-BGP Actual: RM-CONN-10-BGP", + "AFI: ipv6 SAFI: unicast VRF: test Protocol: Connected - Route map mismatch - Expected: RM-CONN-2-BGP Actual: RM-MLAG-PEER-IN", ], }, }, diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index 61f2b4397..5f64b41e7 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -18,6 +18,7 @@ VerifyBGPPeerGroup, VerifyBGPPeerMPCaps, VerifyBGPPeerRouteLimit, + VerifyBGPRedistributedRoutes, VerifyBgpRouteMaps, VerifyBGPSpecificPeers, VerifyBGPTimers, @@ -262,3 +263,45 @@ def test_invalid(self, bgp_peers: list[BgpPeer]) -> None: """Test VerifyBGPPeerGroup.Input invalid inputs.""" with pytest.raises(ValidationError): VerifyBGPPeerGroup.Input(bgp_peers=bgp_peers) + + +class TestVerifyBGPRedistributedRoutes: + """Test anta.tests.routing.bgp.VerifyBGPRedistributedRoutes.Input.""" + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param( + [{"afi": "ipv4", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], id="valid" + ), + ], + ) + def test_valid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPPeerRouteLimit.Input valid inputs.""" + VerifyBGPRedistributedRoutes.Input(address_families=address_families) + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param( + [{"afi": "ipv4", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": None, "route_map": "RM-CONN-2-BGP"}], + id="invalid-redistributed-route-protocol", + ), + pytest.param( + [{"afi": "ipv4", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": None}], + id="invalid-route-map", + ), + pytest.param( + [{"afi": "evpn", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], + id="invalid-afi-for-redistributed-route", + ), + pytest.param( + [{"afi": "ipv4", "safi": "sr-te", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], + id="invalid-safi-for-redistributed-route", + ), + ], + ) + def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPPeerRouteLimit.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPRedistributedRoutes.Input(address_families=address_families) From 145dae6484c8703aa9c07d7909e0827aacd5e1cf Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Tue, 14 Jan 2025 04:26:00 +0000 Subject: [PATCH 05/18] Updated field validator and UT error messages --- anta/tests/routing/bgp.py | 23 ++++++++------------ tests/units/anta_tests/routing/test_bgp.py | 2 +- tests/units/input_models/routing/test_bgp.py | 13 ++++++----- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index e7f201560..d887c2191 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1551,21 +1551,20 @@ class VerifyBGPRedistributedRoutes(AntaTest): This test performs the following checks for each specified route: - 1. Ensures that the expected afi-safi is configured on the device. + 1. Ensures that the expected address-family is configured on the device. 2. Confirms that the redistributed route protocol and route map match the expected values for a route. - Note: The 'User' field in the redistributed route protocol has been updated to 'EOS SDK'. + Note: For "User" redistributed_route_protocol field, checking that it's "EOS SDK" versus User. Expected Results ---------------- * Success: If all of the following conditions are met: - - The expected afi-safi is configured on the device. + - The expected address-family is configured on the device. - The redistributed route protocol and route map align with the expected values for the route. * Failure: If any of the following occur: - - The expected afi-safi is not configured on device. + - The expected address-family is not configured on device. - The redistributed route protocol or route map does not match the expected value for a route. - Examples -------- ```yaml @@ -1610,11 +1609,8 @@ class Input(AntaTest.Input): def validate_address_families(cls, address_families: list[BgpAddressFamily]) -> list[BgpAddressFamily]: """Validate that all required fields are provided in each address family.""" for address_family in address_families: - if address_family.afi not in ["ipv4", "ipv6"]: - msg = f"{address_family}; redistributed route protocol is not supported for address family `{address_family.afi}`" - raise ValueError(msg) - if address_family.safi not in ["unicast", "multicast"]: - msg = f"{address_family}; redistributed route protocol is not supported for subsequent address family `{address_family.safi}`" + if address_family.afi not in ["ipv4", "ipv6"] or address_family.safi not in ["unicast", "multicast"]: + msg = f"{address_family}; redistributed route protocol is not supported for address family `{address_family.eos_key}`" raise ValueError(msg) if address_family.redistributed_route_protocol is None: msg = f"{address_family}; 'redistributed_route_protocol' field missing in the input" @@ -1622,7 +1618,6 @@ def validate_address_families(cls, address_families: list[BgpAddressFamily]) -> if address_family.route_map is None: msg = f"{address_family}; 'route_map' field missing in the input" raise ValueError(msg) - return address_families @AntaTest.anta_test @@ -1643,10 +1638,10 @@ def test(self) -> None: continue if not (route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", redistributed_route_protocol)): - self.result.is_failure(f"{address_family} Protocol: {redistributed_route_protocol} - Not Found") + self.result.is_failure(f"{address_family} Protocol: {address_family.redistributed_route_protocol} - Not Found") continue - if (actual_route_map := route.get("routeMap", "Not Found")) != route_map: + if (act_route_map := route.get("routeMap", "Not Found")) != route_map: self.result.is_failure( - f"{address_family} Protocol: {redistributed_route_protocol} - Route map mismatch - Expected: {route_map} Actual: {actual_route_map}" + f"{address_family} Protocol: {address_family.redistributed_route_protocol} - Route map mismatch - Expected: {route_map} Actual: {act_route_map}" ) diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index ae9412e3f..e6757c9f0 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -4715,7 +4715,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "result": "failure", "messages": [ "AFI: ipv4 SAFI: multicast VRF: default Protocol: Connected - Not Found", - "AFI: ipv6 SAFI: multicast VRF: test Protocol: EOS SDK - Not Found", + "AFI: ipv6 SAFI: multicast VRF: test Protocol: User - Not Found", ], }, }, diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index 5f64b41e7..0a80c0759 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -272,12 +272,15 @@ class TestVerifyBGPRedistributedRoutes: ("address_families"), [ pytest.param( - [{"afi": "ipv4", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], id="valid" + [{"afi": "ipv4", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], id="ipv4-valid" + ), + pytest.param( + [{"afi": "ipv6", "safi": "multicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], id="ipv6-valid" ), ], ) def test_valid(self, address_families: list[BgpAddressFamily]) -> None: - """Test VerifyBGPPeerRouteLimit.Input valid inputs.""" + """Test VerifyBGPRedistributedRoutes.Input valid inputs.""" VerifyBGPRedistributedRoutes.Input(address_families=address_families) @pytest.mark.parametrize( @@ -288,7 +291,7 @@ def test_valid(self, address_families: list[BgpAddressFamily]) -> None: id="invalid-redistributed-route-protocol", ), pytest.param( - [{"afi": "ipv4", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": None}], + [{"afi": "ipv6", "safi": "multicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": None}], id="invalid-route-map", ), pytest.param( @@ -296,12 +299,12 @@ def test_valid(self, address_families: list[BgpAddressFamily]) -> None: id="invalid-afi-for-redistributed-route", ), pytest.param( - [{"afi": "ipv4", "safi": "sr-te", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], + [{"afi": "ipv6", "safi": "sr-te", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], id="invalid-safi-for-redistributed-route", ), ], ) def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: - """Test VerifyBGPPeerRouteLimit.Input invalid inputs.""" + """Test VerifyBGPRedistributedRoutes.Input invalid inputs.""" with pytest.raises(ValidationError): VerifyBGPRedistributedRoutes.Input(address_families=address_families) From 688b67ba4d2955ed68f1689cc566c2924d611952 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Fri, 17 Jan 2025 12:12:11 +0000 Subject: [PATCH 06/18] Added pylint disable for number of line check (C0302) with TODO. --- anta/tests/routing/bgp.py | 18 +++--------------- examples/tests.yaml | 15 --------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index d25b62766..54b1be3b6 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -18,6 +18,9 @@ # Using a TypeVar for the BgpPeer model since mypy thinks it's a ClassVar and not a valid type when used in field validators T = TypeVar("T", bound=BgpPeer) +# pylint: disable=C0302 +# TODO: Refactor to reduce the number of lines in this module later + def _check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool: """Check if a BGP neighbor capability is advertised, received, and enabled. @@ -1718,21 +1721,6 @@ class VerifyBGPRedistributedRoutes(AntaTest): vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP - - afi: "ipv6" - safi: "unicast" - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP - - afi: "ipv4" - safi: "multicast" - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP - - afi: "ipv6" - safi: "multicast" - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP ``` """ diff --git a/examples/tests.yaml b/examples/tests.yaml index 9a007293b..1158dff33 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -514,21 +514,6 @@ anta.tests.routing.bgp: vrf: default redistributed_route_protocol: Connected route_map: RM-CONN-2-BGP - - afi: "ipv6" - safi: "unicast" - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP - - afi: "ipv4" - safi: "multicast" - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP - - afi: "ipv6" - safi: "multicast" - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP - VerifyBGPRoutePaths: # Verifies BGP IPv4 route paths. route_entries: From 1a326a648b6484985088fd1c4283be5e7d54defa Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Tue, 21 Jan 2025 11:48:12 +0000 Subject: [PATCH 07/18] Fixed precommit issues --- anta/custom_types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/anta/custom_types.py b/anta/custom_types.py index 942f0ab27..2333a5fbc 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -265,4 +265,3 @@ def validate_regex(value: str) -> str: DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"] LogSeverityLevel = Literal["alerts", "critical", "debugging", "emergencies", "errors", "informational", "notifications", "warnings"] RedistributedProtocol = Literal["AttachedHost", "Bgp", "Connected", "Dynamic", "IS-IS", "OSPF Internal", "OSPFv3 Internal", "RIP", "Static", "User"] - From e65000c486dce7c09de9c5743c2eb11fb64dc562 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Wed, 22 Jan 2025 11:43:09 +0000 Subject: [PATCH 08/18] Updated testcase with the latest input models --- anta/custom_types.py | 45 ++++++- anta/input_models/routing/bgp.py | 64 ++++++++- anta/tests/routing/bgp.py | 131 ++++++++++++------- examples/tests.yaml | 26 +++- tests/units/anta_tests/routing/test_bgp.py | 52 ++++++-- tests/units/input_models/routing/test_bgp.py | 76 +++++++---- 6 files changed, 290 insertions(+), 104 deletions(-) diff --git a/anta/custom_types.py b/anta/custom_types.py index 2333a5fbc..b343f0b61 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -36,6 +36,12 @@ REGEX_BGP_IPV4_UNICAST = r"\b(ipv4[\s\-]?uni[\s\-]?cast)\b" """Match IPv4 Unicast.""" +# Regular expression for BGP redistributed routes +REGEX_IPV4_UNICAST = r"\bipv4[\s]?unicast\b" +REGEX_IPV4_MULTICAST = r"\bipv4[\s]?multicast\b" +REGEX_IPV6_UNICAST = r"\bipv6[\s]?unicast\b" +REGEX_IPV6_MULTICAST = r"\bipv6[\s]?multicast\b" + def aaa_group_prefix(v: str) -> str: """Prefix the AAA method with 'group' if it is known.""" @@ -114,6 +120,27 @@ def validate_regex(value: str) -> str: return value +def bgp_redistributed_route_proto_abbreviations(value: str) -> str: + """Abbreviations for different BGP redistributed route protocols. + + Examples + -------- + - IPv4 Unicast + - ipv4Unicast + - IPv4 Multicast + - ipv4Multicast + + """ + patterns = {REGEX_IPV4_UNICAST: "v4u", REGEX_IPV4_MULTICAST: "v4m", REGEX_IPV6_UNICAST: "v6u", REGEX_IPV6_MULTICAST: "v6m"} + + for pattern, replacement in patterns.items(): + match = re.search(pattern, value, re.IGNORECASE) + if match: + return replacement + + return value + + # AntaTest.Input types AAAAuthMethod = Annotated[str, AfterValidator(aaa_group_prefix)] Vlan = Annotated[int, Field(ge=0, le=4094)] @@ -264,4 +291,20 @@ def validate_regex(value: str) -> str: SnmpEncryptionAlgorithm = Literal["AES-128", "AES-192", "AES-256", "DES"] DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"] LogSeverityLevel = Literal["alerts", "critical", "debugging", "emergencies", "errors", "informational", "notifications", "warnings"] -RedistributedProtocol = Literal["AttachedHost", "Bgp", "Connected", "Dynamic", "IS-IS", "OSPF Internal", "OSPFv3 Internal", "RIP", "Static", "User"] +RedistributedProtocol = Literal[ + "AttachedHost", + "Bgp", + "Connected", + "Dynamic", + "IS-IS", + "OSPF Internal", + "OSPF External", + "OSPF Nssa-External", + "OSPFv3 Internal", + "OSPFv3 External", + "OSPFv3 Nssa-External", + "RIP", + "Static", + "User", +] +RedisrbutedAfiSafi = Annotated[str, BeforeValidator(bgp_redistributed_route_proto_abbreviations)] diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 483828564..3e01cb814 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator from pydantic_extra_types.mac_address import MacAddress -from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, RedistributedProtocol, Safi, Vni +from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, RedisrbutedAfiSafi, RedistributedProtocol, Safi, Vni if TYPE_CHECKING: import sys @@ -73,10 +73,9 @@ class BgpAddressFamily(BaseModel): """Flag to check if the peers are established with negotiated AFI/SAFI. Defaults to `False`. Can be enabled in the `VerifyBGPPeerCount` tests.""" - redistributed_route_protocol: RedistributedProtocol | None = None - """Specify redistributed route protocol. Required field in the `VerifyBGPRedistributedRoutes` test.""" + route_map: str | None = None - """Specify redistributed route protocol route map. Required field in the `VerifyBGPRedistributedRoutes` test.""" + """Specify redistributed route protocol route map. Required field in the `VerifyBGPRedistribution` test.""" @model_validator(mode="after") def validate_inputs(self) -> Self: @@ -266,3 +265,60 @@ def __str__(self) -> str: - Next-hop: 192.168.66.101 Origin: Igp """ return f"Next-hop: {self.nexthop} Origin: {self.origin}" + + +class RedistributedRoute(BaseModel): + """Model representing BGP redistributed route.""" + + proto: RedistributedProtocol + """The redistributed route protocol.""" + include_leaked: bool | None = None + """Flag to include leaked BGP routes in the advertisement""" + route_map: str | None = None + """The route map of the redistributed routes.""" + + @model_validator(mode="after") + def validate_include_leaked_support(self) -> Self: + """Validate the input provided for included leaked field, included _leaked this field is not supported for proto AttachedHost, User, Dynamic, RIP.""" + if self.include_leaked and self.proto in ["AttachedHost", "User", "Dynamic", "RIP"]: + msg = f"{self.include_leaked}, field is not supported for redistributed route protocol `{self.proto}`" + raise ValueError(msg) + return self + + +class AddressFamilyConfig(BaseModel): + """Model representing BGP address family configs.""" + + afi_safi: RedisrbutedAfiSafi + """BGP redistributed route supported address families""" + redistributed_routes: list[RedistributedRoute] + """A list of redistributed route""" + + @model_validator(mode="after") + def validate_inputs(self) -> Self: + """Validate the inputs provided to the AddressFamilyConfig class. + + address families must be `ipv4` or `ipv6` only, and sub address families can be `unicast` or `multicast`. + """ + if self.afi_safi not in ["v4u", "v4m", "v6u", "v6m"]: + msg = f"Redistributed route protocol is not supported for address family `{self.afi_safi}`" + raise ValueError(msg) + return self + + +class BgpVrf(BaseModel): + """Model representing BGP vrfs.""" + + vrf: str = "default" + """VRF for the BGP instance. Defaults to `default`.""" + address_families: list[AddressFamilyConfig] + """list of address family configuration.""" + + def __str__(self) -> str: + """Return a human-readable string representation of the BgpVrf for reporting. + + Examples + -------- + - VRF: default + """ + return f"VRF: {self.vrf}" diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 54b1be3b6..9899b4231 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -7,11 +7,11 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations -from typing import ClassVar, TypeVar +from typing import Any, ClassVar, TypeVar from pydantic import field_validator -from anta.input_models.routing.bgp import BgpAddressFamily, BgpAfi, BgpNeighbor, BgpPeer, BgpRoute, VxlanEndpoint +from anta.input_models.routing.bgp import BgpAddressFamily, BgpAfi, BgpNeighbor, BgpPeer, BgpRoute, BgpVrf, VxlanEndpoint from anta.models import AntaCommand, AntaTemplate, AntaTest from anta.tools import format_data, get_item, get_value @@ -43,6 +43,55 @@ def _check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool: return all(capability_status.get(state, False) for state in ("advertised", "received", "enabled")) +def _check_bgp_route_redistribution(vrf: str, address_family: list[Any], cmd_output: dict[str, Any]) -> str: + """Check if bgp route proto, included_leaked, routemap values matches the expected values. + + Parameters + ---------- + vrf + A VRF name + address_family + A list containing the bgp address family details. + cmd_output + A dictionary containing output of `show bgp instance vrf all` + + Returns + ------- + str: + Test failure messages. + Example + ------- + >>> _check_bgp_route_redistribution(default, [ + { "afi_safi": "ipv4Unicast","redistributed_routes": ["proto": "Connected","include_leaked": True,"route_map": "RM-CONN-2-BGP"}], + "show bgp instance vrf all" command output) + + """ + failure_result = "" + for route_info in address_family.redistributed_routes: + proto = "EOS SDK" if route_info.proto == "User" else route_info.proto + afi_safi_key = address_family.afi_safi + output_msg_str = f"VRF: {vrf}, AFISAFI: {afi_safi_key}, Protocol: {route_info.proto}" + # If the specified VRF, AFI-SAFI details are not found, test fails. + if not (afi_safi_configs := get_value(cmd_output, f"vrfs.{vrf}.afiSafiConfig.{afi_safi_key}")): + failure_result += f"VRF: {vrf}, AFISAFI: {afi_safi_key} - Not found" + continue + + # If the redistributed route protocol does not match the expected value, test fails. + if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", proto)): + failure_result += f"{output_msg_str} - Not Found" + continue + + # If includes leaked field applicable, and it does not matches the expected value, test fails. + if route_info.include_leaked and (act_include_leaked := actual_route.get("includeLeaked")) != route_info.include_leaked: + failure_result += f"{output_msg_str} - Value for included leaked mismatch - Expected :{route_info.include_leaked}, Actual: {act_include_leaked}" + + # If route map is required and it is not matching the expected value, test fails. + if route_info.route_map and (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map: + failure_result += f"{output_msg_str} - Route map mismatch - Expected: {route_info.route_map} Actual: {act_route_map}" + + return failure_result + + class VerifyBGPPeerCount(AntaTest): """Verifies the count of BGP peers for given address families. @@ -1690,7 +1739,7 @@ def test(self) -> None: self.result.is_failure(f"{route} {path} - Origin mismatch - Actual: {actual_origin}") -class VerifyBGPRedistributedRoutes(AntaTest): +class VerifyBGPRedistribution(AntaTest): """Verifies BGP redistributed routes protocol and route-map. This test performs the following checks for each specified route: @@ -1698,7 +1747,7 @@ class VerifyBGPRedistributedRoutes(AntaTest): 1. Ensures that the expected address-family is configured on the device. 2. Confirms that the redistributed route protocol and route map match the expected values for a route. - Note: For "User" redistributed_route_protocol field, checking that it's "EOS SDK" versus User. + Note: For "User" proto field, checking that it's "EOS SDK" versus User. Expected Results ---------------- @@ -1714,13 +1763,25 @@ class VerifyBGPRedistributedRoutes(AntaTest): ```yaml anta.tests.routing: bgp: - - VerifyBGPRedistributedRoutes: - address_families: - - afi: "ipv4" - safi: "unicast" - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP + - VerifyBGPRedistribution: + vrfs: + - vrf: default + address_families: + - afi_safi: ipv4Unicast + redistributed_routes: + - proto: Connected + include_leaked: True + route_map: RM-CONN-2-BGP + - proto: Static + include_leaked: True + route_map: RM-CONN-2-BGP + - afi_safi: IPv6 Unicast + redistributed_routes: + - proto: Dynamic + route_map: RM-CONN-2-BGP + - proto: Static + include_leaked: True + route_map: RM-CONN-2-BGP ``` """ @@ -1728,49 +1789,19 @@ class VerifyBGPRedistributedRoutes(AntaTest): commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp instance vrf all", revision=4)] class Input(AntaTest.Input): - """Input model for the VerifyBGPRedistributedRoutes test.""" + """Input model for the VerifyBGPRedistribution test.""" - address_families: list[BgpAddressFamily] - """List of BGP address families.""" - - @field_validator("address_families") - @classmethod - def validate_address_families(cls, address_families: list[BgpAddressFamily]) -> list[BgpAddressFamily]: - """Validate that all required fields are provided in each address family.""" - for address_family in address_families: - if address_family.afi not in ["ipv4", "ipv6"] or address_family.safi not in ["unicast", "multicast"]: - msg = f"{address_family}; redistributed route protocol is not supported for address family `{address_family.eos_key}`" - raise ValueError(msg) - if address_family.redistributed_route_protocol is None: - msg = f"{address_family}; 'redistributed_route_protocol' field missing in the input" - raise ValueError(msg) - if address_family.route_map is None: - msg = f"{address_family}; 'route_map' field missing in the input" - raise ValueError(msg) - return address_families + vrfs: list[BgpVrf] + """List of address families.""" @AntaTest.anta_test def test(self) -> None: - """Main test function for VerifyBGPRedistributedRoutes.""" + """Main test function for VerifyBGPRedistribution.""" self.result.is_success() - cmd_output = self.instance_commands[0].json_output - - # If the specified VRF, AFI-SAFI details are not found, or if the redistributed route protocol or route map do not match the expected values, the test fails. - for address_family in self.inputs.address_families: - vrf = address_family.vrf - redistributed_route_protocol = "EOS SDK" if address_family.redistributed_route_protocol == "User" else address_family.redistributed_route_protocol - route_map = address_family.route_map - afi_safi_key = address_family.redistributed_route_key + command_output = self.instance_commands[0].json_output - if not (afi_safi_configs := get_value(cmd_output, f"vrfs.{vrf}.afiSafiConfig.{afi_safi_key}")): - self.result.is_failure(f"{address_family} - Not found") - continue - - if not (route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", redistributed_route_protocol)): - self.result.is_failure(f"{address_family} Protocol: {address_family.redistributed_route_protocol} - Not Found") - continue + for vrf_data in self.inputs.vrfs: + for address_family in vrf_data.address_families: + failure_messages = _check_bgp_route_redistribution(vrf=vrf_data.vrf, address_family=address_family, cmd_output=command_output) + self.result.is_failure(failure_messages) - if (act_route_map := route.get("routeMap", "Not Found")) != route_map: - self.result.is_failure( - f"{address_family} Protocol: {address_family.redistributed_route_protocol} - Route map mismatch - Expected: {route_map} Actual: {act_route_map}" - ) diff --git a/examples/tests.yaml b/examples/tests.yaml index 62387c40e..5b1056225 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -509,14 +509,26 @@ anta.tests.routing.bgp: - VerifyBGPPeersHealthRibd: # Verifies the health of all the BGP IPv4 peer(s). check_tcp_queues: True - - VerifyBGPRedistributedRoutes: + - VerifyBGPRedistribution: # Verifies BGP redistributed routes protocol and route-map. - address_families: - - afi: "ipv4" - safi: "unicast" - vrf: default - redistributed_route_protocol: Connected - route_map: RM-CONN-2-BGP + vrfs: + - vrf: default + address_families: + - afi_safi: ipv4Unicast + redistributed_routes: + - proto: Connected + include_leaked: True + route_map: RM-CONN-2-BGP + - proto: Static + include_leaked: True + route_map: RM-CONN-2-BGP + - afi_safi: IPv6 Unicast + redistributed_routes: + - proto: Dynamic + route_map: RM-CONN-2-BGP + - proto: Static + include_leaked: True + route_map: RM-CONN-2-BGP - VerifyBGPRoutePaths: # Verifies BGP IPv4 route paths. route_entries: diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 2e0f32965..8d71f4df9 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -28,7 +28,7 @@ VerifyBGPPeersHealth, VerifyBGPPeersHealthRibd, VerifyBGPPeerUpdateErrors, - VerifyBGPRedistributedRoutes, + VerifyBGPRedistribution, VerifyBgpRouteMaps, VerifyBGPRoutePaths, VerifyBGPSpecificPeers, @@ -5103,7 +5103,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, { "name": "success", - "test": VerifyBGPRedistributedRoutes, + "test": VerifyBGPRedistribution, "eos_data": [ { "vrfs": { @@ -5113,20 +5113,44 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ], "inputs": { - "address_families": [ + "vrfs": [ { "vrf": "default", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv4", - "safi": "unicast", + "address_families": [ + { + "afi_safi": "ipv4Unicast", + "redistributed_routes": [ + {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + }, + { + "afi_safi": "IPv6 Unicast", + "redistributed_routes": [ + {"proto": "Dynamic", "route_map": "RM-CONN-2-BGP"}, + {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], }, { "vrf": "test", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv6", - "safi": "unicast", + "address_families": [ + { + "afi_safi": "ipv4Unicast", + "redistributed_routes": [ + {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + }, + { + "afi_safi": "IPv6 Unicast", + "redistributed_routes": [ + {"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], }, ] }, @@ -5134,7 +5158,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, { "name": "failure-afi-safi-config-not-found", - "test": VerifyBGPRedistributedRoutes, + "test": VerifyBGPRedistribution, "eos_data": [ { "vrfs": { @@ -5165,7 +5189,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, { "name": "failure-expected-proto-not-found", - "test": VerifyBGPRedistributedRoutes, + "test": VerifyBGPRedistribution, "eos_data": [ { "vrfs": { @@ -5212,7 +5236,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, { "name": "failure-route-map-not-found", - "test": VerifyBGPRedistributedRoutes, + "test": VerifyBGPRedistribution, "eos_data": [ { "vrfs": { diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index 65e179c7f..a4bf7d8b9 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -6,12 +6,12 @@ # pylint: disable=C0302 from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import pytest from pydantic import ValidationError -from anta.input_models.routing.bgp import BgpAddressFamily, BgpPeer +from anta.input_models.routing.bgp import AddressFamilyConfig, BgpAddressFamily, BgpPeer, RedistributedRoute from anta.tests.routing.bgp import ( VerifyBGPExchangedRoutes, VerifyBGPNlriAcceptance, @@ -19,14 +19,13 @@ VerifyBGPPeerGroup, VerifyBGPPeerMPCaps, VerifyBGPPeerRouteLimit, - VerifyBGPRedistributedRoutes, VerifyBgpRouteMaps, VerifyBGPSpecificPeers, VerifyBGPTimers, ) if TYPE_CHECKING: - from anta.custom_types import Afi, Safi + from anta.custom_types import Afi, RedisrbutedAfiSafi, RedistributedProtocol, Safi class TestBgpAddressFamily: @@ -291,46 +290,67 @@ def test_invalid(self, bgp_peers: list[BgpPeer]) -> None: VerifyBGPNlriAcceptance.Input(bgp_peers=bgp_peers) -class TestVerifyBGPRedistributedRoutes: - """Test anta.tests.routing.bgp.VerifyBGPRedistributedRoutes.Input.""" +class TestVerifyBGPAddressFamilyConfig: + """Test anta.tests.routing.bgp.AddressFamilyConfig.Input.""" @pytest.mark.parametrize( - ("address_families"), + ("afi_safi", "redistributed_routes"), [ pytest.param( - [{"afi": "ipv4", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], id="ipv4-valid" + [({"afisafi": "ipv4Unicast"}, {"redistributed_routes": [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}]})], + id="afisafi-ipv4-valid", ), pytest.param( - [{"afi": "ipv6", "safi": "multicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], id="ipv6-valid" + [({"afisafi": "ipv6 Multicast"}, {"redistributed_routes": [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}]})], + id="afisafi-ipv6-valid", ), ], ) - def test_valid(self, address_families: list[BgpAddressFamily]) -> None: - """Test VerifyBGPRedistributedRoutes.Input valid inputs.""" - VerifyBGPRedistributedRoutes.Input(address_families=address_families) + def test_valid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[Any]) -> None: + """Test AddressFamilyConfig valid inputs.""" + AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) @pytest.mark.parametrize( - ("address_families"), + ("afi_safi", "redistributed_routes"), [ pytest.param( - [{"afi": "ipv4", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": None, "route_map": "RM-CONN-2-BGP"}], - id="invalid-redistributed-route-protocol", + [({"afi_safi": "evpn"}, {"redistributed_routes": [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}]})], + id="invalid-address-family", ), pytest.param( - [{"afi": "ipv6", "safi": "multicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": None}], - id="invalid-route-map", - ), - pytest.param( - [{"afi": "evpn", "safi": "unicast", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], - id="invalid-afi-for-redistributed-route", - ), - pytest.param( - [{"afi": "ipv6", "safi": "sr-te", "vrf": "default", "redistributed_route_protocol": "Connected", "route_map": "RM-CONN-2-BGP"}], - id="invalid-safi-for-redistributed-route", + [({"afi_safi": "ipv6 sr-te"}, {"redistributed_routes": [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}]})], + id="ipv6-invalid-address-family", ), ], ) - def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: - """Test VerifyBGPRedistributedRoutes.Input invalid inputs.""" + def test_invalid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[Any]) -> None: + """Test AddressFamilyConfig invalid inputs.""" + with pytest.raises(ValidationError): + AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) + + +class TestVerifyBGPRedistributedRoute: + """Test anta.tests.routing.bgp.RedistributedRoute.Input.""" + + @pytest.mark.parametrize( + ("proto", "include_leaked"), + [ + pytest.param([{"proto": "Connected", "include_leaked": True}], id="proto-valid"), + pytest.param([{"proto": "Static", "include_leaked": False}], id="proto-valid-leaked-false"), + ], + ) + def test_valid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: + """Test RedistributedRoute valid inputs.""" + RedistributedRoute(proto=proto, include_leaked=include_leaked) + + @pytest.mark.parametrize( + ("proto", "include_leaked"), + [ + pytest.param([{"proto": "Dynamic", "include_leaked": True}], id="proto-valid"), + pytest.param([{"proto": "User", "include_leaked": False}], id="proto-valid-leaked-false"), + ], + ) + def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: + """Test RedistributedRoute invalid inputs.""" with pytest.raises(ValidationError): - VerifyBGPRedistributedRoutes.Input(address_families=address_families) + RedistributedRoute(proto=proto, include_leaked=include_leaked) From f4cb68f1a1792f5e23d624a9916007f11ffd877b Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Thu, 23 Jan 2025 18:28:08 +0000 Subject: [PATCH 09/18] Updated testcase with docstrings and Unit testcases --- anta/custom_types.py | 46 +++-- anta/input_models/routing/bgp.py | 49 +++-- anta/tests/routing/bgp.py | 75 ++----- tests/units/anta_tests/routing/test_bgp.py | 205 ++++++++++++++----- tests/units/input_models/routing/test_bgp.py | 69 +++---- 5 files changed, 274 insertions(+), 170 deletions(-) diff --git a/anta/custom_types.py b/anta/custom_types.py index b343f0b61..5d8fe1dca 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -141,6 +141,19 @@ def bgp_redistributed_route_proto_abbreviations(value: str) -> str: return value +def update_bgp_redistributed_proto_user(value: str) -> str: + """Update BGP redistributed route `User` proto with EOS SDK. + + Examples + -------- + - User + """ + if value == "User": + value = "EOS SDK" + + return value + + # AntaTest.Input types AAAAuthMethod = Annotated[str, AfterValidator(aaa_group_prefix)] Vlan = Annotated[int, Field(ge=0, le=4094)] @@ -291,20 +304,23 @@ def bgp_redistributed_route_proto_abbreviations(value: str) -> str: SnmpEncryptionAlgorithm = Literal["AES-128", "AES-192", "AES-256", "DES"] DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"] LogSeverityLevel = Literal["alerts", "critical", "debugging", "emergencies", "errors", "informational", "notifications", "warnings"] -RedistributedProtocol = Literal[ - "AttachedHost", - "Bgp", - "Connected", - "Dynamic", - "IS-IS", - "OSPF Internal", - "OSPF External", - "OSPF Nssa-External", - "OSPFv3 Internal", - "OSPFv3 External", - "OSPFv3 Nssa-External", - "RIP", - "Static", - "User", +RedistributedProtocol = Annotated[ + Literal[ + "AttachedHost", + "Bgp", + "Connected", + "Dynamic", + "IS-IS", + "OSPF Internal", + "OSPF External", + "OSPF Nssa-External", + "OSPFv3 Internal", + "OSPFv3 External", + "OSPFv3 Nssa-External", + "RIP", + "Static", + "User", + ], + AfterValidator(update_bgp_redistributed_proto_user), ] RedisrbutedAfiSafi = Annotated[str, BeforeValidator(bgp_redistributed_route_proto_abbreviations)] diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 3e01cb814..88d356ff3 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -267,6 +267,24 @@ def __str__(self) -> str: return f"Next-hop: {self.nexthop} Origin: {self.origin}" +class BgpVrf(BaseModel): + """Model representing BGP vrfs.""" + + vrf: str = "default" + """VRF for the BGP instance. Defaults to `default`.""" + address_families: list[AddressFamilyConfig] + """list of address family configuration.""" + + def __str__(self) -> str: + """Return a human-readable string representation of the BgpVrf for reporting. + + Examples + -------- + - VRF: default + """ + return f"VRF: {self.vrf}" + + class RedistributedRoute(BaseModel): """Model representing BGP redistributed route.""" @@ -280,11 +298,25 @@ class RedistributedRoute(BaseModel): @model_validator(mode="after") def validate_include_leaked_support(self) -> Self: """Validate the input provided for included leaked field, included _leaked this field is not supported for proto AttachedHost, User, Dynamic, RIP.""" - if self.include_leaked and self.proto in ["AttachedHost", "User", "Dynamic", "RIP"]: + if self.include_leaked and self.proto in ["AttachedHost", "EOS SDK", "Dynamic", "RIP"]: msg = f"{self.include_leaked}, field is not supported for redistributed route protocol `{self.proto}`" raise ValueError(msg) return self + def __str__(self) -> str: + """Return a human-readable string representation of the RedistributedRoute for reporting. + + Examples + -------- + - Proto: Connected, Included Leaked: False, Route Map: RM-CONN-2-BGP + """ + base_string = f"Proto: {self.proto}" + if self.include_leaked is not None: + base_string += f", Included Leaked: {self.include_leaked}" + if self.route_map is not None: + base_string += f", Route Map: {self.route_map}" + return base_string + class AddressFamilyConfig(BaseModel): """Model representing BGP address family configs.""" @@ -305,20 +337,11 @@ def validate_inputs(self) -> Self: raise ValueError(msg) return self - -class BgpVrf(BaseModel): - """Model representing BGP vrfs.""" - - vrf: str = "default" - """VRF for the BGP instance. Defaults to `default`.""" - address_families: list[AddressFamilyConfig] - """list of address family configuration.""" - def __str__(self) -> str: - """Return a human-readable string representation of the BgpVrf for reporting. + """Return a human-readable string representation of the AddressFamilyConfig for reporting. Examples -------- - - VRF: default + - AFI-SAFI: v4u """ - return f"VRF: {self.vrf}" + return f"AFI-SAFI: {self.afi_safi}" diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 9899b4231..cb00efe18 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -7,7 +7,7 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations -from typing import Any, ClassVar, TypeVar +from typing import ClassVar, TypeVar from pydantic import field_validator @@ -43,55 +43,6 @@ def _check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool: return all(capability_status.get(state, False) for state in ("advertised", "received", "enabled")) -def _check_bgp_route_redistribution(vrf: str, address_family: list[Any], cmd_output: dict[str, Any]) -> str: - """Check if bgp route proto, included_leaked, routemap values matches the expected values. - - Parameters - ---------- - vrf - A VRF name - address_family - A list containing the bgp address family details. - cmd_output - A dictionary containing output of `show bgp instance vrf all` - - Returns - ------- - str: - Test failure messages. - Example - ------- - >>> _check_bgp_route_redistribution(default, [ - { "afi_safi": "ipv4Unicast","redistributed_routes": ["proto": "Connected","include_leaked": True,"route_map": "RM-CONN-2-BGP"}], - "show bgp instance vrf all" command output) - - """ - failure_result = "" - for route_info in address_family.redistributed_routes: - proto = "EOS SDK" if route_info.proto == "User" else route_info.proto - afi_safi_key = address_family.afi_safi - output_msg_str = f"VRF: {vrf}, AFISAFI: {afi_safi_key}, Protocol: {route_info.proto}" - # If the specified VRF, AFI-SAFI details are not found, test fails. - if not (afi_safi_configs := get_value(cmd_output, f"vrfs.{vrf}.afiSafiConfig.{afi_safi_key}")): - failure_result += f"VRF: {vrf}, AFISAFI: {afi_safi_key} - Not found" - continue - - # If the redistributed route protocol does not match the expected value, test fails. - if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", proto)): - failure_result += f"{output_msg_str} - Not Found" - continue - - # If includes leaked field applicable, and it does not matches the expected value, test fails. - if route_info.include_leaked and (act_include_leaked := actual_route.get("includeLeaked")) != route_info.include_leaked: - failure_result += f"{output_msg_str} - Value for included leaked mismatch - Expected :{route_info.include_leaked}, Actual: {act_include_leaked}" - - # If route map is required and it is not matching the expected value, test fails. - if route_info.route_map and (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map: - failure_result += f"{output_msg_str} - Route map mismatch - Expected: {route_info.route_map} Actual: {act_route_map}" - - return failure_result - - class VerifyBGPPeerCount(AntaTest): """Verifies the count of BGP peers for given address families. @@ -1745,7 +1696,7 @@ class VerifyBGPRedistribution(AntaTest): This test performs the following checks for each specified route: 1. Ensures that the expected address-family is configured on the device. - 2. Confirms that the redistributed route protocol and route map match the expected values for a route. + 2. Confirms that the redistributed route protocol, included leaked and route map match the expected values for a route. Note: For "User" proto field, checking that it's "EOS SDK" versus User. @@ -1753,10 +1704,10 @@ class VerifyBGPRedistribution(AntaTest): ---------------- * Success: If all of the following conditions are met: - The expected address-family is configured on the device. - - The redistributed route protocol and route map align with the expected values for the route. + - The redistributed route protocol, included leaked and route map align with the expected values for the route. * Failure: If any of the following occur: - The expected address-family is not configured on device. - - The redistributed route protocol or route map does not match the expected value for a route. + - The redistributed route protocolor, included leaked or route map does not match the expected value for a route. Examples -------- @@ -1802,6 +1753,20 @@ def test(self) -> None: for vrf_data in self.inputs.vrfs: for address_family in vrf_data.address_families: - failure_messages = _check_bgp_route_redistribution(vrf=vrf_data.vrf, address_family=address_family, cmd_output=command_output) - self.result.is_failure(failure_messages) + # If the specified VRF, AFI-SAFI details are not found, test fails. + if not (afi_safi_configs := get_value(command_output, f"vrfs.{vrf_data.vrf}.afiSafiConfig.{address_family.afi_safi}")): + self.result.is_failure(f"{vrf_data}, {address_family} - Not configured") + continue + for route_info in address_family.redistributed_routes: + # If the redistributed route protocol does not match the expected value, test fails. + if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", route_info.proto)): + self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Not configured") + continue + + # If includes leaked field applicable, and it does not matches the expected value, test fails. + if all([route_info.include_leaked is not None, (act_include_leaked := actual_route.get("includeLeaked")) != route_info.include_leaked]): + self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Value for included leaked mismatch - Actual: {act_include_leaked}") + # If route map is required and it is not matching the expected value, test fails. + if all([route_info.route_map, (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map]): + self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Route map mismatch - Actual: {act_route_map}") diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 8d71f4df9..fc41c5b7a 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -5107,8 +5107,38 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "eos_data": [ { "vrfs": { - "default": {"afiSafiConfig": {"v4u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, - "test": {"afiSafiConfig": {"v6u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, + "default": { + "afiSafiConfig": { + "v4u": { + "redistributedRoutes": [ + {"proto": "Connected", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"}, + {"proto": "Static", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"}, + ] + }, + "v6m": { + "redistributedRoutes": [ + {"proto": "Dynamic", "routeMap": "RM-CONN-2-BGP"}, + {"proto": "IS-IS", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"}, + ] + }, + } + }, + "test": { + "afiSafiConfig": { + "v4u": { + "redistributedRoutes": [ + {"proto": "EOS SDK", "routeMap": "RM-CONN-2-BGP"}, + {"proto": "OSPF Internal", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"}, + ] + }, + "v6m": { + "redistributedRoutes": [ + {"proto": "RIP", "routeMap": "RM-CONN-2-BGP"}, + {"proto": "Bgp", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"}, + ] + }, + } + }, } } ], @@ -5125,10 +5155,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], }, { - "afi_safi": "IPv6 Unicast", + "afi_safi": "IPv6 multicast", "redistributed_routes": [ {"proto": "Dynamic", "route_map": "RM-CONN-2-BGP"}, - {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + {"proto": "IS-IS", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, ], }, ], @@ -5137,17 +5167,17 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "vrf": "test", "address_families": [ { - "afi_safi": "ipv4Unicast", + "afi_safi": "ipv4 Unicast", "redistributed_routes": [ - {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, - {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + {"proto": "User", "route_map": "RM-CONN-2-BGP"}, + {"proto": "OSPF Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, ], }, { - "afi_safi": "IPv6 Unicast", + "afi_safi": "IPv6Multicast", "redistributed_routes": [ + {"proto": "RIP", "route_map": "RM-CONN-2-BGP"}, {"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, - {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, ], }, ], @@ -5168,24 +5198,22 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ], "inputs": { - "address_families": [ + "vrfs": [ { "vrf": "default", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv4", - "safi": "unicast", - }, - { - "vrf": "test", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv6", - "safi": "multicast", + "address_families": [ + { + "afi_safi": "ipv4Unicast", + "redistributed_routes": [ + {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], }, ] }, - "expected": {"result": "failure", "messages": ["AFI: ipv4 SAFI: unicast VRF: default - Not found"]}, + "expected": {"result": "failure", "messages": ["VRF: default, AFI-SAFI: v4u - Not configured"]}, }, { "name": "failure-expected-proto-not-found", @@ -5209,75 +5237,158 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ], "inputs": { - "address_families": [ + "vrfs": [ { "vrf": "default", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv4", - "safi": "multicast", + "address_families": [ + { + "afi_safi": "ipv4 multicast", + "redistributed_routes": [ + {"proto": "OSPFv3 External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + {"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + } + ], }, { "vrf": "test", - "redistributed_route_protocol": "User", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv6", - "safi": "multicast", + "address_families": [ + { + "afi_safi": "IPv6Unicast", + "redistributed_routes": [ + {"proto": "RIP", "route_map": "RM-CONN-2-BGP"}, + {"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], }, ] }, "expected": { "result": "failure", "messages": [ - "AFI: ipv4 SAFI: multicast VRF: default Protocol: Connected - Not Found", - "AFI: ipv6 SAFI: multicast VRF: test Protocol: User - Not Found", + "VRF: default, AFI-SAFI: v4m, Proto: OSPFv3 External, Included Leaked: True, Route Map: RM-CONN-2-BGP - Not configured", + "VRF: default, AFI-SAFI: v4m, Proto: OSPFv3 Nssa-External, Included Leaked: True, Route Map: RM-CONN-2-BGP - Not configured", + "VRF: test, AFI-SAFI: v6u - Not configured", ], }, }, { "name": "failure-route-map-not-found", "test": VerifyBGPRedistribution, + "eos_data": [ + { + "vrfs": { + "default": {"afiSafiConfig": {"v4u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-10-BGP"}, {"proto": "Static"}]}}}, + "test": { + "afiSafiConfig": { + "v6u": { + "redistributedRoutes": [{"proto": "EOS SDK", "routeMap": "RM-MLAG-PEER-IN"}, {"proto": "OSPF Internal"}], + } + } + }, + } + } + ], + "inputs": { + "vrfs": [ + { + "vrf": "default", + "address_families": [ + { + "afi_safi": "ipv4 UNicast", + "redistributed_routes": [ + {"proto": "Connected", "route_map": "RM-CONN-2-BGP"}, + {"proto": "Static", "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], + }, + { + "vrf": "test", + "address_families": [ + { + "afi_safi": "ipv6 Unicast", + "redistributed_routes": [ + {"proto": "User", "route_map": "RM-CONN-2-BGP"}, + {"proto": "OSPF Internal", "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], + }, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "VRF: default, AFI-SAFI: v4u, Proto: Connected, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: RM-CONN-10-BGP", + "VRF: default, AFI-SAFI: v4u, Proto: Static, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: Not Found", + "VRF: test, AFI-SAFI: v6u, Proto: EOS SDK, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: RM-MLAG-PEER-IN", + "VRF: test, AFI-SAFI: v6u, Proto: OSPF Internal, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: Not Found", + ], + }, + }, + { + "name": "failure-incorrect-value-include-leaked", + "test": VerifyBGPRedistribution, "eos_data": [ { "vrfs": { "default": { "afiSafiConfig": { - "v4m": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-10-BGP"}, {"proto": "IS-IS", "routeMap": "RM-MLAG-PEER-IN"}]} + "v4m": { + "redistributedRoutes": [ + {"proto": "Dynamic", "routeMap": "RM-CONN-2-BGP"}, + {"proto": "IS-IS", "includeLeaked": False, "routeMap": "RM-CONN-2-BGP"}, + ] + }, } }, "test": { "afiSafiConfig": { "v6u": { - "redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-MLAG-PEER-IN"}], - } + "redistributedRoutes": [ + {"proto": "RIP", "routeMap": "RM-CONN-2-BGP"}, + {"proto": "Bgp", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"}, + ] + }, } }, } } ], "inputs": { - "address_families": [ + "vrfs": [ { "vrf": "default", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv4", - "safi": "multicast", + "address_families": [ + { + "afi_safi": "ipv4multicast", + "redistributed_routes": [ + {"proto": "IS-IS", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], }, { "vrf": "test", - "redistributed_route_protocol": "Connected", - "route_map": "RM-CONN-2-BGP", - "afi": "ipv6", - "safi": "unicast", + "address_families": [ + { + "afi_safi": "IPv6unicast", + "redistributed_routes": [ + {"proto": "RIP", "route_map": "RM-CONN-2-BGP"}, + {"proto": "Bgp", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], }, ] }, "expected": { "result": "failure", "messages": [ - "AFI: ipv4 SAFI: multicast VRF: default Protocol: Connected - Route map mismatch - Expected: RM-CONN-2-BGP Actual: RM-CONN-10-BGP", - "AFI: ipv6 SAFI: unicast VRF: test Protocol: Connected - Route map mismatch - Expected: RM-CONN-2-BGP Actual: RM-MLAG-PEER-IN", + "VRF: default, AFI-SAFI: v4m, Proto: IS-IS, Included Leaked: True, Route Map: RM-CONN-2-BGP - Value for included leaked mismatch - Actual: False", + "VRF: test, AFI-SAFI: v6u, Proto: Bgp, Included Leaked: False, Route Map: RM-CONN-2-BGP - Value for included leaked mismatch - Actual: True", ], }, }, diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index a4bf7d8b9..5593b65f7 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -290,67 +290,56 @@ def test_invalid(self, bgp_peers: list[BgpPeer]) -> None: VerifyBGPNlriAcceptance.Input(bgp_peers=bgp_peers) -class TestVerifyBGPAddressFamilyConfig: - """Test anta.tests.routing.bgp.AddressFamilyConfig.Input.""" +class TestVerifyBGPRedistributedRoute: + """Test anta.tests.routing.bgp.RedistributedRoute.Input.""" @pytest.mark.parametrize( - ("afi_safi", "redistributed_routes"), + ("proto", "include_leaked"), [ - pytest.param( - [({"afisafi": "ipv4Unicast"}, {"redistributed_routes": [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}]})], - id="afisafi-ipv4-valid", - ), - pytest.param( - [({"afisafi": "ipv6 Multicast"}, {"redistributed_routes": [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}]})], - id="afisafi-ipv6-valid", - ), + pytest.param("Connected", True, id="proto-valid"), + pytest.param("Static", False, id="proto-valid-leaked-false"), + pytest.param("User", False, id="proto-User"), ], ) - def test_valid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[Any]) -> None: - """Test AddressFamilyConfig valid inputs.""" - AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) + def test_valid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: + """Test RedistributedRoute valid inputs.""" + RedistributedRoute(proto=proto, include_leaked=include_leaked) @pytest.mark.parametrize( - ("afi_safi", "redistributed_routes"), + ("proto", "include_leaked"), [ - pytest.param( - [({"afi_safi": "evpn"}, {"redistributed_routes": [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}]})], - id="invalid-address-family", - ), - pytest.param( - [({"afi_safi": "ipv6 sr-te"}, {"redistributed_routes": [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}]})], - id="ipv6-invalid-address-family", - ), + pytest.param("Dynamic", True, id="proto-valid"), + pytest.param("User", True, id="proto-valid-leaked-false"), ], ) - def test_invalid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[Any]) -> None: - """Test AddressFamilyConfig invalid inputs.""" + def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: + """Test RedistributedRoute invalid inputs.""" with pytest.raises(ValidationError): - AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) + RedistributedRoute(proto=proto, include_leaked=include_leaked) -class TestVerifyBGPRedistributedRoute: - """Test anta.tests.routing.bgp.RedistributedRoute.Input.""" +class TestVerifyBGPAddressFamilyConfig: + """Test anta.tests.routing.bgp.AddressFamilyConfig.Input.""" @pytest.mark.parametrize( - ("proto", "include_leaked"), + ("afi_safi", "redistributed_routes"), [ - pytest.param([{"proto": "Connected", "include_leaked": True}], id="proto-valid"), - pytest.param([{"proto": "Static", "include_leaked": False}], id="proto-valid-leaked-false"), + pytest.param("ipv4Unicast", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-valid"), + pytest.param("ipv6 Multicast", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-valid"), ], ) - def test_valid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: - """Test RedistributedRoute valid inputs.""" - RedistributedRoute(proto=proto, include_leaked=include_leaked) + def test_valid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[Any]) -> None: + """Test AddressFamilyConfig valid inputs.""" + AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) @pytest.mark.parametrize( - ("proto", "include_leaked"), + ("afi_safi", "redistributed_routes"), [ - pytest.param([{"proto": "Dynamic", "include_leaked": True}], id="proto-valid"), - pytest.param([{"proto": "User", "include_leaked": False}], id="proto-valid-leaked-false"), + pytest.param("evpn", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="invalid-address-family"), + pytest.param("ipv6 sr-te", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-invalid-address-family"), ], ) - def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: - """Test RedistributedRoute invalid inputs.""" + def test_invalid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[Any]) -> None: + """Test AddressFamilyConfig invalid inputs.""" with pytest.raises(ValidationError): - RedistributedRoute(proto=proto, include_leaked=include_leaked) + AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) From aeda3fe7df88479a23a34cd6c3eca6b4e438a504 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Wed, 5 Feb 2025 07:38:55 +0000 Subject: [PATCH 10/18] Added unit test for str function, fixed congnetive complexity issue --- anta/input_models/routing/bgp.py | 9 --- anta/tests/routing/bgp.py | 68 +++++++++++--------- tests/units/input_models/routing/test_bgp.py | 12 ++++ 3 files changed, 48 insertions(+), 41 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 88d356ff3..2ccb4ed21 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -40,10 +40,6 @@ } """Dictionary mapping AFI/SAFI to EOS key representation.""" -AFI_SAFI_REDISTRIBUTED_ROUTE_KEY = {"ipv4Unicast": "v4u", "ipv4Multicast": "v4m", "ipv6Unicast": "v6u", "ipv6Multicast": "v6m"} - -"""Dictionary mapping of AFI/SAFI to redistributed routes key representation.""" - class BgpAddressFamily(BaseModel): """Model for a BGP address family.""" @@ -103,11 +99,6 @@ def eos_key(self) -> str: # Pydantic handles the validation of the AFI/SAFI combination, so we can ignore error handling here. return AFI_SAFI_EOS_KEY[(self.afi, self.safi)] - @property - def redistributed_route_key(self) -> str: - """AFI/SAFI Redistributed route key representation.""" - return AFI_SAFI_REDISTRIBUTED_ROUTE_KEY[self.eos_key] - def __str__(self) -> str: """Return a human-readable string representation of the BgpAddressFamily for reporting. diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index db618dfaa..ddf76676d 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -7,7 +7,7 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations -from typing import ClassVar, TypeVar +from typing import Any, ClassVar, TypeVar from pydantic import field_validator @@ -1716,24 +1716,24 @@ class VerifyBGPRedistribution(AntaTest): anta.tests.routing: bgp: - VerifyBGPRedistribution: - vrfs: - - vrf: default - address_families: - - afi_safi: ipv4Unicast - redistributed_routes: - - proto: Connected - include_leaked: True - route_map: RM-CONN-2-BGP - - proto: Static - include_leaked: True - route_map: RM-CONN-2-BGP - - afi_safi: IPv6 Unicast - redistributed_routes: - - proto: Dynamic - route_map: RM-CONN-2-BGP - - proto: Static - include_leaked: True - route_map: RM-CONN-2-BGP + vrfs: + - vrf: default + address_families: + - afi_safi: ipv4Unicast + redistributed_routes: + - proto: Connected + include_leaked: True + route_map: RM-CONN-2-BGP + - proto: Static + include_leaked: True + route_map: RM-CONN-2-BGP + - afi_safi: IPv6 Unicast + redistributed_routes: + - proto: Dynamic + route_map: RM-CONN-2-BGP + - proto: Static + include_leaked: True + route_map: RM-CONN-2-BGP ``` """ @@ -1746,6 +1746,22 @@ class Input(AntaTest.Input): vrfs: list[BgpVrf] """List of address families.""" + def _validate_redistribute_route_details(self, vrf_data: str, address_family: dict[str, Any], afi_safi_configs: list[dict[str, Any]]) -> None: + """Validate the redstributed route details for a given address family.""" + for route_info in address_family.redistributed_routes: + # If the redistributed route protocol does not match the expected value, test fails. + if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", route_info.proto)): + self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Not configured") + continue + + # If includes leaked field applicable, and it does not matches the expected value, test fails. + if all([route_info.include_leaked is not None, (act_include_leaked := actual_route.get("includeLeaked", "Not Found")) != route_info.include_leaked]): + self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Value for included leaked mismatch - Actual: {act_include_leaked}") + + # If route map is required and it is not matching the expected value, test fails. + if all([route_info.route_map, (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map]): + self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Route map mismatch - Actual: {act_route_map}") + @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPRedistribution.""" @@ -1758,16 +1774,4 @@ def test(self) -> None: if not (afi_safi_configs := get_value(command_output, f"vrfs.{vrf_data.vrf}.afiSafiConfig.{address_family.afi_safi}")): self.result.is_failure(f"{vrf_data}, {address_family} - Not configured") continue - for route_info in address_family.redistributed_routes: - # If the redistributed route protocol does not match the expected value, test fails. - if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", route_info.proto)): - self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Not configured") - continue - - # If includes leaked field applicable, and it does not matches the expected value, test fails. - if all([route_info.include_leaked is not None, (act_include_leaked := actual_route.get("includeLeaked")) != route_info.include_leaked]): - self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Value for included leaked mismatch - Actual: {act_include_leaked}") - - # If route map is required and it is not matching the expected value, test fails. - if all([route_info.route_map, (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map]): - self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Route map mismatch - Actual: {act_route_map}") + self._validate_redistribute_route_details(str(vrf_data), address_family, afi_safi_configs) diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index 5593b65f7..e0a620de3 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -317,6 +317,18 @@ def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> No with pytest.raises(ValidationError): RedistributedRoute(proto=proto, include_leaked=include_leaked) + @pytest.mark.parametrize( + ("proto", "include_leaked", "route_map", "expected"), + [ + pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Included Leaked: True, Route Map: RM-CONN-2-BGP", id="check-all-params"), + pytest.param("Static", False, None, "Proto: Static, Included Leaked: False", id="check-proto-include_leaked"), + pytest.param("Bgp", None, "RM-CONN-2-BGP", "Proto: Bgp, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"), + ], + ) + def test_valid_str(self, proto: RedistributedProtocol, include_leaked: bool | None, route_map: str | None, expected: str) -> None: + """Test RedistributedRoute __str__.""" + assert str(RedistributedRoute(proto=proto, include_leaked=include_leaked, route_map=route_map)) == expected + class TestVerifyBGPAddressFamilyConfig: """Test anta.tests.routing.bgp.AddressFamilyConfig.Input.""" From 4e32a6e6bdd3893363686adbbd940fa8485d68c9 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Mon, 10 Feb 2025 11:15:20 +0000 Subject: [PATCH 11/18] Optimized test code, updated docstrings and UT --- anta/custom_types.py | 46 +++++++----- anta/input_models/routing/bgp.py | 73 ++++++++++---------- anta/tests/routing/bgp.py | 50 ++++++++------ examples/tests.yaml | 2 +- tests/units/anta_tests/routing/test_bgp.py | 22 +++--- tests/units/input_models/routing/test_bgp.py | 26 +++---- 6 files changed, 120 insertions(+), 99 deletions(-) diff --git a/anta/custom_types.py b/anta/custom_types.py index 8417f844f..aa36d2fc7 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -25,10 +25,10 @@ # Regular expression for BGP redistributed routes -REGEX_IPV4_UNICAST = r"\bipv4[\s]?unicast\b" -REGEX_IPV4_MULTICAST = r"\bipv4[\s]?multicast\b" -REGEX_IPV6_UNICAST = r"\bipv6[\s]?unicast\b" -REGEX_IPV6_MULTICAST = r"\bipv6[\s]?multicast\b" +REGEX_IPV4_UNICAST = r"ipv4[-_ ]?unicast$" +REGEX_IPV4_MULTICAST = r"ipv4[-_ ]?multicast$" +REGEX_IPV6_UNICAST = r"ipv6[-_ ]?unicast$" +REGEX_IPV6_MULTICAST = r"ipv6[-_ ]?multicast$" def aaa_group_prefix(v: str) -> str: @@ -99,10 +99,10 @@ def bgp_multiprotocol_capabilities_abbreviations(value: str) -> str: patterns = { f"{r'dynamic[-_ ]?path[-_ ]?selection$'}": "dps", f"{r'dps$'}": "dps", - f"{r'ipv4[-_ ]?unicast$'}": "ipv4Unicast", - f"{r'ipv6[-_ ]?unicast$'}": "ipv6Unicast", - f"{r'ipv4[-_ ]?multicast$'}": "ipv4Multicast", - f"{r'ipv6[-_ ]?multicast$'}": "ipv6Multicast", + f"{REGEX_IPV4_UNICAST}": "ipv4Unicast", + f"{REGEX_IPV6_UNICAST}": "ipv6Unicast", + f"{REGEX_IPV4_MULTICAST}": "ipv4Multicast", + f"{REGEX_IPV6_MULTICAST}": "ipv6Multicast", f"{r'ipv4[-_ ]?labeled[-_ ]?Unicast$'}": "ipv4MplsLabels", f"{r'ipv4[-_ ]?mpls[-_ ]?labels$'}": "ipv4MplsLabels", f"{r'ipv6[-_ ]?labeled[-_ ]?Unicast$'}": "ipv6MplsLabels", @@ -142,18 +142,25 @@ def validate_regex(value: str) -> str: def bgp_redistributed_route_proto_abbreviations(value: str) -> str: """Abbreviations for different BGP redistributed route protocols. + Handles different separators (hyphen, underscore, space) and case sensitivity. + Examples -------- - - IPv4 Unicast - - ipv4Unicast - - IPv4 Multicast - - ipv4Multicast - + ```python + >>> bgp_redistributed_route_proto_abbreviations("IPv4 Unicast") + 'v4u' + >>> bgp_redistributed_route_proto_abbreviations("IPv4-multicast") + 'v4m' + >>> bgp_redistributed_route_proto_abbreviations("IPv6_multicast") + 'v6m' + >>> bgp_redistributed_route_proto_abbreviations("ipv6unicast") + 'v6u' + ``` """ patterns = {REGEX_IPV4_UNICAST: "v4u", REGEX_IPV4_MULTICAST: "v4m", REGEX_IPV6_UNICAST: "v6u", REGEX_IPV6_MULTICAST: "v6m"} for pattern, replacement in patterns.items(): - match = re.search(pattern, value, re.IGNORECASE) + match = re.match(pattern, value, re.IGNORECASE) if match: return replacement @@ -165,7 +172,14 @@ def update_bgp_redistributed_proto_user(value: str) -> str: Examples -------- - - User + ```python + >>> update_bgp_redistributed_proto_user("User") + 'EOS SDK' + >>> update_bgp_redistributed_proto_user("Bgp") + 'Bgp' + >>> update_bgp_redistributed_proto_user("RIP") + 'RIP' + ``` """ if value == "User": value = "EOS SDK" @@ -379,4 +393,4 @@ def snmp_v3_prefix(auth_type: Literal["auth", "priv", "noauth"]) -> str: ], AfterValidator(update_bgp_redistributed_proto_user), ] -RedisrbutedAfiSafi = Annotated[str, BeforeValidator(bgp_redistributed_route_proto_abbreviations)] +RedistributedAfiSafi = Annotated[Literal["v4u", "v4m", "v6u", "v6m"], BeforeValidator(bgp_redistributed_route_proto_abbreviations)] diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 7ffcc31fc..271c29e5c 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator from pydantic_extra_types.mac_address import MacAddress -from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, RedisrbutedAfiSafi, RedistributedProtocol, Safi, Vni +from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, RedistributedAfiSafi, RedistributedProtocol, Safi, Vni if TYPE_CHECKING: import sys @@ -70,9 +70,6 @@ class BgpAddressFamily(BaseModel): Can be enabled in the `VerifyBGPPeerCount` tests.""" - route_map: str | None = None - """Specify redistributed route protocol route map. Required field in the `VerifyBGPRedistribution` test.""" - @model_validator(mode="after") def validate_inputs(self) -> Self: """Validate the inputs provided to the BgpAddressFamily class. @@ -261,12 +258,12 @@ def __str__(self) -> str: class BgpVrf(BaseModel): - """Model representing BGP vrfs.""" + """Model representing a VRF in a BGP instance.""" vrf: str = "default" - """VRF for the BGP instance. Defaults to `default`.""" + """VRF context.""" address_families: list[AddressFamilyConfig] - """list of address family configuration.""" + """List of address family configuration.""" def __str__(self) -> str: """Return a human-readable string representation of the BgpVrf for reporting. @@ -278,63 +275,63 @@ def __str__(self) -> str: return f"VRF: {self.vrf}" -class RedistributedRoute(BaseModel): - """Model representing BGP redistributed route.""" +class RedistributedRouteConfig(BaseModel): + """Model representing a BGP redistributed route configuration.""" proto: RedistributedProtocol - """The redistributed route protocol.""" + """he redistributed protocol.""" include_leaked: bool | None = None - """Flag to include leaked BGP routes in the advertisement""" + """Flag to include leaked routes of the redistributed protocol while redistributing.""" route_map: str | None = None - """The route map of the redistributed routes.""" + """Optional route map applied to the redistribution.""" @model_validator(mode="after") - def validate_include_leaked_support(self) -> Self: - """Validate the input provided for included leaked field, included _leaked this field is not supported for proto AttachedHost, User, Dynamic, RIP.""" + def validate_inputs(self) -> Self: + """Validate that 'include_leaked' is not set when the redistributed protocol is AttachedHost, User, Dynamic, or RIP.""" if self.include_leaked and self.proto in ["AttachedHost", "EOS SDK", "Dynamic", "RIP"]: - msg = f"{self.include_leaked}, field is not supported for redistributed route protocol `{self.proto}`" + msg = f"'include_leaked' field is not supported for redistributed protocol '{self.proto}'" raise ValueError(msg) return self def __str__(self) -> str: - """Return a human-readable string representation of the RedistributedRoute for reporting. + """Return a human-readable string representation of the RedistributedRouteConfig for reporting. Examples -------- - - Proto: Connected, Included Leaked: False, Route Map: RM-CONN-2-BGP + - Proto: Connected, Include Leaked: absent, Route Map: RM-CONN-2-BGP """ base_string = f"Proto: {self.proto}" - if self.include_leaked is not None: - base_string += f", Included Leaked: {self.include_leaked}" - if self.route_map is not None: + if not self.include_leaked and self.include_leaked is not None: + base_string += ", Include Leaked: absent" + if self.include_leaked: + base_string += ", Include Leaked: present" + if self.route_map: base_string += f", Route Map: {self.route_map}" return base_string class AddressFamilyConfig(BaseModel): - """Model representing BGP address family configs.""" + """Model representing a BGP address family configuration.""" - afi_safi: RedisrbutedAfiSafi - """BGP redistributed route supported address families""" - redistributed_routes: list[RedistributedRoute] - """A list of redistributed route""" - - @model_validator(mode="after") - def validate_inputs(self) -> Self: - """Validate the inputs provided to the AddressFamilyConfig class. - - address families must be `ipv4` or `ipv6` only, and sub address families can be `unicast` or `multicast`. - """ - if self.afi_safi not in ["v4u", "v4m", "v6u", "v6m"]: - msg = f"Redistributed route protocol is not supported for address family `{self.afi_safi}`" - raise ValueError(msg) - return self + afi_safi: RedistributedAfiSafi + """AFI/SAFI abbreviation per EOS.""" + redistributed_routes: list[RedistributedRouteConfig] + """List of redistributed route configuration.""" def __str__(self) -> str: """Return a human-readable string representation of the AddressFamilyConfig for reporting. Examples -------- - - AFI-SAFI: v4u + - AFI-SAFI: IPv4 Unicast """ - return f"AFI-SAFI: {self.afi_safi}" + base_string = "AFI-SAFI:" + if self.afi_safi == "v4u": + base_string =+ f"{base_string} IPv4 Unicast" + elif self.afi_safi == "v4m": + base_string =+ f"{base_string} IPv4 Multicast" + elif self.afi_safi == "v6u": + base_string =+f"{base_string} IPv6 Unicast" + elif self.afi_safi == "v6m": + base_string =+ f"{base_string} IPv4 Multicast" + return base_string diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index cbadf0405..44231e150 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1803,12 +1803,12 @@ def test(self) -> None: class VerifyBGPRedistribution(AntaTest): - """Verifies BGP redistributed routes protocol and route-map. + """Verifies BGP redistribution. - This test performs the following checks for each specified route: + This test performs the following checks for each specified VRF in the BGP instance: 1. Ensures that the expected address-family is configured on the device. - 2. Confirms that the redistributed route protocol, included leaked and route map match the expected values for a route. + 2. Confirms that the redistributed route protocol, include leaked and route map match the expected values for a route. Note: For "User" proto field, checking that it's "EOS SDK" versus User. @@ -1816,10 +1816,10 @@ class VerifyBGPRedistribution(AntaTest): ---------------- * Success: If all of the following conditions are met: - The expected address-family is configured on the device. - - The redistributed route protocol, included leaked and route map align with the expected values for the route. + - The redistributed route protocol, include leaked and route map align with the expected values for the route. * Failure: If any of the following occur: - The expected address-family is not configured on device. - - The redistributed route protocolor, included leaked or route map does not match the expected value for a route. + - The redistributed route protocolor, include leaked or route map does not match the expected value for a route. Examples -------- @@ -1855,23 +1855,23 @@ class Input(AntaTest.Input): """Input model for the VerifyBGPRedistribution test.""" vrfs: list[BgpVrf] - """List of address families.""" + """List of VRFs in the BGP instance.""" - def _validate_redistribute_route_details(self, vrf_data: str, address_family: dict[str, Any], afi_safi_configs: list[dict[str, Any]]) -> None: + def _validate_redistribute_route(self, vrf_data: str, addr_family: str, afi_safi_configs: list[dict[str, Any]], route_info: dict[str, Any]) -> str | None: """Validate the redstributed route details for a given address family.""" - for route_info in address_family.redistributed_routes: - # If the redistributed route protocol does not match the expected value, test fails. - if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", route_info.proto)): - self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Not configured") - continue + # If the redistributed route protocol does not match the expected value, test fails. + if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", route_info.proto)): + return f"{vrf_data}, {addr_family}, {route_info} - Not configured" - # If includes leaked field applicable, and it does not matches the expected value, test fails. - if all([route_info.include_leaked is not None, (act_include_leaked := actual_route.get("includeLeaked", "Not Found")) != route_info.include_leaked]): - self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Value for included leaked mismatch - Actual: {act_include_leaked}") + # If includes leaked field applicable, and it does not matches the expected value, test fails. + if all([route_info.include_leaked is not None, (act_include_leaked := actual_route.get("includeLeaked", False)) != route_info.include_leaked]): + act_include_leaked = "present" if act_include_leaked else "absent" + return f"{vrf_data}, {addr_family}, {route_info} - Value for include leaked mismatch - Actual: {act_include_leaked}" - # If route map is required and it is not matching the expected value, test fails. - if all([route_info.route_map, (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map]): - self.result.is_failure(f"{vrf_data}, {address_family}, {route_info} - Route map mismatch - Actual: {act_route_map}") + # If route map is required and it is not matching the expected value, test fails. + if all([route_info.route_map, (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map]): + return f"{vrf_data}, {addr_family}, {route_info} - Route map mismatch - Actual: {act_route_map}" + return None @AntaTest.anta_test def test(self) -> None: @@ -1880,9 +1880,17 @@ def test(self) -> None: command_output = self.instance_commands[0].json_output for vrf_data in self.inputs.vrfs: + # If the specified VRF details are not found, test fails. + if not (instance_details := get_value(command_output, f"vrfs.{vrf_data.vrf}")): + self.result.is_failure(f"{vrf_data} - Not configured") + continue for address_family in vrf_data.address_families: - # If the specified VRF, AFI-SAFI details are not found, test fails. - if not (afi_safi_configs := get_value(command_output, f"vrfs.{vrf_data.vrf}.afiSafiConfig.{address_family.afi_safi}")): + # If the AFI-SAFI configuration details are not found, test fails. + if not (afi_safi_configs := get_value(instance_details, f"afiSafiConfig.{address_family.afi_safi}")): self.result.is_failure(f"{vrf_data}, {address_family} - Not configured") continue - self._validate_redistribute_route_details(str(vrf_data), address_family, afi_safi_configs) + + for route_info in address_family.redistributed_routes: + failure_msg = self._validate_redistribute_route(str(vrf_data), str(address_family), afi_safi_configs, route_info) + if failure_msg: + self.result.is_failure(failure_msg) diff --git a/examples/tests.yaml b/examples/tests.yaml index b53198f1c..8b314bc8a 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -514,7 +514,7 @@ anta.tests.routing.bgp: # Verifies the health of all the BGP IPv4 peer(s). check_tcp_queues: True - VerifyBGPRedistribution: - # Verifies BGP redistributed routes protocol and route-map. + # Verifies BGP redistribution. vrfs: - vrf: default address_families: diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index bf3535bc4..76249e95f 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -5901,7 +5901,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, ] }, - "expected": {"result": "failure", "messages": ["VRF: default, AFI-SAFI: v4u - Not configured"]}, + "expected": {"result": "failure", "messages": ["VRF: default, AFI-SAFI: IPv4 Unicast - Not configured"]}, }, { "name": "failure-expected-proto-not-found", @@ -5955,9 +5955,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "VRF: default, AFI-SAFI: v4m, Proto: OSPFv3 External, Included Leaked: True, Route Map: RM-CONN-2-BGP - Not configured", - "VRF: default, AFI-SAFI: v4m, Proto: OSPFv3 Nssa-External, Included Leaked: True, Route Map: RM-CONN-2-BGP - Not configured", - "VRF: test, AFI-SAFI: v6u - Not configured", + "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: OSPFv3 External, Include Leaked: present, Route Map: RM-CONN-2-BGP - Not configured", + "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: OSPFv3 Nssa-External, Include Leaked: present, Route Map: RM-CONN-2-BGP - Not configured", + "VRF: test, AFI-SAFI: IPv6 Unicast - Not configured", ], }, }, @@ -6009,10 +6009,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "VRF: default, AFI-SAFI: v4u, Proto: Connected, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: RM-CONN-10-BGP", - "VRF: default, AFI-SAFI: v4u, Proto: Static, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: Not Found", - "VRF: test, AFI-SAFI: v6u, Proto: EOS SDK, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: RM-MLAG-PEER-IN", - "VRF: test, AFI-SAFI: v6u, Proto: OSPF Internal, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: Not Found", + "VRF: default, AFI-SAFI: IPv4 Unicast, Proto: Connected, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: RM-CONN-10-BGP", + "VRF: default, AFI-SAFI: IPv4 Unicast, Proto: Static, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: Not Found", + "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: EOS SDK, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: RM-MLAG-PEER-IN", + "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: OSPF Internal, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: Not Found", ], }, }, @@ -6075,8 +6075,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "VRF: default, AFI-SAFI: v4m, Proto: IS-IS, Included Leaked: True, Route Map: RM-CONN-2-BGP - Value for included leaked mismatch - Actual: False", - "VRF: test, AFI-SAFI: v6u, Proto: Bgp, Included Leaked: False, Route Map: RM-CONN-2-BGP - Value for included leaked mismatch - Actual: True", + "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: IS-IS, Include Leaked: present, Route Map: RM-CONN-2-BGP - Value for include leaked mismatch " + "- Actual: absent", + "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: Bgp, Include Leaked: absent, Route Map: RM-CONN-2-BGP - Value for include leaked mismatch " + "- Actual: present", ], }, }, diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index 2af8abb6a..61a318547 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -11,7 +11,7 @@ import pytest from pydantic import ValidationError -from anta.input_models.routing.bgp import AddressFamilyConfig, BgpAddressFamily, BgpPeer, BgpRoute, RedistributedRoute +from anta.input_models.routing.bgp import AddressFamilyConfig, BgpAddressFamily, BgpPeer, BgpRoute, RedistributedRouteConfig from anta.tests.routing.bgp import ( VerifyBGPExchangedRoutes, VerifyBGPNlriAcceptance, @@ -27,7 +27,7 @@ ) if TYPE_CHECKING: - from anta.custom_types import Afi, RedisrbutedAfiSafi, RedistributedProtocol, Safi + from anta.custom_types import Afi, RedistributedAfiSafi, RedistributedProtocol, Safi class TestBgpAddressFamily: @@ -351,7 +351,7 @@ def test_invalid(self, route_entries: list[BgpRoute]) -> None: class TestVerifyBGPRedistributedRoute: - """Test anta.tests.routing.bgp.RedistributedRoute.Input.""" + """Test anta.tests.routing.bgp.RedistributedRouteConfig.Input.""" @pytest.mark.parametrize( ("proto", "include_leaked"), @@ -362,8 +362,8 @@ class TestVerifyBGPRedistributedRoute: ], ) def test_valid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: - """Test RedistributedRoute valid inputs.""" - RedistributedRoute(proto=proto, include_leaked=include_leaked) + """Test RedistributedRouteConfig valid inputs.""" + RedistributedRouteConfig(proto=proto, include_leaked=include_leaked) @pytest.mark.parametrize( ("proto", "include_leaked"), @@ -373,21 +373,21 @@ def test_valid(self, proto: RedistributedProtocol, include_leaked: bool) -> None ], ) def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: - """Test RedistributedRoute invalid inputs.""" + """Test RedistributedRouteConfig invalid inputs.""" with pytest.raises(ValidationError): - RedistributedRoute(proto=proto, include_leaked=include_leaked) + RedistributedRouteConfig(proto=proto, include_leaked=include_leaked) @pytest.mark.parametrize( ("proto", "include_leaked", "route_map", "expected"), [ - pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Included Leaked: True, Route Map: RM-CONN-2-BGP", id="check-all-params"), - pytest.param("Static", False, None, "Proto: Static, Included Leaked: False", id="check-proto-include_leaked"), + pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Include Leaked: present, Route Map: RM-CONN-2-BGP", id="check-all-params"), + pytest.param("Static", False, None, "Proto: Static, Include Leaked: absent", id="check-proto-include_leaked"), pytest.param("Bgp", None, "RM-CONN-2-BGP", "Proto: Bgp, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"), ], ) def test_valid_str(self, proto: RedistributedProtocol, include_leaked: bool | None, route_map: str | None, expected: str) -> None: - """Test RedistributedRoute __str__.""" - assert str(RedistributedRoute(proto=proto, include_leaked=include_leaked, route_map=route_map)) == expected + """Test RedistributedRouteConfig __str__.""" + assert str(RedistributedRouteConfig(proto=proto, include_leaked=include_leaked, route_map=route_map)) == expected class TestVerifyBGPAddressFamilyConfig: @@ -400,7 +400,7 @@ class TestVerifyBGPAddressFamilyConfig: pytest.param("ipv6 Multicast", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-valid"), ], ) - def test_valid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[Any]) -> None: + def test_valid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None: """Test AddressFamilyConfig valid inputs.""" AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) @@ -411,7 +411,7 @@ def test_valid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[An pytest.param("ipv6 sr-te", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-invalid-address-family"), ], ) - def test_invalid(self, afi_safi: RedisrbutedAfiSafi, redistributed_routes: list[Any]) -> None: + def test_invalid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None: """Test AddressFamilyConfig invalid inputs.""" with pytest.raises(ValidationError): AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) From 0b95bfe7fa0c42d38cf0dbff03c941596190ea32 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Mon, 10 Feb 2025 11:53:38 +0000 Subject: [PATCH 12/18] Fixed pre-commit issues, and added more test scenarios --- anta/input_models/routing/bgp.py | 8 ++++---- tests/units/anta_tests/routing/test_bgp.py | 14 +++++++------- tests/units/input_models/routing/test_bgp.py | 15 ++++++++++----- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 271c29e5c..051c77949 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -327,11 +327,11 @@ def __str__(self) -> str: """ base_string = "AFI-SAFI:" if self.afi_safi == "v4u": - base_string =+ f"{base_string} IPv4 Unicast" + base_string = f"{base_string} IPv4 Unicast" elif self.afi_safi == "v4m": - base_string =+ f"{base_string} IPv4 Multicast" + base_string = f"{base_string} IPv4 Multicast" elif self.afi_safi == "v6u": - base_string =+f"{base_string} IPv6 Unicast" + base_string = f"{base_string} IPv6 Unicast" elif self.afi_safi == "v6m": - base_string =+ f"{base_string} IPv4 Multicast" + base_string = f"{base_string} IPv6 Multicast" return base_string diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 76249e95f..6be2c7036 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -5880,8 +5880,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "eos_data": [ { "vrfs": { - "default": {"afiSafiConfig": {"v4u": {}}}, - "test": {"afiSafiConfig": {"v6m": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, + "default": {"afiSafiConfig": {"v6m": {}}}, + "test": {"afiSafiConfig": {"v4u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, } } ], @@ -5891,7 +5891,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "vrf": "default", "address_families": [ { - "afi_safi": "ipv4Unicast", + "afi_safi": "ipv6 Multicast", "redistributed_routes": [ {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, @@ -5901,7 +5901,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, ] }, - "expected": {"result": "failure", "messages": ["VRF: default, AFI-SAFI: IPv4 Unicast - Not configured"]}, + "expected": {"result": "failure", "messages": ["VRF: default, AFI-SAFI: IPv6 Multicast - Not configured"]}, }, { "name": "failure-expected-proto-not-found", @@ -5996,7 +5996,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "vrf": "test", "address_families": [ { - "afi_safi": "ipv6 Unicast", + "afi_safi": "ipv6-Unicast", "redistributed_routes": [ {"proto": "User", "route_map": "RM-CONN-2-BGP"}, {"proto": "OSPF Internal", "route_map": "RM-CONN-2-BGP"}, @@ -6051,7 +6051,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "vrf": "default", "address_families": [ { - "afi_safi": "ipv4multicast", + "afi_safi": "ipv4-multicast", "redistributed_routes": [ {"proto": "IS-IS", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, ], @@ -6062,7 +6062,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "vrf": "test", "address_families": [ { - "afi_safi": "IPv6unicast", + "afi_safi": "IPv6_unicast", "redistributed_routes": [ {"proto": "RIP", "route_map": "RM-CONN-2-BGP"}, {"proto": "Bgp", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}, diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index 61a318547..f027a787b 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -382,7 +382,8 @@ def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> No [ pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Include Leaked: present, Route Map: RM-CONN-2-BGP", id="check-all-params"), pytest.param("Static", False, None, "Proto: Static, Include Leaked: absent", id="check-proto-include_leaked"), - pytest.param("Bgp", None, "RM-CONN-2-BGP", "Proto: Bgp, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"), + pytest.param("User", None, "RM-CONN-2-BGP", "Proto: EOS SDK, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"), + pytest.param("Dynamic", None, None, "Proto: Dynamic", id="check-proto-only"), ], ) def test_valid_str(self, proto: RedistributedProtocol, include_leaked: bool | None, route_map: str | None, expected: str) -> None: @@ -396,8 +397,10 @@ class TestVerifyBGPAddressFamilyConfig: @pytest.mark.parametrize( ("afi_safi", "redistributed_routes"), [ - pytest.param("ipv4Unicast", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-valid"), - pytest.param("ipv6 Multicast", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-valid"), + pytest.param("ipv4Unicast", [{"proto": "OSPFv3 External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-unicast"), + pytest.param("ipv6 Multicast", [{"proto": "OSPF Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-multicast"), + pytest.param("ipv4-Multicast", [{"proto": "IS-IS", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-multicast"), + pytest.param("ipv6_Unicast", [{"proto": "AttachedHost", "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-unicast"), ], ) def test_valid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None: @@ -407,8 +410,10 @@ def test_valid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[ @pytest.mark.parametrize( ("afi_safi", "redistributed_routes"), [ - pytest.param("evpn", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="invalid-address-family"), - pytest.param("ipv6 sr-te", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-invalid-address-family"), + pytest.param("evpn", [{"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="invalid-address-family"), + pytest.param("ipv6 sr-te", [{"proto": "RIP", "route_map": "RM-CONN-2-BGP"}], id="ipv6-invalid-address-family"), + pytest.param("iipv6_Unicast", [{"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-unicast-invalid-address-family"), + pytest.param("ipv6_Unicastt", [{"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-unicast-invalid-address-family"), ], ) def test_invalid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None: From e5f9f2cc68e04621882b00a0d09dbcc52cc4d401 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Mon, 10 Feb 2025 13:50:40 +0000 Subject: [PATCH 13/18] Fixed test coverage issue, and updated test failure messages --- anta/tests/routing/bgp.py | 16 ++++--- tests/units/anta_tests/routing/test_bgp.py | 46 ++++++++++++++++++-- tests/units/input_models/routing/test_bgp.py | 15 +++++++ 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 44231e150..b136923ee 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1857,21 +1857,23 @@ class Input(AntaTest.Input): vrfs: list[BgpVrf] """List of VRFs in the BGP instance.""" - def _validate_redistribute_route(self, vrf_data: str, addr_family: str, afi_safi_configs: list[dict[str, Any]], route_info: dict[str, Any]) -> str | None: + def _validate_redistribute_route(self, vrf_data: str, addr_family: str, afi_safi_configs: list[dict[str, Any]], route_info: dict[str, Any]) -> list[Any]: """Validate the redstributed route details for a given address family.""" + failure_msg = [] # If the redistributed route protocol does not match the expected value, test fails. if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", route_info.proto)): - return f"{vrf_data}, {addr_family}, {route_info} - Not configured" + failure_msg.append(f"{vrf_data}, {addr_family}, Proto: {route_info.proto} - Not configured") + return failure_msg # If includes leaked field applicable, and it does not matches the expected value, test fails. if all([route_info.include_leaked is not None, (act_include_leaked := actual_route.get("includeLeaked", False)) != route_info.include_leaked]): act_include_leaked = "present" if act_include_leaked else "absent" - return f"{vrf_data}, {addr_family}, {route_info} - Value for include leaked mismatch - Actual: {act_include_leaked}" + failure_msg.append(f"{vrf_data}, {addr_family}, {route_info} - Value for include leaked mismatch - Actual: {act_include_leaked}") # If route map is required and it is not matching the expected value, test fails. if all([route_info.route_map, (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map]): - return f"{vrf_data}, {addr_family}, {route_info} - Route map mismatch - Actual: {act_route_map}" - return None + failure_msg.append(f"{vrf_data}, {addr_family}, {route_info} - Route map mismatch - Actual: {act_route_map}") + return failure_msg @AntaTest.anta_test def test(self) -> None: @@ -1892,5 +1894,5 @@ def test(self) -> None: for route_info in address_family.redistributed_routes: failure_msg = self._validate_redistribute_route(str(vrf_data), str(address_family), afi_safi_configs, route_info) - if failure_msg: - self.result.is_failure(failure_msg) + for msg in failure_msg: + self.result.is_failure(msg) diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 6be2c7036..57c736a4b 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -5874,6 +5874,43 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": {"result": "success"}, }, + { + "name": "failure-vrf-not-found", + "test": VerifyBGPRedistribution, + "eos_data": [ + { + "vrfs": { + "default": {"afiSafiConfig": {"v6m": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}}, + "tenant": {"afiSafiConfig": {"v4u": {"redistributedRoutes": [{"proto": "Connected"}]}}}, + } + } + ], + "inputs": { + "vrfs": [ + { + "vrf": "default", + "address_families": [ + { + "afi_safi": "ipv6 Multicast", + "redistributed_routes": [{"proto": "Connected", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], + }, + ], + }, + { + "vrf": "test", + "address_families": [ + { + "afi_safi": "ipv6 Multicast", + "redistributed_routes": [ + {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}, + ], + }, + ], + }, + ] + }, + "expected": {"result": "failure", "messages": ["VRF: test - Not configured"]}, + }, { "name": "failure-afi-safi-config-not-found", "test": VerifyBGPRedistribution, @@ -5916,7 +5953,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "test": { "afiSafiConfig": { - "v6m": { + "v6u": { "redistributedRoutes": [{"proto": "Static", "routeMap": "RM-CONN-2-BGP"}], } } @@ -5955,9 +5992,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: OSPFv3 External, Include Leaked: present, Route Map: RM-CONN-2-BGP - Not configured", - "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: OSPFv3 Nssa-External, Include Leaked: present, Route Map: RM-CONN-2-BGP - Not configured", - "VRF: test, AFI-SAFI: IPv6 Unicast - Not configured", + "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: OSPFv3 External - Not configured", + "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: OSPFv3 Nssa-External - Not configured", + "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: RIP - Not configured", + "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: Bgp - Not configured", ], }, }, diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index f027a787b..e39131e4c 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -420,3 +420,18 @@ def test_invalid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: lis """Test AddressFamilyConfig invalid inputs.""" with pytest.raises(ValidationError): AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes) + + @pytest.mark.parametrize( + ("afi_safi", "redistributed_routes", "expected"), + [ + pytest.param( + "v4u", [{"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv4 Unicast", id="valid-ipv4-unicast" + ), + pytest.param("v4m", [{"proto": "RIP", "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv4 Multicast", id="valid-ipv4-multicast"), + pytest.param("v6u", [{"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv6 Unicast", id="valid-ipv6-unicast"), + pytest.param("v6m", [{"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv6 Multicast", id="valid-ipv6-multicast"), + ], + ) + def test_valid_str(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any], expected: str) -> None: + """Test AddressFamilyConfig invalid inputs.""" + assert str(AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)) == expected From c4fd840dbf77adea89f107d231061e0d8726fb19 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Mon, 10 Feb 2025 14:44:50 +0000 Subject: [PATCH 14/18] Fixed Test coverage issue --- anta/input_models/routing/bgp.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 051c77949..0103e12cb 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -325,13 +325,5 @@ def __str__(self) -> str: -------- - AFI-SAFI: IPv4 Unicast """ - base_string = "AFI-SAFI:" - if self.afi_safi == "v4u": - base_string = f"{base_string} IPv4 Unicast" - elif self.afi_safi == "v4m": - base_string = f"{base_string} IPv4 Multicast" - elif self.afi_safi == "v6u": - base_string = f"{base_string} IPv6 Unicast" - elif self.afi_safi == "v6m": - base_string = f"{base_string} IPv6 Multicast" - return base_string + mappings = {"v4u": "IPv4 Unicast", "v4m": "IPv4 Multicast", "v6u": "IPv6 Unicast", "v6m": "IPv6 Multicast"} + return f"AFI-SAFI: {mappings[self.afi_safi]}" From ddf57211808883092d32c9d721b643211e2a9e64 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Thu, 13 Feb 2025 09:48:30 +0000 Subject: [PATCH 15/18] Updated docstrings and set default value for include linked field --- anta/custom_types.py | 1 + anta/input_models/routing/bgp.py | 6 ++---- anta/tests/routing/bgp.py | 13 ++++++------- tests/units/anta_tests/routing/test_bgp.py | 7 ++----- tests/units/input_models/routing/test_bgp.py | 18 +++++++++--------- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/anta/custom_types.py b/anta/custom_types.py index aa36d2fc7..417c526fd 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -378,6 +378,7 @@ def snmp_v3_prefix(auth_type: Literal["auth", "priv", "noauth"]) -> str: Literal[ "AttachedHost", "Bgp", + "BGP", "Connected", "Dynamic", "IS-IS", diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 0103e12cb..4a97c5314 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -279,8 +279,8 @@ class RedistributedRouteConfig(BaseModel): """Model representing a BGP redistributed route configuration.""" proto: RedistributedProtocol - """he redistributed protocol.""" - include_leaked: bool | None = None + """The redistributed protocol.""" + include_leaked: bool = False """Flag to include leaked routes of the redistributed protocol while redistributing.""" route_map: str | None = None """Optional route map applied to the redistribution.""" @@ -301,8 +301,6 @@ def __str__(self) -> str: - Proto: Connected, Include Leaked: absent, Route Map: RM-CONN-2-BGP """ base_string = f"Proto: {self.proto}" - if not self.include_leaked and self.include_leaked is not None: - base_string += ", Include Leaked: absent" if self.include_leaked: base_string += ", Include Leaked: present" if self.route_map: diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index b136923ee..57d20c277 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -18,7 +18,6 @@ # Using a TypeVar for the BgpPeer model since mypy thinks it's a ClassVar and not a valid type when used in field validators T = TypeVar("T", bound=BgpPeer) -# pylint: disable=C0302 # TODO: Refactor to reduce the number of lines in this module later @@ -1810,7 +1809,8 @@ class VerifyBGPRedistribution(AntaTest): 1. Ensures that the expected address-family is configured on the device. 2. Confirms that the redistributed route protocol, include leaked and route map match the expected values for a route. - Note: For "User" proto field, checking that it's "EOS SDK" versus User. + !!! Note + For "User" proto field, checking that it's "EOS SDK" versus User. Expected Results ---------------- @@ -1819,7 +1819,7 @@ class VerifyBGPRedistribution(AntaTest): - The redistributed route protocol, include leaked and route map align with the expected values for the route. * Failure: If any of the following occur: - The expected address-family is not configured on device. - - The redistributed route protocolor, include leaked or route map does not match the expected value for a route. + - The redistributed route protocol, include leaked or route map does not match the expected value for a route. Examples -------- @@ -1866,9 +1866,8 @@ def _validate_redistribute_route(self, vrf_data: str, addr_family: str, afi_safi return failure_msg # If includes leaked field applicable, and it does not matches the expected value, test fails. - if all([route_info.include_leaked is not None, (act_include_leaked := actual_route.get("includeLeaked", False)) != route_info.include_leaked]): - act_include_leaked = "present" if act_include_leaked else "absent" - failure_msg.append(f"{vrf_data}, {addr_family}, {route_info} - Value for include leaked mismatch - Actual: {act_include_leaked}") + if all([route_info.include_leaked, (act_include_leaked := actual_route.get("includeLeaked", "absent")) != route_info.include_leaked]): + failure_msg.append(f"{vrf_data}, {addr_family}, {route_info} - Include leaked mismatch - Actual: {act_include_leaked}") # If route map is required and it is not matching the expected value, test fails. if all([route_info.route_map, (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map]): @@ -1889,7 +1888,7 @@ def test(self) -> None: for address_family in vrf_data.address_families: # If the AFI-SAFI configuration details are not found, test fails. if not (afi_safi_configs := get_value(instance_details, f"afiSafiConfig.{address_family.afi_safi}")): - self.result.is_failure(f"{vrf_data}, {address_family} - Not configured") + self.result.is_failure(f"{vrf_data}, {address_family} - Not redistributed") continue for route_info in address_family.redistributed_routes: diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 57c736a4b..9fdd9e039 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -5938,7 +5938,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, ] }, - "expected": {"result": "failure", "messages": ["VRF: default, AFI-SAFI: IPv6 Multicast - Not configured"]}, + "expected": {"result": "failure", "messages": ["VRF: default, AFI-SAFI: IPv6 Multicast - Not redistributed"]}, }, { "name": "failure-expected-proto-not-found", @@ -6113,10 +6113,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: IS-IS, Include Leaked: present, Route Map: RM-CONN-2-BGP - Value for include leaked mismatch " - "- Actual: absent", - "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: Bgp, Include Leaked: absent, Route Map: RM-CONN-2-BGP - Value for include leaked mismatch " - "- Actual: present", + "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: IS-IS, Include Leaked: present, Route Map: RM-CONN-2-BGP - Include leaked mismatch - Actual: False" ], }, }, diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index e39131e4c..9a3f25346 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -351,7 +351,7 @@ def test_invalid(self, route_entries: list[BgpRoute]) -> None: class TestVerifyBGPRedistributedRoute: - """Test anta.tests.routing.bgp.RedistributedRouteConfig.Input.""" + """Test anta.input_models.routing.bgp.RedistributedRouteConfig.""" @pytest.mark.parametrize( ("proto", "include_leaked"), @@ -361,7 +361,7 @@ class TestVerifyBGPRedistributedRoute: pytest.param("User", False, id="proto-User"), ], ) - def test_valid(self, proto: RedistributedProtocol, include_leaked: bool) -> None: + def test_validate_inputs(self, proto: RedistributedProtocol, include_leaked: bool) -> None: """Test RedistributedRouteConfig valid inputs.""" RedistributedRouteConfig(proto=proto, include_leaked=include_leaked) @@ -381,25 +381,25 @@ def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> No ("proto", "include_leaked", "route_map", "expected"), [ pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Include Leaked: present, Route Map: RM-CONN-2-BGP", id="check-all-params"), - pytest.param("Static", False, None, "Proto: Static, Include Leaked: absent", id="check-proto-include_leaked"), - pytest.param("User", None, "RM-CONN-2-BGP", "Proto: EOS SDK, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"), - pytest.param("Dynamic", None, None, "Proto: Dynamic", id="check-proto-only"), + pytest.param("Static", False, None, "Proto: Static", id="check-proto-include_leaked"), + pytest.param("User", False, "RM-CONN-2-BGP", "Proto: EOS SDK, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"), + pytest.param("Dynamic", False, None, "Proto: Dynamic", id="check-proto-only"), ], ) - def test_valid_str(self, proto: RedistributedProtocol, include_leaked: bool | None, route_map: str | None, expected: str) -> None: + def test_valid_str(self, proto: RedistributedProtocol, include_leaked: bool, route_map: str | None, expected: str) -> None: """Test RedistributedRouteConfig __str__.""" assert str(RedistributedRouteConfig(proto=proto, include_leaked=include_leaked, route_map=route_map)) == expected class TestVerifyBGPAddressFamilyConfig: - """Test anta.tests.routing.bgp.AddressFamilyConfig.Input.""" + """Test anta.input_models.routing.bgp.AddressFamilyConfig.""" @pytest.mark.parametrize( ("afi_safi", "redistributed_routes"), [ pytest.param("ipv4Unicast", [{"proto": "OSPFv3 External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-unicast"), pytest.param("ipv6 Multicast", [{"proto": "OSPF Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-multicast"), - pytest.param("ipv4-Multicast", [{"proto": "IS-IS", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-multicast"), + pytest.param("ipv4-Multicast", [{"proto": "IS-IS", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-multicast"), pytest.param("ipv6_Unicast", [{"proto": "AttachedHost", "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-unicast"), ], ) @@ -433,5 +433,5 @@ def test_invalid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: lis ], ) def test_valid_str(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any], expected: str) -> None: - """Test AddressFamilyConfig invalid inputs.""" + """Test AddressFamilyConfig __str__.""" assert str(AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)) == expected From b1b30b4cf899ac772fb4e303b5b9b09456ae812f Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Thu, 13 Feb 2025 12:48:59 +0000 Subject: [PATCH 16/18] Updated test ailure messages for include leaked --- anta/input_models/routing/bgp.py | 2 +- anta/tests/routing/bgp.py | 2 +- tests/units/anta_tests/routing/test_bgp.py | 3 ++- tests/units/input_models/routing/test_bgp.py | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 4a97c5314..1227ec0e9 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -302,7 +302,7 @@ def __str__(self) -> str: """ base_string = f"Proto: {self.proto}" if self.include_leaked: - base_string += ", Include Leaked: present" + base_string += f", Include Leaked: {self.include_leaked}" if self.route_map: base_string += f", Route Map: {self.route_map}" return base_string diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 57d20c277..c08ef007b 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1866,7 +1866,7 @@ def _validate_redistribute_route(self, vrf_data: str, addr_family: str, afi_safi return failure_msg # If includes leaked field applicable, and it does not matches the expected value, test fails. - if all([route_info.include_leaked, (act_include_leaked := actual_route.get("includeLeaked", "absent")) != route_info.include_leaked]): + if (act_include_leaked := actual_route.get("includeLeaked", False)) != route_info.include_leaked: failure_msg.append(f"{vrf_data}, {addr_family}, {route_info} - Include leaked mismatch - Actual: {act_include_leaked}") # If route map is required and it is not matching the expected value, test fails. diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 9fdd9e039..ee26155f3 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -6113,7 +6113,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: IS-IS, Include Leaked: present, Route Map: RM-CONN-2-BGP - Include leaked mismatch - Actual: False" + "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: IS-IS, Include Leaked: True, Route Map: RM-CONN-2-BGP - Include leaked mismatch - Actual: False", + "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: Bgp, Route Map: RM-CONN-2-BGP - Include leaked mismatch - Actual: True", ], }, }, diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index 9a3f25346..0ff859ff6 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -380,8 +380,8 @@ def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> No @pytest.mark.parametrize( ("proto", "include_leaked", "route_map", "expected"), [ - pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Include Leaked: present, Route Map: RM-CONN-2-BGP", id="check-all-params"), - pytest.param("Static", False, None, "Proto: Static", id="check-proto-include_leaked"), + pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Include Leaked: True, Route Map: RM-CONN-2-BGP", id="check-all-params"), + pytest.param("Static", False, None, "Proto: Static", id="check-proto-include_leaked-false"), pytest.param("User", False, "RM-CONN-2-BGP", "Proto: EOS SDK, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"), pytest.param("Dynamic", False, None, "Proto: Dynamic", id="check-proto-only"), ], From 7d48553b94e4ba334f95e1154341d3767473987c Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Thu, 13 Feb 2025 16:09:26 +0000 Subject: [PATCH 17/18] Updated example docstring --- anta/input_models/routing/bgp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 1227ec0e9..5c9226ec1 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -298,7 +298,7 @@ def __str__(self) -> str: Examples -------- - - Proto: Connected, Include Leaked: absent, Route Map: RM-CONN-2-BGP + - Proto: Connected, Include Leaked: True, Route Map: RM-CONN-2-BGP """ base_string = f"Proto: {self.proto}" if self.include_leaked: From 0d51312e036dd5dd90fb39091317bb191e0ed430 Mon Sep 17 00:00:00 2001 From: "Geetanjali.mane" Date: Thu, 13 Feb 2025 16:39:05 +0000 Subject: [PATCH 18/18] Updated customtypes file and test example --- anta/custom_types.py | 1 - anta/tests/routing/bgp.py | 8 +++----- examples/tests.yaml | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/anta/custom_types.py b/anta/custom_types.py index 417c526fd..aa36d2fc7 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -378,7 +378,6 @@ def snmp_v3_prefix(auth_type: Literal["auth", "priv", "noauth"]) -> str: Literal[ "AttachedHost", "Bgp", - "BGP", "Connected", "Dynamic", "IS-IS", diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index c08ef007b..7522c2549 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1807,10 +1807,8 @@ class VerifyBGPRedistribution(AntaTest): This test performs the following checks for each specified VRF in the BGP instance: 1. Ensures that the expected address-family is configured on the device. - 2. Confirms that the redistributed route protocol, include leaked and route map match the expected values for a route. + 2. Confirms that the redistributed route protocol, include leaked and route map match the expected values. - !!! Note - For "User" proto field, checking that it's "EOS SDK" versus User. Expected Results ---------------- @@ -1819,7 +1817,7 @@ class VerifyBGPRedistribution(AntaTest): - The redistributed route protocol, include leaked and route map align with the expected values for the route. * Failure: If any of the following occur: - The expected address-family is not configured on device. - - The redistributed route protocol, include leaked or route map does not match the expected value for a route. + - The redistributed route protocol, include leaked or route map does not match the expected values. Examples -------- @@ -1840,7 +1838,7 @@ class VerifyBGPRedistribution(AntaTest): route_map: RM-CONN-2-BGP - afi_safi: IPv6 Unicast redistributed_routes: - - proto: Dynamic + - proto: User # Converted to EOS SDK route_map: RM-CONN-2-BGP - proto: Static include_leaked: True diff --git a/examples/tests.yaml b/examples/tests.yaml index 2e9cf175b..f5fd3ebd0 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -528,7 +528,7 @@ anta.tests.routing.bgp: route_map: RM-CONN-2-BGP - afi_safi: IPv6 Unicast redistributed_routes: - - proto: Dynamic + - proto: User # Converted to EOS SDK route_map: RM-CONN-2-BGP - proto: Static include_leaked: True