summaryrefslogtreecommitdiffstats
path: root/tests/units
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2025-01-14 10:18:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2025-01-14 10:18:29 +0000
commit6818d016122ee845a2011b94bbdad0ed28a9aae7 (patch)
treee9865932680acf05b8c353347cf362ab3fd10ff0 /tests/units
parentReleasing debian version 1.1.0-1. (diff)
downloadanta-6818d016122ee845a2011b94bbdad0ed28a9aae7.tar.xz
anta-6818d016122ee845a2011b94bbdad0ed28a9aae7.zip
Merging upstream version 1.2.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/units')
-rw-r--r--tests/units/anta_tests/conftest.py2
-rw-r--r--tests/units/anta_tests/routing/test_bgp.py2381
-rw-r--r--tests/units/anta_tests/routing/test_generic.py46
-rw-r--r--tests/units/anta_tests/test_avt.py259
-rw-r--r--tests/units/anta_tests/test_bfd.py49
-rw-r--r--tests/units/anta_tests/test_connectivity.py96
-rw-r--r--tests/units/anta_tests/test_cvx.py525
-rw-r--r--tests/units/anta_tests/test_interfaces.py144
-rw-r--r--tests/units/anta_tests/test_security.py24
-rw-r--r--tests/units/anta_tests/test_services.py36
-rw-r--r--tests/units/anta_tests/test_stun.py34
-rw-r--r--tests/units/anta_tests/test_system.py48
-rw-r--r--tests/units/cli/conftest.py1
-rw-r--r--tests/units/cli/get/local_module/__init__.py4
-rw-r--r--tests/units/cli/get/test_commands.py183
-rw-r--r--tests/units/cli/get/test_utils.py93
-rw-r--r--tests/units/cli/nrfu/test_commands.py15
-rw-r--r--tests/units/input_models/__init__.py4
-rw-r--r--tests/units/input_models/routing/__init__.py4
-rw-r--r--tests/units/input_models/routing/test_bgp.py238
-rw-r--r--tests/units/input_models/test_interfaces.py33
-rw-r--r--tests/units/reporter/conftest.py2
-rw-r--r--tests/units/reporter/test__init__.py2
-rw-r--r--tests/units/reporter/test_csv.py9
-rw-r--r--tests/units/reporter/test_md_reporter.py4
-rw-r--r--tests/units/result_manager/test__init__.py101
-rw-r--r--tests/units/test_custom_types.py3
-rw-r--r--tests/units/test_decorators.py77
-rw-r--r--tests/units/test_device.py128
-rw-r--r--tests/units/test_models.py151
-rw-r--r--tests/units/test_runner.py39
-rw-r--r--tests/units/test_tools.py16
32 files changed, 2727 insertions, 2024 deletions
diff --git a/tests/units/anta_tests/conftest.py b/tests/units/anta_tests/conftest.py
index 5da7606..5e0c11b 100644
--- a/tests/units/anta_tests/conftest.py
+++ b/tests/units/anta_tests/conftest.py
@@ -21,7 +21,7 @@ def build_test_id(val: dict[str, Any]) -> str:
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
- """Generate ANTA testts unit tests dynamically during test collection.
+ """Generate ANTA tests unit tests dynamically during test collection.
It will parametrize test cases based on the `DATA` data structure defined in `tests.units.anta_tests` modules.
See `tests/units/anta_tests/README.md` for more information on how to use it.
diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py
index e256b04..59a6719 100644
--- a/tests/units/anta_tests/routing/test_bgp.py
+++ b/tests/units/anta_tests/routing/test_bgp.py
@@ -6,8 +6,11 @@
# pylint: disable=C0302
from __future__ import annotations
-from typing import Any
+from typing import TYPE_CHECKING, Any
+import pytest
+
+from anta.input_models.routing.bgp import BgpAddressFamily
from anta.tests.routing.bgp import (
VerifyBGPAdvCommunities,
VerifyBGPExchangedRoutes,
@@ -24,556 +27,397 @@ from anta.tests.routing.bgp import (
VerifyBGPSpecificPeers,
VerifyBGPTimers,
VerifyEVPNType2Route,
+ _check_bgp_neighbor_capability,
)
from tests.units.anta_tests import test
+
+@pytest.mark.parametrize(
+ ("input_dict", "expected"),
+ [
+ pytest.param({"advertised": True, "received": True, "enabled": True}, True, id="all True"),
+ pytest.param({"advertised": False, "received": True, "enabled": True}, False, id="advertised False"),
+ pytest.param({"advertised": True, "received": False, "enabled": True}, False, id="received False"),
+ pytest.param({"advertised": True, "received": True, "enabled": False}, False, id="enabled False"),
+ pytest.param({"advertised": True, "received": True}, False, id="missing enabled"),
+ pytest.param({}, False),
+ ],
+)
+def test_check_bgp_neighbor_capability(input_dict: dict[str, bool], expected: bool) -> None:
+ """Test check_bgp_neighbor_capability."""
+ assert _check_bgp_neighbor_capability(input_dict) == expected
+
+
DATA: list[dict[str, Any]] = [
{
"name": "success",
"test": VerifyBGPPeerCount,
"eos_data": [
- # Need to order the output as the commands would be sorted after template rendering.
{
"vrfs": {
"default": {
+ "vrf": "default",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ "10.1.0.1": {
+ "peerState": "Idle",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
- "10.1.255.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ "10.1.0.2": {
+ "peerState": "Idle",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
},
},
- },
- },
- {
- "vrfs": {
- "MGMT": {
+ "DEV": {
+ "vrf": "DEV",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.255.0.21": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
+ "10.1.254.1": {
+ "peerState": "Idle",
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4},
+ }
},
},
- },
+ }
},
+ ],
+ "inputs": {
+ "address_families": [
+ {"afi": "evpn", "num_peers": 2},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-peer-state-check-true",
+ "test": VerifyBGPPeerCount,
+ "eos_data": [
{
"vrfs": {
"default": {
+ "vrf": "default",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.255.0.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.0.1": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
- "10.255.0.2": {
- "description": "DC1-SPINE2_Ethernet1",
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.0.2": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
- },
- },
- },
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.255.0.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.254.1": {
"peerState": "Established",
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17},
},
- "10.255.0.12": {
- "description": "DC1-SPINE2_Ethernet1",
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.255.0": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14},
+ },
+ "10.1.255.2": {
+ "peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14},
},
},
},
- },
- },
- {
- "vrfs": {
- "default": {
+ "DEV": {
+ "vrf": "DEV",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.255.0.21": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.255.0.22": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.254.1": {
"peerState": "Established",
- },
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4},
+ }
},
},
- },
+ }
},
],
"inputs": {
"address_families": [
- # evpn first to make sure that the correct mapping output to input is kept.
- {"afi": "evpn", "num_peers": 2},
- {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 1},
- {"afi": "link-state", "num_peers": 2},
- {"afi": "path-selection", "num_peers": 2},
+ {"afi": "evpn", "num_peers": 2, "check_peer_state": True},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3, "check_peer_state": True},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1, "check_peer_state": True},
]
},
"expected": {"result": "success"},
},
{
- "name": "failure-wrong-count",
+ "name": "failure-vrf-not-configured",
"test": VerifyBGPPeerCount,
"eos_data": [
{
"vrfs": {
"default": {
+ "vrf": "default",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- },
- },
- },
- {
- "vrfs": {
- "MGMT": {
- "peers": {
- "10.255.0.21": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.0.1": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
- },
- },
- },
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.255.0.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.0.2": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
- "10.255.0.2": {
- "description": "DC1-SPINE2_Ethernet1",
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.254.1": {
"peerState": "Established",
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17},
},
- },
- },
- },
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.255.0.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.255.0": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14},
},
- "10.255.0.12": {
- "description": "DC1-SPINE2_Ethernet1",
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.255.2": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14},
},
},
},
- },
- },
- {
- "vrfs": {
- "default": {
+ "DEV": {
+ "vrf": "DEV",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.255.0.21": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.254.1": {
"peerState": "Established",
- },
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4},
+ }
},
},
- },
- },
- ],
- "inputs": {
- "address_families": [
- {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 2},
- {"afi": "evpn", "num_peers": 1},
- {"afi": "link-state", "num_peers": 3},
- {"afi": "path-selection", "num_peers": 3},
- ]
- },
- "expected": {
- "result": "failure",
- "messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': 'Expected: 3, Actual: 2'}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Expected: 2, Actual: 1'}}, "
- "{'afi': 'evpn', 'vrfs': {'default': 'Expected: 1, Actual: 2'}}, "
- "{'afi': 'link-state', 'vrfs': {'default': 'Expected: 3, Actual: 2'}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': 'Expected: 3, Actual: 1'}}]"
- ],
- },
- },
- {
- "name": "failure-no-peers",
- "test": VerifyBGPPeerCount,
- "eos_data": [
- {
- "vrfs": {
- "default": {
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
- "MGMT": {
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {},
- }
}
},
],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 1},
- {"afi": "evpn", "num_peers": 2},
- {"afi": "link-state", "num_peers": 2},
- {"afi": "path-selection", "num_peers": 2},
+ {"afi": "evpn", "num_peers": 2, "check_peer_state": True},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3, "check_peer_state": True},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "PROD", "num_peers": 2, "check_peer_state": True},
]
},
"expected": {
"result": "failure",
"messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': 'Expected: 2, Actual: 0'}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Expected: 1, Actual: 0'}}, "
- "{'afi': 'evpn', 'vrfs': {'default': 'Expected: 2, Actual: 0'}}, "
- "{'afi': 'link-state', 'vrfs': {'default': 'Expected: 2, Actual: 0'}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': 'Expected: 2, Actual: 0'}}]"
+ "AFI: ipv4 SAFI: unicast VRF: PROD - VRF not configured",
],
},
},
{
- "name": "failure-not-configured",
- "test": VerifyBGPPeerCount,
- "eos_data": [{"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}],
- "inputs": {
- "address_families": [
- {"afi": "ipv6", "safi": "multicast", "vrf": "DEV", "num_peers": 3},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 1},
- {"afi": "evpn", "num_peers": 2},
- {"afi": "link-state", "num_peers": 2},
- {"afi": "path-selection", "num_peers": 2},
- ]
- },
- "expected": {
- "result": "failure",
- "messages": [
- "Failures: [{'afi': 'ipv6', 'safi': 'multicast', 'vrfs': {'DEV': 'Not Configured'}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Not Configured'}}, "
- "{'afi': 'evpn', 'vrfs': {'default': 'Not Configured'}}, "
- "{'afi': 'link-state', 'vrfs': {'default': 'Not Configured'}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': 'Not Configured'}}]"
- ],
- },
- },
- {
- "name": "success-vrf-all",
+ "name": "failure-peer-state-check-true",
"test": VerifyBGPPeerCount,
"eos_data": [
{
"vrfs": {
"default": {
+ "vrf": "default",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.0.1": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
+ },
+ "10.1.0.2": {
+ "peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
- },
- },
- "PROD": {
- "peers": {
"10.1.254.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
"peerState": "Established",
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17},
},
- "192.168.1.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.255.0": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14},
},
- },
- },
- },
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.10": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.255.2": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14},
},
},
},
- "PROD": {
+ "DEV": {
+ "vrf": "DEV",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.1.254.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.254.1": {
"peerState": "Established",
- },
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4},
+ }
},
},
- },
+ }
},
],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "unicast", "vrf": "all", "num_peers": 3},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "all", "num_peers": 2},
+ {"afi": "evpn", "num_peers": 2, "check_peer_state": True},
+ {"afi": "vpn-ipv4", "num_peers": 2, "check_peer_state": True},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 3, "check_peer_state": True},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 1, "check_peer_state": True},
]
},
- "expected": {"result": "success"},
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "AFI: vpn-ipv4 - Expected: 2, Actual: 0",
+ ],
+ },
},
{
- "name": "failure-vrf-all",
+ "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.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.0.1": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
+ },
+ "10.1.0.2": {
+ "peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4MplsVpn": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
- },
- },
- "PROD": {
- "peers": {
"10.1.254.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
"peerState": "Established",
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 17, "nlrisAccepted": 17},
},
- "192.168.1.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.255.0": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14},
},
- },
- },
- },
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.10": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
+ "10.1.255.2": {
"peerState": "Established",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 14, "nlrisAccepted": 14},
},
},
},
- "PROD": {
+ "DEV": {
+ "vrf": "DEV",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
"10.1.254.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
"peerState": "Established",
- },
- "192.168.1.12": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4},
+ }
},
},
- },
+ }
},
],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "unicast", "vrf": "all", "num_peers": 5},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "all", "num_peers": 2},
+ {"afi": "evpn", "num_peers": 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": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'all': 'Expected: 5, Actual: 3'}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'all': 'Expected: 2, Actual: 3'}}]"
+ "AFI: evpn - Expected: 3, Actual: 2",
+ "AFI: ipv4 SAFI: unicast VRF: DEV - Expected: 2, Actual: 1",
],
},
},
{
- "name": "failure-multiple-afi",
+ "name": "failure-wrong-count",
"test": VerifyBGPPeerCount,
"eos_data": [
{
"vrfs": {
- "PROD": {
- "peers": {
- "10.1.254.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "192.168.1.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- },
- },
- },
- {"vrfs": {}},
- {
- "vrfs": {
- "MGMT": {
- "peers": {
- "10.1.254.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "192.168.1.21": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- },
- },
- },
- {
- "vrfs": {
"default": {
+ "vrf": "default",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
"10.1.0.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.0.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- },
- },
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.0.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.0.21": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ "peerState": "Idle",
+ "peerAsn": "65100",
+ "ipv4Unicast": {"afiSafiState": "advertised", "nlrisReceived": 0, "nlrisAccepted": 0},
+ "l2VpnEvpn": {"afiSafiState": "negotiated", "nlrisReceived": 42, "nlrisAccepted": 42},
},
},
},
- },
- },
- {
- "vrfs": {
- "default": {
+ "DEV": {
+ "vrf": "DEV",
+ "routerId": "10.1.0.3",
+ "asn": "65120",
"peers": {
- "10.1.0.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.0.22": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
+ "10.1.254.1": {
+ "peerState": "Idle",
+ "peerAsn": "65120",
+ "ipv4Unicast": {"afiSafiState": "negotiated", "nlrisReceived": 4, "nlrisAccepted": 4},
+ }
},
},
- },
+ }
},
],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "unicast", "vrf": "PROD", "num_peers": 3},
- {"afi": "ipv6", "safi": "unicast", "vrf": "default", "num_peers": 3},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 3},
- {"afi": "evpn", "num_peers": 3},
- {"afi": "link-state", "num_peers": 4},
- {"afi": "path-selection", "num_peers": 1},
- ],
+ {"afi": "evpn", "num_peers": 2},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "DEV", "num_peers": 2},
+ ]
},
"expected": {
"result": "failure",
"messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'PROD': 'Expected: 3, Actual: 2'}}, "
- "{'afi': 'ipv6', 'safi': 'unicast', 'vrfs': {'default': 'Not Configured'}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Expected: 3, Actual: 2'}}, "
- "{'afi': 'evpn', 'vrfs': {'default': 'Expected: 3, Actual: 2'}}, "
- "{'afi': 'link-state', 'vrfs': {'default': 'Expected: 4, Actual: 2'}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': 'Expected: 1, Actual: 2'}}]",
+ "AFI: evpn - Expected: 2, Actual: 1",
+ "AFI: ipv4 SAFI: unicast VRF: default - Expected: 2, Actual: 1",
+ "AFI: ipv4 SAFI: unicast VRF: DEV - Expected: 2, Actual: 1",
],
},
},
@@ -584,163 +428,127 @@ DATA: list[dict[str, Any]] = [
{
"vrfs": {
"default": {
- "peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- }
- }
- },
- {
- "vrfs": {
- "MGMT": {
- "peers": {
- "10.1.255.10": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.12": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.20": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ {
+ "peerAddress": "10.100.0.13",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- "10.1.255.22": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ ]
+ },
+ "DEV": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
- }
+ ]
+ },
}
- },
+ }
+ ],
+ "inputs": {
+ "address_families": [
+ {"afi": "evpn"},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default"},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "DEV"},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-vrf-not-configured",
+ "test": VerifyBGPPeersHealth,
+ "eos_data": [
{
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.30": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.32": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- }
- }
- },
+ "vrfs": {},
+ }
],
"inputs": {
"address_families": [
- # Path selection first to make sure input to output mapping is correct.
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default"},
+ {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"},
{"afi": "path-selection"},
+ {"afi": "link-state"},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "AFI: ipv4 SAFI: unicast VRF: default - VRF not configured",
+ "AFI: ipv4 SAFI: sr-te VRF: MGMT - VRF not configured",
+ "AFI: path-selection - VRF not configured",
+ "AFI: link-state - VRF not configured",
+ ],
+ },
+ },
+ {
+ "name": "failure-peer-not-found",
+ "test": VerifyBGPPeersHealth,
+ "eos_data": [{"vrfs": {"default": {"peerList": []}, "MGMT": {"peerList": []}}}],
+ "inputs": {
+ "address_families": [
{"afi": "ipv4", "safi": "unicast", "vrf": "default"},
{"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"},
+ {"afi": "path-selection"},
{"afi": "link-state"},
]
},
- "expected": {"result": "success"},
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "AFI: ipv4 SAFI: unicast VRF: default - No peers found",
+ "AFI: ipv4 SAFI: sr-te VRF: MGMT - No peers found",
+ "AFI: path-selection - No peers found",
+ "AFI: link-state - No peers found",
+ ],
+ },
},
{
- "name": "failure-issues",
+ "name": "failure-session-not-established",
"test": VerifyBGPPeersHealth,
"eos_data": [
{
"vrfs": {
"default": {
- "peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
- },
- "10.1.255.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- }
- }
- },
- {
- "vrfs": {
- "MGMT": {
- "peers": {
- "10.1.255.10": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.12": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
- },
- },
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.20": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Idle",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
},
- "10.1.255.22": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ {
+ "peerAddress": "10.100.0.13",
+ "state": "Idle",
+ "neighborCapabilities": {"multiprotocolCaps": {"dps": {"advertised": True, "received": True, "enabled": True}}},
},
- },
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.30": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ {
+ "peerAddress": "10.100.0.14",
+ "state": "Active",
+ "neighborCapabilities": {"multiprotocolCaps": {"linkState": {"advertised": True, "received": True, "enabled": True}}},
},
- "10.1.255.32": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
+ ]
+ },
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Active",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4SrTe": {"advertised": True, "received": True, "enabled": True}}},
},
- },
- }
+ ]
+ },
}
- },
+ }
],
"inputs": {
"address_families": [
@@ -753,559 +561,411 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': {'10.1.255.0': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': {'10.1.255.12': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': {'10.1.255.20': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, "
- "{'afi': 'link-state', 'vrfs': {'default': {'10.1.255.32': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}]"
+ "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session state is not established - State: Idle",
+ "AFI: ipv4 SAFI: sr-te VRF: MGMT Peer: 10.100.0.12 - Session state is not established - State: Active",
+ "AFI: path-selection Peer: 10.100.0.13 - Session state is not established - State: Idle",
+ "AFI: link-state Peer: 10.100.0.14 - Session state is not established - State: Active",
],
},
},
{
- "name": "success-vrf-all",
+ "name": "failure-afi-not-negotiated",
"test": VerifyBGPPeersHealth,
"eos_data": [
{
"vrfs": {
"default": {
- "peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": False, "received": False, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- "10.1.255.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ {
+ "peerAddress": "10.100.0.13",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"dps": {"advertised": True, "received": False, "enabled": False}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
- },
- "PROD": {
- "peers": {
- "10.1.254.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ {
+ "peerAddress": "10.100.0.14",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"linkState": {"advertised": False, "received": False, "enabled": False}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- "192.168.1.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ ]
+ },
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4SrTe": {"advertised": False, "received": False, "enabled": False}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
+ ]
},
}
- },
+ }
+ ],
+ "inputs": {
+ "address_families": [
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default"},
+ {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"},
+ {"afi": "path-selection"},
+ {"afi": "link-state"},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - AFI/SAFI state is not negotiated - Advertised: False, Received: False, Enabled: True",
+ "AFI: ipv4 SAFI: sr-te VRF: MGMT Peer: 10.100.0.12 - AFI/SAFI state is not negotiated - Advertised: False, Received: False, Enabled: False",
+ "AFI: path-selection Peer: 10.100.0.13 - AFI/SAFI state is not negotiated - Advertised: True, Received: False, Enabled: False",
+ "AFI: link-state Peer: 10.100.0.14 - AFI/SAFI state is not negotiated - Advertised: False, Received: False, Enabled: False",
+ ],
+ },
+ },
+ {
+ "name": "failure-tcp-queues",
+ "test": VerifyBGPPeersHealth,
+ "eos_data": [
{
"vrfs": {
"default": {
- "peers": {
- "10.1.255.10": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 4, "inputQueueLength": 2},
},
- "10.1.255.12": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ {
+ "peerAddress": "10.100.0.13",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"dps": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 1, "inputQueueLength": 1},
},
- },
- },
- "PROD": {
- "peers": {
- "10.1.254.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ {
+ "peerAddress": "10.100.0.14",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"linkState": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 2, "inputQueueLength": 3},
},
- "192.168.1.111": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ ]
+ },
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4SrTe": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 1, "inputQueueLength": 5},
},
- },
+ ]
},
}
- },
+ }
],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "unicast", "vrf": "all"},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "all"},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "default"},
+ {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"},
+ {"afi": "path-selection"},
+ {"afi": "link-state"},
]
},
"expected": {
- "result": "success",
+ "result": "failure",
+ "messages": [
+ "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session has non-empty message queues - InQ: 2, OutQ: 4",
+ "AFI: ipv4 SAFI: sr-te VRF: MGMT Peer: 10.100.0.12 - Session has non-empty message queues - InQ: 5, OutQ: 1",
+ "AFI: path-selection Peer: 10.100.0.13 - Session has non-empty message queues - InQ: 1, OutQ: 1",
+ "AFI: link-state Peer: 10.100.0.14 - Session has non-empty message queues - InQ: 3, OutQ: 2",
+ ],
},
},
{
- "name": "failure-issues-vrf-all",
- "test": VerifyBGPPeersHealth,
+ "name": "success",
+ "test": VerifyBGPSpecificPeers,
"eos_data": [
{
"vrfs": {
"default": {
- "peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- "10.1.255.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ {
+ "peerAddress": "10.100.0.13",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
+ ]
},
- "PROD": {
- "peers": {
- "10.1.254.1": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "192.168.1.11": {
- "inMsgQueue": 100,
- "outMsgQueue": 200,
- "peerState": "Established",
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.14",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
+ ]
},
}
- },
+ }
+ ],
+ "inputs": {
+ "address_families": [
+ {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]},
+ {"afi": "evpn", "peers": ["10.100.0.13"]},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-peer-not-configured",
+ "test": VerifyBGPSpecificPeers,
+ "eos_data": [
{
"vrfs": {
"default": {
- "peers": {
- "10.1.255.10": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
- },
- "10.1.255.12": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.20",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
+ }
+ ]
},
- "PROD": {
- "peers": {
- "10.1.254.11": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "192.168.1.111": {
- "inMsgQueue": 100,
- "outMsgQueue": 200,
- "peerState": "Established",
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.10",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
+ ]
},
}
- },
+ }
],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "unicast", "vrf": "all"},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "all"},
+ {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]},
+ {"afi": "evpn", "peers": ["10.100.0.13"]},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]},
]
},
"expected": {
"result": "failure",
"messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': {'10.1.255.0': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}, "
- "'PROD': {'192.168.1.11': {'peerState': 'Established', 'inMsgQueue': 100, 'outMsgQueue': 200}}}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'default': {'10.1.255.10': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}, "
- "'PROD': {'192.168.1.111': {'peerState': 'Established', 'inMsgQueue': 100, 'outMsgQueue': 200}}}}]"
+ "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Not configured",
+ "AFI: evpn Peer: 10.100.0.13 - Not configured",
+ "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - Not configured",
],
},
},
{
- "name": "failure-not-configured",
- "test": VerifyBGPPeersHealth,
- "eos_data": [{"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}],
+ "name": "failure-vrf-not-configured",
+ "test": VerifyBGPSpecificPeers,
+ "eos_data": [
+ {
+ "vrfs": {},
+ }
+ ],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "unicast", "vrf": "DEV"},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"},
- {"afi": "link-state"},
- {"afi": "path-selection"},
+ {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]},
+ {"afi": "evpn", "peers": ["10.100.0.13"]},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]},
]
},
"expected": {
"result": "failure",
"messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'DEV': 'Not Configured'}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Not Configured'}}, "
- "{'afi': 'link-state', 'vrfs': {'default': 'Not Configured'}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': 'Not Configured'}}]"
+ "AFI: ipv4 SAFI: unicast VRF: default - VRF not configured",
+ "AFI: evpn - VRF not configured",
+ "AFI: ipv4 SAFI: unicast VRF: MGMT - VRF not configured",
],
},
},
{
- "name": "failure-no-peers",
- "test": VerifyBGPPeersHealth,
+ "name": "failure-session-not-established",
+ "test": VerifyBGPSpecificPeers,
"eos_data": [
{
"vrfs": {
"default": {
- "vrf": "default",
- "routerId": "10.1.0.3",
- "asn": "65120",
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Idle",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ }
+ ]
+ },
"MGMT": {
- "vrf": "MGMT",
- "routerId": "10.1.0.3",
- "asn": "65120",
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "vrf": "default",
- "routerId": "10.1.0.3",
- "asn": "65120",
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "vrf": "default",
- "routerId": "10.1.0.3",
- "asn": "65120",
- "peers": {},
- }
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.14",
+ "state": "Idle",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ },
+ ]
+ },
}
- },
+ }
],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "multicast"},
- {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"},
- {"afi": "link-state"},
- {"afi": "path-selection"},
+ {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]},
]
},
"expected": {
"result": "failure",
"messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'multicast', 'vrfs': {'default': 'No Peers'}}, {'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'No Peers'}}, "
- "{'afi': 'link-state', 'vrfs': {'default': 'No Peers'}}, {'afi': 'path-selection', 'vrfs': {'default': 'No Peers'}}]"
+ "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session state is not established - State: Idle",
+ "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - Session state is not established - State: Idle",
],
},
},
{
- "name": "success",
+ "name": "failure-afi-safi-not-negotiated",
"test": VerifyBGPSpecificPeers,
"eos_data": [
{
"vrfs": {
"default": {
- "peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- }
- }
- },
- {
- "vrfs": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": False, "received": False, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
+ }
+ ]
+ },
"MGMT": {
- "peers": {
- "10.1.255.10": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.12": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.20": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.22": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.30": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.32": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.14",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": False, "received": False, "enabled": False}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
- }
+ ]
+ },
}
- },
+ }
],
"inputs": {
"address_families": [
- # Path selection first to make sure input to output mapping is correct.
- {"afi": "path-selection", "peers": ["10.1.255.20", "10.1.255.22"]},
- {
- "afi": "ipv4",
- "safi": "unicast",
- "vrf": "default",
- "peers": ["10.1.255.0", "10.1.255.2"],
- },
- {
- "afi": "ipv4",
- "safi": "sr-te",
- "vrf": "MGMT",
- "peers": ["10.1.255.10", "10.1.255.12"],
- },
- {"afi": "link-state", "peers": ["10.1.255.30", "10.1.255.32"]},
+ {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]},
]
},
- "expected": {"result": "success"},
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - AFI/SAFI state is not negotiated - Advertised: False, Received: False, Enabled: True",
+ "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - AFI/SAFI state is not negotiated - Advertised: False, Received: False, Enabled: False",
+ ],
+ },
},
{
- "name": "failure-issues",
+ "name": "failure-afi-safi-not-correct",
"test": VerifyBGPSpecificPeers,
"eos_data": [
{
"vrfs": {
"default": {
- "peers": {
- "10.1.255.0": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
- },
- "10.1.255.2": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- }
- }
- },
- {
- "vrfs": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": False, "received": False, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
+ }
+ ]
+ },
"MGMT": {
- "peers": {
- "10.1.255.10": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.12": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
- },
- },
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.20": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
- },
- "10.1.255.22": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- },
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "peers": {
- "10.1.255.30": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Established",
- },
- "10.1.255.32": {
- "inMsgQueue": 0,
- "outMsgQueue": 0,
- "peerState": "Idle",
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.14",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"l2VpnEvpn": {"advertised": False, "received": False, "enabled": False}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 0, "inputQueueLength": 0},
},
- },
- }
+ ]
+ },
}
- },
+ }
],
"inputs": {
"address_families": [
- {
- "afi": "ipv4",
- "safi": "unicast",
- "vrf": "default",
- "peers": ["10.1.255.0", "10.1.255.2"],
- },
- {
- "afi": "ipv4",
- "safi": "sr-te",
- "vrf": "MGMT",
- "peers": ["10.1.255.10", "10.1.255.12"],
- },
- {"afi": "path-selection", "peers": ["10.1.255.20", "10.1.255.22"]},
- {"afi": "link-state", "peers": ["10.1.255.30", "10.1.255.32"]},
+ {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]},
]
},
"expected": {
"result": "failure",
"messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'default': {'10.1.255.0': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': {'10.1.255.12': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': {'10.1.255.20': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}, "
- "{'afi': 'link-state', 'vrfs': {'default': {'10.1.255.32': {'peerState': 'Idle', 'inMsgQueue': 0, 'outMsgQueue': 0}}}}]"
+ "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - AFI/SAFI state is not negotiated",
+ "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - AFI/SAFI state is not negotiated",
],
},
},
{
- "name": "failure-not-configured",
- "test": VerifyBGPSpecificPeers,
- "eos_data": [{"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}, {"vrfs": {}}],
- "inputs": {
- "address_families": [
- {
- "afi": "ipv4",
- "safi": "unicast",
- "vrf": "DEV",
- "peers": ["10.1.255.0"],
- },
- {
- "afi": "ipv4",
- "safi": "sr-te",
- "vrf": "MGMT",
- "peers": ["10.1.255.10"],
- },
- {"afi": "link-state", "peers": ["10.1.255.20"]},
- {"afi": "path-selection", "peers": ["10.1.255.30"]},
- ]
- },
- "expected": {
- "result": "failure",
- "messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'DEV': 'Not Configured'}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': 'Not Configured'}}, {'afi': 'link-state', 'vrfs': {'default': 'Not Configured'}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': 'Not Configured'}}]"
- ],
- },
- },
- {
- "name": "failure-no-peers",
+ "name": "failure-tcp-queues",
"test": VerifyBGPSpecificPeers,
"eos_data": [
{
"vrfs": {
"default": {
- "vrf": "default",
- "routerId": "10.1.0.3",
- "asn": "65120",
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.12",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 3, "inputQueueLength": 3},
+ }
+ ]
+ },
"MGMT": {
- "vrf": "MGMT",
- "routerId": "10.1.0.3",
- "asn": "65120",
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "vrf": "default",
- "routerId": "10.1.0.3",
- "asn": "65120",
- "peers": {},
- }
- }
- },
- {
- "vrfs": {
- "default": {
- "vrf": "default",
- "routerId": "10.1.0.3",
- "asn": "65120",
- "peers": {},
- }
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.14",
+ "state": "Established",
+ "neighborCapabilities": {"multiprotocolCaps": {"ipv4Unicast": {"advertised": True, "received": True, "enabled": True}}},
+ "peerTcpInfo": {"state": "ESTABLISHED", "outputQueueLength": 2, "inputQueueLength": 2},
+ },
+ ]
+ },
}
- },
+ }
],
"inputs": {
"address_families": [
- {"afi": "ipv4", "safi": "multicast", "peers": ["10.1.255.0"]},
- {
- "afi": "ipv4",
- "safi": "sr-te",
- "vrf": "MGMT",
- "peers": ["10.1.255.10"],
- },
- {"afi": "link-state", "peers": ["10.1.255.20"]},
- {"afi": "path-selection", "peers": ["10.1.255.30"]},
+ {"afi": "ipv4", "safi": "unicast", "peers": ["10.100.0.12"]},
+ {"afi": "ipv4", "safi": "unicast", "vrf": "MGMT", "peers": ["10.100.0.14"]},
]
},
"expected": {
"result": "failure",
"messages": [
- "Failures: [{'afi': 'ipv4', 'safi': 'multicast', 'vrfs': {'default': {'10.1.255.0': {'peerNotFound': True}}}}, "
- "{'afi': 'ipv4', 'safi': 'sr-te', 'vrfs': {'MGMT': {'10.1.255.10': {'peerNotFound': True}}}}, "
- "{'afi': 'link-state', 'vrfs': {'default': {'10.1.255.20': {'peerNotFound': True}}}}, "
- "{'afi': 'path-selection', 'vrfs': {'default': {'10.1.255.30': {'peerNotFound': True}}}}]"
+ "AFI: ipv4 SAFI: unicast VRF: default Peer: 10.100.0.12 - Session has non-empty message queues - InQ: 3, OutQ: 3",
+ "AFI: ipv4 SAFI: unicast VRF: MGMT Peer: 10.100.0.14 - Session has non-empty message queues - InQ: 2, OutQ: 2",
],
},
},
@@ -1508,234 +1168,10 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -1875,11 +1311,14 @@ DATA: list[dict[str, Any]] = [
"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 - 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 - 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 - 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",
],
},
},
@@ -1991,9 +1430,7 @@ DATA: list[dict[str, Any]] = [
},
"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"],
},
},
{
@@ -2054,8 +1491,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2095,9 +1532,7 @@ DATA: list[dict[str, Any]] = [
},
"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"],
},
},
{
@@ -2190,13 +1625,15 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2339,10 +1776,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2398,63 +1833,6 @@ DATA: list[dict[str, Any]] = [
"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,
"eos_data": [
@@ -2489,9 +1867,7 @@ DATA: list[dict[str, Any]] = [
},
"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"],
},
},
{
@@ -2544,8 +1920,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2595,9 +1971,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2653,25 +2028,6 @@ DATA: list[dict[str, Any]] = [
"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,
"eos_data": [
@@ -2727,8 +2083,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2782,8 +2138,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2833,8 +2189,7 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2880,40 +2235,6 @@ DATA: list[dict[str, Any]] = [
"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,
"eos_data": [
@@ -2947,16 +2268,16 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -2980,7 +2301,7 @@ DATA: list[dict[str, Any]] = [
{
"peerAddress": "172.30.11.10",
"state": "Idle",
- "md5AuthEnabled": False,
+ "md5AuthEnabled": True,
}
]
},
@@ -3002,9 +2323,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -3054,9 +2374,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -3155,8 +2474,8 @@ DATA: list[dict[str, Any]] = [
"evpnRoutePaths": [
{
"routeType": {
- "active": True,
- "valid": True,
+ "active": False,
+ "valid": False,
},
},
]
@@ -3303,7 +2622,7 @@ DATA: list[dict[str, Any]] = [
"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)]"],
+ "messages": ["Address: 192.168.20.102 VNI: 10020 - No EVPN Type-2 route"],
},
},
{
@@ -3331,103 +2650,7 @@ DATA: list[dict[str, Any]] = [
"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": [
- {
- "routeType": {
- "active": False,
- "valid": True,
- },
- },
- ]
- },
- "RD: 10.1.0.6:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102": {
- "evpnRoutePaths": [
- {
- "routeType": {
- "active": False,
- "valid": False,
- },
- },
- ]
- },
- },
- },
- ],
- "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']"
- ],
- },
- },
- {
- "name": "failure-multiple-routes-multiple-paths-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": True,
- "valid": True,
- },
- },
- {
- "routeType": {
- "active": False,
- "valid": True,
- },
- },
- ]
- },
- "RD: 10.1.0.6:500 mac-ip 10020 aac1.ab4e.bec2 192.168.20.102": {
- "evpnRoutePaths": [
- {
- "routeType": {
- "active": False,
- "valid": False,
- },
- },
- {
- "routeType": {
- "active": False,
- "valid": False,
- },
- },
- ]
- },
- },
- },
- ],
- "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 valid and active path"],
},
},
{
@@ -3477,67 +2700,7 @@ DATA: list[dict[str, Any]] = [
},
"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": {
- "evpnRoutePaths": [
- {
- "routeType": {
- "active": False,
- "valid": False,
- },
- },
- ]
- },
- },
- },
- ],
- "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},
- ]
- },
- "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"],
},
},
{
@@ -3587,43 +2750,6 @@ DATA: list[dict[str, Any]] = [
"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,
"eos_data": [
@@ -3671,8 +2797,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -3724,9 +2850,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -3781,15 +2906,7 @@ DATA: list[dict[str, Any]] = [
"eos_data": [
{
"vrfs": {
- "default": {
- "peerList": [
- {
- "peerAddress": "172.30.11.1",
- "holdTime": 180,
- "keepaliveTime": 60,
- }
- ]
- },
+ "default": {"peerList": []},
"MGMT": {"peerList": []},
}
}
@@ -3804,7 +2921,7 @@ DATA: list[dict[str, Any]] = [
},
{
"peer_address": "172.30.11.11",
- "vrf": "MGMT",
+ "vrf": "default",
"hold_time": 180,
"keep_alive_time": 60,
},
@@ -3813,8 +2930,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -3864,9 +2981,9 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -3894,10 +3011,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -3934,10 +3047,7 @@ DATA: list[dict[str, Any]] = [
{
"name": "failure-not-found",
"test": VerifyBGPPeerDropStats,
- "eos_data": [
- {"vrfs": {}},
- {"vrfs": {}},
- ],
+ "eos_data": [{"vrfs": {}}],
"inputs": {
"bgp_peers": [
{
@@ -3951,8 +3061,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -3980,10 +3090,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4018,9 +3124,10 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4048,10 +3155,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4105,10 +3208,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4139,49 +3238,14 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4205,10 +3269,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4239,7 +3299,6 @@ DATA: list[dict[str, Any]] = [
"test": VerifyBGPPeerUpdateErrors,
"eos_data": [
{"vrfs": {}},
- {"vrfs": {}},
],
"inputs": {
"bgp_peers": [
@@ -4250,8 +3309,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4275,10 +3334,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4305,9 +3360,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4331,10 +3385,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4380,10 +3430,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4414,9 +3460,10 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4439,10 +3486,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4472,9 +3515,10 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4493,10 +3537,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4532,10 +3572,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4557,9 +3593,10 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4578,10 +3615,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4603,8 +3636,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4621,10 +3654,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4644,9 +3673,10 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4657,10 +3687,6 @@ DATA: list[dict[str, Any]] = [
{
"vrfs": {
"default": {"peerList": []},
- },
- },
- {
- "vrfs": {
"MGMT": {"peerList": []},
},
},
@@ -4674,8 +3700,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4694,10 +3720,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4725,10 +3747,6 @@ DATA: list[dict[str, Any]] = [
{
"vrfs": {
"default": {},
- },
- },
- {
- "vrfs": {
"MGMT": {},
},
},
@@ -4742,8 +3760,8 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4762,10 +3780,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4787,9 +3801,10 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
@@ -4807,10 +3822,6 @@ DATA: list[dict[str, Any]] = [
}
]
},
- },
- },
- {
- "vrfs": {
"MGMT": {
"peerList": [
{
@@ -4830,9 +3841,9 @@ DATA: list[dict[str, Any]] = [
"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",
],
},
},
diff --git a/tests/units/anta_tests/routing/test_generic.py b/tests/units/anta_tests/routing/test_generic.py
index 20f83b9..4e9d654 100644
--- a/tests/units/anta_tests/routing/test_generic.py
+++ b/tests/units/anta_tests/routing/test_generic.py
@@ -11,7 +11,7 @@ from typing import Any
import pytest
from pydantic import ValidationError
-from anta.tests.routing.generic import VerifyRoutingProtocolModel, VerifyRoutingTableEntry, VerifyRoutingTableSize
+from anta.tests.routing.generic import VerifyIPv4RouteType, VerifyRoutingProtocolModel, VerifyRoutingTableEntry, VerifyRoutingTableSize
from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
@@ -304,6 +304,50 @@ DATA: list[dict[str, Any]] = [
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"], "collect": "all"},
"expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: ['10.1.0.2']"]},
},
+ {
+ "name": "success-valid-route-type",
+ "test": VerifyIPv4RouteType,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {"routes": {"10.10.0.1/32": {"routeType": "eBGP"}, "10.100.0.12/31": {"routeType": "connected"}}},
+ "MGMT": {"routes": {"10.100.1.5/32": {"routeType": "iBGP"}}},
+ }
+ }
+ ],
+ "inputs": {
+ "routes_entries": [
+ {"vrf": "default", "prefix": "10.10.0.1/32", "route_type": "eBGP"},
+ {"vrf": "default", "prefix": "10.100.0.12/31", "route_type": "connected"},
+ {"vrf": "MGMT", "prefix": "10.100.1.5/32", "route_type": "iBGP"},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-route-not-found",
+ "test": VerifyIPv4RouteType,
+ "eos_data": [{"vrfs": {"default": {"routes": {}}}}],
+ "inputs": {"routes_entries": [{"vrf": "default", "prefix": "10.10.0.1/32", "route_type": "eBGP"}]},
+ "expected": {"result": "failure", "messages": ["Prefix: 10.10.0.1/32 VRF: default - Route not found"]},
+ },
+ {
+ "name": "failure-invalid-route-type",
+ "test": VerifyIPv4RouteType,
+ "eos_data": [{"vrfs": {"default": {"routes": {"10.10.0.1/32": {"routeType": "eBGP"}}}}}],
+ "inputs": {"routes_entries": [{"vrf": "default", "prefix": "10.10.0.1/32", "route_type": "iBGP"}]},
+ "expected": {
+ "result": "failure",
+ "messages": ["Prefix: 10.10.0.1/32 VRF: default - Incorrect route type - Expected: iBGP Actual: eBGP"],
+ },
+ },
+ {
+ "name": "failure-vrf-not-configured",
+ "test": VerifyIPv4RouteType,
+ "eos_data": [{"vrfs": {}}],
+ "inputs": {"routes_entries": [{"vrf": "default", "prefix": "10.10.0.1/32", "route_type": "eBGP"}]},
+ "expected": {"result": "failure", "messages": ["Prefix: 10.10.0.1/32 VRF: default - VRF not configured"]},
+ },
]
diff --git a/tests/units/anta_tests/test_avt.py b/tests/units/anta_tests/test_avt.py
index 80fbce0..d9cdaa1 100644
--- a/tests/units/anta_tests/test_avt.py
+++ b/tests/units/anta_tests/test_avt.py
@@ -361,48 +361,63 @@ DATA: list[dict[str, Any]] = [
"avts": {
"DEFAULT-AVT-POLICY-CONTROL-PLANE": {
"avtPaths": {
- "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- }
- }
- }
- }
- }
- },
- {
- "vrfs": {
- "data": {
- "avts": {
- "DATA-AVT-POLICY-CONTROL-PLANE": {
- "avtPaths": {
- "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "direct:8": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
+ "direct:10": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
+ "direct:9": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
+ "multihop:1": {
+ "flags": {"directPath": False, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
+ "multihop:3": {
+ "flags": {"directPath": False, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
}
}
- }
- }
- }
- },
- {
- "vrfs": {
+ },
+ },
"data": {
"avts": {
"DATA-AVT-POLICY-CONTROL-PLANE": {
"avtPaths": {
- "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "direct:8": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
+ "direct:10": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.1",
+ },
+ "direct:9": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.1",
+ },
+ "direct:8": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.2",
+ "destination": "10.101.255.1",
+ },
+ "multihop:1": {
+ "flags": {"directPath": False, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.2",
+ "destination": "10.101.255.1",
+ },
+ "multihop:3": {
+ "flags": {"directPath": False, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.2",
+ "destination": "10.101.255.1",
+ },
}
- }
+ },
}
- }
+ },
}
},
],
@@ -420,36 +435,85 @@ DATA: list[dict[str, Any]] = [
"test": VerifyAVTSpecificPath,
"eos_data": [
{"vrfs": {}},
+ ],
+ "inputs": {
+ "avt_paths": [
+ {"avt_name": "MGMT-AVT-POLICY-DEFAULT", "vrf": "default", "destination": "10.101.255.2", "next_hop": "10.101.255.1", "path_type": "multihop"},
+ {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.2", "path_type": "multihop"},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": ["AVT MGMT-AVT-POLICY-DEFAULT VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.1) - No AVT path configured"],
+ },
+ },
+ {
+ "name": "failure-path_type_check_true",
+ "test": VerifyAVTSpecificPath,
+ "eos_data": [
{
"vrfs": {
+ "default": {
+ "avts": {
+ "DEFAULT-AVT-POLICY-CONTROL-PLANE": {
+ "avtPaths": {
+ "direct:10": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
+ "direct:9": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
+ }
+ }
+ },
+ },
"data": {
"avts": {
"DATA-AVT-POLICY-CONTROL-PLANE": {
"avtPaths": {
- "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
+ "direct:10": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.3",
+ },
+ "direct:9": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.3",
+ },
}
- }
+ },
}
- }
+ },
}
},
],
"inputs": {
"avt_paths": [
- {"avt_name": "MGMT-AVT-POLICY-DEFAULT", "vrf": "default", "destination": "10.101.255.2", "next_hop": "10.101.255.1", "path_type": "multihop"},
- {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.2", "path_type": "multihop"},
+ {
+ "avt_name": "DEFAULT-AVT-POLICY-CONTROL-PLANE",
+ "vrf": "default",
+ "destination": "10.101.255.2",
+ "next_hop": "10.101.255.11",
+ "path_type": "multihop",
+ },
+ {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.21", "path_type": "direct"},
]
},
"expected": {
"result": "failure",
- "messages": ["AVT configuration for peer '10.101.255.2' under topology 'MGMT-AVT-POLICY-DEFAULT' in VRF 'default' is not found."],
+ "messages": [
+ "AVT DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.11) Path Type: multihop - Path not found",
+ "AVT DATA-AVT-POLICY-CONTROL-PLANE VRF: data (Destination: 10.101.255.1, Next-hop: 10.101.255.21) Path Type: direct - Path not found",
+ ],
},
},
{
- "name": "failure-no-path-with-correct-next-hop",
+ "name": "failure-path_type_check_false",
"test": VerifyAVTSpecificPath,
"eos_data": [
{
@@ -458,30 +522,38 @@ DATA: list[dict[str, Any]] = [
"avts": {
"DEFAULT-AVT-POLICY-CONTROL-PLANE": {
"avtPaths": {
- "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
+ "direct:10": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
+ "direct:9": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
}
}
- }
- }
- }
- },
- {
- "vrfs": {
+ },
+ },
"data": {
"avts": {
"DATA-AVT-POLICY-CONTROL-PLANE": {
"avtPaths": {
- "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
+ "direct:10": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.3",
+ },
+ "direct:9": {
+ "flags": {"directPath": True, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.3",
+ },
}
- }
+ },
}
- }
+ },
}
},
],
@@ -492,18 +564,15 @@ DATA: list[dict[str, Any]] = [
"vrf": "default",
"destination": "10.101.255.2",
"next_hop": "10.101.255.11",
- "path_type": "multihop",
},
- {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.21", "path_type": "direct"},
+ {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.21"},
]
},
"expected": {
"result": "failure",
"messages": [
- "No 'multihop' path found with next-hop address '10.101.255.11' for AVT peer '10.101.255.2' under "
- "topology 'DEFAULT-AVT-POLICY-CONTROL-PLANE' in VRF 'default'.",
- "No 'direct' path found with next-hop address '10.101.255.21' for AVT peer '10.101.255.1' under "
- "topology 'DATA-AVT-POLICY-CONTROL-PLANE' in VRF 'data'.",
+ "AVT DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.11) - Path not found",
+ "AVT DATA-AVT-POLICY-CONTROL-PLANE VRF: data (Destination: 10.101.255.1, Next-hop: 10.101.255.21) - Path not found",
],
},
},
@@ -517,30 +586,48 @@ DATA: list[dict[str, Any]] = [
"avts": {
"DEFAULT-AVT-POLICY-CONTROL-PLANE": {
"avtPaths": {
- "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:1": {"flags": {"directPath": True, "valid": False, "active": False}, "nexthopAddr": "10.101.255.1"},
- "multihop:3": {"flags": {"directPath": False, "valid": True, "active": False}, "nexthopAddr": "10.101.255.1"},
+ "multihop:3": {
+ "flags": {"directPath": False, "valid": False, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.2",
+ },
}
}
- }
- }
- }
- },
- {
- "vrfs": {
+ },
+ },
"data": {
"avts": {
"DATA-AVT-POLICY-CONTROL-PLANE": {
"avtPaths": {
- "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"},
- "direct:9": {"flags": {"directPath": True, "valid": False, "active": True}, "nexthopAddr": "10.101.255.1"},
- "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
- "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"},
+ "direct:10": {
+ "flags": {"directPath": True, "valid": False, "active": True},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.1",
+ },
+ "direct:9": {
+ "flags": {"directPath": True, "valid": True, "active": False},
+ "nexthopAddr": "10.101.255.1",
+ "destination": "10.101.255.1",
+ },
+ "direct:8": {
+ "flags": {"directPath": True, "valid": False, "active": False},
+ "nexthopAddr": "10.101.255.2",
+ "destination": "10.101.255.1",
+ },
+ "multihop:1": {
+ "flags": {"directPath": False, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.2",
+ "destination": "10.101.255.1",
+ },
+ "multihop:3": {
+ "flags": {"directPath": False, "valid": True, "active": True},
+ "nexthopAddr": "10.101.255.2",
+ "destination": "10.101.255.1",
+ },
}
- }
+ },
}
- }
+ },
}
},
],
@@ -559,8 +646,12 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "AVT path 'multihop:3' for topology 'DEFAULT-AVT-POLICY-CONTROL-PLANE' in VRF 'default' is inactive.",
- "AVT path 'direct:9' for topology 'DATA-AVT-POLICY-CONTROL-PLANE' in VRF 'data' is invalid.",
+ "AVT DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.1) - "
+ "Incorrect path multihop:3 - Valid: False, Active: True",
+ "AVT DATA-AVT-POLICY-CONTROL-PLANE VRF: data (Destination: 10.101.255.1, Next-hop: 10.101.255.1) - "
+ "Incorrect path direct:10 - Valid: False, Active: True",
+ "AVT DATA-AVT-POLICY-CONTROL-PLANE VRF: data (Destination: 10.101.255.1, Next-hop: 10.101.255.1) - "
+ "Incorrect path direct:9 - Valid: True, Active: False",
],
},
},
diff --git a/tests/units/anta_tests/test_bfd.py b/tests/units/anta_tests/test_bfd.py
index 9bd6465..952e838 100644
--- a/tests/units/anta_tests/test_bfd.py
+++ b/tests/units/anta_tests/test_bfd.py
@@ -107,8 +107,8 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Following BFD peers are not configured or timers are not correct:\n"
- "{'192.0.255.7': {'CS': 'Not Configured'}, '192.0.255.70': {'MGMT': 'Not Configured'}}"
+ "Peer: 192.0.255.7 VRF: CS - Not found",
+ "Peer: 192.0.255.70 VRF: MGMT - Not found",
],
},
},
@@ -160,9 +160,11 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Following BFD peers are not configured or timers are not correct:\n"
- "{'192.0.255.7': {'default': {'tx_interval': 1300, 'rx_interval': 1200, 'multiplier': 4}}, "
- "'192.0.255.70': {'MGMT': {'tx_interval': 120, 'rx_interval': 120, 'multiplier': 5}}}"
+ "Peer: 192.0.255.7 VRF: default - Incorrect Transmit interval - Expected: 1200 Actual: 1300",
+ "Peer: 192.0.255.7 VRF: default - Incorrect Multiplier - Expected: 3 Actual: 4",
+ "Peer: 192.0.255.70 VRF: MGMT - Incorrect Transmit interval - Expected: 1200 Actual: 120",
+ "Peer: 192.0.255.70 VRF: MGMT - Incorrect Receive interval - Expected: 1200 Actual: 120",
+ "Peer: 192.0.255.70 VRF: MGMT - Incorrect Multiplier - Expected: 3 Actual: 5",
],
},
},
@@ -239,8 +241,8 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Following BFD peers are not configured, status is not up or remote disc is zero:\n"
- "{'192.0.255.7': {'CS': 'Not Configured'}, '192.0.255.70': {'MGMT': 'Not Configured'}}"
+ "Peer: 192.0.255.7 VRF: CS - Not found",
+ "Peer: 192.0.255.70 VRF: MGMT - Not found",
],
},
},
@@ -255,7 +257,7 @@ DATA: list[dict[str, Any]] = [
"192.0.255.7": {
"peerStats": {
"": {
- "status": "Down",
+ "status": "down",
"remoteDisc": 108328132,
}
}
@@ -267,7 +269,7 @@ DATA: list[dict[str, Any]] = [
"192.0.255.70": {
"peerStats": {
"": {
- "status": "Down",
+ "status": "down",
"remoteDisc": 0,
}
}
@@ -281,9 +283,8 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Following BFD peers are not configured, status is not up or remote disc is zero:\n"
- "{'192.0.255.7': {'default': {'status': 'Down', 'remote_disc': 108328132}}, "
- "'192.0.255.70': {'MGMT': {'status': 'Down', 'remote_disc': 0}}}"
+ "Peer: 192.0.255.7 VRF: default - Session not properly established - State: down Remote Discriminator: 108328132",
+ "Peer: 192.0.255.70 VRF: MGMT - Session not properly established - State: down Remote Discriminator: 0",
],
},
},
@@ -414,7 +415,8 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Following BFD peers are not up:\n192.0.255.7 is down in default VRF with remote disc 0.\n192.0.255.71 is down in MGMT VRF with remote disc 0."
+ "Peer: 192.0.255.7 VRF: default - Session not properly established - State: down Remote Discriminator: 0",
+ "Peer: 192.0.255.71 VRF: MGMT - Session not properly established - State: down Remote Discriminator: 0",
],
},
},
@@ -458,7 +460,10 @@ DATA: list[dict[str, Any]] = [
"inputs": {},
"expected": {
"result": "failure",
- "messages": ["Following BFD peers were down:\n192.0.255.7 in default VRF has remote disc 0.\n192.0.255.71 in default VRF has remote disc 0."],
+ "messages": [
+ "Peer: 192.0.255.7 VRF: default - Session not properly established - State: up Remote Discriminator: 0",
+ "Peer: 192.0.255.71 VRF: default - Session not properly established - State: up Remote Discriminator: 0",
+ ],
},
},
{
@@ -512,8 +517,9 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Following BFD peers were down:\n192.0.255.7 in default VRF was down 3 hours ago.\n"
- "192.0.255.71 in default VRF was down 3 hours ago.\n192.0.255.17 in default VRF was down 3 hours ago."
+ "Peer: 192.0.255.7 VRF: default - Session failure detected within the expected uptime threshold (3 hours ago)",
+ "Peer: 192.0.255.71 VRF: default - Session failure detected within the expected uptime threshold (3 hours ago)",
+ "Peer: 192.0.255.17 VRF: default - Session failure detected within the expected uptime threshold (3 hours ago)",
],
},
},
@@ -609,15 +615,14 @@ DATA: list[dict[str, Any]] = [
"inputs": {
"bfd_peers": [
{"peer_address": "192.0.255.7", "vrf": "default", "protocols": ["isis"]},
- {"peer_address": "192.0.255.70", "vrf": "MGMT", "protocols": ["isis"]},
+ {"peer_address": "192.0.255.70", "vrf": "MGMT", "protocols": ["isis", "ospf"]},
]
},
"expected": {
"result": "failure",
"messages": [
- "The following BFD peers are not configured or have non-registered protocol(s):\n"
- "{'192.0.255.7': {'default': ['isis']}, "
- "'192.0.255.70': {'MGMT': ['isis']}}"
+ "Peer: 192.0.255.7 VRF: default - `isis` routing protocol(s) not configured",
+ "Peer: 192.0.255.70 VRF: MGMT - `isis` `ospf` routing protocol(s) not configured",
],
},
},
@@ -641,8 +646,8 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "The following BFD peers are not configured or have non-registered protocol(s):\n"
- "{'192.0.255.7': {'default': 'Not Configured'}, '192.0.255.70': {'MGMT': 'Not Configured'}}"
+ "Peer: 192.0.255.7 VRF: default - Not found",
+ "Peer: 192.0.255.70 VRF: MGMT - Not found",
],
},
},
diff --git a/tests/units/anta_tests/test_connectivity.py b/tests/units/anta_tests/test_connectivity.py
index beeaae6..eac3084 100644
--- a/tests/units/anta_tests/test_connectivity.py
+++ b/tests/units/anta_tests/test_connectivity.py
@@ -153,7 +153,7 @@ DATA: list[dict[str, Any]] = [
],
},
],
- "expected": {"result": "failure", "messages": ["Connectivity test failed for the following source-destination pairs: [('10.0.0.5', '10.0.0.11')]"]},
+ "expected": {"result": "failure", "messages": ["Host 10.0.0.11 (src: 10.0.0.5, vrf: default, size: 100B, repeat: 2) - Unreachable"]},
},
{
"name": "failure-interface",
@@ -187,7 +187,7 @@ DATA: list[dict[str, Any]] = [
],
},
],
- "expected": {"result": "failure", "messages": ["Connectivity test failed for the following source-destination pairs: [('Management0', '10.0.0.11')]"]},
+ "expected": {"result": "failure", "messages": ["Host 10.0.0.11 (src: Management0, vrf: default, size: 100B, repeat: 2) - Unreachable"]},
},
{
"name": "failure-size",
@@ -209,17 +209,11 @@ DATA: list[dict[str, Any]] = [
],
},
],
- "expected": {"result": "failure", "messages": ["Connectivity test failed for the following source-destination pairs: [('Management0', '10.0.0.1')]"]},
+ "expected": {"result": "failure", "messages": ["Host 10.0.0.1 (src: Management0, vrf: default, size: 1501B, repeat: 5, df-bit: enabled) - Unreachable"]},
},
{
"name": "success",
"test": VerifyLLDPNeighbors,
- "inputs": {
- "neighbors": [
- {"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
- {"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
- ],
- },
"eos_data": [
{
"lldpNeighbors": {
@@ -256,16 +250,17 @@ DATA: list[dict[str, Any]] = [
},
},
],
+ "inputs": {
+ "neighbors": [
+ {"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
+ {"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
+ ],
+ },
"expected": {"result": "success"},
},
{
"name": "success-multiple-neighbors",
"test": VerifyLLDPNeighbors,
- "inputs": {
- "neighbors": [
- {"port": "Ethernet1", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
- ],
- },
"eos_data": [
{
"lldpNeighbors": {
@@ -298,17 +293,16 @@ DATA: list[dict[str, Any]] = [
},
},
],
+ "inputs": {
+ "neighbors": [
+ {"port": "Ethernet1", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
+ ],
+ },
"expected": {"result": "success"},
},
{
"name": "failure-port-not-configured",
"test": VerifyLLDPNeighbors,
- "inputs": {
- "neighbors": [
- {"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
- {"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
- ],
- },
"eos_data": [
{
"lldpNeighbors": {
@@ -330,17 +324,17 @@ DATA: list[dict[str, Any]] = [
},
},
],
- "expected": {"result": "failure", "messages": ["Port(s) not configured:\n Ethernet2"]},
- },
- {
- "name": "failure-no-neighbor",
- "test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
+ "expected": {"result": "failure", "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - Port not found"]},
+ },
+ {
+ "name": "failure-no-neighbor",
+ "test": VerifyLLDPNeighbors,
"eos_data": [
{
"lldpNeighbors": {
@@ -363,17 +357,17 @@ DATA: list[dict[str, Any]] = [
},
},
],
- "expected": {"result": "failure", "messages": ["No LLDP neighbor(s) on port(s):\n Ethernet2"]},
- },
- {
- "name": "failure-wrong-neighbor",
- "test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
+ "expected": {"result": "failure", "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - No LLDP neighbors"]},
+ },
+ {
+ "name": "failure-wrong-neighbor",
+ "test": VerifyLLDPNeighbors,
"eos_data": [
{
"lldpNeighbors": {
@@ -410,18 +404,20 @@ DATA: list[dict[str, Any]] = [
},
},
],
- "expected": {"result": "failure", "messages": ["Wrong LLDP neighbor(s) on port(s):\n Ethernet2\n DC1-SPINE2_Ethernet2"]},
- },
- {
- "name": "failure-multiple",
- "test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
- {"port": "Ethernet3", "neighbor_device": "DC1-SPINE3", "neighbor_port": "Ethernet1"},
],
},
+ "expected": {
+ "result": "failure",
+ "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE2/Ethernet2"],
+ },
+ },
+ {
+ "name": "failure-multiple",
+ "test": VerifyLLDPNeighbors,
"eos_data": [
{
"lldpNeighbors": {
@@ -444,23 +440,25 @@ DATA: list[dict[str, Any]] = [
},
},
],
+ "inputs": {
+ "neighbors": [
+ {"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
+ {"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
+ {"port": "Ethernet3", "neighbor_device": "DC1-SPINE3", "neighbor_port": "Ethernet1"},
+ ],
+ },
"expected": {
"result": "failure",
"messages": [
- "Wrong LLDP neighbor(s) on port(s):\n Ethernet1\n DC1-SPINE1_Ethernet2\n"
- "No LLDP neighbor(s) on port(s):\n Ethernet2\n"
- "Port(s) not configured:\n Ethernet3"
+ "Port Ethernet1 (Neighbor: DC1-SPINE1, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE1/Ethernet2",
+ "Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - No LLDP neighbors",
+ "Port Ethernet3 (Neighbor: DC1-SPINE3, Neighbor Port: Ethernet1) - Port not found",
],
},
},
{
"name": "failure-multiple-neighbors",
"test": VerifyLLDPNeighbors,
- "inputs": {
- "neighbors": [
- {"port": "Ethernet1", "neighbor_device": "DC1-SPINE3", "neighbor_port": "Ethernet1"},
- ],
- },
"eos_data": [
{
"lldpNeighbors": {
@@ -493,6 +491,14 @@ DATA: list[dict[str, Any]] = [
},
},
],
- "expected": {"result": "failure", "messages": ["Wrong LLDP neighbor(s) on port(s):\n Ethernet1\n DC1-SPINE1_Ethernet1\n DC1-SPINE2_Ethernet1"]},
+ "inputs": {
+ "neighbors": [
+ {"port": "Ethernet1", "neighbor_device": "DC1-SPINE3", "neighbor_port": "Ethernet1"},
+ ],
+ },
+ "expected": {
+ "result": "failure",
+ "messages": ["Port Ethernet1 (Neighbor: DC1-SPINE3, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE1/Ethernet1, DC1-SPINE2/Ethernet1"],
+ },
},
]
diff --git a/tests/units/anta_tests/test_cvx.py b/tests/units/anta_tests/test_cvx.py
new file mode 100644
index 0000000..46d83b0
--- /dev/null
+++ b/tests/units/anta_tests/test_cvx.py
@@ -0,0 +1,525 @@
+# 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.
+"""Data for testing anta.tests.cvx."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from anta.tests.cvx import VerifyActiveCVXConnections, VerifyCVXClusterStatus, VerifyManagementCVX, VerifyMcsClientMounts, VerifyMcsServerMounts
+from tests.units.anta_tests import test
+
+DATA: list[dict[str, Any]] = [
+ {
+ "name": "success",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [{"mountStates": [{"path": "mcs/v1/toSwitch/28-99-3a-8f-93-7b", "type": "Mcs::DeviceConfigV1", "state": "mountStateMountComplete"}]}],
+ "inputs": None,
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-haclient",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [
+ {
+ "mountStates": [
+ {"path": "mcs/v1/apiCfgRedState", "type": "Mcs::ApiConfigRedundancyState", "state": "mountStateMountComplete"},
+ {"path": "mcs/v1/toSwitch/00-1c-73-74-c0-8b", "type": "Mcs::DeviceConfigV1", "state": "mountStateMountComplete"},
+ ]
+ },
+ ],
+ "inputs": None,
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-partial-non-mcs",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [
+ {
+ "mountStates": [
+ {"path": "blah/blah/blah", "type": "blah::blah", "state": "mountStatePreservedUnmounted"},
+ {"path": "mcs/v1/toSwitch/00-1c-73-74-c0-8b", "type": "Mcs::DeviceConfigV1", "state": "mountStateMountComplete"},
+ ]
+ },
+ ],
+ "inputs": None,
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-nomounts",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [
+ {"mountStates": []},
+ ],
+ "inputs": None,
+ "expected": {"result": "failure", "messages": ["MCS Client mount states are not present"]},
+ },
+ {
+ "name": "failure-mountStatePreservedUnmounted",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [{"mountStates": [{"path": "mcs/v1/toSwitch/28-99-3a-8f-93-7b", "type": "Mcs::DeviceConfigV1", "state": "mountStatePreservedUnmounted"}]}],
+ "inputs": None,
+ "expected": {"result": "failure", "messages": ["MCS Client mount states are not valid: mountStatePreservedUnmounted"]},
+ },
+ {
+ "name": "failure-partial-haclient",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [
+ {
+ "mountStates": [
+ {"path": "mcs/v1/apiCfgRedState", "type": "Mcs::ApiConfigRedundancyState", "state": "mountStateMountComplete"},
+ {"path": "mcs/v1/toSwitch/00-1c-73-74-c0-8b", "type": "Mcs::DeviceConfigV1", "state": "mountStatePreservedUnmounted"},
+ ]
+ },
+ ],
+ "inputs": None,
+ "expected": {"result": "failure", "messages": ["MCS Client mount states are not valid: mountStatePreservedUnmounted"]},
+ },
+ {
+ "name": "failure-full-haclient",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [
+ {
+ "mountStates": [
+ {"path": "blah/blah/blah", "type": "blah::blahState", "state": "mountStatePreservedUnmounted"},
+ {"path": "mcs/v1/toSwitch/00-1c-73-74-c0-8b", "type": "Mcs::DeviceConfigV1", "state": "mountStatePreservedUnmounted"},
+ ]
+ },
+ ],
+ "inputs": None,
+ "expected": {"result": "failure", "messages": ["MCS Client mount states are not valid: mountStatePreservedUnmounted"]},
+ },
+ {
+ "name": "failure-non-mcs-client",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [
+ {"mountStates": [{"path": "blah/blah/blah", "type": "blah::blahState", "state": "mountStatePreservedUnmounted"}]},
+ ],
+ "inputs": None,
+ "expected": {"result": "failure", "messages": ["MCS Client mount states are not present"]},
+ },
+ {
+ "name": "failure-partial-mcs-client",
+ "test": VerifyMcsClientMounts,
+ "eos_data": [
+ {
+ "mountStates": [
+ {"path": "blah/blah/blah", "type": "blah::blahState", "state": "mountStatePreservedUnmounted"},
+ {"path": "blah/blah/blah", "type": "Mcs::DeviceConfigV1", "state": "mountStatePreservedUnmounted"},
+ ]
+ },
+ ],
+ "inputs": None,
+ "expected": {"result": "failure", "messages": ["MCS Client mount states are not valid: mountStatePreservedUnmounted"]},
+ },
+ {
+ "name": "success-enabled",
+ "test": VerifyManagementCVX,
+ "eos_data": [
+ {
+ "clusterStatus": {
+ "enabled": True,
+ }
+ }
+ ],
+ "inputs": {"enabled": True},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-disabled",
+ "test": VerifyManagementCVX,
+ "eos_data": [
+ {
+ "clusterStatus": {
+ "enabled": False,
+ }
+ }
+ ],
+ "inputs": {"enabled": False},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure - no enabled state",
+ "test": VerifyManagementCVX,
+ "eos_data": [{"clusterStatus": {}}],
+ "inputs": {"enabled": False},
+ "expected": {"result": "failure", "messages": ["Management CVX status is not valid: None"]},
+ },
+ {
+ "name": "failure - no clusterStatus",
+ "test": VerifyManagementCVX,
+ "eos_data": [{}],
+ "inputs": {"enabled": False},
+ "expected": {"result": "failure", "messages": ["Management CVX status is not valid: None"]},
+ },
+ {
+ "name": "success",
+ "test": VerifyMcsServerMounts,
+ "eos_data": [
+ {
+ "connections": [
+ {
+ "hostname": "media-leaf-1",
+ "mounts": [
+ {
+ "service": "Mcs",
+ "mountStates": [
+ {
+ "pathStates": [
+ {"path": "mcs/v1/apiCfgRedStatus", "type": "Mcs::ApiConfigRedundancyStatus", "state": "mountStateMountComplete"},
+ {"path": "mcs/v1/activeflows", "type": "Mcs::ActiveFlows", "state": "mountStateMountComplete"},
+ {"path": "mcs/switch/status", "type": "Mcs::Client::Status", "state": "mountStateMountComplete"},
+ ]
+ }
+ ],
+ }
+ ],
+ }
+ ]
+ }
+ ],
+ "inputs": {"connections_count": 1},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-no-mounts",
+ "test": VerifyMcsServerMounts,
+ "eos_data": [{"connections": [{"hostname": "media-leaf-1", "mounts": []}]}],
+ "inputs": {"connections_count": 1},
+ "expected": {
+ "result": "failure",
+ "messages": ["No mount status for media-leaf-1", "Incorrect CVX successful connections count. Expected: 1, Actual : 0"],
+ },
+ },
+ {
+ "name": "failure-unexpected-number-paths",
+ "test": VerifyMcsServerMounts,
+ "eos_data": [
+ {
+ "connections": [
+ {
+ "hostname": "media-leaf-1",
+ "mounts": [
+ {
+ "service": "Mcs",
+ "mountStates": [
+ {
+ "pathStates": [
+ {"path": "mcs/v1/apiCfgRedStatus", "type": "Mcs::ApiStatus", "state": "mountStateMountComplete"},
+ {"path": "mcs/v1/activeflows", "type": "Mcs::ActiveFlows", "state": "mountStateMountComplete"},
+ ]
+ }
+ ],
+ }
+ ],
+ }
+ ]
+ }
+ ],
+ "inputs": {"connections_count": 1},
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "Incorrect number of mount path states for media-leaf-1 - Expected: 3, Actual: 2",
+ "Unexpected MCS path type for media-leaf-1: 'Mcs::ApiStatus'.",
+ ],
+ },
+ },
+ {
+ "name": "failure-unexpected-path-type",
+ "test": VerifyMcsServerMounts,
+ "eos_data": [
+ {
+ "connections": [
+ {
+ "hostname": "media-leaf-1",
+ "mounts": [
+ {
+ "service": "Mcs",
+ "mountStates": [
+ {
+ "pathStates": [
+ {"path": "mcs/v1/apiCfgRedStatus", "type": "Mcs::ApiStatus", "state": "mountStateMountComplete"},
+ {"path": "mcs/v1/activeflows", "type": "Mcs::ActiveFlows", "state": "mountStateMountComplete"},
+ {"path": "mcs/switch/status", "type": "Mcs::Client::Status", "state": "mountStateMountComplete"},
+ ]
+ }
+ ],
+ }
+ ],
+ }
+ ]
+ }
+ ],
+ "inputs": {"connections_count": 1},
+ "expected": {"result": "failure", "messages": ["Unexpected MCS path type for media-leaf-1: 'Mcs::ApiStatus'"]},
+ },
+ {
+ "name": "failure-invalid-mount-state",
+ "test": VerifyMcsServerMounts,
+ "eos_data": [
+ {
+ "connections": [
+ {
+ "hostname": "media-leaf-1",
+ "mounts": [
+ {
+ "service": "Mcs",
+ "mountStates": [
+ {
+ "pathStates": [
+ {"path": "mcs/v1/apiCfgRedStatus", "type": "Mcs::ApiConfigRedundancyStatus", "state": "mountStateMountFailed"},
+ {"path": "mcs/v1/activeflows", "type": "Mcs::ActiveFlows", "state": "mountStateMountComplete"},
+ {"path": "mcs/switch/status", "type": "Mcs::Client::Status", "state": "mountStateMountComplete"},
+ ]
+ }
+ ],
+ }
+ ],
+ }
+ ]
+ }
+ ],
+ "inputs": {"connections_count": 1},
+ "expected": {
+ "result": "failure",
+ "messages": ["MCS server mount state for path 'Mcs::ApiConfigRedundancyStatus' is not valid is for media-leaf-1: 'mountStateMountFailed'"],
+ },
+ },
+ {
+ "name": "failure-no-mcs-mount",
+ "test": VerifyMcsServerMounts,
+ "eos_data": [
+ {
+ "connections": [
+ {
+ "hostname": "media-leaf-1",
+ "mounts": [
+ {
+ "service": "blah-blah",
+ "mountStates": [{"pathStates": [{"path": "blah-blah-path", "type": "blah-blah-type", "state": "blah-blah-state"}]}],
+ }
+ ],
+ }
+ ]
+ }
+ ],
+ "inputs": {"connections_count": 1},
+ "expected": {"result": "failure", "messages": ["MCS mount state not detected", "Incorrect CVX successful connections count. Expected: 1, Actual : 0"]},
+ },
+ {
+ "name": "failure-connections",
+ "test": VerifyMcsServerMounts,
+ "eos_data": [{}],
+ "inputs": {"connections_count": 1},
+ "expected": {"result": "failure", "messages": ["CVX connections are not available."]},
+ },
+ {
+ "name": "success",
+ "test": VerifyActiveCVXConnections,
+ "eos_data": [
+ {
+ "connections": [
+ {
+ "switchId": "fc:bd:67:c3:16:55",
+ "hostname": "lyv563",
+ "oobConnectionActive": True,
+ },
+ {
+ "switchId": "00:1c:73:3c:e3:9e",
+ "hostname": "tg264",
+ "oobConnectionActive": True,
+ },
+ ]
+ }
+ ],
+ "inputs": {"connections_count": 2},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure",
+ "test": VerifyActiveCVXConnections,
+ "eos_data": [
+ {
+ "connections": [
+ {
+ "switchId": "fc:bd:67:c3:16:55",
+ "hostname": "lyv563",
+ "oobConnectionActive": False,
+ },
+ {
+ "switchId": "00:1c:73:3c:e3:9e",
+ "hostname": "tg264",
+ "oobConnectionActive": True,
+ },
+ ]
+ }
+ ],
+ "inputs": {"connections_count": 2},
+ "expected": {"result": "failure", "messages": ["CVX active connections count. Expected: 2, Actual : 1"]},
+ },
+ {
+ "name": "failure-no-connections",
+ "test": VerifyActiveCVXConnections,
+ "eos_data": [{}],
+ "inputs": {"connections_count": 2},
+ "expected": {"result": "failure", "messages": ["CVX connections are not available"]},
+ },
+ {
+ "name": "success-all",
+ "test": VerifyCVXClusterStatus,
+ "eos_data": [
+ {
+ "enabled": True,
+ "clusterMode": True,
+ "clusterStatus": {
+ "role": "Master",
+ "peerStatus": {
+ "cvx-red-2": {"peerName": "cvx-red-2", "registrationState": "Registration complete"},
+ "cvx-red-3": {"peerName": "cvx-red-3", "registrationState": "Registration complete"},
+ },
+ },
+ }
+ ],
+ "inputs": {
+ "role": "Master",
+ "peer_status": [
+ {"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
+ {"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
+ ],
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-invalid-role",
+ "test": VerifyCVXClusterStatus,
+ "eos_data": [
+ {
+ "enabled": True,
+ "clusterMode": True,
+ "clusterStatus": {
+ "role": "Standby",
+ "peerStatus": {
+ "cvx-red-2": {"peerName": "cvx-red-2", "registrationState": "Registration complete"},
+ "cvx-red-3": {"peerName": "cvx-red-3", "registrationState": "Registration complete"},
+ },
+ },
+ }
+ ],
+ "inputs": {
+ "role": "Master",
+ "peer_status": [
+ {"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
+ {"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
+ ],
+ },
+ "expected": {"result": "failure", "messages": ["CVX Role is not valid: Standby"]},
+ },
+ {
+ "name": "failure-cvx-enabled",
+ "test": VerifyCVXClusterStatus,
+ "eos_data": [
+ {
+ "enabled": False,
+ "clusterMode": True,
+ "clusterStatus": {
+ "role": "Master",
+ "peerStatus": {},
+ },
+ }
+ ],
+ "inputs": {
+ "role": "Master",
+ "peer_status": [],
+ },
+ "expected": {"result": "failure", "messages": ["CVX Server status is not enabled"]},
+ },
+ {
+ "name": "failure-cluster-enabled",
+ "test": VerifyCVXClusterStatus,
+ "eos_data": [
+ {
+ "enabled": True,
+ "clusterMode": False,
+ "clusterStatus": {},
+ }
+ ],
+ "inputs": {
+ "role": "Master",
+ "peer_status": [],
+ },
+ "expected": {"result": "failure", "messages": ["CVX Server is not a cluster"]},
+ },
+ {
+ "name": "failure-missing-peers",
+ "test": VerifyCVXClusterStatus,
+ "eos_data": [
+ {
+ "enabled": True,
+ "clusterMode": True,
+ "clusterStatus": {
+ "role": "Master",
+ "peerStatus": {
+ "cvx-red-2": {"peerName": "cvx-red-2", "registrationState": "Registration complete"},
+ },
+ },
+ }
+ ],
+ "inputs": {
+ "role": "Master",
+ "peer_status": [
+ {"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
+ {"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
+ ],
+ },
+ "expected": {"result": "failure", "messages": ["Unexpected number of peers 1 vs 2", "cvx-red-3 is not present"]},
+ },
+ {
+ "name": "failure-invalid-peers",
+ "test": VerifyCVXClusterStatus,
+ "eos_data": [
+ {
+ "enabled": True,
+ "clusterMode": True,
+ "clusterStatus": {
+ "role": "Master",
+ "peerStatus": {},
+ },
+ }
+ ],
+ "inputs": {
+ "role": "Master",
+ "peer_status": [
+ {"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
+ {"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
+ ],
+ },
+ "expected": {"result": "failure", "messages": ["Unexpected number of peers 0 vs 2", "cvx-red-2 is not present", "cvx-red-3 is not present"]},
+ },
+ {
+ "name": "failure-registration-error",
+ "test": VerifyCVXClusterStatus,
+ "eos_data": [
+ {
+ "enabled": True,
+ "clusterMode": True,
+ "clusterStatus": {
+ "role": "Master",
+ "peerStatus": {
+ "cvx-red-2": {"peerName": "cvx-red-2", "registrationState": "Registration error"},
+ "cvx-red-3": {"peerName": "cvx-red-3", "registrationState": "Registration complete"},
+ },
+ },
+ }
+ ],
+ "inputs": {
+ "role": "Master",
+ "peer_status": [
+ {"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
+ {"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
+ ],
+ },
+ "expected": {"result": "failure", "messages": ["cvx-red-2 registration state is not complete: Registration error"]},
+ },
+]
diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py
index ea8106e..f3b4ee0 100644
--- a/tests/units/anta_tests/test_interfaces.py
+++ b/tests/units/anta_tests/test_interfaces.py
@@ -1108,7 +1108,7 @@ DATA: list[dict[str, Any]] = [
"inputs": {"interfaces": [{"name": "Ethernet2", "status": "up"}, {"name": "Ethernet8", "status": "up"}, {"name": "Ethernet3", "status": "up"}]},
"expected": {
"result": "failure",
- "messages": ["The following interface(s) are not configured: ['Ethernet8']"],
+ "messages": ["Ethernet8 - Not configured"],
},
},
{
@@ -1126,7 +1126,7 @@ DATA: list[dict[str, Any]] = [
"inputs": {"interfaces": [{"name": "Ethernet2", "status": "up"}, {"name": "Ethernet8", "status": "up"}, {"name": "Ethernet3", "status": "up"}]},
"expected": {
"result": "failure",
- "messages": ["The following interface(s) are not in the expected state: ['Ethernet8 is down/down'"],
+ "messages": ["Ethernet8 - Expected: up/up, Actual: down/down"],
},
},
{
@@ -1150,7 +1150,7 @@ DATA: list[dict[str, Any]] = [
},
"expected": {
"result": "failure",
- "messages": ["The following interface(s) are not in the expected state: ['Ethernet8 is up/down'"],
+ "messages": ["Ethernet8 - Expected: up/up, Actual: up/down"],
},
},
{
@@ -1166,7 +1166,7 @@ DATA: list[dict[str, Any]] = [
"inputs": {"interfaces": [{"name": "PortChannel100", "status": "up"}]},
"expected": {
"result": "failure",
- "messages": ["The following interface(s) are not in the expected state: ['Port-Channel100 is down/lowerLayerDown'"],
+ "messages": ["Port-Channel100 - Expected: up/up, Actual: down/lowerLayerDown"],
},
},
{
@@ -1190,7 +1190,38 @@ DATA: list[dict[str, Any]] = [
},
"expected": {
"result": "failure",
- "messages": ["The following interface(s) are not in the expected state: ['Ethernet2 is up/unknown'"],
+ "messages": [
+ "Ethernet2 - Expected: up/down, Actual: up/unknown",
+ "Ethernet8 - Expected: up/up, Actual: up/down",
+ ],
+ },
+ },
+ {
+ "name": "failure-interface-status-down",
+ "test": VerifyInterfacesStatus,
+ "eos_data": [
+ {
+ "interfaceDescriptions": {
+ "Ethernet8": {"interfaceStatus": "up", "description": "", "lineProtocolStatus": "down"},
+ "Ethernet2": {"interfaceStatus": "up", "description": "", "lineProtocolStatus": "unknown"},
+ "Ethernet3": {"interfaceStatus": "up", "description": "", "lineProtocolStatus": "up"},
+ }
+ }
+ ],
+ "inputs": {
+ "interfaces": [
+ {"name": "Ethernet2", "status": "down"},
+ {"name": "Ethernet8", "status": "down"},
+ {"name": "Ethernet3", "status": "down"},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "Ethernet2 - Expected: down, Actual: up",
+ "Ethernet8 - Expected: down, Actual: up",
+ "Ethernet3 - Expected: down, Actual: up",
+ ],
},
},
{
@@ -1938,8 +1969,8 @@ DATA: list[dict[str, Any]] = [
"interfaces": {
"Ethernet2": {
"interfaceAddress": {
- "primaryIp": {"address": "172.30.11.0", "maskLen": 31},
- "secondaryIpsOrderedList": [{"address": "10.10.10.0", "maskLen": 31}, {"address": "10.10.10.10", "maskLen": 31}],
+ "primaryIp": {"address": "172.30.11.1", "maskLen": 31},
+ "secondaryIpsOrderedList": [{"address": "10.10.10.1", "maskLen": 31}, {"address": "10.10.10.10", "maskLen": 31}],
}
}
}
@@ -1957,7 +1988,7 @@ DATA: list[dict[str, Any]] = [
],
"inputs": {
"interfaces": [
- {"name": "Ethernet2", "primary_ip": "172.30.11.0/31", "secondary_ips": ["10.10.10.0/31", "10.10.10.10/31"]},
+ {"name": "Ethernet2", "primary_ip": "172.30.11.1/31", "secondary_ips": ["10.10.10.1/31", "10.10.10.10/31"]},
{"name": "Ethernet12", "primary_ip": "172.30.11.10/31", "secondary_ips": ["10.10.10.10/31", "10.10.10.20/31"]},
]
},
@@ -2480,6 +2511,43 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "success"},
},
{
+ "name": "success-short-timeout",
+ "test": VerifyLACPInterfacesStatus,
+ "eos_data": [
+ {
+ "portChannels": {
+ "Port-Channel5": {
+ "interfaces": {
+ "Ethernet5": {
+ "actorPortStatus": "bundled",
+ "partnerPortState": {
+ "activity": True,
+ "timeout": True,
+ "aggregation": True,
+ "synchronization": True,
+ "collecting": True,
+ "distributing": True,
+ },
+ "actorPortState": {
+ "activity": True,
+ "timeout": True,
+ "aggregation": True,
+ "synchronization": True,
+ "collecting": True,
+ "distributing": True,
+ },
+ }
+ }
+ }
+ },
+ "interface": "Ethernet5",
+ "orphanPorts": {},
+ }
+ ],
+ "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Port-Channel5", "lacp_rate_fast": True}]},
+ "expected": {"result": "success"},
+ },
+ {
"name": "failure-not-bundled",
"test": VerifyLACPInterfacesStatus,
"eos_data": [
@@ -2500,7 +2568,7 @@ DATA: list[dict[str, Any]] = [
"inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Po5"}]},
"expected": {
"result": "failure",
- "messages": ["For Interface Ethernet5:\nExpected `bundled` as the local port status, but found `No Aggregate` instead.\n"],
+ "messages": ["Interface: Ethernet5 Port-Channel: Port-Channel5 - Not bundled - Port Status: No Aggregate"],
},
},
{
@@ -2514,7 +2582,7 @@ DATA: list[dict[str, Any]] = [
"inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Po 5"}]},
"expected": {
"result": "failure",
- "messages": ["Interface 'Ethernet5' is not configured to be a member of LACP 'Port-Channel5'."],
+ "messages": ["Interface: Ethernet5 Port-Channel: Port-Channel5 - Not configured"],
},
},
{
@@ -2555,13 +2623,55 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "For Interface Ethernet5:\n"
- "Actor port details:\nExpected `True` as the activity, but found `False` instead."
- "\nExpected `True` as the aggregation, but found `False` instead."
- "\nExpected `True` as the synchronization, but found `False` instead."
- "\nPartner port details:\nExpected `True` as the activity, but found `False` instead.\n"
- "Expected `True` as the aggregation, but found `False` instead.\n"
- "Expected `True` as the synchronization, but found `False` instead.\n"
+ "Interface: Ethernet5 Port-Channel: Port-Channel5 - Actor port details mismatch - Activity: False, Aggregation: False, "
+ "Synchronization: False, Collecting: True, Distributing: True, Timeout: False",
+ "Interface: Ethernet5 Port-Channel: Port-Channel5 - Partner port details mismatch - Activity: False, Aggregation: False, "
+ "Synchronization: False, Collecting: True, Distributing: True, Timeout: False",
+ ],
+ },
+ },
+ {
+ "name": "failure-short-timeout",
+ "test": VerifyLACPInterfacesStatus,
+ "eos_data": [
+ {
+ "portChannels": {
+ "Port-Channel5": {
+ "interfaces": {
+ "Ethernet5": {
+ "actorPortStatus": "bundled",
+ "partnerPortState": {
+ "activity": True,
+ "timeout": False,
+ "aggregation": True,
+ "synchronization": True,
+ "collecting": True,
+ "distributing": True,
+ },
+ "actorPortState": {
+ "activity": True,
+ "timeout": False,
+ "aggregation": True,
+ "synchronization": True,
+ "collecting": True,
+ "distributing": True,
+ },
+ }
+ }
+ }
+ },
+ "interface": "Ethernet5",
+ "orphanPorts": {},
+ }
+ ],
+ "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "port-channel 5", "lacp_rate_fast": True}]},
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "Interface: Ethernet5 Port-Channel: Port-Channel5 - Actor port details mismatch - Activity: True, Aggregation: True, "
+ "Synchronization: True, Collecting: True, Distributing: True, Timeout: False",
+ "Interface: Ethernet5 Port-Channel: Port-Channel5 - Partner port details mismatch - Activity: True, Aggregation: True, "
+ "Synchronization: True, Collecting: True, Distributing: True, Timeout: False",
],
},
},
diff --git a/tests/units/anta_tests/test_security.py b/tests/units/anta_tests/test_security.py
index 0d4a478..472eb7e 100644
--- a/tests/units/anta_tests/test_security.py
+++ b/tests/units/anta_tests/test_security.py
@@ -1079,7 +1079,7 @@ DATA: list[dict[str, Any]] = [
},
]
},
- "expected": {"result": "failure", "messages": ["No IPv4 security connection configured for peer `10.255.0.1`."]},
+ "expected": {"result": "failure", "messages": ["Peer: 10.255.0.1 VRF: default - Not configured"]},
},
{
"name": "failure-not-established",
@@ -1127,14 +1127,10 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Expected state of IPv4 security connection `source:172.18.3.2 destination:172.18.2.2 vrf:default` for peer `10.255.0.1` is `Established` "
- "but found `Idle` instead.",
- "Expected state of IPv4 security connection `source:100.64.2.2 destination:100.64.1.2 vrf:default` for peer `10.255.0.1` is `Established` "
- "but found `Idle` instead.",
- "Expected state of IPv4 security connection `source:100.64.2.2 destination:100.64.1.2 vrf:MGMT` for peer `10.255.0.2` is `Established` "
- "but found `Idle` instead.",
- "Expected state of IPv4 security connection `source:172.18.2.2 destination:172.18.1.2 vrf:MGMT` for peer `10.255.0.2` is `Established` "
- "but found `Idle` instead.",
+ "Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established, Actual: Idle",
+ "Peer: 10.255.0.1 VRF: default Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established, Actual: Idle",
+ "Peer: 10.255.0.2 VRF: MGMT Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established, Actual: Idle",
+ "Peer: 10.255.0.2 VRF: MGMT Source: 172.18.2.2 Destination: 172.18.1.2 - Connection down - Expected: Established, Actual: Idle",
],
},
},
@@ -1194,12 +1190,10 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "Expected state of IPv4 security connection `source:172.18.3.2 destination:172.18.2.2 vrf:default` for peer `10.255.0.1` is `Established` "
- "but found `Idle` instead.",
- "Expected state of IPv4 security connection `source:100.64.3.2 destination:100.64.2.2 vrf:default` for peer `10.255.0.1` is `Established` "
- "but found `Idle` instead.",
- "IPv4 security connection `source:100.64.4.2 destination:100.64.1.2 vrf:default` for peer `10.255.0.2` is not found.",
- "IPv4 security connection `source:172.18.4.2 destination:172.18.1.2 vrf:default` for peer `10.255.0.2` is not found.",
+ "Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established, Actual: Idle",
+ "Peer: 10.255.0.1 VRF: default Source: 100.64.3.2 Destination: 100.64.2.2 - Connection down - Expected: Established, Actual: Idle",
+ "Peer: 10.255.0.2 VRF: default Source: 100.64.4.2 Destination: 100.64.1.2 - Connection not found.",
+ "Peer: 10.255.0.2 VRF: default Source: 172.18.4.2 Destination: 172.18.1.2 - Connection not found.",
],
},
},
diff --git a/tests/units/anta_tests/test_services.py b/tests/units/anta_tests/test_services.py
index 3f13dfc..639c5c6 100644
--- a/tests/units/anta_tests/test_services.py
+++ b/tests/units/anta_tests/test_services.py
@@ -59,31 +59,23 @@ DATA: list[dict[str, Any]] = [
"test": VerifyDNSServers,
"eos_data": [
{
- "nameServerConfigs": [{"ipAddr": "10.14.0.1", "vrf": "default", "priority": 0}, {"ipAddr": "10.14.0.11", "vrf": "MGMT", "priority": 1}],
+ "nameServerConfigs": [
+ {"ipAddr": "10.14.0.1", "vrf": "default", "priority": 0},
+ {"ipAddr": "10.14.0.11", "vrf": "MGMT", "priority": 1},
+ {"ipAddr": "fd12:3456:789a::1", "vrf": "default", "priority": 0},
+ ],
}
],
"inputs": {
- "dns_servers": [{"server_address": "10.14.0.1", "vrf": "default", "priority": 0}, {"server_address": "10.14.0.11", "vrf": "MGMT", "priority": 1}]
+ "dns_servers": [
+ {"server_address": "10.14.0.1", "vrf": "default", "priority": 0},
+ {"server_address": "10.14.0.11", "vrf": "MGMT", "priority": 1},
+ {"server_address": "fd12:3456:789a::1", "vrf": "default", "priority": 0},
+ ]
},
"expected": {"result": "success"},
},
{
- "name": "failure-dns-missing",
- "test": VerifyDNSServers,
- "eos_data": [
- {
- "nameServerConfigs": [{"ipAddr": "10.14.0.1", "vrf": "default", "priority": 0}, {"ipAddr": "10.14.0.11", "vrf": "MGMT", "priority": 1}],
- }
- ],
- "inputs": {
- "dns_servers": [{"server_address": "10.14.0.10", "vrf": "default", "priority": 0}, {"server_address": "10.14.0.21", "vrf": "MGMT", "priority": 1}]
- },
- "expected": {
- "result": "failure",
- "messages": ["DNS server `10.14.0.10` is not configured with any VRF.", "DNS server `10.14.0.21` is not configured with any VRF."],
- },
- },
- {
"name": "failure-no-dns-found",
"test": VerifyDNSServers,
"eos_data": [
@@ -96,7 +88,7 @@ DATA: list[dict[str, Any]] = [
},
"expected": {
"result": "failure",
- "messages": ["DNS server `10.14.0.10` is not configured with any VRF.", "DNS server `10.14.0.21` is not configured with any VRF."],
+ "messages": ["Server 10.14.0.10 (VRF: default, Priority: 0) - Not configured", "Server 10.14.0.21 (VRF: MGMT, Priority: 1) - Not configured"],
},
},
{
@@ -117,9 +109,9 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "For DNS server `10.14.0.1`, the expected priority is `0`, but `1` was found instead.",
- "DNS server `10.14.0.11` is not configured with VRF `default`.",
- "DNS server `10.14.0.110` is not configured with any VRF.",
+ "Server 10.14.0.1 (VRF: CS, Priority: 0) - Incorrect priority - Priority: 1",
+ "Server 10.14.0.11 (VRF: default, Priority: 0) - Not configured",
+ "Server 10.14.0.110 (VRF: MGMT, Priority: 0) - Not configured",
],
},
},
diff --git a/tests/units/anta_tests/test_stun.py b/tests/units/anta_tests/test_stun.py
index 005ae35..2383483 100644
--- a/tests/units/anta_tests/test_stun.py
+++ b/tests/units/anta_tests/test_stun.py
@@ -7,13 +7,13 @@ from __future__ import annotations
from typing import Any
-from anta.tests.stun import VerifyStunClient, VerifyStunServer
+from anta.tests.stun import VerifyStunClientTranslation, VerifyStunServer
from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
"name": "success",
- "test": VerifyStunClient,
+ "test": VerifyStunClientTranslation,
"eos_data": [
{
"bindings": {
@@ -60,7 +60,7 @@ DATA: list[dict[str, Any]] = [
},
{
"name": "failure-incorrect-public-ip",
- "test": VerifyStunClient,
+ "test": VerifyStunClientTranslation,
"eos_data": [
{
"bindings": {
@@ -88,14 +88,14 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "For STUN source `100.64.3.2:4500`:\nExpected `192.164.3.2` as the public ip, but found `192.64.3.2` instead.",
- "For STUN source `172.18.3.2:4500`:\nExpected `192.118.3.2` as the public ip, but found `192.18.3.2` instead.",
+ "Client 100.64.3.2 Port: 4500 - Incorrect public-facing address - Expected: 192.164.3.2 Actual: 192.64.3.2",
+ "Client 172.18.3.2 Port: 4500 - Incorrect public-facing address - Expected: 192.118.3.2 Actual: 192.18.3.2",
],
},
},
{
"name": "failure-no-client",
- "test": VerifyStunClient,
+ "test": VerifyStunClientTranslation,
"eos_data": [
{"bindings": {}},
{"bindings": {}},
@@ -108,12 +108,12 @@ DATA: list[dict[str, Any]] = [
},
"expected": {
"result": "failure",
- "messages": ["STUN client transaction for source `100.64.3.2:4500` is not found.", "STUN client transaction for source `172.18.3.2:4500` is not found."],
+ "messages": ["Client 100.64.3.2 Port: 4500 - STUN client translation not found.", "Client 172.18.3.2 Port: 4500 - STUN client translation not found."],
},
},
{
"name": "failure-incorrect-public-port",
- "test": VerifyStunClient,
+ "test": VerifyStunClientTranslation,
"eos_data": [
{"bindings": {}},
{
@@ -134,16 +134,15 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "STUN client transaction for source `100.64.3.2:4500` is not found.",
- "For STUN source `172.18.3.2:4500`:\n"
- "Expected `192.118.3.2` as the public ip, but found `192.18.3.2` instead.\n"
- "Expected `6006` as the public port, but found `4800` instead.",
+ "Client 100.64.3.2 Port: 4500 - STUN client translation not found.",
+ "Client 172.18.3.2 Port: 4500 - Incorrect public-facing address - Expected: 192.118.3.2 Actual: 192.18.3.2",
+ "Client 172.18.3.2 Port: 4500 - Incorrect public-facing port - Expected: 6006 Actual: 4800",
],
},
},
{
"name": "failure-all-type",
- "test": VerifyStunClient,
+ "test": VerifyStunClientTranslation,
"eos_data": [
{"bindings": {}},
{
@@ -164,12 +163,9 @@ DATA: list[dict[str, Any]] = [
"expected": {
"result": "failure",
"messages": [
- "STUN client transaction for source `100.64.3.2:4500` is not found.",
- "For STUN source `172.18.4.2:4800`:\n"
- "Expected `172.18.4.2` as the source ip, but found `172.18.3.2` instead.\n"
- "Expected `4800` as the source port, but found `4500` instead.\n"
- "Expected `192.118.3.2` as the public ip, but found `192.18.3.2` instead.\n"
- "Expected `6006` as the public port, but found `4800` instead.",
+ "Client 100.64.3.2 Port: 4500 - STUN client translation not found.",
+ "Client 172.18.4.2 Port: 4800 - Incorrect public-facing address - Expected: 192.118.3.2 Actual: 192.18.3.2",
+ "Client 172.18.4.2 Port: 4800 - Incorrect public-facing port - Expected: 6006 Actual: 4800",
],
},
},
diff --git a/tests/units/anta_tests/test_system.py b/tests/units/anta_tests/test_system.py
index 1eda8a1..f610a8e 100644
--- a/tests/units/anta_tests/test_system.py
+++ b/tests/units/anta_tests/test_system.py
@@ -347,6 +347,39 @@ poll interval unknown
"expected": {"result": "success"},
},
{
+ "name": "success-ip-dns",
+ "test": VerifyNTPAssociations,
+ "eos_data": [
+ {
+ "peers": {
+ "1.1.1.1 (1.ntp.networks.com)": {
+ "condition": "sys.peer",
+ "peerIpAddr": "1.1.1.1",
+ "stratumLevel": 1,
+ },
+ "2.2.2.2 (2.ntp.networks.com)": {
+ "condition": "candidate",
+ "peerIpAddr": "2.2.2.2",
+ "stratumLevel": 2,
+ },
+ "3.3.3.3 (3.ntp.networks.com)": {
+ "condition": "candidate",
+ "peerIpAddr": "3.3.3.3",
+ "stratumLevel": 2,
+ },
+ }
+ }
+ ],
+ "inputs": {
+ "ntp_servers": [
+ {"server_address": "1.1.1.1", "preferred": True, "stratum": 1},
+ {"server_address": "2.2.2.2", "stratum": 2},
+ {"server_address": "3.3.3.3", "stratum": 2},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
"name": "failure",
"test": VerifyNTPAssociations,
"eos_data": [
@@ -380,9 +413,9 @@ poll interval unknown
"expected": {
"result": "failure",
"messages": [
- "For NTP peer 1.1.1.1:\nExpected `sys.peer` as the condition, but found `candidate` instead.\nExpected `1` as the stratum, but found `2` instead.\n"
- "For NTP peer 2.2.2.2:\nExpected `candidate` as the condition, but found `sys.peer` instead.\n"
- "For NTP peer 3.3.3.3:\nExpected `candidate` as the condition, but found `sys.peer` instead.\nExpected `2` as the stratum, but found `3` instead."
+ "1.1.1.1 (Preferred: True, Stratum: 1) - Bad association - Condition: candidate, Stratum: 2",
+ "2.2.2.2 (Preferred: False, Stratum: 2) - Bad association - Condition: sys.peer, Stratum: 2",
+ "3.3.3.3 (Preferred: False, Stratum: 2) - Bad association - Condition: sys.peer, Stratum: 3",
],
},
},
@@ -399,7 +432,7 @@ poll interval unknown
},
"expected": {
"result": "failure",
- "messages": ["None of NTP peers are not configured."],
+ "messages": ["No NTP peers configured"],
},
},
{
@@ -430,7 +463,7 @@ poll interval unknown
},
"expected": {
"result": "failure",
- "messages": ["NTP peer 3.3.3.3 is not configured."],
+ "messages": ["3.3.3.3 (Preferred: False, Stratum: 1) - Not configured"],
},
},
{
@@ -457,8 +490,9 @@ poll interval unknown
"expected": {
"result": "failure",
"messages": [
- "For NTP peer 1.1.1.1:\nExpected `sys.peer` as the condition, but found `candidate` instead.\n"
- "NTP peer 2.2.2.2 is not configured.\nNTP peer 3.3.3.3 is not configured."
+ "1.1.1.1 (Preferred: True, Stratum: 1) - Bad association - Condition: candidate, Stratum: 1",
+ "2.2.2.2 (Preferred: False, Stratum: 1) - Not configured",
+ "3.3.3.3 (Preferred: False, Stratum: 1) - Not configured",
],
},
},
diff --git a/tests/units/cli/conftest.py b/tests/units/cli/conftest.py
index e63e60e..71c23e9 100644
--- a/tests/units/cli/conftest.py
+++ b/tests/units/cli/conftest.py
@@ -39,6 +39,7 @@ MOCK_CLI_JSON: dict[str, asynceapi.EapiCommandError | dict[str, Any]] = {
errmsg="Invalid command",
not_exec=[],
),
+ "show interfaces": {},
}
MOCK_CLI_TEXT: dict[str, asynceapi.EapiCommandError | str] = {
diff --git a/tests/units/cli/get/local_module/__init__.py b/tests/units/cli/get/local_module/__init__.py
new file mode 100644
index 0000000..f93ff2b
--- /dev/null
+++ b/tests/units/cli/get/local_module/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 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 used for test purposes."""
diff --git a/tests/units/cli/get/test_commands.py b/tests/units/cli/get/test_commands.py
index ff3d922..0e263f7 100644
--- a/tests/units/cli/get/test_commands.py
+++ b/tests/units/cli/get/test_commands.py
@@ -114,6 +114,27 @@ def test_from_cvp(
assert result.exit_code == ExitCode.OK
+def test_from_cvp_os_error(tmp_path: Path, click_runner: CliRunner, caplog: pytest.LogCaptureFixture) -> None:
+ """Test from_cvp when an OSError occurs."""
+ output: Path = tmp_path / "output.yml"
+ cli_args = ["get", "from-cvp", "--output", str(output), "--host", "42.42.42.42", "--username", "anta", "--password", "anta"]
+
+ with (
+ patch("anta.cli.get.commands.get_cv_token", autospec=True, side_effect=None),
+ patch("cvprac.cvp_client.CvpClient.connect", autospec=True, side_effect=None) as mocked_cvp_connect,
+ patch("cvprac.cvp_client.CvpApi.get_inventory", autospec=True, return_value=[]) as mocked_get_inventory,
+ patch("cvprac.cvp_client.CvpApi.get_devices_in_container", autospec=True, return_value=[]),
+ patch("anta.cli.get.utils.Path.open", side_effect=OSError("Permission denied")),
+ ):
+ result = click_runner.invoke(anta, cli_args)
+
+ mocked_cvp_connect.assert_called_once()
+ mocked_get_inventory.assert_called_once()
+ assert not output.exists()
+ assert "Could not write inventory to path" in caplog.text
+ assert result.exit_code == ExitCode.USAGE_ERROR
+
+
@pytest.mark.parametrize(
("ansible_inventory", "ansible_group", "expected_exit", "expected_log"),
[
@@ -257,8 +278,7 @@ def test_from_ansible_overwrite(
else:
temp_env["ANTA_INVENTORY"] = None
tmp_inv = tmp_output
- cli_args.extend(["--output", str(tmp_output)])
-
+ cli_args.extend(["--output", str(tmp_inv)])
if overwrite:
cli_args.append("--overwrite")
@@ -275,3 +295,162 @@ def test_from_ansible_overwrite(
elif expected_exit == ExitCode.INTERNAL_ERROR:
assert expected_log
assert expected_log in result.output
+
+
+@pytest.mark.parametrize(
+ ("module", "test_name", "short", "count", "expected_output", "expected_exit_code"),
+ [
+ pytest.param(
+ None,
+ None,
+ False,
+ False,
+ "VerifyAcctConsoleMethods",
+ ExitCode.OK,
+ id="Get all tests",
+ ),
+ pytest.param(
+ "anta.tests.aaa",
+ None,
+ False,
+ False,
+ "VerifyAcctConsoleMethods",
+ ExitCode.OK,
+ id="Get tests, filter on module",
+ ),
+ pytest.param(
+ None,
+ "VerifyNTPAssociations",
+ False,
+ False,
+ "VerifyNTPAssociations",
+ ExitCode.OK,
+ id="Get tests, filter on exact test name",
+ ),
+ pytest.param(
+ None,
+ "VerifyNTP",
+ False,
+ False,
+ "anta.tests.system",
+ ExitCode.OK,
+ id="Get tests, filter on included test name",
+ ),
+ pytest.param(
+ None,
+ "VerifyNTP",
+ True,
+ False,
+ "VerifyNTPAssociations",
+ ExitCode.OK,
+ id="Get tests --short",
+ ),
+ pytest.param(
+ "unknown_module",
+ None,
+ True,
+ False,
+ "Module `unknown_module` was not found!",
+ ExitCode.USAGE_ERROR,
+ id="Get tests wrong module",
+ ),
+ pytest.param(
+ "unknown_module.unknown",
+ None,
+ True,
+ False,
+ "Module `unknown_module.unknown` was not found!",
+ ExitCode.USAGE_ERROR,
+ id="Get tests wrong submodule",
+ ),
+ pytest.param(
+ ".unknown_module",
+ None,
+ True,
+ False,
+ "`anta get tests --module <module>` does not support relative imports",
+ ExitCode.USAGE_ERROR,
+ id="Use relative module name",
+ ),
+ pytest.param(
+ None,
+ "VerifySomething",
+ True,
+ False,
+ "No test 'VerifySomething' found in 'anta.tests'",
+ ExitCode.OK,
+ id="Get tests wrong test name",
+ ),
+ pytest.param(
+ "anta.tests.aaa",
+ "VerifyNTP",
+ True,
+ False,
+ "No test 'VerifyNTP' found in 'anta.tests.aaa'",
+ ExitCode.OK,
+ id="Get tests test exists but not in module",
+ ),
+ pytest.param(
+ "anta.tests.system",
+ "VerifyNTPAssociations",
+ False,
+ True,
+ "There is 1 test available in 'anta.tests.system'.",
+ ExitCode.OK,
+ id="Get single test count",
+ ),
+ pytest.param(
+ "anta.tests.stun",
+ None,
+ False,
+ True,
+ "There are 3 tests available in 'anta.tests.stun'",
+ ExitCode.OK,
+ id="Get multiple test count",
+ ),
+ ],
+)
+def test_get_tests(
+ click_runner: CliRunner, module: str | None, test_name: str | None, *, short: bool, count: bool, expected_output: str, expected_exit_code: str
+) -> None:
+ """Test `anta get tests`."""
+ cli_args = [
+ "get",
+ "tests",
+ ]
+ if module is not None:
+ cli_args.extend(["--module", module])
+
+ if test_name is not None:
+ cli_args.extend(["--test", test_name])
+
+ if short:
+ cli_args.append("--short")
+
+ if count:
+ cli_args.append("--count")
+
+ result = click_runner.invoke(anta, cli_args)
+
+ assert result.exit_code == expected_exit_code
+ assert expected_output in result.output
+
+
+def test_get_tests_local_module(click_runner: CliRunner) -> None:
+ """Test injecting CWD in sys.
+
+ The test overwrite CWD to return this file parents and local_module is located there.
+ """
+ cli_args = ["get", "tests", "--module", "local_module"]
+
+ cwd = Path.cwd()
+ local_module_parent_path = Path(__file__).parent
+ with patch("anta.cli.get.utils.Path.cwd", return_value=local_module_parent_path):
+ result = click_runner.invoke(anta, cli_args)
+
+ assert result.exit_code == ExitCode.OK
+
+ # In the rare case where people would be running `pytest .` in this directory
+ if cwd != local_module_parent_path:
+ assert "injecting CWD in PYTHONPATH and retrying..." in result.output
+ assert "No test found in 'local_module'" in result.output
diff --git a/tests/units/cli/get/test_utils.py b/tests/units/cli/get/test_utils.py
index 46ce14f..9cff4ce 100644
--- a/tests/units/cli/get/test_utils.py
+++ b/tests/units/cli/get/test_utils.py
@@ -7,14 +7,15 @@ from __future__ import annotations
from contextlib import AbstractContextManager, nullcontext
from pathlib import Path
-from typing import Any
+from typing import Any, ClassVar
from unittest.mock import MagicMock, patch
import pytest
import requests
-from anta.cli.get.utils import create_inventory_from_ansible, create_inventory_from_cvp, get_cv_token
+from anta.cli.get.utils import create_inventory_from_ansible, create_inventory_from_cvp, extract_examples, find_tests_examples, get_cv_token, print_test
from anta.inventory import AntaInventory
+from anta.models import AntaCommand, AntaTemplate, AntaTest
DATA_DIR: Path = Path(__file__).parents[3].resolve() / "data"
@@ -160,3 +161,91 @@ def test_create_inventory_from_ansible(
assert not target_file.exists()
if expected_log:
assert expected_log in caplog.text
+
+
+class MissingExampleTest(AntaTest):
+ """ANTA test that always succeed but has no Examples section."""
+
+ categories: ClassVar[list[str]] = []
+ commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ """Test function."""
+ self.result.is_success()
+
+
+class EmptyExampleTest(AntaTest):
+ """ANTA test that always succeed but has an empty Examples section.
+
+ Examples
+ --------
+ """
+
+ # For the test purpose we want am empty section as custom tests could not be using ruff.
+ # ruff: noqa: D414
+
+ categories: ClassVar[list[str]] = []
+ commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ """Test function."""
+ self.result.is_success()
+
+
+class TypoExampleTest(AntaTest):
+ """ANTA test that always succeed but has a Typo in the test name in the example.
+
+ Notice capital P in TyPo below.
+
+ Examples
+ --------
+ ```yaml
+ tests.units.cli.get.test_utils:
+ - TyPoExampleTest:
+ ```
+ """
+
+ # For the test purpose we want am empty section as custom tests could not be using ruff.
+ # ruff: noqa: D414
+
+ categories: ClassVar[list[str]] = []
+ commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ """Test function."""
+ self.result.is_success()
+
+
+def test_find_tests_examples() -> None:
+ """Test find_tests_examples.
+
+ Only testing the failure scenarii not tested through test_commands.
+ TODO: expand
+ """
+ with pytest.raises(ValueError, match="Error when importing"):
+ find_tests_examples("blah", "UnusedTestName")
+
+
+def test_print_test() -> None:
+ """Test print_test."""
+ with pytest.raises(ValueError, match="Could not find the name of the test"):
+ print_test(TypoExampleTest)
+ with pytest.raises(LookupError, match="is missing an Example"):
+ print_test(MissingExampleTest)
+ with pytest.raises(LookupError, match="is missing an Example"):
+ print_test(EmptyExampleTest)
+
+
+def test_extract_examples() -> None:
+ """Test extract_examples.
+
+ Only testing the case where the 'Examples' is missing as everything else
+ is covered already in test_commands.py.
+ """
+ assert MissingExampleTest.__doc__ is not None
+ assert EmptyExampleTest.__doc__ is not None
+ assert extract_examples(MissingExampleTest.__doc__) is None
+ assert extract_examples(EmptyExampleTest.__doc__) is None
diff --git a/tests/units/cli/nrfu/test_commands.py b/tests/units/cli/nrfu/test_commands.py
index 6a2624c..372c86a 100644
--- a/tests/units/cli/nrfu/test_commands.py
+++ b/tests/units/cli/nrfu/test_commands.py
@@ -17,7 +17,7 @@ from anta.cli.utils import ExitCode
if TYPE_CHECKING:
from click.testing import CliRunner
-DATA_DIR: Path = Path(__file__).parent.parent.parent.parent.resolve() / "data"
+DATA_DIR: Path = Path(__file__).parents[3].resolve() / "data"
def test_anta_nrfu_table_help(click_runner: CliRunner) -> None:
@@ -76,6 +76,19 @@ def test_anta_nrfu_text(click_runner: CliRunner) -> None:
assert "leaf1 :: VerifyEOSVersion :: SUCCESS" in result.output
+def test_anta_nrfu_text_multiple_failures(click_runner: CliRunner) -> None:
+ """Test anta nrfu text with multiple failures, catalog is given via env."""
+ result = click_runner.invoke(anta, ["nrfu", "text"], env={"ANTA_CATALOG": str(DATA_DIR / "test_catalog_double_failure.yml")})
+ assert result.exit_code == ExitCode.TESTS_FAILED
+ assert (
+ """spine1 :: VerifyInterfacesSpeed :: FAILURE
+ Interface `Ethernet2` is not found.
+ Interface `Ethernet3` is not found.
+ Interface `Ethernet4` is not found."""
+ in result.output
+ )
+
+
def test_anta_nrfu_json(click_runner: CliRunner) -> None:
"""Test anta nrfu, catalog is given via env."""
result = click_runner.invoke(anta, ["nrfu", "json"])
diff --git a/tests/units/input_models/__init__.py b/tests/units/input_models/__init__.py
new file mode 100644
index 0000000..62747a6
--- /dev/null
+++ b/tests/units/input_models/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""Tests for anta.input_models module."""
diff --git a/tests/units/input_models/routing/__init__.py b/tests/units/input_models/routing/__init__.py
new file mode 100644
index 0000000..b56adb5
--- /dev/null
+++ b/tests/units/input_models/routing/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""Test for anta.input_models.routing submodule."""
diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py
new file mode 100644
index 0000000..66c37af
--- /dev/null
+++ b/tests/units/input_models/routing/test_bgp.py
@@ -0,0 +1,238 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""Tests for anta.input_models.routing.bgp.py."""
+
+# pylint: disable=C0302
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import pytest
+from pydantic import ValidationError
+
+from anta.input_models.routing.bgp import BgpAddressFamily, BgpPeer
+from anta.tests.routing.bgp import (
+ VerifyBGPExchangedRoutes,
+ VerifyBGPPeerCount,
+ VerifyBGPPeerMPCaps,
+ VerifyBGPPeerRouteLimit,
+ VerifyBgpRouteMaps,
+ VerifyBGPSpecificPeers,
+ VerifyBGPTimers,
+)
+
+if TYPE_CHECKING:
+ from anta.custom_types import Afi, Safi
+
+
+class TestBgpAddressFamily:
+ """Test anta.input_models.routing.bgp.BgpAddressFamily."""
+
+ @pytest.mark.parametrize(
+ ("afi", "safi", "vrf"),
+ [
+ pytest.param("ipv4", "unicast", "MGMT", id="afi"),
+ pytest.param("evpn", None, "default", id="safi"),
+ pytest.param("ipv4", "unicast", "default", id="vrf"),
+ ],
+ )
+ def test_valid(self, afi: Afi, safi: Safi, vrf: str) -> None:
+ """Test BgpAddressFamily valid inputs."""
+ BgpAddressFamily(afi=afi, safi=safi, vrf=vrf)
+
+ @pytest.mark.parametrize(
+ ("afi", "safi", "vrf"),
+ [
+ pytest.param("ipv4", None, "default", id="afi"),
+ pytest.param("evpn", "multicast", "default", id="safi"),
+ pytest.param("evpn", None, "MGMT", id="vrf"),
+ ],
+ )
+ def test_invalid(self, afi: Afi, safi: Safi, vrf: str) -> None:
+ """Test BgpAddressFamily invalid inputs."""
+ with pytest.raises(ValidationError):
+ BgpAddressFamily(afi=afi, safi=safi, vrf=vrf)
+
+
+class TestVerifyBGPPeerCountInput:
+ """Test anta.tests.routing.bgp.VerifyBGPPeerCount.Input."""
+
+ @pytest.mark.parametrize(
+ ("address_families"),
+ [
+ pytest.param([{"afi": "evpn", "num_peers": 2}], id="valid"),
+ ],
+ )
+ def test_valid(self, address_families: list[BgpAddressFamily]) -> None:
+ """Test VerifyBGPPeerCount.Input valid inputs."""
+ VerifyBGPPeerCount.Input(address_families=address_families)
+
+ @pytest.mark.parametrize(
+ ("address_families"),
+ [
+ pytest.param([{"afi": "evpn", "num_peers": 0}], id="zero-peer"),
+ pytest.param([{"afi": "evpn"}], id="None"),
+ ],
+ )
+ def test_invalid(self, address_families: list[BgpAddressFamily]) -> None:
+ """Test VerifyBGPPeerCount.Input invalid inputs."""
+ with pytest.raises(ValidationError):
+ VerifyBGPPeerCount.Input(address_families=address_families)
+
+
+class TestVerifyBGPSpecificPeersInput:
+ """Test anta.tests.routing.bgp.VerifyBGPSpecificPeers.Input."""
+
+ @pytest.mark.parametrize(
+ ("address_families"),
+ [
+ pytest.param([{"afi": "evpn", "peers": ["10.1.0.1", "10.1.0.2"]}], id="valid"),
+ ],
+ )
+ def test_valid(self, address_families: list[BgpAddressFamily]) -> None:
+ """Test VerifyBGPSpecificPeers.Input valid inputs."""
+ VerifyBGPSpecificPeers.Input(address_families=address_families)
+
+ @pytest.mark.parametrize(
+ ("address_families"),
+ [
+ pytest.param([{"afi": "evpn"}], id="None"),
+ ],
+ )
+ def test_invalid(self, address_families: list[BgpAddressFamily]) -> None:
+ """Test VerifyBGPSpecificPeers.Input invalid inputs."""
+ with pytest.raises(ValidationError):
+ VerifyBGPSpecificPeers.Input(address_families=address_families)
+
+
+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)
diff --git a/tests/units/input_models/test_interfaces.py b/tests/units/input_models/test_interfaces.py
new file mode 100644
index 0000000..87d742d
--- /dev/null
+++ b/tests/units/input_models/test_interfaces.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""Tests for anta.input_models.interfaces.py."""
+
+# pylint: disable=C0302
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+from anta.input_models.interfaces import InterfaceState
+
+if TYPE_CHECKING:
+ from anta.custom_types import Interface, PortChannelInterface
+
+
+class TestInterfaceState:
+ """Test anta.input_models.interfaces.InterfaceState."""
+
+ # pylint: disable=too-few-public-methods
+
+ @pytest.mark.parametrize(
+ ("name", "portchannel", "expected"),
+ [
+ pytest.param("Ethernet1", "Port-Channel42", "Interface: Ethernet1 Port-Channel: Port-Channel42", id="with port-channel"),
+ pytest.param("Ethernet1", None, "Interface: Ethernet1", id="no port-channel"),
+ ],
+ )
+ def test_valid__str__(self, name: Interface, portchannel: PortChannelInterface | None, expected: str) -> None:
+ """Test InterfaceState __str__."""
+ assert str(InterfaceState(name=name, portchannel=portchannel)) == expected
diff --git a/tests/units/reporter/conftest.py b/tests/units/reporter/conftest.py
index ae7d3df..d0eed36 100644
--- a/tests/units/reporter/conftest.py
+++ b/tests/units/reporter/conftest.py
@@ -5,4 +5,4 @@
from tests.units.result_manager.conftest import list_result_factory, result_manager, result_manager_factory, test_result_factory
-__all__ = ["result_manager", "result_manager_factory", "list_result_factory", "test_result_factory"]
+__all__ = ["list_result_factory", "result_manager", "result_manager_factory", "test_result_factory"]
diff --git a/tests/units/reporter/test__init__.py b/tests/units/reporter/test__init__.py
index af26b54..71cccdd 100644
--- a/tests/units/reporter/test__init__.py
+++ b/tests/units/reporter/test__init__.py
@@ -188,5 +188,5 @@ class TestReportJinja:
def test_fail__init__file_not_found(self) -> None:
"""Test __init__ failure if file is not found."""
- with pytest.raises(FileNotFoundError, match="template file is not found: /gnu/terry/pratchett"):
+ with pytest.raises(FileNotFoundError, match=r"template file is not found: [/|\\]gnu[/|\\]terry[/|\\]pratchett"):
ReportJinja(Path("/gnu/terry/pratchett"))
diff --git a/tests/units/reporter/test_csv.py b/tests/units/reporter/test_csv.py
index 1d59dae..d88098e 100644
--- a/tests/units/reporter/test_csv.py
+++ b/tests/units/reporter/test_csv.py
@@ -8,6 +8,7 @@
import csv
import pathlib
from typing import Any, Callable
+from unittest.mock import patch
import pytest
@@ -49,8 +50,8 @@ class TestReportCsv:
# Generate the CSV report
ReportCsv.generate(result_manager, csv_filename)
- # Read the generated CSV file
- with pathlib.Path.open(csv_filename, encoding="utf-8") as csvfile:
+ # Read the generated CSV file - newline required on Windows..
+ with pathlib.Path.open(csv_filename, encoding="utf-8", newline="") as csvfile:
reader = csv.reader(csvfile, delimiter=",")
rows = list(reader)
@@ -82,11 +83,9 @@ class TestReportCsv:
max_test_entries = 10
result_manager = result_manager_factory(max_test_entries)
- # Create a temporary CSV file path and make tmp_path read_only
- tmp_path.chmod(0o400)
csv_filename = tmp_path / "read_only.csv"
- with pytest.raises(OSError, match="Permission denied"):
+ with patch("pathlib.Path.open", side_effect=OSError("Any OSError")), pytest.raises(OSError, match="Any OSError"):
# Generate the CSV report
ReportCsv.generate(result_manager, csv_filename)
diff --git a/tests/units/reporter/test_md_reporter.py b/tests/units/reporter/test_md_reporter.py
index a607733..c0676bb 100644
--- a/tests/units/reporter/test_md_reporter.py
+++ b/tests/units/reporter/test_md_reporter.py
@@ -5,7 +5,7 @@
from __future__ import annotations
-from io import StringIO
+from io import BytesIO, TextIOWrapper
from pathlib import Path
import pytest
@@ -46,7 +46,7 @@ def test_md_report_base() -> None:
results = ResultManager()
- with StringIO() as mock_file:
+ with TextIOWrapper(BytesIO(b"1 2 3")) as mock_file:
report = FakeMDReportBase(mock_file, results)
assert report.generate_heading_name() == "Fake MD Report Base"
diff --git a/tests/units/result_manager/test__init__.py b/tests/units/result_manager/test__init__.py
index 1fd51cb..e41a436 100644
--- a/tests/units/result_manager/test__init__.py
+++ b/tests/units/result_manager/test__init__.py
@@ -6,6 +6,7 @@
from __future__ import annotations
import json
+import logging
import re
from contextlib import AbstractContextManager, nullcontext
from typing import TYPE_CHECKING, Callable
@@ -379,3 +380,103 @@ class TestResultManager:
assert len(result_manager.get_devices()) == 2
assert all(t in result_manager.get_devices() for t in ["Device1", "Device2"])
+
+ def test_stats_computation_methods(self, test_result_factory: Callable[[], TestResult], caplog: pytest.LogCaptureFixture) -> None:
+ """Test ResultManager internal stats computation methods."""
+ result_manager = ResultManager()
+
+ # Initially stats should be unsynced
+ assert result_manager._stats_in_sync is False
+
+ # Test _reset_stats
+ result_manager._reset_stats()
+ assert result_manager._stats_in_sync is False
+ assert len(result_manager._device_stats) == 0
+ assert len(result_manager._category_stats) == 0
+ assert len(result_manager._test_stats) == 0
+
+ # Add some test results
+ test1 = test_result_factory()
+ test1.name = "device1"
+ test1.result = AntaTestStatus.SUCCESS
+ test1.categories = ["system"]
+ test1.test = "test1"
+
+ test2 = test_result_factory()
+ test2.name = "device2"
+ test2.result = AntaTestStatus.FAILURE
+ test2.categories = ["interfaces"]
+ test2.test = "test2"
+
+ result_manager.add(test1)
+ result_manager.add(test2)
+
+ # Stats should still be unsynced after adding results
+ assert result_manager._stats_in_sync is False
+
+ # Test _compute_stats directly
+ with caplog.at_level(logging.INFO):
+ result_manager._compute_stats()
+ assert "Computing statistics for all results" in caplog.text
+ assert result_manager._stats_in_sync is True
+
+ # Verify stats content
+ assert len(result_manager._device_stats) == 2
+ assert len(result_manager._category_stats) == 2
+ assert len(result_manager._test_stats) == 2
+ assert result_manager._device_stats["device1"].tests_success_count == 1
+ assert result_manager._device_stats["device2"].tests_failure_count == 1
+ assert result_manager._category_stats["system"].tests_success_count == 1
+ assert result_manager._category_stats["interfaces"].tests_failure_count == 1
+ assert result_manager._test_stats["test1"].devices_success_count == 1
+ assert result_manager._test_stats["test2"].devices_failure_count == 1
+
+ def test_stats_property_computation(self, test_result_factory: Callable[[], TestResult], caplog: pytest.LogCaptureFixture) -> None:
+ """Test that stats are computed only once when accessed via properties."""
+ result_manager = ResultManager()
+
+ # Add some test results
+ test1 = test_result_factory()
+ test1.name = "device1"
+ test1.result = AntaTestStatus.SUCCESS
+ test1.categories = ["system"]
+ result_manager.add(test1)
+
+ test2 = test_result_factory()
+ test2.name = "device2"
+ test2.result = AntaTestStatus.FAILURE
+ test2.categories = ["interfaces"]
+ result_manager.add(test2)
+
+ # Stats should be unsynced after adding results
+ assert result_manager._stats_in_sync is False
+ assert "Computing statistics" not in caplog.text
+
+ # Access device_stats property - should trigger computation
+ with caplog.at_level(logging.INFO):
+ _ = result_manager.device_stats
+ assert "Computing statistics for all results" in caplog.text
+ assert result_manager._stats_in_sync is True
+
+ # Clear the log
+ caplog.clear()
+
+ # Access other stats properties - should not trigger computation again
+ with caplog.at_level(logging.INFO):
+ _ = result_manager.category_stats
+ _ = result_manager.test_stats
+ _ = result_manager.sorted_category_stats
+ assert "Computing statistics" not in caplog.text
+
+ # Add another result - should mark stats as unsynced
+ test3 = test_result_factory()
+ test3.name = "device3"
+ test3.result = "error"
+ result_manager.add(test3)
+ assert result_manager._stats_in_sync is False
+
+ # Access stats again - should trigger recomputation
+ with caplog.at_level(logging.INFO):
+ _ = result_manager.device_stats
+ assert "Computing statistics for all results" in caplog.text
+ assert result_manager._stats_in_sync is True
diff --git a/tests/units/test_custom_types.py b/tests/units/test_custom_types.py
index 6970171..95c5234 100644
--- a/tests/units/test_custom_types.py
+++ b/tests/units/test_custom_types.py
@@ -192,8 +192,7 @@ def test_regexp_eos_blacklist_cmds(test_string: str, expected: bool) -> None:
"""Test REGEXP_EOS_BLACKLIST_CMDS."""
def matches_any_regex(string: str, regex_list: list[str]) -> bool:
- """
- Check if a string matches at least one regular expression in a list.
+ """Check if a string matches at least one regular expression in a list.
:param string: The string to check.
:param regex_list: A list of regular expressions.
diff --git a/tests/units/test_decorators.py b/tests/units/test_decorators.py
new file mode 100644
index 0000000..c267df1
--- /dev/null
+++ b/tests/units/test_decorators.py
@@ -0,0 +1,77 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""test anta.decorators.py."""
+
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING, ClassVar
+
+import pytest
+
+from anta.decorators import deprecated_test_class, skip_on_platforms
+from anta.models import AntaCommand, AntaTemplate, AntaTest
+
+if TYPE_CHECKING:
+ from anta.device import AntaDevice
+
+
+class ExampleTest(AntaTest):
+ """ANTA test that always succeed."""
+
+ categories: ClassVar[list[str]] = []
+ commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ """Test function."""
+ self.result.is_success()
+
+
+@pytest.mark.parametrize(
+ "new_tests",
+ [
+ pytest.param(None, id="No new_tests"),
+ pytest.param(["NewExampleTest"], id="one new_tests"),
+ pytest.param(["NewExampleTest1", "NewExampleTest2"], id="multiple new_tests"),
+ ],
+)
+def test_deprecated_test_class(caplog: pytest.LogCaptureFixture, device: AntaDevice, new_tests: list[str] | None) -> None:
+ """Test deprecated_test_class decorator."""
+ caplog.set_level(logging.INFO)
+
+ decorated_test_class = deprecated_test_class(new_tests=new_tests)(ExampleTest)
+
+ # Initialize the decorated test
+ decorated_test_class(device)
+
+ if new_tests is None:
+ assert "ExampleTest test is deprecated." in caplog.messages
+ else:
+ assert f"ExampleTest test is deprecated. Consider using the following new tests: {', '.join(new_tests)}." in caplog.messages
+
+
+@pytest.mark.parametrize(
+ ("platforms", "device_platform", "expected_result"),
+ [
+ pytest.param([], "cEOS-lab", "success", id="empty platforms"),
+ pytest.param(["cEOS-lab"], "cEOS-lab", "skipped", id="skip on one platform - match"),
+ pytest.param(["cEOS-lab"], "vEOS", "success", id="skip on one platform - no match"),
+ pytest.param(["cEOS-lab", "vEOS"], "cEOS-lab", "skipped", id="skip on multiple platforms - match"),
+ ],
+)
+async def test_skip_on_platforms(device: AntaDevice, platforms: list[str], device_platform: str, expected_result: str) -> None:
+ """Test skip_on_platforms decorator.
+
+ Leverage the ExampleTest defined at the top of the module.
+ """
+ # Apply the decorator - ignoring mypy warning - this is for testing
+ ExampleTest.test = skip_on_platforms(platforms)(ExampleTest.test) # type: ignore[method-assign]
+
+ device.hw_model = device_platform
+
+ test_instance = ExampleTest(device)
+ await test_instance.test()
+
+ assert test_instance.result.result == expected_result
diff --git a/tests/units/test_device.py b/tests/units/test_device.py
index faf6144..17669df 100644
--- a/tests/units/test_device.py
+++ b/tests/units/test_device.py
@@ -6,13 +6,15 @@
from __future__ import annotations
import asyncio
+from contextlib import AbstractContextManager
+from contextlib import nullcontext as does_not_raise
from pathlib import Path
from typing import TYPE_CHECKING, Any
from unittest.mock import patch
import pytest
from asyncssh import SSHClientConnection, SSHClientConnectionOptions
-from httpx import ConnectError, HTTPError
+from httpx import ConnectError, HTTPError, TimeoutException
from rich import print as rprint
from anta.device import AntaDevice, AsyncEOSDevice
@@ -24,13 +26,37 @@ if TYPE_CHECKING:
from _pytest.mark.structures import ParameterSet
INIT_PARAMS: list[ParameterSet] = [
- pytest.param({"host": "42.42.42.42", "username": "anta", "password": "anta"}, {"name": "42.42.42.42"}, id="no name, no port"),
- pytest.param({"host": "42.42.42.42", "username": "anta", "password": "anta", "port": 666}, {"name": "42.42.42.42:666"}, id="no name, port"),
+ pytest.param({"host": "42.42.42.42", "username": "anta", "password": "anta"}, {"name": "42.42.42.42"}, does_not_raise(), id="no name, no port"),
+ pytest.param({"host": "42.42.42.42", "username": "anta", "password": "anta", "port": 666}, {"name": "42.42.42.42:666"}, does_not_raise(), id="no name, port"),
pytest.param(
- {"host": "42.42.42.42", "username": "anta", "password": "anta", "name": "test.anta.ninja", "disable_cache": True}, {"name": "test.anta.ninja"}, id="name"
+ {"host": "42.42.42.42", "username": "anta", "password": "anta", "name": "test.anta.ninja", "disable_cache": True},
+ {"name": "test.anta.ninja"},
+ does_not_raise(),
+ id="name",
),
pytest.param(
- {"host": "42.42.42.42", "username": "anta", "password": "anta", "name": "test.anta.ninja", "insecure": True}, {"name": "test.anta.ninja"}, id="insecure"
+ {"host": "42.42.42.42", "username": "anta", "password": "anta", "name": "test.anta.ninja", "insecure": True},
+ {"name": "test.anta.ninja"},
+ does_not_raise(),
+ id="insecure",
+ ),
+ pytest.param(
+ {"host": None, "username": "anta", "password": "anta", "name": "test.anta.ninja"},
+ None,
+ pytest.raises(ValueError, match="'host' is required to create an AsyncEOSDevice"),
+ id="host is None",
+ ),
+ pytest.param(
+ {"host": "42.42.42.42", "username": None, "password": "anta", "name": "test.anta.ninja"},
+ None,
+ pytest.raises(ValueError, match="'username' is required to instantiate device 'test.anta.ninja'"),
+ id="username is None",
+ ),
+ pytest.param(
+ {"host": "42.42.42.42", "username": "anta", "password": None, "name": "test.anta.ninja"},
+ None,
+ pytest.raises(ValueError, match="'password' is required to instantiate device 'test.anta.ninja'"),
+ id="password is None",
),
]
EQUALITY_PARAMS: list[ParameterSet] = [
@@ -48,7 +74,10 @@ EQUALITY_PARAMS: list[ParameterSet] = [
id="not-equal-port",
),
pytest.param(
- {"host": "42.42.42.41", "username": "anta", "password": "anta"}, {"host": "42.42.42.42", "username": "anta", "password": "anta"}, False, id="not-equal-host"
+ {"host": "42.42.42.41", "username": "anta", "password": "anta"},
+ {"host": "42.42.42.42", "username": "anta", "password": "anta"},
+ False,
+ id="not-equal-host",
),
]
ASYNCEAPI_COLLECT_PARAMS: list[ParameterSet] = [
@@ -287,7 +316,58 @@ ASYNCEAPI_COLLECT_PARAMS: list[ParameterSet] = [
},
},
{"output": None, "errors": ["Authorization denied for command 'show version'"]},
- id="asynceapi.EapiCommandError",
+ id="asynceapi.EapiCommandError - Authorization denied",
+ ),
+ pytest.param(
+ {},
+ {
+ "command": "show version",
+ "patch_kwargs": {
+ "side_effect": EapiCommandError(
+ passed=[],
+ failed="show version",
+ errors=["not supported on this hardware platform"],
+ errmsg="Invalid command",
+ not_exec=[],
+ )
+ },
+ },
+ {"output": None, "errors": ["not supported on this hardware platform"]},
+ id="asynceapi.EapiCommandError - not supported",
+ ),
+ pytest.param(
+ {},
+ {
+ "command": "show version",
+ "patch_kwargs": {
+ "side_effect": EapiCommandError(
+ passed=[],
+ failed="show version",
+ errors=["BGP inactive"],
+ errmsg="Invalid command",
+ not_exec=[],
+ )
+ },
+ },
+ {"output": None, "errors": ["BGP inactive"]},
+ id="asynceapi.EapiCommandError - known EOS error",
+ ),
+ pytest.param(
+ {},
+ {
+ "command": "show version",
+ "patch_kwargs": {
+ "side_effect": EapiCommandError(
+ passed=[],
+ failed="show version",
+ errors=["Invalid input (privileged mode required)"],
+ errmsg="Invalid command",
+ not_exec=[],
+ )
+ },
+ },
+ {"output": None, "errors": ["Invalid input (privileged mode required)"]},
+ id="asynceapi.EapiCommandError - requires privileges",
),
pytest.param(
{},
@@ -301,6 +381,12 @@ ASYNCEAPI_COLLECT_PARAMS: list[ParameterSet] = [
{"output": None, "errors": ["ConnectError: Cannot open port"]},
id="httpx.ConnectError",
),
+ pytest.param(
+ {},
+ {"command": "show version", "patch_kwargs": {"side_effect": TimeoutException("Test")}},
+ {"output": None, "errors": ["TimeoutException: Test"]},
+ id="httpx.TimeoutException",
+ ),
]
ASYNCEAPI_COPY_PARAMS: list[ParameterSet] = [
pytest.param({}, {"sources": [Path("/mnt/flash"), Path("/var/log/agents")], "destination": Path(), "direction": "from"}, id="from"),
@@ -531,22 +617,24 @@ class TestAntaDevice:
class TestAsyncEOSDevice:
"""Test for anta.device.AsyncEOSDevice."""
- @pytest.mark.parametrize(("device", "expected"), INIT_PARAMS)
- def test__init__(self, device: dict[str, Any], expected: dict[str, Any]) -> None:
+ @pytest.mark.parametrize(("device", "expected", "expected_raise"), INIT_PARAMS)
+ def test__init__(self, device: dict[str, Any], expected: dict[str, Any] | None, expected_raise: AbstractContextManager[Exception]) -> None:
"""Test the AsyncEOSDevice constructor."""
- dev = AsyncEOSDevice(**device)
+ with expected_raise:
+ dev = AsyncEOSDevice(**device)
- assert dev.name == expected["name"]
- if device.get("disable_cache") is True:
- assert dev.cache is None
- assert dev.cache_locks is None
- else: # False or None
- assert dev.cache is not None
- assert dev.cache_locks is not None
- hash(dev)
+ assert expected is not None
+ assert dev.name == expected["name"]
+ if device.get("disable_cache") is True:
+ assert dev.cache is None
+ assert dev.cache_locks is None
+ else: # False or None
+ assert dev.cache is not None
+ assert dev.cache_locks is not None
+ hash(dev)
- with patch("anta.device.__DEBUG__", new=True):
- rprint(dev)
+ with patch("anta.device.__DEBUG__", new=True):
+ rprint(dev)
@pytest.mark.parametrize(("device1", "device2", "expected"), EQUALITY_PARAMS)
def test__eq(self, device1: dict[str, Any], device2: dict[str, Any], expected: bool) -> None:
diff --git a/tests/units/test_models.py b/tests/units/test_models.py
index d604b48..d12d859 100644
--- a/tests/units/test_models.py
+++ b/tests/units/test_models.py
@@ -26,8 +26,6 @@ if TYPE_CHECKING:
class FakeTest(AntaTest):
"""ANTA test that always succeed."""
- name = "FakeTest"
- description = "ANTA test that always succeed"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@@ -40,8 +38,6 @@ class FakeTest(AntaTest):
class FakeTestWithFailedCommand(AntaTest):
"""ANTA test with a command that failed."""
- name = "FakeTestWithFailedCommand"
- description = "ANTA test with a command that failed"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show version", errors=["failed command"])]
@@ -54,8 +50,6 @@ class FakeTestWithFailedCommand(AntaTest):
class FakeTestWithUnsupportedCommand(AntaTest):
"""ANTA test with an unsupported command."""
- name = "FakeTestWithUnsupportedCommand"
- description = "ANTA test with an unsupported command"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
AntaCommand(
@@ -70,11 +64,26 @@ class FakeTestWithUnsupportedCommand(AntaTest):
self.result.is_success()
+class FakeTestWithKnownEOSError(AntaTest):
+ """ANTA test triggering a known EOS Error that should translate to failure of the test."""
+
+ categories: ClassVar[list[str]] = []
+ commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
+ AntaCommand(
+ command="show bgp evpn route-type mac-ip aa:c1:ab:de:50:ad vni 10010",
+ errors=["BGP inactive"],
+ )
+ ]
+
+ @AntaTest.anta_test
+ def test(self) -> None:
+ """Test function."""
+ self.result.is_success()
+
+
class FakeTestWithInput(AntaTest):
"""ANTA test with inputs that always succeed."""
- name = "FakeTestWithInput"
- description = "ANTA test with inputs that always succeed"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@@ -92,8 +101,6 @@ class FakeTestWithInput(AntaTest):
class FakeTestWithTemplate(AntaTest):
"""ANTA test with template that always succeed."""
- name = "FakeTestWithTemplate"
- description = "ANTA test with template that always succeed"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")]
@@ -115,8 +122,6 @@ class FakeTestWithTemplate(AntaTest):
class FakeTestWithTemplateNoRender(AntaTest):
"""ANTA test with template that miss the render() method."""
- name = "FakeTestWithTemplateNoRender"
- description = "ANTA test with template that miss the render() method"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")]
@@ -134,8 +139,6 @@ class FakeTestWithTemplateNoRender(AntaTest):
class FakeTestWithTemplateBadRender1(AntaTest):
"""ANTA test with template that raises a AntaTemplateRenderError exception."""
- name = "FakeTestWithTemplateBadRender"
- description = "ANTA test with template that raises a AntaTemplateRenderError exception"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")]
@@ -157,8 +160,6 @@ class FakeTestWithTemplateBadRender1(AntaTest):
class FakeTestWithTemplateBadRender2(AntaTest):
"""ANTA test with template that raises an arbitrary exception in render()."""
- name = "FakeTestWithTemplateBadRender2"
- description = "ANTA test with template that raises an arbitrary exception in render()"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")]
@@ -180,8 +181,6 @@ class FakeTestWithTemplateBadRender2(AntaTest):
class FakeTestWithTemplateBadRender3(AntaTest):
"""ANTA test with template that gives extra template parameters in render()."""
- name = "FakeTestWithTemplateBadRender3"
- description = "ANTA test with template that gives extra template parameters in render()"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")]
@@ -203,8 +202,6 @@ class FakeTestWithTemplateBadRender3(AntaTest):
class FakeTestWithTemplateBadTest(AntaTest):
"""ANTA test with template that tries to access an undefined template parameter in test()."""
- name = "FakeTestWithTemplateBadTest"
- description = "ANTA test with template that tries to access an undefined template parameter in test()"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show interface {interface}")]
@@ -227,8 +224,6 @@ class FakeTestWithTemplateBadTest(AntaTest):
class SkipOnPlatformTest(AntaTest):
"""ANTA test that is skipped."""
- name = "SkipOnPlatformTest"
- description = "ANTA test that is skipped on a specific platform"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@@ -242,8 +237,6 @@ class SkipOnPlatformTest(AntaTest):
class UnSkipOnPlatformTest(AntaTest):
"""ANTA test that is skipped."""
- name = "UnSkipOnPlatformTest"
- description = "ANTA test that is skipped on a specific platform"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@@ -257,8 +250,6 @@ class UnSkipOnPlatformTest(AntaTest):
class SkipOnPlatformTestWithInput(AntaTest):
"""ANTA test skipped on platforms but with Input."""
- name = "SkipOnPlatformTestWithInput"
- description = "ANTA test skipped on platforms but with Input"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@@ -277,8 +268,6 @@ class SkipOnPlatformTestWithInput(AntaTest):
class DeprecatedTestWithoutNewTest(AntaTest):
"""ANTA test that is deprecated without new test."""
- name = "DeprecatedTestWitouthNewTest"
- description = "ANTA test that is deprecated without new test"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@@ -292,8 +281,6 @@ class DeprecatedTestWithoutNewTest(AntaTest):
class DeprecatedTestWithNewTest(AntaTest):
"""ANTA test that is deprecated with new test."""
- name = "DeprecatedTestWithNewTest"
- description = "ANTA deprecated test with New Test"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@@ -307,8 +294,6 @@ class DeprecatedTestWithNewTest(AntaTest):
class FakeTestWithMissingTest(AntaTest):
"""ANTA test with missing test() method implementation."""
- name = "FakeTestWithMissingTest"
- description = "ANTA test with missing test() method implementation"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@@ -516,6 +501,18 @@ ANTATEST_DATA: list[dict[str, Any]] = [
},
},
},
+ {
+ "name": "known EOS error command",
+ "test": FakeTestWithKnownEOSError,
+ "inputs": None,
+ "expected": {
+ "__init__": {"result": "unset"},
+ "test": {
+ "result": "failure",
+ "messages": ["BGP inactive"],
+ },
+ },
+ },
]
BLACKLIST_COMMANDS_PARAMS = ["reload", "reload --force", "write", "wr mem"]
@@ -526,65 +523,61 @@ class TestAntaTest:
def test__init_subclass__(self) -> None:
"""Test __init_subclass__."""
- with pytest.raises(NotImplementedError) as exec_info:
+ with pytest.raises(AttributeError) as exec_info:
- class _WrongTestNoName(AntaTest):
- """ANTA test that is missing a name."""
+ class _WrongTestNoCategories(AntaTest):
+ """ANTA test that is missing categories."""
- description = "ANTA test that is missing a name"
- categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@AntaTest.anta_test
def test(self) -> None:
self.result.is_success()
- assert exec_info.value.args[0] == "Class tests.units.test_models._WrongTestNoName is missing required class attribute name"
+ assert exec_info.value.args[0] == "Class tests.units.test_models._WrongTestNoCategories is missing required class attribute(s): categories"
- with pytest.raises(NotImplementedError) as exec_info:
+ with pytest.raises(AttributeError) as exec_info:
- class _WrongTestNoDescription(AntaTest):
- """ANTA test that is missing a description."""
+ class _WrongTestNoCommands(AntaTest):
+ """ANTA test that is missing commands."""
- name = "WrongTestNoDescription"
categories: ClassVar[list[str]] = []
- commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
@AntaTest.anta_test
def test(self) -> None:
self.result.is_success()
- assert exec_info.value.args[0] == "Class tests.units.test_models._WrongTestNoDescription is missing required class attribute description"
+ assert exec_info.value.args[0] == "Class tests.units.test_models._WrongTestNoCommands is missing required class attribute(s): commands"
- with pytest.raises(NotImplementedError) as exec_info:
+ with pytest.raises(
+ AttributeError,
+ match="Cannot set the description for class _WrongTestNoDescription, either set it in the class definition or add a docstring to the class.",
+ ):
- class _WrongTestNoCategories(AntaTest):
- """ANTA test that is missing categories."""
+ class _WrongTestNoDescription(AntaTest):
+ # ANTA test that is missing a description and does not have a doctstring.
- name = "WrongTestNoCategories"
- description = "ANTA test that is missing categories"
commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
+ categories: ClassVar[list[str]] = []
@AntaTest.anta_test
def test(self) -> None:
self.result.is_success()
- assert exec_info.value.args[0] == "Class tests.units.test_models._WrongTestNoCategories is missing required class attribute categories"
+ class _TestOverwriteNameAndDescription(AntaTest):
+ """ANTA test where both the test name and description are overwritten in the class definition."""
- with pytest.raises(NotImplementedError) as exec_info:
-
- class _WrongTestNoCommands(AntaTest):
- """ANTA test that is missing commands."""
-
- name = "WrongTestNoCommands"
- description = "ANTA test that is missing commands"
- categories: ClassVar[list[str]] = []
+ name: ClassVar[str] = "CustomName"
+ description: ClassVar[str] = "Custom description"
+ commands: ClassVar[list[AntaCommand | AntaTemplate]] = []
+ categories: ClassVar[list[str]] = []
- @AntaTest.anta_test
- def test(self) -> None:
- self.result.is_success()
+ @AntaTest.anta_test
+ def test(self) -> None:
+ self.result.is_success()
- assert exec_info.value.args[0] == "Class tests.units.test_models._WrongTestNoCommands is missing required class attribute commands"
+ assert _TestOverwriteNameAndDescription.name == "CustomName"
+ assert _TestOverwriteNameAndDescription.description == "Custom description"
def test_abc(self) -> None:
"""Test that an error is raised if AntaTest is not implemented."""
@@ -626,8 +619,6 @@ class TestAntaTest:
class FakeTestWithBlacklist(AntaTest):
"""Fake Test for blacklist."""
- name = "FakeTestWithBlacklist"
- description = "ANTA test that has blacklisted command"
categories: ClassVar[list[str]] = []
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command=command)]
@@ -651,7 +642,7 @@ class TestAntaTest:
assert test.result.custom_field == "a custom field"
-class TestAntaComamnd:
+class TestAntaCommand:
"""Test for anta.models.AntaCommand."""
# ruff: noqa: B018
@@ -710,6 +701,32 @@ class TestAntaComamnd:
)
assert command.requires_privileges is False
command = AntaCommand(command="show aaa methods accounting")
- with pytest.raises(RuntimeError) as exec_info:
+ with pytest.raises(
+ RuntimeError, match="Command 'show aaa methods accounting' has not been collected and has not returned an error. Call AntaDevice.collect()."
+ ):
command.requires_privileges
- assert exec_info.value.args[0] == "Command 'show aaa methods accounting' has not been collected and has not returned an error. Call AntaDevice.collect()."
+
+ @pytest.mark.parametrize(
+ ("command_str", "error", "is_known"),
+ [
+ ("show ip interface Ethernet1", "Ethernet1 does not support IP", True),
+ ("ping vrf MGMT 1.1.1.1 source Management0 size 100 df-bit repeat 2", "VRF 'MGMT' is not active", True),
+ ("ping vrf MGMT 1.1.1.1 source Management1 size 100 df-bit repeat 2", "No source interface Management1", True),
+ ("show bgp evpn route-type mac-ip aa:c1:ab:de:50:ad vni 10010", "BGP inactive", True),
+ ("show isis BLAH neighbors", "IS-IS (BLAH) is disabled because: IS-IS Network Entity Title (NET) configuration is not present", True),
+ ("show ip interface Ethernet1", None, False),
+ ],
+ )
+ def test_returned_known_eos_error(self, command_str: str, error: str | None, is_known: bool) -> None:
+ """Test the returned_known_eos_error property."""
+ # Adding fake output when no error is present to mimic that the command has been collected
+ command = AntaCommand(command=command_str, errors=[error] if error else [], output=None if error else "{}")
+ assert command.returned_known_eos_error is is_known
+
+ def test_returned_known_eos_error_failure(self) -> None:
+ """Test the returned_known_eos_error property unset."""
+ command = AntaCommand(command="show ip interface Ethernet1")
+ with pytest.raises(
+ RuntimeError, match="Command 'show ip interface Ethernet1' has not been collected and has not returned an error. Call AntaDevice.collect()."
+ ):
+ command.returned_known_eos_error
diff --git a/tests/units/test_runner.py b/tests/units/test_runner.py
index b80259c..23f4102 100644
--- a/tests/units/test_runner.py
+++ b/tests/units/test_runner.py
@@ -6,7 +6,7 @@
from __future__ import annotations
import logging
-import resource
+import os
import sys
from pathlib import Path
from unittest.mock import patch
@@ -16,10 +16,16 @@ import pytest
from anta.catalog import AntaCatalog
from anta.inventory import AntaInventory
from anta.result_manager import ResultManager
-from anta.runner import adjust_rlimit_nofile, main, prepare_tests
+from anta.runner import main, prepare_tests
from .test_models import FakeTest, FakeTestWithMissingTest
+if os.name == "posix":
+ # The function is not defined on non-POSIX system
+ import resource
+
+ from anta.runner import adjust_rlimit_nofile
+
DATA_DIR: Path = Path(__file__).parent.parent.resolve() / "data"
FAKE_CATALOG: AntaCatalog = AntaCatalog.from_list([(FakeTest, None)])
@@ -65,8 +71,10 @@ async def test_no_selected_device(caplog: pytest.LogCaptureFixture, inventory: A
assert msg in caplog.messages
+@pytest.mark.skipif(os.name != "posix", reason="Cannot run this test on Windows")
def test_adjust_rlimit_nofile_valid_env(caplog: pytest.LogCaptureFixture) -> None:
"""Test adjust_rlimit_nofile with valid environment variables."""
+ # pylint: disable=E0606
with (
caplog.at_level(logging.DEBUG),
patch.dict("os.environ", {"ANTA_NOFILE": "20480"}),
@@ -96,6 +104,7 @@ def test_adjust_rlimit_nofile_valid_env(caplog: pytest.LogCaptureFixture) -> Non
setrlimit_mock.assert_called_once_with(resource.RLIMIT_NOFILE, (20480, 1048576))
+@pytest.mark.skipif(os.name != "posix", reason="Cannot run this test on Windows")
def test_adjust_rlimit_nofile_invalid_env(caplog: pytest.LogCaptureFixture) -> None:
"""Test adjust_rlimit_nofile with valid environment variables."""
with (
@@ -129,6 +138,31 @@ def test_adjust_rlimit_nofile_invalid_env(caplog: pytest.LogCaptureFixture) -> N
setrlimit_mock.assert_called_once_with(resource.RLIMIT_NOFILE, (16384, 1048576))
+@pytest.mark.skipif(os.name == "posix", reason="Run this test on Windows only")
+async def test_check_runner_log_for_windows(caplog: pytest.LogCaptureFixture, inventory: AntaInventory) -> None:
+ """Test log output for Windows host regarding rlimit."""
+ caplog.set_level(logging.INFO)
+ manager = ResultManager()
+ # Using dry-run to shorten the test
+ await main(manager, inventory, FAKE_CATALOG, dry_run=True)
+ assert "Running on a non-POSIX system, cannot adjust the maximum number of file descriptors." in caplog.records[-3].message
+
+
+# We could instead merge multiple coverage report together but that requires more work than just this.
+@pytest.mark.skipif(os.name != "posix", reason="Fake non-posix for coverage")
+async def test_check_runner_log_for_windows_fake(caplog: pytest.LogCaptureFixture, inventory: AntaInventory) -> None:
+ """Test log output for Windows host regarding rlimit."""
+ with patch("os.name", new="win32"):
+ del sys.modules["anta.runner"]
+ from anta.runner import main # pylint: disable=W0621
+
+ caplog.set_level(logging.INFO)
+ manager = ResultManager()
+ # Using dry-run to shorten the test
+ await main(manager, inventory, FAKE_CATALOG, dry_run=True)
+ assert "Running on a non-POSIX system, cannot adjust the maximum number of file descriptors." in caplog.records[-3].message
+
+
@pytest.mark.parametrize(
("inventory", "tags", "tests", "devices_count", "tests_count"),
[
@@ -138,6 +172,7 @@ def test_adjust_rlimit_nofile_invalid_env(caplog: pytest.LogCaptureFixture) -> N
pytest.param({"filename": "test_inventory_with_tags.yml"}, None, {"VerifyMlagStatus", "VerifyUptime"}, 3, 5, id="filtered-tests"),
pytest.param({"filename": "test_inventory_with_tags.yml"}, {"leaf"}, {"VerifyMlagStatus", "VerifyUptime"}, 2, 4, id="1-tag-filtered-tests"),
pytest.param({"filename": "test_inventory_with_tags.yml"}, {"invalid"}, None, 0, 0, id="invalid-tag"),
+ pytest.param({"filename": "test_inventory_with_tags.yml"}, {"dc1"}, None, 0, 0, id="device-tag-no-tests"),
],
indirect=["inventory"],
)
diff --git a/tests/units/test_tools.py b/tests/units/test_tools.py
index 16f0443..b1f96a5 100644
--- a/tests/units/test_tools.py
+++ b/tests/units/test_tools.py
@@ -11,7 +11,7 @@ from typing import Any
import pytest
-from anta.tools import convert_categories, custom_division, get_dict_superset, get_failed_logs, get_item, get_value
+from anta.tools import convert_categories, custom_division, format_data, get_dict_superset, get_failed_logs, get_item, get_value
TEST_GET_FAILED_LOGS_DATA = [
{"id": 1, "name": "Alice", "age": 30, "email": "alice@example.com"},
@@ -513,3 +513,17 @@ def test_convert_categories(test_input: list[str], expected_raise: AbstractConte
"""Test convert_categories."""
with expected_raise:
assert convert_categories(test_input) == expected_result
+
+
+@pytest.mark.parametrize(
+ ("input_data", "expected_output"),
+ [
+ pytest.param({"advertised": True, "received": True, "enabled": True}, "Advertised: True, Received: True, Enabled: True", 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:
+ """Test format_data."""
+ assert format_data(input_data) == expected_output