summaryrefslogtreecommitdiffstats
path: root/tests/units/anta_tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests/units/anta_tests')
-rw-r--r--tests/units/anta_tests/__init__.py31
-rw-r--r--tests/units/anta_tests/conftest.py35
-rw-r--r--tests/units/anta_tests/routing/test_bgp.py1120
-rw-r--r--tests/units/anta_tests/routing/test_generic.py129
-rw-r--r--tests/units/anta_tests/routing/test_isis.py2
-rw-r--r--tests/units/anta_tests/routing/test_ospf.py2
-rw-r--r--tests/units/anta_tests/test_aaa.py2
-rw-r--r--tests/units/anta_tests/test_avt.py2
-rw-r--r--tests/units/anta_tests/test_bfd.py139
-rw-r--r--tests/units/anta_tests/test_configuration.py12
-rw-r--r--tests/units/anta_tests/test_connectivity.py46
-rw-r--r--tests/units/anta_tests/test_field_notices.py6
-rw-r--r--tests/units/anta_tests/test_flow_tracking.py391
-rw-r--r--tests/units/anta_tests/test_greent.py2
-rw-r--r--tests/units/anta_tests/test_hardware.py2
-rw-r--r--tests/units/anta_tests/test_interfaces.py130
-rw-r--r--tests/units/anta_tests/test_lanz.py2
-rw-r--r--tests/units/anta_tests/test_logging.py17
-rw-r--r--tests/units/anta_tests/test_mlag.py13
-rw-r--r--tests/units/anta_tests/test_multicast.py2
-rw-r--r--tests/units/anta_tests/test_path_selection.py2
-rw-r--r--tests/units/anta_tests/test_profiles.py2
-rw-r--r--tests/units/anta_tests/test_ptp.py6
-rw-r--r--tests/units/anta_tests/test_security.py144
-rw-r--r--tests/units/anta_tests/test_services.py2
-rw-r--r--tests/units/anta_tests/test_snmp.py199
-rw-r--r--tests/units/anta_tests/test_software.py2
-rw-r--r--tests/units/anta_tests/test_stp.py166
-rw-r--r--tests/units/anta_tests/test_stun.py61
-rw-r--r--tests/units/anta_tests/test_system.py192
-rw-r--r--tests/units/anta_tests/test_vlan.py2
-rw-r--r--tests/units/anta_tests/test_vxlan.py8
32 files changed, 2747 insertions, 124 deletions
diff --git a/tests/units/anta_tests/__init__.py b/tests/units/anta_tests/__init__.py
index 8ca0e8c..bfebc6d 100644
--- a/tests/units/anta_tests/__init__.py
+++ b/tests/units/anta_tests/__init__.py
@@ -1,4 +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.
-"""Test for anta.tests submodule."""
+"""Tests for anta.tests module."""
+
+import asyncio
+from typing import Any
+
+from anta.device import AntaDevice
+
+
+def test(device: AntaDevice, data: dict[str, Any]) -> None:
+ """Generic test function for AntaTest subclass.
+
+ Generate unit tests for each AntaTest subclass.
+
+ See `tests/units/anta_tests/README.md` for more information on how to use it.
+ """
+ # Instantiate the AntaTest subclass
+ test_instance = data["test"](device, inputs=data["inputs"], eos_data=data["eos_data"])
+ # Run the test() method
+ asyncio.run(test_instance.test())
+ # Assert expected result
+ assert test_instance.result.result == data["expected"]["result"], f"Expected '{data['expected']['result']}' result, got '{test_instance.result.result}'"
+ if "messages" in data["expected"]:
+ # We expect messages in test result
+ assert len(test_instance.result.messages) == len(data["expected"]["messages"])
+ # Test will pass if the expected message is included in the test result message
+ for message, expected in zip(test_instance.result.messages, data["expected"]["messages"]): # NOTE: zip(strict=True) has been added in Python 3.10
+ assert expected in message
+ else:
+ # Test result should not have messages
+ assert test_instance.result.messages == []
diff --git a/tests/units/anta_tests/conftest.py b/tests/units/anta_tests/conftest.py
new file mode 100644
index 0000000..5da7606
--- /dev/null
+++ b/tests/units/anta_tests/conftest.py
@@ -0,0 +1,35 @@
+# 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.
+"""See https://docs.pytest.org/en/stable/reference/fixtures.html#conftest-py-sharing-fixtures-across-multiple-files."""
+
+from typing import Any
+
+import pytest
+
+
+def build_test_id(val: dict[str, Any]) -> str:
+ """Build id for a unit test of an AntaTest subclass.
+
+ {
+ "name": "meaniful test name",
+ "test": <AntaTest instance>,
+ ...
+ }
+ """
+ return f"{val['test'].__module__}.{val['test'].__name__}-{val['name']}"
+
+
+def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
+ """Generate ANTA testts 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.
+ Test IDs are generated using the `build_test_id` function above.
+
+ Checking that only the function "test" is parametrized with data to allow for writing tests for helper functions
+ in each module.
+ """
+ if "tests.units.anta_tests" in metafunc.module.__package__ and metafunc.function.__name__ == "test":
+ # This is a unit test for an AntaTest subclass
+ metafunc.parametrize("data", metafunc.module.DATA, ids=build_test_id)
diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py
index e712e12..e256b04 100644
--- a/tests/units/anta_tests/routing/test_bgp.py
+++ b/tests/units/anta_tests/routing/test_bgp.py
@@ -8,22 +8,24 @@ from __future__ import annotations
from typing import Any
-# pylint: disable=C0413
-# because of the patch above
from anta.tests.routing.bgp import (
VerifyBGPAdvCommunities,
VerifyBGPExchangedRoutes,
VerifyBGPPeerASNCap,
VerifyBGPPeerCount,
+ VerifyBGPPeerDropStats,
VerifyBGPPeerMD5Auth,
VerifyBGPPeerMPCaps,
+ VerifyBGPPeerRouteLimit,
VerifyBGPPeerRouteRefreshCap,
VerifyBGPPeersHealth,
+ VerifyBGPPeerUpdateErrors,
+ VerifyBgpRouteMaps,
VerifyBGPSpecificPeers,
VerifyBGPTimers,
VerifyEVPNType2Route,
)
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -2199,6 +2201,152 @@ DATA: list[dict[str, Any]] = [
},
},
{
+ "name": "success-strict",
+ "test": VerifyBGPPeerMPCaps,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "172.30.11.1",
+ "neighborCapabilities": {
+ "multiprotocolCaps": {
+ "ipv4Unicast": {
+ "advertised": True,
+ "received": True,
+ "enabled": True,
+ },
+ "ipv4MplsLabels": {
+ "advertised": True,
+ "received": True,
+ "enabled": True,
+ },
+ }
+ },
+ }
+ ]
+ },
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "172.30.11.10",
+ "neighborCapabilities": {
+ "multiprotocolCaps": {
+ "ipv4Unicast": {
+ "advertised": True,
+ "received": True,
+ "enabled": True,
+ },
+ "ipv4MplsVpn": {
+ "advertised": True,
+ "received": True,
+ "enabled": True,
+ },
+ }
+ },
+ }
+ ]
+ },
+ }
+ }
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {
+ "peer_address": "172.30.11.1",
+ "vrf": "default",
+ "strict": True,
+ "capabilities": ["Ipv4 Unicast", "ipv4 Mpls labels"],
+ },
+ {
+ "peer_address": "172.30.11.10",
+ "vrf": "MGMT",
+ "strict": True,
+ "capabilities": ["ipv4 Unicast", "ipv4 MplsVpn"],
+ },
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-srict",
+ "test": VerifyBGPPeerMPCaps,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "172.30.11.1",
+ "neighborCapabilities": {
+ "multiprotocolCaps": {
+ "ipv4Unicast": {
+ "advertised": True,
+ "received": True,
+ "enabled": True,
+ },
+ "ipv4MplsLabels": {
+ "advertised": True,
+ "received": True,
+ "enabled": True,
+ },
+ }
+ },
+ }
+ ]
+ },
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "172.30.11.10",
+ "neighborCapabilities": {
+ "multiprotocolCaps": {
+ "ipv4Unicast": {
+ "advertised": True,
+ "received": True,
+ "enabled": True,
+ },
+ "ipv4MplsVpn": {
+ "advertised": False,
+ "received": True,
+ "enabled": True,
+ },
+ }
+ },
+ }
+ ]
+ },
+ }
+ }
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {
+ "peer_address": "172.30.11.1",
+ "vrf": "default",
+ "strict": True,
+ "capabilities": ["Ipv4 Unicast"],
+ },
+ {
+ "peer_address": "172.30.11.10",
+ "vrf": "MGMT",
+ "strict": True,
+ "capabilities": ["ipv4MplsVpn", "L2vpnEVPN"],
+ },
+ ]
+ },
+ "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.'}}}}"
+ ],
+ },
+ },
+ {
"name": "success",
"test": VerifyBGPPeerASNCap,
"eos_data": [
@@ -3722,4 +3870,970 @@ DATA: list[dict[str, Any]] = [
],
},
},
+ {
+ "name": "success",
+ "test": VerifyBGPPeerDropStats,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "dropStats": {
+ "inDropAsloop": 0,
+ "inDropClusterIdLoop": 0,
+ "inDropMalformedMpbgp": 0,
+ "inDropOrigId": 0,
+ "inDropNhLocal": 0,
+ "inDropNhAfV6": 0,
+ "prefixDroppedMartianV4": 0,
+ "prefixDroppedMaxRouteLimitViolatedV4": 0,
+ "prefixDroppedMartianV6": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "dropStats": {
+ "inDropAsloop": 0,
+ "inDropClusterIdLoop": 0,
+ "inDropMalformedMpbgp": 0,
+ "inDropOrigId": 0,
+ "inDropNhLocal": 0,
+ "inDropNhAfV6": 0,
+ "prefixDroppedMartianV4": 0,
+ "prefixDroppedMaxRouteLimitViolatedV4": 0,
+ "prefixDroppedMartianV6": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {
+ "peer_address": "10.100.0.8",
+ "vrf": "default",
+ "drop_stats": ["prefixDroppedMartianV4", "prefixDroppedMaxRouteLimitViolatedV4", "prefixDroppedMartianV6"],
+ },
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "drop_stats": ["inDropClusterIdLoop", "inDropOrigId", "inDropNhLocal"]},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-not-found",
+ "test": VerifyBGPPeerDropStats,
+ "eos_data": [
+ {"vrfs": {}},
+ {"vrfs": {}},
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {
+ "peer_address": "10.100.0.8",
+ "vrf": "default",
+ "drop_stats": ["prefixDroppedMartianV4", "prefixDroppedMaxRouteLimitViolatedV4", "prefixDroppedMartianV6"],
+ },
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "drop_stats": ["inDropClusterIdLoop", "inDropOrigId", "inDropNhLocal"]},
+ ]
+ },
+ "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'}}"
+ ],
+ },
+ },
+ {
+ "name": "failure",
+ "test": VerifyBGPPeerDropStats,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "dropStats": {
+ "inDropAsloop": 0,
+ "inDropClusterIdLoop": 0,
+ "inDropMalformedMpbgp": 0,
+ "inDropOrigId": 1,
+ "inDropNhLocal": 1,
+ "inDropNhAfV6": 0,
+ "prefixDroppedMartianV4": 1,
+ "prefixDroppedMaxRouteLimitViolatedV4": 1,
+ "prefixDroppedMartianV6": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "dropStats": {
+ "inDropAsloop": 0,
+ "inDropClusterIdLoop": 0,
+ "inDropMalformedMpbgp": 0,
+ "inDropOrigId": 1,
+ "inDropNhLocal": 1,
+ "inDropNhAfV6": 0,
+ "prefixDroppedMartianV4": 0,
+ "prefixDroppedMaxRouteLimitViolatedV4": 0,
+ "prefixDroppedMartianV6": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {
+ "peer_address": "10.100.0.8",
+ "vrf": "default",
+ "drop_stats": ["prefixDroppedMartianV4", "prefixDroppedMaxRouteLimitViolatedV4", "prefixDroppedMartianV6"],
+ },
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "drop_stats": ["inDropClusterIdLoop", "inDropOrigId", "inDropNhLocal"]},
+ ]
+ },
+ "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}}}"
+ ],
+ },
+ },
+ {
+ "name": "success-all-drop-stats",
+ "test": VerifyBGPPeerDropStats,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "dropStats": {
+ "inDropAsloop": 0,
+ "inDropClusterIdLoop": 0,
+ "inDropMalformedMpbgp": 0,
+ "inDropOrigId": 0,
+ "inDropNhLocal": 0,
+ "inDropNhAfV6": 0,
+ "prefixDroppedMartianV4": 0,
+ "prefixDroppedMaxRouteLimitViolatedV4": 0,
+ "prefixDroppedMartianV6": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "dropStats": {
+ "inDropAsloop": 0,
+ "inDropClusterIdLoop": 0,
+ "inDropMalformedMpbgp": 0,
+ "inDropOrigId": 0,
+ "inDropNhLocal": 0,
+ "inDropNhAfV6": 0,
+ "prefixDroppedMartianV4": 0,
+ "prefixDroppedMaxRouteLimitViolatedV4": 0,
+ "prefixDroppedMartianV6": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default"},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT"},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-all-drop-stats",
+ "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,
+ "prefixDroppedMartianV4": 1,
+ "prefixDroppedMaxRouteLimitViolatedV4": 1,
+ "prefixDroppedMartianV6": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "dropStats": {
+ "inDropAsloop": 2,
+ "inDropClusterIdLoop": 0,
+ "inDropMalformedMpbgp": 0,
+ "inDropOrigId": 1,
+ "inDropNhLocal": 1,
+ "inDropNhAfV6": 0,
+ "prefixDroppedMartianV4": 0,
+ "prefixDroppedMaxRouteLimitViolatedV4": 0,
+ "prefixDroppedMartianV6": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default"},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT"},
+ ]
+ },
+ "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'}}}"
+ ],
+ },
+ },
+ {
+ "name": "success",
+ "test": VerifyBGPPeerUpdateErrors,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 0,
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 0,
+ "disabledAfiSafi": "None",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 0,
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 0,
+ "disabledAfiSafi": "None",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi"]},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi"]},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-not-found",
+ "test": VerifyBGPPeerUpdateErrors,
+ "eos_data": [
+ {"vrfs": {}},
+ {"vrfs": {}},
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi"]},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi"]},
+ ]
+ },
+ "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'}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-errors",
+ "test": VerifyBGPPeerUpdateErrors,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 0,
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 0,
+ "disabledAfiSafi": "ipv4Unicast",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 1,
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 0,
+ "disabledAfiSafi": "None",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi"]},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi"]},
+ ]
+ },
+ "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}}}"
+ ],
+ },
+ },
+ {
+ "name": "success-all-error-counters",
+ "test": VerifyBGPPeerUpdateErrors,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 0,
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 0,
+ "disabledAfiSafi": "None",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 0,
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 0,
+ "disabledAfiSafi": "None",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default"},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT"},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-all-error-counters",
+ "test": VerifyBGPPeerUpdateErrors,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 1,
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 0,
+ "disabledAfiSafi": "ipv4Unicast",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 1,
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 1,
+ "disabledAfiSafi": "None",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi"]},
+ {
+ "peer_address": "10.100.0.9",
+ "vrf": "MGMT",
+ "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi", "inUpdErrDisableAfiSafi"],
+ },
+ ]
+ },
+ "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}}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-all-not-found",
+ "test": VerifyBGPPeerUpdateErrors,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "peerInUpdateErrors": {
+ "inUpdErrIgnore": 0,
+ "inUpdErrDisableAfiSafi": 0,
+ "disabledAfiSafi": "ipv4Unicast",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "peerInUpdateErrors": {
+ "inUpdErrWithdraw": 1,
+ "inUpdErrIgnore": 0,
+ "disabledAfiSafi": "None",
+ "lastUpdErrTime": 0,
+ },
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi"]},
+ {
+ "peer_address": "10.100.0.9",
+ "vrf": "MGMT",
+ "update_errors": ["inUpdErrWithdraw", "inUpdErrIgnore", "disabledAfiSafi", "inUpdErrDisableAfiSafi"],
+ },
+ ]
+ },
+ "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'}}}"
+ ],
+ },
+ },
+ {
+ "name": "success",
+ "test": VerifyBgpRouteMaps,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "routeMapInbound": "RM-MLAG-PEER-IN",
+ "routeMapOutbound": "RM-MLAG-PEER-OUT",
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.10",
+ "routeMapInbound": "RM-MLAG-PEER-IN",
+ "routeMapOutbound": "RM-MLAG-PEER-OUT",
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"},
+ {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-incorrect-route-map",
+ "test": VerifyBgpRouteMaps,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "routeMapInbound": "RM-MLAG-PEER",
+ "routeMapOutbound": "RM-MLAG-PEER",
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.10",
+ "routeMapInbound": "RM-MLAG-PEER",
+ "routeMapOutbound": "RM-MLAG-PEER",
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"},
+ {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n"
+ "{'10.100.0.8': {'default': {'Inbound route-map': 'RM-MLAG-PEER', 'Outbound route-map': 'RM-MLAG-PEER'}}, "
+ "'10.100.0.10': {'MGMT': {'Inbound route-map': 'RM-MLAG-PEER', 'Outbound route-map': 'RM-MLAG-PEER'}}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-incorrect-inbound-map",
+ "test": VerifyBgpRouteMaps,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "routeMapInbound": "RM-MLAG-PEER",
+ "routeMapOutbound": "RM-MLAG-PEER",
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.10",
+ "routeMapInbound": "RM-MLAG-PEER",
+ "routeMapOutbound": "RM-MLAG-PEER",
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN"},
+ {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN"},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n"
+ "{'10.100.0.8': {'default': {'Inbound route-map': 'RM-MLAG-PEER'}}, '10.100.0.10': {'MGMT': {'Inbound route-map': 'RM-MLAG-PEER'}}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-route-maps-not-configured",
+ "test": VerifyBgpRouteMaps,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.10",
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"},
+ {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN", "outbound_route_map": "RM-MLAG-PEER-OUT"},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n"
+ "{'10.100.0.8': {'default': {'Inbound route-map': 'Not Configured', 'Outbound route-map': 'Not Configured'}}, "
+ "'10.100.0.10': {'MGMT': {'Inbound route-map': 'Not Configured', 'Outbound route-map': 'Not Configured'}}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-peer-not-found",
+ "test": VerifyBgpRouteMaps,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {"peerList": []},
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {"peerList": []},
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "inbound_route_map": "RM-MLAG-PEER-IN"},
+ {"peer_address": "10.100.0.10", "vrf": "MGMT", "inbound_route_map": "RM-MLAG-PEER-IN"},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "The following BGP peers are not configured or has an incorrect or missing route map in either the inbound or outbound direction:\n"
+ "{'10.100.0.8': {'default': 'Not configured'}, '10.100.0.10': {'MGMT': 'Not configured'}}"
+ ],
+ },
+ },
+ {
+ "name": "success",
+ "test": VerifyBGPPeerRouteLimit,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "maxTotalRoutes": 12000,
+ "totalRoutesWarnLimit": 10000,
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "maxTotalRoutes": 10000,
+ "totalRoutesWarnLimit": 9000,
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "maximum_routes": 12000, "warning_limit": 10000},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "maximum_routes": 10000},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-peer-not-found",
+ "test": VerifyBGPPeerRouteLimit,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {},
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {},
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "maximum_routes": 12000, "warning_limit": 10000},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "maximum_routes": 10000, "warning_limit": 9000},
+ ]
+ },
+ "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'}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-incorrect-max-routes",
+ "test": VerifyBGPPeerRouteLimit,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "maxTotalRoutes": 13000,
+ "totalRoutesWarnLimit": 11000,
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ "maxTotalRoutes": 11000,
+ "totalRoutesWarnLimit": 10000,
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "maximum_routes": 12000, "warning_limit": 10000},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "maximum_routes": 10000, "warning_limit": 9000},
+ ]
+ },
+ "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}}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-routes-not-found",
+ "test": VerifyBGPPeerRouteLimit,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.8",
+ "maxTotalRoutes": 12000,
+ }
+ ]
+ },
+ },
+ },
+ {
+ "vrfs": {
+ "MGMT": {
+ "peerList": [
+ {
+ "peerAddress": "10.100.0.9",
+ }
+ ]
+ },
+ },
+ },
+ ],
+ "inputs": {
+ "bgp_peers": [
+ {"peer_address": "10.100.0.8", "vrf": "default", "maximum_routes": 12000, "warning_limit": 10000},
+ {"peer_address": "10.100.0.9", "vrf": "MGMT", "maximum_routes": 10000, "warning_limit": 9000},
+ ]
+ },
+ "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'}}}"
+ ],
+ },
+ },
]
diff --git a/tests/units/anta_tests/routing/test_generic.py b/tests/units/anta_tests/routing/test_generic.py
index 36658f5..20f83b9 100644
--- a/tests/units/anta_tests/routing/test_generic.py
+++ b/tests/units/anta_tests/routing/test_generic.py
@@ -5,10 +5,14 @@
from __future__ import annotations
+import sys
from typing import Any
+import pytest
+from pydantic import ValidationError
+
from anta.tests.routing.generic import VerifyRoutingProtocolModel, VerifyRoutingTableEntry, VerifyRoutingTableSize
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -67,16 +71,6 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "failure", "messages": ["routing-table has 1000 routes and not between min (42) and maximum (666)"]},
},
{
- "name": "error-max-smaller-than-min",
- "test": VerifyRoutingTableSize,
- "eos_data": [{}],
- "inputs": {"minimum": 666, "maximum": 42},
- "expected": {
- "result": "error",
- "messages": ["Minimum 666 is greater than maximum 42"],
- },
- },
- {
"name": "success",
"test": VerifyRoutingTableEntry,
"eos_data": [
@@ -131,6 +125,48 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "success"},
},
{
+ "name": "success-collect-all",
+ "test": VerifyRoutingTableEntry,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "routingDisabled": False,
+ "allRoutesProgrammedHardware": True,
+ "allRoutesProgrammedKernel": True,
+ "defaultRouteState": "notSet",
+ "routes": {
+ "10.1.0.1/32": {
+ "hardwareProgrammed": True,
+ "routeType": "eBGP",
+ "routeLeaked": False,
+ "kernelProgrammed": True,
+ "routeAction": "forward",
+ "directlyConnected": False,
+ "preference": 20,
+ "metric": 0,
+ "vias": [{"nexthopAddr": "10.1.255.4", "interface": "Ethernet1"}],
+ },
+ "10.1.0.2/32": {
+ "hardwareProgrammed": True,
+ "routeType": "eBGP",
+ "routeLeaked": False,
+ "kernelProgrammed": True,
+ "routeAction": "forward",
+ "directlyConnected": False,
+ "preference": 20,
+ "metric": 0,
+ "vias": [{"nexthopAddr": "10.1.255.6", "interface": "Ethernet2"}],
+ },
+ },
+ },
+ },
+ },
+ ],
+ "inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"], "collect": "all"},
+ "expected": {"result": "success"},
+ },
+ {
"name": "failure-missing-route",
"test": VerifyRoutingTableEntry,
"eos_data": [
@@ -226,4 +262,75 @@ DATA: list[dict[str, Any]] = [
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"]},
"expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: ['10.1.0.2']"]},
},
+ {
+ "name": "failure-wrong-route-collect-all",
+ "test": VerifyRoutingTableEntry,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "routingDisabled": False,
+ "allRoutesProgrammedHardware": True,
+ "allRoutesProgrammedKernel": True,
+ "defaultRouteState": "notSet",
+ "routes": {
+ "10.1.0.1/32": {
+ "hardwareProgrammed": True,
+ "routeType": "eBGP",
+ "routeLeaked": False,
+ "kernelProgrammed": True,
+ "routeAction": "forward",
+ "directlyConnected": False,
+ "preference": 20,
+ "metric": 0,
+ "vias": [{"nexthopAddr": "10.1.255.4", "interface": "Ethernet1"}],
+ },
+ "10.1.0.55/32": {
+ "hardwareProgrammed": True,
+ "routeType": "eBGP",
+ "routeLeaked": False,
+ "kernelProgrammed": True,
+ "routeAction": "forward",
+ "directlyConnected": False,
+ "preference": 20,
+ "metric": 0,
+ "vias": [{"nexthopAddr": "10.1.255.6", "interface": "Ethernet2"}],
+ },
+ },
+ },
+ },
+ },
+ ],
+ "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']"]},
+ },
]
+
+
+class TestVerifyRoutingTableSizeInputs:
+ """Test anta.tests.routing.generic.VerifyRoutingTableSize.Input."""
+
+ @pytest.mark.parametrize(
+ ("minimum", "maximum"),
+ [
+ pytest.param(0, 0, id="zero"),
+ pytest.param(1, 2, id="1<2"),
+ pytest.param(0, sys.maxsize, id="max"),
+ ],
+ )
+ def test_valid(self, minimum: int, maximum: int) -> None:
+ """Test VerifyRoutingTableSize valid inputs."""
+ VerifyRoutingTableSize.Input(minimum=minimum, maximum=maximum)
+
+ @pytest.mark.parametrize(
+ ("minimum", "maximum"),
+ [
+ pytest.param(-2, -1, id="negative"),
+ pytest.param(2, 1, id="2<1"),
+ pytest.param(sys.maxsize, 0, id="max"),
+ ],
+ )
+ def test_invalid(self, minimum: int, maximum: int) -> None:
+ """Test VerifyRoutingTableSize invalid inputs."""
+ with pytest.raises(ValidationError):
+ VerifyRoutingTableSize.Input(minimum=minimum, maximum=maximum)
diff --git a/tests/units/anta_tests/routing/test_isis.py b/tests/units/anta_tests/routing/test_isis.py
index 2167ea4..84f5bdc 100644
--- a/tests/units/anta_tests/routing/test_isis.py
+++ b/tests/units/anta_tests/routing/test_isis.py
@@ -20,7 +20,7 @@ from anta.tests.routing.isis import (
VerifyISISSegmentRoutingTunnels,
_get_interface_data,
)
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/routing/test_ospf.py b/tests/units/anta_tests/routing/test_ospf.py
index 81d8010..1555af6 100644
--- a/tests/units/anta_tests/routing/test_ospf.py
+++ b/tests/units/anta_tests/routing/test_ospf.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.routing.ospf import VerifyOSPFMaxLSA, VerifyOSPFNeighborCount, VerifyOSPFNeighborState
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_aaa.py b/tests/units/anta_tests/test_aaa.py
index 40bf82e..119e206 100644
--- a/tests/units/anta_tests/test_aaa.py
+++ b/tests/units/anta_tests/test_aaa.py
@@ -16,7 +16,7 @@ from anta.tests.aaa import (
VerifyTacacsServers,
VerifyTacacsSourceIntf,
)
-from tests.lib.anta import test # noqa: F401; pylint: disable=unused-import
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_avt.py b/tests/units/anta_tests/test_avt.py
index 7ef6be3..80fbce0 100644
--- a/tests/units/anta_tests/test_avt.py
+++ b/tests/units/anta_tests/test_avt.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.avt import VerifyAVTPathHealth, VerifyAVTRole, VerifyAVTSpecificPath
-from tests.lib.anta import test # noqa: F401; pylint: disable=unused-import
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_bfd.py b/tests/units/anta_tests/test_bfd.py
index 54dc7a0..9bd6465 100644
--- a/tests/units/anta_tests/test_bfd.py
+++ b/tests/units/anta_tests/test_bfd.py
@@ -8,10 +8,8 @@ from __future__ import annotations
from typing import Any
-# pylint: disable=C0413
-# because of the patch above
-from anta.tests.bfd import VerifyBFDPeersHealth, VerifyBFDPeersIntervals, VerifyBFDSpecificPeers
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from anta.tests.bfd import VerifyBFDPeersHealth, VerifyBFDPeersIntervals, VerifyBFDPeersRegProtocols, VerifyBFDSpecificPeers
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -163,8 +161,8 @@ DATA: list[dict[str, Any]] = [
"result": "failure",
"messages": [
"Following BFD peers are not configured or timers are not correct:\n"
- "{'192.0.255.7': {'default': {'tx_interval': 1300000, 'rx_interval': 1200000, 'multiplier': 4}}, "
- "'192.0.255.70': {'MGMT': {'tx_interval': 120000, 'rx_interval': 120000, 'multiplier': 5}}}"
+ "{'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}}}"
],
},
},
@@ -519,4 +517,133 @@ DATA: list[dict[str, Any]] = [
],
},
},
+ {
+ "name": "success",
+ "test": VerifyBFDPeersRegProtocols,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "ipv4Neighbors": {
+ "192.0.255.7": {
+ "peerStats": {
+ "": {
+ "status": "up",
+ "remoteDisc": 108328132,
+ "peerStatsDetail": {
+ "role": "active",
+ "apps": ["ospf"],
+ },
+ }
+ }
+ }
+ }
+ },
+ "MGMT": {
+ "ipv4Neighbors": {
+ "192.0.255.70": {
+ "peerStats": {
+ "": {
+ "status": "up",
+ "remoteDisc": 108328132,
+ "peerStatsDetail": {
+ "role": "active",
+ "apps": ["bgp"],
+ },
+ }
+ }
+ }
+ }
+ },
+ }
+ }
+ ],
+ "inputs": {
+ "bfd_peers": [
+ {"peer_address": "192.0.255.7", "vrf": "default", "protocols": ["ospf"]},
+ {"peer_address": "192.0.255.70", "vrf": "MGMT", "protocols": ["bgp"]},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure",
+ "test": VerifyBFDPeersRegProtocols,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {
+ "ipv4Neighbors": {
+ "192.0.255.7": {
+ "peerStats": {
+ "": {
+ "status": "up",
+ "peerStatsDetail": {
+ "role": "active",
+ "apps": ["ospf"],
+ },
+ }
+ }
+ }
+ }
+ },
+ "MGMT": {
+ "ipv4Neighbors": {
+ "192.0.255.70": {
+ "peerStats": {
+ "": {
+ "status": "up",
+ "remoteDisc": 0,
+ "peerStatsDetail": {
+ "role": "active",
+ "apps": ["bgp"],
+ },
+ }
+ }
+ }
+ }
+ },
+ }
+ }
+ ],
+ "inputs": {
+ "bfd_peers": [
+ {"peer_address": "192.0.255.7", "vrf": "default", "protocols": ["isis"]},
+ {"peer_address": "192.0.255.70", "vrf": "MGMT", "protocols": ["isis"]},
+ ]
+ },
+ "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']}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-not-found",
+ "test": VerifyBFDPeersRegProtocols,
+ "eos_data": [
+ {
+ "vrfs": {
+ "default": {},
+ "MGMT": {},
+ }
+ }
+ ],
+ "inputs": {
+ "bfd_peers": [
+ {"peer_address": "192.0.255.7", "vrf": "default", "protocols": ["isis"]},
+ {"peer_address": "192.0.255.70", "vrf": "MGMT", "protocols": ["isis"]},
+ ]
+ },
+ "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'}}"
+ ],
+ },
+ },
]
diff --git a/tests/units/anta_tests/test_configuration.py b/tests/units/anta_tests/test_configuration.py
index 7f198a3..d8f86be 100644
--- a/tests/units/anta_tests/test_configuration.py
+++ b/tests/units/anta_tests/test_configuration.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.configuration import VerifyRunningConfigDiffs, VerifyRunningConfigLines, VerifyZeroTouch
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -60,14 +60,4 @@ DATA: list[dict[str, Any]] = [
"inputs": {"regex_patterns": ["bla", "bleh"]},
"expected": {"result": "failure", "messages": ["Following patterns were not found: 'bla','bleh'"]},
},
- {
- "name": "failure-invalid-regex",
- "test": VerifyRunningConfigLines,
- "eos_data": ["enable password something\nsome other line"],
- "inputs": {"regex_patterns": ["["]},
- "expected": {
- "result": "error",
- "messages": ["1 validation error for Input\nregex_patterns.0\n Value error, Invalid regex: unterminated character set at position 0"],
- },
- },
]
diff --git a/tests/units/anta_tests/test_connectivity.py b/tests/units/anta_tests/test_connectivity.py
index bd30811..beeaae6 100644
--- a/tests/units/anta_tests/test_connectivity.py
+++ b/tests/units/anta_tests/test_connectivity.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.connectivity import VerifyLLDPNeighbors, VerifyReachability
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -100,6 +100,28 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "success"},
},
{
+ "name": "success-df-bit-size",
+ "test": VerifyReachability,
+ "inputs": {"hosts": [{"destination": "10.0.0.1", "source": "Management0", "repeat": 5, "size": 1500, "df_bit": True}]},
+ "eos_data": [
+ {
+ "messages": [
+ """PING 10.0.0.1 (10.0.0.1) from 172.20.20.6 : 1472(1500) bytes of data.
+ 1480 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.085 ms
+ 1480 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.020 ms
+ 1480 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=0.019 ms
+ 1480 bytes from 10.0.0.1: icmp_seq=4 ttl=64 time=0.018 ms
+ 1480 bytes from 10.0.0.1: icmp_seq=5 ttl=64 time=0.017 ms
+
+ --- 10.0.0.1 ping statistics ---
+ 5 packets transmitted, 5 received, 0% packet loss, time 0ms
+ rtt min/avg/max/mdev = 0.017/0.031/0.085/0.026 ms, ipg/ewma 0.061/0.057 ms""",
+ ],
+ },
+ ],
+ "expected": {"result": "success"},
+ },
+ {
"name": "failure-ip",
"test": VerifyReachability,
"inputs": {"hosts": [{"destination": "10.0.0.11", "source": "10.0.0.5"}, {"destination": "10.0.0.2", "source": "10.0.0.5"}]},
@@ -168,6 +190,28 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "failure", "messages": ["Connectivity test failed for the following source-destination pairs: [('Management0', '10.0.0.11')]"]},
},
{
+ "name": "failure-size",
+ "test": VerifyReachability,
+ "inputs": {"hosts": [{"destination": "10.0.0.1", "source": "Management0", "repeat": 5, "size": 1501, "df_bit": True}]},
+ "eos_data": [
+ {
+ "messages": [
+ """PING 10.0.0.1 (10.0.0.1) from 172.20.20.6 : 1473(1501) bytes of data.
+ ping: local error: message too long, mtu=1500
+ ping: local error: message too long, mtu=1500
+ ping: local error: message too long, mtu=1500
+ ping: local error: message too long, mtu=1500
+ ping: local error: message too long, mtu=1500
+
+ --- 10.0.0.1 ping statistics ---
+ 5 packets transmitted, 0 received, +5 errors, 100% packet loss, time 40ms
+ """,
+ ],
+ },
+ ],
+ "expected": {"result": "failure", "messages": ["Connectivity test failed for the following source-destination pairs: [('Management0', '10.0.0.1')]"]},
+ },
+ {
"name": "success",
"test": VerifyLLDPNeighbors,
"inputs": {
diff --git a/tests/units/anta_tests/test_field_notices.py b/tests/units/anta_tests/test_field_notices.py
index 3cb7286..8e7c9d8 100644
--- a/tests/units/anta_tests/test_field_notices.py
+++ b/tests/units/anta_tests/test_field_notices.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.field_notices import VerifyFieldNotice44Resolution, VerifyFieldNotice72Resolution
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -358,8 +358,8 @@ DATA: list[dict[str, Any]] = [
],
"inputs": None,
"expected": {
- "result": "error",
- "messages": ["Error in running test - FixedSystemvrm1 not found"],
+ "result": "failure",
+ "messages": ["Error in running test - Component FixedSystemvrm1 not found in 'show version'"],
},
},
]
diff --git a/tests/units/anta_tests/test_flow_tracking.py b/tests/units/anta_tests/test_flow_tracking.py
new file mode 100644
index 0000000..f50a76b
--- /dev/null
+++ b/tests/units/anta_tests/test_flow_tracking.py
@@ -0,0 +1,391 @@
+# 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 inputs for anta.tests.flow_tracking."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from anta.tests.flow_tracking import VerifyHardwareFlowTrackerStatus
+from tests.units.anta_tests import test
+
+DATA: list[dict[str, Any]] = [
+ {
+ "name": "success",
+ "test": VerifyHardwareFlowTrackerStatus,
+ "eos_data": [
+ {
+ "trackers": {
+ "FLOW-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "HARDWARE-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ ],
+ "inputs": {"trackers": [{"name": "FLOW-TRACKER"}, {"name": "HARDWARE-TRACKER"}]},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-with-optional-field",
+ "test": VerifyHardwareFlowTrackerStatus,
+ "eos_data": [
+ {
+ "trackers": {
+ "FLOW-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "HARDWARE-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ ],
+ "inputs": {
+ "trackers": [
+ {
+ "name": "FLOW-TRACKER",
+ "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000},
+ "exporters": [{"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000}],
+ },
+ {
+ "name": "HARDWARE-TRACKER",
+ "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000},
+ "exporters": [{"name": "CVP-TELEMETRY", "local_interface": "Loopback10", "template_interval": 3600000}],
+ },
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-flow-tracking-not-running",
+ "test": VerifyHardwareFlowTrackerStatus,
+ "eos_data": [{"trackers": {}, "running": False}],
+ "inputs": {"trackers": [{"name": "FLOW-TRACKER"}]},
+ "expected": {
+ "result": "failure",
+ "messages": ["Hardware flow tracking is not running."],
+ },
+ },
+ {
+ "name": "failure-tracker-not-configured",
+ "test": VerifyHardwareFlowTrackerStatus,
+ "eos_data": [
+ {
+ "trackers": {
+ "HARDWARE-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ }
+ ],
+ "inputs": {"trackers": [{"name": "FLOW-Sample"}]},
+ "expected": {
+ "result": "failure",
+ "messages": ["Hardware flow tracker `FLOW-Sample` is not configured."],
+ },
+ },
+ {
+ "name": "failure-tracker-not-active",
+ "test": VerifyHardwareFlowTrackerStatus,
+ "eos_data": [
+ {
+ "trackers": {
+ "FLOW-TRACKER": {
+ "active": False,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "HARDWARE-TRACKER": {
+ "active": False,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ ],
+ "inputs": {
+ "trackers": [
+ {
+ "name": "FLOW-TRACKER",
+ "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000},
+ "exporters": [{"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000}],
+ },
+ {
+ "name": "HARDWARE-TRACKER",
+ "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000},
+ "exporters": [{"name": "CVP-TELEMETRY", "local_interface": "Loopback10", "template_interval": 3600000}],
+ },
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": ["Hardware flow tracker `FLOW-TRACKER` is not active.", "Hardware flow tracker `HARDWARE-TRACKER` is not active."],
+ },
+ },
+ {
+ "name": "failure-incorrect-record-export",
+ "test": VerifyHardwareFlowTrackerStatus,
+ "eos_data": [
+ {
+ "trackers": {
+ "FLOW-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "HARDWARE-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 6000,
+ "activeInterval": 30000,
+ "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ ],
+ "inputs": {
+ "trackers": [
+ {
+ "name": "FLOW-TRACKER",
+ "record_export": {"on_inactive_timeout": 6000, "on_interval": 30000},
+ },
+ {
+ "name": "HARDWARE-TRACKER",
+ "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000},
+ },
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "FLOW-TRACKER: \n"
+ "Expected `6000` as the inactive timeout, but found `60000` instead.\nExpected `30000` as the interval, but found `300000` instead.\n",
+ "HARDWARE-TRACKER: \n"
+ "Expected `60000` as the inactive timeout, but found `6000` instead.\nExpected `300000` as the interval, but found `30000` instead.\n",
+ ],
+ },
+ },
+ {
+ "name": "failure-incorrect-exporters",
+ "test": VerifyHardwareFlowTrackerStatus,
+ "eos_data": [
+ {
+ "trackers": {
+ "FLOW-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {
+ "CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000},
+ "CVP-FLOW": {"localIntf": "Loopback0", "templateInterval": 3600000},
+ },
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "HARDWARE-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 6000,
+ "activeInterval": 30000,
+ "exporters": {
+ "CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000},
+ "Hardware-flow": {"localIntf": "Loopback10", "templateInterval": 3600000},
+ },
+ }
+ },
+ "running": True,
+ },
+ ],
+ "inputs": {
+ "trackers": [
+ {
+ "name": "FLOW-TRACKER",
+ "exporters": [
+ {"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000},
+ {"name": "CVP-FLOW", "local_interface": "Loopback10", "template_interval": 3500000},
+ ],
+ },
+ {
+ "name": "HARDWARE-TRACKER",
+ "exporters": [
+ {"name": "Hardware-flow", "local_interface": "Loopback99", "template_interval": 3000000},
+ {"name": "Reverse-flow", "local_interface": "Loopback101", "template_interval": 3000000},
+ ],
+ },
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "FLOW-TRACKER: \n"
+ "Exporter `CVP-FLOW`: \n"
+ "Expected `Loopback10` as the local interface, but found `Loopback0` instead.\n"
+ "Expected `3500000` as the template interval, but found `3600000` instead.\n",
+ "HARDWARE-TRACKER: \n"
+ "Exporter `Hardware-flow`: \n"
+ "Expected `Loopback99` as the local interface, but found `Loopback10` instead.\n"
+ "Expected `3000000` as the template interval, but found `3600000` instead.\n"
+ "Exporter `Reverse-flow` is not configured.\n",
+ ],
+ },
+ },
+ {
+ "name": "failure-all-type",
+ "test": VerifyHardwareFlowTrackerStatus,
+ "eos_data": [
+ {
+ "trackers": {
+ "HARDWARE-TRACKER": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "FLOW-TRIGGER": {
+ "active": False,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "HARDWARE-FLOW": {
+ "active": True,
+ "inactiveTimeout": 6000,
+ "activeInterval": 30000,
+ "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}},
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "FLOW-TRACKER2": {
+ "active": True,
+ "inactiveTimeout": 60000,
+ "activeInterval": 300000,
+ "exporters": {
+ "CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000},
+ "CVP-FLOW": {"localIntf": "Loopback0", "templateInterval": 3600000},
+ },
+ }
+ },
+ "running": True,
+ },
+ {
+ "trackers": {
+ "HARDWARE-TRACKER2": {
+ "active": True,
+ "inactiveTimeout": 6000,
+ "activeInterval": 30000,
+ "exporters": {
+ "CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000},
+ "Hardware-flow": {"localIntf": "Loopback10", "templateInterval": 3600000},
+ },
+ }
+ },
+ "running": True,
+ },
+ ],
+ "inputs": {
+ "trackers": [
+ {"name": "FLOW-Sample"},
+ {
+ "name": "FLOW-TRIGGER",
+ "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000},
+ "exporters": [{"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000}],
+ },
+ {
+ "name": "HARDWARE-FLOW",
+ "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000},
+ },
+ {
+ "name": "FLOW-TRACKER2",
+ "exporters": [
+ {"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000},
+ {"name": "CVP-FLOW", "local_interface": "Loopback10", "template_interval": 3500000},
+ ],
+ },
+ {
+ "name": "HARDWARE-TRACKER2",
+ "exporters": [
+ {"name": "Hardware-flow", "local_interface": "Loopback99", "template_interval": 3000000},
+ {"name": "Reverse-flow", "local_interface": "Loopback101", "template_interval": 3000000},
+ ],
+ },
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "Hardware flow tracker `FLOW-Sample` is not configured.",
+ "Hardware flow tracker `FLOW-TRIGGER` is not active.",
+ "HARDWARE-FLOW: \n"
+ "Expected `60000` as the inactive timeout, but found `6000` instead.\nExpected `300000` as the interval, but found `30000` instead.\n",
+ "FLOW-TRACKER2: \nExporter `CVP-FLOW`: \n"
+ "Expected `Loopback10` as the local interface, but found `Loopback0` instead.\n"
+ "Expected `3500000` as the template interval, but found `3600000` instead.\n",
+ "HARDWARE-TRACKER2: \nExporter `Hardware-flow`: \n"
+ "Expected `Loopback99` as the local interface, but found `Loopback10` instead.\n"
+ "Expected `3000000` as the template interval, but found `3600000` instead.\n"
+ "Exporter `Reverse-flow` is not configured.\n",
+ ],
+ },
+ },
+]
diff --git a/tests/units/anta_tests/test_greent.py b/tests/units/anta_tests/test_greent.py
index 2c48301..16f3616 100644
--- a/tests/units/anta_tests/test_greent.py
+++ b/tests/units/anta_tests/test_greent.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.greent import VerifyGreenT, VerifyGreenTCounters
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_hardware.py b/tests/units/anta_tests/test_hardware.py
index e601c68..646ca58 100644
--- a/tests/units/anta_tests/test_hardware.py
+++ b/tests/units/anta_tests/test_hardware.py
@@ -16,7 +16,7 @@ from anta.tests.hardware import (
VerifyTransceiversManufacturers,
VerifyTransceiversTemperature,
)
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py
index b8cf493..ea8106e 100644
--- a/tests/units/anta_tests/test_interfaces.py
+++ b/tests/units/anta_tests/test_interfaces.py
@@ -21,12 +21,13 @@ from anta.tests.interfaces import (
VerifyIpVirtualRouterMac,
VerifyL2MTU,
VerifyL3MTU,
+ VerifyLACPInterfacesStatus,
VerifyLoopbackCount,
VerifyPortChannels,
VerifyStormControlDrops,
VerifySVI,
)
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -651,7 +652,7 @@ DATA: list[dict[str, Any]] = [
],
"inputs": {"threshold": 70.0},
"expected": {
- "result": "error",
+ "result": "failure",
"messages": ["Interface Ethernet1/1 or one of its member interfaces is not Full-Duplex. VerifyInterfaceUtilization has not been implemented."],
},
},
@@ -796,7 +797,7 @@ DATA: list[dict[str, Any]] = [
],
"inputs": {"threshold": 70.0},
"expected": {
- "result": "error",
+ "result": "failure",
"messages": ["Interface Port-Channel31 or one of its member interfaces is not Full-Duplex. VerifyInterfaceUtilization has not been implemented."],
},
},
@@ -2441,4 +2442,127 @@ DATA: list[dict[str, Any]] = [
],
},
},
+ {
+ "name": "success",
+ "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-Channel5"}]},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-not-bundled",
+ "test": VerifyLACPInterfacesStatus,
+ "eos_data": [
+ {
+ "portChannels": {
+ "Port-Channel5": {
+ "interfaces": {
+ "Ethernet5": {
+ "actorPortStatus": "No Aggregate",
+ }
+ }
+ }
+ },
+ "interface": "Ethernet5",
+ "orphanPorts": {},
+ }
+ ],
+ "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"],
+ },
+ },
+ {
+ "name": "failure-no-details-found",
+ "test": VerifyLACPInterfacesStatus,
+ "eos_data": [
+ {
+ "portChannels": {"Port-Channel5": {"interfaces": {}}},
+ }
+ ],
+ "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Po 5"}]},
+ "expected": {
+ "result": "failure",
+ "messages": ["Interface 'Ethernet5' is not configured to be a member of LACP 'Port-Channel5'."],
+ },
+ },
+ {
+ "name": "failure-lacp-params",
+ "test": VerifyLACPInterfacesStatus,
+ "eos_data": [
+ {
+ "portChannels": {
+ "Port-Channel5": {
+ "interfaces": {
+ "Ethernet5": {
+ "actorPortStatus": "bundled",
+ "partnerPortState": {
+ "activity": False,
+ "timeout": False,
+ "aggregation": False,
+ "synchronization": False,
+ "collecting": True,
+ "distributing": True,
+ },
+ "actorPortState": {
+ "activity": False,
+ "timeout": False,
+ "aggregation": False,
+ "synchronization": False,
+ "collecting": True,
+ "distributing": True,
+ },
+ }
+ }
+ }
+ },
+ "interface": "Ethernet5",
+ "orphanPorts": {},
+ }
+ ],
+ "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "port-channel 5"}]},
+ "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"
+ ],
+ },
+ },
]
diff --git a/tests/units/anta_tests/test_lanz.py b/tests/units/anta_tests/test_lanz.py
index bfbf6ae..03694d4 100644
--- a/tests/units/anta_tests/test_lanz.py
+++ b/tests/units/anta_tests/test_lanz.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.lanz import VerifyLANZ
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_logging.py b/tests/units/anta_tests/test_logging.py
index d46c865..b429436 100644
--- a/tests/units/anta_tests/test_logging.py
+++ b/tests/units/anta_tests/test_logging.py
@@ -17,7 +17,7 @@ from anta.tests.logging import (
VerifyLoggingSourceIntf,
VerifyLoggingTimestamp,
)
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -201,7 +201,7 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "failure", "messages": ["Logs are not generated with the device FQDN"]},
},
{
- "name": "success",
+ "name": "success-negative-offset",
"test": VerifyLoggingTimestamp,
"eos_data": [
"",
@@ -214,6 +214,19 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "success"},
},
{
+ "name": "success-positive-offset",
+ "test": VerifyLoggingTimestamp,
+ "eos_data": [
+ "",
+ "2023-05-10T15:41:44.680813+05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
+ "Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingTimestamp validation\n"
+ "2023-05-10T15:42:44.680813+05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
+ "Other log\n",
+ ],
+ "inputs": None,
+ "expected": {"result": "success"},
+ },
+ {
"name": "failure",
"test": VerifyLoggingTimestamp,
"eos_data": [
diff --git a/tests/units/anta_tests/test_mlag.py b/tests/units/anta_tests/test_mlag.py
index ae8ff7c..193d69c 100644
--- a/tests/units/anta_tests/test_mlag.py
+++ b/tests/units/anta_tests/test_mlag.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.mlag import VerifyMlagConfigSanity, VerifyMlagDualPrimary, VerifyMlagInterfaces, VerifyMlagPrimaryPriority, VerifyMlagReloadDelay, VerifyMlagStatus
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -111,17 +111,6 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "skipped", "messages": ["MLAG is disabled"]},
},
{
- "name": "error",
- "test": VerifyMlagConfigSanity,
- "eos_data": [
- {
- "dummy": False,
- },
- ],
- "inputs": None,
- "expected": {"result": "error", "messages": ["Incorrect JSON response - 'mlagActive' state was not found"]},
- },
- {
"name": "failure-global",
"test": VerifyMlagConfigSanity,
"eos_data": [
diff --git a/tests/units/anta_tests/test_multicast.py b/tests/units/anta_tests/test_multicast.py
index a52a1d2..1fdcadd 100644
--- a/tests/units/anta_tests/test_multicast.py
+++ b/tests/units/anta_tests/test_multicast.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.multicast import VerifyIGMPSnoopingGlobal, VerifyIGMPSnoopingVlans
-from tests.lib.anta import test # noqa: F401; pylint: disable=unused-import
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_path_selection.py b/tests/units/anta_tests/test_path_selection.py
index c5fb079..d1882d0 100644
--- a/tests/units/anta_tests/test_path_selection.py
+++ b/tests/units/anta_tests/test_path_selection.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.path_selection import VerifyPathsHealth, VerifySpecificPath
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_profiles.py b/tests/units/anta_tests/test_profiles.py
index d58e987..f822d09 100644
--- a/tests/units/anta_tests/test_profiles.py
+++ b/tests/units/anta_tests/test_profiles.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.profiles import VerifyTcamProfile, VerifyUnifiedForwardingTableMode
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_ptp.py b/tests/units/anta_tests/test_ptp.py
index 8f4c77f..fc94480 100644
--- a/tests/units/anta_tests/test_ptp.py
+++ b/tests/units/anta_tests/test_ptp.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.ptp import VerifyPtpGMStatus, VerifyPtpLockStatus, VerifyPtpModeStatus, VerifyPtpOffset, VerifyPtpPortModeStatus
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -295,14 +295,14 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "success"},
},
{
- "name": "failure",
+ "name": "failure-no-interfaces",
"test": VerifyPtpPortModeStatus,
"eos_data": [{"ptpIntfSummaries": {}}],
"inputs": None,
"expected": {"result": "failure", "messages": ["No interfaces are PTP enabled"]},
},
{
- "name": "failure",
+ "name": "failure-invalid-state",
"test": VerifyPtpPortModeStatus,
"eos_data": [
{
diff --git a/tests/units/anta_tests/test_security.py b/tests/units/anta_tests/test_security.py
index 3a732bd..0d4a478 100644
--- a/tests/units/anta_tests/test_security.py
+++ b/tests/units/anta_tests/test_security.py
@@ -7,6 +7,9 @@ from __future__ import annotations
from typing import Any
+import pytest
+from pydantic import ValidationError
+
from anta.tests.security import (
VerifyAPIHttpsSSL,
VerifyAPIHttpStatus,
@@ -15,6 +18,7 @@ from anta.tests.security import (
VerifyAPISSLCertificate,
VerifyBannerLogin,
VerifyBannerMotd,
+ VerifyHardwareEntropy,
VerifyIPSecConnHealth,
VerifyIPv4ACL,
VerifySpecificIPSecConn,
@@ -23,7 +27,7 @@ from anta.tests.security import (
VerifySSHStatus,
VerifyTelnetStatus,
)
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -38,16 +42,36 @@ DATA: list[dict[str, Any]] = [
"test": VerifySSHStatus,
"eos_data": ["SSH per host connection limit is 20\nFIPS status: disabled\n\n"],
"inputs": None,
- "expected": {"result": "error", "messages": ["Could not find SSH status in returned output."]},
+ "expected": {"result": "failure", "messages": ["Could not find SSH status in returned output."]},
},
{
- "name": "failure-ssh-disabled",
+ "name": "failure-ssh-enabled",
"test": VerifySSHStatus,
"eos_data": ["SSHD status for Default VRF is enabled\nSSH connection limit is 50\nSSH per host connection limit is 20\nFIPS status: disabled\n\n"],
"inputs": None,
"expected": {"result": "failure", "messages": ["SSHD status for Default VRF is enabled"]},
},
{
+ "name": "success-4.32",
+ "test": VerifySSHStatus,
+ "eos_data": [
+ "User certificate authentication methods: none (neither trusted CA nor SSL profile configured)\n"
+ "SSHD status for Default VRF: disabled\nSSH connection limit: 50\nSSH per host connection limit: 20\nFIPS status: disabled\n\n"
+ ],
+ "inputs": None,
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-ssh-enabled-4.32",
+ "test": VerifySSHStatus,
+ "eos_data": [
+ "User certificate authentication methods: none (neither trusted CA nor SSL profile configured)\n"
+ "SSHD status for Default VRF: enabled\nSSH connection limit: 50\nSSH per host connection limit: 20\nFIPS status: disabled\n\n"
+ ],
+ "inputs": None,
+ "expected": {"result": "failure", "messages": ["SSHD status for Default VRF: enabled"]},
+ },
+ {
"name": "success",
"test": VerifySSHIPv4Acl,
"eos_data": [{"ipAclList": {"aclList": [{"type": "Ip4Acl", "name": "ACL_IPV4_SSH", "configuredVrfs": ["MGMT"], "activeVrfs": ["MGMT"]}]}}],
@@ -581,40 +605,6 @@ DATA: list[dict[str, Any]] = [
},
},
{
- "name": "error-wrong-input-rsa",
- "test": VerifyAPISSLCertificate,
- "eos_data": [],
- "inputs": {
- "certificates": [
- {
- "certificate_name": "ARISTA_ROOT_CA.crt",
- "expiry_threshold": 30,
- "common_name": "Arista Networks Internal IT Root Cert Authority",
- "encryption_algorithm": "RSA",
- "key_size": 256,
- },
- ]
- },
- "expected": {"result": "error", "messages": ["Allowed sizes are (2048, 3072, 4096)."]},
- },
- {
- "name": "error-wrong-input-ecdsa",
- "test": VerifyAPISSLCertificate,
- "eos_data": [],
- "inputs": {
- "certificates": [
- {
- "certificate_name": "ARISTA_SIGNING_CA.crt",
- "expiry_threshold": 30,
- "common_name": "AristaIT-ICA ECDSA Issuing Cert Authority",
- "encryption_algorithm": "ECDSA",
- "key_size": 2048,
- },
- ]
- },
- "expected": {"result": "error", "messages": ["Allowed sizes are (256, 384, 512)."]},
- },
- {
"name": "success",
"test": VerifyBannerLogin,
"eos_data": [
@@ -1213,4 +1203,84 @@ DATA: list[dict[str, Any]] = [
],
},
},
+ {
+ "name": "success",
+ "test": VerifyHardwareEntropy,
+ "eos_data": [{"cpuModel": "2.20GHz", "cryptoModule": "Crypto Module v3.0", "hardwareEntropyEnabled": True, "blockedNetworkProtocols": []}],
+ "inputs": {},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure",
+ "test": VerifyHardwareEntropy,
+ "eos_data": [{"cpuModel": "2.20GHz", "cryptoModule": "Crypto Module v3.0", "hardwareEntropyEnabled": False, "blockedNetworkProtocols": []}],
+ "inputs": {},
+ "expected": {"result": "failure", "messages": ["Hardware entropy generation is disabled."]},
+ },
]
+
+
+class TestAPISSLCertificate:
+ """Test anta.tests.security.VerifyAPISSLCertificate.Input.APISSLCertificate."""
+
+ @pytest.mark.parametrize(
+ ("model_params", "error"),
+ [
+ pytest.param(
+ {
+ "certificate_name": "ARISTA_ROOT_CA.crt",
+ "expiry_threshold": 30,
+ "common_name": "Arista Networks Internal IT Root Cert Authority",
+ "encryption_algorithm": "RSA",
+ "key_size": 256,
+ },
+ "Value error, `ARISTA_ROOT_CA.crt` key size 256 is invalid for RSA encryption. Allowed sizes are (2048, 3072, 4096).",
+ id="RSA_wrong_size",
+ ),
+ pytest.param(
+ {
+ "certificate_name": "ARISTA_SIGNING_CA.crt",
+ "expiry_threshold": 30,
+ "common_name": "AristaIT-ICA ECDSA Issuing Cert Authority",
+ "encryption_algorithm": "ECDSA",
+ "key_size": 2048,
+ },
+ "Value error, `ARISTA_SIGNING_CA.crt` key size 2048 is invalid for ECDSA encryption. Allowed sizes are (256, 384, 512).",
+ id="ECDSA_wrong_size",
+ ),
+ ],
+ )
+ def test_invalid(self, model_params: dict[str, Any], error: str) -> None:
+ """Test invalid inputs for anta.tests.security.VerifyAPISSLCertificate.Input.APISSLCertificate."""
+ with pytest.raises(ValidationError) as exec_info:
+ VerifyAPISSLCertificate.Input.APISSLCertificate.model_validate(model_params)
+ assert error == exec_info.value.errors()[0]["msg"]
+
+ @pytest.mark.parametrize(
+ "model_params",
+ [
+ pytest.param(
+ {
+ "certificate_name": "ARISTA_SIGNING_CA.crt",
+ "expiry_threshold": 30,
+ "common_name": "AristaIT-ICA ECDSA Issuing Cert Authority",
+ "encryption_algorithm": "ECDSA",
+ "key_size": 256,
+ },
+ id="ECDSA",
+ ),
+ pytest.param(
+ {
+ "certificate_name": "ARISTA_ROOT_CA.crt",
+ "expiry_threshold": 30,
+ "common_name": "Arista Networks Internal IT Root Cert Authority",
+ "encryption_algorithm": "RSA",
+ "key_size": 4096,
+ },
+ id="RSA",
+ ),
+ ],
+ )
+ def test_valid(self, model_params: dict[str, Any]) -> None:
+ """Test valid inputs for anta.tests.security.VerifyAPISSLCertificate.Input.APISSLCertificate."""
+ VerifyAPISSLCertificate.Input.APISSLCertificate.model_validate(model_params)
diff --git a/tests/units/anta_tests/test_services.py b/tests/units/anta_tests/test_services.py
index 61c44d0..3f13dfc 100644
--- a/tests/units/anta_tests/test_services.py
+++ b/tests/units/anta_tests/test_services.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.services import VerifyDNSLookup, VerifyDNSServers, VerifyErrdisableRecovery, VerifyHostname
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_snmp.py b/tests/units/anta_tests/test_snmp.py
index b4d3152..e7d8da8 100644
--- a/tests/units/anta_tests/test_snmp.py
+++ b/tests/units/anta_tests/test_snmp.py
@@ -7,8 +7,16 @@ from __future__ import annotations
from typing import Any
-from anta.tests.snmp import VerifySnmpContact, VerifySnmpIPv4Acl, VerifySnmpIPv6Acl, VerifySnmpLocation, VerifySnmpStatus
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from anta.tests.snmp import (
+ VerifySnmpContact,
+ VerifySnmpErrorCounters,
+ VerifySnmpIPv4Acl,
+ VerifySnmpIPv6Acl,
+ VerifySnmpLocation,
+ VerifySnmpPDUCounters,
+ VerifySnmpStatus,
+)
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -100,6 +108,20 @@ DATA: list[dict[str, Any]] = [
},
},
{
+ "name": "failure-details-not-configured",
+ "test": VerifySnmpLocation,
+ "eos_data": [
+ {
+ "location": {"location": ""},
+ }
+ ],
+ "inputs": {"location": "New York"},
+ "expected": {
+ "result": "failure",
+ "messages": ["SNMP location is not configured."],
+ },
+ },
+ {
"name": "success",
"test": VerifySnmpContact,
"eos_data": [
@@ -124,4 +146,177 @@ DATA: list[dict[str, Any]] = [
"messages": ["Expected `Bob@example.com` as the contact, but found `Jon@example.com` instead."],
},
},
+ {
+ "name": "failure-details-not-configured",
+ "test": VerifySnmpContact,
+ "eos_data": [
+ {
+ "contact": {"contact": ""},
+ }
+ ],
+ "inputs": {"contact": "Bob@example.com"},
+ "expected": {
+ "result": "failure",
+ "messages": ["SNMP contact is not configured."],
+ },
+ },
+ {
+ "name": "success",
+ "test": VerifySnmpPDUCounters,
+ "eos_data": [
+ {
+ "counters": {
+ "inGetPdus": 3,
+ "inGetNextPdus": 2,
+ "inSetPdus": 3,
+ "outGetResponsePdus": 3,
+ "outTrapPdus": 9,
+ },
+ }
+ ],
+ "inputs": {},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-specific-pdus",
+ "test": VerifySnmpPDUCounters,
+ "eos_data": [
+ {
+ "counters": {
+ "inGetPdus": 3,
+ "inGetNextPdus": 0,
+ "inSetPdus": 0,
+ "outGetResponsePdus": 0,
+ "outTrapPdus": 9,
+ },
+ }
+ ],
+ "inputs": {"pdus": ["inGetPdus", "outTrapPdus"]},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-counters-not-found",
+ "test": VerifySnmpPDUCounters,
+ "eos_data": [
+ {
+ "counters": {},
+ }
+ ],
+ "inputs": {},
+ "expected": {"result": "failure", "messages": ["SNMP counters not found."]},
+ },
+ {
+ "name": "failure-incorrect-counters",
+ "test": VerifySnmpPDUCounters,
+ "eos_data": [
+ {
+ "counters": {
+ "inGetPdus": 0,
+ "inGetNextPdus": 2,
+ "inSetPdus": 0,
+ "outGetResponsePdus": 3,
+ "outTrapPdus": 9,
+ },
+ }
+ ],
+ "inputs": {},
+ "expected": {
+ "result": "failure",
+ "messages": ["The following SNMP PDU counters are not found or have zero PDU counters:\n{'inGetPdus': 0, 'inSetPdus': 0}"],
+ },
+ },
+ {
+ "name": "failure-pdu-not-found",
+ "test": VerifySnmpPDUCounters,
+ "eos_data": [
+ {
+ "counters": {
+ "inGetNextPdus": 0,
+ "inSetPdus": 0,
+ "outGetResponsePdus": 0,
+ },
+ }
+ ],
+ "inputs": {"pdus": ["inGetPdus", "outTrapPdus"]},
+ "expected": {
+ "result": "failure",
+ "messages": ["The following SNMP PDU counters are not found or have zero PDU counters:\n{'inGetPdus': 'Not Found', 'outTrapPdus': 'Not Found'}"],
+ },
+ },
+ {
+ "name": "success",
+ "test": VerifySnmpErrorCounters,
+ "eos_data": [
+ {
+ "counters": {
+ "inVersionErrs": 0,
+ "inBadCommunityNames": 0,
+ "inBadCommunityUses": 0,
+ "inParseErrs": 0,
+ "outTooBigErrs": 0,
+ "outNoSuchNameErrs": 0,
+ "outBadValueErrs": 0,
+ "outGeneralErrs": 0,
+ },
+ }
+ ],
+ "inputs": {},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-specific-counters",
+ "test": VerifySnmpErrorCounters,
+ "eos_data": [
+ {
+ "counters": {
+ "inVersionErrs": 0,
+ "inBadCommunityNames": 0,
+ "inBadCommunityUses": 0,
+ "inParseErrs": 0,
+ "outTooBigErrs": 5,
+ "outNoSuchNameErrs": 0,
+ "outBadValueErrs": 10,
+ "outGeneralErrs": 1,
+ },
+ }
+ ],
+ "inputs": {"error_counters": ["inVersionErrs", "inParseErrs"]},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-counters-not-found",
+ "test": VerifySnmpErrorCounters,
+ "eos_data": [
+ {
+ "counters": {},
+ }
+ ],
+ "inputs": {},
+ "expected": {"result": "failure", "messages": ["SNMP counters not found."]},
+ },
+ {
+ "name": "failure-incorrect-counters",
+ "test": VerifySnmpErrorCounters,
+ "eos_data": [
+ {
+ "counters": {
+ "inVersionErrs": 1,
+ "inBadCommunityNames": 0,
+ "inBadCommunityUses": 0,
+ "inParseErrs": 2,
+ "outTooBigErrs": 0,
+ "outNoSuchNameErrs": 0,
+ "outBadValueErrs": 2,
+ "outGeneralErrs": 0,
+ },
+ }
+ ],
+ "inputs": {},
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "The following SNMP error counters are not found or have non-zero error counters:\n{'inVersionErrs': 1, 'inParseErrs': 2, 'outBadValueErrs': 2}"
+ ],
+ },
+ },
]
diff --git a/tests/units/anta_tests/test_software.py b/tests/units/anta_tests/test_software.py
index e46f526..d2172bb 100644
--- a/tests/units/anta_tests/test_software.py
+++ b/tests/units/anta_tests/test_software.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.software import VerifyEOSExtensions, VerifyEOSVersion, VerifyTerminAttrVersion
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_stp.py b/tests/units/anta_tests/test_stp.py
index 64a1168..3742210 100644
--- a/tests/units/anta_tests/test_stp.py
+++ b/tests/units/anta_tests/test_stp.py
@@ -7,8 +7,8 @@ from __future__ import annotations
from typing import Any
-from anta.tests.stp import VerifySTPBlockedPorts, VerifySTPCounters, VerifySTPForwardingPorts, VerifySTPMode, VerifySTPRootPriority
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from anta.tests.stp import VerifySTPBlockedPorts, VerifySTPCounters, VerifySTPForwardingPorts, VerifySTPMode, VerifySTPRootPriority, VerifyStpTopologyChanges
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -324,4 +324,166 @@ DATA: list[dict[str, Any]] = [
"inputs": {"priority": 32768, "instances": [10, 20, 30]},
"expected": {"result": "failure", "messages": ["The following instance(s) have the wrong STP root priority configured: ['VL20', 'VL30']"]},
},
+ {
+ "name": "success-mstp",
+ "test": VerifyStpTopologyChanges,
+ "eos_data": [
+ {
+ "unmappedVlans": [],
+ "topologies": {
+ "Cist": {
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1723990624.735365},
+ "Port-Channel5": {"state": "forwarding", "numChanges": 1, "lastChange": 1723990624.7353542},
+ }
+ },
+ "NoStp": {
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1723990624.735365},
+ "Ethernet1": {"state": "forwarding", "numChanges": 15, "lastChange": 1723990624.7353542},
+ }
+ },
+ },
+ },
+ ],
+ "inputs": {"threshold": 10},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-rstp",
+ "test": VerifyStpTopologyChanges,
+ "eos_data": [
+ {
+ "unmappedVlans": [],
+ "topologies": {
+ "Cist": {
+ "interfaces": {
+ "Vxlan1": {"state": "forwarding", "numChanges": 1, "lastChange": 1723990624.735365},
+ "PeerEthernet3": {"state": "forwarding", "numChanges": 1, "lastChange": 1723990624.7353542},
+ }
+ },
+ "NoStp": {
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1723990624.735365},
+ "Ethernet1": {"state": "forwarding", "numChanges": 15, "lastChange": 1723990624.7353542},
+ }
+ },
+ },
+ },
+ ],
+ "inputs": {"threshold": 10},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "success-rapid-pvst",
+ "test": VerifyStpTopologyChanges,
+ "eos_data": [
+ {
+ "unmappedVlans": [],
+ "topologies": {
+ "NoStp": {
+ "vlans": [4094, 4093, 1006],
+ "interfaces": {
+ "PeerEthernet2": {"state": "forwarding", "numChanges": 1, "lastChange": 1727151356.1330667},
+ },
+ },
+ "Vl1": {"vlans": [1], "interfaces": {"Port-Channel5": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0615358}}},
+ "Vl10": {
+ "vlans": [10],
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0673406},
+ "Vxlan1": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0677001},
+ "Port-Channel5": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0728855},
+ "Ethernet3": {"state": "forwarding", "numChanges": 3, "lastChange": 1727326730.255137},
+ },
+ },
+ "Vl1198": {
+ "vlans": [1198],
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.074386},
+ "Vxlan1": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0743902},
+ "Port-Channel5": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0743942},
+ },
+ },
+ "Vl1199": {
+ "vlans": [1199],
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0744},
+ "Vxlan1": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.07453},
+ "Port-Channel5": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.074535},
+ },
+ },
+ "Vl20": {
+ "vlans": [20],
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.073489},
+ "Vxlan1": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0743747},
+ "Port-Channel5": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0743794},
+ "Ethernet3": {"state": "forwarding", "numChanges": 3, "lastChange": 1727326730.2551405},
+ },
+ },
+ "Vl3009": {
+ "vlans": [3009],
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.074541},
+ "Port-Channel5": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0745454},
+ },
+ },
+ "Vl3019": {
+ "vlans": [3019],
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0745502},
+ "Port-Channel5": {"state": "forwarding", "numChanges": 1, "lastChange": 1727326710.0745537},
+ },
+ },
+ },
+ },
+ ],
+ "inputs": {"threshold": 10},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-unstable-topology",
+ "test": VerifyStpTopologyChanges,
+ "eos_data": [
+ {
+ "unmappedVlans": [],
+ "topologies": {
+ "Cist": {
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 15, "lastChange": 1723990624.735365},
+ "Port-Channel5": {"state": "forwarding", "numChanges": 15, "lastChange": 1723990624.7353542},
+ }
+ },
+ },
+ },
+ ],
+ "inputs": {"threshold": 10},
+ "expected": {
+ "result": "failure",
+ "messages": [
+ "The following STP topologies are not configured or number of changes not within the threshold:\n"
+ "{'topologies': {'Cist': {'Cpu': {'Number of changes': 15}, 'Port-Channel5': {'Number of changes': 15}}}}"
+ ],
+ },
+ },
+ {
+ "name": "failure-topologies-not-configured",
+ "test": VerifyStpTopologyChanges,
+ "eos_data": [
+ {
+ "unmappedVlans": [],
+ "topologies": {
+ "NoStp": {
+ "interfaces": {
+ "Cpu": {"state": "forwarding", "numChanges": 1, "lastChange": 1723990624.735365},
+ "Ethernet1": {"state": "forwarding", "numChanges": 15, "lastChange": 1723990624.7353542},
+ }
+ }
+ },
+ },
+ ],
+ "inputs": {"threshold": 10},
+ "expected": {"result": "failure", "messages": ["STP is not configured."]},
+ },
]
diff --git a/tests/units/anta_tests/test_stun.py b/tests/units/anta_tests/test_stun.py
index 2c87365..005ae35 100644
--- a/tests/units/anta_tests/test_stun.py
+++ b/tests/units/anta_tests/test_stun.py
@@ -7,8 +7,8 @@ from __future__ import annotations
from typing import Any
-from anta.tests.stun import VerifyStunClient
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from anta.tests.stun import VerifyStunClient, VerifyStunServer
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -173,4 +173,61 @@ DATA: list[dict[str, Any]] = [
],
},
},
+ {
+ "name": "success",
+ "test": VerifyStunServer,
+ "eos_data": [
+ {
+ "enabled": True,
+ "pid": 1895,
+ }
+ ],
+ "inputs": {},
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure-disabled",
+ "test": VerifyStunServer,
+ "eos_data": [
+ {
+ "enabled": False,
+ "pid": 1895,
+ }
+ ],
+ "inputs": {},
+ "expected": {
+ "result": "failure",
+ "messages": ["STUN server status is disabled."],
+ },
+ },
+ {
+ "name": "failure-not-running",
+ "test": VerifyStunServer,
+ "eos_data": [
+ {
+ "enabled": True,
+ "pid": 0,
+ }
+ ],
+ "inputs": {},
+ "expected": {
+ "result": "failure",
+ "messages": ["STUN server is not running."],
+ },
+ },
+ {
+ "name": "failure-not-running-disabled",
+ "test": VerifyStunServer,
+ "eos_data": [
+ {
+ "enabled": False,
+ "pid": 0,
+ }
+ ],
+ "inputs": {},
+ "expected": {
+ "result": "failure",
+ "messages": ["STUN server status is disabled and not running."],
+ },
+ },
]
diff --git a/tests/units/anta_tests/test_system.py b/tests/units/anta_tests/test_system.py
index 6965461..1eda8a1 100644
--- a/tests/units/anta_tests/test_system.py
+++ b/tests/units/anta_tests/test_system.py
@@ -14,10 +14,11 @@ from anta.tests.system import (
VerifyFileSystemUtilization,
VerifyMemoryUtilization,
VerifyNTP,
+ VerifyNTPAssociations,
VerifyReloadCause,
VerifyUptime,
)
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -76,13 +77,6 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "failure", "messages": ["Reload cause is: 'Reload after crash.'"]},
},
{
- "name": "error",
- "test": VerifyReloadCause,
- "eos_data": [{}],
- "inputs": None,
- "expected": {"result": "error", "messages": ["No reload causes available"]},
- },
- {
"name": "success-without-minidump",
"test": VerifyCoredump,
"eos_data": [{"mode": "compressedDeferred", "coreFiles": []}],
@@ -286,4 +280,186 @@ poll interval unknown
"inputs": None,
"expected": {"result": "failure", "messages": ["The device is not synchronized with the configured NTP server(s): 'unsynchronised'"]},
},
+ {
+ "name": "success",
+ "test": VerifyNTPAssociations,
+ "eos_data": [
+ {
+ "peers": {
+ "1.1.1.1": {
+ "condition": "sys.peer",
+ "peerIpAddr": "1.1.1.1",
+ "stratumLevel": 1,
+ },
+ "2.2.2.2": {
+ "condition": "candidate",
+ "peerIpAddr": "2.2.2.2",
+ "stratumLevel": 2,
+ },
+ "3.3.3.3": {
+ "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": "success-pool-name",
+ "test": VerifyNTPAssociations,
+ "eos_data": [
+ {
+ "peers": {
+ "1.ntp.networks.com": {
+ "condition": "sys.peer",
+ "peerIpAddr": "1.1.1.1",
+ "stratumLevel": 1,
+ },
+ "2.ntp.networks.com": {
+ "condition": "candidate",
+ "peerIpAddr": "2.2.2.2",
+ "stratumLevel": 2,
+ },
+ "3.ntp.networks.com": {
+ "condition": "candidate",
+ "peerIpAddr": "3.3.3.3",
+ "stratumLevel": 2,
+ },
+ }
+ }
+ ],
+ "inputs": {
+ "ntp_servers": [
+ {"server_address": "1.ntp.networks.com", "preferred": True, "stratum": 1},
+ {"server_address": "2.ntp.networks.com", "stratum": 2},
+ {"server_address": "3.ntp.networks.com", "stratum": 2},
+ ]
+ },
+ "expected": {"result": "success"},
+ },
+ {
+ "name": "failure",
+ "test": VerifyNTPAssociations,
+ "eos_data": [
+ {
+ "peers": {
+ "1.1.1.1": {
+ "condition": "candidate",
+ "peerIpAddr": "1.1.1.1",
+ "stratumLevel": 2,
+ },
+ "2.2.2.2": {
+ "condition": "sys.peer",
+ "peerIpAddr": "2.2.2.2",
+ "stratumLevel": 2,
+ },
+ "3.3.3.3": {
+ "condition": "sys.peer",
+ "peerIpAddr": "3.3.3.3",
+ "stratumLevel": 3,
+ },
+ }
+ }
+ ],
+ "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": "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."
+ ],
+ },
+ },
+ {
+ "name": "failure-no-peers",
+ "test": VerifyNTPAssociations,
+ "eos_data": [{"peers": {}}],
+ "inputs": {
+ "ntp_servers": [
+ {"server_address": "1.1.1.1", "preferred": True, "stratum": 1},
+ {"server_address": "2.2.2.2", "stratum": 1},
+ {"server_address": "3.3.3.3", "stratum": 1},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": ["None of NTP peers are not configured."],
+ },
+ },
+ {
+ "name": "failure-one-peer-not-found",
+ "test": VerifyNTPAssociations,
+ "eos_data": [
+ {
+ "peers": {
+ "1.1.1.1": {
+ "condition": "sys.peer",
+ "peerIpAddr": "1.1.1.1",
+ "stratumLevel": 1,
+ },
+ "2.2.2.2": {
+ "condition": "candidate",
+ "peerIpAddr": "2.2.2.2",
+ "stratumLevel": 1,
+ },
+ }
+ }
+ ],
+ "inputs": {
+ "ntp_servers": [
+ {"server_address": "1.1.1.1", "preferred": True, "stratum": 1},
+ {"server_address": "2.2.2.2", "stratum": 1},
+ {"server_address": "3.3.3.3", "stratum": 1},
+ ]
+ },
+ "expected": {
+ "result": "failure",
+ "messages": ["NTP peer 3.3.3.3 is not configured."],
+ },
+ },
+ {
+ "name": "failure-with-two-peers-not-found",
+ "test": VerifyNTPAssociations,
+ "eos_data": [
+ {
+ "peers": {
+ "1.1.1.1": {
+ "condition": "candidate",
+ "peerIpAddr": "1.1.1.1",
+ "stratumLevel": 1,
+ }
+ }
+ }
+ ],
+ "inputs": {
+ "ntp_servers": [
+ {"server_address": "1.1.1.1", "preferred": True, "stratum": 1},
+ {"server_address": "2.2.2.2", "stratum": 1},
+ {"server_address": "3.3.3.3", "stratum": 1},
+ ]
+ },
+ "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."
+ ],
+ },
+ },
]
diff --git a/tests/units/anta_tests/test_vlan.py b/tests/units/anta_tests/test_vlan.py
index 53bf92f..6bbfac4 100644
--- a/tests/units/anta_tests/test_vlan.py
+++ b/tests/units/anta_tests/test_vlan.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.vlan import VerifyVlanInternalPolicy
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
diff --git a/tests/units/anta_tests/test_vxlan.py b/tests/units/anta_tests/test_vxlan.py
index f450897..4278a59 100644
--- a/tests/units/anta_tests/test_vxlan.py
+++ b/tests/units/anta_tests/test_vxlan.py
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any
from anta.tests.vxlan import VerifyVxlan1ConnSettings, VerifyVxlan1Interface, VerifyVxlanConfigSanity, VerifyVxlanVniBinding, VerifyVxlanVtep
-from tests.lib.anta import test # noqa: F401; pylint: disable=W0611
+from tests.units.anta_tests import test
DATA: list[dict[str, Any]] = [
{
@@ -26,21 +26,21 @@ DATA: list[dict[str, Any]] = [
"expected": {"result": "skipped", "messages": ["Vxlan1 interface is not configured"]},
},
{
- "name": "failure",
+ "name": "failure-down-up",
"test": VerifyVxlan1Interface,
"eos_data": [{"interfaceDescriptions": {"Vxlan1": {"lineProtocolStatus": "down", "interfaceStatus": "up"}}}],
"inputs": None,
"expected": {"result": "failure", "messages": ["Vxlan1 interface is down/up"]},
},
{
- "name": "failure",
+ "name": "failure-up-down",
"test": VerifyVxlan1Interface,
"eos_data": [{"interfaceDescriptions": {"Vxlan1": {"lineProtocolStatus": "up", "interfaceStatus": "down"}}}],
"inputs": None,
"expected": {"result": "failure", "messages": ["Vxlan1 interface is up/down"]},
},
{
- "name": "failure",
+ "name": "failure-down-down",
"test": VerifyVxlan1Interface,
"eos_data": [{"interfaceDescriptions": {"Vxlan1": {"lineProtocolStatus": "down", "interfaceStatus": "down"}}}],
"inputs": None,