From f8f2259bf7d290423ed9e280a5f6600500051c49 Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Thu, 17 Oct 2024 14:31:52 -0400 Subject: [PATCH 01/21] fix(anta.tests): First round of cleaning up BGP tests module --- anta/tests/routing/bgp.py | 1 - anta/tests/routing/bgp_input_models.py | 114 +++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) 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 4f55a0f4b..874aeb0e9 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -389,7 +389,6 @@ def test(self) -> None: 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}") - class VerifyBGPExchangedRoutes(AntaTest): """Verifies the advertised and received routes of BGP peers. 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) From 1b31f40bda82526a5900a517de18d7777663213a Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Thu, 24 Oct 2024 12:01:28 -0400 Subject: [PATCH 02/21] Added queues knob --- anta/tests/routing/bgp_input_models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 a4dd185cb4818ab701263027f8675e88ca903cf0 Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Fri, 25 Oct 2024 13:01:57 -0400 Subject: [PATCH 03/21] Update unit tests for VerifyBGPPeerCount --- anta/tests/routing/bgp_input_models.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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}" From bee14620b48a54969ed761794f564fcde94f8b4a Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Tue, 29 Oct 2024 14:29:32 -0400 Subject: [PATCH 04/21] Updated unit tests --- anta/tests/routing/bgp_input_models.py | 125 ------------------------- tests/units/test_tools.py | 8 +- 2 files changed, 4 insertions(+), 129 deletions(-) delete mode 100644 anta/tests/routing/bgp_input_models.py diff --git a/anta/tests/routing/bgp_input_models.py b/anta/tests/routing/bgp_input_models.py deleted file mode 100644 index 282738220..000000000 --- a/anta/tests/routing/bgp_input_models.py +++ /dev/null @@ -1,125 +0,0 @@ -# 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.""" - 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: - """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.""" - # 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. - - 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}" - if self.afi in ["ipv4", "ipv6"]: - base_string += f" VRF:{self.vrf}" - 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/tests/units/test_tools.py b/tests/units/test_tools.py index b1f96a50c..b5410ee3c 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", 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"), + 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: From 1a24678d2e27e60440017578c827a9edf4bc76c1 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Wed, 30 Oct 2024 04:05:18 -0400 Subject: [PATCH 05/21] Added unit tests for helper function, updated docstrings --- tests/units/anta_tests/routing/test_bgp.py | 2 +- tests/units/test_tools.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index c5b8cedb6..18ce46a9b 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -851,7 +851,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "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", - ], + ] }, }, { 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 fef781019e7d7eb4310ce24ba042150c4eea7428 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Wed, 30 Oct 2024 08:07:20 -0400 Subject: [PATCH 06/21] Updated unit tests for _check_bgp_neighbor_capability --- tests/units/test_tools.py | 17 ----------------- 1 file changed, 17 deletions(-) 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 de277c72f1c1f4e19188b8a2b961c1a9863328a9 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Tue, 5 Nov 2024 06:10:21 -0500 Subject: [PATCH 07/21] Resolved conflicts and updated the space after : in failure msgs --- anta/tests/routing/bgp.py | 3 + tests/units/anta_tests/routing/test_bgp.py | 70 ---------------------- tests/units/test_tools.py | 8 +-- 3 files changed, 7 insertions(+), 74 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 874aeb0e9..60d64b22d 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -237,6 +237,9 @@ class VerifyBGPPeersHealth(AntaTest): ``` """ + name = "VerifyBGPPeersHealth" + description = "Verifies the health of BGP peers for the given address families." +>>>>>>> 44581a0 (Resolved conflicts and updated the space after : in failure msgs) categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 18ce46a9b..20c9ae6b7 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -301,76 +301,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], }, }, - { - "name": "failure-wrong-count-peer-state-check-true", - "test": VerifyBGPPeerCount, - "eos_data": [ - { - "vrfs": { - "default": { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": { - "10.1.0.1": { - "peerState": "Established", - "peerAsn": "65100", - "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, - "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, - }, - "10.1.0.2": { - "peerState": "Established", - "peerAsn": "65100", - "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": { - "vrf": "DEV", - "routerId": "10.1.0.3", - "asn": "65120", - "peers": { - "10.1.254.1": { - "peerState": "Established", - "peerAsn": "65120", - "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, - } - }, - }, - } - }, - ], - "inputs": { - "address_families": [ - {"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": { - "result": "failure", - "messages": [ - "AFI: evpn - Expected: 3, Actual: 2", - "AFI: ipv4 SAFI: unicast VRF: DEV - Expected: 2, Actual: 1", - ], - }, - }, { "name": "failure-wrong-count", "test": VerifyBGPPeerCount, 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 01170cd979a237de974ea6267cf722eb89b5e72c Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Tue, 5 Nov 2024 19:33:38 -0500 Subject: [PATCH 08/21] fix(anta.tests): Refactor input subclasses of BGP tests --- anta/input_models/routing/bgp.py | 75 ++- anta/tests/routing/bgp.py | 760 ++++++++++++++----------------- 2 files changed, 410 insertions(+), 425 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index a291809c6..0e17f5acd 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -5,13 +5,14 @@ from __future__ import annotations -from ipaddress import IPv4Address, IPv6Address +from ipaddress import IPv4Address, IPv4Network, IPv6Address from typing import TYPE_CHECKING, Any from warnings import warn -from pydantic import BaseModel, ConfigDict, PositiveInt, model_validator +from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator +from pydantic_extra_types.mac_address import MacAddress -from anta.custom_types import Afi, Safi +from anta.custom_types import Afi, BgpDropStats, MultiProtocolCaps, Safi, Vni if TYPE_CHECKING: import sys @@ -97,7 +98,7 @@ def eos_key(self) -> str: 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 human-readable string representation of the BgpAddressFamily for reporting. Examples -------- @@ -128,3 +129,69 @@ def __init__(self, **data: Any) -> None: # noqa: ANN401 stacklevel=2, ) super().__init__(**data) + + +class BgpPeer(BaseModel): + """Model for a BGP peer. + + Only IPv4 peers are supported for now. + """ + + model_config = ConfigDict(extra="forbid") + peer_address: IPv4Address + """IPv4 address of the BGP peer.""" + vrf: str = "default" + """Optional VRF for the BGP peer. Defaults to `default`.""" + advertised_routes: list[IPv4Network] | None = None + """List of advertised routes in CIDR format. Required field in the `VerifyBGPExchangedRoutes` test.""" + received_routes: list[IPv4Network] | None = None + """List of received routes in CIDR format. Required field in the `VerifyBGPExchangedRoutes` test.""" + capabilities: list[MultiProtocolCaps] | None = None + """List of BGP multiprotocol capabilities. Required field in the `VerifyBGPPeerMPCaps` test.""" + strict: bool = False + """If True, requires exact match of the provided BGP multiprotocol capabilities. + + Optional field in the `VerifyBGPPeerMPCaps` test. Defaults to False.""" + hold_time: int | None = Field(default=None, ge=3, le=7200) + """BGP hold time in seconds. Required field in the `VerifyBGPTimers` test.""" + keep_alive_time: int | None = Field(default=None, ge=0, le=3600) + """BGP keepalive time in seconds. Required field in the `VerifyBGPTimers` test.""" + drop_stats: list[BgpDropStats] | None = None + """List of drop statistics to be verified. + + Optional field in the `VerifyBGPPeerDropStats` test. If not provided, the test will verifies all drop statistics.""" + + def __str__(self) -> str: + """Return a human-readable string representation of the BgpPeer for reporting.""" + return f"Peer: {self.peer_address} VRF: {self.vrf}" + + +class BgpNeighbor(BgpPeer): + """Alias for the BgpPeer model to maintain backward compatibility. + + When initialized, it will emit a depreciation warning and call the BgpPeer 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="BgpNeighbor model is deprecated and will be removed in ANTA v2.0.0. Use the BgpPeer model instead.", + category=DeprecationWarning, + stacklevel=2, + ) + super().__init__(**data) + + +class VxlanEndpoint(BaseModel): + """Model for a VXLAN endpoint.""" + + address: IPv4Address | MacAddress + """IPv4 or MAC address of the VXLAN endpoint.""" + vni: Vni + """VNI of the VXLAN endpoint.""" + + def __str__(self) -> str: + """Return a human-readable string representation of the VxlanEndpoint for reporting.""" + return f"Address: {self.address} VNI: {self.vni}" diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 60d64b22d..9dc78484f 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -7,15 +7,13 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations -from ipaddress import IPv4Address, IPv4Network -from typing import TYPE_CHECKING, Any, ClassVar +from ipaddress import IPv4Address +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar 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 BgpDropStats, BgpUpdateError, MultiProtocolCaps, Vni -from anta.input_models.routing.bgp import BgpAddressFamily, BgpAfi +from anta.custom_types import BgpUpdateError +from anta.input_models.routing.bgp import BgpAddressFamily, BgpAfi, BgpNeighbor, BgpPeer, VxlanEndpoint from anta.models import AntaCommand, AntaTemplate, AntaTest from anta.tools import format_data, get_item, get_value @@ -27,60 +25,8 @@ else: from typing_extensions import Self - -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]]]]]: - """Identify missing BGP routes and invalid or inactive route entries. - - This function checks the BGP output from the device against the expected routes. - - It identifies any missing routes as well as any routes that are invalid or inactive. The results are returned in a dictionary. - - Parameters - ---------- - bgp_routes - The list of expected routes. - bgp_output - The BGP output from the device. - peer - The IP address of the BGP peer. - vrf - The name of the VRF for which the routes need to be verified. - route_type - The type of BGP routes. Defaults to 'advertised_routes'. - - Returns - ------- - dict[str, dict[str, dict[str, dict[str, list[str]]]]] - A dictionary containing the missing routes and invalid or inactive routes. - - """ - # Prepare the failure routes dictionary - failure_routes: dict[str, dict[str, Any]] = {} - - # Iterate over the expected BGP routes - for route in bgp_routes: - str_route = str(route) - failure: dict[str, Any] = {"bgp_peers": {peer: {vrf: {route_type: {}}}}} - - # Check if the route is missing in the BGP output - if str_route not in bgp_output: - # If missing, add it to the failure routes dictionary - failure["bgp_peers"][peer][vrf][route_type][str_route] = "Not found" - failure_routes = deep_update(failure_routes, failure) - continue - - # Check if the route is active and valid - is_active = bgp_output[str_route]["bgpRoutePaths"][0]["routeType"]["valid"] - is_valid = bgp_output[str_route]["bgpRoutePaths"][0]["routeType"]["active"] - - # If the route is either inactive or invalid, add it to the failure routes dictionary - if not is_active or not is_valid: - failure["bgp_peers"][peer][vrf][route_type][str_route] = {"valid": is_valid, "active": is_active} - failure_routes = deep_update(failure_routes, failure) - - return failure_routes +# Using a TypeVar for the BgpPeer model since mypy thinks it's a ClassVar and not a valid type when used in field validators +T = TypeVar("T", bound=BgpPeer) def _check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool: @@ -237,9 +183,6 @@ class VerifyBGPPeersHealth(AntaTest): ``` """ - name = "VerifyBGPPeersHealth" - description = "Verifies the health of BGP peers for the given address families." ->>>>>>> 44581a0 (Resolved conflicts and updated the space after : in failure msgs) categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] @@ -395,12 +338,20 @@ def test(self) -> None: class VerifyBGPExchangedRoutes(AntaTest): """Verifies the advertised and received routes of BGP peers. - The route type should be 'valid' and 'active' for a specified VRF. + This test performs the following checks for each specified peer: + + 1. For each advertised and received route: + - Confirms that the route exists in the BGP route table. + - Verifies that the route is in an 'active' and 'valid' state. Expected Results ---------------- - * Success: If the BGP peers have correctly advertised and received routes of type 'valid' and 'active' for a specified VRF. - * Failure: If a BGP peer is not found, the expected advertised/received routes are not found, or the routes are not 'valid' or 'active'. + * Success: If all of the following conditions are met: + - All specified advertised/received routes are found in the BGP route table. + - All routes are in both 'active' and 'valid' states. + * Failure: If any of the following occur: + - An advertised/received route is not found in the BGP route table. + - Any route is not in an 'active' or 'valid' state. Examples -------- @@ -434,71 +385,85 @@ class VerifyBGPExchangedRoutes(AntaTest): class Input(AntaTest.Input): """Input model for the VerifyBGPExchangedRoutes test.""" - bgp_peers: list[BgpNeighbor] - """List of BGP neighbors.""" - - class BgpNeighbor(BaseModel): - """Model for a BGP neighbor.""" + bgp_peers: list[BgpPeer] + """List of BGP peers.""" + BgpNeighbor: ClassVar[type[BgpNeighbor]] = BgpNeighbor - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" - advertised_routes: list[IPv4Network] - """List of advertised routes in CIDR format.""" - received_routes: list[IPv4Network] - """List of received routes in CIDR format.""" + @field_validator("bgp_peers") + @classmethod + def validate_bgp_peers(cls, bgp_peers: list[BgpPeer]) -> list[BgpPeer]: + """Validate that 'peers' field is provided in each address family.""" + for peer in bgp_peers: + if peer.advertised_routes is None or peer.received_routes is None: + msg = f"{peer} 'advertised_routes' or 'received_routes' field missing in the input" + raise ValueError(msg) + return bgp_peers def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each BGP neighbor in the input list.""" + """Render the template for each BGP peer in the input list.""" return [template.render(peer=str(bgp_peer.peer_address), vrf=bgp_peer.vrf) for bgp_peer in self.inputs.bgp_peers] @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPExchangedRoutes.""" - failures: dict[str, dict[str, Any]] = {"bgp_peers": {}} + self.result.is_success() - # Iterating over command output for different peers - for command in self.instance_commands: - peer = command.params.peer - vrf = command.params.vrf - for input_entry in self.inputs.bgp_peers: - if str(input_entry.peer_address) == peer and input_entry.vrf == vrf: - advertised_routes = input_entry.advertised_routes - received_routes = input_entry.received_routes - break - failure = {vrf: ""} - - # Verify if a BGP peer is configured with the provided vrf - if not (bgp_routes := get_value(command.json_output, f"vrfs.{vrf}.bgpRouteEntries")): - failure[vrf] = "Not configured" - failures["bgp_peers"][peer] = failure - continue + num_peers = len(self.inputs.bgp_peers) - # Validate advertised routes - if "advertised-routes" in command.command: - failure_routes = _add_bgp_routes_failure(advertised_routes, bgp_routes, peer, vrf) + # Process each peer and its corresponding command pair + for peer_idx, peer in enumerate(self.inputs.bgp_peers): + # For n peers, advertised routes are at indices 0 to n-1, and received routes are at indices n to 2n-1 + advertised_routes_cmd = self.instance_commands[peer_idx] + received_routes_cmd = self.instance_commands[peer_idx + num_peers] - # Validate received routes - else: - failure_routes = _add_bgp_routes_failure(received_routes, bgp_routes, peer, vrf, route_type="received_routes") - failures = deep_update(failures, failure_routes) + # Get the BGP route entries of each command + advertised_routes_entries = get_value(advertised_routes_cmd.json_output, f"vrfs.{peer.vrf}.bgpRouteEntries", default={}) + received_routes_entries = get_value(received_routes_cmd.json_output, f"vrfs.{peer.vrf}.bgpRouteEntries", default={}) - if not failures["bgp_peers"]: - self.result.is_success() - else: - self.result.is_failure(f"Following BGP peers are not found or routes are not exchanged properly:\n{failures}") + # Validate both advertised and received routes + for route_type, routes in zip(["Advertised", "Received"], [peer.advertised_routes, peer.received_routes]): + entries = advertised_routes_entries if route_type == "Advertised" else received_routes_entries + for route in routes: + # Check if the route is found + if str(route) not in entries: + self.result.is_failure(f"{peer} {route_type} route: {route} - Not found") + continue + + # Check if the route is active and valid + route_paths = entries[str(route)]["bgpRoutePaths"][0]["routeType"] + is_active = route_paths["active"] + is_valid = route_paths["valid"] + if not is_active or not is_valid: + self.result.is_failure(f"{peer} {route_type} route: {route} - Invalid/inactive; Valid: {is_valid}, Active: {is_active}") class VerifyBGPPeerMPCaps(AntaTest): - """Verifies the multiprotocol capabilities of a BGP peer in a specified VRF. + """Verifies the multiprotocol capabilities of BGP peers. - Supports `strict: True` to verify that only the specified capabilities are configured, requiring an exact match. + This test performs the following checks for each specified peer: + + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. For each specified capability: + - Validates that the capability is present in the peer's configuration. + - Confirms that the capability is advertised, received, and enabled. + 4. When strict mode is enabled (`strict: true`): + - Verifies that only the specified capabilities are configured. + - Ensures an exact match between configured and expected capabilities. Expected Results ---------------- - * Success: The test will pass if the BGP peer's multiprotocol capabilities are advertised, received, and enabled in the specified VRF. - * Failure: The test will fail if BGP peers are not found or multiprotocol capabilities are not advertised, received, and enabled in the specified VRF. + * Success: If all of the following conditions are met: + - The specified VRF is configured. + - All specified peers are found in the BGP configuration. + - All specified capabilities are present and properly negotiated. + - In strict mode, only the specified capabilities are configured. + * Failure: If any of the following occur: + - The specified VRF is not configured. + - A specified peer is not found in the BGP configuration. + - A specified capability is not found. + - A capability is not properly negotiated (not advertised, received, or enabled). + - In strict mode, additional or missing capabilities are detected. Examples -------- @@ -515,7 +480,6 @@ class VerifyBGPPeerMPCaps(AntaTest): ``` """ - description = "Verifies the multiprotocol capabilities of a BGP peer." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] @@ -524,78 +488,78 @@ class Input(AntaTest.Input): bgp_peers: list[BgpPeer] """List of BGP peers""" + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" - strict: bool = False - """If True, requires exact matching of provided capabilities. Defaults to False.""" - capabilities: list[MultiProtocolCaps] - """List of multiprotocol capabilities to be verified.""" + @field_validator("bgp_peers") + @classmethod + def validate_bgp_peers(cls, bgp_peers: list[T]) -> list[T]: + """Validate that 'peers' field is provided in each address family.""" + for peer in bgp_peers: + if peer.capabilities is None: + msg = f"{peer} 'capabilities' field missing in the input" + raise ValueError(msg) + return bgp_peers @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeerMPCaps.""" - failures: dict[str, Any] = {"bgp_peers": {}} - - # Iterate over each bgp peer. - for bgp_peer in self.inputs.bgp_peers: - peer = str(bgp_peer.peer_address) - vrf = bgp_peer.vrf - capabilities = bgp_peer.capabilities - failure: dict[str, dict[str, dict[str, Any]]] = {"bgp_peers": {peer: {vrf: {}}}} - - # Check if BGP output exists. - if ( - not (bgp_output := get_value(self.instance_commands[0].json_output, f"vrfs.{vrf}.peerList")) - or (bgp_output := get_item(bgp_output, "peerAddress", peer)) is None - ): - failure["bgp_peers"][peer][vrf] = {"status": "Not configured"} - failures = deep_update(failures, failure) + self.result.is_success() + + output = self.instance_commands[0].json_output + + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + + # Check if the VRF is configured + if (vrf_output := get_value(output, f"vrfs.{peer.vrf}")) is None: + self.result.is_failure(f"{peer} - VRF not configured") + continue + + # Check if the peer is found + if (peer_data := get_item(vrf_output["peerList"], "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") continue - # Fetching the capabilities output. - bgp_output = get_value(bgp_output, "neighborCapabilities.multiprotocolCaps") + # Fetching the multiprotocol capabilities + act_mp_caps = get_value(peer_data, "neighborCapabilities.multiprotocolCaps") - if bgp_peer.strict and sorted(capabilities) != sorted(bgp_output): - failure["bgp_peers"][peer][vrf] = { - "status": f"Expected only `{', '.join(capabilities)}` capabilities should be listed but found `{', '.join(bgp_output)}` instead." - } - failures = deep_update(failures, failure) + # If strict is True, check if only the specified capabilities are configured + if peer.strict and sorted(peer.capabilities) != sorted(act_mp_caps): + self.result.is_failure(f"{peer} - Mismatch; Expected: {', '.join(peer.capabilities)} Actual: {', '.join(act_mp_caps)}") continue # Check each capability - for capability in capabilities: - capability_output = bgp_output.get(capability) + for capability in peer.capabilities: + # Check if the capability is found + if (capability_status := get_value(act_mp_caps, capability)) is None: + self.result.is_failure(f"{peer} - {capability} not found") + continue - # Check if capabilities are missing - if not capability_output: - failure["bgp_peers"][peer][vrf][capability] = "not found" - failures = deep_update(failures, failure) + # Check if the capability is advertised, received, and enabled + if not _check_bgp_neighbor_capability(capability_status): + self.result.is_failure(f"{peer} - {capability} not negotiated; {format_data(capability_status)}") - # Check if capabilities are not advertised, received, or enabled - elif not all(capability_output.get(prop, False) for prop in ["advertised", "received", "enabled"]): - failure["bgp_peers"][peer][vrf][capability] = capability_output - failures = deep_update(failures, failure) - # Check if there are any failures - if not failures["bgp_peers"]: - self.result.is_success() - else: - self.result.is_failure(f"Following BGP peer multiprotocol capabilities are not found or not ok:\n{failures}") +class VerifyBGPPeerASNCap(AntaTest): + """Verifies the four octet asn ASN capability of BGP peers. + This test performs the following checks for each specified peer: -class VerifyBGPPeerASNCap(AntaTest): - """Verifies the four octet asn capabilities of a BGP peer in a specified VRF. + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Validates that the capability is present in the peer's configuration. + 4. Confirms that the capability is advertised, received, and enabled. Expected Results ---------------- - * Success: The test will pass if BGP peer's four octet asn capabilities are advertised, received, and enabled in the specified VRF. - * Failure: The test will fail if BGP peers are not found or four octet asn capabilities are not advertised, received, and enabled in the specified VRF. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - The four octet ASN capability is present in each peer's configuration. + - The capability is properly negotiated (advertised, received, and enabled) for all peers. + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - The four octet ASN capability is not present for a peer. + - The capability is not properly negotiated (not advertised, received, or enabled) for any peer. Examples -------- @@ -609,7 +573,6 @@ class VerifyBGPPeerASNCap(AntaTest): ``` """ - description = "Verifies the four octet asn capabilities of a BGP peer." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] @@ -618,61 +581,54 @@ class Input(AntaTest.Input): bgp_peers: list[BgpPeer] """List of BGP peers.""" - - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeerASNCap.""" - failures: dict[str, Any] = {"bgp_peers": {}} - - # Iterate over each bgp peer - for bgp_peer in self.inputs.bgp_peers: - peer = str(bgp_peer.peer_address) - vrf = bgp_peer.vrf - failure: dict[str, dict[str, dict[str, Any]]] = {"bgp_peers": {peer: {vrf: {}}}} - - # Check if BGP output exists - if ( - not (bgp_output := get_value(self.instance_commands[0].json_output, f"vrfs.{vrf}.peerList")) - or (bgp_output := get_item(bgp_output, "peerAddress", peer)) is None - ): - failure["bgp_peers"][peer][vrf] = {"status": "Not configured"} - failures = deep_update(failures, failure) - continue + self.result.is_success() - bgp_output = get_value(bgp_output, "neighborCapabilities.fourOctetAsnCap") + output = self.instance_commands[0].json_output - # Check if four octet asn capabilities are found - if not bgp_output: - failure["bgp_peers"][peer][vrf] = {"fourOctetAsnCap": "not found"} - failures = deep_update(failures, failure) + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) - # Check if capabilities are not advertised, received, or enabled - elif not all(bgp_output.get(prop, False) for prop in ["advertised", "received", "enabled"]): - failure["bgp_peers"][peer][vrf] = {"fourOctetAsnCap": bgp_output} - failures = deep_update(failures, failure) + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") + continue - # Check if there are any failures - if not failures["bgp_peers"]: - self.result.is_success() - else: - self.result.is_failure(f"Following BGP peer four octet asn capabilities are not found or not ok:\n{failures}") + # Check if the 4-octet ASN capability is found + if (capablity_status := get_value(peer_data, "neighborCapabilities.fourOctetAsnCap")) is None: + self.result.is_failure(f"{peer} - 4-octet ASN capability not found") + continue + + # Check if the 4-octet ASN capability is advertised, received, and enabled + if not _check_bgp_neighbor_capability(capablity_status): + self.result.is_failure(f"{peer} - 4-octet ASN capability not negotiated; {format_data(capablity_status)}") class VerifyBGPPeerRouteRefreshCap(AntaTest): """Verifies the route refresh capabilities of a BGP peer in a specified VRF. + This test performs the following checks for each specified peer: + + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Validates that the route refresh capability is present in the peer's configuration. + 4. Confirms that the capability is advertised, received, and enabled. + Expected Results ---------------- - * Success: The test will pass if the BGP peer's route refresh capabilities are advertised, received, and enabled in the specified VRF. - * Failure: The test will fail if BGP peers are not found or route refresh capabilities are not advertised, received, and enabled in the specified VRF. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - The route refresh capability is present in each peer's configuration. + - The capability is properly negotiated (advertised, received, and enabled) for all peers. + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - The route refresh capability is not present for a peer. + - The capability is not properly negotiated (not advertised, received, or enabled) for any peer. Examples -------- @@ -695,61 +651,54 @@ class Input(AntaTest.Input): bgp_peers: list[BgpPeer] """List of BGP peers""" - - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeerRouteRefreshCap.""" - failures: dict[str, Any] = {"bgp_peers": {}} - - # Iterate over each bgp peer - for bgp_peer in self.inputs.bgp_peers: - peer = str(bgp_peer.peer_address) - vrf = bgp_peer.vrf - failure: dict[str, dict[str, dict[str, Any]]] = {"bgp_peers": {peer: {vrf: {}}}} - - # Check if BGP output exists - if ( - not (bgp_output := get_value(self.instance_commands[0].json_output, f"vrfs.{vrf}.peerList")) - or (bgp_output := get_item(bgp_output, "peerAddress", peer)) is None - ): - failure["bgp_peers"][peer][vrf] = {"status": "Not configured"} - failures = deep_update(failures, failure) - continue + self.result.is_success() - bgp_output = get_value(bgp_output, "neighborCapabilities.routeRefreshCap") + output = self.instance_commands[0].json_output - # Check if route refresh capabilities are found - if not bgp_output: - failure["bgp_peers"][peer][vrf] = {"routeRefreshCap": "not found"} - failures = deep_update(failures, failure) + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) - # Check if capabilities are not advertised, received, or enabled - elif not all(bgp_output.get(prop, False) for prop in ["advertised", "received", "enabled"]): - failure["bgp_peers"][peer][vrf] = {"routeRefreshCap": bgp_output} - failures = deep_update(failures, failure) + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") + continue - # Check if there are any failures - if not failures["bgp_peers"]: - self.result.is_success() - else: - self.result.is_failure(f"Following BGP peer route refresh capabilities are not found or not ok:\n{failures}") + # Check if the route refresh capability is found + if (capablity_status := get_value(peer_data, "neighborCapabilities.routeRefreshCap")) is None: + self.result.is_failure(f"{peer} - Route refresh capability not found") + continue + + # Check if the route refresh capability is advertised, received, and enabled + if not _check_bgp_neighbor_capability(capablity_status): + self.result.is_failure(f"{peer} - Route refresh capability not negotiated; {format_data(capablity_status)}") class VerifyBGPPeerMD5Auth(AntaTest): """Verifies the MD5 authentication and state of IPv4 BGP peers in a specified VRF. + This test performs the following checks for each specified peer: + + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Validates that the BGP session is in `Established` state. + 4. Confirms that MD5 authentication is enabled for the peer. + Expected Results ---------------- - * Success: The test will pass if IPv4 BGP peers are configured with MD5 authentication and state as established in the specified VRF. - * Failure: The test will fail if IPv4 BGP peers are not found, state is not as established or MD5 authentication is not enabled in the specified VRF. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - All peers are in `Established` state. + - MD5 authentication is enabled for all peers. + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - A peer's session state is not `Established`. + - MD5 authentication is not enabled for a peer. Examples -------- @@ -774,56 +723,53 @@ class Input(AntaTest.Input): bgp_peers: list[BgpPeer] """List of IPv4 BGP peers.""" - - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeerMD5Auth.""" - failures: dict[str, Any] = {"bgp_peers": {}} - - # Iterate over each command - for bgp_peer in self.inputs.bgp_peers: - peer = str(bgp_peer.peer_address) - vrf = bgp_peer.vrf - failure: dict[str, dict[str, dict[str, Any]]] = {"bgp_peers": {peer: {vrf: {}}}} - - # Check if BGP output exists - if ( - not (bgp_output := get_value(self.instance_commands[0].json_output, f"vrfs.{vrf}.peerList")) - or (bgp_output := get_item(bgp_output, "peerAddress", peer)) is None - ): - failure["bgp_peers"][peer][vrf] = {"status": "Not configured"} - failures = deep_update(failures, failure) - continue + self.result.is_success() - # Check if BGP peer state and authentication - state = bgp_output.get("state") - md5_auth_enabled = bgp_output.get("md5AuthEnabled") - if state != "Established" or not md5_auth_enabled: - failure["bgp_peers"][peer][vrf] = {"state": state, "md5_auth_enabled": md5_auth_enabled} - failures = deep_update(failures, failure) + output = self.instance_commands[0].json_output - # Check if there are any failures - if not failures["bgp_peers"]: - self.result.is_success() - else: - self.result.is_failure(f"Following BGP peers are not configured, not established or MD5 authentication is not enabled:\n{failures}") + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) + + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") + continue + + # Check BGP peer state and MD5 authentication + state = peer_data.get("state") + md5_auth_enabled = peer_data.get("md5AuthEnabled") + if state != "Established": + self.result.is_failure(f"{peer} - Session state is not established; State: {state}") + if not md5_auth_enabled: + self.result.is_failure(f"{peer} - Session does not have MD5 authentication enabled") class VerifyEVPNType2Route(AntaTest): """Verifies the EVPN Type-2 routes for a given IPv4 or MAC address and VNI. + This test performs the following checks for each specified VXLAN endpoint: + + 1. Verifies that the endpoint exists in the BGP EVPN table. + 2. For each EVPN route found: + - Validates that at least one path exists for the route. + - Confirms that at least one path is valid and active. + Expected Results ---------------- - * Success: If all provided VXLAN endpoints have at least one valid and active path to their EVPN Type-2 routes. - * Failure: If any of the provided VXLAN endpoints do not have at least one valid and active path to their EVPN Type-2 routes. + * Success: If all of the following conditions are met: + - All specified VXLAN endpoints are found in the BGP EVPN table. + - Each endpoint has at least one EVPN Type-2 route. + - Each route has at least one path that is both valid and active. + * Failure: If any of the following occur: + - A VXLAN endpoint is not found in the BGP EVPN table. + - No EVPN Type-2 route exists for an endpoint. + - No valid and active path exists for a route. Examples -------- @@ -847,14 +793,7 @@ class Input(AntaTest.Input): vxlan_endpoints: list[VxlanEndpoint] """List of VXLAN endpoints to verify.""" - - class VxlanEndpoint(BaseModel): - """Model for a VXLAN endpoint.""" - - address: IPv4Address | MacAddress - """IPv4 or MAC address of the VXLAN endpoint.""" - vni: Vni - """VNI of the VXLAN endpoint.""" + VxlanEndpoint: ClassVar[type[VxlanEndpoint]] = VxlanEndpoint def render(self, template: AntaTemplate) -> list[AntaCommand]: """Render the template for each VXLAN endpoint in the input list.""" @@ -864,19 +803,15 @@ def render(self, template: AntaTemplate) -> list[AntaCommand]: def test(self) -> None: """Main test function for VerifyEVPNType2Route.""" self.result.is_success() - no_evpn_routes = [] - bad_evpn_routes = [] - for command in self.instance_commands: - address = command.params.address - vni = command.params.vni + for command, endpoint in zip(self.instance_commands, self.inputs.vxlan_endpoints): # Verify that the VXLAN endpoint is in the BGP EVPN table evpn_routes = command.json_output["evpnRoutes"] if not evpn_routes: - no_evpn_routes.append((address, vni)) + self.result.is_failure(f"{endpoint} - No EVPN Type-2 route") continue # Verify that each EVPN route has at least one valid and active path - for route, route_data in evpn_routes.items(): + for route_data in evpn_routes.values(): has_active_path = False for path in route_data["evpnRoutePaths"]: if path["routeType"]["valid"] is True and path["routeType"]["active"] is True: @@ -884,21 +819,29 @@ def test(self) -> None: has_active_path = True break if not has_active_path: - bad_evpn_routes.append(route) - - if no_evpn_routes: - self.result.is_failure(f"The following VXLAN endpoint do not have any EVPN Type-2 route: {no_evpn_routes}") - if bad_evpn_routes: - self.result.is_failure(f"The following EVPN Type-2 routes do not have at least one valid and active path: {bad_evpn_routes}") + self.result.is_failure(f"{endpoint} - No valid and active path") class VerifyBGPAdvCommunities(AntaTest): - """Verifies if the advertised communities of BGP peers are standard, extended, and large in the specified VRF. + """Verifies that advertised communities are standard, extended and large for BGP peers. + + This test performs the following checks for each specified peer: + + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Validates that all required community types are advertised: + - Standard communities + - Extended communities + - Large communities Expected Results ---------------- - * Success: The test will pass if the advertised communities of BGP peers are standard, extended, and large in the specified VRF. - * Failure: The test will fail if the advertised communities of BGP peers are not standard, extended, and large in the specified VRF. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - Each peer advertises standard, extended and large communities. + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - A peer does not advertise standard, extended or large communities. Examples -------- @@ -923,54 +866,46 @@ class Input(AntaTest.Input): bgp_peers: list[BgpPeer] """List of BGP peers.""" - - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPAdvCommunities.""" - failures: dict[str, Any] = {"bgp_peers": {}} - - # Iterate over each bgp peer - for bgp_peer in self.inputs.bgp_peers: - peer = str(bgp_peer.peer_address) - vrf = bgp_peer.vrf - failure: dict[str, dict[str, dict[str, Any]]] = {"bgp_peers": {peer: {vrf: {}}}} - - # Verify BGP peer - if ( - not (bgp_output := get_value(self.instance_commands[0].json_output, f"vrfs.{vrf}.peerList")) - or (bgp_output := get_item(bgp_output, "peerAddress", peer)) is None - ): - failure["bgp_peers"][peer][vrf] = {"status": "Not configured"} - failures = deep_update(failures, failure) - continue + self.result.is_success() - # Verify BGP peer's advertised communities - bgp_output = bgp_output.get("advertisedCommunities") - if not bgp_output["standard"] or not bgp_output["extended"] or not bgp_output["large"]: - failure["bgp_peers"][peer][vrf] = {"advertised_communities": bgp_output} - failures = deep_update(failures, failure) + output = self.instance_commands[0].json_output - if not failures["bgp_peers"]: - self.result.is_success() - else: - self.result.is_failure(f"Following BGP peers are not configured or advertised communities are not standard, extended, and large:\n{failures}") + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) + + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") + continue + + # Check BGP peer advertised communities + if not all(get_value(peer_data, f"advertisedCommunities.{community}") is True for community in ["standard", "extended", "large"]): + self.result.is_failure(f"{peer} - {format_data(peer_data['advertisedCommunities'])}") class VerifyBGPTimers(AntaTest): - """Verifies if the BGP peers are configured with the correct hold and keep-alive timers in the specified VRF. + """Verifies the timers of BGP peers. + + This test performs the following checks for each specified peer: + + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Confirms the BGP session hold time/keepalive timers match the expected value. Expected Results ---------------- - * Success: The test will pass if the hold and keep-alive timers are correct for BGP peers in the specified VRF. - * Failure: The test will fail if BGP peers are not found or hold and keep-alive timers are not correct in the specified VRF. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - The hold time/keepalive timers match the expected value for each peer. + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - The hold time/keepalive timers do not match the expected value for a peer. Examples -------- @@ -990,7 +925,6 @@ class VerifyBGPTimers(AntaTest): ``` """ - description = "Verifies the timers of a BGP peer." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] @@ -999,59 +933,62 @@ class Input(AntaTest.Input): bgp_peers: list[BgpPeer] """List of BGP peers""" + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" - hold_time: int = Field(ge=3, le=7200) - """BGP hold time in seconds.""" - keep_alive_time: int = Field(ge=0, le=3600) - """BGP keep-alive time in seconds.""" + @field_validator("bgp_peers") + @classmethod + def validate_bgp_peers(cls, bgp_peers: list[T]) -> list[T]: + """Validate that 'peers' field is provided in each address family.""" + for peer in bgp_peers: + if peer.hold_time is None or peer.keep_alive_time is None: + msg = f"{peer} 'hold_time' or 'keep_alive_time' field missing in the input" + raise ValueError(msg) + return bgp_peers @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPTimers.""" - failures: dict[str, Any] = {} - - # Iterate over each bgp peer - for bgp_peer in self.inputs.bgp_peers: - peer_address = str(bgp_peer.peer_address) - vrf = bgp_peer.vrf - hold_time = bgp_peer.hold_time - keep_alive_time = bgp_peer.keep_alive_time - - # Verify BGP peer - if ( - not (bgp_output := get_value(self.instance_commands[0].json_output, f"vrfs.{vrf}.peerList")) - or (bgp_output := get_item(bgp_output, "peerAddress", peer_address)) is None - ): - failures[peer_address] = {vrf: "Not configured"} - continue + self.result.is_success() + + output = self.instance_commands[0].json_output - # Verify BGP peer's hold and keep alive timers - if bgp_output.get("holdTime") != hold_time or bgp_output.get("keepaliveTime") != keep_alive_time: - failures[peer_address] = {vrf: {"hold_time": bgp_output.get("holdTime"), "keep_alive_time": bgp_output.get("keepaliveTime")}} + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) - if not failures: - self.result.is_success() - else: - self.result.is_failure(f"Following BGP peers are not configured or hold and keep-alive timers are not correct:\n{failures}") + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") + continue + + # Check BGP peer timers + if peer_data["holdTime"] != peer.hold_time: + self.result.is_failure(f"{peer} - Hold time mismatch; Expected: {peer.hold_time} Actual: {peer_data['holdTime']}") + if peer_data["keepaliveTime"] != peer.keep_alive_time: + self.result.is_failure(f"{peer} - Keepalive time mismatch; Expected: {peer.keep_alive_time} Actual: {peer_data['keepaliveTime']}") class VerifyBGPPeerDropStats(AntaTest): """Verifies BGP NLRI drop statistics for the provided BGP IPv4 peer(s). - By default, all drop statistics counters will be checked for any non-zero values. - An optional list of specific drop statistics can be provided for granular testing. + This test performs the following checks for each specified peer: + + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Validates the BGP drop statistics: + - If specific drop statistics are provided, checks only those counters. + - If no specific drop statistics are provided, checks all available counters. + - Confirms that all checked counters have a value of zero. Expected Results ---------------- - * Success: The test will pass if the BGP peer's drop statistic(s) are zero. - * Failure: The test will fail if the BGP peer's drop statistic(s) are non-zero/Not Found or peer is not configured. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - All specified drop statistics counters (or all counters if none specified) are zero. + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - Any checked drop statistics counter has a non-zero value. + - A specified drop statistics counter does not exist. Examples -------- @@ -1069,62 +1006,43 @@ class VerifyBGPPeerDropStats(AntaTest): """ categories: ClassVar[list[str]] = ["bgp"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show bgp neighbors {peer} vrf {vrf}", revision=3)] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show bgp neighbors vrf all", revision=3)] class Input(AntaTest.Input): """Input model for the VerifyBGPPeerDropStats test.""" bgp_peers: list[BgpPeer] """List of BGP peers""" - - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" - drop_stats: list[BgpDropStats] | None = None - """Optional list of drop statistics to be verified. If not provided, test will verifies all the drop statistics.""" - - def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each BGP peer in the input list.""" - return [template.render(peer=str(bgp_peer.peer_address), vrf=bgp_peer.vrf) for bgp_peer in self.inputs.bgp_peers] + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeerDropStats.""" - failures: dict[Any, Any] = {} + self.result.is_success() - for command, input_entry in zip(self.instance_commands, self.inputs.bgp_peers): - peer = command.params.peer - vrf = command.params.vrf - drop_statistics = input_entry.drop_stats + output = self.instance_commands[0].json_output - # Verify BGP peer - if not (peer_list := get_value(command.json_output, f"vrfs.{vrf}.peerList")) or (peer_detail := get_item(peer_list, "peerAddress", peer)) is None: - failures[peer] = {vrf: "Not configured"} + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + drop_stats_input = peer.drop_stats + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) + + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") continue - # Verify BGP peer's drop stats - drop_stats_output = peer_detail.get("dropStats", {}) + # Verify BGP peers' drop stats + drop_stats_output = peer_data["dropStats"] # In case drop stats not provided, It will check all drop statistics - if not drop_statistics: - drop_statistics = drop_stats_output + if not drop_stats_input: + drop_stats_input = drop_stats_output # Verify BGP peer's drop stats - drop_stats_not_ok = { - drop_stat: drop_stats_output.get(drop_stat, "Not Found") for drop_stat in drop_statistics if drop_stats_output.get(drop_stat, "Not Found") - } - if any(drop_stats_not_ok): - failures[peer] = {vrf: drop_stats_not_ok} - - # Check if any failures - if not failures: - self.result.is_success() - else: - self.result.is_failure(f"The following BGP peers are not configured or have non-zero NLRI drop statistics counters:\n{failures}") + for drop_stat in drop_stats_input: + if (stat_value := drop_stats_output.get(drop_stat, 0)) != 0: + self.result.is_failure(f"{peer} - Non-zero NLRI drop statistics counter; {drop_stat}: {stat_value}") class VerifyBGPPeerUpdateErrors(AntaTest): From f7f0950d6c1ff1a3ee134a4fc5b1ea693abce00f Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Mon, 11 Nov 2024 07:57:27 -0500 Subject: [PATCH 09/21] Updated test VerifyBGPPeerDropStats, VerifyBGPPeerUpdateErrors, VerifyBgpRouteMaps, VerifyBGPPeerRouteLimit --- anta/input_models/routing/bgp.py | 16 +- anta/tests/routing/bgp.py | 275 ++++++++++++++----------------- 2 files changed, 136 insertions(+), 155 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 0e17f5acd..5abbe1911 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator from pydantic_extra_types.mac_address import MacAddress -from anta.custom_types import Afi, BgpDropStats, MultiProtocolCaps, Safi, Vni +from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, Safi, Vni if TYPE_CHECKING: import sys @@ -160,6 +160,18 @@ class BgpPeer(BaseModel): """List of drop statistics to be verified. Optional field in the `VerifyBGPPeerDropStats` test. If not provided, the test will verifies all drop statistics.""" + update_errors: list[BgpUpdateError] | None = None + """List of update error counters to be verified. + + Optional field in the `VerifyBGPPeerUpdateErrors` test. If not provided, the test will verifies all the update error counters.""" + inbound_route_map: str | None = None + """Inbound route map applied, defaults to None. Required field in the `VerifyBgpRouteMaps` test.""" + outbound_route_map: str | None = None + """Outbound route map applied, defaults to None. Required field in the `VerifyBgpRouteMaps` test.""" + maximum_routes: int | None = Field(default=None, ge=0, le=4294967294) + """The maximum allowable number of BGP routes, `0` means unlimited. Required field in the `VerifyBGPPeerRouteLimit` test""" + warning_limit: int | None = Field(default=None, ge=0, le=4294967294) + """Optional maximum routes warning limit. If not provided, it defaults to `0` meaning no warning limit.""" def __str__(self) -> str: """Return a human-readable string representation of the BgpPeer for reporting.""" @@ -175,7 +187,7 @@ class BgpNeighbor(BgpPeer): """ def __init__(self, **data: Any) -> None: # noqa: ANN401 - """Initialize the BgpAfi class, emitting a depreciation warning.""" + """Initialize the BgpPeer class, emitting a depreciation warning.""" warn( message="BgpNeighbor model is deprecated and will be removed in ANTA v2.0.0. Use the BgpPeer model instead.", category=DeprecationWarning, diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 9dc78484f..ac3d2df1d 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -7,12 +7,10 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations -from ipaddress import IPv4Address -from typing import TYPE_CHECKING, Any, ClassVar, TypeVar +from typing import TYPE_CHECKING, ClassVar, TypeVar -from pydantic import BaseModel, Field, field_validator, model_validator +from pydantic import field_validator -from anta.custom_types import BgpUpdateError from anta.input_models.routing.bgp import BgpAddressFamily, BgpAfi, BgpNeighbor, BgpPeer, VxlanEndpoint from anta.models import AntaCommand, AntaTemplate, AntaTest from anta.tools import format_data, get_item, get_value @@ -21,9 +19,9 @@ import sys if sys.version_info >= (3, 11): - from typing import Self + pass else: - from typing_extensions import Self + pass # Using a TypeVar for the BgpPeer model since mypy thinks it's a ClassVar and not a valid type when used in field validators T = TypeVar("T", bound=BgpPeer) @@ -1006,7 +1004,7 @@ class VerifyBGPPeerDropStats(AntaTest): """ categories: ClassVar[list[str]] = ["bgp"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show bgp neighbors vrf all", revision=3)] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] class Input(AntaTest.Input): """Input model for the VerifyBGPPeerDropStats test.""" @@ -1048,16 +1046,26 @@ def test(self) -> None: class VerifyBGPPeerUpdateErrors(AntaTest): """Verifies BGP update error counters for the provided BGP IPv4 peer(s). - By default, all update error counters will be checked for any non-zero values. - An optional list of specific update error counters can be provided for granular testing. + This test performs the following checks for each specified peer: + + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Validates the BGP update error counters: + - If specific update error counters are provided, checks only those counters. + - If no update error counters are provided, checks all available counters. + - Confirms that all checked counters have a value of zero. Note: For "disabledAfiSafi" error counter field, checking that it's not "None" versus 0. Expected Results ---------------- - * Success: The test will pass if the BGP peer's update error counter(s) are zero/None. - * Failure: The test will fail if the BGP peer's update error counter(s) are non-zero/not None/Not Found or - peer is not configured. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - All specified update error counters (or all counters if none specified) are zero. + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - Any checked update error counters has a non-zero value. + - A specified update error counters does not exist. Examples -------- @@ -1068,79 +1076,68 @@ class VerifyBGPPeerUpdateErrors(AntaTest): bgp_peers: - peer_address: 172.30.11.1 vrf: default - update_error_filter: + update_errors: - inUpdErrWithdraw ``` """ categories: ClassVar[list[str]] = ["bgp"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show bgp neighbors {peer} vrf {vrf}", revision=3)] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] class Input(AntaTest.Input): """Input model for the VerifyBGPPeerUpdateErrors test.""" bgp_peers: list[BgpPeer] """List of BGP peers""" - - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" - update_errors: list[BgpUpdateError] | None = None - """Optional list of update error counters to be verified. If not provided, test will verifies all the update error counters.""" - - def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each BGP peer in the input list.""" - return [template.render(peer=str(bgp_peer.peer_address), vrf=bgp_peer.vrf) for bgp_peer in self.inputs.bgp_peers] + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeerUpdateErrors.""" - failures: dict[Any, Any] = {} + self.result.is_success() + + output = self.instance_commands[0].json_output - for command, input_entry in zip(self.instance_commands, self.inputs.bgp_peers): - peer = command.params.peer - vrf = command.params.vrf - update_error_counters = input_entry.update_errors + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + update_errors_input = peer.update_errors + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) - # Verify BGP peer. - if not (peer_list := get_value(command.json_output, f"vrfs.{vrf}.peerList")) or (peer_detail := get_item(peer_list, "peerAddress", peer)) is None: - failures[peer] = {vrf: "Not configured"} + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") continue # Getting the BGP peer's error counters output. - error_counters_output = peer_detail.get("peerInUpdateErrors", {}) + error_counters_output = peer_data.get("peerInUpdateErrors", {}) # In case update error counters not provided, It will check all the update error counters. - if not update_error_counters: - update_error_counters = error_counters_output + if not update_errors_input: + update_errors_input = error_counters_output - # verifying the error counters. - error_counters_not_ok = { - ("disabledAfiSafi" if error_counter == "disabledAfiSafi" else error_counter): value - for error_counter in update_error_counters - if (value := error_counters_output.get(error_counter, "Not Found")) != "None" and value != 0 - } - if error_counters_not_ok: - failures[peer] = {vrf: error_counters_not_ok} - - # Check if any failures - if not failures: - self.result.is_success() - else: - self.result.is_failure(f"The following BGP peers are not configured or have non-zero update error counters:\n{failures}") + # Verify BGP peer's update error counters + for error_counter in update_errors_input: + if (stat_value := error_counters_output.get(error_counter, "Not Found")) != 0 and stat_value != "None": + self.result.is_failure(f"{peer} - Non-zero update error counter; {error_counter}: {stat_value}") class VerifyBgpRouteMaps(AntaTest): """Verifies BGP inbound and outbound route-maps of BGP IPv4 peer(s). + This test performs the following checks for each specified peer: + + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Validates the correct BGP route maps are applied in the correct direction (inbound or outbound). + Expected Results ---------------- - * Success: The test will pass if the correct route maps are applied in the correct direction (inbound or outbound) for IPv4 BGP peers in the specified VRF. - * Failure: The test will fail if BGP peers are not configured or any neighbor has an incorrect or missing route map in either the inbound or outbound direction. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - All specified peers has correct BGP route maps are applied in the correct direction (inbound or outbound). + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - A incorrect or missing route map in either the inbound or outbound direction. Examples -------- @@ -1157,86 +1154,72 @@ class VerifyBgpRouteMaps(AntaTest): """ categories: ClassVar[list[str]] = ["bgp"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show bgp neighbors {peer} vrf {vrf}", revision=3)] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] class Input(AntaTest.Input): """Input model for the VerifyBgpRouteMaps test.""" bgp_peers: list[BgpPeer] """List of BGP peers""" + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" - inbound_route_map: str | None = None - """Inbound route map applied, defaults to None.""" - outbound_route_map: str | None = None - """Outbound route map applied, defaults to None.""" - - @model_validator(mode="after") - def validate_inputs(self) -> Self: - """Validate the inputs provided to the BgpPeer class. - - At least one of 'inbound' or 'outbound' route-map must be provided. - """ - if not (self.inbound_route_map or self.outbound_route_map): - msg = "At least one of 'inbound_route_map' or 'outbound_route_map' must be provided." - raise ValueError(msg) - return self + @field_validator("bgp_peers") + @classmethod + def validate_bgp_peers(cls, bgp_peers: list[T]) -> list[T]: + """Validate that 'peers' field is provided in each address family. - def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each BGP peer in the input list.""" - return [template.render(peer=str(bgp_peer.peer_address), vrf=bgp_peer.vrf) for bgp_peer in self.inputs.bgp_peers] + At least one of 'inbound' or 'outbound' route-map must be provided. + """ + for peer in bgp_peers: + if not (peer.inbound_route_map or peer.outbound_route_map): + msg = f"{peer}; At least one of 'inbound_route_map' or 'outbound_route_map' must be provided." + raise ValueError(msg) + return bgp_peers @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBgpRouteMaps.""" - failures: dict[Any, Any] = {} - - for command, input_entry in zip(self.instance_commands, self.inputs.bgp_peers): - peer = str(input_entry.peer_address) - vrf = input_entry.vrf - inbound_route_map = input_entry.inbound_route_map - outbound_route_map = input_entry.outbound_route_map - failure: dict[Any, Any] = {vrf: {}} - - # Verify BGP peer. - if not (peer_list := get_value(command.json_output, f"vrfs.{vrf}.peerList")) or (peer_detail := get_item(peer_list, "peerAddress", peer)) is None: - failures[peer] = {vrf: "Not configured"} + self.result.is_success() + + output = self.instance_commands[0].json_output + + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + inbound_route_map = peer.inbound_route_map + outbound_route_map = peer.outbound_route_map + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) + + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") continue # Verify Inbound route-map - if inbound_route_map and (inbound_map := peer_detail.get("routeMapInbound", "Not Configured")) != inbound_route_map: - failure[vrf].update({"Inbound route-map": inbound_map}) + if inbound_route_map and (inbound_map := peer_data.get("routeMapInbound", "Not Configured")) != inbound_route_map: + self.result.is_failure(f"{peer} - Inbound route-map mismatch; Expected: {inbound_route_map} Actual: {inbound_map}") # Verify Outbound route-map - if outbound_route_map and (outbound_map := peer_detail.get("routeMapOutbound", "Not Configured")) != outbound_route_map: - failure[vrf].update({"Outbound route-map": outbound_map}) + if outbound_route_map and (outbound_map := peer_data.get("routeMapOutbound", "Not Configured")) != outbound_route_map: + self.result.is_failure(f"{peer} - Outbound route-map mismatch; Expected: {outbound_route_map} Actual: {outbound_map}") - if failure[vrf]: - failures[peer] = failure - # Check if any failures - if not failures: - self.result.is_success() - else: - self.result.is_failure( - f"The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n{failures}" - ) +class VerifyBGPPeerRouteLimit(AntaTest): + """Verifies maximum routes and outbound route-maps of BGP IPv4 peer(s). + This test performs the following checks for each specified peer: -class VerifyBGPPeerRouteLimit(AntaTest): - """Verifies the maximum routes and optionally verifies the maximum routes warning limit for the provided BGP IPv4 peer(s). + 1. Confirms that the specified VRF is configured. + 2. Verifies that the peer exists in the BGP configuration. + 3. Confirms the Maximum routes and maximum routes warning limit, if provided match the expected value. Expected Results ---------------- - * Success: The test will pass if the BGP peer's maximum routes and, if provided, the maximum routes warning limit are equal to the given limits. - * Failure: The test will fail if the BGP peer's maximum routes do not match the given limit, or if the maximum routes warning limit is provided - and does not match the given limit, or if the peer is not configured. + * Success: If all of the following conditions are met: + - All specified peers are found in the BGP configuration. + - The maximum routese/maximum routes warning limit match the expected value for a peer. + * Failure: If any of the following occur: + - A specified peer is not found in the BGP configuration. + - The maximum routese/maximum routes warning limit do not match the expected value for a peer. Examples -------- @@ -1253,61 +1236,47 @@ class VerifyBGPPeerRouteLimit(AntaTest): """ categories: ClassVar[list[str]] = ["bgp"] - commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show bgp neighbors {peer} vrf {vrf}", revision=3)] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] class Input(AntaTest.Input): """Input model for the VerifyBGPPeerRouteLimit test.""" bgp_peers: list[BgpPeer] """List of BGP peers""" + BgpPeer: ClassVar[type[BgpPeer]] = BgpPeer - class BgpPeer(BaseModel): - """Model for a BGP peer.""" - - peer_address: IPv4Address - """IPv4 address of a BGP peer.""" - vrf: str = "default" - """Optional VRF for BGP peer. If not provided, it defaults to `default`.""" - maximum_routes: int = Field(ge=0, le=4294967294) - """The maximum allowable number of BGP routes, `0` means unlimited.""" - warning_limit: int = Field(default=0, ge=0, le=4294967294) - """Optional maximum routes warning limit. If not provided, it defaults to `0` meaning no warning limit.""" - - def render(self, template: AntaTemplate) -> list[AntaCommand]: - """Render the template for each BGP peer in the input list.""" - return [template.render(peer=str(bgp_peer.peer_address), vrf=bgp_peer.vrf) for bgp_peer in self.inputs.bgp_peers] + @field_validator("bgp_peers") + @classmethod + def validate_bgp_peers(cls, bgp_peers: list[T]) -> list[T]: + """Validate that 'peers' field is provided in each address family.""" + for peer in bgp_peers: + if peer.maximum_routes is None: + msg = f"{peer}; 'maximum_routes' field missing in the input" + raise ValueError(msg) + return bgp_peers @AntaTest.anta_test def test(self) -> None: """Main test function for VerifyBGPPeerRouteLimit.""" - failures: dict[Any, Any] = {} - - for command, input_entry in zip(self.instance_commands, self.inputs.bgp_peers): - peer = str(input_entry.peer_address) - vrf = input_entry.vrf - maximum_routes = input_entry.maximum_routes - warning_limit = input_entry.warning_limit - failure: dict[Any, Any] = {} - - # Verify BGP peer. - if not (peer_list := get_value(command.json_output, f"vrfs.{vrf}.peerList")) or (peer_detail := get_item(peer_list, "peerAddress", peer)) is None: - failures[peer] = {vrf: "Not configured"} + self.result.is_success() + + output = self.instance_commands[0].json_output + + for peer in self.inputs.bgp_peers: + peer_ip = str(peer.peer_address) + maximum_routes = peer.maximum_routes + warning_limit = peer.warning_limit + peer_list = get_value(output, f"vrfs.{peer.vrf}.peerList", default=[]) + + # Check if the peer is found + if (peer_data := get_item(peer_list, "peerAddress", peer_ip)) is None: + self.result.is_failure(f"{peer} - Not found") continue # Verify maximum routes configured. - if (actual_routes := peer_detail.get("maxTotalRoutes", "Not Found")) != maximum_routes: - failure["Maximum total routes"] = actual_routes + if (actual_routes := peer_data.get("maxTotalRoutes", "Not Found")) != maximum_routes: + self.result.is_failure(f"{peer} - Maximum routes mismatch; Expected: {maximum_routes} Actual: {actual_routes}") # Verify warning limit if given. - if warning_limit and (actual_warning_limit := peer_detail.get("totalRoutesWarnLimit", "Not Found")) != warning_limit: - failure["Warning limit"] = actual_warning_limit - - # Updated failures if any. - if failure: - failures[peer] = {vrf: failure} - - # Check if any failures - if not failures: - self.result.is_success() - else: - self.result.is_failure(f"The following BGP peer(s) are not configured or maximum routes and maximum routes warning limit is not correct:\n{failures}") + if warning_limit and (actual_warning_limit := peer_data.get("totalRoutesWarnLimit", "Not Found")) != warning_limit: + self.result.is_failure(f"{peer} - Maximum route warning limit mismatch; Expected: {warning_limit} Actual: {actual_warning_limit}") From d7e7486e315f51386f0f882c19f9e5624d1d89d8 Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Wed, 13 Nov 2024 06:16:48 -0500 Subject: [PATCH 10/21] Updated unit tests --- anta/tests/routing/bgp.py | 4 +- tests/units/anta_tests/routing/test_bgp.py | 955 ++++----------------- 2 files changed, 154 insertions(+), 805 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index ac3d2df1d..7f4475a63 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -539,7 +539,7 @@ def test(self) -> None: class VerifyBGPPeerASNCap(AntaTest): - """Verifies the four octet asn ASN capability of BGP peers. + """Verifies the four octet ASN capability of BGP peers. This test performs the following checks for each specified peer: @@ -640,7 +640,6 @@ class VerifyBGPPeerRouteRefreshCap(AntaTest): ``` """ - description = "Verifies the route refresh capabilities of a BGP peer." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] @@ -712,7 +711,6 @@ class VerifyBGPPeerMD5Auth(AntaTest): ``` """ - description = "Verifies the MD5 authentication and state of a BGP peer." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 20c9ae6b7..e9b1b82f3 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -1112,234 +1112,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peers are not found or routes are not exchanged properly:\n" - "{'bgp_peers': {'172.30.11.11': {'default': 'Not configured'}, '172.30.11.12': {'default': 'Not configured'}}}" - ], - }, - }, - { - "name": "failure-no-peer", - "test": VerifyBGPExchangedRoutes, - "eos_data": [ - {"vrfs": {}}, - { - "vrfs": { - "default": { - "bgpRouteEntries": { - "192.0.254.3/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ] - }, - "192.0.254.5/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ] - }, - }, - } - } - }, - {"vrfs": {}}, - { - "vrfs": { - "default": { - "bgpRouteEntries": { - "192.0.254.3/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ], - }, - "192.0.255.4/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ], - }, - }, - } - } - }, - ], - "inputs": { - "bgp_peers": [ - { - "peer_address": "172.30.11.11", - "vrf": "MGMT", - "advertised_routes": ["192.0.254.3/32"], - "received_routes": ["192.0.255.3/32"], - }, - { - "peer_address": "172.30.11.5", - "vrf": "default", - "advertised_routes": ["192.0.254.3/32", "192.0.254.5/32"], - "received_routes": ["192.0.254.3/32", "192.0.255.4/32"], - }, - ] - }, - "expected": { - "result": "failure", - "messages": ["Following BGP peers are not found or routes are not exchanged properly:\n{'bgp_peers': {'172.30.11.11': {'MGMT': 'Not configured'}}}"], - }, - }, - { - "name": "failure-missing-routes", - "test": VerifyBGPExchangedRoutes, - "eos_data": [ - { - "vrfs": { - "default": { - "bgpRouteEntries": { - "192.0.254.3/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ] - }, - "192.0.254.5/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ] - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "bgpRouteEntries": { - "192.0.254.3/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ] - }, - "192.0.254.5/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ] - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "bgpRouteEntries": { - "192.0.254.3/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ], - }, - "192.0.255.4/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ], - }, - }, - } - } - }, - { - "vrfs": { - "default": { - "bgpRouteEntries": { - "192.0.254.3/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ], - }, - "192.0.255.4/32": { - "bgpRoutePaths": [ - { - "routeType": { - "valid": True, - "active": True, - }, - } - ], - }, - }, - } - } - }, - ], - "inputs": { - "bgp_peers": [ - { - "peer_address": "172.30.11.1", - "vrf": "default", - "advertised_routes": ["192.0.254.3/32", "192.0.254.51/32"], - "received_routes": ["192.0.254.31/32", "192.0.255.4/32"], - }, - { - "peer_address": "172.30.11.5", - "vrf": "default", - "advertised_routes": ["192.0.254.31/32", "192.0.254.5/32"], - "received_routes": ["192.0.254.3/32", "192.0.255.41/32"], - }, - ] - }, - "expected": { - "result": "failure", - "messages": [ - "Following BGP peers are not found or routes are not exchanged properly:\n{'bgp_peers': " - "{'172.30.11.1': {'default': {'advertised_routes': {'192.0.254.51/32': 'Not found'}, 'received_routes': {'192.0.254.31/32': 'Not found'}}}, " - "'172.30.11.5': {'default': {'advertised_routes': {'192.0.254.31/32': 'Not found'}, 'received_routes': {'192.0.255.41/32': 'Not found'}}}}}" + "Peer: 172.30.11.11 VRF: default Advertised route: 192.0.254.3/32 - Not found", + "Peer: 172.30.11.11 VRF: default Received route: 192.0.255.3/32 - Not found", + "Peer: 172.30.11.12 VRF: default Advertised route: 192.0.254.31/32 - Not found", + "Peer: 172.30.11.12 VRF: default Received route: 192.0.255.31/32 - Not found", ], }, }, @@ -1479,11 +1255,14 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peers are not found or routes are not exchanged properly:\n{'bgp_peers': " - "{'172.30.11.1': {'default': {'advertised_routes': {'192.0.254.3/32': {'valid': True, 'active': False}, '192.0.254.51/32': 'Not found'}, " - "'received_routes': {'192.0.254.31/32': 'Not found', '192.0.255.4/32': {'valid': False, 'active': False}}}}, " - "'172.30.11.5': {'default': {'advertised_routes': {'192.0.254.31/32': 'Not found', '192.0.254.5/32': {'valid': True, 'active': False}}, " - "'received_routes': {'192.0.254.3/32': {'valid': False, 'active': True}, '192.0.255.41/32': 'Not found'}}}}}" + "Peer: 172.30.11.1 VRF: default Advertised route: 192.0.254.3/32 - Invalid/inactive; Valid: False, Active: True", + "Peer: 172.30.11.1 VRF: default Advertised route: 192.0.254.51/32 - Not found", + "Peer: 172.30.11.1 VRF: default Received route: 192.0.254.31/32 - Not found", + "Peer: 172.30.11.1 VRF: default Received route: 192.0.255.4/32 - Invalid/inactive; Valid: False, Active: False", + "Peer: 172.30.11.5 VRF: default Advertised route: 192.0.254.31/32 - Not found", + "Peer: 172.30.11.5 VRF: default Advertised route: 192.0.254.5/32 - Invalid/inactive; Valid: False, Active: True", + "Peer: 172.30.11.5 VRF: default Received route: 192.0.254.3/32 - Invalid/inactive; Valid: True, Active: False", + "Peer: 172.30.11.5 VRF: default Received route: 192.0.255.41/32 - Not found", ], }, }, @@ -1595,9 +1374,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": { "result": "failure", - "messages": [ - "Following BGP peer multiprotocol capabilities are not found or not ok:\n{'bgp_peers': {'172.30.11.1': {'MGMT': {'status': 'Not configured'}}}}" - ], + "messages": ["Peer: 172.30.11.1 VRF: MGMT - VRF not configured"], }, }, { @@ -1658,8 +1435,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peer multiprotocol capabilities are not found or not ok:\n" - "{'bgp_peers': {'172.30.11.10': {'default': {'status': 'Not configured'}}, '172.30.11.1': {'MGMT': {'status': 'Not configured'}}}}" + "Peer: 172.30.11.10 VRF: default - Not found", + "Peer: 172.30.11.1 VRF: MGMT - Not found", ], }, }, @@ -1699,9 +1476,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": { "result": "failure", - "messages": [ - "Following BGP peer multiprotocol capabilities are not found or not ok:\n{'bgp_peers': {'172.30.11.1': {'default': {'l2VpnEvpn': 'not found'}}}}" - ], + "messages": ["Peer: 172.30.11.1 VRF: default - l2VpnEvpn not found"], }, }, { @@ -1794,13 +1569,15 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peer multiprotocol capabilities are not found or not ok:\n" - "{'bgp_peers': {'172.30.11.1': {'default': {'ipv4Unicast': {'advertised': False, 'received': False, 'enabled': False}, " - "'ipv4MplsVpn': {'advertised': False, 'received': True, 'enabled': False}, 'l2VpnEvpn': 'not found'}}, " - "'172.30.11.10': {'MGMT': {'ipv4Unicast': 'not found', 'ipv4MplsVpn': {'advertised': False, 'received': False, 'enabled': True}, " - "'l2VpnEvpn': {'advertised': True, 'received': False, 'enabled': False}}}, " - "'172.30.11.11': {'MGMT': {'ipv4Unicast': {'advertised': False, 'received': False, 'enabled': False}, " - "'ipv4MplsVpn': {'advertised': False, 'received': False, 'enabled': False}, 'l2VpnEvpn': 'not found'}}}}" + "Peer: 172.30.11.1 VRF: default - ipv4Unicast not negotiated; Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.1 VRF: default - ipv4MplsVpn not negotiated; Advertised: False, Received: True, Enabled: False", + "Peer: 172.30.11.1 VRF: default - l2VpnEvpn not found", + "Peer: 172.30.11.10 VRF: MGMT - ipv4Unicast not found", + "Peer: 172.30.11.10 VRF: MGMT - ipv4MplsVpn not negotiated; Advertised: False, Received: False, Enabled: True", + "Peer: 172.30.11.10 VRF: MGMT - l2VpnEvpn not negotiated; Advertised: True, Received: False, Enabled: False", + "Peer: 172.30.11.11 VRF: MGMT - ipv4Unicast not negotiated; Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.11 VRF: MGMT - ipv4MplsVpn not negotiated; Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.11 VRF: MGMT - l2VpnEvpn not found", ], }, }, @@ -1943,10 +1720,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peer multiprotocol capabilities are not found or not ok:\n{'bgp_peers': {'172.30.11.1': " - "{'default': {'status': 'Expected only `ipv4Unicast` capabilities should be listed but found `ipv4Unicast, ipv4MplsLabels` instead.'}}," - " '172.30.11.10': {'MGMT': {'status': 'Expected only `ipv4MplsVpn, l2VpnEvpn` capabilities should be listed but found `ipv4Unicast, " - "ipv4MplsVpn` instead.'}}}}" + "Peer: 172.30.11.1 VRF: default - Mismatch; Expected: ipv4Unicast Actual: ipv4Unicast, ipv4MplsLabels", + "Peer: 172.30.11.10 VRF: MGMT - Mismatch; Expected: ipv4MplsVpn, l2VpnEvpn Actual: ipv4Unicast, ipv4MplsVpn", ], }, }, @@ -2001,63 +1776,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": {"result": "success"}, }, - { - "name": "failure-no-vrf", - "test": VerifyBGPPeerASNCap, - "eos_data": [ - { - "vrfs": { - "default": { - "peerList": [ - { - "peerAddress": "172.30.11.1", - "neighborCapabilities": { - "fourOctetAsnCap": { - "advertised": True, - "received": True, - "enabled": True, - }, - }, - } - ] - } - }, - "MGMT": { - "peerList": [ - { - "peerAddress": "172.30.11.10", - "neighborCapabilities": { - "fourOctetAsnCap": { - "advertised": True, - "received": True, - "enabled": True, - }, - }, - } - ] - }, - } - ], - "inputs": { - "bgp_peers": [ - { - "peer_address": "172.30.11.1", - "vrf": "MGMT", - }, - { - "peer_address": "172.30.11.10", - "vrf": "default", - }, - ] - }, - "expected": { - "result": "failure", - "messages": [ - "Following BGP peer four octet asn capabilities are not found or not ok:\n" - "{'bgp_peers': {'172.30.11.1': {'MGMT': {'status': 'Not configured'}}, '172.30.11.10': {'default': {'status': 'Not configured'}}}}" - ], - }, - }, { "name": "failure-no-peer", "test": VerifyBGPPeerASNCap, @@ -2093,9 +1811,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": { "result": "failure", - "messages": [ - "Following BGP peer four octet asn capabilities are not found or not ok:\n{'bgp_peers': {'172.30.11.10': {'default': {'status': 'Not configured'}}}}" - ], + "messages": ["Peer: 172.30.11.10 VRF: default - Not found"], }, }, { @@ -2148,8 +1864,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peer four octet asn capabilities are not found or not ok:\n" - "{'bgp_peers': {'172.30.11.1': {'default': {'fourOctetAsnCap': 'not found'}}, '172.30.11.10': {'MGMT': {'fourOctetAsnCap': 'not found'}}}}" + "Peer: 172.30.11.1 VRF: default - 4-octet ASN capability not found", + "Peer: 172.30.11.10 VRF: MGMT - 4-octet ASN capability not found", ], }, }, @@ -2199,9 +1915,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peer four octet asn capabilities are not found or not ok:\n" - "{'bgp_peers': {'172.30.11.1': {'default': {'fourOctetAsnCap': {'advertised': False, 'received': False, 'enabled': False}}}, " - "'172.30.11.10': {'MGMT': {'fourOctetAsnCap': {'advertised': True, 'received': False, 'enabled': True}}}}}" + "Peer: 172.30.11.1 VRF: default - 4-octet ASN capability not negotiated; Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.10 VRF: MGMT - 4-octet ASN capability not negotiated; Advertised: True, Received: False, Enabled: True", ], }, }, @@ -2256,25 +1971,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": {"result": "success"}, }, - { - "name": "failure-no-vrf", - "test": VerifyBGPPeerRouteRefreshCap, - "eos_data": [{"vrfs": {}}], - "inputs": { - "bgp_peers": [ - { - "peer_address": "172.30.11.1", - "vrf": "MGMT", - } - ] - }, - "expected": { - "result": "failure", - "messages": [ - "Following BGP peer route refresh capabilities are not found or not ok:\n{'bgp_peers': {'172.30.11.1': {'MGMT': {'status': 'Not configured'}}}}" - ], - }, - }, { "name": "failure-no-peer", "test": VerifyBGPPeerRouteRefreshCap, @@ -2331,8 +2027,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peer route refresh capabilities are not found or not ok:\n" - "{'bgp_peers': {'172.30.11.12': {'default': {'status': 'Not configured'}}, '172.30.11.1': {'CS': {'status': 'Not configured'}}}}" + "Peer: 172.30.11.12 VRF: default - Not found", + "Peer: 172.30.11.1 VRF: CS - Not found", ], }, }, @@ -2386,8 +2082,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peer route refresh capabilities are not found or not ok:\n" - "{'bgp_peers': {'172.30.11.1': {'default': {'routeRefreshCap': 'not found'}}, '172.30.11.11': {'CS': {'routeRefreshCap': 'not found'}}}}" + "Peer: 172.30.11.1 VRF: default - Route refresh capability not found", + "Peer: 172.30.11.11 VRF: CS - Route refresh capability not found", ], }, }, @@ -2437,8 +2133,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peer route refresh capabilities are not found or not ok:\n" - "{'bgp_peers': {'172.30.11.1': {'default': {'routeRefreshCap': {'advertised': False, 'received': False, 'enabled': False}}}}}" + "Peer: 172.30.11.1 VRF: default - Route refresh capability not negotiated; Advertised: False, Received: False, Enabled: False", ], }, }, @@ -2483,40 +2178,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": {"result": "success"}, }, - { - "name": "failure-no-vrf", - "test": VerifyBGPPeerMD5Auth, - "eos_data": [ - { - "vrfs": { - "default": { - "peerList": [ - { - "peerAddress": "172.30.11.10", - "state": "Established", - "md5AuthEnabled": True, - } - ] - }, - } - } - ], - "inputs": { - "bgp_peers": [ - { - "peer_address": "172.30.11.1", - "vrf": "MGMT", - } - ] - }, - "expected": { - "result": "failure", - "messages": [ - "Following BGP peers are not configured, not established or MD5 authentication is not enabled:\n" - "{'bgp_peers': {'172.30.11.1': {'MGMT': {'status': 'Not configured'}}}}" - ], - }, - }, { "name": "failure-no-peer", "test": VerifyBGPPeerMD5Auth, @@ -2551,16 +2212,16 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "vrf": "default", }, { - "peer_address": "172.30.11.11", - "vrf": "default", + "peer_address": "172.30.11.12", + "vrf": "CS", }, ] }, "expected": { "result": "failure", "messages": [ - "Following BGP peers are not configured, not established or MD5 authentication is not enabled:\n" - "{'bgp_peers': {'172.30.11.10': {'default': {'status': 'Not configured'}}, '172.30.11.11': {'default': {'status': 'Not configured'}}}}" + "Peer: 172.30.11.10 VRF: default - Not found", + "Peer: 172.30.11.12 VRF: CS - Not found", ], }, }, @@ -2584,7 +2245,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "peerAddress": "172.30.11.10", "state": "Idle", - "md5AuthEnabled": False, + "md5AuthEnabled": True, } ] }, @@ -2606,9 +2267,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peers are not configured, not established or MD5 authentication is not enabled:\n" - "{'bgp_peers': {'172.30.11.1': {'default': {'state': 'Idle', 'md5_auth_enabled': True}}, " - "'172.30.11.10': {'MGMT': {'state': 'Idle', 'md5_auth_enabled': False}}}}" + "Peer: 172.30.11.1 VRF: default - Session state is not established; State: Idle", + "Peer: 172.30.11.10 VRF: MGMT - Session state is not established; State: Idle", ], }, }, @@ -2658,9 +2318,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peers are not configured, not established or MD5 authentication is not enabled:\n" - "{'bgp_peers': {'172.30.11.1': {'default': {'state': 'Established', 'md5_auth_enabled': None}}, " - "'172.30.11.11': {'MGMT': {'state': 'Established', 'md5_auth_enabled': False}}}}" + "Peer: 172.30.11.1 VRF: default - Session does not have MD5 authentication enabled", + "Peer: 172.30.11.11 VRF: MGMT - Session does not have MD5 authentication enabled", ], }, }, @@ -2821,140 +2480,18 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "routeType": { "active": True, "valid": True, - "ecmp": True, - "ecmpContributor": True, - "ecmpHead": True, - }, - }, - { - "routeType": { - "active": False, - "valid": True, - "ecmp": True, - "ecmpContributor": True, - "ecmpHead": False, - }, - }, - ] - }, - "RD: 10.1.0.6:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102": { - "evpnRoutePaths": [ - { - "routeType": { - "active": True, - "valid": True, - }, - }, - ] - }, - }, - }, - ], - "inputs": {"vxlan_endpoints": [{"address": "192.168.20.102", "vni": 10020}]}, - "expected": {"result": "success"}, - }, - { - "name": "success-multiple-routes-multiple-paths-mac", - "test": VerifyEVPNType2Route, - "eos_data": [ - { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": 65120, - "evpnRoutes": { - "RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2": { - "evpnRoutePaths": [ - { - "routeType": { - "active": True, - "valid": True, - "ecmp": True, - "ecmpContributor": True, - "ecmpHead": True, - }, - }, - { - "routeType": { - "active": False, - "valid": True, - "ecmp": True, - "ecmpContributor": True, - "ecmpHead": False, - }, - }, - ] - }, - "RD: 10.1.0.6:500 mac-ip 10020 aac1.ab4e.bec2": { - "evpnRoutePaths": [ - { - "routeType": { - "active": True, - "valid": True, - }, - }, - ] - }, - }, - }, - ], - "inputs": {"vxlan_endpoints": [{"address": "aac1.ab4e.bec2", "vni": 10020}]}, - "expected": {"result": "success"}, - }, - { - "name": "failure-no-routes", - "test": VerifyEVPNType2Route, - "eos_data": [{"vrf": "default", "routerId": "10.1.0.3", "asn": 65120, "evpnRoutes": {}}], - "inputs": {"vxlan_endpoints": [{"address": "192.168.20.102", "vni": 10020}]}, - "expected": { - "result": "failure", - "messages": ["The following VXLAN endpoint do not have any EVPN Type-2 route: [('192.168.20.102', 10020)]"], - }, - }, - { - "name": "failure-path-not-active", - "test": VerifyEVPNType2Route, - "eos_data": [ - { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": 65120, - "evpnRoutes": { - "RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102": { - "evpnRoutePaths": [ - { - "routeType": { - "active": False, - "valid": True, - }, - }, - ] - }, - }, - }, - ], - "inputs": {"vxlan_endpoints": [{"address": "192.168.20.102", "vni": 10020}]}, - "expected": { - "result": "failure", - "messages": [ - "The following EVPN Type-2 routes do not have at least one valid and active path: ['RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102']" - ], - }, - }, - { - "name": "failure-multiple-routes-not-active", - "test": VerifyEVPNType2Route, - "eos_data": [ - { - "vrf": "default", - "routerId": "10.1.0.3", - "asn": 65120, - "evpnRoutes": { - "RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102": { - "evpnRoutePaths": [ + "ecmp": True, + "ecmpContributor": True, + "ecmpHead": True, + }, + }, { "routeType": { "active": False, "valid": True, + "ecmp": True, + "ecmpContributor": True, + "ecmpHead": False, }, }, ] @@ -2963,8 +2500,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "evpnRoutePaths": [ { "routeType": { - "active": False, - "valid": False, + "active": True, + "valid": True, }, }, ] @@ -2973,17 +2510,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, ], "inputs": {"vxlan_endpoints": [{"address": "192.168.20.102", "vni": 10020}]}, - "expected": { - "result": "failure", - "messages": [ - "The following EVPN Type-2 routes do not have at least one valid and active path: " - "['RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102', " - "'RD: 10.1.0.6:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102']" - ], - }, + "expected": {"result": "success"}, }, { - "name": "failure-multiple-routes-multiple-paths-not-active", + "name": "success-multiple-routes-multiple-paths-mac", "test": VerifyEVPNType2Route, "eos_data": [ { @@ -2991,34 +2521,34 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "routerId": "10.1.0.3", "asn": 65120, "evpnRoutes": { - "RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102": { + "RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2": { "evpnRoutePaths": [ { "routeType": { "active": True, "valid": True, + "ecmp": True, + "ecmpContributor": True, + "ecmpHead": True, }, }, { "routeType": { "active": False, "valid": True, + "ecmp": True, + "ecmpContributor": True, + "ecmpHead": False, }, }, ] }, - "RD: 10.1.0.6:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102": { + "RD: 10.1.0.6:500 mac-ip 10020 aac1.ab4e.bec2": { "evpnRoutePaths": [ { "routeType": { - "active": False, - "valid": False, - }, - }, - { - "routeType": { - "active": False, - "valid": False, + "active": True, + "valid": True, }, }, ] @@ -3026,16 +2556,21 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, }, ], + "inputs": {"vxlan_endpoints": [{"address": "aac1.ab4e.bec2", "vni": 10020}]}, + "expected": {"result": "success"}, + }, + { + "name": "failure-no-routes", + "test": VerifyEVPNType2Route, + "eos_data": [{"vrf": "default", "routerId": "10.1.0.3", "asn": 65120, "evpnRoutes": {}}], "inputs": {"vxlan_endpoints": [{"address": "192.168.20.102", "vni": 10020}]}, "expected": { "result": "failure", - "messages": [ - "The following EVPN Type-2 routes do not have at least one valid and active path: ['RD: 10.1.0.6:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102']" - ], + "messages": ["Address: 192.168.20.102 VNI: 10020 - No EVPN Type-2 route"], }, }, { - "name": "failure-multiple-endpoints", + "name": "failure-path-not-active", "test": VerifyEVPNType2Route, "eos_data": [ { @@ -3048,19 +2583,30 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "routeType": { "active": False, - "valid": False, + "valid": True, }, }, ] }, }, }, + ], + "inputs": {"vxlan_endpoints": [{"address": "192.168.20.102", "vni": 10020}]}, + "expected": { + "result": "failure", + "messages": ["Address: 192.168.20.102 VNI: 10020 - No valid and active path"], + }, + }, + { + "name": "failure-multiple-endpoints", + "test": VerifyEVPNType2Route, + "eos_data": [ { "vrf": "default", "routerId": "10.1.0.3", "asn": 65120, "evpnRoutes": { - "RD: 10.1.0.5:500 mac-ip 10010 aac1.ab5d.b41e": { + "RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102": { "evpnRoutePaths": [ { "routeType": { @@ -3072,33 +2618,12 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, }, }, - ], - "inputs": { - "vxlan_endpoints": [ - {"address": "192.168.20.102", "vni": 10020}, - {"address": "aac1.ab5d.b41e", "vni": 10010}, - ] - }, - "expected": { - "result": "failure", - "messages": [ - "The following EVPN Type-2 routes do not have at least one valid and active path: " - "['RD: 10.1.0.5:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102', " - "'RD: 10.1.0.5:500 mac-ip 10010 aac1.ab5d.b41e']" - ], - }, - }, - { - "name": "failure-multiple-endpoints-one-no-routes", - "test": VerifyEVPNType2Route, - "eos_data": [ - {"vrf": "default", "routerId": "10.1.0.3", "asn": 65120, "evpnRoutes": {}}, { "vrf": "default", "routerId": "10.1.0.3", "asn": 65120, "evpnRoutes": { - "RD: 10.1.0.5:500 mac-ip 10010 aac1.ab5d.b41e 192.168.10.101": { + "RD: 10.1.0.5:500 mac-ip 10010 aac1.ab5d.b41e": { "evpnRoutePaths": [ { "routeType": { @@ -3113,35 +2638,13 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], "inputs": { "vxlan_endpoints": [ - {"address": "aac1.ab4e.bec2", "vni": 10020}, - {"address": "192.168.10.101", "vni": 10010}, - ] - }, - "expected": { - "result": "failure", - "messages": [ - "The following VXLAN endpoint do not have any EVPN Type-2 route: [('aa:c1:ab:4e:be:c2', 10020)]", - "The following EVPN Type-2 routes do not have at least one valid and active path: " - "['RD: 10.1.0.5:500 mac-ip 10010 aac1.ab5d.b41e 192.168.10.101']", - ], - }, - }, - { - "name": "failure-multiple-endpoints-no-routes", - "test": VerifyEVPNType2Route, - "eos_data": [ - {"vrf": "default", "routerId": "10.1.0.3", "asn": 65120, "evpnRoutes": {}}, - {"vrf": "default", "routerId": "10.1.0.3", "asn": 65120, "evpnRoutes": {}}, - ], - "inputs": { - "vxlan_endpoints": [ - {"address": "aac1.ab4e.bec2", "vni": 10020}, - {"address": "192.168.10.101", "vni": 10010}, + {"address": "192.168.20.102", "vni": 10020}, + {"address": "aac1.ab5d.b41e", "vni": 10010}, ] }, "expected": { "result": "failure", - "messages": ["The following VXLAN endpoint do not have any EVPN Type-2 route: [('aa:c1:ab:4e:be:c2', 10020), ('192.168.10.101', 10010)]"], + "messages": ["Address: 192.168.20.102 VNI: 10020 - No valid and active path", "Address: aa:c1:ab:5d:b4:1e VNI: 10010 - No valid and active path"], }, }, { @@ -3190,43 +2693,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, "expected": {"result": "success"}, }, - { - "name": "failure-no-vrf", - "test": VerifyBGPAdvCommunities, - "eos_data": [ - { - "vrfs": { - "default": { - "peerList": [ - { - "peerAddress": "172.30.11.1", - "advertisedCommunities": { - "standard": True, - "extended": True, - "large": True, - }, - } - ] - }, - } - } - ], - "inputs": { - "bgp_peers": [ - { - "peer_address": "172.30.11.17", - "vrf": "MGMT", - } - ] - }, - "expected": { - "result": "failure", - "messages": [ - "Following BGP peers are not configured or advertised communities are not standard, extended, and large:\n" - "{'bgp_peers': {'172.30.11.17': {'MGMT': {'status': 'Not configured'}}}}" - ], - }, - }, { "name": "failure-no-peer", "test": VerifyBGPAdvCommunities, @@ -3275,8 +2741,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peers are not configured or advertised communities are not standard, extended, and large:\n" - "{'bgp_peers': {'172.30.11.10': {'default': {'status': 'Not configured'}}, '172.30.11.12': {'MGMT': {'status': 'Not configured'}}}}" + "Peer: 172.30.11.10 VRF: default - Not found", + "Peer: 172.30.11.12 VRF: MGMT - Not found", ], }, }, @@ -3328,9 +2794,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peers are not configured or advertised communities are not standard, extended, and large:\n" - "{'bgp_peers': {'172.30.11.1': {'default': {'advertised_communities': {'standard': False, 'extended': False, 'large': False}}}, " - "'172.30.11.10': {'CS': {'advertised_communities': {'standard': True, 'extended': True, 'large': False}}}}}" + "Peer: 172.30.11.1 VRF: default - Standard: False, Extended: False, Large: False", + "Peer: 172.30.11.10 VRF: CS - Standard: True, Extended: True, Large: False", ], }, }, @@ -3385,15 +2850,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "eos_data": [ { "vrfs": { - "default": { - "peerList": [ - { - "peerAddress": "172.30.11.1", - "holdTime": 180, - "keepaliveTime": 60, - } - ] - }, + "default": {"peerList": []}, "MGMT": {"peerList": []}, } } @@ -3408,7 +2865,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo }, { "peer_address": "172.30.11.11", - "vrf": "MGMT", + "vrf": "default", "hold_time": 180, "keep_alive_time": 60, }, @@ -3417,8 +2874,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peers are not configured or hold and keep-alive timers are not correct:\n" - "{'172.30.11.1': {'MGMT': 'Not configured'}, '172.30.11.11': {'MGMT': 'Not configured'}}" + "Peer: 172.30.11.1 VRF: MGMT - Not found", + "Peer: 172.30.11.11 VRF: default - Not found", ], }, }, @@ -3468,9 +2925,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Following BGP peers are not configured or hold and keep-alive timers are not correct:\n" - "{'172.30.11.1': {'default': {'hold_time': 160, 'keep_alive_time': 60}}, " - "'172.30.11.11': {'MGMT': {'hold_time': 120, 'keep_alive_time': 40}}}" + "Peer: 172.30.11.1 VRF: default - Hold time mismatch; Expected: 180 Actual: 160", + "Peer: 172.30.11.11 VRF: MGMT - Hold time mismatch; Expected: 180 Actual: 120", + "Peer: 172.30.11.11 VRF: MGMT - Keepalive time mismatch; Expected: 60 Actual: 40", ], }, }, @@ -3498,10 +2955,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -3538,10 +2991,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "name": "failure-not-found", "test": VerifyBGPPeerDropStats, - "eos_data": [ - {"vrfs": {}}, - {"vrfs": {}}, - ], + "eos_data": [{"vrfs": {}}], "inputs": { "bgp_peers": [ { @@ -3555,8 +3005,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or have non-zero NLRI drop statistics counters:\n" - "{'10.100.0.8': {'default': 'Not configured'}, '10.100.0.9': {'MGMT': 'Not configured'}}" + "Peer: 10.100.0.8 VRF: default - Not found", + "Peer: 10.100.0.9 VRF: MGMT - Not found", ], }, }, @@ -3584,10 +3034,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -3622,9 +3068,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or have non-zero NLRI drop statistics counters:\n" - "{'10.100.0.8': {'default': {'prefixDroppedMartianV4': 1, 'prefixDroppedMaxRouteLimitViolatedV4': 1}}, " - "'10.100.0.9': {'MGMT': {'inDropOrigId': 1, 'inDropNhLocal': 1}}}" + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; prefixDroppedMartianV4: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; prefixDroppedMaxRouteLimitViolatedV4: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropOrigId: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropNhLocal: 1", ], }, }, @@ -3652,10 +3099,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -3709,10 +3152,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -3743,49 +3182,14 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or have non-zero NLRI drop statistics counters:\n" - "{'10.100.0.8': {'default': {'inDropAsloop': 3, 'inDropOrigId': 1, 'inDropNhLocal': 1, " - "'prefixDroppedMartianV4': 1, 'prefixDroppedMaxRouteLimitViolatedV4': 1}}, " - "'10.100.0.9': {'MGMT': {'inDropAsloop': 2, 'inDropOrigId': 1, 'inDropNhLocal': 1}}}" - ], - }, - }, - { - "name": "failure-drop-stat-not-found", - "test": VerifyBGPPeerDropStats, - "eos_data": [ - { - "vrfs": { - "default": { - "peerList": [ - { - "peerAddress": "10.100.0.8", - "dropStats": { - "inDropAsloop": 3, - "inDropClusterIdLoop": 0, - "inDropMalformedMpbgp": 0, - "inDropOrigId": 1, - "inDropNhLocal": 1, - "inDropNhAfV6": 0, - "prefixDroppedMaxRouteLimitViolatedV4": 1, - "prefixDroppedMartianV6": 0, - }, - } - ] - }, - }, - }, - ], - "inputs": { - "bgp_peers": [ - {"peer_address": "10.100.0.8", "vrf": "default", "drop_stats": ["inDropAsloop", "inDropOrigId", "inDropNhLocal", "prefixDroppedMartianV4"]} - ] - }, - "expected": { - "result": "failure", - "messages": [ - "The following BGP peers are not configured or have non-zero NLRI drop statistics counters:\n" - "{'10.100.0.8': {'default': {'inDropAsloop': 3, 'inDropOrigId': 1, 'inDropNhLocal': 1, 'prefixDroppedMartianV4': 'Not Found'}}}" + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; inDropAsloop: 3", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; inDropOrigId: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; inDropNhLocal: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; prefixDroppedMartianV4: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; prefixDroppedMaxRouteLimitViolatedV4: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropAsloop: 2", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropOrigId: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropNhLocal: 1", ], }, }, @@ -3809,10 +3213,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -3843,7 +3243,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "test": VerifyBGPPeerUpdateErrors, "eos_data": [ {"vrfs": {}}, - {"vrfs": {}}, ], "inputs": { "bgp_peers": [ @@ -3854,8 +3253,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or have non-zero update error counters:\n" - "{'10.100.0.8': {'default': 'Not configured'}, '10.100.0.9': {'MGMT': 'Not configured'}}" + "Peer: 10.100.0.8 VRF: default - Not found", + "Peer: 10.100.0.9 VRF: MGMT - Not found", ], }, }, @@ -3879,10 +3278,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -3909,9 +3304,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or have non-zero update error counters:\n" - "{'10.100.0.8': {'default': {'disabledAfiSafi': 'ipv4Unicast'}}, " - "'10.100.0.9': {'MGMT': {'inUpdErrWithdraw': 1}}}" + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; disabledAfiSafi: ipv4Unicast", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrWithdraw: 1", ], }, }, @@ -3935,10 +3329,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -3984,10 +3374,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4018,9 +3404,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or have non-zero update error counters:\n" - "{'10.100.0.8': {'default': {'inUpdErrWithdraw': 1, 'disabledAfiSafi': 'ipv4Unicast'}}, " - "'10.100.0.9': {'MGMT': {'inUpdErrWithdraw': 1, 'inUpdErrDisableAfiSafi': 1}}}" + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; inUpdErrWithdraw: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; disabledAfiSafi: ipv4Unicast", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrWithdraw: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrDisableAfiSafi: 1", ], }, }, @@ -4043,10 +3430,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4076,9 +3459,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or have non-zero update error counters:\n" - "{'10.100.0.8': {'default': {'inUpdErrWithdraw': 'Not Found', 'disabledAfiSafi': 'ipv4Unicast'}}, " - "'10.100.0.9': {'MGMT': {'inUpdErrWithdraw': 1, 'inUpdErrDisableAfiSafi': 'Not Found'}}}" + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; inUpdErrWithdraw: Not Found", + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; disabledAfiSafi: ipv4Unicast", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrWithdraw: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrDisableAfiSafi: Not Found", ], }, }, @@ -4097,10 +3481,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4136,10 +3516,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4161,9 +3537,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n" - "{'10.100.0.8': {'default': {'Inbound route-map': 'RM-MLAG-PEER', 'Outbound route-map': 'RM-MLAG-PEER'}}, " - "'10.100.0.10': {'MGMT': {'Inbound route-map': 'RM-MLAG-PEER', 'Outbound route-map': 'RM-MLAG-PEER'}}}" + "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: RM-MLAG-PEER", + "Peer: 10.100.0.8 VRF: default - Outbound route-map mismatch; Expected: RM-MLAG-PEER-OUT Actual: RM-MLAG-PEER", + "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: RM-MLAG-PEER", + "Peer: 10.100.0.10 VRF: MGMT - Outbound route-map mismatch; Expected: RM-MLAG-PEER-OUT Actual: RM-MLAG-PEER", ], }, }, @@ -4182,10 +3559,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4207,8 +3580,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n" - "{'10.100.0.8': {'default': {'Inbound route-map': 'RM-MLAG-PEER'}}, '10.100.0.10': {'MGMT': {'Inbound route-map': 'RM-MLAG-PEER'}}}" + "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: RM-MLAG-PEER", + "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: RM-MLAG-PEER", ], }, }, @@ -4225,10 +3598,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4248,9 +3617,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n" - "{'10.100.0.8': {'default': {'Inbound route-map': 'Not Configured', 'Outbound route-map': 'Not Configured'}}, " - "'10.100.0.10': {'MGMT': {'Inbound route-map': 'Not Configured', 'Outbound route-map': 'Not Configured'}}}" + "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: Not Configured", + "Peer: 10.100.0.8 VRF: default - Outbound route-map mismatch; Expected: RM-MLAG-PEER-OUT Actual: Not Configured", + "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: Not Configured", + "Peer: 10.100.0.10 VRF: MGMT - Outbound route-map mismatch; Expected: RM-MLAG-PEER-OUT Actual: Not Configured", ], }, }, @@ -4261,10 +3631,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "vrfs": { "default": {"peerList": []}, - }, - }, - { - "vrfs": { "MGMT": {"peerList": []}, }, }, @@ -4278,8 +3644,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n" - "{'10.100.0.8': {'default': 'Not configured'}, '10.100.0.10': {'MGMT': 'Not configured'}}" + "Peer: 10.100.0.8 VRF: default - Not found", + "Peer: 10.100.0.10 VRF: MGMT - Not found", ], }, }, @@ -4298,10 +3664,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4329,10 +3691,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo { "vrfs": { "default": {}, - }, - }, - { - "vrfs": { "MGMT": {}, }, }, @@ -4346,8 +3704,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peer(s) are not configured or maximum routes and maximum routes warning limit is not correct:\n" - "{'10.100.0.8': {'default': 'Not configured'}, '10.100.0.9': {'MGMT': 'Not configured'}}" + "Peer: 10.100.0.8 VRF: default - Not found", + "Peer: 10.100.0.9 VRF: MGMT - Not found", ], }, }, @@ -4366,10 +3724,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4391,9 +3745,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peer(s) are not configured or maximum routes and maximum routes warning limit is not correct:\n" - "{'10.100.0.8': {'default': {'Maximum total routes': 13000, 'Warning limit': 11000}}, " - "'10.100.0.9': {'MGMT': {'Maximum total routes': 11000, 'Warning limit': 10000}}}" + "Peer: 10.100.0.8 VRF: default - Maximum routes mismatch; Expected: 12000 Actual: 13000", + "Peer: 10.100.0.8 VRF: default - Maximum route warning limit mismatch; Expected: 10000 Actual: 11000", + "Peer: 10.100.0.9 VRF: MGMT - Maximum routes mismatch; Expected: 10000 Actual: 11000", + "Peer: 10.100.0.9 VRF: MGMT - Maximum route warning limit mismatch; Expected: 9000 Actual: 10000", ], }, }, @@ -4411,10 +3766,6 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo } ] }, - }, - }, - { - "vrfs": { "MGMT": { "peerList": [ { @@ -4434,9 +3785,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "The following BGP peer(s) are not configured or maximum routes and maximum routes warning limit is not correct:\n" - "{'10.100.0.8': {'default': {'Warning limit': 'Not Found'}}, " - "'10.100.0.9': {'MGMT': {'Maximum total routes': 'Not Found', 'Warning limit': 'Not Found'}}}" + "Peer: 10.100.0.8 VRF: default - Maximum route warning limit mismatch; Expected: 10000 Actual: Not Found", + "Peer: 10.100.0.9 VRF: MGMT - Maximum routes mismatch; Expected: 10000 Actual: Not Found", + "Peer: 10.100.0.9 VRF: MGMT - Maximum route warning limit mismatch; Expected: 9000 Actual: Not Found", ], }, }, From 1dcdf57b40b7816069d6a3991a5fbe9d7157b65b Mon Sep 17 00:00:00 2001 From: VitthalMagadum Date: Wed, 13 Nov 2024 07:25:43 -0500 Subject: [PATCH 11/21] Fix the CI failure --- anta/tests/routing/bgp.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 7f4475a63..0140e9157 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -7,7 +7,7 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations -from typing import TYPE_CHECKING, ClassVar, TypeVar +from typing import ClassVar, TypeVar from pydantic import field_validator @@ -15,14 +15,6 @@ from anta.models import AntaCommand, AntaTemplate, AntaTest from anta.tools import format_data, get_item, get_value -if TYPE_CHECKING: - import sys - - if sys.version_info >= (3, 11): - pass - else: - pass - # Using a TypeVar for the BgpPeer model since mypy thinks it's a ClassVar and not a valid type when used in field validators T = TypeVar("T", bound=BgpPeer) @@ -531,10 +523,9 @@ def test(self) -> None: # Check if the capability is found if (capability_status := get_value(act_mp_caps, capability)) is None: self.result.is_failure(f"{peer} - {capability} not found") - continue # Check if the capability is advertised, received, and enabled - if not _check_bgp_neighbor_capability(capability_status): + elif not _check_bgp_neighbor_capability(capability_status): self.result.is_failure(f"{peer} - {capability} not negotiated; {format_data(capability_status)}") From f1387c8c3cd2dc37719bdf5367b121c1026d7114 Mon Sep 17 00:00:00 2001 From: vitthalmagadum Date: Fri, 15 Nov 2024 04:44:42 -0500 Subject: [PATCH 12/21] Fix the unit test afte merge conflicts --- tests/units/anta_tests/routing/test_bgp.py | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index e9b1b82f3..0f1ae6ae7 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -301,6 +301,76 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo ], }, }, + { + "name": "failure-wrong-count-peer-state-check-true", + "test": VerifyBGPPeerCount, + "eos_data": [ + { + "vrfs": { + "default": { + "vrf": "default", + "routerId": "10.1.0.3", + "asn": "65120", + "peers": { + "10.1.0.1": { + "peerState": "Established", + "peerAsn": "65100", + "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0}, + "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42}, + }, + "10.1.0.2": { + "peerState": "Established", + "peerAsn": "65100", + "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": { + "vrf": "DEV", + "routerId": "10.1.0.3", + "asn": "65120", + "peers": { + "10.1.254.1": { + "peerState": "Established", + "peerAsn": "65120", + "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4}, + } + }, + }, + } + }, + ], + "inputs": { + "address_families": [ + {"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": { + "result": "failure", + "messages": [ + "AFI: evpn - Expected: 3, Actual: 2", + "AFI: ipv4 SAFI: unicast VRF: DEV - Expected: 2, Actual: 1", + ], + }, + }, { "name": "failure-wrong-count", "test": VerifyBGPPeerCount, From ecf9fa1ddf845e2c1dc0289c5996c3fb73c0479e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:49:54 +0000 Subject: [PATCH 13/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- anta/tests/routing/bgp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 0140e9157..484fcdc79 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -325,6 +325,7 @@ def test(self) -> None: 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}") + class VerifyBGPExchangedRoutes(AntaTest): """Verifies the advertised and received routes of BGP peers. From 4ddb29096406d33ed720cc88c70d291c5ffe1c98 Mon Sep 17 00:00:00 2001 From: vitthalmagadum Date: Tue, 19 Nov 2024 02:39:09 -0500 Subject: [PATCH 14/21] Addressed review comments: updated failure msgs --- anta/tests/routing/bgp.py | 2 +- tests/units/anta_tests/routing/test_bgp.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 484fcdc79..fdbded848 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -425,7 +425,7 @@ def test(self) -> None: is_active = route_paths["active"] is_valid = route_paths["valid"] if not is_active or not is_valid: - self.result.is_failure(f"{peer} {route_type} route: {route} - Invalid/inactive; Valid: {is_valid}, Active: {is_active}") + self.result.is_failure(f"{peer} {route_type} route: {route} - Valid: {is_valid}, Active: {is_active}") class VerifyBGPPeerMPCaps(AntaTest): diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 0f1ae6ae7..2263f588d 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -1325,13 +1325,13 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 172.30.11.1 VRF: default Advertised route: 192.0.254.3/32 - Invalid/inactive; Valid: False, Active: True", + "Peer: 172.30.11.1 VRF: default Advertised route: 192.0.254.3/32 - Valid: False, Active: True", "Peer: 172.30.11.1 VRF: default Advertised route: 192.0.254.51/32 - Not found", "Peer: 172.30.11.1 VRF: default Received route: 192.0.254.31/32 - Not found", - "Peer: 172.30.11.1 VRF: default Received route: 192.0.255.4/32 - Invalid/inactive; Valid: False, Active: False", + "Peer: 172.30.11.1 VRF: default Received route: 192.0.255.4/32 - Valid: False, Active: False", "Peer: 172.30.11.5 VRF: default Advertised route: 192.0.254.31/32 - Not found", - "Peer: 172.30.11.5 VRF: default Advertised route: 192.0.254.5/32 - Invalid/inactive; Valid: False, Active: True", - "Peer: 172.30.11.5 VRF: default Received route: 192.0.254.3/32 - Invalid/inactive; Valid: True, Active: False", + "Peer: 172.30.11.5 VRF: default Advertised route: 192.0.254.5/32 - Valid: False, Active: True", + "Peer: 172.30.11.5 VRF: default Received route: 192.0.254.3/32 - Valid: True, Active: False", "Peer: 172.30.11.5 VRF: default Received route: 192.0.255.41/32 - Not found", ], }, From 1ed7934d62aba11dfa85a4c7bb1ab87b2f29640f Mon Sep 17 00:00:00 2001 From: vitthalmagadum Date: Thu, 28 Nov 2024 05:52:13 -0500 Subject: [PATCH 15/21] Addressed review comments: updated ci failure --- anta/tests/routing/bgp.py | 35 +++---- tests/units/anta_tests/routing/test_bgp.py | 110 ++++++++++----------- 2 files changed, 73 insertions(+), 72 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index fdbded848..51efdbfac 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -408,12 +408,14 @@ def test(self) -> None: received_routes_cmd = self.instance_commands[peer_idx + num_peers] # Get the BGP route entries of each command - advertised_routes_entries = get_value(advertised_routes_cmd.json_output, f"vrfs.{peer.vrf}.bgpRouteEntries", default={}) - received_routes_entries = get_value(received_routes_cmd.json_output, f"vrfs.{peer.vrf}.bgpRouteEntries", default={}) + command_output = { + "Advertised": get_value(advertised_routes_cmd.json_output, f"vrfs.{peer.vrf}.bgpRouteEntries", default={}), + "Received": get_value(received_routes_cmd.json_output, f"vrfs.{peer.vrf}.bgpRouteEntries", default={}), + } # Validate both advertised and received routes for route_type, routes in zip(["Advertised", "Received"], [peer.advertised_routes, peer.received_routes]): - entries = advertised_routes_entries if route_type == "Advertised" else received_routes_entries + entries = command_output[route_type] for route in routes: # Check if the route is found if str(route) not in entries: @@ -516,7 +518,7 @@ def test(self) -> None: # If strict is True, check if only the specified capabilities are configured if peer.strict and sorted(peer.capabilities) != sorted(act_mp_caps): - self.result.is_failure(f"{peer} - Mismatch; Expected: {', '.join(peer.capabilities)} Actual: {', '.join(act_mp_caps)}") + self.result.is_failure(f"{peer} - Mismatch - Expected: {', '.join(peer.capabilities)} Actual: {', '.join(act_mp_caps)}") continue # Check each capability @@ -527,7 +529,7 @@ def test(self) -> None: # Check if the capability is advertised, received, and enabled elif not _check_bgp_neighbor_capability(capability_status): - self.result.is_failure(f"{peer} - {capability} not negotiated; {format_data(capability_status)}") + self.result.is_failure(f"{peer} - {capability} not negotiated - {format_data(capability_status)}") class VerifyBGPPeerASNCap(AntaTest): @@ -596,7 +598,7 @@ def test(self) -> None: # Check if the 4-octet ASN capability is advertised, received, and enabled if not _check_bgp_neighbor_capability(capablity_status): - self.result.is_failure(f"{peer} - 4-octet ASN capability not negotiated; {format_data(capablity_status)}") + self.result.is_failure(f"{peer} - 4-octet ASN capability not negotiated - {format_data(capablity_status)}") class VerifyBGPPeerRouteRefreshCap(AntaTest): @@ -665,7 +667,7 @@ def test(self) -> None: # Check if the route refresh capability is advertised, received, and enabled if not _check_bgp_neighbor_capability(capablity_status): - self.result.is_failure(f"{peer} - Route refresh capability not negotiated; {format_data(capablity_status)}") + self.result.is_failure(f"{peer} - Route refresh capability not negotiated - {format_data(capablity_status)}") class VerifyBGPPeerMD5Auth(AntaTest): @@ -733,7 +735,7 @@ def test(self) -> None: state = peer_data.get("state") md5_auth_enabled = peer_data.get("md5AuthEnabled") if state != "Established": - self.result.is_failure(f"{peer} - Session state is not established; State: {state}") + self.result.is_failure(f"{peer} - Session state is not established - State: {state}") if not md5_auth_enabled: self.result.is_failure(f"{peer} - Session does not have MD5 authentication enabled") @@ -845,7 +847,6 @@ class VerifyBGPAdvCommunities(AntaTest): ``` """ - description = "Verifies the advertised communities of a BGP peer." categories: ClassVar[list[str]] = ["bgp"] commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp neighbors vrf all", revision=3)] @@ -951,9 +952,9 @@ def test(self) -> None: # Check BGP peer timers if peer_data["holdTime"] != peer.hold_time: - self.result.is_failure(f"{peer} - Hold time mismatch; Expected: {peer.hold_time} Actual: {peer_data['holdTime']}") + self.result.is_failure(f"{peer} - Hold time mismatch - Expected: {peer.hold_time}, Actual: {peer_data['holdTime']}") if peer_data["keepaliveTime"] != peer.keep_alive_time: - self.result.is_failure(f"{peer} - Keepalive time mismatch; Expected: {peer.keep_alive_time} Actual: {peer_data['keepaliveTime']}") + self.result.is_failure(f"{peer} - Keepalive time mismatch - Expected: {peer.keep_alive_time}, Actual: {peer_data['keepaliveTime']}") class VerifyBGPPeerDropStats(AntaTest): @@ -1030,7 +1031,7 @@ def test(self) -> None: # Verify BGP peer's drop stats for drop_stat in drop_stats_input: if (stat_value := drop_stats_output.get(drop_stat, 0)) != 0: - self.result.is_failure(f"{peer} - Non-zero NLRI drop statistics counter; {drop_stat}: {stat_value}") + self.result.is_failure(f"{peer} - Non-zero NLRI drop statistics counter - {drop_stat}: {stat_value}") class VerifyBGPPeerUpdateErrors(AntaTest): @@ -1108,7 +1109,7 @@ def test(self) -> None: # Verify BGP peer's update error counters for error_counter in update_errors_input: if (stat_value := error_counters_output.get(error_counter, "Not Found")) != 0 and stat_value != "None": - self.result.is_failure(f"{peer} - Non-zero update error counter; {error_counter}: {stat_value}") + self.result.is_failure(f"{peer} - Non-zero update error counter - {error_counter}: {stat_value}") class VerifyBgpRouteMaps(AntaTest): @@ -1186,11 +1187,11 @@ def test(self) -> None: # Verify Inbound route-map if inbound_route_map and (inbound_map := peer_data.get("routeMapInbound", "Not Configured")) != inbound_route_map: - self.result.is_failure(f"{peer} - Inbound route-map mismatch; Expected: {inbound_route_map} Actual: {inbound_map}") + self.result.is_failure(f"{peer} - Inbound route-map mismatch - Expected: {inbound_route_map}, Actual: {inbound_map}") # Verify Outbound route-map if outbound_route_map and (outbound_map := peer_data.get("routeMapOutbound", "Not Configured")) != outbound_route_map: - self.result.is_failure(f"{peer} - Outbound route-map mismatch; Expected: {outbound_route_map} Actual: {outbound_map}") + self.result.is_failure(f"{peer} - Outbound route-map mismatch - Expected: {outbound_route_map}, Actual: {outbound_map}") class VerifyBGPPeerRouteLimit(AntaTest): @@ -1265,8 +1266,8 @@ def test(self) -> None: # Verify maximum routes configured. if (actual_routes := peer_data.get("maxTotalRoutes", "Not Found")) != maximum_routes: - self.result.is_failure(f"{peer} - Maximum routes mismatch; Expected: {maximum_routes} Actual: {actual_routes}") + self.result.is_failure(f"{peer} - Maximum routes mismatch - Expected: {maximum_routes}, Actual: {actual_routes}") # Verify warning limit if given. if warning_limit and (actual_warning_limit := peer_data.get("totalRoutesWarnLimit", "Not Found")) != warning_limit: - self.result.is_failure(f"{peer} - Maximum route warning limit mismatch; Expected: {warning_limit} Actual: {actual_warning_limit}") + self.result.is_failure(f"{peer} - Maximum route warning limit mismatch - Expected: {warning_limit}, Actual: {actual_warning_limit}") diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 2263f588d..9f7912e68 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -1639,14 +1639,14 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 172.30.11.1 VRF: default - ipv4Unicast not negotiated; Advertised: False, Received: False, Enabled: False", - "Peer: 172.30.11.1 VRF: default - ipv4MplsVpn not negotiated; Advertised: False, Received: True, Enabled: False", + "Peer: 172.30.11.1 VRF: default - ipv4Unicast not negotiated - Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.1 VRF: default - ipv4MplsVpn not negotiated - Advertised: False, Received: True, Enabled: False", "Peer: 172.30.11.1 VRF: default - l2VpnEvpn not found", "Peer: 172.30.11.10 VRF: MGMT - ipv4Unicast not found", - "Peer: 172.30.11.10 VRF: MGMT - ipv4MplsVpn not negotiated; Advertised: False, Received: False, Enabled: True", - "Peer: 172.30.11.10 VRF: MGMT - l2VpnEvpn not negotiated; Advertised: True, Received: False, Enabled: False", - "Peer: 172.30.11.11 VRF: MGMT - ipv4Unicast not negotiated; Advertised: False, Received: False, Enabled: False", - "Peer: 172.30.11.11 VRF: MGMT - ipv4MplsVpn not negotiated; Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.10 VRF: MGMT - ipv4MplsVpn not negotiated - Advertised: False, Received: False, Enabled: True", + "Peer: 172.30.11.10 VRF: MGMT - l2VpnEvpn not negotiated - Advertised: True, Received: False, Enabled: False", + "Peer: 172.30.11.11 VRF: MGMT - ipv4Unicast not negotiated - Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.11 VRF: MGMT - ipv4MplsVpn not negotiated - Advertised: False, Received: False, Enabled: False", "Peer: 172.30.11.11 VRF: MGMT - l2VpnEvpn not found", ], }, @@ -1790,8 +1790,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 172.30.11.1 VRF: default - Mismatch; Expected: ipv4Unicast Actual: ipv4Unicast, ipv4MplsLabels", - "Peer: 172.30.11.10 VRF: MGMT - Mismatch; Expected: ipv4MplsVpn, l2VpnEvpn Actual: ipv4Unicast, ipv4MplsVpn", + "Peer: 172.30.11.1 VRF: default - Mismatch - Expected: ipv4Unicast Actual: ipv4Unicast, ipv4MplsLabels", + "Peer: 172.30.11.10 VRF: MGMT - Mismatch - Expected: ipv4MplsVpn, l2VpnEvpn Actual: ipv4Unicast, ipv4MplsVpn", ], }, }, @@ -1985,8 +1985,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 172.30.11.1 VRF: default - 4-octet ASN capability not negotiated; Advertised: False, Received: False, Enabled: False", - "Peer: 172.30.11.10 VRF: MGMT - 4-octet ASN capability not negotiated; Advertised: True, Received: False, Enabled: True", + "Peer: 172.30.11.1 VRF: default - 4-octet ASN capability not negotiated - Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.10 VRF: MGMT - 4-octet ASN capability not negotiated - Advertised: True, Received: False, Enabled: True", ], }, }, @@ -2203,7 +2203,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 172.30.11.1 VRF: default - Route refresh capability not negotiated; Advertised: False, Received: False, Enabled: False", + "Peer: 172.30.11.1 VRF: default - Route refresh capability not negotiated - Advertised: False, Received: False, Enabled: False", ], }, }, @@ -2337,8 +2337,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 172.30.11.1 VRF: default - Session state is not established; State: Idle", - "Peer: 172.30.11.10 VRF: MGMT - Session state is not established; State: Idle", + "Peer: 172.30.11.1 VRF: default - Session state is not established - State: Idle", + "Peer: 172.30.11.10 VRF: MGMT - Session state is not established - State: Idle", ], }, }, @@ -2995,9 +2995,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 172.30.11.1 VRF: default - Hold time mismatch; Expected: 180 Actual: 160", - "Peer: 172.30.11.11 VRF: MGMT - Hold time mismatch; Expected: 180 Actual: 120", - "Peer: 172.30.11.11 VRF: MGMT - Keepalive time mismatch; Expected: 60 Actual: 40", + "Peer: 172.30.11.1 VRF: default - Hold time mismatch - Expected: 180, Actual: 160", + "Peer: 172.30.11.11 VRF: MGMT - Hold time mismatch - Expected: 180, Actual: 120", + "Peer: 172.30.11.11 VRF: MGMT - Keepalive time mismatch - Expected: 60, Actual: 40", ], }, }, @@ -3138,10 +3138,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; prefixDroppedMartianV4: 1", - "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; prefixDroppedMaxRouteLimitViolatedV4: 1", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropOrigId: 1", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropNhLocal: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter - prefixDroppedMartianV4: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter - prefixDroppedMaxRouteLimitViolatedV4: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter - inDropOrigId: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter - inDropNhLocal: 1", ], }, }, @@ -3252,14 +3252,14 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; inDropAsloop: 3", - "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; inDropOrigId: 1", - "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; inDropNhLocal: 1", - "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; prefixDroppedMartianV4: 1", - "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter; prefixDroppedMaxRouteLimitViolatedV4: 1", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropAsloop: 2", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropOrigId: 1", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter; inDropNhLocal: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter - inDropAsloop: 3", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter - inDropOrigId: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter - inDropNhLocal: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter - prefixDroppedMartianV4: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero NLRI drop statistics counter - prefixDroppedMaxRouteLimitViolatedV4: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter - inDropAsloop: 2", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter - inDropOrigId: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero NLRI drop statistics counter - inDropNhLocal: 1", ], }, }, @@ -3374,8 +3374,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; disabledAfiSafi: ipv4Unicast", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrWithdraw: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter - disabledAfiSafi: ipv4Unicast", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter - inUpdErrWithdraw: 1", ], }, }, @@ -3474,10 +3474,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; inUpdErrWithdraw: 1", - "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; disabledAfiSafi: ipv4Unicast", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrWithdraw: 1", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrDisableAfiSafi: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter - inUpdErrWithdraw: 1", + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter - disabledAfiSafi: ipv4Unicast", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter - inUpdErrWithdraw: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter - inUpdErrDisableAfiSafi: 1", ], }, }, @@ -3529,10 +3529,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; inUpdErrWithdraw: Not Found", - "Peer: 10.100.0.8 VRF: default - Non-zero update error counter; disabledAfiSafi: ipv4Unicast", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrWithdraw: 1", - "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter; inUpdErrDisableAfiSafi: Not Found", + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter - inUpdErrWithdraw: Not Found", + "Peer: 10.100.0.8 VRF: default - Non-zero update error counter - disabledAfiSafi: ipv4Unicast", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter - inUpdErrWithdraw: 1", + "Peer: 10.100.0.9 VRF: MGMT - Non-zero update error counter - inUpdErrDisableAfiSafi: Not Found", ], }, }, @@ -3607,10 +3607,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: RM-MLAG-PEER", - "Peer: 10.100.0.8 VRF: default - Outbound route-map mismatch; Expected: RM-MLAG-PEER-OUT Actual: RM-MLAG-PEER", - "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: RM-MLAG-PEER", - "Peer: 10.100.0.10 VRF: MGMT - Outbound route-map mismatch; Expected: RM-MLAG-PEER-OUT Actual: RM-MLAG-PEER", + "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch - Expected: RM-MLAG-PEER-IN, Actual: RM-MLAG-PEER", + "Peer: 10.100.0.8 VRF: default - Outbound route-map mismatch - Expected: RM-MLAG-PEER-OUT, Actual: RM-MLAG-PEER", + "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch - Expected: RM-MLAG-PEER-IN, Actual: RM-MLAG-PEER", + "Peer: 10.100.0.10 VRF: MGMT - Outbound route-map mismatch - Expected: RM-MLAG-PEER-OUT, Actual: RM-MLAG-PEER", ], }, }, @@ -3650,8 +3650,8 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: RM-MLAG-PEER", - "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: RM-MLAG-PEER", + "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch - Expected: RM-MLAG-PEER-IN, Actual: RM-MLAG-PEER", + "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch - Expected: RM-MLAG-PEER-IN, Actual: RM-MLAG-PEER", ], }, }, @@ -3687,10 +3687,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: Not Configured", - "Peer: 10.100.0.8 VRF: default - Outbound route-map mismatch; Expected: RM-MLAG-PEER-OUT Actual: Not Configured", - "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch; Expected: RM-MLAG-PEER-IN Actual: Not Configured", - "Peer: 10.100.0.10 VRF: MGMT - Outbound route-map mismatch; Expected: RM-MLAG-PEER-OUT Actual: Not Configured", + "Peer: 10.100.0.8 VRF: default - Inbound route-map mismatch - Expected: RM-MLAG-PEER-IN, Actual: Not Configured", + "Peer: 10.100.0.8 VRF: default - Outbound route-map mismatch - Expected: RM-MLAG-PEER-OUT, Actual: Not Configured", + "Peer: 10.100.0.10 VRF: MGMT - Inbound route-map mismatch - Expected: RM-MLAG-PEER-IN, Actual: Not Configured", + "Peer: 10.100.0.10 VRF: MGMT - Outbound route-map mismatch - Expected: RM-MLAG-PEER-OUT, Actual: Not Configured", ], }, }, @@ -3815,10 +3815,10 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Maximum routes mismatch; Expected: 12000 Actual: 13000", - "Peer: 10.100.0.8 VRF: default - Maximum route warning limit mismatch; Expected: 10000 Actual: 11000", - "Peer: 10.100.0.9 VRF: MGMT - Maximum routes mismatch; Expected: 10000 Actual: 11000", - "Peer: 10.100.0.9 VRF: MGMT - Maximum route warning limit mismatch; Expected: 9000 Actual: 10000", + "Peer: 10.100.0.8 VRF: default - Maximum routes mismatch - Expected: 12000, Actual: 13000", + "Peer: 10.100.0.8 VRF: default - Maximum route warning limit mismatch - Expected: 10000, Actual: 11000", + "Peer: 10.100.0.9 VRF: MGMT - Maximum routes mismatch - Expected: 10000, Actual: 11000", + "Peer: 10.100.0.9 VRF: MGMT - Maximum route warning limit mismatch - Expected: 9000, Actual: 10000", ], }, }, @@ -3855,9 +3855,9 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "expected": { "result": "failure", "messages": [ - "Peer: 10.100.0.8 VRF: default - Maximum route warning limit mismatch; Expected: 10000 Actual: Not Found", - "Peer: 10.100.0.9 VRF: MGMT - Maximum routes mismatch; Expected: 10000 Actual: Not Found", - "Peer: 10.100.0.9 VRF: MGMT - Maximum route warning limit mismatch; Expected: 9000 Actual: Not Found", + "Peer: 10.100.0.8 VRF: default - Maximum route warning limit mismatch - Expected: 10000, Actual: Not Found", + "Peer: 10.100.0.9 VRF: MGMT - Maximum routes mismatch - Expected: 10000, Actual: Not Found", + "Peer: 10.100.0.9 VRF: MGMT - Maximum route warning limit mismatch - Expected: 9000, Actual: Not Found", ], }, }, From 4a801c5243636ff23393dd5fc0333c4d66e76d82 Mon Sep 17 00:00:00 2001 From: vitthalmagadum Date: Thu, 28 Nov 2024 11:23:57 -0500 Subject: [PATCH 16/21] Updated unit tests for input models --- anta/input_models/routing/bgp.py | 2 +- anta/tests/routing/bgp.py | 6 +- tests/units/input_models/routing/test_bgp.py | 144 ++++++++++++++++++- 3 files changed, 146 insertions(+), 6 deletions(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index 5abbe1911..f75135381 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -178,7 +178,7 @@ def __str__(self) -> str: return f"Peer: {self.peer_address} VRF: {self.vrf}" -class BgpNeighbor(BgpPeer): +class BgpNeighbor(BgpPeer): # pragma: no cover """Alias for the BgpPeer model to maintain backward compatibility. When initialized, it will emit a depreciation warning and call the BgpPeer model. diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 51efdbfac..cab7c61ce 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -383,7 +383,7 @@ class Input(AntaTest.Input): @field_validator("bgp_peers") @classmethod def validate_bgp_peers(cls, bgp_peers: list[BgpPeer]) -> list[BgpPeer]: - """Validate that 'peers' field is provided in each address family.""" + """Validate that 'advertised_routes' or 'received_routes' field is provided in each address family.""" for peer in bgp_peers: if peer.advertised_routes is None or peer.received_routes is None: msg = f"{peer} 'advertised_routes' or 'received_routes' field missing in the input" @@ -486,7 +486,7 @@ class Input(AntaTest.Input): @field_validator("bgp_peers") @classmethod def validate_bgp_peers(cls, bgp_peers: list[T]) -> list[T]: - """Validate that 'peers' field is provided in each address family.""" + """Validate that 'capabilities' field is provided in each address family.""" for peer in bgp_peers: if peer.capabilities is None: msg = f"{peer} 'capabilities' field missing in the input" @@ -927,7 +927,7 @@ class Input(AntaTest.Input): @field_validator("bgp_peers") @classmethod def validate_bgp_peers(cls, bgp_peers: list[T]) -> list[T]: - """Validate that 'peers' field is provided in each address family.""" + """Validate that 'hold_time' or 'keep_alive_time' field is provided in each address family.""" for peer in bgp_peers: if peer.hold_time is None or peer.keep_alive_time is None: msg = f"{peer} 'hold_time' or 'keep_alive_time' field missing in the input" diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py index aabc8f293..66c37af61 100644 --- a/tests/units/input_models/routing/test_bgp.py +++ b/tests/units/input_models/routing/test_bgp.py @@ -11,8 +11,16 @@ import pytest from pydantic import ValidationError -from anta.input_models.routing.bgp import BgpAddressFamily -from anta.tests.routing.bgp import VerifyBGPPeerCount, VerifyBGPSpecificPeers +from anta.input_models.routing.bgp import BgpAddressFamily, BgpPeer +from anta.tests.routing.bgp import ( + VerifyBGPExchangedRoutes, + VerifyBGPPeerCount, + VerifyBGPPeerMPCaps, + VerifyBGPPeerRouteLimit, + VerifyBgpRouteMaps, + VerifyBGPSpecificPeers, + VerifyBGPTimers, +) if TYPE_CHECKING: from anta.custom_types import Afi, Safi @@ -96,3 +104,135 @@ def test_invalid(self, address_families: list[BgpAddressFamily]) -> None: """Test VerifyBGPSpecificPeers.Input invalid inputs.""" with pytest.raises(ValidationError): VerifyBGPSpecificPeers.Input(address_families=address_families) + + +class TestVerifyBGPExchangedRoutesInput: + """Test anta.tests.routing.bgp.VerifyBGPExchangedRoutes.Input.""" + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param( + [{"peer_address": "172.30.255.5", "vrf": "default", "advertised_routes": ["192.0.254.5/32"], "received_routes": ["192.0.255.4/32"]}], + id="valid_both_received_advertised", + ), + ], + ) + def test_valid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBGPExchangedRoutes.Input valid inputs.""" + VerifyBGPExchangedRoutes.Input(bgp_peers=bgp_peers) + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default"}], id="invalid"), + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "advertised_routes": ["192.0.254.5/32"]}], id="invalid_received_route"), + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "received_routes": ["192.0.254.5/32"]}], id="invalid_advertised_route"), + ], + ) + def test_invalid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBGPExchangedRoutes.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPExchangedRoutes.Input(bgp_peers=bgp_peers) + + +class TestVerifyBGPPeerMPCapsInput: + """Test anta.tests.routing.bgp.VerifyBGPPeerMPCaps.Input.""" + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "capabilities": ["ipv4Unicast"]}], id="valid"), + ], + ) + def test_valid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBGPPeerMPCaps.Input valid inputs.""" + VerifyBGPPeerMPCaps.Input(bgp_peers=bgp_peers) + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default"}], id="invalid"), + ], + ) + def test_invalid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBGPPeerMPCaps.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPPeerMPCaps.Input(bgp_peers=bgp_peers) + + +class TestVerifyBGPTimersInput: + """Test anta.tests.routing.bgp.VerifyBGPTimers.Input.""" + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "hold_time": 180, "keep_alive_time": 60}], id="valid"), + ], + ) + def test_valid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBGPTimers.Input valid inputs.""" + VerifyBGPTimers.Input(bgp_peers=bgp_peers) + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default"}], id="invalid"), + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "hold_time": 180}], id="invalid_keep_alive"), + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "keep_alive_time": 180}], id="invalid_hold_time"), + ], + ) + def test_invalid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBGPTimers.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPTimers.Input(bgp_peers=bgp_peers) + + +class TestVerifyBgpRouteMapsInput: + """Test anta.tests.routing.bgp.VerifyBgpRouteMaps.Input.""" + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "inbound_route_map": "Test", "outbound_route_map": "Test"}], id="valid"), + ], + ) + def test_valid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBgpRouteMaps.Input valid inputs.""" + VerifyBgpRouteMaps.Input(bgp_peers=bgp_peers) + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default"}], id="invalid"), + ], + ) + def test_invalid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBgpRouteMaps.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBgpRouteMaps.Input(bgp_peers=bgp_peers) + + +class TestVerifyBGPPeerRouteLimitInput: + """Test anta.tests.routing.bgp.VerifyBGPPeerRouteLimit.Input.""" + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "maximum_routes": 10000}], id="valid"), + ], + ) + def test_valid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBGPPeerRouteLimit.Input valid inputs.""" + VerifyBGPPeerRouteLimit.Input(bgp_peers=bgp_peers) + + @pytest.mark.parametrize( + ("bgp_peers"), + [ + pytest.param([{"peer_address": "172.30.255.5", "vrf": "default"}], id="invalid"), + ], + ) + def test_invalid(self, bgp_peers: list[BgpPeer]) -> None: + """Test VerifyBGPPeerRouteLimit.Input invalid inputs.""" + with pytest.raises(ValidationError): + VerifyBGPPeerRouteLimit.Input(bgp_peers=bgp_peers) From c62cfd8de81ad1547d8201f59d223e9689404f0d Mon Sep 17 00:00:00 2001 From: vitthalmagadum Date: Tue, 3 Dec 2024 00:41:24 -0500 Subject: [PATCH 17/21] autogenerated test.yml --- examples/tests.yaml | 16 ++++++++-------- tests/units/anta_tests/routing/test_bgp.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/tests.yaml b/examples/tests.yaml index 273d20a5b..7f90f7cff 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -352,7 +352,7 @@ anta.tests.ptp: # Verifies the PTP interfaces state. anta.tests.routing.bgp: - VerifyBGPAdvCommunities: - # Verifies the advertised communities of a BGP peer. + # Verifies that advertised communities are standard, extended and large for BGP peers. bgp_peers: - peer_address: 172.30.11.17 vrf: default @@ -375,7 +375,7 @@ anta.tests.routing.bgp: received_routes: - 192.0.254.3/32 - VerifyBGPPeerASNCap: - # Verifies the four octet asn capabilities of a BGP peer. + # Verifies the four octet ASN capability of BGP peers. bgp_peers: - peer_address: 172.30.11.1 vrf: default @@ -405,14 +405,14 @@ anta.tests.routing.bgp: - inDropAsloop - prefixEvpnDroppedUnsupportedRouteType - VerifyBGPPeerMD5Auth: - # Verifies the MD5 authentication and state of a BGP peer. + # Verifies the MD5 authentication and state of IPv4 BGP peers in a specified VRF. bgp_peers: - peer_address: 172.30.11.1 vrf: default - peer_address: 172.30.11.5 vrf: default - VerifyBGPPeerMPCaps: - # Verifies the multiprotocol capabilities of a BGP peer. + # Verifies the multiprotocol capabilities of BGP peers. bgp_peers: - peer_address: 172.30.11.1 vrf: default @@ -420,14 +420,14 @@ anta.tests.routing.bgp: capabilities: - ipv4Unicast - VerifyBGPPeerRouteLimit: - # Verifies the maximum routes and optionally verifies the maximum routes warning limit for the provided BGP IPv4 peer(s). + # Verifies maximum routes and outbound route-maps of BGP IPv4 peer(s). bgp_peers: - peer_address: 172.30.11.1 vrf: default maximum_routes: 12000 warning_limit: 10000 - VerifyBGPPeerRouteRefreshCap: - # Verifies the route refresh capabilities of a BGP peer. + # Verifies the route refresh capabilities of a BGP peer in a specified VRF. bgp_peers: - peer_address: 172.30.11.1 vrf: default @@ -436,7 +436,7 @@ anta.tests.routing.bgp: bgp_peers: - peer_address: 172.30.11.1 vrf: default - update_error_filter: + update_errors: - inUpdErrWithdraw - VerifyBGPPeersHealth: # Verifies the health of BGP peers for given address families. @@ -464,7 +464,7 @@ anta.tests.routing.bgp: - 10.1.255.2 - 10.1.255.4 - VerifyBGPTimers: - # Verifies the timers of a BGP peer. + # Verifies the timers of BGP peers. bgp_peers: - peer_address: 172.30.11.1 vrf: default diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 9f7912e68..c2ab53aef 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -851,7 +851,7 @@ def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bo "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", - ] + ], }, }, { From 34a40537111d43ce2dfb0965e39c7d08c0d82584 Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Mon, 23 Dec 2024 11:16:08 -0500 Subject: [PATCH 18/21] Fix CI --- anta/reporter/md_reporter.py | 5 ++--- anta/tests/routing/bgp.py | 12 ++++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/anta/reporter/md_reporter.py b/anta/reporter/md_reporter.py index be3e86faf..94c4a8668 100644 --- a/anta/reporter/md_reporter.py +++ b/anta/reporter/md_reporter.py @@ -8,7 +8,7 @@ import logging import re from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING, ClassVar, TextIO from anta.constants import MD_REPORT_TOC from anta.logger import anta_log_exception @@ -17,7 +17,6 @@ if TYPE_CHECKING: from collections.abc import Generator - from io import TextIOWrapper from pathlib import Path from anta.result_manager import ResultManager @@ -72,7 +71,7 @@ class MDReportBase(ABC): to generate and write content to the provided markdown file. """ - def __init__(self, mdfile: TextIOWrapper, results: ResultManager) -> None: + def __init__(self, mdfile: TextIO, results: ResultManager) -> None: """Initialize the MDReportBase with an open markdown file object to write to and a ResultManager instance. Parameters diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 80322eab7..34b6d1dab 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -750,20 +750,16 @@ class VerifyEVPNType2Route(AntaTest): This test performs the following checks for each specified VXLAN endpoint: 1. Verifies that the endpoint exists in the BGP EVPN table. - 2. For each EVPN route found: - - Validates that at least one path exists for the route. - - Confirms that at least one path is valid and active. + 2. Confirms that at least one EVPN Type-2 route with a valid and active path exists. Expected Results ---------------- * Success: If all of the following conditions are met: - All specified VXLAN endpoints are found in the BGP EVPN table. - - Each endpoint has at least one EVPN Type-2 route. - - Each route has at least one path that is both valid and active. + - Each endpoint has at least one EVPN Type-2 route with a valid and active path. * Failure: If any of the following occur: - A VXLAN endpoint is not found in the BGP EVPN table. - - No EVPN Type-2 route exists for an endpoint. - - No valid and active path exists for a route. + - No EVPN Type-2 route with a valid and active path exists for an endpoint. Examples -------- @@ -805,7 +801,7 @@ def test(self) -> None: self.result.is_failure(f"{endpoint} - No EVPN Type-2 route") continue - # Verify that at least one EVPN route has at least one active/valid path across all learned routes from all RDs combined + # Verify that at least one EVPN Type-2 route has at least one active and valid path across all learned routes from all RDs combined has_active_path = False for route_data in evpn_routes.values(): for path in route_data.get("evpnRoutePaths", []): From 7716349d13e1e7d5d9baff9756b96db7aec5389e Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Tue, 24 Dec 2024 15:24:18 +0100 Subject: [PATCH 19/21] Update anta/input_models/routing/bgp.py --- anta/input_models/routing/bgp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py index f75135381..57c821740 100644 --- a/anta/input_models/routing/bgp.py +++ b/anta/input_models/routing/bgp.py @@ -181,7 +181,7 @@ def __str__(self) -> str: class BgpNeighbor(BgpPeer): # pragma: no cover """Alias for the BgpPeer model to maintain backward compatibility. - When initialized, it will emit a depreciation warning and call the BgpPeer model. + When initialised, it will emit a deprecation warning and call the BgpPeer model. TODO: Remove this class in ANTA v2.0.0. """ From 7cfb7d01413119ee773ad2632bfc7dec963fd3bd Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Tue, 24 Dec 2024 15:26:08 +0100 Subject: [PATCH 20/21] Update anta/tests/routing/bgp.py --- 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 34b6d1dab..8de58dcee 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -335,7 +335,7 @@ class VerifyBGPExchangedRoutes(AntaTest): This test performs the following checks for each specified peer: - 1. For each advertised and received route: + For each advertised and received route: - Confirms that the route exists in the BGP route table. - Verifies that the route is in an 'active' and 'valid' state. From 59cfe5ec93a7fd16e49bffebb4ceda4a3f78931e Mon Sep 17 00:00:00 2001 From: Carl Baillargeon Date: Tue, 24 Dec 2024 10:28:26 -0500 Subject: [PATCH 21/21] Apply suggestions from code review Co-authored-by: Guillaume Mulocher --- anta/tests/routing/bgp.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py index 8de58dcee..2a140ddb2 100644 --- a/anta/tests/routing/bgp.py +++ b/anta/tests/routing/bgp.py @@ -442,7 +442,7 @@ class VerifyBGPPeerMPCaps(AntaTest): 1. Confirms that the specified VRF is configured. 2. Verifies that the peer exists in the BGP configuration. 3. For each specified capability: - - Validates that the capability is present in the peer's configuration. + - Validates that the capability is present in the peer configuration. - Confirms that the capability is advertised, received, and enabled. 4. When strict mode is enabled (`strict: true`): - Verifies that only the specified capabilities are configured. @@ -543,14 +543,14 @@ class VerifyBGPPeerASNCap(AntaTest): 1. Confirms that the specified VRF is configured. 2. Verifies that the peer exists in the BGP configuration. - 3. Validates that the capability is present in the peer's configuration. + 3. Validates that the capability is present in the peer configuration. 4. Confirms that the capability is advertised, received, and enabled. Expected Results ---------------- * Success: If all of the following conditions are met: - All specified peers are found in the BGP configuration. - - The four octet ASN capability is present in each peer's configuration. + - The four octet ASN capability is present in each peer configuration. - The capability is properly negotiated (advertised, received, and enabled) for all peers. * Failure: If any of the following occur: - A specified peer is not found in the BGP configuration. @@ -612,14 +612,14 @@ class VerifyBGPPeerRouteRefreshCap(AntaTest): 1. Confirms that the specified VRF is configured. 2. Verifies that the peer exists in the BGP configuration. - 3. Validates that the route refresh capability is present in the peer's configuration. + 3. Validates that the route refresh capability is present in the peer configuration. 4. Confirms that the capability is advertised, received, and enabled. Expected Results ---------------- * Success: If all of the following conditions are met: - All specified peers are found in the BGP configuration. - - The route refresh capability is present in each peer's configuration. + - The route refresh capability is present in each peer configuration. - The capability is properly negotiated (advertised, received, and enabled) for all peers. * Failure: If any of the following occur: - A specified peer is not found in the BGP configuration.