diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 3477fc8b2..344330d2c 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -1437,3 +1437,100 @@ def test(self) -> None: self.result.is_success() else: self.result.is_failure(f"The following BGP peers are not configured or have non-zero update error counters:\n{failures}") + + +class VerifyBgpRouteMaps(AntaTest): + """Verifies BGP inbound and outbound route-maps of BGP IPv4 peer(s). + + Expected Results + ---------------- + * Success: The test will pass if the correct route maps are applied in the correct direction (inbound or outbound) for IPv4 BGP peers in the specified VRF. + * Failure: The test will fail if BGP peers are not configured or any neighbor has an incorrect or missing route map in either the inbound or outbound direction. + + Examples + -------- + ```yaml + anta.tests.routing: + bgp: + - VerifyBgpRouteMaps: + bgp_peers: + - peer_address: 172.30.11.1 + vrf: default + inbound_route_map: RM-MLAG-PEER-IN + outbound_route_map: RM-MLAG-PEER-OUT + ``` + """ + + name = "VerifyBgpRouteMaps" + description = "Verifies BGP inbound and outbound route-maps of BGP IPv4 peer(s)." + categories: ClassVar[list[str]] = ["bgp"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show bgp neighbors {peer} vrf {vrf}", revision=3)] + + class Input(AntaTest.Input): + """Input model for the VerifyBgpRouteMaps test.""" + + bgp_peers: list[BgpPeer] + """List of BGP peers""" + + class BgpPeer(BaseModel): + """Model for a BGP peer.""" + + peer_address: IPv4Address + """IPv4 address of a BGP peer.""" + vrf: str = "default" + """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" + inbound_route_map: str | None = None + """Inbound route map applied, defaults to None.""" + outbound_route_map: str | None = None + """Outbound route map applied, defaults to None.""" + + @model_validator(mode="after") + def validate_inputs(self: BaseModel) -> BaseModel: + """Validate the inputs provided to the BgpPeer class. + + At least one of 'inbound' or 'outbound' route-map must be provided. + """ + if not (self.inbound_route_map or self.outbound_route_map): + msg = "At least one of 'inbound_route_map' or 'outbound_route_map' must be provided." + raise ValueError(msg) + return self + + def render(self, template: AntaTemplate) -> list[AntaCommand]: + """Render the template for each BGP peer in the input list.""" + return [template.render(peer=str(bgp_peer.peer_address), vrf=bgp_peer.vrf) for bgp_peer in self.inputs.bgp_peers] + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyBgpRouteMaps.""" + failures: dict[Any, Any] = {} + + for command, input_entry in zip(self.instance_commands, self.inputs.bgp_peers): + peer = str(input_entry.peer_address) + vrf = input_entry.vrf + inbound_route_map = input_entry.inbound_route_map + outbound_route_map = input_entry.outbound_route_map + failure: dict[Any, Any] = {vrf: {}} + + # Verify BGP peer. + if not (peer_list := get_value(command.json_output, f"vrfs.{vrf}.peerList")) or (peer_detail := get_item(peer_list, "peerAddress", peer)) is None: + failures[peer] = {vrf: "Not configured"} + continue + + # Verify Inbound route-map + if inbound_route_map and (inbound_map := peer_detail.get("routeMapInbound", "Not Configured")) != inbound_route_map: + failure[vrf].update({"Inbound route-map": inbound_map}) + + # Verify Outbound route-map + if outbound_route_map and (outbound_map := peer_detail.get("routeMapOutbound", "Not Configured")) != outbound_route_map: + failure[vrf].update({"Outbound route-map": outbound_map}) + + if failure[vrf]: + failures[peer] = failure + + # Check if any failures + if not failures: + self.result.is_success() + else: + self.result.is_failure( + f"The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n{failures}" + ) diff --git a/examples/tests.yaml b/examples/tests.yaml index bb7d3b0d6..1c8f223d7 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -609,6 +609,12 @@ anta.tests.routing: update_errors: - inUpdErrWithdraw - inUpdErrIgnore + - VerifyBgpRouteMaps: + bgp_peers: + - peer_address: 10.100.4.1 + vrf: default + inbound_route_map: RM-MLAG-PEER-IN + outbound_route_map: RM-MLAG-PEER-IN ospf: - VerifyOSPFNeighborState: - VerifyOSPFNeighborCount: diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index b76939bd5..9948a53be 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -21,6 +21,7 @@ VerifyBGPPeerRouteRefreshCap, VerifyBGPPeersHealth, VerifyBGPPeerUpdateErrors, + VerifyBgpRouteMaps, VerifyBGPSpecificPeers, VerifyBGPTimers, VerifyEVPNType2Route, @@ -4503,4 +4504,205 @@ ], }, }, + { + "name": "success", + "test": VerifyBgpRouteMaps, + "eos_data": [ + { + "vrfs": { + "default": { + "peerList": [ + { + "peerAddress": "10.100.0.8", + "routeMapInbound": "RM-MLAG-PEER-IN", + "routeMapOutbound": "RM-MLAG-PEER-OUT", + } + ] + }, + }, + }, + { + "vrfs": { + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.10", + "routeMapInbound": "RM-MLAG-PEER-IN", + "routeMapOutbound": "RM-MLAG-PEER-OUT", + } + ] + }, + }, + }, + ], + "inputs": { + "bgp_peers": [ + {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"}, + {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"}, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "failure-incorrect-route-map", + "test": VerifyBgpRouteMaps, + "eos_data": [ + { + "vrfs": { + "default": { + "peerList": [ + { + "peerAddress": "10.100.0.8", + "routeMapInbound": "RM-MLAG-PEER", + "routeMapOutbound": "RM-MLAG-PEER", + } + ] + }, + }, + }, + { + "vrfs": { + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.10", + "routeMapInbound": "RM-MLAG-PEER", + "routeMapOutbound": "RM-MLAG-PEER", + } + ] + }, + }, + }, + ], + "inputs": { + "bgp_peers": [ + {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"}, + {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n" + "{'10.100.0.8': {'default': {'Inbound route-map': 'RM-MLAG-PEER', 'Outbound route-map': 'RM-MLAG-PEER'}}, " + "'10.100.0.10': {'MGMT': {'Inbound route-map': 'RM-MLAG-PEER', 'Outbound route-map': 'RM-MLAG-PEER'}}}" + ], + }, + }, + { + "name": "failure-incorrect-inbound-map", + "test": VerifyBgpRouteMaps, + "eos_data": [ + { + "vrfs": { + "default": { + "peerList": [ + { + "peerAddress": "10.100.0.8", + "routeMapInbound": "RM-MLAG-PEER", + "routeMapOutbound": "RM-MLAG-PEER", + } + ] + }, + }, + }, + { + "vrfs": { + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.10", + "routeMapInbound": "RM-MLAG-PEER", + "routeMapOutbound": "RM-MLAG-PEER", + } + ] + }, + }, + }, + ], + "inputs": { + "bgp_peers": [ + {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN"}, + {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n" + "{'10.100.0.8': {'default': {'Inbound route-map': 'RM-MLAG-PEER'}}, '10.100.0.10': {'MGMT': {'Inbound route-map': 'RM-MLAG-PEER'}}}" + ], + }, + }, + { + "name": "failure-route-maps-not-configured", + "test": VerifyBgpRouteMaps, + "eos_data": [ + { + "vrfs": { + "default": { + "peerList": [ + { + "peerAddress": "10.100.0.8", + } + ] + }, + }, + }, + { + "vrfs": { + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.10", + } + ] + }, + }, + }, + ], + "inputs": { + "bgp_peers": [ + {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"}, + {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n" + "{'10.100.0.8': {'default': {'Inbound route-map': 'Not Configured', 'Outbound route-map': 'Not Configured'}}, " + "'10.100.0.10': {'MGMT': {'Inbound route-map': 'Not Configured', 'Outbound route-map': 'Not Configured'}}}" + ], + }, + }, + { + "name": "failure-peer-not-found", + "test": VerifyBgpRouteMaps, + "eos_data": [ + { + "vrfs": { + "default": {"peerList": []}, + }, + }, + { + "vrfs": { + "MGMT": {"peerList": []}, + }, + }, + ], + "inputs": { + "bgp_peers": [ + {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN"}, + {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n" + "{'10.100.0.8': {'default': 'Not configured'}, '10.100.0.10': {'MGMT': 'Not configured'}}" + ], + }, + }, ]