-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(anta): Added testcase to verify the BGP Redistributed Routes #993
base: main
Are you sure you want to change the base?
Changes from 7 commits
a52988d
9215731
ddcf15c
3a4eed8
145dae6
e8d0f11
688b67b
75f2a83
1a326a6
e65000c
f4cb68f
b4e2ba6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, RedistributedProtocol, Safi, Vni | ||
|
||
if TYPE_CHECKING: | ||
import sys | ||
|
@@ -40,6 +40,10 @@ | |
} | ||
"""Dictionary mapping AFI/SAFI to EOS key representation.""" | ||
|
||
AFI_SAFI_REDISTRIBUTED_ROUTE_KEY = {"ipv4Unicast": "v4u", "ipv4Multicast": "v4m", "ipv6Unicast": "v6u", "ipv6Multicast": "v6m"} | ||
vitthalmagadum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
"""Dictionary mapping of AFI/SAFI to redistributed routes key representation.""" | ||
|
||
|
||
class BgpAddressFamily(BaseModel): | ||
"""Model for a BGP address family.""" | ||
|
@@ -68,8 +72,11 @@ 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: 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.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use separate models for the class RedistributedRoute(BaseModel):
proto: RedistributedProtocol # Custom type that you already created
include_leaked: bool | None = None # We should also check this for protocols that it applies
route_map: str | None = None # This is optional, we can redistribute without a route-map
class AfiSafiConfig(BaseModel):
name: Literal["v4u", "v4m"] # Here we should also support other formats like `IPv4 Unicast` or `ipv4Unicast`
redistributed_routes: list[RedistributedRoute]
class BgpVrf(BaseModel):
name: str = "default"
address_families: list[AfiSafiConfig]
# With this structure we can easily add more fields in the future for other tests.
# router_id: str
# local_as: int There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @carl-baillargeon.
However, I'm encountering an issue with Cognitive Complexity. To resolve this, I've proposed an alternative input model approach:
Key Reasons for the Proposed Change
As discussed with @gmuloc we will revisit this once Carl returns and then finalize the approach, In the meantime, I’m putting this on hold. Thanks, |
||
|
||
@model_validator(mode="after") | ||
def validate_inputs(self) -> Self: | ||
|
@@ -97,6 +104,11 @@ 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. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -1685,3 +1688,89 @@ def test(self) -> None: | |
|
||
if (actual_origin := get_value(route_path, "routeType.origin")) != origin: | ||
self.result.is_failure(f"{route} {path} - Origin mismatch - Actual: {actual_origin}") | ||
|
||
|
||
class VerifyBGPRedistributedRoutes(AntaTest): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would call this test |
||
"""Verifies BGP redistributed routes protocol and route-map. | ||
|
||
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. | ||
|
||
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 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 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 | ||
anta.tests.routing: | ||
bgp: | ||
- VerifyBGPRedistributedRoutes: | ||
address_families: | ||
- afi: "ipv4" | ||
safi: "unicast" | ||
vrf: default | ||
redistributed_route_protocol: Connected | ||
route_map: RM-CONN-2-BGP | ||
``` | ||
""" | ||
|
||
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.""" | ||
|
||
@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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check could go in the |
||
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 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 | ||
|
||
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 | ||
|
||
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}" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to add
OSPF External
,OSPF Nssa-External
, same for OSPFv3. We can also redistribute specific IS-IS levels; level-1, level-2, level-1-2 so please double check the options.