From b1abaad7f59df4cb6cc20566b2a0a7d96469d125 Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Thu, 17 Oct 2024 14:31:52 -0400 Subject: [PATCH 01/15] fix(anta.tests): First round of cleaning up BGP tests module --- anta/tests/routing/bgp.py | 520 +++++++------------------ anta/tests/routing/bgp_input_models.py | 114 ++++++ anta/tools.py | 44 ++- 3 files changed, 294 insertions(+), 384 deletions(-) create mode 100644 anta/tests/routing/bgp_input_models.py diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index e573f3f71..8a19e6a97 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -7,16 +7,18 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations -from ipaddress import IPv4Address, IPv4Network, IPv6Address +from ipaddress import IPv4Address, IPv4Network from typing import TYPE_CHECKING, Any, ClassVar -from pydantic import BaseModel, Field, PositiveInt, model_validator +from pydantic import BaseModel, Field, field_validator, model_validator from pydantic.v1.utils import deep_update from pydantic_extra_types.mac_address import MacAddress -from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, Safi, Vni +from anta.custom_types import BgpDropStats, BgpUpdateError, MultiProtocolCaps, Vni from anta.models import AntaCommand, AntaTemplate, AntaTest -from anta.tools import get_item, get_value +from anta.tools import check_bgp_neighbor_capability, format_data, get_item, get_value + +from .bgp_input_models import BgpAddressFamily, BgpAfi if TYPE_CHECKING: import sys @@ -27,95 +29,6 @@ from typing_extensions import Self -def _add_bgp_failures(failures: dict[tuple[str, str | None], dict[str, Any]], afi: Afi, safi: Safi | None, vrf: str, issue: str | dict[str, Any]) -> None: - """Add a BGP failure entry to the given `failures` dictionary. - - Note: This function modifies `failures` in-place. - - Parameters - ---------- - failures - The dictionary to which the failure will be added. - afi - The address family identifier. - vrf - The VRF name. - safi - The subsequent address family identifier. - issue - A description of the issue. Can be of any type. - - Example - ------- - The `failures` dictionary will have the following structure: - ``` - { - ('afi1', 'safi1'): { - 'afi': 'afi1', - 'safi': 'safi1', - 'vrfs': { - 'vrf1': issue1, - 'vrf2': issue2 - } - }, - ('afi2', None): { - 'afi': 'afi2', - 'vrfs': { - 'vrf1': issue3 - } - } - } - ``` - - """ - key = (afi, safi) - - failure_entry = failures.setdefault(key, {"afi": afi, "safi": safi, "vrfs": {}}) if safi else failures.setdefault(key, {"afi": afi, "vrfs": {}}) - - failure_entry["vrfs"][vrf] = issue - - -def _check_peer_issues(peer_data: dict[str, Any] | None) -> dict[str, Any]: - """Check for issues in BGP peer data. - - Parameters - ---------- - peer_data - The BGP peer data dictionary nested in the `show bgp summary` command. - - Returns - ------- - dict - Dictionary with keys indicating issues or an empty dictionary if no issues. - - Raises - ------ - ValueError - If any of the required keys ("peerState", "inMsgQueue", "outMsgQueue") are missing in `peer_data`, i.e. invalid BGP peer data. - - Example - ------- - This can for instance return - ``` - {"peerNotFound": True} - {"peerState": "Idle", "inMsgQueue": 2, "outMsgQueue": 0} - {} - ``` - - """ - if peer_data is None: - return {"peerNotFound": True} - - if any(key not in peer_data for key in ["peerState", "inMsgQueue", "outMsgQueue"]): - msg = "Provided BGP peer data is invalid." - raise ValueError(msg) - - if peer_data["peerState"] != "Established" or peer_data["inMsgQueue"] != 0 or peer_data["outMsgQueue"] != 0: - return {"peerState": peer_data["peerState"], "inMsgQueue": peer_data["inMsgQueue"], "outMsgQueue": peer_data["outMsgQueue"]} - - return {} - - def _add_bgp_routes_failure( bgp_routes: list[str], bgp_output: dict[str, Any], peer: str, vrf: str, route_type: str = "advertised_routes" ) -> dict[str, dict[str, dict[str, dict[str, list[str]]]]]: @@ -172,18 +85,21 @@ def _add_bgp_routes_failure( class VerifyBGPPeerCount(AntaTest): - """Verifies the count of BGP peers for a given address family. + """Verifies the count of established BGP peers with negotiated AFI/SAFI for given address families. - It supports multiple types of Address Families Identifiers (AFI) and Subsequent Address Family Identifiers (SAFI). + This test performs the following checks for each specified address family: - For SR-TE SAFI, the EOS command supports sr-te first then ipv4/ipv6 (AFI) which is handled automatically in this test. - - Please refer to the Input class attributes below for details. + 1. Confirms that the specified VRF is configured. + 2. Counts the number of peers that are: + - In the `Established` state + - Have successfully negotiated the specified AFI/SAFI Expected Results ---------------- - * Success: If the count of BGP peers matches the expected count for each address family and VRF. - * Failure: If the count of BGP peers does not match the expected count, or if BGP is not configured for an expected VRF or address family. + * Success: If the count of established BGP peers with negotiated AFI/SAFI matches the expected count for all specified address families. + * Failure: If any of the following occur: + - The specified VRF is not configured. + - The count of established peers with negotiated AFI/SAFI does not match the expected count for any address family. Examples -------- @@ -209,129 +125,74 @@ class VerifyBGPPeerCount(AntaTest): ``` """ - description = "Verifies the count of BGP peers." + name = "VerifyBGPPeerCount" + description = "Verifies the count of established BGP peers with negotiated AFI/SAFI for given address families." categories: ClassVar[list[str]] = ["bgp"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [ - AntaTemplate(template="show bgp {afi} {safi} summary vrf {vrf}", revision=3), - AntaTemplate(template="show bgp {afi} summary", revision=3), - ] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp summary vrf all", revision=1)] class Input(AntaTest.Input): """Input model for the VerifyBGPPeerCount test.""" - address_families: list[BgpAfi] - """List of BGP address families (BgpAfi).""" - - class BgpAfi(BaseModel): - """Model for a BGP address family (AFI) and subsequent address family (SAFI).""" - - afi: Afi - """BGP address family (AFI).""" - safi: Safi | None = None - """Optional BGP subsequent service family (SAFI). - - If the input `afi` is `ipv4` or `ipv6`, a valid `safi` must be provided. - """ - vrf: str = "default" - """ - Optional VRF for IPv4 and IPv6. If not provided, it defaults to `default`. - - If the input `afi` is not `ipv4` or `ipv6`, e.g. `evpn`, `vrf` must be `default`. - """ - num_peers: PositiveInt - """Number of expected BGP peer(s).""" - - @model_validator(mode="after") - def validate_inputs(self) -> Self: - """Validate the inputs provided to the BgpAfi class. - - If afi is either ipv4 or ipv6, safi must be provided. - - If afi is not ipv4 or ipv6, safi must not be provided and vrf must be default. - """ - if self.afi in ["ipv4", "ipv6"]: - if self.safi is None: - msg = "'safi' must be provided when afi is ipv4 or ipv6" - raise ValueError(msg) - elif self.safi is not None: - msg = "'safi' must not be provided when afi is not ipv4 or ipv6" - raise ValueError(msg) - elif self.vrf != "default": - msg = "'vrf' must be default when afi is not ipv4 or ipv6" + address_families: list[BgpAddressFamily] + """List of BGP address families.""" + BgpAfi: ClassVar[type[BgpAfi]] = BgpAfi + + @field_validator("address_families") + @classmethod + def validate_address_families(cls, address_families: list[BgpAddressFamily]) -> list[BgpAddressFamily]: + """Validate that 'peers' field is provided in each address family.""" + for af in address_families: + if af.num_peers is None: + msg = f"{af} 'num_peers' field missing in the input" raise ValueError(msg) - return self - - def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each BGP address family in the input list.""" - commands = [] - for afi in self.inputs.address_families: - if template == VerifyBGPPeerCount.commands[0] and afi.afi in ["ipv4", "ipv6"] and afi.safi != "sr-te": - commands.append(template.render(afi=afi.afi, safi=afi.safi, vrf=afi.vrf)) - - # For SR-TE SAFI, the EOS command supports sr-te first then ipv4/ipv6 - elif template == VerifyBGPPeerCount.commands[0] and afi.afi in ["ipv4", "ipv6"] and afi.safi == "sr-te": - commands.append(template.render(afi=afi.safi, safi=afi.afi, vrf=afi.vrf)) - elif template == VerifyBGPPeerCount.commands[1] and afi.afi not in ["ipv4", "ipv6"]: - commands.append(template.render(afi=afi.afi)) - return commands + return address_families @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeerCount.""" self.result.is_success() - failures: dict[tuple[str, Any], dict[str, Any]] = {} + output = self.instance_commands[0].json_output - for command in self.instance_commands: - num_peers = None + for address_family in self.inputs.address_families: peer_count = 0 - command_output = command.json_output - - afi = command.params.afi - safi = command.params.safi if hasattr(command.params, "safi") else None - afi_vrf = command.params.vrf if hasattr(command.params, "vrf") else "default" - - # Swapping AFI and SAFI in case of SR-TE - if afi == "sr-te": - afi, safi = safi, afi - for input_entry in self.inputs.address_families: - if input_entry.afi == afi and input_entry.safi == safi and input_entry.vrf == afi_vrf: - num_peers = input_entry.num_peers - break - - if not (vrfs := command_output.get("vrfs")): - _add_bgp_failures(failures=failures, afi=afi, safi=safi, vrf=afi_vrf, issue="Not Configured") + # Check if the VRF is configured + if (vrf_output := get_value(output, f"vrfs.{address_family.vrf}")) is None: + self.result.is_failure(f"{address_family} VRF not configured") continue - if afi_vrf == "all": - for vrf_data in vrfs.values(): - peer_count += len(vrf_data["peers"]) - else: - peer_count += len(command_output["vrfs"][afi_vrf]["peers"]) - - if peer_count != num_peers: - _add_bgp_failures(failures=failures, afi=afi, safi=safi, vrf=afi_vrf, issue=f"Expected: {num_peers}, Actual: {peer_count}") + # Count the number of established peers with negotiated AFI/SAFI + for peer_data in vrf_output.get("peers", {}).values(): + if peer_data.get("peerState") == "Established" and get_value(peer_data, f"{address_family.eos_key}.afiSafiState") == "negotiated": + peer_count += 1 - if failures: - self.result.is_failure(f"Failures: {list(failures.values())}") + # Check if the count matches the expected count + if address_family.num_peers != peer_count: + self.result.is_failure(f"{address_family} Expected: {address_family.num_peers}, Actual: {peer_count}") class VerifyBGPPeersHealth(AntaTest): - """Verifies the health of BGP peers. - - It will validate that all BGP sessions are established and all message queues for these BGP sessions are empty for a given address family. + """Verifies the health of BGP peers for given address families. - It supports multiple types of Address Families Identifiers (AFI) and Subsequent Address Family Identifiers (SAFI). + This test performs the following checks for each specified address family: - For SR-TE SAFI, the EOS command supports sr-te first then ipv4/ipv6 (AFI) which is handled automatically in this test. - - Please refer to the Input class attributes below for details. + 1. Validates that the VRF is configured. + 2. Checks if there are any peers for the given AFI/SAFI. + 3. For each relevant peer: + - Verifies that the BGP session is in the `Established` state. + - Confirms that the AFI/SAFI state is `negotiated`. + - Checks that both input and output TCP message queues are empty. Expected Results ---------------- - * Success: If all BGP sessions are established and all messages queues are empty for each address family and VRF. - * Failure: If there are issues with any of the BGP sessions, or if BGP is not configured for an expected VRF or address family. + * Success: If all checks pass for all specified address families and their peers. + * Failure: If any of the following occur: + - The specified VRF is not configured. + - No peers are found for a given AFI/SAFI. + - Any BGP session is not in the `Established` state. + - The AFI/SAFI state is not 'negotiated' for any peer. + - Any TCP message queue (input or output) is not empty. Examples -------- @@ -350,125 +211,76 @@ class VerifyBGPPeersHealth(AntaTest): ``` """ + name = "VerifyBGPPeersHealth" + description = "Verifies the health of BGP peers for given address families." categories: ClassVar[list[str]] = ["bgp"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [ - AntaTemplate(template="show bgp {afi} {safi} summary vrf {vrf}", revision=3), - AntaTemplate(template="show bgp {afi} summary", revision=3), - ] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] class Input(AntaTest.Input): """Input model for the VerifyBGPPeersHealth test.""" - address_families: list[BgpAfi] - """List of BGP address families (BgpAfi).""" - - class BgpAfi(BaseModel): - """Model for a BGP address family (AFI) and subsequent address family (SAFI).""" - - afi: Afi - """BGP address family (AFI).""" - safi: Safi | None = None - """Optional BGP subsequent service family (SAFI). - - If the input `afi` is `ipv4` or `ipv6`, a valid `safi` must be provided. - """ - vrf: str = "default" - """ - Optional VRF for IPv4 and IPv6. If not provided, it defaults to `default`. - - If the input `afi` is not `ipv4` or `ipv6`, e.g. `evpn`, `vrf` must be `default`. - """ - - @model_validator(mode="after") - def validate_inputs(self) -> Self: - """Validate the inputs provided to the BgpAfi class. - - If afi is either ipv4 or ipv6, safi must be provided. - - If afi is not ipv4 or ipv6, safi must not be provided and vrf must be default. - """ - if self.afi in ["ipv4", "ipv6"]: - if self.safi is None: - msg = "'safi' must be provided when afi is ipv4 or ipv6" - raise ValueError(msg) - elif self.safi is not None: - msg = "'safi' must not be provided when afi is not ipv4 or ipv6" - raise ValueError(msg) - elif self.vrf != "default": - msg = "'vrf' must be default when afi is not ipv4 or ipv6" - raise ValueError(msg) - return self - - def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each BGP address family in the input list.""" - commands = [] - for afi in self.inputs.address_families: - if template == VerifyBGPPeersHealth.commands[0] and afi.afi in ["ipv4", "ipv6"] and afi.safi != "sr-te": - commands.append(template.render(afi=afi.afi, safi=afi.safi, vrf=afi.vrf)) - - # For SR-TE SAFI, the EOS command supports sr-te first then ipv4/ipv6 - elif template == VerifyBGPPeersHealth.commands[0] and afi.afi in ["ipv4", "ipv6"] and afi.safi == "sr-te": - commands.append(template.render(afi=afi.safi, safi=afi.afi, vrf=afi.vrf)) - elif template == VerifyBGPPeersHealth.commands[1] and afi.afi not in ["ipv4", "ipv6"]: - commands.append(template.render(afi=afi.afi)) - return commands + address_families: list[BgpAddressFamily] + """List of BGP address families.""" + BgpAfi: ClassVar[type[BgpAfi]] = BgpAfi @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeersHealth.""" self.result.is_success() - failures: dict[tuple[str, Any], dict[str, Any]] = {} + output = self.instance_commands[0].json_output - for command in self.instance_commands: - command_output = command.json_output - - afi = command.params.afi - safi = command.params.safi if hasattr(command.params, "safi") else None - afi_vrf = command.params.vrf if hasattr(command.params, "vrf") else "default" - - # Swapping AFI and SAFI in case of SR-TE - if afi == "sr-te": - afi, safi = safi, afi - - if not (vrfs := command_output.get("vrfs")): - _add_bgp_failures(failures=failures, afi=afi, safi=safi, vrf=afi_vrf, issue="Not Configured") + for address_family in self.inputs.address_families: + # Check if the VRF is configured + if (vrf_output := get_value(output, f"vrfs.{address_family.vrf}")) is None: + self.result.is_failure(f"{address_family} VRF not configured") continue - for vrf, vrf_data in vrfs.items(): - if not (peers := vrf_data.get("peers")): - _add_bgp_failures(failures=failures, afi=afi, safi=safi, vrf=afi_vrf, issue="No Peers") - continue + # Check if any peers are found for this AFI/SAFI + relevant_peers = [ + peer for peer in vrf_output.get("peerList", []) if get_value(peer, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") is not None + ] - peer_issues = {} - for peer, peer_data in peers.items(): - issues = _check_peer_issues(peer_data) + if not relevant_peers: + self.result.is_failure(f"{address_family} No peers found") + continue - if issues: - peer_issues[peer] = issues + for peer in relevant_peers: + # Check if the BGP session is established + if peer["state"] != "Established": + self.result.is_failure(f"{address_family} Peer:{peer['peerAddress']} session state is not established; State:{peer['state']}") - if peer_issues: - _add_bgp_failures(failures=failures, afi=afi, safi=safi, vrf=vrf, issue=peer_issues) + # Check if the AFI/SAFI state is negotiated + capability_status = get_value(peer, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") + if not check_bgp_neighbor_capability(capability_status): + self.result.is_failure(f"{address_family} Peer:{peer['peerAddress']} AFI/SAFI state is not negotiated; {format_data(capability_status)}") - if failures: - self.result.is_failure(f"Failures: {list(failures.values())}") + # Check the TCP session message queues + if (inq := peer["peerTcpInfo"]["inputQueueLength"]) != 0 or (outq := peer["peerTcpInfo"]["outputQueueLength"]) != 0: + self.result.is_failure(f"{address_family} Peer:{peer['peerAddress']} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") class VerifyBGPSpecificPeers(AntaTest): - """Verifies the health of specific BGP peer(s). - - It will validate that the BGP session is established and all message queues for this BGP session are empty for the given peer(s). - - It supports multiple types of Address Families Identifiers (AFI) and Subsequent Address Family Identifiers (SAFI). + """Verifies the health of specific BGP peer(s) for given address families. - For SR-TE SAFI, the EOS command supports sr-te first then ipv4/ipv6 (AFI) which is handled automatically in this test. + This test performs the following checks for each specified address family and peer: - Please refer to the Input class attributes below for details. + 1. Confirms that the specified VRF is configured. + 2. For each specified peer: + - Verifies that the peer is found in the BGP configuration. + - Checks that the BGP session is in the `Established` state. + - Confirms that the AFI/SAFI state is `negotiated`. + - Ensures that both input and output TCP message queues are empty. Expected Results ---------------- - * Success: If the BGP session is established and all messages queues are empty for each given peer. - * Failure: If the BGP session has issues or is not configured, or if BGP is not configured for an expected VRF or address family. + * Success: If all checks pass for all specified peers in all address families. + * Failure: If any of the following occur: + - The specified VRF is not configured. + - A specified peer is not found in the BGP configuration. + - The BGP session for a peer is not in the `Established` state. + - The AFI/SAFI state is not `negotiated` for a peer. + - Any TCP message queue (input or output) is not empty for a peer. Examples -------- @@ -492,116 +304,58 @@ class VerifyBGPSpecificPeers(AntaTest): """ categories: ClassVar[list[str]] = ["bgp"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [ - AntaTemplate(template="show bgp {afi} {safi} summary vrf {vrf}", revision=3), - AntaTemplate(template="show bgp {afi} summary", revision=3), - ] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] class Input(AntaTest.Input): """Input model for the VerifyBGPSpecificPeers test.""" - address_families: list[BgpAfi] - """List of BGP address families (BgpAfi).""" - - class BgpAfi(BaseModel): - """Model for a BGP address family (AFI) and subsequent address family (SAFI).""" - - afi: Afi - """BGP address family (AFI).""" - safi: Safi | None = None - """Optional BGP subsequent service family (SAFI). - - If the input `afi` is `ipv4` or `ipv6`, a valid `safi` must be provided. - """ - vrf: str = "default" - """ - Optional VRF for IPv4 and IPv6. If not provided, it defaults to `default`. - - `all` is NOT supported. - - If the input `afi` is not `ipv4` or `ipv6`, e.g. `evpn`, `vrf` must be `default`. - """ - peers: list[IPv4Address | IPv6Address] - """List of BGP IPv4 or IPv6 peer.""" - - @model_validator(mode="after") - def validate_inputs(self) -> Self: - """Validate the inputs provided to the BgpAfi class. - - If afi is either ipv4 or ipv6, safi must be provided and vrf must NOT be all. - - If afi is not ipv4 or ipv6, safi must not be provided and vrf must be default. - """ - if self.afi in ["ipv4", "ipv6"]: - if self.safi is None: - msg = "'safi' must be provided when afi is ipv4 or ipv6" - raise ValueError(msg) - if self.vrf == "all": - msg = "'all' is not supported in this test. Use VerifyBGPPeersHealth test instead." - raise ValueError(msg) - elif self.safi is not None: - msg = "'safi' must not be provided when afi is not ipv4 or ipv6" + address_families: list[BgpAddressFamily] + """List of BGP address families.""" + BgpAfi: ClassVar[type[BgpAfi]] = BgpAfi + + @field_validator("address_families") + @classmethod + def validate_address_families(cls, address_families: list[BgpAddressFamily]) -> list[BgpAddressFamily]: + """Validate that 'peers' field is provided in each address family.""" + for af in address_families: + if af.peers is None: + msg = f"{af} 'peers' field missing in the input" raise ValueError(msg) - elif self.vrf != "default": - msg = "'vrf' must be default when afi is not ipv4 or ipv6" - raise ValueError(msg) - return self - - def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each BGP address family in the input list.""" - commands = [] - - for afi in self.inputs.address_families: - if template == VerifyBGPSpecificPeers.commands[0] and afi.afi in ["ipv4", "ipv6"] and afi.safi != "sr-te": - commands.append(template.render(afi=afi.afi, safi=afi.safi, vrf=afi.vrf)) - - # For SR-TE SAFI, the EOS command supports sr-te first then ipv4/ipv6 - elif template == VerifyBGPSpecificPeers.commands[0] and afi.afi in ["ipv4", "ipv6"] and afi.safi == "sr-te": - commands.append(template.render(afi=afi.safi, safi=afi.afi, vrf=afi.vrf)) - elif template == VerifyBGPSpecificPeers.commands[1] and afi.afi not in ["ipv4", "ipv6"]: - commands.append(template.render(afi=afi.afi)) - return commands + return address_families @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPSpecificPeers.""" self.result.is_success() - failures: dict[tuple[str, Any], dict[str, Any]] = {} - - for command in self.instance_commands: - command_output = command.json_output + output = self.instance_commands[0].json_output - afi = command.params.afi - safi = command.params.safi if hasattr(command.params, "safi") else None - afi_vrf = command.params.vrf if hasattr(command.params, "vrf") else "default" - - # Swapping AFI and SAFI in case of SR-TE - if afi == "sr-te": - afi, safi = safi, afi - - for input_entry in self.inputs.address_families: - if input_entry.afi == afi and input_entry.safi == safi and input_entry.vrf == afi_vrf: - afi_peers = input_entry.peers - break - - if not (vrfs := command_output.get("vrfs")): - _add_bgp_failures(failures=failures, afi=afi, safi=safi, vrf=afi_vrf, issue="Not Configured") + for address_family in self.inputs.address_families: + # Check if the VRF is configured + if (vrf_output := get_value(output, f"vrfs.{address_family.vrf}")) is None: + self.result.is_failure(f"{address_family} VRF not configured") continue - peer_issues = {} - for peer in afi_peers: + for peer in address_family.peers: peer_ip = str(peer) - peer_data = get_value(dictionary=vrfs, key=f"{afi_vrf}_peers_{peer_ip}", separator="_") - issues = _check_peer_issues(peer_data) - if issues: - peer_issues[peer_ip] = issues - if peer_issues: - _add_bgp_failures(failures=failures, afi=afi, safi=safi, vrf=afi_vrf, issue=peer_issues) + # Check if the peer is found + if (peer_data := get_item(vrf_output["peerList"], "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{address_family} Peer:{peer_ip} not found") + continue + + # Check if the BGP session is established + if peer_data["state"] != "Established": + self.result.is_failure(f"{address_family} Peer:{peer_ip} session state is not established; State:{peer_data['state']}") + + # Check if the AFI/SAFI state is negotiated + capability_status = get_value(peer_data, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") + if not check_bgp_neighbor_capability(capability_status): + self.result.is_failure(f"{address_family} Peer:{peer_ip} AFI/SAFI state is not negotiated; {format_data(capability_status)}") - if failures: - self.result.is_failure(f"Failures: {list(failures.values())}") + # Check the TCP session message queues + if (inq := peer_data["peerTcpInfo"]["inputQueueLength"]) != 0 or (outq := peer_data["peerTcpInfo"]["outputQueueLength"]) != 0: + self.result.is_failure(f"{address_family} Peer:{peer_ip} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") class VerifyBGPExchangedRoutes(AntaTest): diff --git a/anta/tests/routing/bgp_input_models.py b/anta/tests/routing/bgp_input_models.py new file mode 100644 index 000000000..351e628cb --- /dev/null +++ b/anta/tests/routing/bgp_input_models.py @@ -0,0 +1,114 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Module containing input models for routing tests.""" + +from __future__ import annotations + +from ipaddress import IPv4Address, IPv6Address +from typing import TYPE_CHECKING, Any +from warnings import warn + +from pydantic import BaseModel, ConfigDict, PositiveInt, model_validator + +from anta.custom_types import Afi, Safi + +if TYPE_CHECKING: + import sys + + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self + +AFI_SAFI_EOS_KEY = { + ("ipv4", "unicast"): "ipv4Unicast", + ("ipv4", "multicast"): "ipv4Multicast", + ("ipv4", "labeled-unicast"): "ipv4MplsLabels", + ("ipv4", "sr-te"): "ipv4SrTe", + ("ipv6", "unicast"): "ipv6Unicast", + ("ipv6", "multicast"): "ipv6Multicast", + ("ipv6", "labeled-unicast"): "ipv6MplsLabels", + ("ipv6", "sr-te"): "ipv6SrTe", + ("vpn-ipv4", None): "ipv4MplsVpn", + ("vpn-ipv6", None): "ipv6MplsVpn", + ("evpn", None): "l2VpnEvpn", + ("rt-membership", None): "rtMembership", + ("path-selection", None): "dps", + ("link-state", None): "linkState", +} +"""Dictionary mapping AFI/SAFI to EOS key representation.""" + + +class BgpAddressFamily(BaseModel): + """Model for a BGP address family.""" + + model_config = ConfigDict(extra="forbid") + afi: Afi + """BGP Address Family Identifier (AFI).""" + safi: Safi | None = None + """BGP Subsequent Address Family Identifier (SAFI). Required when `afi` is `ipv4` or `ipv6`.""" + vrf: str = "default" + """Optional VRF when `afi` is `ipv4` or `ipv6`. Defaults to `default`. + + If the input `afi` is NOT `ipv4` or `ipv6` (e.g. `evpn`, `vpn-ipv4`, etc.), the `vrf` must be `default`. + + These AFIs operate at a global level and do not use the VRF concept in the same way as IPv4/IPv6. + """ + num_peers: PositiveInt | None = None + """Number of expected established BGP peers with negotiated AFI/SAFI. Required field in the `VerifyBGPPeerCount` test.""" + peers: list[IPv4Address | IPv6Address] | None = None + """List of expected IPv4/IPv6 BGP peers supporting the AFI/SAFI. Required field in the `VerifyBGPSpecificPeers` test.""" + + @model_validator(mode="after") + def validate_inputs(self) -> Self: + """Validate the inputs provided to the BgpAddressFamily class. + + If `afi` is either `ipv4` or `ipv6`, `safi` must be provided. + + If `afi` is not `ipv4` or `ipv6`, `safi` must NOT be provided and `vrf` must be `default`. + """ + if self.afi in ["ipv4", "ipv6"]: + if self.safi is None: + msg = "'safi' must be provided when afi is ipv4 or ipv6" + raise ValueError(msg) + elif self.safi is not None: + msg = "'safi' must not be provided when afi is not ipv4 or ipv6" + raise ValueError(msg) + elif self.vrf != "default": + msg = "'vrf' must be default when afi is not ipv4 or ipv6" + raise ValueError(msg) + return self + + @property + def eos_key(self) -> str: + """AFI/SAFI EOS key representation.""" + return AFI_SAFI_EOS_KEY[(self.afi, self.safi)] + + def __str__(self) -> str: + """Return a string representation of the BgpAddressFamily model. Used in failure messages.""" + base_string = f"AFI:{self.afi}" + if self.safi is not None: + base_string += f" SAFI:{self.safi}" + if self.afi in ["ipv4", "ipv6"]: + base_string += f" VRF:{self.vrf}" + base_string += " -" + return base_string + + +class BgpAfi(BgpAddressFamily): + """Alias for the BgpAddressFamily model to maintain backward compatibility. + + When initialized, it will emit a depreciation warning and call the BgpAddressFamily model. + + TODO: Remove this class in ANTA v2.0.0. + """ + + def __init__(self, **data: Any) -> None: # noqa: ANN401 + """Initialize the BgpAfi class, emitting a depreciation warning.""" + warn( + message="BgpAfi model is deprecated and will be removed in ANTA v2.0.0. Use the BgpAddressFamily model instead.", + category=DeprecationWarning, + stacklevel=2, + ) + super().__init__(**data) diff --git a/anta/tools.py b/anta/tools.py index b00ccf7f9..a83c4cbf3 100644 --- a/anta/tools.py +++ b/anta/tools.py @@ -377,7 +377,7 @@ def safe_command(command: str) -> str: def convert_categories(categories: list[str]) -> list[str]: """Convert categories for reports. - if the category is part of the defined acronym, transform it to upper case + If the category is part of the defined acronym, transform it to upper case otherwise capitalize the first letter. Parameters @@ -394,3 +394,45 @@ def convert_categories(categories: list[str]) -> list[str]: return [" ".join(word.upper() if word.lower() in ACRONYM_CATEGORIES else word.title() for word in category.split()) for category in categories] msg = f"Wrong input type '{type(categories)}' for convert_categories." raise TypeError(msg) + + +def check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool: + """Check if a BGP neighbor capability is advertised, received, and enabled. + + Parameters + ---------- + capability_status + A dictionary containing the capability status. + + Returns + ------- + bool + True if the capability is advertised, received, and enabled, False otherwise. + + Example + ------- + >>> check_bgp_neighbor_capability({"advertised": True, "received": True, "enabled": True}) + True + """ + return all(capability_status.get(state, False) for state in ("advertised", "received", "enabled")) + + +def format_data(data: dict[str, bool]) -> str: + """Format a data dictionary for logging purposes. + + Parameters + ---------- + data + A dictionary containing the data to format. + + Returns + ------- + str + The formatted data. + + Example + ------- + >>> format_data({"advertised": True, "received": True, "enabled": True}) + "Advertised:True, Received:True, Enabled:True" + """ + return ", ".join(f"{k.capitalize()}:{v}" for k, v in data.items()) From 3a8fcd854038ec6caa13d389be3435c90065d882 Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Fri, 18 Oct 2024 16:10:18 -0400 Subject: [PATCH 02/15] Fix docstring --- anta/tests/routing/bgp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 8a19e6a97..595548a79 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -140,7 +140,7 @@ class Input(AntaTest.Input): @field_validator("address_families") @classmethod def validate_address_families(cls, address_families: list[BgpAddressFamily]) -> list[BgpAddressFamily]: - """Validate that 'peers' field is provided in each address family.""" + """Validate that 'num_peers' field is provided in each address family.""" for af in address_families: if af.num_peers is None: msg = f"{af} 'num_peers' field missing in the input" From 50ad09e290858831acb95497840cb56b0f84fd3a Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Thu, 24 Oct 2024 12:01:28 -0400 Subject: [PATCH 03/15] Added queues knob --- anta/tests/routing/bgp.py | 37 +++++++++++++++----------- anta/tests/routing/bgp_input_models.py | 6 ++++- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 595548a79..2df0847c7 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -159,7 +159,7 @@ def test(self) -> None: # Check if the VRF is configured if (vrf_output := get_value(output, f"vrfs.{address_family.vrf}")) is None: - self.result.is_failure(f"{address_family} VRF not configured") + self.result.is_failure(f"{address_family} - VRF not configured") continue # Count the number of established peers with negotiated AFI/SAFI @@ -169,7 +169,7 @@ def test(self) -> None: # Check if the count matches the expected count if address_family.num_peers != peer_count: - self.result.is_failure(f"{address_family} Expected: {address_family.num_peers}, Actual: {peer_count}") + self.result.is_failure(f"{address_family} - Expected: {address_family.num_peers}, Actual: {peer_count}") class VerifyBGPPeersHealth(AntaTest): @@ -183,6 +183,7 @@ class VerifyBGPPeersHealth(AntaTest): - Verifies that the BGP session is in the `Established` state. - Confirms that the AFI/SAFI state is `negotiated`. - Checks that both input and output TCP message queues are empty. + Can be disabled by setting `check_tcp_queues` to `False`. Expected Results ---------------- @@ -192,7 +193,7 @@ class VerifyBGPPeersHealth(AntaTest): - No peers are found for a given AFI/SAFI. - Any BGP session is not in the `Established` state. - The AFI/SAFI state is not 'negotiated' for any peer. - - Any TCP message queue (input or output) is not empty. + - Any TCP message queue (input or output) is not empty when `check_tcp_queues` is `True` (default). Examples -------- @@ -208,6 +209,7 @@ class VerifyBGPPeersHealth(AntaTest): - afi: "ipv6" safi: "unicast" vrf: "DEV" + check_tcp_queues: false ``` """ @@ -233,7 +235,7 @@ def test(self) -> None: for address_family in self.inputs.address_families: # Check if the VRF is configured if (vrf_output := get_value(output, f"vrfs.{address_family.vrf}")) is None: - self.result.is_failure(f"{address_family} VRF not configured") + self.result.is_failure(f"{address_family} - VRF not configured") continue # Check if any peers are found for this AFI/SAFI @@ -242,22 +244,24 @@ def test(self) -> None: ] if not relevant_peers: - self.result.is_failure(f"{address_family} No peers found") + self.result.is_failure(f"{address_family} - No peers found") continue for peer in relevant_peers: # Check if the BGP session is established if peer["state"] != "Established": - self.result.is_failure(f"{address_family} Peer:{peer['peerAddress']} session state is not established; State:{peer['state']}") + self.result.is_failure(f"{address_family} - Peer:{peer['peerAddress']} session state is not established; State:{peer['state']}") # Check if the AFI/SAFI state is negotiated capability_status = get_value(peer, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") if not check_bgp_neighbor_capability(capability_status): - self.result.is_failure(f"{address_family} Peer:{peer['peerAddress']} AFI/SAFI state is not negotiated; {format_data(capability_status)}") + self.result.is_failure(f"{address_family} - Peer:{peer['peerAddress']} AFI/SAFI state is not negotiated; {format_data(capability_status)}") # Check the TCP session message queues - if (inq := peer["peerTcpInfo"]["inputQueueLength"]) != 0 or (outq := peer["peerTcpInfo"]["outputQueueLength"]) != 0: - self.result.is_failure(f"{address_family} Peer:{peer['peerAddress']} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") + if address_family.check_tcp_queues and ( + (inq := peer["peerTcpInfo"]["inputQueueLength"]) != 0 or (outq := peer["peerTcpInfo"]["outputQueueLength"]) != 0 + ): + self.result.is_failure(f"{address_family} - Peer:{peer['peerAddress']} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") class VerifyBGPSpecificPeers(AntaTest): @@ -271,6 +275,7 @@ class VerifyBGPSpecificPeers(AntaTest): - Checks that the BGP session is in the `Established` state. - Confirms that the AFI/SAFI state is `negotiated`. - Ensures that both input and output TCP message queues are empty. + Can be disabled by setting `check_tcp_queues` to `False`. Expected Results ---------------- @@ -280,7 +285,7 @@ class VerifyBGPSpecificPeers(AntaTest): - A specified peer is not found in the BGP configuration. - The BGP session for a peer is not in the `Established` state. - The AFI/SAFI state is not `negotiated` for a peer. - - Any TCP message queue (input or output) is not empty for a peer. + - Any TCP message queue (input or output) is not empty for a peer when `check_tcp_queues` is `True` (default). Examples -------- @@ -333,7 +338,7 @@ def test(self) -> None: for address_family in self.inputs.address_families: # Check if the VRF is configured if (vrf_output := get_value(output, f"vrfs.{address_family.vrf}")) is None: - self.result.is_failure(f"{address_family} VRF not configured") + self.result.is_failure(f"{address_family} - VRF not configured") continue for peer in address_family.peers: @@ -341,12 +346,12 @@ def test(self) -> None: # Check if the peer is found if (peer_data := get_item(vrf_output["peerList"], "peerAddress", peer_ip)) is None: - self.result.is_failure(f"{address_family} Peer:{peer_ip} not found") + self.result.is_failure(f"{address_family} - Peer:{peer_ip} not found") continue # Check if the BGP session is established if peer_data["state"] != "Established": - self.result.is_failure(f"{address_family} Peer:{peer_ip} session state is not established; State:{peer_data['state']}") + self.result.is_failure(f"{address_family} - Peer:{peer_ip} session state is not established; State:{peer_data['state']}") # Check if the AFI/SAFI state is negotiated capability_status = get_value(peer_data, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") @@ -354,8 +359,10 @@ def test(self) -> None: self.result.is_failure(f"{address_family} Peer:{peer_ip} AFI/SAFI state is not negotiated; {format_data(capability_status)}") # Check the TCP session message queues - if (inq := peer_data["peerTcpInfo"]["inputQueueLength"]) != 0 or (outq := peer_data["peerTcpInfo"]["outputQueueLength"]) != 0: - self.result.is_failure(f"{address_family} Peer:{peer_ip} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") + if address_family.check_tcp_queues and ( + (inq := peer_data["peerTcpInfo"]["inputQueueLength"]) != 0 or (outq := peer_data["peerTcpInfo"]["outputQueueLength"]) != 0 + ): + self.result.is_failure(f"{address_family} - Peer:{peer_ip} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") class VerifyBGPExchangedRoutes(AntaTest): diff --git a/anta/tests/routing/bgp_input_models.py b/anta/tests/routing/bgp_input_models.py index 351e628cb..d0e5049cc 100644 --- a/anta/tests/routing/bgp_input_models.py +++ b/anta/tests/routing/bgp_input_models.py @@ -59,6 +59,11 @@ class BgpAddressFamily(BaseModel): """Number of expected established BGP peers with negotiated AFI/SAFI. Required field in the `VerifyBGPPeerCount` test.""" peers: list[IPv4Address | IPv6Address] | None = None """List of expected IPv4/IPv6 BGP peers supporting the AFI/SAFI. Required field in the `VerifyBGPSpecificPeers` test.""" + check_tcp_queues: bool = True + """Flag to check if the TCP session queues are empty for a BGP peer. Defaults to `True`. + + Can be disabled in the `VerifyBGPPeersHealth` and `VerifyBGPSpecificPeers` tests. + """ @model_validator(mode="after") def validate_inputs(self) -> Self: @@ -92,7 +97,6 @@ def __str__(self) -> str: base_string += f" SAFI:{self.safi}" if self.afi in ["ipv4", "ipv6"]: base_string += f" VRF:{self.vrf}" - base_string += " -" return base_string From c952502a5688f530e89b353795e44af399eb1e60 Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Fri, 25 Oct 2024 13:01:57 -0400 Subject: [PATCH 04/15] Update unit tests for VerifyBGPPeerCount --- anta/tests/routing/bgp_input_models.py | 9 +- tests/units/anta_tests/routing/test_bgp.py | 544 +++++---------------- 2 files changed, 141 insertions(+), 412 deletions(-) diff --git a/anta/tests/routing/bgp_input_models.py b/anta/tests/routing/bgp_input_models.py index d0e5049cc..282738220 100644 --- a/anta/tests/routing/bgp_input_models.py +++ b/anta/tests/routing/bgp_input_models.py @@ -88,10 +88,17 @@ def validate_inputs(self) -> Self: @property def eos_key(self) -> str: """AFI/SAFI EOS key representation.""" + # 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)] def __str__(self) -> str: - """Return a string representation of the BgpAddressFamily model. Used in failure messages.""" + """Return a string representation of the BgpAddressFamily model. Used in failure messages. + + Examples + -------- + - AFI:ipv4 SAFI:unicast VRF:default + - AFI:evpn + """ base_string = f"AFI:{self.afi}" if self.safi is not None: base_string += f" SAFI:{self.safi}" diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index e256b04dd..97957de90 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -32,548 +32,272 @@ "name": "success", "test": VerifyBGPPeerCount, "eos_data": [ - # Need to order the output as the commands would be sorted after template rendering. { "vrfs": { "default": { + "vrf": "default", + "routerId": "10.1.0.3", + "asn": "65120", "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - }, - }, - { - "vrfs": { - "MGMT": { - "peers": { - "10.255.0.21": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.0.1": { "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, - }, - }, - }, - }, - { - "vrfs": { - "default": { - "peers": { - "10.255.0.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.0.2": { "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, - "10.255.0.2": { - "description": "DC1-SPINE2_Ethernet1", - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.254.1": { "peerState": "Established", + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17}, }, - }, - }, - }, - }, - { - "vrfs": { - "default": { - "peers": { - "10.255.0.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.255.0": { "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, }, - "10.255.0.12": { - "description": "DC1-SPINE2_Ethernet1", - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.255.2": { "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, }, }, }, - }, - }, - { - "vrfs": { - "default": { + "DEV": { + "vrf": "DEV", + "routerId": "10.1.0.3", + "asn": "65120", "peers": { - "10.255.0.21": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.255.0.22": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.254.1": { "peerState": "Established", - }, + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, + } }, }, - }, + } }, ], "inputs": { "address_families": [ - # evpn first to make sure that the correct mapping output to input is kept. {"afi": "evpn", "num_peers": 2}, - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 1}, - {"afi": "link-state", "num_peers": 2}, - {"afi": "path-selection", "num_peers": 2}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1}, ] }, "expected": {"result": "success"}, }, { - "name": "failure-wrong-count", + "name": "failure-vrf-not-configured", "test": VerifyBGPPeerCount, "eos_data": [ { "vrfs": { "default": { + "vrf": "default", + "routerId": "10.1.0.3", + "asn": "65120", "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - }, - }, - { - "vrfs": { - "MGMT": { - "peers": { - "10.255.0.21": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.0.1": { "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, - }, - }, - }, - }, - { - "vrfs": { - "default": { - "peers": { - "10.255.0.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.0.2": { "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, - "10.255.0.2": { - "description": "DC1-SPINE2_Ethernet1", - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.254.1": { "peerState": "Established", + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17}, }, - }, - }, - }, - }, - { - "vrfs": { - "default": { - "peers": { - "10.255.0.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.255.0": { "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, }, - "10.255.0.12": { - "description": "DC1-SPINE2_Ethernet1", - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.255.2": { "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, }, }, }, - }, - }, - { - "vrfs": { - "default": { + "DEV": { + "vrf": "DEV", + "routerId": "10.1.0.3", + "asn": "65120", "peers": { - "10.255.0.21": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.254.1": { "peerState": "Established", - }, + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, + } }, }, - }, - }, - ], - "inputs": { - "address_families": [ - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 2}, - {"afi": "evpn", "num_peers": 1}, - {"afi": "link-state", "num_peers": 3}, - {"afi": "path-selection", "num_peers": 3}, - ] - }, - "expected": { - "result": "failure", - "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': 'Expected: 3, Actual: 2'}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Expected: 2, Actual: 1'}}, " - "{'afi': 'evpn', 'vrfs': {'default': 'Expected: 1, Actual: 2'}}, " - "{'afi': 'link-state', 'vrfs': {'default': 'Expected: 3, Actual: 2'}}, " - "{'afi': 'path-selection', 'vrfs': {'default': 'Expected: 3, Actual: 1'}}]" - ], - }, - }, - { - "name": "failure-no-peers", - "test": VerifyBGPPeerCount, - "eos_data": [ - { - "vrfs": { - "default": { - "peers": {}, - } - } - }, - { - "vrfs": { - "MGMT": { - "peers": {}, - } - } - }, - { - "vrfs": { - "default": { - "peers": {}, - } - } - }, - { - "vrfs": { - "default": { - "peers": {}, - } - } - }, - { - "vrfs": { - "default": { - "peers": {}, - } } }, ], "inputs": { "address_families": [ - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 1}, - {"afi": "evpn", "num_peers": 2}, - {"afi": "link-state", "num_peers": 2}, - {"afi": "path-selection", "num_peers": 2}, - ] - }, - "expected": { - "result": "failure", - "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': 'Expected: 2, Actual: 0'}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Expected: 1, Actual: 0'}}, " - "{'afi': 'evpn', 'vrfs': {'default': 'Expected: 2, Actual: 0'}}, " - "{'afi': 'link-state', 'vrfs': {'default': 'Expected: 2, Actual: 0'}}, " - "{'afi': 'path-selection', 'vrfs': {'default': 'Expected: 2, Actual: 0'}}]" - ], - }, - }, - { - "name": "failure-not-configured", - "test": VerifyBGPPeerCount, - "eos_data": [{"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}], - "inputs": { - "address_families": [ - {"afi": "ipv6", "safi": "multicast", "vrf": "DEV", "num_peers": 3}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 1}, {"afi": "evpn", "num_peers": 2}, - {"afi": "link-state", "num_peers": 2}, - {"afi": "path-selection", "num_peers": 2}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, + {"afi": "ipv4", "safi": "unicast", "vrf": "PROD", "num_peers": 2}, ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv6', 'safi': 'multicast', 'vrfs': {'DEV': 'Not Configured'}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Not Configured'}}, " - "{'afi': 'evpn', 'vrfs': {'default': 'Not Configured'}}, " - "{'afi': 'link-state', 'vrfs': {'default': 'Not Configured'}}, " - "{'afi': 'path-selection', 'vrfs': {'default': 'Not Configured'}}]" + "AFI:ipv4 SAFI:unicast VRF:PROD - VRF not configured", ], }, }, { - "name": "success-vrf-all", + "name": "failure-afi-safi-not-negotiated", "test": VerifyBGPPeerCount, "eos_data": [ { "vrfs": { "default": { + "vrf": "default", + "routerId": "10.1.0.3", + "asn": "65120", "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - "PROD": { - "peers": { - "10.1.254.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "192.168.1.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - }, - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.10": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - "PROD": { - "peers": { - "10.1.254.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.0.1": { "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, - }, - }, - }, - }, - ], - "inputs": { - "address_families": [ - {"afi": "ipv4", "safi": "unicast", "vrf": "all", "num_peers": 3}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "all", "num_peers": 2}, - ] - }, - "expected": {"result": "success"}, - }, - { - "name": "failure-vrf-all", - "test": VerifyBGPPeerCount, - "eos_data": [ - { - "vrfs": { - "default": { - "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.0.2": { "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, - }, - }, - "PROD": { - "peers": { "10.1.254.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, "peerState": "Established", + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17}, }, - "192.168.1.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.255.0": { "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, }, - }, - }, - }, - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.10": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.255.2": { "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, }, }, }, - "PROD": { + "DEV": { + "vrf": "DEV", + "routerId": "10.1.0.3", + "asn": "65120", "peers": { "10.1.254.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, "peerState": "Established", - }, - "192.168.1.12": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, + } }, }, - }, + } }, ], "inputs": { "address_families": [ - {"afi": "ipv4", "safi": "unicast", "vrf": "all", "num_peers": 5}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "all", "num_peers": 2}, + {"afi": "evpn", "num_peers": 2}, + {"afi": "vpn-ipv4", "num_peers": 2}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1}, ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'all': 'Expected: 5, Actual: 3'}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'all': 'Expected: 2, Actual: 3'}}]" + "AFI:vpn-ipv4 - Expected: 2, Actual: 0", ], }, }, { - "name": "failure-multiple-afi", + "name": "failure-wrong-count", "test": VerifyBGPPeerCount, "eos_data": [ - { - "vrfs": { - "PROD": { - "peers": { - "10.1.254.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "192.168.1.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - }, - }, - {"vrfs": {}}, - { - "vrfs": { - "MGMT": { - "peers": { - "10.1.254.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "192.168.1.21": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - }, - }, { "vrfs": { "default": { + "vrf": "default", + "routerId": "10.1.0.3", + "asn": "65120", "peers": { "10.1.0.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, "10.1.0.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, - }, - }, - }, - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.0.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.254.1": { "peerState": "Established", + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17}, }, - "10.1.0.21": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.255.0": { "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, + }, + "10.1.255.2": { + "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, }, }, }, - }, - }, - { - "vrfs": { - "default": { + "DEV": { + "vrf": "DEV", + "routerId": "10.1.0.3", + "asn": "65120", "peers": { - "10.1.0.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.0.22": { - "inMsgQueue": 0, - "outMsgQueue": 0, + "10.1.254.1": { "peerState": "Established", - }, + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, + } }, }, - }, + } }, ], "inputs": { "address_families": [ - {"afi": "ipv4", "safi": "unicast", "vrf": "PROD", "num_peers": 3}, - {"afi": "ipv6", "safi": "unicast", "vrf": "default", "num_peers": 3}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 3}, {"afi": "evpn", "num_peers": 3}, - {"afi": "link-state", "num_peers": 4}, - {"afi": "path-selection", "num_peers": 1}, - ], + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 2}, + ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'PROD': 'Expected: 3, Actual: 2'}}, " - "{'afi': 'ipv6', 'safi': 'unicast', 'vrfs': {'default': 'Not Configured'}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Expected: 3, Actual: 2'}}, " - "{'afi': 'evpn', 'vrfs': {'default': 'Expected: 3, Actual: 2'}}, " - "{'afi': 'link-state', 'vrfs': {'default': 'Expected: 4, Actual: 2'}}, " - "{'afi': 'path-selection', 'vrfs': {'default': 'Expected: 1, Actual: 2'}}]", + "AFI:evpn - Expected: 3, Actual: 2", + "AFI:ipv4 SAFI:unicast VRF:DEV - Expected: 2, Actual: 1", ], }, }, @@ -656,11 +380,9 @@ ], "inputs": { "address_families": [ - # Path selection first to make sure input to output mapping is correct. - {"afi": "path-selection"}, + {"afi": "evpn"}, {"afi": "ipv4", "safi": "unicast", "vrf": "default"}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"}, - {"afi": "link-state"}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV"}, ] }, "expected": {"result": "success"}, From 9f55019d8127b2b3811ab967c57780b07afb6eef Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Tue, 29 Oct 2024 14:29:32 -0400 Subject: [PATCH 05/15] Updated unit tests --- anta/input_models/routing/__init__.py | 4 + .../routing/bgp.py} | 0 anta/tests/routing/bgp.py | 45 +- anta/tools.py | 21 - tests/units/anta_tests/routing/test_bgp.py | 916 +++++++----------- tests/units/test_tools.py | 16 +- 6 files changed, 427 insertions(+), 575 deletions(-) create mode 100644 anta/input_models/routing/__init__.py rename anta/{tests/routing/bgp_input_models.py => input_models/routing/bgp.py} (100%) diff --git a/anta/input_models/routing/__init__.py b/anta/input_models/routing/__init__.py new file mode 100644 index 000000000..ed658a595 --- /dev/null +++ b/anta/input_models/routing/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Module related to routing tests input.""" diff --git a/anta/tests/routing/bgp_input_models.py b/anta/input_models/routing/bgp.py similarity index 100% rename from anta/tests/routing/bgp_input_models.py rename to anta/input_models/routing/bgp.py diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 2df0847c7..297f501a3 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -15,10 +15,9 @@ from pydantic_extra_types.mac_address import MacAddress from anta.custom_types import BgpDropStats, BgpUpdateError, MultiProtocolCaps, Vni +from anta.input_models.routing.bgp import BgpAddressFamily, BgpAfi from anta.models import AntaCommand, AntaTemplate, AntaTest -from anta.tools import check_bgp_neighbor_capability, format_data, get_item, get_value - -from .bgp_input_models import BgpAddressFamily, BgpAfi +from anta.tools import format_data, get_item, get_value if TYPE_CHECKING: import sys @@ -84,6 +83,27 @@ def _add_bgp_routes_failure( return failure_routes +def _check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool: + """Check if a BGP neighbor capability is advertised, received, and enabled. + + Parameters + ---------- + capability_status + A dictionary containing the capability status. + + Returns + ------- + bool + True if the capability is advertised, received, and enabled, False otherwise. + + Example + ------- + >>> _check_bgp_neighbor_capability({"advertised": True, "received": True, "enabled": True}) + True + """ + return all(capability_status.get(state, False) for state in ("advertised", "received", "enabled")) + + class VerifyBGPPeerCount(AntaTest): """Verifies the count of established BGP peers with negotiated AFI/SAFI for given address families. @@ -254,13 +274,13 @@ def test(self) -> None: # Check if the AFI/SAFI state is negotiated capability_status = get_value(peer, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") - if not check_bgp_neighbor_capability(capability_status): + if not _check_bgp_neighbor_capability(capability_status): self.result.is_failure(f"{address_family} - Peer:{peer['peerAddress']} AFI/SAFI state is not negotiated; {format_data(capability_status)}") # Check the TCP session message queues - if address_family.check_tcp_queues and ( - (inq := peer["peerTcpInfo"]["inputQueueLength"]) != 0 or (outq := peer["peerTcpInfo"]["outputQueueLength"]) != 0 - ): + inq = peer["peerTcpInfo"]["inputQueueLength"] + outq = peer["peerTcpInfo"]["outputQueueLength"] + if address_family.check_tcp_queues and (inq != 0 or outq != 0): self.result.is_failure(f"{address_family} - Peer:{peer['peerAddress']} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") @@ -355,13 +375,16 @@ def test(self) -> None: # Check if the AFI/SAFI state is negotiated capability_status = get_value(peer_data, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") - if not check_bgp_neighbor_capability(capability_status): + if not capability_status: + self.result.is_failure(f"{address_family} Peer:{peer_ip} AFI/SAFI state is not negotiated") + + if capability_status and not _check_bgp_neighbor_capability(capability_status): self.result.is_failure(f"{address_family} Peer:{peer_ip} AFI/SAFI state is not negotiated; {format_data(capability_status)}") # Check the TCP session message queues - if address_family.check_tcp_queues and ( - (inq := peer_data["peerTcpInfo"]["inputQueueLength"]) != 0 or (outq := peer_data["peerTcpInfo"]["outputQueueLength"]) != 0 - ): + inq = peer_data["peerTcpInfo"]["inputQueueLength"] + outq = peer_data["peerTcpInfo"]["outputQueueLength"] + if address_family.check_tcp_queues and (inq != 0 or outq != 0): self.result.is_failure(f"{address_family} - Peer:{peer_ip} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") diff --git a/anta/tools.py b/anta/tools.py index a83c4cbf3..b215a111c 100644 --- a/anta/tools.py +++ b/anta/tools.py @@ -396,27 +396,6 @@ def convert_categories(categories: list[str]) -> list[str]: raise TypeError(msg) -def check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool: - """Check if a BGP neighbor capability is advertised, received, and enabled. - - Parameters - ---------- - capability_status - A dictionary containing the capability status. - - Returns - ------- - bool - True if the capability is advertised, received, and enabled, False otherwise. - - Example - ------- - >>> check_bgp_neighbor_capability({"advertised": True, "received": True, "enabled": True}) - True - """ - return all(capability_status.get(state, False) for state in ("advertised", "received", "enabled")) - - def format_data(data: dict[str, bool]) -> str: """Format a data dictionary for logging purposes. diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 97957de90..3d7d7e0f1 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -308,75 +308,33 @@ { "vrfs": { "default": { - "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { - "MGMT": { - "peers": { - "10.1.255.10": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.12": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.20": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.22": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, - } - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.30": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + { + "peerAddress": "10.100.0.13", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - "10.1.255.32": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + ] + }, + "DEV": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, - } + ] + }, } - }, + } ], "inputs": { "address_families": [ @@ -388,81 +346,12 @@ "expected": {"result": "success"}, }, { - "name": "failure-issues", + "name": "failure-vrf-not-configured", "test": VerifyBGPPeersHealth, "eos_data": [ { - "vrfs": { - "default": { - "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", - }, - "10.1.255.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { - "MGMT": { - "peers": { - "10.1.255.10": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.12": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.20": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", - }, - "10.1.255.22": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.30": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.32": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", - }, - }, - } - } - }, + "vrfs": {}, + } ], "inputs": { "address_families": [ @@ -475,559 +364,502 @@ "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': {'10.1.255.0': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': {'10.1.255.12': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, " - "{'afi': 'path-selection', 'vrfs': {'default': {'10.1.255.20': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, " - "{'afi': 'link-state', 'vrfs': {'default': {'10.1.255.32': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}]" + "AFI:ipv4 SAFI:unicast VRF:default - VRF not configured", + "AFI:ipv4 SAFI:sr-te VRF:MGMT - VRF not configured", + "AFI:path-selection - VRF not configured", + "AFI:link-state - VRF not configured", + ], + }, + }, + { + "name": "failure-peer-not-found", + "test": VerifyBGPPeersHealth, + "eos_data": [{"vrfs": {"default": {"peerList": []}, "MGMT": {"peerList": []}}}], + "inputs": { + "address_families": [ + {"afi": "ipv4", "safi": "unicast", "vrf": "default"}, + {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"}, + {"afi": "path-selection"}, + {"afi": "link-state"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "AFI:ipv4 SAFI:unicast VRF:default - No peers found", + "AFI:ipv4 SAFI:sr-te VRF:MGMT - No peers found", + "AFI:path-selection - No peers found", + "AFI:link-state - No peers found", ], }, }, { - "name": "success-vrf-all", + "name": "failure-session-not-established", "test": VerifyBGPPeersHealth, "eos_data": [ { "vrfs": { "default": { - "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - "PROD": { - "peers": { - "10.1.254.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "192.168.1.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, - }, - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.10": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + { + "peerAddress": "10.100.0.13", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"dps": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - "10.1.255.12": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + { + "peerAddress": "10.100.0.14", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"linkState": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, + ] }, - "PROD": { - "peers": { - "10.1.254.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "192.168.1.111": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4SrTe": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, + ] }, } - }, + } ], "inputs": { "address_families": [ - {"afi": "ipv4", "safi": "unicast", "vrf": "all"}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "all"}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default"}, + {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"}, + {"afi": "path-selection"}, + {"afi": "link-state"}, ] }, "expected": { - "result": "success", + "result": "failure", + "messages": [ + "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:path-selection - Peer:10.100.0.13 session state is not established; State:Idle", + "AFI:link-state - Peer:10.100.0.14 session state is not established; State:Idle", + ], }, }, { - "name": "failure-issues-vrf-all", + "name": "failure-afi-not-negotiated", "test": VerifyBGPPeersHealth, "eos_data": [ { "vrfs": { "default": { - "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", - }, - "10.1.255.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - }, - "PROD": { - "peers": { - "10.1.254.1": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "192.168.1.11": { - "inMsgQueue": 100, - "outMsgQueue": 200, - "peerState": "Established", + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": False, "received": False, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, - }, - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.10": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", + { + "peerAddress": "10.100.0.13", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"dps": {"advertised": True, "received": False, "enabled": False}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - "10.1.255.12": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + { + "peerAddress": "10.100.0.14", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"linkState": {"advertised": False, "received": False, "enabled": False}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, + ] }, - "PROD": { - "peers": { - "10.1.254.11": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "192.168.1.111": { - "inMsgQueue": 100, - "outMsgQueue": 200, - "peerState": "Established", + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4SrTe": {"advertised": False, "received": False, "enabled": False}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, + ] }, } - }, + } ], "inputs": { "address_families": [ - {"afi": "ipv4", "safi": "unicast", "vrf": "all"}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "all"}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default"}, + {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"}, + {"afi": "path-selection"}, + {"afi": "link-state"}, ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': {'10.1.255.0': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}, " - "'PROD': {'192.168.1.11': {'peerState': 'Established', 'inMsgQueue': 100, 'outMsgQueue': 200}}}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'default': {'10.1.255.10': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}, " - "'PROD': {'192.168.1.111': {'peerState': 'Established', 'inMsgQueue': 100, 'outMsgQueue': 200}}}}]" + "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:True", + "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:False", + "AFI:path-selection - Peer:10.100.0.13 session state is not established; State:Idle", + "AFI:path-selection - Peer:10.100.0.13 AFI/SAFI state is not negotiated; Advertised:True, Received:False, Enabled:False", + "AFI:link-state - Peer:10.100.0.14 session state is not established; State:Idle", + "AFI:link-state - Peer:10.100.0.14 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:False", ], }, }, { - "name": "failure-not-configured", + "name": "failure-tcp-queues", "test": VerifyBGPPeersHealth, - "eos_data": [{"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}], + "eos_data": [ + { + "vrfs": { + "default": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 4, "inputQueueLength": 2}, + }, + { + "peerAddress": "10.100.0.13", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"dps": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 1, "inputQueueLength": 1}, + }, + { + "peerAddress": "10.100.0.14", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"linkState": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 2, "inputQueueLength": 3}, + }, + ] + }, + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4SrTe": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 1, "inputQueueLength": 5}, + }, + ] + }, + } + } + ], "inputs": { "address_families": [ - {"afi": "ipv4", "safi": "unicast", "vrf": "DEV"}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default"}, {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"}, - {"afi": "link-state"}, {"afi": "path-selection"}, + {"afi": "link-state"}, ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'DEV': 'Not Configured'}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Not Configured'}}, " - "{'afi': 'link-state', 'vrfs': {'default': 'Not Configured'}}, " - "{'afi': 'path-selection', 'vrfs': {'default': 'Not Configured'}}]" + "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session has non-empty message queues; InQ: 2, OutQ: 4", + "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session has non-empty message queues; InQ: 5, OutQ: 1", + "AFI:path-selection - Peer:10.100.0.13 session state is not established; State:Idle", + "AFI:path-selection - Peer:10.100.0.13 session has non-empty message queues; InQ: 1, OutQ: 1", + "AFI:link-state - Peer:10.100.0.14 session state is not established; State:Idle", + "AFI:link-state - Peer:10.100.0.14 session has non-empty message queues; InQ: 3, OutQ: 2", ], }, }, { - "name": "failure-no-peers", - "test": VerifyBGPPeersHealth, + "name": "success", + "test": VerifyBGPSpecificPeers, "eos_data": [ { "vrfs": { "default": { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": {}, - } - } - }, - { - "vrfs": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + }, + { + "peerAddress": "10.100.0.13", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + }, + ] + }, "MGMT": { - "vrf": "MGMT", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": {}, - } + "peerList": [ + { + "peerAddress": "10.100.0.14", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + }, + ] + }, } - }, + } + ], + "inputs": { + "address_families": [ + {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]}, + {"afi": "evpn", "peers": ["10.100.0.13"]}, + {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]}, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "failure-peer-not-configured", + "test": VerifyBGPSpecificPeers, + "eos_data": [ { "vrfs": { "default": { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": {}, - } + "peerList": [ + { + "peerAddress": "10.100.0.20", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + } + ] + }, + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.10", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + }, + ] + }, } - }, + } + ], + "inputs": { + "address_families": [ + {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]}, + {"afi": "evpn", "peers": ["10.100.0.13"]}, + {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 not found", + "AFI:evpn - Peer:10.100.0.13 not found", + "AFI:ipv4 SAFI:unicast VRF:MGMT - Peer:10.100.0.14 not found", + ], + }, + }, + { + "name": "failure-vrf-not-configured", + "test": VerifyBGPSpecificPeers, + "eos_data": [ { - "vrfs": { - "default": { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": {}, - } - } - }, + "vrfs": {}, + } ], "inputs": { "address_families": [ - {"afi": "ipv4", "safi": "multicast"}, - {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"}, - {"afi": "link-state"}, - {"afi": "path-selection"}, + {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]}, + {"afi": "evpn", "peers": ["10.100.0.13"]}, + {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]}, ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'multicast', 'vrfs': {'default': 'No Peers'}}, {'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'No Peers'}}, " - "{'afi': 'link-state', 'vrfs': {'default': 'No Peers'}}, {'afi': 'path-selection', 'vrfs': {'default': 'No Peers'}}]" + "AFI:ipv4 SAFI:unicast VRF:default - VRF not configured", + "AFI:evpn - VRF not configured", + "AFI:ipv4 SAFI:unicast VRF:MGMT - VRF not configured", ], }, }, { - "name": "success", + "name": "failure-session-not-established", "test": VerifyBGPSpecificPeers, "eos_data": [ { "vrfs": { "default": { - "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + } + ] + }, "MGMT": { - "peers": { - "10.1.255.10": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.12": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.20": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.22": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.30": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.32": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", + "peerList": [ + { + "peerAddress": "10.100.0.14", + "state": "Idle", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, - } + ] + }, } - }, + } ], "inputs": { "address_families": [ - # Path selection first to make sure input to output mapping is correct. - {"afi": "path-selection", "peers": ["10.1.255.20", "10.1.255.22"]}, - { - "afi": "ipv4", - "safi": "unicast", - "vrf": "default", - "peers": ["10.1.255.0", "10.1.255.2"], - }, - { - "afi": "ipv4", - "safi": "sr-te", - "vrf": "MGMT", - "peers": ["10.1.255.10", "10.1.255.12"], - }, - {"afi": "link-state", "peers": ["10.1.255.30", "10.1.255.32"]}, + {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]}, + {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]}, ] }, - "expected": {"result": "success"}, + "expected": { + "result": "failure", + "messages": [ + "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:unicast VRF:MGMT - Peer:10.100.0.14 session state is not established; State:Idle", + ], + }, }, { - "name": "failure-issues", + "name": "failure-afi-safi-not-negotiated", "test": VerifyBGPSpecificPeers, "eos_data": [ { "vrfs": { "default": { - "peers": { - "10.1.255.0": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", - }, - "10.1.255.2": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": False, "received": False, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + } + ] + }, "MGMT": { - "peers": { - "10.1.255.10": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.12": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.20": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", - }, - "10.1.255.22": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "peers": { - "10.1.255.30": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Established", - }, - "10.1.255.32": { - "inMsgQueue": 0, - "outMsgQueue": 0, - "peerState": "Idle", + "peerList": [ + { + "peerAddress": "10.100.0.14", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": False, "received": False, "enabled": False}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, }, - }, - } + ] + }, } - }, + } ], "inputs": { "address_families": [ - { - "afi": "ipv4", - "safi": "unicast", - "vrf": "default", - "peers": ["10.1.255.0", "10.1.255.2"], - }, - { - "afi": "ipv4", - "safi": "sr-te", - "vrf": "MGMT", - "peers": ["10.1.255.10", "10.1.255.12"], - }, - {"afi": "path-selection", "peers": ["10.1.255.20", "10.1.255.22"]}, - {"afi": "link-state", "peers": ["10.1.255.30", "10.1.255.32"]}, + {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]}, + {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]}, ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': {'10.1.255.0': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': {'10.1.255.12': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, " - "{'afi': 'path-selection', 'vrfs': {'default': {'10.1.255.20': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, " - "{'afi': 'link-state', 'vrfs': {'default': {'10.1.255.32': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}]" + "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:True", + "AFI:ipv4 SAFI:unicast VRF:MGMT Peer:10.100.0.14 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:False", ], }, }, { - "name": "failure-not-configured", + "name": "failure-afi-safi-not-correct", "test": VerifyBGPSpecificPeers, - "eos_data": [{"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}], + "eos_data": [ + { + "vrfs": { + "default": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": False, "received": False, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + } + ] + }, + "MGMT": { + "peerList": [ + { + "peerAddress": "10.100.0.14", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": False, "received": False, "enabled": False}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0}, + }, + ] + }, + } + } + ], "inputs": { "address_families": [ - { - "afi": "ipv4", - "safi": "unicast", - "vrf": "DEV", - "peers": ["10.1.255.0"], - }, - { - "afi": "ipv4", - "safi": "sr-te", - "vrf": "MGMT", - "peers": ["10.1.255.10"], - }, - {"afi": "link-state", "peers": ["10.1.255.20"]}, - {"afi": "path-selection", "peers": ["10.1.255.30"]}, + {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]}, + {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]}, ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'DEV': 'Not Configured'}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Not Configured'}}, {'afi': 'link-state', 'vrfs': {'default': 'Not Configured'}}, " - "{'afi': 'path-selection', 'vrfs': {'default': 'Not Configured'}}]" + "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 AFI/SAFI state is not negotiated", + "AFI:ipv4 SAFI:unicast VRF:MGMT Peer:10.100.0.14 AFI/SAFI state is not negotiated", ], }, }, { - "name": "failure-no-peers", + "name": "failure-tcp-queues", "test": VerifyBGPSpecificPeers, "eos_data": [ { "vrfs": { "default": { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": {}, - } - } - }, - { - "vrfs": { + "peerList": [ + { + "peerAddress": "10.100.0.12", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 3, "inputQueueLength": 3}, + } + ] + }, "MGMT": { - "vrf": "MGMT", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": {}, - } - } - }, - { - "vrfs": { - "default": { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": {}, - } - } - }, - { - "vrfs": { - "default": { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": {}, - } + "peerList": [ + { + "peerAddress": "10.100.0.14", + "state": "Established", + "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}}, + "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 2, "inputQueueLength": 2}, + }, + ] + }, } - }, + } ], "inputs": { "address_families": [ - {"afi": "ipv4", "safi": "multicast", "peers": ["10.1.255.0"]}, - { - "afi": "ipv4", - "safi": "sr-te", - "vrf": "MGMT", - "peers": ["10.1.255.10"], - }, - {"afi": "link-state", "peers": ["10.1.255.20"]}, - {"afi": "path-selection", "peers": ["10.1.255.30"]}, + {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]}, + {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]}, ] }, "expected": { "result": "failure", "messages": [ - "Failures: [{'afi': 'ipv4', 'safi': 'multicast', 'vrfs': {'default': {'10.1.255.0': {'peerNotFound': True}}}}, " - "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': {'10.1.255.10': {'peerNotFound': True}}}}, " - "{'afi': 'link-state', 'vrfs': {'default': {'10.1.255.20': {'peerNotFound': True}}}}, " - "{'afi': 'path-selection', 'vrfs': {'default': {'10.1.255.30': {'peerNotFound': True}}}}]" + "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session has non-empty message queues; InQ: 3, OutQ: 3", + "AFI:ipv4 SAFI:unicast VRF:MGMT - Peer:10.100.0.14 session has non-empty message queues; InQ: 2, OutQ: 2", ], }, }, diff --git a/tests/units/test_tools.py b/tests/units/test_tools.py index 16f044333..b5410ee3c 100644 --- a/tests/units/test_tools.py +++ b/tests/units/test_tools.py @@ -11,7 +11,7 @@ import pytest -from anta.tools import convert_categories, custom_division, get_dict_superset, get_failed_logs, get_item, get_value +from anta.tools import convert_categories, custom_division, format_data, get_dict_superset, get_failed_logs, get_item, get_value TEST_GET_FAILED_LOGS_DATA = [ {"id": 1, "name": "Alice", "age": 30, "email": "alice@example.com"}, @@ -513,3 +513,17 @@ def test_convert_categories(test_input: list[str], expected_raise: AbstractConte """Test convert_categories.""" with expected_raise: assert convert_categories(test_input) == expected_result + + +@pytest.mark.parametrize( + ("input_data", "expected_output"), + [ + pytest.param({"advertised": True, "received": True, "enabled": True}, "Advertised:True, Received:True, Enabled:True"), + pytest.param({"advertised": False, "received": False}, "Advertised:False, Received:False"), + pytest.param({}, ""), + pytest.param({"test": True}, "Test:True"), + ], +) +def test_format_data(input_data: dict[str, bool], expected_output: str) -> None: + """Test format_data.""" + assert format_data(input_data) == expected_output From 85d92e37460db9155e024f021ffa982207bc2013 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Wed, 30 Oct 2024 04:05:18 -0400 Subject: [PATCH 06/15] Added unit tests for helper function, updated docstrings --- anta/input_models/routing/__init__.py | 2 +- anta/input_models/routing/bgp.py | 2 +- anta/tests/routing/bgp.py | 2 +- tests/units/anta_tests/routing/test_bgp.py | 28 +++++++++++----------- tests/units/test_tools.py | 17 +++++++++++++ 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/anta/input_models/routing/__init__.py b/anta/input_models/routing/__init__.py index ed658a595..e1188cc9f 100644 --- a/anta/input_models/routing/__init__.py +++ b/anta/input_models/routing/__init__.py @@ -1,4 +1,4 @@ # Copyright (c) 2023-2024 Arista Networks, Inc. # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. -"""Module related to routing tests input.""" +"""Package related to routing tests input models.""" diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 282738220..679f81ca6 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -1,7 +1,7 @@ # Copyright (c) 2023-2024 Arista Networks, Inc. # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. -"""Module containing input models for routing tests.""" +"""Module containing input models for routing BGP tests.""" from __future__ import annotations diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 297f501a3..a1aaeba6c 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -270,7 +270,7 @@ def test(self) -> None: for peer in relevant_peers: # Check if the BGP session is established if peer["state"] != "Established": - self.result.is_failure(f"{address_family} - Peer:{peer['peerAddress']} session state is not established; State:{peer['state']}") + self.result.is_failure(f"{address_family} Peer:{peer['peerAddress']} - Session state is not established; State:{peer['state']}") # Check if the AFI/SAFI state is negotiated capability_status = get_value(peer, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 3d7d7e0f1..9b9f49381 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -445,10 +445,10 @@ "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session state is not established; State:Idle", - "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session state is not established; State:Idle", - "AFI:path-selection - Peer:10.100.0.13 session state is not established; State:Idle", - "AFI:link-state - Peer:10.100.0.14 session state is not established; State:Idle", + "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 - Session state is not established; State:Idle", + "AFI:ipv4 SAFI:sr-te VRF:MGMT Peer:10.100.0.12 - Session state is not established; State:Idle", + "AFI:path-selection Peer:10.100.0.13 - Session state is not established; State:Idle", + "AFI:link-state Peer:10.100.0.14 - Session state is not established; State:Idle", ], }, }, @@ -504,13 +504,13 @@ "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 - Session state is not established; State:Idle", "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:True", - "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:sr-te VRF:MGMT Peer:10.100.0.12 - Session state is not established; State:Idle", "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:False", - "AFI:path-selection - Peer:10.100.0.13 session state is not established; State:Idle", + "AFI:path-selection Peer:10.100.0.13 - Session state is not established; State:Idle", "AFI:path-selection - Peer:10.100.0.13 AFI/SAFI state is not negotiated; Advertised:True, Received:False, Enabled:False", - "AFI:link-state - Peer:10.100.0.14 session state is not established; State:Idle", + "AFI:link-state Peer:10.100.0.14 - Session state is not established; State:Idle", "AFI:link-state - Peer:10.100.0.14 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:False", ], }, @@ -567,13 +567,13 @@ "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 - Session state is not established; State:Idle", "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session has non-empty message queues; InQ: 2, OutQ: 4", - "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session state is not established; State:Idle", + "AFI:ipv4 SAFI:sr-te VRF:MGMT Peer:10.100.0.12 - Session state is not established; State:Idle", "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session has non-empty message queues; InQ: 5, OutQ: 1", - "AFI:path-selection - Peer:10.100.0.13 session state is not established; State:Idle", + "AFI:path-selection Peer:10.100.0.13 - Session state is not established; State:Idle", "AFI:path-selection - Peer:10.100.0.13 session has non-empty message queues; InQ: 1, OutQ: 1", - "AFI:link-state - Peer:10.100.0.14 session state is not established; State:Idle", + "AFI:link-state Peer:10.100.0.14 - Session state is not established; State:Idle", "AFI:link-state - Peer:10.100.0.14 session has non-empty message queues; InQ: 3, OutQ: 2", ], }, @@ -729,8 +729,8 @@ "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session state is not established; State:Idle", - "AFI:ipv4 SAFI:unicast VRF:MGMT - Peer:10.100.0.14 session state is not established; State:Idle", + "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 - Session state is not established; State:Idle", + "AFI:ipv4 SAFI:unicast VRF:MGMT Peer:10.100.0.14 - Session state is not established; State:Idle", ], }, }, diff --git a/tests/units/test_tools.py b/tests/units/test_tools.py index b5410ee3c..8f0ecfe13 100644 --- a/tests/units/test_tools.py +++ b/tests/units/test_tools.py @@ -11,6 +11,7 @@ import pytest +from anta.tests.routing.bgp import _check_bgp_neighbor_capability from anta.tools import convert_categories, custom_division, format_data, get_dict_superset, get_failed_logs, get_item, get_value TEST_GET_FAILED_LOGS_DATA = [ @@ -527,3 +528,19 @@ def test_convert_categories(test_input: list[str], expected_raise: AbstractConte def test_format_data(input_data: dict[str, bool], expected_output: str) -> None: """Test format_data.""" assert format_data(input_data) == expected_output + + +@pytest.mark.parametrize( + ("input_dict", "expected"), + [ + pytest.param({"advertised": True, "received": True, "enabled": True}, True), + pytest.param({"advertised": False, "received": True, "enabled": True}, False), + pytest.param({"advertised": True, "received": False, "enabled": True}, False), + pytest.param({"advertised": True, "received": True, "enabled": False}, False), + pytest.param({"advertised": True, "received": True}, False), # Missing 'enabled' + pytest.param({}, False), + ], +) +def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bool) -> None: + """Test check_bgp_neighbor_capability.""" + assert _check_bgp_neighbor_capability(input_dict) == expected From be8595ba468ef6ee77de095480eccbef5b3a9711 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Wed, 30 Oct 2024 08:07:20 -0400 Subject: [PATCH 07/15] Updated unit tests for _check_bgp_neighbor_capability --- anta/tests/routing/bgp.py | 2 +- tests/units/anta_tests/routing/test_bgp.py | 21 +++++++++++++++++++++ tests/units/test_tools.py | 17 ----------------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index a1aaeba6c..b8094c91e 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -371,7 +371,7 @@ def test(self) -> None: # Check if the BGP session is established if peer_data["state"] != "Established": - self.result.is_failure(f"{address_family} - Peer:{peer_ip} session state is not established; State:{peer_data['state']}") + self.result.is_failure(f"{address_family} Peer:{peer_ip} - Session state is not established; State:{peer_data['state']}") # Check if the AFI/SAFI state is negotiated capability_status = get_value(peer_data, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 9b9f49381..7d8aff61a 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -8,6 +8,8 @@ from typing import Any +import pytest + from anta.tests.routing.bgp import ( VerifyBGPAdvCommunities, VerifyBGPExchangedRoutes, @@ -24,9 +26,28 @@ VerifyBGPSpecificPeers, VerifyBGPTimers, VerifyEVPNType2Route, + _check_bgp_neighbor_capability, ) from tests.units.anta_tests import test + +# Unit testing for _check_bgp_neighbor_capability helper function from BGP test module. +@pytest.mark.parametrize( + ("input_dict", "expected"), + [ + pytest.param({"advertised": True, "received": True, "enabled": True}, True), + pytest.param({"advertised": False, "received": True, "enabled": True}, False), + pytest.param({"advertised": True, "received": False, "enabled": True}, False), + pytest.param({"advertised": True, "received": True, "enabled": False}, False), + pytest.param({"advertised": True, "received": True}, False), # Missing 'enabled' + pytest.param({}, False), + ], +) +def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bool) -> None: + """Test check_bgp_neighbor_capability.""" + assert _check_bgp_neighbor_capability(input_dict) == expected + + DATA: list[dict[str, Any]] = [ { "name": "success", diff --git a/tests/units/test_tools.py b/tests/units/test_tools.py index 8f0ecfe13..b5410ee3c 100644 --- a/tests/units/test_tools.py +++ b/tests/units/test_tools.py @@ -11,7 +11,6 @@ import pytest -from anta.tests.routing.bgp import _check_bgp_neighbor_capability from anta.tools import convert_categories, custom_division, format_data, get_dict_superset, get_failed_logs, get_item, get_value TEST_GET_FAILED_LOGS_DATA = [ @@ -528,19 +527,3 @@ def test_convert_categories(test_input: list[str], expected_raise: AbstractConte def test_format_data(input_data: dict[str, bool], expected_output: str) -> None: """Test format_data.""" assert format_data(input_data) == expected_output - - -@pytest.mark.parametrize( - ("input_dict", "expected"), - [ - pytest.param({"advertised": True, "received": True, "enabled": True}, True), - pytest.param({"advertised": False, "received": True, "enabled": True}, False), - pytest.param({"advertised": True, "received": False, "enabled": True}, False), - pytest.param({"advertised": True, "received": True, "enabled": False}, False), - pytest.param({"advertised": True, "received": True}, False), # Missing 'enabled' - pytest.param({}, False), - ], -) -def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bool) -> None: - """Test check_bgp_neighbor_capability.""" - assert _check_bgp_neighbor_capability(input_dict) == expected From 62708417523b4df465d909adb1a4387f47cb7306 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Tue, 5 Nov 2024 06:10:21 -0500 Subject: [PATCH 08/15] Resolved conflicts and updated the space after : in failure msgs --- anta/input_models/routing/bgp.py | 6 +- anta/tests/routing/bgp.py | 18 ++-- anta/tools.py | 4 +- tests/units/anta_tests/routing/test_bgp.py | 102 ++++++++++----------- tests/units/test_tools.py | 8 +- 5 files changed, 69 insertions(+), 69 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 679f81ca6..660a1d2e9 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -99,11 +99,11 @@ def __str__(self) -> str: - AFI:ipv4 SAFI:unicast VRF:default - AFI:evpn """ - base_string = f"AFI:{self.afi}" + base_string = f"AFI: {self.afi}" if self.safi is not None: - base_string += f" SAFI:{self.safi}" + base_string += f" SAFI: {self.safi}" if self.afi in ["ipv4", "ipv6"]: - base_string += f" VRF:{self.vrf}" + base_string += f" VRF: {self.vrf}" return base_string diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index b8094c91e..53fa73662 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -234,7 +234,7 @@ class VerifyBGPPeersHealth(AntaTest): """ name = "VerifyBGPPeersHealth" - description = "Verifies the health of BGP peers for given address families." + description = "Verifies the health of BGP peers for the given address families." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] @@ -270,18 +270,18 @@ def test(self) -> None: for peer in relevant_peers: # Check if the BGP session is established if peer["state"] != "Established": - self.result.is_failure(f"{address_family} Peer:{peer['peerAddress']} - Session state is not established; State:{peer['state']}") + self.result.is_failure(f"{address_family} Peer: {peer['peerAddress']} - Session state is not established; State: {peer['state']}") # Check if the AFI/SAFI state is negotiated capability_status = get_value(peer, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") if not _check_bgp_neighbor_capability(capability_status): - self.result.is_failure(f"{address_family} - Peer:{peer['peerAddress']} AFI/SAFI state is not negotiated; {format_data(capability_status)}") + self.result.is_failure(f"{address_family} Peer: {peer['peerAddress']} - AFI/SAFI state is not negotiated; {format_data(capability_status)}") # Check the TCP session message queues inq = peer["peerTcpInfo"]["inputQueueLength"] outq = peer["peerTcpInfo"]["outputQueueLength"] if address_family.check_tcp_queues and (inq != 0 or outq != 0): - self.result.is_failure(f"{address_family} - Peer:{peer['peerAddress']} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") + self.result.is_failure(f"{address_family} Peer: {peer['peerAddress']} - Session has non-empty message queues; InQ: {inq}, OutQ: {outq}") class VerifyBGPSpecificPeers(AntaTest): @@ -366,26 +366,26 @@ def test(self) -> None: # Check if the peer is found if (peer_data := get_item(vrf_output["peerList"], "peerAddress", peer_ip)) is None: - self.result.is_failure(f"{address_family} - Peer:{peer_ip} not found") + self.result.is_failure(f"{address_family} Peer: {peer_ip} - Not configured") continue # Check if the BGP session is established if peer_data["state"] != "Established": - self.result.is_failure(f"{address_family} Peer:{peer_ip} - Session state is not established; State:{peer_data['state']}") + self.result.is_failure(f"{address_family} Peer: {peer_ip} - Session state is not established; State: {peer_data['state']}") # Check if the AFI/SAFI state is negotiated capability_status = get_value(peer_data, f"neighborCapabilities.multiprotocolCaps.{address_family.eos_key}") if not capability_status: - self.result.is_failure(f"{address_family} Peer:{peer_ip} AFI/SAFI state is not negotiated") + self.result.is_failure(f"{address_family} Peer: {peer_ip} - AFI/SAFI state is not negotiated") if capability_status and not _check_bgp_neighbor_capability(capability_status): - self.result.is_failure(f"{address_family} Peer:{peer_ip} AFI/SAFI state is not negotiated; {format_data(capability_status)}") + self.result.is_failure(f"{address_family} Peer: {peer_ip} - AFI/SAFI state is not negotiated; {format_data(capability_status)}") # Check the TCP session message queues inq = peer_data["peerTcpInfo"]["inputQueueLength"] outq = peer_data["peerTcpInfo"]["outputQueueLength"] if address_family.check_tcp_queues and (inq != 0 or outq != 0): - self.result.is_failure(f"{address_family} - Peer:{peer_ip} session has non-empty message queues; InQ: {inq}, OutQ: {outq}") + self.result.is_failure(f"{address_family} Peer: {peer_ip} - Session has non-empty message queues; InQ: {inq}, OutQ: {outq}") class VerifyBGPExchangedRoutes(AntaTest): diff --git a/anta/tools.py b/anta/tools.py index b215a111c..8b116a0e0 100644 --- a/anta/tools.py +++ b/anta/tools.py @@ -412,6 +412,6 @@ def format_data(data: dict[str, bool]) -> str: Example ------- >>> format_data({"advertised": True, "received": True, "enabled": True}) - "Advertised:True, Received:True, Enabled:True" + "Advertised: True, Received: True, Enabled: True" """ - return ", ".join(f"{k.capitalize()}:{v}" for k, v in data.items()) + return ", ".join(f"{k.capitalize()}: {v}" for k, v in data.items()) diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 7d8aff61a..84485fce3 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -35,11 +35,11 @@ @pytest.mark.parametrize( ("input_dict", "expected"), [ - pytest.param({"advertised": True, "received": True, "enabled": True}, True), - pytest.param({"advertised": False, "received": True, "enabled": True}, False), - pytest.param({"advertised": True, "received": False, "enabled": True}, False), - pytest.param({"advertised": True, "received": True, "enabled": False}, False), - pytest.param({"advertised": True, "received": True}, False), # Missing 'enabled' + pytest.param({"advertised": True, "received": True, "enabled": True}, True, id="all True"), + pytest.param({"advertised": False, "received": True, "enabled": True}, False, id="advertised False"), + pytest.param({"advertised": True, "received": False, "enabled": True}, False, id="received False"), + pytest.param({"advertised": True, "received": True, "enabled": False}, False, id="enabled False"), + pytest.param({"advertised": True, "received": True}, False, id="missing enabled"), pytest.param({}, False), ], ) @@ -178,7 +178,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:PROD - VRF not configured", + "AFI: ipv4 SAFI: unicast VRF: PROD - VRF not configured", ], }, }, @@ -248,7 +248,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:vpn-ipv4 - Expected: 2, Actual: 0", + "AFI: vpn-ipv4 - Expected: 2, Actual: 0", ], }, }, @@ -317,8 +317,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:evpn - Expected: 3, Actual: 2", - "AFI:ipv4 SAFI:unicast VRF:DEV - Expected: 2, Actual: 1", + "AFI: evpn - Expected: 3, Actual: 2", + "AFI: ipv4 SAFI: unicast VRF: DEV - Expected: 2, Actual: 1", ], }, }, @@ -385,10 +385,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - VRF not configured", - "AFI:ipv4 SAFI:sr-te VRF:MGMT - VRF not configured", - "AFI:path-selection - VRF not configured", - "AFI:link-state - VRF not configured", + "AFI: ipv4 SAFI: unicast VRF: default - VRF not configured", + "AFI: ipv4 SAFI: sr-te VRF: MGMT - VRF not configured", + "AFI: path-selection - VRF not configured", + "AFI: link-state - VRF not configured", ], }, }, @@ -407,10 +407,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - No peers found", - "AFI:ipv4 SAFI:sr-te VRF:MGMT - No peers found", - "AFI:path-selection - No peers found", - "AFI:link-state - No peers found", + "AFI: ipv4 SAFI: unicast VRF: default - No peers found", + "AFI: ipv4 SAFI: sr-te VRF: MGMT - No peers found", + "AFI: path-selection - No peers found", + "AFI: link-state - No peers found", ], }, }, @@ -466,10 +466,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 - Session state is not established; State:Idle", - "AFI:ipv4 SAFI:sr-te VRF:MGMT Peer:10.100.0.12 - Session state is not established; State:Idle", - "AFI:path-selection Peer:10.100.0.13 - Session state is not established; State:Idle", - "AFI:link-state Peer:10.100.0.14 - Session state is not established; State:Idle", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session state is not established; State: Idle", + "AFI: ipv4 SAFI: sr-te VRF: MGMT Peer: 10.100.0.12 - Session state is not established; State: Idle", + "AFI: path-selection Peer: 10.100.0.13 - Session state is not established; State: Idle", + "AFI: link-state Peer: 10.100.0.14 - Session state is not established; State: Idle", ], }, }, @@ -525,14 +525,14 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 - Session state is not established; State:Idle", - "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:True", - "AFI:ipv4 SAFI:sr-te VRF:MGMT Peer:10.100.0.12 - Session state is not established; State:Idle", - "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:False", - "AFI:path-selection Peer:10.100.0.13 - Session state is not established; State:Idle", - "AFI:path-selection - Peer:10.100.0.13 AFI/SAFI state is not negotiated; Advertised:True, Received:False, Enabled:False", - "AFI:link-state Peer:10.100.0.14 - Session state is not established; State:Idle", - "AFI:link-state - Peer:10.100.0.14 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:False", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session state is not established; State: Idle", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - AFI/SAFI state is not negotiated; Advertised: False, Received: False, Enabled: True", + "AFI: ipv4 SAFI: sr-te VRF: MGMT Peer: 10.100.0.12 - Session state is not established; State: Idle", + "AFI: ipv4 SAFI: sr-te VRF: MGMT Peer: 10.100.0.12 - AFI/SAFI state is not negotiated; Advertised: False, Received: False, Enabled: False", + "AFI: path-selection Peer: 10.100.0.13 - Session state is not established; State: Idle", + "AFI: path-selection Peer: 10.100.0.13 - AFI/SAFI state is not negotiated; Advertised: True, Received: False, Enabled: False", + "AFI: link-state Peer: 10.100.0.14 - Session state is not established; State: Idle", + "AFI: link-state Peer: 10.100.0.14 - AFI/SAFI state is not negotiated; Advertised: False, Received: False, Enabled: False", ], }, }, @@ -588,14 +588,14 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 - Session state is not established; State:Idle", - "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session has non-empty message queues; InQ: 2, OutQ: 4", - "AFI:ipv4 SAFI:sr-te VRF:MGMT Peer:10.100.0.12 - Session state is not established; State:Idle", - "AFI:ipv4 SAFI:sr-te VRF:MGMT - Peer:10.100.0.12 session has non-empty message queues; InQ: 5, OutQ: 1", - "AFI:path-selection Peer:10.100.0.13 - Session state is not established; State:Idle", - "AFI:path-selection - Peer:10.100.0.13 session has non-empty message queues; InQ: 1, OutQ: 1", - "AFI:link-state Peer:10.100.0.14 - Session state is not established; State:Idle", - "AFI:link-state - Peer:10.100.0.14 session has non-empty message queues; InQ: 3, OutQ: 2", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session state is not established; State: Idle", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session has non-empty message queues; InQ: 2, OutQ: 4", + "AFI: ipv4 SAFI: sr-te VRF: MGMT Peer: 10.100.0.12 - Session state is not established; State: Idle", + "AFI: ipv4 SAFI: sr-te VRF: MGMT Peer: 10.100.0.12 - Session has non-empty message queues; InQ: 5, OutQ: 1", + "AFI: path-selection Peer: 10.100.0.13 - Session state is not established; State: Idle", + "AFI: path-selection Peer: 10.100.0.13 - Session has non-empty message queues; InQ: 1, OutQ: 1", + "AFI: link-state Peer: 10.100.0.14 - Session state is not established; State: Idle", + "AFI: link-state Peer: 10.100.0.14 - Session has non-empty message queues; InQ: 3, OutQ: 2", ], }, }, @@ -682,9 +682,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 not found", - "AFI:evpn - Peer:10.100.0.13 not found", - "AFI:ipv4 SAFI:unicast VRF:MGMT - Peer:10.100.0.14 not found", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Not configured", + "AFI: evpn Peer: 10.100.0.13 - Not configured", + "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - Not configured", ], }, }, @@ -706,9 +706,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - VRF not configured", - "AFI:evpn - VRF not configured", - "AFI:ipv4 SAFI:unicast VRF:MGMT - VRF not configured", + "AFI: ipv4 SAFI: unicast VRF: default - VRF not configured", + "AFI: evpn - VRF not configured", + "AFI: ipv4 SAFI: unicast VRF: MGMT - VRF not configured", ], }, }, @@ -750,8 +750,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 - Session state is not established; State:Idle", - "AFI:ipv4 SAFI:unicast VRF:MGMT Peer:10.100.0.14 - Session state is not established; State:Idle", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session state is not established; State: Idle", + "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - Session state is not established; State: Idle", ], }, }, @@ -793,8 +793,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:True", - "AFI:ipv4 SAFI:unicast VRF:MGMT Peer:10.100.0.14 AFI/SAFI state is not negotiated; Advertised:False, Received:False, Enabled:False", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - AFI/SAFI state is not negotiated; Advertised: False, Received: False, Enabled: True", + "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - AFI/SAFI state is not negotiated; Advertised: False, Received: False, Enabled: False", ], }, }, @@ -836,8 +836,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default Peer:10.100.0.12 AFI/SAFI state is not negotiated", - "AFI:ipv4 SAFI:unicast VRF:MGMT Peer:10.100.0.14 AFI/SAFI state is not negotiated", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - AFI/SAFI state is not negotiated", + "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - AFI/SAFI state is not negotiated", ], }, }, @@ -879,8 +879,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "AFI:ipv4 SAFI:unicast VRF:default - Peer:10.100.0.12 session has non-empty message queues; InQ: 3, OutQ: 3", - "AFI:ipv4 SAFI:unicast VRF:MGMT - Peer:10.100.0.14 session has non-empty message queues; InQ: 2, OutQ: 2", + "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session has non-empty message queues; InQ: 3, OutQ: 3", + "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - Session has non-empty message queues; InQ: 2, OutQ: 2", ], }, }, diff --git a/tests/units/test_tools.py b/tests/units/test_tools.py index b5410ee3c..b1f96a50c 100644 --- a/tests/units/test_tools.py +++ b/tests/units/test_tools.py @@ -518,10 +518,10 @@ def test_convert_categories(test_input: list[str], expected_raise: AbstractConte @pytest.mark.parametrize( ("input_data", "expected_output"), [ - pytest.param({"advertised": True, "received": True, "enabled": True}, "Advertised:True, Received:True, Enabled:True"), - pytest.param({"advertised": False, "received": False}, "Advertised:False, Received:False"), - pytest.param({}, ""), - pytest.param({"test": True}, "Test:True"), + pytest.param({"advertised": True, "received": True, "enabled": True}, "Advertised: True, Received: True, Enabled: True", id="multiple entry, all True"), + pytest.param({"advertised": False, "received": False}, "Advertised: False, Received: False", id="multiple entry, all False"), + pytest.param({}, "", id="empty dict"), + pytest.param({"test": True}, "Test: True", id="single entry"), ], ) def test_format_data(input_data: dict[str, bool], expected_output: str) -> None: From c9f240d7c9e034b2ce6e775297ac4608fcb1d3eb Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Wed, 6 Nov 2024 01:40:15 -0500 Subject: [PATCH 09/15] Updated BgpPeerCount test and added unit tests --- anta/input_models/routing/bgp.py | 5 ++ anta/tests/routing/bgp.py | 17 ++-- docs/api/tests.routing.bgp.md | 15 ++++ tests/units/anta_tests/routing/test_bgp.py | 99 ++++++++++++++++++++++ 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 660a1d2e9..b1c0753db 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -64,6 +64,11 @@ class BgpAddressFamily(BaseModel): Can be disabled in the `VerifyBGPPeersHealth` and `VerifyBGPSpecificPeers` tests. """ + check_peer_state: bool = True + """Flag to check if the peers are established with negotiated AFI/SAFI. Defaults to `True`. + + Can be disabled in the `VerifyBGPPeerCount` tests. + """ @model_validator(mode="after") def validate_inputs(self) -> Self: diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 53fa73662..d248f9112 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -175,17 +175,22 @@ def test(self) -> None: output = self.instance_commands[0].json_output for address_family in self.inputs.address_families: - peer_count = 0 - # Check if the VRF is configured if (vrf_output := get_value(output, f"vrfs.{address_family.vrf}")) is None: self.result.is_failure(f"{address_family} - VRF not configured") continue - # Count the number of established peers with negotiated AFI/SAFI - for peer_data in vrf_output.get("peers", {}).values(): - if peer_data.get("peerState") == "Established" and get_value(peer_data, f"{address_family.eos_key}.afiSafiState") == "negotiated": - peer_count += 1 + peers_data = vrf_output.get("peers", {}).values() + if not address_family.check_peer_state: + # Count the number of peers without considering the state and negotiated AFI/SAFI check if the count matches the expected count + peer_count = sum(1 for peer_data in peers_data if address_family.eos_key in peer_data) + else: + # Count the number of established peers with negotiated AFI/SAFI + peer_count = sum( + 1 + for peer_data in peers_data + if peer_data.get("peerState") == "Established" and get_value(peer_data, f"{address_family.eos_key}.afiSafiState") == "negotiated" + ) # Check if the count matches the expected count if address_family.num_peers != peer_count: diff --git a/docs/api/tests.routing.bgp.md b/docs/api/tests.routing.bgp.md index 4537ec24b..acac97f83 100644 --- a/docs/api/tests.routing.bgp.md +++ b/docs/api/tests.routing.bgp.md @@ -7,7 +7,10 @@ anta_title: ANTA catalog for BGP tests ~ that can be found in the LICENSE file. --> +# Tests + ::: anta.tests.routing.bgp + options: show_root_heading: false show_root_toc_entry: false @@ -19,3 +22,15 @@ anta_title: ANTA catalog for BGP tests - "!test" - "!render" - "!^_[^_]" + +# Input models + +::: anta.input_models.routing.bgp + + options: + show_root_heading: false + show_root_toc_entry: false + show_bases: false + anta_hide_test_module_description: true + show_labels: true + filters: ["!^__str__"] diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 84485fce3..025bc93e2 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -113,6 +113,55 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": {"result": "success"}, }, + { + "name": "success-peer-state-check-false", + "test": VerifyBGPPeerCount, + "eos_data": [ + { + "vrfs": { + "default": { + "vrf": "default", + "routerId": "10.1.0.3", + "asn": "65120", + "peers": { + "10.1.0.1": { + "peerState": "Idle", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, + }, + "10.1.0.2": { + "peerState": "Idle", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, + }, + }, + }, + "DEV": { + "vrf": "DEV", + "routerId": "10.1.0.3", + "asn": "65120", + "peers": { + "10.1.254.1": { + "peerState": "Idle", + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, + } + }, + }, + } + }, + ], + "inputs": { + "address_families": [ + {"afi": "evpn", "num_peers": 2, "check_peer_state": False}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2, "check_peer_state": False}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1, "check_peer_state": False}, + ] + }, + "expected": {"result": "success"}, + }, { "name": "failure-vrf-not-configured", "test": VerifyBGPPeerCount, @@ -322,6 +371,56 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], }, }, + { + "name": "failure-peer-state-check-false", + "test": VerifyBGPPeerCount, + "eos_data": [ + { + "vrfs": { + "default": { + "vrf": "default", + "routerId": "10.1.0.3", + "asn": "65120", + "peers": { + "10.1.0.1": { + "peerState": "Idle", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, + }, + }, + }, + "DEV": { + "vrf": "DEV", + "routerId": "10.1.0.3", + "asn": "65120", + "peers": { + "10.1.254.1": { + "peerState": "Idle", + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, + } + }, + }, + } + }, + ], + "inputs": { + "address_families": [ + {"afi": "evpn", "num_peers": 2, "check_peer_state": False}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2, "check_peer_state": False}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 2, "check_peer_state": False}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "AFI: evpn - Expected: 2, Actual: 1", + "AFI: ipv4 SAFI: unicast VRF: default - Expected: 2, Actual: 1", + "AFI: ipv4 SAFI: unicast VRF: DEV - Expected: 2, Actual: 1", + ], + }, + }, { "name": "success", "test": VerifyBGPPeersHealth, From 6fc9feb6d11eb7340b355224af47a1d8b009b114 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Fri, 8 Nov 2024 03:40:56 -0500 Subject: [PATCH 10/15] Addressed review comments: updted docstring for PeerCount test --- anta/input_models/routing/bgp.py | 6 +- anta/tests/routing/bgp.py | 13 +-- tests/units/anta_tests/routing/test_bgp.py | 92 +++++++++++----------- 3 files changed, 56 insertions(+), 55 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index b1c0753db..0674bab97 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -64,10 +64,10 @@ class BgpAddressFamily(BaseModel): Can be disabled in the `VerifyBGPPeersHealth` and `VerifyBGPSpecificPeers` tests. """ - check_peer_state: bool = True - """Flag to check if the peers are established with negotiated AFI/SAFI. Defaults to `True`. + check_peer_state: bool = False + """Flag to check if the peers are established with negotiated AFI/SAFI. Defaults to `False`. - Can be disabled in the `VerifyBGPPeerCount` tests. + Can be enabled in the `VerifyBGPPeerCount` tests. """ @model_validator(mode="after") diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index d248f9112..d877e1828 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -105,21 +105,22 @@ def _check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool: class VerifyBGPPeerCount(AntaTest): - """Verifies the count of established BGP peers with negotiated AFI/SAFI for given address families. + """Verifies the count of BGP peers for given address families. This test performs the following checks for each specified address family: 1. Confirms that the specified VRF is configured. 2. Counts the number of peers that are: - - In the `Established` state - - Have successfully negotiated the specified AFI/SAFI + - If `check_peer_state` is set to True, Counts the number of BGP peers that are in the `Established` state and + have successfully negotiated the specified AFI/SAFI + - If `check_peer_state` is set to False, skips validation of the `Established` state and AFI/SAFI negotiation. Expected Results ---------------- - * Success: If the count of established BGP peers with negotiated AFI/SAFI matches the expected count for all specified address families. + * Success: If the count of BGP peers matches the expected count with `check_peer_state` enabled/disabled. * Failure: If any of the following occur: - The specified VRF is not configured. - - The count of established peers with negotiated AFI/SAFI does not match the expected count for any address family. + - The BGP peer count does not match expected value with `check_peer_state` enabled/disabled." Examples -------- @@ -146,7 +147,7 @@ class VerifyBGPPeerCount(AntaTest): """ name = "VerifyBGPPeerCount" - description = "Verifies the count of established BGP peers with negotiated AFI/SAFI for given address families." + description = "Verifies the count of BGP peers for given address families." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp summary vrf all", revision=1)] diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 025bc93e2..cce58dc46 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -61,32 +61,17 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "asn": "65120", "peers": { "10.1.0.1": { - "peerState": "Established", + "peerState": "Idle", "peerAsn": "65100", - "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, "10.1.0.2": { - "peerState": "Established", + "peerState": "Idle", "peerAsn": "65100", - "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, - "10.1.254.1": { - "peerState": "Established", - "peerAsn": "65120", - "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17}, - }, - "10.1.255.0": { - "peerState": "Established", - "peerAsn": "65100", - "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, - }, - "10.1.255.2": { - "peerState": "Established", - "peerAsn": "65100", - "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, - }, }, }, "DEV": { @@ -95,7 +80,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "asn": "65120", "peers": { "10.1.254.1": { - "peerState": "Established", + "peerState": "Idle", "peerAsn": "65120", "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, } @@ -107,14 +92,14 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "inputs": { "address_families": [ {"afi": "evpn", "num_peers": 2}, - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2}, {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1}, ] }, "expected": {"result": "success"}, }, { - "name": "success-peer-state-check-false", + "name": "success-peer-state-check-true", "test": VerifyBGPPeerCount, "eos_data": [ { @@ -125,17 +110,32 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "asn": "65120", "peers": { "10.1.0.1": { - "peerState": "Idle", + "peerState": "Established", "peerAsn": "65100", - "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, "10.1.0.2": { - "peerState": "Idle", + "peerState": "Established", "peerAsn": "65100", - "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, }, + "10.1.254.1": { + "peerState": "Established", + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17}, + }, + "10.1.255.0": { + "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, + }, + "10.1.255.2": { + "peerState": "Established", + "peerAsn": "65100", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14}, + }, }, }, "DEV": { @@ -144,7 +144,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "asn": "65120", "peers": { "10.1.254.1": { - "peerState": "Idle", + "peerState": "Established", "peerAsn": "65120", "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, } @@ -155,9 +155,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], "inputs": { "address_families": [ - {"afi": "evpn", "num_peers": 2, "check_peer_state": False}, - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2, "check_peer_state": False}, - {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1, "check_peer_state": False}, + {"afi": "evpn", "num_peers": 2, "check_peer_state": True}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3, "check_peer_state": True}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1, "check_peer_state": True}, ] }, "expected": {"result": "success"}, @@ -219,9 +219,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], "inputs": { "address_families": [ - {"afi": "evpn", "num_peers": 2}, - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, - {"afi": "ipv4", "safi": "unicast", "vrf": "PROD", "num_peers": 2}, + {"afi": "evpn", "num_peers": 2, "check_peer_state": True}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3, "check_peer_state": True}, + {"afi": "ipv4", "safi": "unicast", "vrf": "PROD", "num_peers": 2, "check_peer_state": True}, ] }, "expected": { @@ -232,7 +232,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, }, { - "name": "failure-afi-safi-not-negotiated", + "name": "failure-peer-state-check-true", "test": VerifyBGPPeerCount, "eos_data": [ { @@ -288,10 +288,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], "inputs": { "address_families": [ - {"afi": "evpn", "num_peers": 2}, - {"afi": "vpn-ipv4", "num_peers": 2}, - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, - {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1}, + {"afi": "evpn", "num_peers": 2, "check_peer_state": True}, + {"afi": "vpn-ipv4", "num_peers": 2, "check_peer_state": True}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3, "check_peer_state": True}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1, "check_peer_state": True}, ] }, "expected": { @@ -302,7 +302,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, }, { - "name": "failure-wrong-count", + "name": "failure-wrong-count-peer-state-check-true", "test": VerifyBGPPeerCount, "eos_data": [ { @@ -358,9 +358,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], "inputs": { "address_families": [ - {"afi": "evpn", "num_peers": 3}, - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3}, - {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 2}, + {"afi": "evpn", "num_peers": 3, "check_peer_state": True}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3, "check_peer_state": True}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 2, "check_peer_state": True}, ] }, "expected": { @@ -372,7 +372,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, }, { - "name": "failure-peer-state-check-false", + "name": "failure-wrong-count", "test": VerifyBGPPeerCount, "eos_data": [ { @@ -407,9 +407,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], "inputs": { "address_families": [ - {"afi": "evpn", "num_peers": 2, "check_peer_state": False}, - {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2, "check_peer_state": False}, - {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 2, "check_peer_state": False}, + {"afi": "evpn", "num_peers": 2}, + {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2}, + {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 2}, ] }, "expected": { From 726084215e923043b0bde63df27a0d102d948616 Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Fri, 8 Nov 2024 09:26:13 -0500 Subject: [PATCH 11/15] Remove name and description --- anta/tests/routing/bgp.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index d877e1828..b3034ea78 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -146,8 +146,6 @@ class VerifyBGPPeerCount(AntaTest): ``` """ - name = "VerifyBGPPeerCount" - description = "Verifies the count of BGP peers for given address families." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp summary vrf all", revision=1)] @@ -239,8 +237,6 @@ class VerifyBGPPeersHealth(AntaTest): ``` """ - name = "VerifyBGPPeersHealth" - description = "Verifies the health of BGP peers for the given address families." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] From e8c0805464a672155afb65273dc8dac7d51c9cf8 Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Fri, 8 Nov 2024 09:32:57 -0500 Subject: [PATCH 12/15] Fix doc --- docs/api/tests.routing.bgp.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api/tests.routing.bgp.md b/docs/api/tests.routing.bgp.md index acac97f83..595b3108c 100644 --- a/docs/api/tests.routing.bgp.md +++ b/docs/api/tests.routing.bgp.md @@ -33,4 +33,7 @@ anta_title: ANTA catalog for BGP tests show_bases: false anta_hide_test_module_description: true show_labels: true - filters: ["!^__str__"] + filters: + - "!^__str__" + - "!AFI_SAFI_EOS_KEY" + - "!eos_key" From 9fc752fe65745874bf0a9a87112fd38c5e510e0d Mon Sep 17 00:00:00 2001 From: gmuloc Date: Fri, 8 Nov 2024 19:46:01 +0100 Subject: [PATCH 13/15] doc: Adjust doc --- docs/api/tests.connectivity.md | 1 + docs/api/tests.interfaces.md | 1 + docs/api/tests.routing.bgp.md | 2 + docs/api/tests.services.md | 1 + docs/api/tests.system.md | 2 + .../material/anta_test_input_model.html.jinja | 14 +----- .../python/material/class.html.jinja | 44 ++++++++++--------- 7 files changed, 32 insertions(+), 33 deletions(-) diff --git a/docs/api/tests.connectivity.md b/docs/api/tests.connectivity.md index cb0cf061e..439cec89d 100644 --- a/docs/api/tests.connectivity.md +++ b/docs/api/tests.connectivity.md @@ -30,6 +30,7 @@ anta_title: ANTA catalog for connectivity tests show_root_heading: false show_root_toc_entry: false show_bases: false + merge_init_into_class: false anta_hide_test_module_description: true show_labels: true filters: ["!^__str__"] diff --git a/docs/api/tests.interfaces.md b/docs/api/tests.interfaces.md index 821b80bd3..3d863ee2d 100644 --- a/docs/api/tests.interfaces.md +++ b/docs/api/tests.interfaces.md @@ -30,6 +30,7 @@ anta_title: ANTA catalog for interfaces tests show_root_heading: false show_root_toc_entry: false show_bases: false + merge_init_into_class: false anta_hide_test_module_description: true show_labels: true filters: ["!^__str__"] diff --git a/docs/api/tests.routing.bgp.md b/docs/api/tests.routing.bgp.md index 595b3108c..30e4362a0 100644 --- a/docs/api/tests.routing.bgp.md +++ b/docs/api/tests.routing.bgp.md @@ -32,8 +32,10 @@ anta_title: ANTA catalog for BGP tests show_root_toc_entry: false show_bases: false anta_hide_test_module_description: true + merge_init_into_class: false show_labels: true filters: + - "!^__init__" - "!^__str__" - "!AFI_SAFI_EOS_KEY" - "!eos_key" diff --git a/docs/api/tests.services.md b/docs/api/tests.services.md index fba716ab1..cd371489f 100644 --- a/docs/api/tests.services.md +++ b/docs/api/tests.services.md @@ -30,6 +30,7 @@ anta_title: ANTA catalog for services tests show_root_heading: false show_root_toc_entry: false show_bases: false + merge_init_into_class: false anta_hide_test_module_description: true show_labels: true filters: ["!^__str__"] diff --git a/docs/api/tests.system.md b/docs/api/tests.system.md index e474295c5..26568e205 100644 --- a/docs/api/tests.system.md +++ b/docs/api/tests.system.md @@ -10,6 +10,7 @@ anta_title: ANTA catalog for System tests # Tests ::: anta.tests.system + options: show_root_heading: false show_root_toc_entry: false @@ -29,6 +30,7 @@ anta_title: ANTA catalog for System tests show_root_heading: false show_root_toc_entry: false show_bases: false + merge_init_into_class: false anta_hide_test_module_description: true show_labels: true filters: ["!^__str__"] diff --git a/docs/templates/python/material/anta_test_input_model.html.jinja b/docs/templates/python/material/anta_test_input_model.html.jinja index ccd4307dd..f867ad00d 100644 --- a/docs/templates/python/material/anta_test_input_model.html.jinja +++ b/docs/templates/python/material/anta_test_input_model.html.jinja @@ -52,18 +52,8 @@ {% endif %} {% with heading_level = heading_level + extra_level %} {% for class in classes|order_members(config.members_order, members_list) %} - {% if class.name == "Input" %} - {% filter heading(heading_level, id=html_id ~ "-attributes") %}Inputs{% endfilter %} - {% set root = False %} - {% set heading_level = heading_level + 1 %} - {% set old_obj = obj %} - {% set obj = class %} - {% include "attributes_table.html" with context %} - {% set obj = old_obj %} - {% else %} - {% if members_list is not none or class.is_public %} - {% include class|get_template with context %} - {% endif %} + {% if members_list is not none or class.is_public %} + {% include class|get_template with context %} {% endif %} {% endfor %} {% endwith %} diff --git a/docs/templates/python/material/class.html.jinja b/docs/templates/python/material/class.html.jinja index c9c672fd0..cf016c92e 100644 --- a/docs/templates/python/material/class.html.jinja +++ b/docs/templates/python/material/class.html.jinja @@ -5,40 +5,42 @@ {% set basestr = base | string %} {% if "AntaTest" == basestr %} {% set anta_test.found = True %} -{% elif class.parent.parent.name == "input_models" %} -{% set anta_test_input_model.found = True %} {% endif %} {% endfor %} +{# TODO make this nicer #} +{% if class.parent.parent.name == "input_models" or class.parent.parent.parent.name == "input_models" %} +{% set anta_test_input_model.found = True %} +{% endif %} {% block children %} {% if anta_test.found %} {% set root = False %} {% set heading_level = heading_level + 1 %} {% include "anta_test.html.jinja" with context %} {# render source after children - TODO make add flag to respect disabling it.. though do we want to disable?#} -
- Source code in - {%- if class.relative_filepath.is_absolute() -%} - {{ class.relative_package_filepath }} - {%- else -%} - {{ class.relative_filepath }} - {%- endif -%} - - {{ class.source|highlight(language="python", linestart=class.lineno, linenums=True) }} +
+ Source code in + {%- if class.relative_filepath.is_absolute() -%} + {{ class.relative_package_filepath }} + {%- else -%} + {{ class.relative_filepath }} + {%- endif -%} + + {{ class.source|highlight(language="python", linestart=class.lineno, linenums=True) }}
{% elif anta_test_input_model.found %} {% set root = False %} {% set heading_level = heading_level + 1 %} {% include "anta_test_input_model.html.jinja" with context %} {# render source after children - TODO make add flag to respect disabling it.. though do we want to disable?#} -
- Source code in - {%- if class.relative_filepath.is_absolute() -%} - {{ class.relative_package_filepath }} - {%- else -%} - {{ class.relative_filepath }} - {%- endif -%} - - {{ class.source|highlight(language="python", linestart=class.lineno, linenums=True) }} +
+ Source code in + {%- if class.relative_filepath.is_absolute() -%} + {{ class.relative_package_filepath }} + {%- else -%} + {{ class.relative_filepath }} + {%- endif -%} + + {{ class.source|highlight(language="python", linestart=class.lineno, linenums=True) }}
{% else %} {{ super() }} @@ -47,7 +49,7 @@ {# Do not render source before children for AntaTest #} {% block source %} -{% if not anta_test.found %} +{% if not anta_test.found and not anta_test_input_model%} {{ super() }} {% endif %} {% endblock source %} From 6a4541fa8766fc588b284db6c3aa82d691c141b1 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Wed, 13 Nov 2024 12:48:04 -0500 Subject: [PATCH 14/15] Updated unit tests for input validation --- anta/input_models/routing/bgp.py | 6 +- tests/units/anta_tests/routing/test_bgp.py | 87 +++++++++++++++++++++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 0674bab97..a291809c6 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -112,16 +112,16 @@ def __str__(self) -> str: return base_string -class BgpAfi(BgpAddressFamily): +class BgpAfi(BgpAddressFamily): # pragma: no cover """Alias for the BgpAddressFamily model to maintain backward compatibility. - When initialized, it will emit a depreciation warning and call the BgpAddressFamily model. + When initialized, it will emit a deprecation warning and call the BgpAddressFamily model. TODO: Remove this class in ANTA v2.0.0. """ def __init__(self, **data: Any) -> None: # noqa: ANN401 - """Initialize the BgpAfi class, emitting a depreciation warning.""" + """Initialize the BgpAfi class, emitting a deprecation warning.""" warn( message="BgpAfi model is deprecated and will be removed in ANTA v2.0.0. Use the BgpAddressFamily model instead.", category=DeprecationWarning, diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index cce58dc46..00e480267 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -6,10 +6,12 @@ # pylint: disable=C0302 from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any import pytest +from pydantic import PositiveInt, ValidationError +from anta.input_models.routing.bgp import BgpAddressFamily from anta.tests.routing.bgp import ( VerifyBGPAdvCommunities, VerifyBGPExchangedRoutes, @@ -30,6 +32,9 @@ ) from tests.units.anta_tests import test +if TYPE_CHECKING: + from anta.custom_types import Afi, Safi + # Unit testing for _check_bgp_neighbor_capability helper function from BGP test module. @pytest.mark.parametrize( @@ -4511,3 +4516,83 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, }, ] + + +class TestBgpAddressFamily: + """Test anta.input_models.routing.bgp.BgpAddressFamily.""" + + @pytest.mark.parametrize( + ("afi", "safi", "vrf"), + [ + pytest.param("ipv4", "unicast", "MGMT", id="afi"), + pytest.param("evpn", None, "default", id="safi"), + pytest.param("ipv4", "unicast", "default", id="vrf"), + ], + ) + def test_valid(self, afi: Afi, safi: Safi, vrf: str) -> None: + """Test BgpAddressFamily valid inputs.""" + BgpAddressFamily(afi=afi, safi=safi, vrf=vrf) + + @pytest.mark.parametrize( + ("afi", "safi", "vrf"), + [ + pytest.param("ipv4", None, "default", id="afi"), + pytest.param("evpn", "multicast", "default", id="safi"), + pytest.param("evpn", None, "MGMT", id="vrf"), + ], + ) + def test_invalid(self, afi: Afi, safi: Safi, vrf: str) -> None: + """Test BgpAddressFamily invalid inputs.""" + with pytest.raises(ValidationError): + BgpAddressFamily(afi=afi, safi=safi, vrf=vrf) + + +class TestVerifyBGPPeerCountInput: + """Test anta.tests.routing.bgp.VerifyBGPPeerCount.Input.""" + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param([{"afi": "evpn", "num_peers": 2}], id="valid"), + ], + ) + def test_valid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPPeerCount.Input valid inputs.""" + VerifyBGPPeerCount.Input(address_families=address_families) + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param([{"afi": "evpn", "num_peers": 0}], id="zero-peer"), + pytest.param([{"afi": "evpn"}], id="None"), + ], + ) + def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPPeerCount.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPPeerCount.Input(address_families=address_families) + + +class TestVerifyBGPSpecificPeersInput: + """Test anta.tests.routing.bgp.VerifyBGPSpecificPeers.Input.""" + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param([{"afi": "evpn", "peers": ["10.1.0.1", "10.1.0.2"]}], id="valid"), + ], + ) + def test_valid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPSpecificPeers.Input valid inputs.""" + VerifyBGPSpecificPeers.Input(address_families=address_families) + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param([{"afi": "evpn"}], id="None"), + ], + ) + def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPSpecificPeers.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPSpecificPeers.Input(address_families=address_families) From 4412d8ab3146d86b0ae5f4f13b78f31a013216a5 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Thu, 14 Nov 2024 12:29:38 -0500 Subject: [PATCH 15/15] Updated unit tests for input validation in new folder --- tests/units/anta_tests/routing/test_bgp.py | 85 ----------------- tests/units/input_models/__init__.py | 4 + tests/units/input_models/routing/__init__.py | 4 + tests/units/input_models/routing/test_bgp.py | 98 ++++++++++++++++++++ 4 files changed, 106 insertions(+), 85 deletions(-) create mode 100644 tests/units/input_models/__init__.py create mode 100644 tests/units/input_models/routing/__init__.py create mode 100644 tests/units/input_models/routing/test_bgp.py diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 00e480267..e4b8b5c8b 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Any import pytest -from pydantic import PositiveInt, ValidationError from anta.input_models.routing.bgp import BgpAddressFamily from anta.tests.routing.bgp import ( @@ -32,11 +31,7 @@ ) from tests.units.anta_tests import test -if TYPE_CHECKING: - from anta.custom_types import Afi, Safi - -# Unit testing for _check_bgp_neighbor_capability helper function from BGP test module. @pytest.mark.parametrize( ("input_dict", "expected"), [ @@ -4516,83 +4511,3 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, }, ] - - -class TestBgpAddressFamily: - """Test anta.input_models.routing.bgp.BgpAddressFamily.""" - - @pytest.mark.parametrize( - ("afi", "safi", "vrf"), - [ - pytest.param("ipv4", "unicast", "MGMT", id="afi"), - pytest.param("evpn", None, "default", id="safi"), - pytest.param("ipv4", "unicast", "default", id="vrf"), - ], - ) - def test_valid(self, afi: Afi, safi: Safi, vrf: str) -> None: - """Test BgpAddressFamily valid inputs.""" - BgpAddressFamily(afi=afi, safi=safi, vrf=vrf) - - @pytest.mark.parametrize( - ("afi", "safi", "vrf"), - [ - pytest.param("ipv4", None, "default", id="afi"), - pytest.param("evpn", "multicast", "default", id="safi"), - pytest.param("evpn", None, "MGMT", id="vrf"), - ], - ) - def test_invalid(self, afi: Afi, safi: Safi, vrf: str) -> None: - """Test BgpAddressFamily invalid inputs.""" - with pytest.raises(ValidationError): - BgpAddressFamily(afi=afi, safi=safi, vrf=vrf) - - -class TestVerifyBGPPeerCountInput: - """Test anta.tests.routing.bgp.VerifyBGPPeerCount.Input.""" - - @pytest.mark.parametrize( - ("address_families"), - [ - pytest.param([{"afi": "evpn", "num_peers": 2}], id="valid"), - ], - ) - def test_valid(self, address_families: list[BgpAddressFamily]) -> None: - """Test VerifyBGPPeerCount.Input valid inputs.""" - VerifyBGPPeerCount.Input(address_families=address_families) - - @pytest.mark.parametrize( - ("address_families"), - [ - pytest.param([{"afi": "evpn", "num_peers": 0}], id="zero-peer"), - pytest.param([{"afi": "evpn"}], id="None"), - ], - ) - def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: - """Test VerifyBGPPeerCount.Input invalid inputs.""" - with pytest.raises(ValidationError): - VerifyBGPPeerCount.Input(address_families=address_families) - - -class TestVerifyBGPSpecificPeersInput: - """Test anta.tests.routing.bgp.VerifyBGPSpecificPeers.Input.""" - - @pytest.mark.parametrize( - ("address_families"), - [ - pytest.param([{"afi": "evpn", "peers": ["10.1.0.1", "10.1.0.2"]}], id="valid"), - ], - ) - def test_valid(self, address_families: list[BgpAddressFamily]) -> None: - """Test VerifyBGPSpecificPeers.Input valid inputs.""" - VerifyBGPSpecificPeers.Input(address_families=address_families) - - @pytest.mark.parametrize( - ("address_families"), - [ - pytest.param([{"afi": "evpn"}], id="None"), - ], - ) - def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: - """Test VerifyBGPSpecificPeers.Input invalid inputs.""" - with pytest.raises(ValidationError): - VerifyBGPSpecificPeers.Input(address_families=address_families) diff --git a/tests/units/input_models/__init__.py b/tests/units/input_models/__init__.py new file mode 100644 index 000000000..62747a681 --- /dev/null +++ b/tests/units/input_models/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Tests for anta.input_models module.""" diff --git a/tests/units/input_models/routing/__init__.py b/tests/units/input_models/routing/__init__.py new file mode 100644 index 000000000..b56adb5fe --- /dev/null +++ b/tests/units/input_models/routing/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Test for anta.input_models.routing submodule.""" diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py new file mode 100644 index 000000000..aabc8f293 --- /dev/null +++ b/tests/units/input_models/routing/test_bgp.py @@ -0,0 +1,98 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Tests for anta.input_models.routing.bgp.py.""" + +# pylint: disable=C0302 +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest +from pydantic import ValidationError + +from anta.input_models.routing.bgp import BgpAddressFamily +from anta.tests.routing.bgp import VerifyBGPPeerCount, VerifyBGPSpecificPeers + +if TYPE_CHECKING: + from anta.custom_types import Afi, Safi + + +class TestBgpAddressFamily: + """Test anta.input_models.routing.bgp.BgpAddressFamily.""" + + @pytest.mark.parametrize( + ("afi", "safi", "vrf"), + [ + pytest.param("ipv4", "unicast", "MGMT", id="afi"), + pytest.param("evpn", None, "default", id="safi"), + pytest.param("ipv4", "unicast", "default", id="vrf"), + ], + ) + def test_valid(self, afi: Afi, safi: Safi, vrf: str) -> None: + """Test BgpAddressFamily valid inputs.""" + BgpAddressFamily(afi=afi, safi=safi, vrf=vrf) + + @pytest.mark.parametrize( + ("afi", "safi", "vrf"), + [ + pytest.param("ipv4", None, "default", id="afi"), + pytest.param("evpn", "multicast", "default", id="safi"), + pytest.param("evpn", None, "MGMT", id="vrf"), + ], + ) + def test_invalid(self, afi: Afi, safi: Safi, vrf: str) -> None: + """Test BgpAddressFamily invalid inputs.""" + with pytest.raises(ValidationError): + BgpAddressFamily(afi=afi, safi=safi, vrf=vrf) + + +class TestVerifyBGPPeerCountInput: + """Test anta.tests.routing.bgp.VerifyBGPPeerCount.Input.""" + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param([{"afi": "evpn", "num_peers": 2}], id="valid"), + ], + ) + def test_valid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPPeerCount.Input valid inputs.""" + VerifyBGPPeerCount.Input(address_families=address_families) + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param([{"afi": "evpn", "num_peers": 0}], id="zero-peer"), + pytest.param([{"afi": "evpn"}], id="None"), + ], + ) + def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPPeerCount.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPPeerCount.Input(address_families=address_families) + + +class TestVerifyBGPSpecificPeersInput: + """Test anta.tests.routing.bgp.VerifyBGPSpecificPeers.Input.""" + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param([{"afi": "evpn", "peers": ["10.1.0.1", "10.1.0.2"]}], id="valid"), + ], + ) + def test_valid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPSpecificPeers.Input valid inputs.""" + VerifyBGPSpecificPeers.Input(address_families=address_families) + + @pytest.mark.parametrize( + ("address_families"), + [ + pytest.param([{"afi": "evpn"}], id="None"), + ], + ) + def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: + """Test VerifyBGPSpecificPeers.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPSpecificPeers.Input(address_families=address_families)