diff options
Diffstat (limited to 'tests/units/anta_tests')
-rw-r--r-- | tests/units/anta_tests/routing/test_bgp.py | 10 | ||||
-rw-r--r-- | tests/units/anta_tests/routing/test_isis.py | 570 | ||||
-rw-r--r-- | tests/units/anta_tests/test_aaa.py | 12 | ||||
-rw-r--r-- | tests/units/anta_tests/test_avt.py | 581 | ||||
-rw-r--r-- | tests/units/anta_tests/test_configuration.py | 41 | ||||
-rw-r--r-- | tests/units/anta_tests/test_field_notices.py | 22 | ||||
-rw-r--r-- | tests/units/anta_tests/test_interfaces.py | 288 | ||||
-rw-r--r-- | tests/units/anta_tests/test_logging.py | 14 | ||||
-rw-r--r-- | tests/units/anta_tests/test_path_selection.py | 327 | ||||
-rw-r--r-- | tests/units/anta_tests/test_ptp.py | 14 | ||||
-rw-r--r-- | tests/units/anta_tests/test_security.py | 43 | ||||
-rw-r--r-- | tests/units/anta_tests/test_services.py | 2 | ||||
-rw-r--r-- | tests/units/anta_tests/test_software.py | 11 |
13 files changed, 1918 insertions, 17 deletions
diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py index 31006c5..e712e12 100644 --- a/tests/units/anta_tests/routing/test_bgp.py +++ b/tests/units/anta_tests/routing/test_bgp.py @@ -30,6 +30,7 @@ DATA: list[dict[str, Any]] = [ "name": "success", "test": VerifyBGPPeerCount, "eos_data": [ + # Need to order the output as the commands would be sorted after template rendering. { "vrfs": { "default": { @@ -120,9 +121,10 @@ DATA: list[dict[str, Any]] = [ ], "inputs": { "address_families": [ + # evpn first to make sure that the correct mapping output to input is kept. + {"afi": "evpn", "num_peers": 2}, {"afi": "ipv4", "safi": "unicast", "vrf": "default", "num_peers": 2}, {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT", "num_peers": 1}, - {"afi": "evpn", "num_peers": 2}, {"afi": "link-state", "num_peers": 2}, {"afi": "path-selection", "num_peers": 2}, ] @@ -652,9 +654,10 @@ DATA: list[dict[str, Any]] = [ ], "inputs": { "address_families": [ + # Path selection first to make sure input to output mapping is correct. + {"afi": "path-selection"}, {"afi": "ipv4", "safi": "unicast", "vrf": "default"}, {"afi": "ipv4", "safi": "sr-te", "vrf": "MGMT"}, - {"afi": "path-selection"}, {"afi": "link-state"}, ] }, @@ -1081,6 +1084,8 @@ DATA: list[dict[str, Any]] = [ ], "inputs": { "address_families": [ + # Path selection first to make sure input to output mapping is correct. + {"afi": "path-selection", "peers": ["10.1.255.20", "10.1.255.22"]}, { "afi": "ipv4", "safi": "unicast", @@ -1093,7 +1098,6 @@ DATA: list[dict[str, Any]] = [ "vrf": "MGMT", "peers": ["10.1.255.10", "10.1.255.12"], }, - {"afi": "path-selection", "peers": ["10.1.255.20", "10.1.255.22"]}, {"afi": "link-state", "peers": ["10.1.255.30", "10.1.255.32"]}, ] }, diff --git a/tests/units/anta_tests/routing/test_isis.py b/tests/units/anta_tests/routing/test_isis.py new file mode 100644 index 0000000..ec41105 --- /dev/null +++ b/tests/units/anta_tests/routing/test_isis.py @@ -0,0 +1,570 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Tests for anta.tests.routing.ospf.py.""" + +from __future__ import annotations + +from typing import Any + +from anta.tests.routing.isis import VerifyISISInterfaceMode, VerifyISISNeighborCount, VerifyISISNeighborState +from tests.lib.anta import test # noqa: F401; pylint: disable=W0611 + +DATA: list[dict[str, Any]] = [ + { + "name": "success only default vrf", + "test": VerifyISISNeighborState, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "neighbors": { + "0168.0000.0111": { + "adjacencies": [ + { + "hostname": "s1-p01", + "circuitId": "83", + "interfaceName": "Ethernet1", + "state": "up", + "lastHelloTime": 1713688408, + "routerIdV4": "1.0.0.111", + } + ] + }, + "0168.0000.0112": { + "adjacencies": [ + { + "hostname": "s1-p02", + "circuitId": "87", + "interfaceName": "Ethernet2", + "state": "up", + "lastHelloTime": 1713688405, + "routerIdV4": "1.0.0.112", + } + ] + }, + } + } + } + } + } + }, + ], + "inputs": None, + "expected": {"result": "success"}, + }, + { + "name": "success different vrfs", + "test": VerifyISISNeighborState, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "neighbors": { + "0168.0000.0111": { + "adjacencies": [ + { + "hostname": "s1-p01", + "circuitId": "83", + "interfaceName": "Ethernet1", + "state": "up", + "lastHelloTime": 1713688408, + "routerIdV4": "1.0.0.111", + } + ] + }, + }, + }, + }, + "customer": { + "isisInstances": { + "CORE-ISIS": { + "neighbors": { + "0168.0000.0112": { + "adjacencies": [ + { + "hostname": "s1-p02", + "circuitId": "87", + "interfaceName": "Ethernet2", + "state": "up", + "lastHelloTime": 1713688405, + "routerIdV4": "1.0.0.112", + } + ] + } + } + } + } + }, + } + } + }, + ], + "inputs": None, + "expected": {"result": "success"}, + }, + { + "name": "failure", + "test": VerifyISISNeighborState, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "neighbors": { + "0168.0000.0111": { + "adjacencies": [ + { + "hostname": "s1-p01", + "circuitId": "83", + "interfaceName": "Ethernet1", + "state": "down", + "lastHelloTime": 1713688408, + "routerIdV4": "1.0.0.111", + } + ] + }, + "0168.0000.0112": { + "adjacencies": [ + { + "hostname": "s1-p02", + "circuitId": "87", + "interfaceName": "Ethernet2", + "state": "up", + "lastHelloTime": 1713688405, + "routerIdV4": "1.0.0.112", + } + ] + }, + } + } + } + } + } + }, + ], + "inputs": None, + "expected": { + "result": "failure", + "messages": ["Some neighbors are not in the correct state (UP): [{'vrf': 'default', 'instance': 'CORE-ISIS', 'neighbor': 's1-p01', 'state': 'down'}]."], + }, + }, + { + "name": "success only default vrf", + "test": VerifyISISNeighborCount, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "interfaces": { + "Loopback0": { + "enabled": True, + "intfLevels": { + "2": { + "ipv4Metric": 10, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "areaProxyBoundary": False, + }, + "Ethernet1": { + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 1, + "linkId": "84", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + "Ethernet2": { + "enabled": True, + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 1, + "linkId": "88", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + } + } + } + } + } + }, + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet1", "level": 2, "count": 1}, + {"name": "Ethernet2", "level": 2, "count": 1}, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "success VerifyISISInterfaceMode only default vrf", + "test": VerifyISISInterfaceMode, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "interfaces": { + "Loopback0": { + "enabled": True, + "index": 2, + "snpa": "0:0:0:0:0:0", + "mtu": 65532, + "interfaceAddressFamily": "ipv4", + "interfaceType": "loopback", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "areaProxyBoundary": False, + }, + "Ethernet1": { + "enabled": True, + "index": 132, + "snpa": "P2P", + "interfaceType": "point-to-point", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 1, + "linkId": "84", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + "Ethernet2": { + "enabled": True, + "interfaceType": "broadcast", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 0, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + } + } + } + } + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Loopback0", "mode": "passive"}, + {"name": "Ethernet2", "mode": "passive"}, + {"name": "Ethernet1", "mode": "point-to-point", "vrf": "default"}, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "failure VerifyISISInterfaceMode default vrf with interface not running passive mode", + "test": VerifyISISInterfaceMode, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "interfaces": { + "Loopback0": { + "enabled": True, + "index": 2, + "snpa": "0:0:0:0:0:0", + "mtu": 65532, + "interfaceAddressFamily": "ipv4", + "interfaceType": "loopback", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "areaProxyBoundary": False, + }, + "Ethernet1": { + "enabled": True, + "index": 132, + "snpa": "P2P", + "interfaceType": "point-to-point", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 1, + "linkId": "84", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + "Ethernet2": { + "enabled": True, + "interfaceType": "point-to-point", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 0, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + } + } + } + } + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Loopback0", "mode": "passive"}, + {"name": "Ethernet2", "mode": "passive"}, + {"name": "Ethernet1", "mode": "point-to-point", "vrf": "default"}, + ] + }, + "expected": { + "result": "failure", + "messages": ["Interface Ethernet2 in VRF default is not running in passive mode"], + }, + }, + { + "name": "failure VerifyISISInterfaceMode default vrf with interface not running point-point mode", + "test": VerifyISISInterfaceMode, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "interfaces": { + "Loopback0": { + "enabled": True, + "index": 2, + "snpa": "0:0:0:0:0:0", + "mtu": 65532, + "interfaceAddressFamily": "ipv4", + "interfaceType": "loopback", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "areaProxyBoundary": False, + }, + "Ethernet1": { + "enabled": True, + "index": 132, + "snpa": "P2P", + "interfaceType": "broadcast", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 1, + "linkId": "84", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + "Ethernet2": { + "enabled": True, + "interfaceType": "broadcast", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 0, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + } + } + } + } + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Loopback0", "mode": "passive"}, + {"name": "Ethernet2", "mode": "passive"}, + {"name": "Ethernet1", "mode": "point-to-point", "vrf": "default"}, + ] + }, + "expected": { + "result": "failure", + "messages": ["Interface Ethernet1 in VRF default is not running in point-to-point reporting broadcast"], + }, + }, + { + "name": "failure VerifyISISInterfaceMode default vrf with interface not running correct VRF mode", + "test": VerifyISISInterfaceMode, + "eos_data": [ + { + "vrfs": { + "fake_vrf": { + "isisInstances": { + "CORE-ISIS": { + "interfaces": { + "Loopback0": { + "enabled": True, + "index": 2, + "snpa": "0:0:0:0:0:0", + "mtu": 65532, + "interfaceAddressFamily": "ipv4", + "interfaceType": "loopback", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "areaProxyBoundary": False, + }, + "Ethernet1": { + "enabled": True, + "index": 132, + "snpa": "P2P", + "interfaceType": "point-to-point", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 1, + "linkId": "84", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + "Ethernet2": { + "enabled": True, + "interfaceType": "broadcast", + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 0, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + } + } + } + } + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Loopback0", "mode": "passive"}, + {"name": "Ethernet2", "mode": "passive"}, + {"name": "Ethernet1", "mode": "point-to-point", "vrf": "default"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "Interface Loopback0 not found in VRF default", + "Interface Ethernet2 not found in VRF default", + "Interface Ethernet1 not found in VRF default", + ], + }, + }, +] diff --git a/tests/units/anta_tests/test_aaa.py b/tests/units/anta_tests/test_aaa.py index f0324c5..40bf82e 100644 --- a/tests/units/anta_tests/test_aaa.py +++ b/tests/units/anta_tests/test_aaa.py @@ -272,6 +272,18 @@ DATA: list[dict[str, Any]] = [ "expected": {"result": "success"}, }, { + "name": "success-skipping-exec", + "test": VerifyAuthzMethods, + "eos_data": [ + { + "commandsAuthzMethods": {"privilege0-15": {"methods": ["group tacacs+", "local"]}}, + "execAuthzMethods": {"exec": {"methods": ["group tacacs+", "local"]}}, + }, + ], + "inputs": {"methods": ["tacacs+", "local"], "types": ["commands"]}, + "expected": {"result": "success"}, + }, + { "name": "failure-commands", "test": VerifyAuthzMethods, "eos_data": [ diff --git a/tests/units/anta_tests/test_avt.py b/tests/units/anta_tests/test_avt.py new file mode 100644 index 0000000..7ef6be3 --- /dev/null +++ b/tests/units/anta_tests/test_avt.py @@ -0,0 +1,581 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Tests for anta.tests.avt.py.""" + +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 + +DATA: list[dict[str, Any]] = [ + { + "name": "success", + "test": VerifyAVTPathHealth, + "eos_data": [ + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:9": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:1": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + } + } + }, + "guest": { + "avts": { + "GUEST-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + } + } + }, + "default": { + "avts": { + "CONTROL-PLANE-PROFILE": { + "avtPaths": { + "direct:9": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:1": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + }, + "DEFAULT-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + }, + } + }, + } + } + ], + "inputs": {}, + "expected": {"result": "success"}, + }, + { + "name": "failure-avt-not-configured", + "test": VerifyAVTPathHealth, + "eos_data": [{"vrfs": {}}], + "inputs": {}, + "expected": { + "result": "failure", + "messages": ["Adaptive virtual topology paths are not configured."], + }, + }, + { + "name": "failure-not-active-path", + "test": VerifyAVTPathHealth, + "eos_data": [ + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:9": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:1": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + } + } + }, + "guest": { + "avts": { + "GUEST-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": False}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + } + } + }, + "default": { + "avts": { + "CONTROL-PLANE-PROFILE": { + "avtPaths": { + "direct:9": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:1": { + "flags": {"directPath": True, "valid": True, "active": False}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + }, + "DEFAULT-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": False}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + }, + } + }, + } + } + ], + "inputs": {}, + "expected": { + "result": "failure", + "messages": [ + "AVT path direct:10 for profile GUEST-AVT-POLICY-DEFAULT in VRF guest is not active.", + "AVT path direct:1 for profile CONTROL-PLANE-PROFILE in VRF default is not active.", + "AVT path direct:10 for profile DEFAULT-AVT-POLICY-DEFAULT in VRF default is not active.", + ], + }, + }, + { + "name": "failure-invalid-path", + "test": VerifyAVTPathHealth, + "eos_data": [ + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:9": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:10": { + "flags": {"directPath": True, "valid": False, "active": True}, + }, + "direct:1": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + } + } + }, + "guest": { + "avts": { + "GUEST-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": False, "active": True}, + }, + } + } + } + }, + "default": { + "avts": { + "CONTROL-PLANE-PROFILE": { + "avtPaths": { + "direct:9": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:10": { + "flags": {"directPath": True, "valid": False, "active": True}, + }, + "direct:1": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + }, + "DEFAULT-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": False, "active": True}, + }, + } + }, + } + }, + } + } + ], + "inputs": {}, + "expected": { + "result": "failure", + "messages": [ + "AVT path direct:10 for profile DATA-AVT-POLICY-DEFAULT in VRF data is invalid.", + "AVT path direct:8 for profile GUEST-AVT-POLICY-DEFAULT in VRF guest is invalid.", + "AVT path direct:10 for profile CONTROL-PLANE-PROFILE in VRF default is invalid.", + "AVT path direct:8 for profile DEFAULT-AVT-POLICY-DEFAULT in VRF default is invalid.", + ], + }, + }, + { + "name": "failure-not-active-and-invalid", + "test": VerifyAVTPathHealth, + "eos_data": [ + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:9": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:10": { + "flags": {"directPath": True, "valid": False, "active": False}, + }, + "direct:1": { + "flags": {"directPath": True, "valid": True, "active": False}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + } + } + }, + "guest": { + "avts": { + "GUEST-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:10": { + "flags": {"directPath": True, "valid": False, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": False, "active": False}, + }, + } + } + } + }, + "default": { + "avts": { + "CONTROL-PLANE-PROFILE": { + "avtPaths": { + "direct:9": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:10": { + "flags": {"directPath": True, "valid": False, "active": False}, + }, + "direct:1": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": True, "active": True}, + }, + } + }, + "DEFAULT-AVT-POLICY-DEFAULT": { + "avtPaths": { + "direct:10": { + "flags": {"directPath": True, "valid": True, "active": False}, + }, + "direct:8": { + "flags": {"directPath": True, "valid": False, "active": False}, + }, + } + }, + } + }, + } + } + ], + "inputs": {}, + "expected": { + "result": "failure", + "messages": [ + "AVT path direct:10 for profile DATA-AVT-POLICY-DEFAULT in VRF data is invalid and not active.", + "AVT path direct:1 for profile DATA-AVT-POLICY-DEFAULT in VRF data is not active.", + "AVT path direct:10 for profile GUEST-AVT-POLICY-DEFAULT in VRF guest is invalid.", + "AVT path direct:8 for profile GUEST-AVT-POLICY-DEFAULT in VRF guest is invalid and not active.", + "AVT path direct:10 for profile CONTROL-PLANE-PROFILE in VRF default is invalid and not active.", + "AVT path direct:10 for profile DEFAULT-AVT-POLICY-DEFAULT in VRF default is not active.", + "AVT path direct:8 for profile DEFAULT-AVT-POLICY-DEFAULT in VRF default is invalid and not active.", + ], + }, + }, + { + "name": "success", + "test": VerifyAVTSpecificPath, + "eos_data": [ + { + "vrfs": { + "default": { + "avts": { + "DEFAULT-AVT-POLICY-CONTROL-PLANE": { + "avtPaths": { + "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + } + } + } + } + } + }, + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-CONTROL-PLANE": { + "avtPaths": { + "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "direct:8": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + } + } + } + } + } + }, + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-CONTROL-PLANE": { + "avtPaths": { + "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "direct:8": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + } + } + } + } + } + }, + ], + "inputs": { + "avt_paths": [ + {"avt_name": "DEFAULT-AVT-POLICY-CONTROL-PLANE", "destination": "10.101.255.2", "next_hop": "10.101.255.1", "path_type": "multihop"}, + {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.2", "path_type": "direct"}, + {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.2"}, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "failure-no-peer", + "test": VerifyAVTSpecificPath, + "eos_data": [ + {"vrfs": {}}, + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-CONTROL-PLANE": { + "avtPaths": { + "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + } + } + } + } + } + }, + ], + "inputs": { + "avt_paths": [ + {"avt_name": "MGMT-AVT-POLICY-DEFAULT", "vrf": "default", "destination": "10.101.255.2", "next_hop": "10.101.255.1", "path_type": "multihop"}, + {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.2", "path_type": "multihop"}, + ] + }, + "expected": { + "result": "failure", + "messages": ["AVT configuration for peer '10.101.255.2' under topology 'MGMT-AVT-POLICY-DEFAULT' in VRF 'default' is not found."], + }, + }, + { + "name": "failure-no-path-with-correct-next-hop", + "test": VerifyAVTSpecificPath, + "eos_data": [ + { + "vrfs": { + "default": { + "avts": { + "DEFAULT-AVT-POLICY-CONTROL-PLANE": { + "avtPaths": { + "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + } + } + } + } + } + }, + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-CONTROL-PLANE": { + "avtPaths": { + "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + } + } + } + } + } + }, + ], + "inputs": { + "avt_paths": [ + { + "avt_name": "DEFAULT-AVT-POLICY-CONTROL-PLANE", + "vrf": "default", + "destination": "10.101.255.2", + "next_hop": "10.101.255.11", + "path_type": "multihop", + }, + {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.21", "path_type": "direct"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "No 'multihop' path found with next-hop address '10.101.255.11' for AVT peer '10.101.255.2' under " + "topology 'DEFAULT-AVT-POLICY-CONTROL-PLANE' in VRF 'default'.", + "No 'direct' path found with next-hop address '10.101.255.21' for AVT peer '10.101.255.1' under " + "topology 'DATA-AVT-POLICY-CONTROL-PLANE' in VRF 'data'.", + ], + }, + }, + { + "name": "failure-incorrect-path", + "test": VerifyAVTSpecificPath, + "eos_data": [ + { + "vrfs": { + "default": { + "avts": { + "DEFAULT-AVT-POLICY-CONTROL-PLANE": { + "avtPaths": { + "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "direct:9": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:1": {"flags": {"directPath": True, "valid": False, "active": False}, "nexthopAddr": "10.101.255.1"}, + "multihop:3": {"flags": {"directPath": False, "valid": True, "active": False}, "nexthopAddr": "10.101.255.1"}, + } + } + } + } + } + }, + { + "vrfs": { + "data": { + "avts": { + "DATA-AVT-POLICY-CONTROL-PLANE": { + "avtPaths": { + "direct:10": {"flags": {"directPath": True, "valid": True, "active": True}, "nexthopAddr": "10.101.255.1"}, + "direct:9": {"flags": {"directPath": True, "valid": False, "active": True}, "nexthopAddr": "10.101.255.1"}, + "multihop:1": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + "multihop:3": {"flags": {"directPath": False, "valid": True, "active": True}, "nexthopAddr": "10.101.255.2"}, + } + } + } + } + } + }, + ], + "inputs": { + "avt_paths": [ + { + "avt_name": "DEFAULT-AVT-POLICY-CONTROL-PLANE", + "vrf": "default", + "destination": "10.101.255.2", + "next_hop": "10.101.255.1", + "path_type": "multihop", + }, + {"avt_name": "DATA-AVT-POLICY-CONTROL-PLANE", "vrf": "data", "destination": "10.101.255.1", "next_hop": "10.101.255.1", "path_type": "direct"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "AVT path 'multihop:3' for topology 'DEFAULT-AVT-POLICY-CONTROL-PLANE' in VRF 'default' is inactive.", + "AVT path 'direct:9' for topology 'DATA-AVT-POLICY-CONTROL-PLANE' in VRF 'data' is invalid.", + ], + }, + }, + { + "name": "success", + "test": VerifyAVTRole, + "eos_data": [{"role": "edge"}], + "inputs": {"role": "edge"}, + "expected": {"result": "success"}, + }, + { + "name": "failure-incorrect-role", + "test": VerifyAVTRole, + "eos_data": [{"role": "transit"}], + "inputs": {"role": "edge"}, + "expected": {"result": "failure", "messages": ["Expected AVT role as `edge`, but found `transit` instead."]}, + }, +] diff --git a/tests/units/anta_tests/test_configuration.py b/tests/units/anta_tests/test_configuration.py index 0444db6..7f198a3 100644 --- a/tests/units/anta_tests/test_configuration.py +++ b/tests/units/anta_tests/test_configuration.py @@ -7,7 +7,7 @@ from __future__ import annotations from typing import Any -from anta.tests.configuration import VerifyRunningConfigDiffs, VerifyZeroTouch +from anta.tests.configuration import VerifyRunningConfigDiffs, VerifyRunningConfigLines, VerifyZeroTouch from tests.lib.anta import test # noqa: F401; pylint: disable=W0611 DATA: list[dict[str, Any]] = [ @@ -32,5 +32,42 @@ DATA: list[dict[str, Any]] = [ "inputs": None, "expected": {"result": "success"}, }, - {"name": "failure", "test": VerifyRunningConfigDiffs, "eos_data": ["blah blah"], "inputs": None, "expected": {"result": "failure", "messages": ["blah blah"]}}, + { + "name": "failure", + "test": VerifyRunningConfigDiffs, + "eos_data": ["blah blah"], + "inputs": None, + "expected": {"result": "failure", "messages": ["blah blah"]}, + }, + { + "name": "success", + "test": VerifyRunningConfigLines, + "eos_data": ["blah blah"], + "inputs": {"regex_patterns": ["blah"]}, + "expected": {"result": "success"}, + }, + { + "name": "success", + "test": VerifyRunningConfigLines, + "eos_data": ["enable password something\nsome other line"], + "inputs": {"regex_patterns": ["^enable password .*$", "^.*other line$"]}, + "expected": {"result": "success"}, + }, + { + "name": "failure", + "test": VerifyRunningConfigLines, + "eos_data": ["enable password something\nsome other line"], + "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_field_notices.py b/tests/units/anta_tests/test_field_notices.py index 66e7801..3cb7286 100644 --- a/tests/units/anta_tests/test_field_notices.py +++ b/tests/units/anta_tests/test_field_notices.py @@ -21,7 +21,7 @@ DATA: list[dict[str, Any]] = [ "modelName": "DCS-7280QRA-C36S", "details": { "deviations": [], - "components": [{"name": "Aboot", "version": "Aboot-veos-8.0.0-3255441"}], + "components": [{"name": "Aboot", "version": "Aboot-veos-8.0.0-3255441"}, {"name": "NotAboot", "version": "Aboot-veos-8.0.0-3255441"}], }, }, ], @@ -129,6 +129,26 @@ DATA: list[dict[str, Any]] = [ }, }, { + "name": "failure-no-aboot-component", + "test": VerifyFieldNotice44Resolution, + "eos_data": [ + { + "imageFormatVersion": "1.0", + "uptime": 1109144.35, + "modelName": "DCS-7280QRA-C36S", + "details": { + "deviations": [], + "components": [{"name": "NotAboot", "version": "Aboot-veos-4.0.1-3255441"}], + }, + }, + ], + "inputs": None, + "expected": { + "result": "failure", + "messages": ["Aboot component not found"], + }, + }, + { "name": "success-JPE", "test": VerifyFieldNotice72Resolution, "eos_data": [ diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py index 58f568f..b8cf493 100644 --- a/tests/units/anta_tests/test_interfaces.py +++ b/tests/units/anta_tests/test_interfaces.py @@ -1,7 +1,7 @@ # Copyright (c) 2023-2024 Arista Networks, Inc. # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. -"""Test inputs for anta.tests.hardware.""" +"""Test inputs for anta.tests.interfaces.""" # pylint: disable=C0302 from __future__ import annotations @@ -14,6 +14,7 @@ from anta.tests.interfaces import ( VerifyInterfaceErrDisabled, VerifyInterfaceErrors, VerifyInterfaceIPv4, + VerifyInterfacesSpeed, VerifyInterfacesStatus, VerifyInterfaceUtilization, VerifyIPProxyARP, @@ -1354,6 +1355,14 @@ DATA: list[dict[str, Any]] = [ "lineProtocolStatus": "up", "mtu": 65535, }, + # Checking not loopbacks are skipped + "Ethernet666": { + "name": "Ethernet666", + "interfaceStatus": "connected", + "interfaceAddress": {"ipAddr": {"maskLen": 32, "address": "6.6.6.6"}}, + "ipv4Routable240": False, + "lineProtocolStatus": "up", + }, }, }, ], @@ -1733,7 +1742,7 @@ DATA: list[dict[str, Any]] = [ }, }, ], - "inputs": {"mtu": 9214}, + "inputs": {"mtu": 9214, "ignored_interfaces": ["Loopback", "Port-Channel", "Management", "Vxlan"], "specific_mtu": [{"Ethernet10": 9214}]}, "expected": {"result": "success"}, }, { @@ -2157,4 +2166,279 @@ DATA: list[dict[str, Any]] = [ "inputs": {"mac_address": "00:1c:73:00:dc:01"}, "expected": {"result": "failure", "messages": ["IP virtual router MAC address `00:1c:73:00:dc:01` is not configured."]}, }, + { + "name": "success", + "test": VerifyInterfacesSpeed, + "eos_data": [ + { + "interfaces": { + "Ethernet1": { + "bandwidth": 1000000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 2, + }, + "Ethernet1/1/2": { + "bandwidth": 1000000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 2, + }, + "Ethernet3": { + "bandwidth": 100000000000, + "autoNegotiate": "success", + "duplex": "duplexFull", + "lanes": 8, + }, + "Ethernet4": { + "bandwidth": 2500000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 8, + }, + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet1", "auto": False, "speed": 1}, + {"name": "Ethernet1", "auto": False, "speed": 1, "lanes": 2}, + {"name": "Ethernet1/1/2", "auto": False, "speed": 1}, + {"name": "Ethernet3", "auto": True, "speed": 100}, + {"name": "Ethernet3", "auto": True, "speed": 100, "lanes": 8}, + {"name": "Ethernet3", "auto": True, "speed": 100}, + {"name": "Ethernet4", "auto": False, "speed": 2.5}, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "failure-incorrect-speed", + "test": VerifyInterfacesSpeed, + "eos_data": [ + { + "interfaces": { + "Ethernet1": { + "bandwidth": 100000000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 2, + }, + "Ethernet1/1/1": { + "bandwidth": 100000000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 2, + }, + "Ethernet3": { + "bandwidth": 10000000000, + "autoNegotiate": "success", + "duplex": "duplexFull", + "lanes": 8, + }, + "Ethernet4": { + "bandwidth": 25000000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 8, + }, + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet1", "auto": False, "speed": 1}, + {"name": "Ethernet1/1/1", "auto": False, "speed": 1}, + {"name": "Ethernet3", "auto": True, "speed": 100}, + {"name": "Ethernet4", "auto": False, "speed": 2.5}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "For interface Ethernet1:\nExpected `1Gbps` as the speed, but found `100Gbps` instead.", + "For interface Ethernet1/1/1:\nExpected `1Gbps` as the speed, but found `100Gbps` instead.", + "For interface Ethernet3:\nExpected `100Gbps` as the speed, but found `10Gbps` instead.", + "For interface Ethernet4:\nExpected `2.5Gbps` as the speed, but found `25Gbps` instead.", + ], + }, + }, + { + "name": "failure-incorrect-mode", + "test": VerifyInterfacesSpeed, + "eos_data": [ + { + "interfaces": { + "Ethernet1": { + "bandwidth": 1000000000, + "autoNegotiate": "unknown", + "duplex": "duplexHalf", + "lanes": 2, + }, + "Ethernet1/2/2": { + "bandwidth": 1000000000, + "autoNegotiate": "unknown", + "duplex": "duplexHalf", + "lanes": 2, + }, + "Ethernet3": { + "bandwidth": 100000000000, + "autoNegotiate": "success", + "duplex": "duplexHalf", + "lanes": 8, + }, + "Ethernet4": { + "bandwidth": 2500000000, + "autoNegotiate": "unknown", + "duplex": "duplexHalf", + "lanes": 8, + }, + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet1", "auto": False, "speed": 1}, + {"name": "Ethernet1/2/2", "auto": False, "speed": 1}, + {"name": "Ethernet3", "auto": True, "speed": 100}, + {"name": "Ethernet3", "auto": True, "speed": 100, "lanes": 8}, + {"name": "Ethernet4", "auto": False, "speed": 2.5}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "For interface Ethernet1:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.", + "For interface Ethernet1/2/2:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.", + "For interface Ethernet3:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.", + "For interface Ethernet3:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.", + "For interface Ethernet4:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.", + ], + }, + }, + { + "name": "failure-incorrect-lane", + "test": VerifyInterfacesSpeed, + "eos_data": [ + { + "interfaces": { + "Ethernet1": { + "bandwidth": 1000000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 4, + }, + "Ethernet2": { + "bandwidth": 10000000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 4, + }, + "Ethernet3": { + "bandwidth": 100000000000, + "autoNegotiate": "success", + "duplex": "duplexFull", + "lanes": 4, + }, + "Ethernet4": { + "bandwidth": 2500000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 6, + }, + "Ethernet4/1/1": { + "bandwidth": 2500000000, + "autoNegotiate": "unknown", + "duplex": "duplexFull", + "lanes": 6, + }, + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet1", "auto": False, "speed": 1, "lanes": 2}, + {"name": "Ethernet3", "auto": True, "speed": 100, "lanes": 8}, + {"name": "Ethernet4", "auto": False, "speed": 2.5, "lanes": 4}, + {"name": "Ethernet4/1/1", "auto": False, "speed": 2.5, "lanes": 4}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "For interface Ethernet1:\nExpected `2` as the lanes, but found `4` instead.", + "For interface Ethernet3:\nExpected `8` as the lanes, but found `4` instead.", + "For interface Ethernet4:\nExpected `4` as the lanes, but found `6` instead.", + "For interface Ethernet4/1/1:\nExpected `4` as the lanes, but found `6` instead.", + ], + }, + }, + { + "name": "failure-all-type", + "test": VerifyInterfacesSpeed, + "eos_data": [ + { + "interfaces": { + "Ethernet1": { + "bandwidth": 10000000000, + "autoNegotiate": "unknown", + "duplex": "duplexHalf", + "lanes": 4, + }, + "Ethernet2/1/2": { + "bandwidth": 1000000000, + "autoNegotiate": "unknown", + "duplex": "duplexHalf", + "lanes": 2, + }, + "Ethernet3": { + "bandwidth": 10000000000, + "autoNegotiate": "unknown", + "duplex": "duplexHalf", + "lanes": 6, + }, + "Ethernet4": { + "bandwidth": 25000000000, + "autoNegotiate": "unknown", + "duplex": "duplexHalf", + "lanes": 4, + }, + } + } + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet1", "auto": False, "speed": 1}, + {"name": "Ethernet1", "auto": False, "speed": 1, "lanes": 2}, + {"name": "Ethernet2/1/2", "auto": False, "speed": 10}, + {"name": "Ethernet3", "auto": True, "speed": 1}, + {"name": "Ethernet3", "auto": True, "speed": 100, "lanes": 8}, + {"name": "Ethernet3", "auto": True, "speed": 100}, + {"name": "Ethernet4", "auto": False, "speed": 2.5}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "For interface Ethernet1:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.\n" + "Expected `1Gbps` as the speed, but found `10Gbps` instead.", + "For interface Ethernet1:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.\n" + "Expected `1Gbps` as the speed, but found `10Gbps` instead.\n" + "Expected `2` as the lanes, but found `4` instead.", + "For interface Ethernet2/1/2:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.\n" + "Expected `10Gbps` as the speed, but found `1Gbps` instead.", + "For interface Ethernet3:\nExpected `success` as the auto negotiation, but found `unknown` instead.\n" + "Expected `duplexFull` as the duplex mode, but found `duplexHalf` instead.", + "For interface Ethernet3:\nExpected `success` as the auto negotiation, but found `unknown` instead.\n" + "Expected `duplexFull` as the duplex mode, but found `duplexHalf` instead.\n" + "Expected `100Gbps` as the speed, but found `10Gbps` instead.\n" + "Expected `8` as the lanes, but found `6` instead.", + "For interface Ethernet3:\nExpected `success` as the auto negotiation, but found `unknown` instead.\n" + "Expected `duplexFull` as the duplex mode, but found `duplexHalf` instead.\n" + "Expected `100Gbps` as the speed, but found `10Gbps` instead.", + "For interface Ethernet4:\nExpected `duplexFull` as the duplex mode, but found `duplexHalf` instead.\n" + "Expected `2.5Gbps` as the speed, but found `25Gbps` instead.", + ], + }, + }, ] diff --git a/tests/units/anta_tests/test_logging.py b/tests/units/anta_tests/test_logging.py index 1e8ee3d..d46c865 100644 --- a/tests/units/anta_tests/test_logging.py +++ b/tests/units/anta_tests/test_logging.py @@ -206,7 +206,9 @@ DATA: list[dict[str, Any]] = [ "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", + "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"}, @@ -223,6 +225,16 @@ DATA: list[dict[str, Any]] = [ "expected": {"result": "failure", "messages": ["Logs are not generated with the appropriate timestamp format"]}, }, { + "name": "failure-no-matching-log", + "test": VerifyLoggingTimestamp, + "eos_data": [ + "", + "May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: Message from arista on command-api (10.22.1.107): BLAH\n", + ], + "inputs": None, + "expected": {"result": "failure", "messages": ["Logs are not generated with the appropriate timestamp format"]}, + }, + { "name": "success", "test": VerifyLoggingAccounting, "eos_data": ["2023 May 10 15:50:31 arista command-api 10.22.1.107 stop service=shell priv-lvl=15 cmd=show aaa accounting logs | tail\n"], diff --git a/tests/units/anta_tests/test_path_selection.py b/tests/units/anta_tests/test_path_selection.py new file mode 100644 index 0000000..c5fb079 --- /dev/null +++ b/tests/units/anta_tests/test_path_selection.py @@ -0,0 +1,327 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Tests for anta.tests.path_selection.py.""" + +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 + +DATA: list[dict[str, Any]] = [ + { + "name": "success", + "test": VerifyPathsHealth, + "eos_data": [ + { + "dpsPeers": { + "10.255.0.1": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path3": {"state": "routeResolved", "dpsSessions": {"0": {"active": True}}}, + }, + }, + "mpls": { + "dpsPaths": { + "path4": {"state": "ipsecEstablished", "dpsSessions": {"0": {"active": True}}}, + }, + }, + }, + }, + "10.255.0.2": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path1": {"state": "ipsecEstablished", "dpsSessions": {"0": {"active": True}}}, + }, + }, + "mpls": { + "dpsPaths": { + "path2": {"state": "routeResolved", "dpsSessions": {"0": {"active": True}}}, + }, + }, + }, + }, + } + }, + ], + "inputs": {}, + "expected": {"result": "success"}, + }, + { + "name": "failure-no-peer", + "test": VerifyPathsHealth, + "eos_data": [ + {"dpsPeers": {}}, + ], + "inputs": {}, + "expected": {"result": "failure", "messages": ["No path configured for router path-selection."]}, + }, + { + "name": "failure-not-established", + "test": VerifyPathsHealth, + "eos_data": [ + { + "dpsPeers": { + "10.255.0.1": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path3": {"state": "ipsecPending", "dpsSessions": {"0": {"active": False}}}, + }, + }, + "mpls": { + "dpsPaths": { + "path4": {"state": "ipsecPending", "dpsSessions": {"0": {"active": False}}}, + }, + }, + }, + }, + "10.255.0.2": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path1": {"state": "ipsecEstablished", "dpsSessions": {"0": {"active": True}}}, + }, + }, + "mpls": { + "dpsPaths": { + "path2": {"state": "ipsecPending", "dpsSessions": {"0": {"active": False}}}, + }, + }, + }, + }, + } + }, + ], + "inputs": {}, + "expected": { + "result": "failure", + "messages": [ + "Path state for peer 10.255.0.1 in path-group internet is `ipsecPending`.", + "Path state for peer 10.255.0.1 in path-group mpls is `ipsecPending`.", + "Path state for peer 10.255.0.2 in path-group mpls is `ipsecPending`.", + ], + }, + }, + { + "name": "failure-inactive", + "test": VerifyPathsHealth, + "eos_data": [ + { + "dpsPeers": { + "10.255.0.1": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path3": {"state": "routeResolved", "dpsSessions": {"0": {"active": False}}}, + }, + }, + "mpls": { + "dpsPaths": { + "path4": {"state": "routeResolved", "dpsSessions": {"0": {"active": False}}}, + }, + }, + }, + }, + "10.255.0.2": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path1": {"state": "routeResolved", "dpsSessions": {"0": {"active": True}}}, + }, + }, + "mpls": { + "dpsPaths": { + "path2": {"state": "routeResolved", "dpsSessions": {"0": {"active": False}}}, + }, + }, + }, + }, + } + }, + ], + "inputs": {}, + "expected": { + "result": "failure", + "messages": [ + "Telemetry state for peer 10.255.0.1 in path-group internet is `inactive`.", + "Telemetry state for peer 10.255.0.1 in path-group mpls is `inactive`.", + "Telemetry state for peer 10.255.0.2 in path-group mpls is `inactive`.", + ], + }, + }, + { + "name": "success", + "test": VerifySpecificPath, + "eos_data": [ + { + "dpsPeers": { + "10.255.0.1": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path3": { + "state": "ipsecEstablished", + "source": "172.18.13.2", + "destination": "172.18.15.2", + "dpsSessions": {"0": {"active": True}}, + } + } + } + } + } + } + }, + { + "dpsPeers": { + "10.255.0.2": { + "dpsGroups": { + "mpls": { + "dpsPaths": { + "path2": { + "state": "ipsecEstablished", + "source": "172.18.3.2", + "destination": "172.18.5.2", + "dpsSessions": {"0": {"active": True}}, + } + } + } + } + } + } + }, + ], + "inputs": { + "paths": [ + {"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}, + {"peer": "10.255.0.2", "path_group": "mpls", "source_address": "172.18.13.2", "destination_address": "172.18.15.2"}, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "failure-no-peer", + "test": VerifySpecificPath, + "eos_data": [ + {"dpsPeers": {}}, + {"dpsPeers": {}}, + ], + "inputs": { + "paths": [ + {"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}, + {"peer": "10.255.0.2", "path_group": "mpls", "source_address": "172.18.13.2", "destination_address": "172.18.15.2"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "Path `peer: 10.255.0.1 source: 172.18.3.2 destination: 172.18.5.2` is not configured for path-group `internet`.", + "Path `peer: 10.255.0.2 source: 172.18.13.2 destination: 172.18.15.2` is not configured for path-group `mpls`.", + ], + }, + }, + { + "name": "failure-not-established", + "test": VerifySpecificPath, + "eos_data": [ + { + "dpsPeers": { + "10.255.0.1": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path3": {"state": "ipsecPending", "source": "172.18.3.2", "destination": "172.18.5.2", "dpsSessions": {"0": {"active": True}}} + } + } + } + } + } + }, + { + "dpsPeers": { + "10.255.0.2": { + "dpsGroups": { + "mpls": { + "dpsPaths": { + "path4": { + "state": "ipsecPending", + "source": "172.18.13.2", + "destination": "172.18.15.2", + "dpsSessions": {"0": {"active": False}}, + } + } + } + } + } + } + }, + ], + "inputs": { + "paths": [ + {"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}, + {"peer": "10.255.0.2", "path_group": "mpls", "source_address": "172.18.13.2", "destination_address": "172.18.15.2"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "Path state for `peer: 10.255.0.1 source: 172.18.3.2 destination: 172.18.5.2` in path-group internet is `ipsecPending`.", + "Path state for `peer: 10.255.0.2 source: 172.18.13.2 destination: 172.18.15.2` in path-group mpls is `ipsecPending`.", + ], + }, + }, + { + "name": "failure-inactive", + "test": VerifySpecificPath, + "eos_data": [ + { + "dpsPeers": { + "10.255.0.1": { + "dpsGroups": { + "internet": { + "dpsPaths": { + "path3": {"state": "routeResolved", "source": "172.18.3.2", "destination": "172.18.5.2", "dpsSessions": {"0": {"active": False}}} + } + } + } + } + } + }, + { + "dpsPeers": { + "10.255.0.2": { + "dpsGroups": { + "mpls": { + "dpsPaths": { + "path4": { + "state": "routeResolved", + "source": "172.18.13.2", + "destination": "172.18.15.2", + "dpsSessions": {"0": {"active": False}}, + } + } + } + } + } + } + }, + ], + "inputs": { + "paths": [ + {"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}, + {"peer": "10.255.0.2", "path_group": "mpls", "source_address": "172.18.13.2", "destination_address": "172.18.15.2"}, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "Telemetry state for path `peer: 10.255.0.1 source: 172.18.3.2 destination: 172.18.5.2` in path-group internet is `inactive`.", + "Telemetry state for path `peer: 10.255.0.2 source: 172.18.13.2 destination: 172.18.15.2` in path-group mpls is `inactive`.", + ], + }, + }, +] diff --git a/tests/units/anta_tests/test_ptp.py b/tests/units/anta_tests/test_ptp.py index ef42a58..8f4c77f 100644 --- a/tests/units/anta_tests/test_ptp.py +++ b/tests/units/anta_tests/test_ptp.py @@ -42,11 +42,11 @@ DATA: list[dict[str, Any]] = [ "expected": {"result": "failure", "messages": ["The device is not configured as a PTP Boundary Clock: 'ptpDisabled'"]}, }, { - "name": "error", + "name": "skipped", "test": VerifyPtpModeStatus, "eos_data": [{"ptpIntfSummaries": {}}], "inputs": None, - "expected": {"result": "error", "messages": ["'ptpMode' variable is not present in the command output"]}, + "expected": {"result": "skipped", "messages": ["PTP is not configured"]}, }, { "name": "success", @@ -104,11 +104,11 @@ DATA: list[dict[str, Any]] = [ }, }, { - "name": "error", + "name": "skipped", "test": VerifyPtpGMStatus, "eos_data": [{"ptpIntfSummaries": {}}], "inputs": {"gmid": "0xec:46:70:ff:fe:00:ff:a8"}, - "expected": {"result": "error", "messages": ["'ptpClockSummary' variable is not present in the command output"]}, + "expected": {"result": "skipped", "messages": ["PTP is not configured"]}, }, { "name": "success", @@ -161,14 +161,14 @@ DATA: list[dict[str, Any]] = [ "expected": {"result": "failure", "messages": ["The device lock is more than 60s old: 157s"]}, }, { - "name": "error", + "name": "skipped", "test": VerifyPtpLockStatus, "eos_data": [{"ptpIntfSummaries": {}}], "inputs": None, "expected": { - "result": "error", + "result": "skipped", "messages": [ - "'ptpClockSummary' variable is not present in the command output", + "PTP is not configured", ], }, }, diff --git a/tests/units/anta_tests/test_security.py b/tests/units/anta_tests/test_security.py index 4c28541..3a732bd 100644 --- a/tests/units/anta_tests/test_security.py +++ b/tests/units/anta_tests/test_security.py @@ -34,7 +34,14 @@ DATA: list[dict[str, Any]] = [ "expected": {"result": "success"}, }, { - "name": "failure", + "name": "error-missing-ssh-status", + "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."]}, + }, + { + "name": "failure-ssh-disabled", "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, @@ -574,6 +581,40 @@ 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": [ diff --git a/tests/units/anta_tests/test_services.py b/tests/units/anta_tests/test_services.py index ed86e10..61c44d0 100644 --- a/tests/units/anta_tests/test_services.py +++ b/tests/units/anta_tests/test_services.py @@ -127,10 +127,12 @@ DATA: list[dict[str, Any]] = [ "name": "success", "test": VerifyErrdisableRecovery, "eos_data": [ + # Adding empty line on purpose to verify they are skipped """ Errdisable Reason Timer Status Timer Interval ------------------------------ ----------------- -------------- acl Enabled 300 + bpduguard Enabled 300 arp-inspection Enabled 30 """ diff --git a/tests/units/anta_tests/test_software.py b/tests/units/anta_tests/test_software.py index 84e90e8..e46f526 100644 --- a/tests/units/anta_tests/test_software.py +++ b/tests/units/anta_tests/test_software.py @@ -79,6 +79,7 @@ DATA: list[dict[str, Any]] = [ "inputs": {"versions": ["v1.17.1", "v1.18.1"]}, "expected": {"result": "failure", "messages": ["device is running TerminAttr version v1.17.0 and is not in the allowed list: ['v1.17.1', 'v1.18.1']"]}, }, + # TODO: add a test with a real extension? { "name": "success-no-extensions", "test": VerifyEOSExtensions, @@ -90,6 +91,16 @@ DATA: list[dict[str, Any]] = [ "expected": {"result": "success"}, }, { + "name": "success-empty-extension", + "test": VerifyEOSExtensions, + "eos_data": [ + {"extensions": {}, "extensionStoredDir": "flash:", "warnings": ["No extensions are available"]}, + {"extensions": [""]}, + ], + "inputs": None, + "expected": {"result": "success"}, + }, + { "name": "failure", "test": VerifyEOSExtensions, "eos_data": [ |