diff options
Diffstat (limited to '')
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, |