Merging upstream version 1.3.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
5b922100c9
commit
8a6a3342fc
337 changed files with 16571 additions and 4891 deletions
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Unit tests for ANTA."""
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!--
|
||||
~ Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
~ Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
~ Use of this source code is governed by the Apache License 2.0
|
||||
~ that can be found in the LICENSE file.
|
||||
-->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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 module."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.routing submodule."""
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.generic.py."""
|
||||
|
@ -11,7 +11,7 @@ from typing import Any
|
|||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.tests.routing.generic import VerifyIPv4RouteType, VerifyRoutingProtocolModel, VerifyRoutingTableEntry, VerifyRoutingTableSize
|
||||
from anta.tests.routing.generic import VerifyIPv4RouteNextHops, VerifyIPv4RouteType, VerifyRoutingProtocolModel, VerifyRoutingTableEntry, VerifyRoutingTableSize
|
||||
from tests.units.anta_tests import test
|
||||
|
||||
DATA: list[dict[str, Any]] = [
|
||||
|
@ -27,14 +27,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyRoutingProtocolModel,
|
||||
"eos_data": [{"vrfs": {"default": {}}, "protoModelStatus": {"configuredProtoModel": "ribd", "operatingProtoModel": "ribd"}}],
|
||||
"inputs": {"model": "multi-agent"},
|
||||
"expected": {"result": "failure", "messages": ["routing model is misconfigured: configured: ribd - operating: ribd - expected: multi-agent"]},
|
||||
"expected": {"result": "failure", "messages": ["Routing model is misconfigured - Expected: multi-agent Actual: ribd"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-mismatch-operating-model",
|
||||
"test": VerifyRoutingProtocolModel,
|
||||
"eos_data": [{"vrfs": {"default": {}}, "protoModelStatus": {"configuredProtoModel": "multi-agent", "operatingProtoModel": "ribd"}}],
|
||||
"inputs": {"model": "multi-agent"},
|
||||
"expected": {"result": "failure", "messages": ["routing model is misconfigured: configured: multi-agent - operating: ribd - expected: multi-agent"]},
|
||||
"expected": {"result": "failure", "messages": ["Routing model is misconfigured - Expected: multi-agent Actual: ribd"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -68,7 +68,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"minimum": 42, "maximum": 666},
|
||||
"expected": {"result": "failure", "messages": ["routing-table has 1000 routes and not between min (42) and maximum (666)"]},
|
||||
"expected": {"result": "failure", "messages": ["Routing table routes are outside the routes range - Expected: 42 <= to >= 666 Actual: 1000"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -204,9 +204,20 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"routingDisabled": False,
|
||||
"allRoutesProgrammedHardware": True,
|
||||
"allRoutesProgrammedKernel": True,
|
||||
"defaultRouteState": "notSet",
|
||||
"routes": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"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.1']"]},
|
||||
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2", "10.1.0.3"]},
|
||||
"expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: 10.1.0.1, 10.1.0.3"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-route",
|
||||
|
@ -260,7 +271,7 @@ 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']"]},
|
||||
"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",
|
||||
|
@ -302,7 +313,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"], "collect": "all"},
|
||||
"expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: ['10.1.0.2']"]},
|
||||
"expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: 10.1.0.2"]},
|
||||
},
|
||||
{
|
||||
"name": "success-valid-route-type",
|
||||
|
@ -348,6 +359,164 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"routes_entries": [{"vrf": "default", "prefix": "10.10.0.1/32", "route_type": "eBGP"}]},
|
||||
"expected": {"result": "failure", "messages": ["Prefix: 10.10.0.1/32 VRF: default - VRF not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifyIPv4RouteNextHops,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"routes": {
|
||||
"10.10.0.1/32": {
|
||||
"vias": [{"nexthopAddr": "10.100.0.8", "interface": "Ethernet1"}, {"nexthopAddr": "10.100.0.10", "interface": "Ethernet2"}],
|
||||
}
|
||||
}
|
||||
},
|
||||
"MGMT": {
|
||||
"routes": {
|
||||
"10.100.0.128/31": {
|
||||
"vias": [
|
||||
{"nexthopAddr": "10.100.0.8", "interface": "Ethernet1"},
|
||||
{"nexthopAddr": "10.100.0.10", "interface": "Ethernet2"},
|
||||
{"nexthopAddr": "10.100.0.101", "interface": "Ethernet4"},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
"inputs": {
|
||||
"route_entries": [
|
||||
{"prefix": "10.10.0.1/32", "vrf": "default", "nexthops": ["10.100.0.10", "10.100.0.8"]},
|
||||
{"prefix": "10.100.0.128/31", "vrf": "MGMT", "nexthops": ["10.100.0.8", "10.100.0.10"]},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-strict-true",
|
||||
"test": VerifyIPv4RouteNextHops,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"routes": {
|
||||
"10.10.0.1/32": {
|
||||
"vias": [{"nexthopAddr": "10.100.0.8", "interface": "Ethernet1"}, {"nexthopAddr": "10.100.0.10", "interface": "Ethernet2"}],
|
||||
}
|
||||
}
|
||||
},
|
||||
"MGMT": {
|
||||
"routes": {
|
||||
"10.100.0.128/31": {
|
||||
"vias": [{"nexthopAddr": "10.100.0.8", "interface": "Ethernet1"}, {"nexthopAddr": "10.100.0.10", "interface": "Ethernet2"}],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
"inputs": {
|
||||
"route_entries": [
|
||||
{"prefix": "10.10.0.1/32", "vrf": "default", "strict": True, "nexthops": ["10.100.0.8", "10.100.0.10"]},
|
||||
{"prefix": "10.100.0.128/31", "vrf": "MGMT", "strict": True, "nexthops": ["10.100.0.8", "10.100.0.10"]},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-configured",
|
||||
"test": VerifyIPv4RouteNextHops,
|
||||
"eos_data": [
|
||||
{"vrfs": {"default": {"routes": {}}, "MGMT": {"routes": {}}}},
|
||||
],
|
||||
"inputs": {
|
||||
"route_entries": [
|
||||
{"prefix": "10.10.0.1/32", "vrf": "default", "strict": True, "nexthops": ["10.100.0.8", "10.100.0.10"]},
|
||||
{"prefix": "10.100.0.128/31", "vrf": "MGMT", "strict": True, "nexthops": ["10.100.0.8", "10.100.0.10"]},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Prefix: 10.10.0.1/32 VRF: default - prefix not found", "Prefix: 10.100.0.128/31 VRF: MGMT - prefix not found"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-strict-failed",
|
||||
"test": VerifyIPv4RouteNextHops,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"routes": {
|
||||
"10.10.0.1/32": {
|
||||
"vias": [{"nexthopAddr": "10.100.0.8", "interface": "Ethernet1"}, {"nexthopAddr": "10.100.0.10", "interface": "Ethernet2"}],
|
||||
}
|
||||
}
|
||||
},
|
||||
"MGMT": {
|
||||
"routes": {
|
||||
"10.100.0.128/31": {
|
||||
"vias": [{"nexthopAddr": "10.100.0.8", "interface": "Ethernet1"}, {"nexthopAddr": "10.100.0.11", "interface": "Ethernet2"}],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
"inputs": {
|
||||
"route_entries": [
|
||||
{"prefix": "10.10.0.1/32", "vrf": "default", "strict": True, "nexthops": ["10.100.0.8", "10.100.0.10", "10.100.0.11"]},
|
||||
{"prefix": "10.100.0.128/31", "vrf": "MGMT", "strict": True, "nexthops": ["10.100.0.8", "10.100.0.10"]},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Prefix: 10.10.0.1/32 VRF: default - List of next-hops not matching - Expected: 10.100.0.10, 10.100.0.11, 10.100.0.8 "
|
||||
"Actual: 10.100.0.10, 10.100.0.8",
|
||||
"Prefix: 10.100.0.128/31 VRF: MGMT - List of next-hops not matching - Expected: 10.100.0.10, 10.100.0.8 Actual: 10.100.0.11, 10.100.0.8",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure",
|
||||
"test": VerifyIPv4RouteNextHops,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"routes": {
|
||||
"10.10.0.1/32": {
|
||||
"vias": [{"nexthopAddr": "10.100.0.8", "interface": "Ethernet1"}, {"nexthopAddr": "10.100.0.10", "interface": "Ethernet2"}],
|
||||
}
|
||||
}
|
||||
},
|
||||
"MGMT": {
|
||||
"routes": {
|
||||
"10.100.0.128/31": {
|
||||
"vias": [{"nexthopAddr": "10.100.0.8", "interface": "Ethernet1"}, {"nexthopAddr": "10.100.0.10", "interface": "Ethernet2"}],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
"inputs": {
|
||||
"route_entries": [
|
||||
{"prefix": "10.10.0.1/32", "vrf": "default", "nexthops": ["10.100.0.8", "10.100.0.10", "10.100.0.11"]},
|
||||
{"prefix": "10.100.0.128/31", "vrf": "MGMT", "nexthops": ["10.100.0.8", "10.100.0.10", "10.100.0.11"]},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Prefix: 10.10.0.1/32 VRF: default Nexthop: 10.100.0.11 - Route not found",
|
||||
"Prefix: 10.100.0.128/31 VRF: MGMT Nexthop: 10.100.0.11 - Route not found",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
@ -122,13 +122,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Some neighbors are not correctly configured: [{'vrf': 'default', 'instance': '666', 'neighbor': '7.7.7.7', 'state': '2-way'},"
|
||||
" {'vrf': 'BLAH', 'instance': '777', 'neighbor': '8.8.8.8', 'state': 'down'}].",
|
||||
"Instance: 666 VRF: default Interface: 7.7.7.7 - Incorrect adjacency state - Expected: Full Actual: 2-way",
|
||||
"Instance: 777 VRF: BLAH Interface: 8.8.8.8 - Incorrect adjacency state - Expected: Full Actual: down",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "skipped",
|
||||
"name": "skipped-ospf-not-configured",
|
||||
"test": VerifyOSPFNeighborState,
|
||||
"eos_data": [
|
||||
{
|
||||
|
@ -136,7 +136,33 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "skipped", "messages": ["no OSPF neighbor found"]},
|
||||
"expected": {"result": "skipped", "messages": ["OSPF not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "skipped-neighbor-not-found",
|
||||
"test": VerifyOSPFNeighborState,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"instList": {
|
||||
"666": {
|
||||
"ospfNeighborEntries": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"BLAH": {
|
||||
"instList": {
|
||||
"777": {
|
||||
"ospfNeighborEntries": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "skipped", "messages": ["No OSPF neighbor detected"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -193,35 +219,6 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"number": 3},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-number",
|
||||
"test": VerifyOSPFNeighborCount,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"instList": {
|
||||
"666": {
|
||||
"ospfNeighborEntries": [
|
||||
{
|
||||
"routerId": "7.7.7.7",
|
||||
"priority": 1,
|
||||
"drState": "DR",
|
||||
"interfaceName": "Ethernet1",
|
||||
"adjacencyState": "full",
|
||||
"inactivity": 1683298014.844345,
|
||||
"interfaceAddress": "10.3.0.1",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": {"number": 3},
|
||||
"expected": {"result": "failure", "messages": ["device has 1 neighbors (expected 3)"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-good-number-wrong-state",
|
||||
"test": VerifyOSPFNeighborCount,
|
||||
|
@ -277,14 +274,11 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"number": 3},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Some neighbors are not correctly configured: [{'vrf': 'default', 'instance': '666', 'neighbor': '7.7.7.7', 'state': '2-way'},"
|
||||
" {'vrf': 'BLAH', 'instance': '777', 'neighbor': '8.8.8.8', 'state': 'down'}].",
|
||||
],
|
||||
"messages": ["Neighbor count mismatch - Expected: 3 Actual: 1"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "skipped",
|
||||
"name": "skipped-ospf-not-configured",
|
||||
"test": VerifyOSPFNeighborCount,
|
||||
"eos_data": [
|
||||
{
|
||||
|
@ -292,7 +286,38 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"number": 3},
|
||||
"expected": {"result": "skipped", "messages": ["no OSPF neighbor found"]},
|
||||
"expected": {"result": "skipped", "messages": ["OSPF not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "skipped-no-neighbor-detected",
|
||||
"test": VerifyOSPFNeighborCount,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"instList": {
|
||||
"666": {
|
||||
"ospfNeighborEntries": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"BLAH": {
|
||||
"instList": {
|
||||
"777": {
|
||||
"ospfNeighborEntries": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": {"number": 3},
|
||||
"expected": {
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"No OSPF neighbor detected",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -394,7 +419,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["OSPF Instances ['1', '10'] crossed the maximum LSA threshold."],
|
||||
"messages": [
|
||||
"Instance: 1 - Crossed the maximum LSA threshold - Expected: < 9000 Actual: 11500",
|
||||
"Instance: 10 - Crossed the maximum LSA threshold - Expected: < 750 Actual: 1500",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -406,6 +434,6 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "skipped", "messages": ["No OSPF instance found."]},
|
||||
"expected": {"result": "skipped", "messages": ["OSPF not configured"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.aaa.py."""
|
||||
|
@ -47,7 +47,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"intf": "Management0", "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Source-interface Management0 is not configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT Source Interface: Management0 - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-intf",
|
||||
|
@ -64,7 +64,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"intf": "Management0", "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Wrong source-interface configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Source interface mismatch - Expected: Management0 Actual: Management1"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-vrf",
|
||||
|
@ -81,7 +81,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"intf": "Management0", "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Source-interface Management0 is not configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT Source Interface: Management0 - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -128,7 +128,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"servers": ["10.22.10.91", "10.22.10.92"], "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["TACACS servers ['10.22.10.92'] are not configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["TACACS servers 10.22.10.92 are not configured in VRF MGMT"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-vrf",
|
||||
|
@ -145,7 +145,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"servers": ["10.22.10.91"], "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["TACACS servers ['10.22.10.91'] are not configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["TACACS servers 10.22.10.91 are not configured in VRF MGMT"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -192,7 +192,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"groups": ["GROUP1"]},
|
||||
"expected": {"result": "failure", "messages": ["TACACS server group(s) ['GROUP1'] are not configured"]},
|
||||
"expected": {"result": "failure", "messages": ["TACACS server group(s) GROUP1 are not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "success-login-enable",
|
||||
|
@ -244,7 +244,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "local"], "types": ["login", "enable"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA authentication methods ['group tacacs+', 'local'] are not matching for login console"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA authentication methods group tacacs+, local are not matching for login console"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-login-default",
|
||||
|
@ -257,7 +257,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "local"], "types": ["login", "enable"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA authentication methods ['group tacacs+', 'local'] are not matching for ['login']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA authentication methods group tacacs+, local are not matching for login"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -293,7 +293,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "local"], "types": ["commands", "exec"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA authorization methods ['group tacacs+', 'local'] are not matching for ['commands']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA authorization methods group tacacs+, local are not matching for commands"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-exec",
|
||||
|
@ -305,7 +305,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "local"], "types": ["commands", "exec"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA authorization methods ['group tacacs+', 'local'] are not matching for ['exec']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA authorization methods group tacacs+, local are not matching for exec"]},
|
||||
},
|
||||
{
|
||||
"name": "success-commands-exec-system",
|
||||
|
@ -347,7 +347,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "logging"], "types": ["commands", "exec", "system"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA default accounting is not configured for ['commands']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA default accounting is not configured for commands"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-configured-empty",
|
||||
|
@ -361,7 +361,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "logging"], "types": ["commands", "exec", "system"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA default accounting is not configured for ['system', 'exec', 'commands']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA default accounting is not configured for system, exec, commands"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-matching",
|
||||
|
@ -375,7 +375,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "logging"], "types": ["commands", "exec", "system"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA accounting default methods ['group tacacs+', 'logging'] are not matching for ['commands']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA accounting default methods group tacacs+, logging are not matching for commands"]},
|
||||
},
|
||||
{
|
||||
"name": "success-commands-exec-system",
|
||||
|
@ -476,7 +476,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "logging"], "types": ["commands", "exec", "system"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA console accounting is not configured for ['commands']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA console accounting is not configured for commands"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-configured-empty",
|
||||
|
@ -490,7 +490,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "logging"], "types": ["commands", "exec", "system"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA console accounting is not configured for ['system', 'exec', 'commands']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA console accounting is not configured for system, exec, commands"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-matching",
|
||||
|
@ -522,6 +522,6 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"methods": ["tacacs+", "logging"], "types": ["commands", "exec", "system"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA accounting console methods ['group tacacs+', 'logging'] are not matching for ['commands']"]},
|
||||
"expected": {"result": "failure", "messages": ["AAA accounting console methods group tacacs+, logging are not matching for commands"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
@ -94,7 +94,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Adaptive virtual topology paths are not configured."],
|
||||
"messages": ["Adaptive virtual topology paths are not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -174,9 +174,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"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.",
|
||||
"VRF: guest Profile: GUEST-AVT-POLICY-DEFAULT AVT path: direct:10 - Not active",
|
||||
"VRF: default Profile: CONTROL-PLANE-PROFILE AVT path: direct:1 - Not active",
|
||||
"VRF: default Profile: DEFAULT-AVT-POLICY-DEFAULT AVT path: direct:10 - Not active",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -257,10 +257,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"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.",
|
||||
"VRF: data Profile: DATA-AVT-POLICY-DEFAULT AVT path: direct:10 - Invalid",
|
||||
"VRF: guest Profile: GUEST-AVT-POLICY-DEFAULT AVT path: direct:8 - Invalid",
|
||||
"VRF: default Profile: CONTROL-PLANE-PROFILE AVT path: direct:10 - Invalid",
|
||||
"VRF: default Profile: DEFAULT-AVT-POLICY-DEFAULT AVT path: direct:8 - Invalid",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -341,13 +341,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"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.",
|
||||
"VRF: data Profile: DATA-AVT-POLICY-DEFAULT AVT path: direct:10 - Invalid and not active",
|
||||
"VRF: data Profile: DATA-AVT-POLICY-DEFAULT AVT path: direct:1 - Not active",
|
||||
"VRF: guest Profile: GUEST-AVT-POLICY-DEFAULT AVT path: direct:10 - Invalid",
|
||||
"VRF: guest Profile: GUEST-AVT-POLICY-DEFAULT AVT path: direct:8 - Invalid and not active",
|
||||
"VRF: default Profile: CONTROL-PLANE-PROFILE AVT path: direct:10 - Invalid and not active",
|
||||
"VRF: default Profile: DEFAULT-AVT-POLICY-DEFAULT AVT path: direct:10 - Not active",
|
||||
"VRF: default Profile: DEFAULT-AVT-POLICY-DEFAULT AVT path: direct:8 - Invalid and not active",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -444,7 +444,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["AVT MGMT-AVT-POLICY-DEFAULT VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.1) - No AVT path configured"],
|
||||
"messages": ["AVT: MGMT-AVT-POLICY-DEFAULT VRF: default Destination: 10.101.255.2 Next-hop: 10.101.255.1 - No AVT path configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -507,8 +507,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"AVT DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.11) Path Type: multihop - Path not found",
|
||||
"AVT DATA-AVT-POLICY-CONTROL-PLANE VRF: data (Destination: 10.101.255.1, Next-hop: 10.101.255.21) Path Type: direct - Path not found",
|
||||
"AVT: DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default Destination: 10.101.255.2 Next-hop: 10.101.255.11 Path Type: multihop - Path not found",
|
||||
"AVT: DATA-AVT-POLICY-CONTROL-PLANE VRF: data Destination: 10.101.255.1 Next-hop: 10.101.255.21 Path Type: direct - Path not found",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -571,8 +571,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"AVT DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.11) - Path not found",
|
||||
"AVT DATA-AVT-POLICY-CONTROL-PLANE VRF: data (Destination: 10.101.255.1, Next-hop: 10.101.255.21) - Path not found",
|
||||
"AVT: DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default Destination: 10.101.255.2 Next-hop: 10.101.255.11 - Path not found",
|
||||
"AVT: DATA-AVT-POLICY-CONTROL-PLANE VRF: data Destination: 10.101.255.1 Next-hop: 10.101.255.21 - Path not found",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -646,12 +646,12 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"AVT DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.1) - "
|
||||
"Incorrect path multihop:3 - Valid: False, Active: True",
|
||||
"AVT DATA-AVT-POLICY-CONTROL-PLANE VRF: data (Destination: 10.101.255.1, Next-hop: 10.101.255.1) - "
|
||||
"Incorrect path direct:10 - Valid: False, Active: True",
|
||||
"AVT DATA-AVT-POLICY-CONTROL-PLANE VRF: data (Destination: 10.101.255.1, Next-hop: 10.101.255.1) - "
|
||||
"Incorrect path direct:9 - Valid: True, Active: False",
|
||||
"AVT: DEFAULT-AVT-POLICY-CONTROL-PLANE VRF: default Destination: 10.101.255.2 Next-hop: 10.101.255.1 - "
|
||||
"Incorrect path multihop:3 - Valid: False Active: True",
|
||||
"AVT: DATA-AVT-POLICY-CONTROL-PLANE VRF: data Destination: 10.101.255.1 Next-hop: 10.101.255.1 - "
|
||||
"Incorrect path direct:10 - Valid: False Active: True",
|
||||
"AVT: DATA-AVT-POLICY-CONTROL-PLANE VRF: data Destination: 10.101.255.1 Next-hop: 10.101.255.1 - "
|
||||
"Incorrect path direct:9 - Valid: True Active: False",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -667,6 +667,6 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyAVTRole,
|
||||
"eos_data": [{"role": "transit"}],
|
||||
"inputs": {"role": "edge"},
|
||||
"expected": {"result": "failure", "messages": ["Expected AVT role as `edge`, but found `transit` instead."]},
|
||||
"expected": {"result": "failure", "messages": ["AVT role mismatch - Expected: edge Actual: transit"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.bfd.py."""
|
||||
|
@ -27,6 +27,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"operTxInterval": 1200000,
|
||||
"operRxInterval": 1200000,
|
||||
"detectMult": 3,
|
||||
"detectTime": 3600000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +43,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"operTxInterval": 1200000,
|
||||
"operRxInterval": 1200000,
|
||||
"detectMult": 3,
|
||||
"detectTime": 3600000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +61,55 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-detection-time",
|
||||
"test": VerifyBFDPeersIntervals,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"ipv4Neighbors": {
|
||||
"192.0.255.7": {
|
||||
"peerStats": {
|
||||
"": {
|
||||
"peerStatsDetail": {
|
||||
"operTxInterval": 1200000,
|
||||
"operRxInterval": 1200000,
|
||||
"detectMult": 3,
|
||||
"detectTime": 3600000,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"MGMT": {
|
||||
"ipv4Neighbors": {
|
||||
"192.0.255.70": {
|
||||
"peerStats": {
|
||||
"": {
|
||||
"peerStatsDetail": {
|
||||
"operTxInterval": 1200000,
|
||||
"operRxInterval": 1200000,
|
||||
"detectMult": 3,
|
||||
"detectTime": 3600000,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"bfd_peers": [
|
||||
{"peer_address": "192.0.255.7", "vrf": "default", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3, "detection_time": 3600},
|
||||
{"peer_address": "192.0.255.70", "vrf": "MGMT", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3, "detection_time": 3600},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-peer",
|
||||
"test": VerifyBFDPeersIntervals,
|
||||
|
@ -74,6 +125,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"operTxInterval": 1200000,
|
||||
"operRxInterval": 1200000,
|
||||
"detectMult": 3,
|
||||
"detectTime": 3600000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +141,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"operTxInterval": 1200000,
|
||||
"operRxInterval": 1200000,
|
||||
"detectMult": 3,
|
||||
"detectTime": 3600000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,8 +153,8 @@ DATA: list[dict[str, Any]] = [
|
|||
],
|
||||
"inputs": {
|
||||
"bfd_peers": [
|
||||
{"peer_address": "192.0.255.7", "vrf": "CS", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3},
|
||||
{"peer_address": "192.0.255.70", "vrf": "MGMT", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3},
|
||||
{"peer_address": "192.0.255.7", "vrf": "CS", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3, "detection_time": 3600},
|
||||
{"peer_address": "192.0.255.70", "vrf": "MGMT", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3, "detection_time": 3600},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
|
@ -127,6 +180,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"operTxInterval": 1300000,
|
||||
"operRxInterval": 1200000,
|
||||
"detectMult": 4,
|
||||
"detectTime": 4000000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,6 +196,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"operTxInterval": 120000,
|
||||
"operRxInterval": 120000,
|
||||
"detectMult": 5,
|
||||
"detectTime": 4000000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +223,66 @@ DATA: list[dict[str, Any]] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-timers-with-detection-time",
|
||||
"test": VerifyBFDPeersIntervals,
|
||||
"eos_data": [
|
||||
{
|
||||
"vrfs": {
|
||||
"default": {
|
||||
"ipv4Neighbors": {
|
||||
"192.0.255.7": {
|
||||
"peerStats": {
|
||||
"": {
|
||||
"peerStatsDetail": {
|
||||
"operTxInterval": 1300000,
|
||||
"operRxInterval": 1200000,
|
||||
"detectMult": 4,
|
||||
"detectTime": 4000000,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"MGMT": {
|
||||
"ipv4Neighbors": {
|
||||
"192.0.255.70": {
|
||||
"peerStats": {
|
||||
"": {
|
||||
"peerStatsDetail": {
|
||||
"operTxInterval": 120000,
|
||||
"operRxInterval": 120000,
|
||||
"detectMult": 5,
|
||||
"detectTime": 4000000,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"bfd_peers": [
|
||||
{"peer_address": "192.0.255.7", "vrf": "default", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3, "detection_time": 3600},
|
||||
{"peer_address": "192.0.255.70", "vrf": "MGMT", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3, "detection_time": 3600},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Peer: 192.0.255.7 VRF: default - Incorrect Transmit interval - Expected: 1200 Actual: 1300",
|
||||
"Peer: 192.0.255.7 VRF: default - Incorrect Multiplier - Expected: 3 Actual: 4",
|
||||
"Peer: 192.0.255.7 VRF: default - Incorrect Detection Time - Expected: 3600 Actual: 4000",
|
||||
"Peer: 192.0.255.70 VRF: MGMT - Incorrect Transmit interval - Expected: 1200 Actual: 120",
|
||||
"Peer: 192.0.255.70 VRF: MGMT - Incorrect Receive interval - Expected: 1200 Actual: 120",
|
||||
"Peer: 192.0.255.70 VRF: MGMT - Incorrect Multiplier - Expected: 3 Actual: 5",
|
||||
"Peer: 192.0.255.70 VRF: MGMT - Incorrect Detection Time - Expected: 3600 Actual: 4000",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifyBFDSpecificPeers,
|
||||
|
@ -356,7 +471,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["No IPv4 BFD peers are configured for any VRF."],
|
||||
"messages": ["No IPv4 BFD peers are configured for any VRF"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -622,7 +737,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"result": "failure",
|
||||
"messages": [
|
||||
"Peer: 192.0.255.7 VRF: default - `isis` routing protocol(s) not configured",
|
||||
"Peer: 192.0.255.70 VRF: MGMT - `isis` `ospf` routing protocol(s) not configured",
|
||||
"Peer: 192.0.255.70 VRF: MGMT - `isis`, `ospf` routing protocol(s) not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Data for testing anta.tests.configuration."""
|
||||
|
@ -58,6 +58,6 @@ DATA: list[dict[str, Any]] = [
|
|||
"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'"]},
|
||||
"expected": {"result": "failure", "messages": ["Following patterns were not found: 'bla', 'bleh'"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.connectivity.py."""
|
||||
|
@ -45,6 +45,63 @@ DATA: list[dict[str, Any]] = [
|
|||
],
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-expected-unreachable",
|
||||
"test": VerifyReachability,
|
||||
"eos_data": [
|
||||
{
|
||||
"messages": [
|
||||
"""PING 10.0.0.1 (10.0.0.1) from 10.0.0.5 : 72(100) bytes of data.
|
||||
|
||||
--- 10.0.0.1 ping statistics ---
|
||||
2 packets transmitted, 0 received, 100% packet loss, time 10ms
|
||||
""",
|
||||
],
|
||||
},
|
||||
],
|
||||
"inputs": {"hosts": [{"destination": "10.0.0.1", "source": "10.0.0.5", "reachable": False}]},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-ipv6",
|
||||
"test": VerifyReachability,
|
||||
"eos_data": [
|
||||
{
|
||||
"messages": [
|
||||
"""PING fd12:3456:789a:1::2(fd12:3456:789a:1::2) from fd12:3456:789a:1::1 : 52 data bytes
|
||||
60 bytes from fd12:3456:789a:1::2: icmp_seq=1 ttl=64 time=0.097 ms
|
||||
60 bytes from fd12:3456:789a:1::2: icmp_seq=2 ttl=64 time=0.033 ms
|
||||
|
||||
--- fd12:3456:789a:1::2 ping statistics ---
|
||||
2 packets transmitted, 2 received, 0% packet loss, time 0ms
|
||||
rtt min/avg/max/mdev = 0.033/0.065/0.097/0.032 ms, ipg/ewma 0.148/0.089 ms
|
||||
""",
|
||||
],
|
||||
},
|
||||
],
|
||||
"inputs": {"hosts": [{"destination": "fd12:3456:789a:1::2", "source": "fd12:3456:789a:1::1"}]},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-ipv6-vlan",
|
||||
"test": VerifyReachability,
|
||||
"eos_data": [
|
||||
{
|
||||
"messages": [
|
||||
"""PING fd12:3456:789a:1::2(fd12:3456:789a:1::2) 52 data bytes
|
||||
60 bytes from fd12:3456:789a:1::2: icmp_seq=1 ttl=64 time=0.094 ms
|
||||
60 bytes from fd12:3456:789a:1::2: icmp_seq=2 ttl=64 time=0.027 ms
|
||||
|
||||
--- fd12:3456:789a:1::2 ping statistics ---
|
||||
2 packets transmitted, 2 received, 0% packet loss, time 0ms
|
||||
rtt min/avg/max/mdev = 0.027/0.060/0.094/0.033 ms, ipg/ewma 0.152/0.085 ms
|
||||
""",
|
||||
],
|
||||
},
|
||||
],
|
||||
"inputs": {"hosts": [{"destination": "fd12:3456:789a:1::2", "source": "vl110"}]},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-interface",
|
||||
"test": VerifyReachability,
|
||||
|
@ -153,7 +210,24 @@ DATA: list[dict[str, Any]] = [
|
|||
],
|
||||
},
|
||||
],
|
||||
"expected": {"result": "failure", "messages": ["Host 10.0.0.11 (src: 10.0.0.5, vrf: default, size: 100B, repeat: 2) - Unreachable"]},
|
||||
"expected": {"result": "failure", "messages": ["Host: 10.0.0.11 Source: 10.0.0.5 VRF: default - Unreachable"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-ipv6",
|
||||
"test": VerifyReachability,
|
||||
"eos_data": [
|
||||
{
|
||||
"messages": [
|
||||
"""PING fd12:3456:789a:1::2(fd12:3456:789a:1::2) from fd12:3456:789a:1::1 : 52 data bytes
|
||||
|
||||
--- fd12:3456:789a:1::3 ping statistics ---
|
||||
2 packets transmitted, 0 received, 100% packet loss, time 10ms
|
||||
""",
|
||||
],
|
||||
},
|
||||
],
|
||||
"inputs": {"hosts": [{"destination": "fd12:3456:789a:1::2", "source": "fd12:3456:789a:1::1"}]},
|
||||
"expected": {"result": "failure", "messages": ["Host: fd12:3456:789a:1::2 Source: fd12:3456:789a:1::1 VRF: default - Unreachable"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-interface",
|
||||
|
@ -187,7 +261,7 @@ DATA: list[dict[str, Any]] = [
|
|||
],
|
||||
},
|
||||
],
|
||||
"expected": {"result": "failure", "messages": ["Host 10.0.0.11 (src: Management0, vrf: default, size: 100B, repeat: 2) - Unreachable"]},
|
||||
"expected": {"result": "failure", "messages": ["Host: 10.0.0.11 Source: Management0 VRF: default - Unreachable"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-size",
|
||||
|
@ -209,7 +283,31 @@ DATA: list[dict[str, Any]] = [
|
|||
],
|
||||
},
|
||||
],
|
||||
"expected": {"result": "failure", "messages": ["Host 10.0.0.1 (src: Management0, vrf: default, size: 1501B, repeat: 5, df-bit: enabled) - Unreachable"]},
|
||||
"expected": {"result": "failure", "messages": ["Host: 10.0.0.1 Source: Management0 VRF: default - Unreachable"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-expected-unreachable",
|
||||
"test": VerifyReachability,
|
||||
"eos_data": [
|
||||
{
|
||||
"messages": [
|
||||
"""PING 10.0.0.1 (10.0.0.1) from 10.0.0.5 : 72(100) bytes of data.
|
||||
80 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.247 ms
|
||||
80 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.072 ms
|
||||
|
||||
--- 10.0.0.1 ping statistics ---
|
||||
2 packets transmitted, 2 received, 0% packet loss, time 0ms
|
||||
rtt min/avg/max/mdev = 0.072/0.159/0.247/0.088 ms, ipg/ewma 0.370/0.225 ms
|
||||
|
||||
""",
|
||||
],
|
||||
},
|
||||
],
|
||||
"inputs": {"hosts": [{"destination": "10.0.0.1", "source": "10.0.0.5", "reachable": False}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Host: 10.0.0.1 Source: 10.0.0.5 VRF: default - Destination is expected to be unreachable but found reachable"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -330,7 +428,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
|
||||
],
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - Port not found"]},
|
||||
"expected": {"result": "failure", "messages": ["Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - Port not found"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-neighbor",
|
||||
|
@ -363,7 +461,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
|
||||
],
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - No LLDP neighbors"]},
|
||||
"expected": {"result": "failure", "messages": ["Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - No LLDP neighbors"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-neighbor",
|
||||
|
@ -412,7 +510,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE2/Ethernet2"],
|
||||
"messages": ["Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - Wrong LLDP neighbors: DC1-SPINE2/Ethernet2"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -450,9 +548,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Port Ethernet1 (Neighbor: DC1-SPINE1, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE1/Ethernet2",
|
||||
"Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - No LLDP neighbors",
|
||||
"Port Ethernet3 (Neighbor: DC1-SPINE3, Neighbor Port: Ethernet1) - Port not found",
|
||||
"Port: Ethernet1 Neighbor: DC1-SPINE1 Neighbor Port: Ethernet1 - Wrong LLDP neighbors: DC1-SPINE1/Ethernet2",
|
||||
"Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - No LLDP neighbors",
|
||||
"Port: Ethernet3 Neighbor: DC1-SPINE3 Neighbor Port: Ethernet1 - Port not found",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -498,7 +596,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Port Ethernet1 (Neighbor: DC1-SPINE3, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE1/Ethernet1, DC1-SPINE2/Ethernet1"],
|
||||
"messages": ["Port: Ethernet1 Neighbor: DC1-SPINE3 Neighbor Port: Ethernet1 - Wrong LLDP neighbors: DC1-SPINE1/Ethernet1, DC1-SPINE2/Ethernet1"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Data for testing anta.tests.cvx."""
|
||||
|
@ -60,7 +60,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyMcsClientMounts,
|
||||
"eos_data": [{"mountStates": [{"path": "mcs/v1/toSwitch/28-99-3a-8f-93-7b", "type": "Mcs::DeviceConfigV1", "state": "mountStatePreservedUnmounted"}]}],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["MCS Client mount states are not valid: mountStatePreservedUnmounted"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MCS Client mount states are not valid - Expected: mountStateMountComplete Actual: mountStatePreservedUnmounted"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-partial-haclient",
|
||||
|
@ -74,7 +77,10 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["MCS Client mount states are not valid: mountStatePreservedUnmounted"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MCS Client mount states are not valid - Expected: mountStateMountComplete Actual: mountStatePreservedUnmounted"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-full-haclient",
|
||||
|
@ -88,7 +94,10 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["MCS Client mount states are not valid: mountStatePreservedUnmounted"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MCS Client mount states are not valid - Expected: mountStateMountComplete Actual: mountStatePreservedUnmounted"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-non-mcs-client",
|
||||
|
@ -111,7 +120,10 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["MCS Client mount states are not valid: mountStatePreservedUnmounted"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MCS Client mount states are not valid - Expected: mountStateMountComplete Actual: mountStatePreservedUnmounted"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success-enabled",
|
||||
|
@ -140,18 +152,31 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure - no enabled state",
|
||||
"name": "failure-invalid-state",
|
||||
"test": VerifyManagementCVX,
|
||||
"eos_data": [
|
||||
{
|
||||
"clusterStatus": {
|
||||
"enabled": False,
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {"enabled": True},
|
||||
"expected": {"result": "failure", "messages": ["Management CVX status is not valid: Expected: enabled Actual: disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-enabled state",
|
||||
"test": VerifyManagementCVX,
|
||||
"eos_data": [{"clusterStatus": {}}],
|
||||
"inputs": {"enabled": False},
|
||||
"expected": {"result": "failure", "messages": ["Management CVX status is not valid: None"]},
|
||||
"expected": {"result": "failure", "messages": ["Management CVX status - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure - no clusterStatus",
|
||||
"test": VerifyManagementCVX,
|
||||
"eos_data": [{}],
|
||||
"inputs": {"enabled": False},
|
||||
"expected": {"result": "failure", "messages": ["Management CVX status is not valid: None"]},
|
||||
"expected": {"result": "failure", "messages": ["Management CVX status - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -189,7 +214,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"connections_count": 1},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["No mount status for media-leaf-1", "Incorrect CVX successful connections count. Expected: 1, Actual : 0"],
|
||||
"messages": ["Host: media-leaf-1 - No mount status found", "Incorrect CVX successful connections count - Expected: 1 Actual: 0"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -221,8 +246,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Incorrect number of mount path states for media-leaf-1 - Expected: 3, Actual: 2",
|
||||
"Unexpected MCS path type for media-leaf-1: 'Mcs::ApiStatus'.",
|
||||
"Host: media-leaf-1 - Incorrect number of mount path states - Expected: 3 Actual: 2",
|
||||
"Host: media-leaf-1 - Unexpected MCS path type - Expected: Mcs::ApiConfigRedundancyStatus, Mcs::ActiveFlows, "
|
||||
"Mcs::Client::Status Actual: Mcs::ApiStatus",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -253,7 +279,13 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
],
|
||||
"inputs": {"connections_count": 1},
|
||||
"expected": {"result": "failure", "messages": ["Unexpected MCS path type for media-leaf-1: 'Mcs::ApiStatus'"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Host: media-leaf-1 - Unexpected MCS path type - Expected: Mcs::ApiConfigRedundancyStatus, Mcs::ActiveFlows, Mcs::Client::Status"
|
||||
" Actual: Mcs::ApiStatus"
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-invalid-mount-state",
|
||||
|
@ -284,7 +316,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"connections_count": 1},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MCS server mount state for path 'Mcs::ApiConfigRedundancyStatus' is not valid is for media-leaf-1: 'mountStateMountFailed'"],
|
||||
"messages": [
|
||||
"Host: media-leaf-1 Path Type: Mcs::ApiConfigRedundancyStatus - MCS server mount state is not valid - Expected: mountStateMountComplete"
|
||||
" Actual:mountStateMountFailed"
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -306,14 +341,14 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
],
|
||||
"inputs": {"connections_count": 1},
|
||||
"expected": {"result": "failure", "messages": ["MCS mount state not detected", "Incorrect CVX successful connections count. Expected: 1, Actual : 0"]},
|
||||
"expected": {"result": "failure", "messages": ["MCS mount state not detected", "Incorrect CVX successful connections count - Expected: 1 Actual: 0"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-connections",
|
||||
"test": VerifyMcsServerMounts,
|
||||
"eos_data": [{}],
|
||||
"inputs": {"connections_count": 1},
|
||||
"expected": {"result": "failure", "messages": ["CVX connections are not available."]},
|
||||
"expected": {"result": "failure", "messages": ["CVX connections are not available"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -357,7 +392,7 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
],
|
||||
"inputs": {"connections_count": 2},
|
||||
"expected": {"result": "failure", "messages": ["CVX active connections count. Expected: 2, Actual : 1"]},
|
||||
"expected": {"result": "failure", "messages": ["CVX active connections count - Expected: 2 Actual: 1"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-connections",
|
||||
|
@ -414,7 +449,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
|
||||
],
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["CVX Role is not valid: Standby"]},
|
||||
"expected": {"result": "failure", "messages": ["CVX Role is not valid: Expected: Master Actual: Standby"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-cvx-enabled",
|
||||
|
@ -473,7 +508,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
|
||||
],
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["Unexpected number of peers 1 vs 2", "cvx-red-3 is not present"]},
|
||||
"expected": {"result": "failure", "messages": ["Unexpected number of peers - Expected: 2 Actual: 1", "cvx-red-3 - Not present"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-invalid-peers",
|
||||
|
@ -495,7 +530,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
|
||||
],
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["Unexpected number of peers 0 vs 2", "cvx-red-2 is not present", "cvx-red-3 is not present"]},
|
||||
"expected": {"result": "failure", "messages": ["Unexpected number of peers - Expected: 2 Actual: 0", "cvx-red-2 - Not present", "cvx-red-3 - Not present"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-registration-error",
|
||||
|
@ -520,6 +555,6 @@ DATA: list[dict[str, Any]] = [
|
|||
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
|
||||
],
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["cvx-red-2 registration state is not complete: Registration error"]},
|
||||
"expected": {"result": "failure", "messages": ["cvx-red-2 - Invalid registration state - Expected: Registration complete Actual: Registration error"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.field_notices."""
|
||||
|
@ -45,7 +45,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["device is running incorrect version of aboot (4.0.1)"],
|
||||
"messages": ["Device is running incorrect version of aboot 4.0.1"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -65,7 +65,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["device is running incorrect version of aboot (4.1.0)"],
|
||||
"messages": ["Device is running incorrect version of aboot 4.1.0"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -85,7 +85,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["device is running incorrect version of aboot (6.0.1)"],
|
||||
"messages": ["Device is running incorrect version of aboot 6.0.1"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -105,7 +105,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["device is running incorrect version of aboot (6.1.1)"],
|
||||
"messages": ["Device is running incorrect version of aboot 6.1.1"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -125,7 +125,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "skipped",
|
||||
"messages": ["device is not impacted by FN044"],
|
||||
"messages": ["Device is not impacted by FN044"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
@ -22,18 +22,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"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,
|
||||
},
|
||||
|
@ -52,18 +47,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"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,
|
||||
},
|
||||
|
@ -113,7 +103,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"trackers": [{"name": "FLOW-Sample"}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Hardware flow tracker `FLOW-Sample` is not configured."],
|
||||
"messages": ["Flow Tracker: FLOW-Sample - Not found"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -127,18 +117,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"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,
|
||||
},
|
||||
|
@ -159,7 +144,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Hardware flow tracker `FLOW-TRACKER` is not active.", "Hardware flow tracker `HARDWARE-TRACKER` is not active."],
|
||||
"messages": ["Flow Tracker: FLOW-TRACKER - Disabled", "Flow Tracker: HARDWARE-TRACKER - Disabled"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -173,18 +158,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"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,
|
||||
},
|
||||
|
@ -204,10 +184,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"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",
|
||||
"Flow Tracker: FLOW-TRACKER Inactive Timeout: 6000 Active Interval: 30000 - Incorrect timers - Inactive Timeout: 60000 OnActive Interval: 300000",
|
||||
"Flow Tracker: HARDWARE-TRACKER Inactive Timeout: 60000 Active Interval: 300000 - Incorrect timers - "
|
||||
"Inactive Timeout: 6000 OnActive Interval: 30000",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -225,12 +204,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000},
|
||||
"CVP-FLOW": {"localIntf": "Loopback0", "templateInterval": 3600000},
|
||||
},
|
||||
}
|
||||
},
|
||||
"running": True,
|
||||
},
|
||||
{
|
||||
"trackers": {
|
||||
},
|
||||
"HARDWARE-TRACKER": {
|
||||
"active": True,
|
||||
"inactiveTimeout": 6000,
|
||||
|
@ -239,7 +213,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000},
|
||||
"Hardware-flow": {"localIntf": "Loopback10", "templateInterval": 3600000},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"running": True,
|
||||
},
|
||||
|
@ -265,15 +239,11 @@ DATA: list[dict[str, Any]] = [
|
|||
"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",
|
||||
"Flow Tracker: FLOW-TRACKER Exporter: CVP-FLOW - Incorrect local interface - Expected: Loopback10 Actual: Loopback0",
|
||||
"Flow Tracker: FLOW-TRACKER Exporter: CVP-FLOW - Incorrect template interval - Expected: 3500000 Actual: 3600000",
|
||||
"Flow Tracker: HARDWARE-TRACKER Exporter: Hardware-flow - Incorrect local interface - Expected: Loopback99 Actual: Loopback10",
|
||||
"Flow Tracker: HARDWARE-TRACKER Exporter: Hardware-flow - Incorrect template interval - Expected: 3000000 Actual: 3600000",
|
||||
"Flow Tracker: HARDWARE-TRACKER Exporter: Reverse-flow - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -288,34 +258,19 @@ DATA: list[dict[str, Any]] = [
|
|||
"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,
|
||||
|
@ -324,12 +279,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000},
|
||||
"CVP-FLOW": {"localIntf": "Loopback0", "templateInterval": 3600000},
|
||||
},
|
||||
}
|
||||
},
|
||||
"running": True,
|
||||
},
|
||||
{
|
||||
"trackers": {
|
||||
},
|
||||
"HARDWARE-TRACKER2": {
|
||||
"active": True,
|
||||
"inactiveTimeout": 6000,
|
||||
|
@ -338,7 +288,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000},
|
||||
"Hardware-flow": {"localIntf": "Loopback10", "templateInterval": 3600000},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"running": True,
|
||||
},
|
||||
|
@ -374,17 +324,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"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",
|
||||
"Flow Tracker: FLOW-Sample - Not found",
|
||||
"Flow Tracker: FLOW-TRIGGER - Disabled",
|
||||
"Flow Tracker: HARDWARE-FLOW Inactive Timeout: 60000 Active Interval: 300000 - Incorrect timers - Inactive Timeout: 6000 OnActive Interval: 30000",
|
||||
"Flow Tracker: FLOW-TRACKER2 Exporter: CVP-FLOW - Incorrect local interface - Expected: Loopback10 Actual: Loopback0",
|
||||
"Flow Tracker: FLOW-TRACKER2 Exporter: CVP-FLOW - Incorrect template interval - Expected: 3500000 Actual: 3600000",
|
||||
"Flow Tracker: HARDWARE-TRACKER2 Exporter: Hardware-flow - Incorrect local interface - Expected: Loopback99 Actual: Loopback10",
|
||||
"Flow Tracker: HARDWARE-TRACKER2 Exporter: Hardware-flow - Incorrect template interval - Expected: 3000000 Actual: 3600000",
|
||||
"Flow Tracker: HARDWARE-TRACKER2 Exporter: Reverse-flow - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Data for testing anta.tests.configuration."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
@ -45,7 +45,13 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"manufacturers": ["Arista"]},
|
||||
"expected": {"result": "failure", "messages": ["Some transceivers are from unapproved manufacturers: {'1': 'Arista Networks', '2': 'Arista Networks'}"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Interface: 1 - Transceiver is from unapproved manufacturers - Expected: Arista Actual: Arista Networks",
|
||||
"Interface: 2 - Transceiver is from unapproved manufacturers - Expected: Arista Actual: Arista Networks",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -72,12 +78,12 @@ DATA: list[dict[str, Any]] = [
|
|||
"ambientThreshold": 45,
|
||||
"cardSlots": [],
|
||||
"shutdownOnOverheat": "True",
|
||||
"systemStatus": "temperatureKO",
|
||||
"systemStatus": "temperatureCritical",
|
||||
"recoveryModeOnOverheat": "recoveryModeNA",
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Device temperature exceeds acceptable limits. Current system status: 'temperatureKO'"]},
|
||||
"expected": {"result": "failure", "messages": ["Device temperature exceeds acceptable limits - Expected: temperatureOk Actual: temperatureCritical"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -139,11 +145,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following sensors are operating outside the acceptable temperature range or have raised alerts: "
|
||||
"{'DomTemperatureSensor54': "
|
||||
"{'hwStatus': 'ko', 'alertCount': 0}}",
|
||||
],
|
||||
"messages": ["Sensor: DomTemperatureSensor54 - Invalid hardware state - Expected: ok Actual: ko"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -176,11 +178,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following sensors are operating outside the acceptable temperature range or have raised alerts: "
|
||||
"{'DomTemperatureSensor54': "
|
||||
"{'hwStatus': 'ok', 'alertCount': 1}}",
|
||||
],
|
||||
"messages": ["Sensor: DomTemperatureSensor54 - Incorrect alert counter - Expected: 0 Actual: 1"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -227,7 +225,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Device system cooling is not OK: 'coolingKo'"]},
|
||||
"expected": {"result": "failure", "messages": ["Device system cooling status invalid - Expected: coolingOk Actual: coolingKo"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -626,7 +624,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"states": ["ok", "Not Inserted"]},
|
||||
"expected": {"result": "failure", "messages": ["Fan 1/1 on Fan Tray 1 is: 'down'"]},
|
||||
"expected": {"result": "failure", "messages": ["Fan Tray: 1 Fan: 1/1 - Invalid state - Expected: ok, Not Inserted Actual: down"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-power-supply",
|
||||
|
@ -759,7 +757,12 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"states": ["ok", "Not Inserted"]},
|
||||
"expected": {"result": "failure", "messages": ["Fan PowerSupply1/1 on PowerSupply PowerSupply1 is: 'down'"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Power Slot: PowerSupply1 Fan: PowerSupply1/1 - Invalid state - Expected: ok, Not Inserted Actual: down",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -900,7 +903,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"states": ["ok"]},
|
||||
"expected": {"result": "failure", "messages": ["The following power supplies status are not in the accepted states list: {'1': {'state': 'powerLoss'}}"]},
|
||||
"expected": {"result": "failure", "messages": ["Power Slot: 1 - Invalid power supplies state - Expected: ok Actual: powerLoss"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -914,6 +917,6 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyAdverseDrops,
|
||||
"eos_data": [{"totalAdverseDrops": 10}],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Device totalAdverseDrops counter is: '10'"]},
|
||||
"expected": {"result": "failure", "messages": ["Incorrect total adverse drops counter - Expected: 0 Actual: 10"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.interfaces."""
|
||||
|
@ -508,7 +508,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"threshold": 3.0},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["The following interfaces have a usage > 3.0%: {'Ethernet1/1': {'inBpsRate': 10.0}, 'Port-Channel31': {'outBpsRate': 5.0}}"],
|
||||
"messages": [
|
||||
"Interface: Ethernet1/1 BPS Rate: inBpsRate - Usage exceeds the threshold - Expected: < 3.0% Actual: 10.0%",
|
||||
"Interface: Port-Channel31 BPS Rate: outBpsRate - Usage exceeds the threshold - Expected: < 3.0% Actual: 5.0%",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -653,7 +656,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"threshold": 70.0},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Interface Ethernet1/1 or one of its member interfaces is not Full-Duplex. VerifyInterfaceUtilization has not been implemented."],
|
||||
"messages": ["Interface Ethernet1/1 or one of its member interfaces is not Full-Duplex. VerifyInterfaceUtilization has not been implemented"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -787,7 +790,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"memberInterfaces": {
|
||||
"Ethernet3/1": {"bandwidth": 1000000000, "duplex": "duplexHalf"},
|
||||
"Ethernet4/1": {"bandwidth": 1000000000, "duplex": "duplexFull"},
|
||||
"Ethernet4/1": {"bandwidth": 1000000000, "duplex": "duplexHalf"},
|
||||
},
|
||||
"fallbackEnabled": False,
|
||||
"fallbackEnabledType": "fallbackNone",
|
||||
|
@ -798,7 +801,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"threshold": 70.0},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Interface Port-Channel31 or one of its member interfaces is not Full-Duplex. VerifyInterfaceUtilization has not been implemented."],
|
||||
"messages": ["Interface Port-Channel31 or one of its member interfaces is not Full-Duplex. VerifyInterfaceUtilization has not been implemented"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -830,9 +833,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following interface(s) have non-zero error counters: [{'Ethernet1': {'inErrors': 42, 'frameTooLongs': 0, 'outErrors': 0, 'frameTooShorts': 0,"
|
||||
" 'fcsErrors': 0, 'alignmentErrors': 0, 'symbolErrors': 0}}, {'Ethernet6': {'inErrors': 0, 'frameTooLongs': 0, 'outErrors': 0, 'frameTooShorts':"
|
||||
" 0, 'fcsErrors': 0, 'alignmentErrors': 666, 'symbolErrors': 0}}]",
|
||||
"Interface: Ethernet1 - Non-zero error counter(s) - inErrors: 42",
|
||||
"Interface: Ethernet6 - Non-zero error counter(s) - alignmentErrors: 666",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -851,9 +853,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following interface(s) have non-zero error counters: [{'Ethernet1': {'inErrors': 42, 'frameTooLongs': 0, 'outErrors': 10, 'frameTooShorts': 0,"
|
||||
" 'fcsErrors': 0, 'alignmentErrors': 0, 'symbolErrors': 0}}, {'Ethernet6': {'inErrors': 0, 'frameTooLongs': 0, 'outErrors': 0, 'frameTooShorts':"
|
||||
" 0, 'fcsErrors': 0, 'alignmentErrors': 6, 'symbolErrors': 10}}]",
|
||||
"Interface: Ethernet1 - Non-zero error counter(s) - inErrors: 42, outErrors: 10",
|
||||
"Interface: Ethernet6 - Non-zero error counter(s) - alignmentErrors: 6, symbolErrors: 10",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -870,10 +871,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following interface(s) have non-zero error counters: [{'Ethernet1': {'inErrors': 42, 'frameTooLongs': 0, 'outErrors': 2, 'frameTooShorts': 0,"
|
||||
" 'fcsErrors': 0, 'alignmentErrors': 0, 'symbolErrors': 0}}]",
|
||||
],
|
||||
"messages": ["Interface: Ethernet1 - Non-zero error counter(s) - inErrors: 42, outErrors: 2"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -909,8 +907,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following interfaces have non 0 discard counter(s): [{'Ethernet2': {'outDiscards': 42, 'inDiscards': 0}},"
|
||||
" {'Ethernet1': {'outDiscards': 0, 'inDiscards': 42}}]",
|
||||
"Interface: Ethernet2 - Non-zero discard counter(s): outDiscards: 42",
|
||||
"Interface: Ethernet1 - Non-zero discard counter(s): inDiscards: 42",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -948,7 +946,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The following interfaces are in error disabled state: ['Management1', 'Ethernet8']"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Management1 - Link status Error disabled", "Interface: Ethernet8 - Link status Error disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -1126,7 +1124,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"interfaces": [{"name": "Ethernet2", "status": "up"}, {"name": "Ethernet8", "status": "up"}, {"name": "Ethernet3", "status": "up"}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Ethernet8 - Expected: up/up, Actual: down/down"],
|
||||
"messages": ["Ethernet8 - Status mismatch - Expected: up/up, Actual: down/down"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1150,7 +1148,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Ethernet8 - Expected: up/up, Actual: up/down"],
|
||||
"messages": ["Ethernet8 - Status mismatch - Expected: up/up, Actual: up/down"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1166,7 +1164,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"interfaces": [{"name": "PortChannel100", "status": "up"}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Port-Channel100 - Expected: up/up, Actual: down/lowerLayerDown"],
|
||||
"messages": ["Port-Channel100 - Status mismatch - Expected: up/up, Actual: down/lowerLayerDown"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1191,8 +1189,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Ethernet2 - Expected: up/down, Actual: up/unknown",
|
||||
"Ethernet8 - Expected: up/up, Actual: up/down",
|
||||
"Ethernet2 - Status mismatch - Expected: up/down, Actual: up/unknown",
|
||||
"Ethernet8 - Status mismatch - Expected: up/up, Actual: up/down",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -1218,9 +1216,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Ethernet2 - Expected: down, Actual: up",
|
||||
"Ethernet8 - Expected: down, Actual: up",
|
||||
"Ethernet3 - Expected: down, Actual: up",
|
||||
"Ethernet2 - Status mismatch - Expected: down, Actual: up",
|
||||
"Ethernet8 - Status mismatch - Expected: down, Actual: up",
|
||||
"Ethernet3 - Status mismatch - Expected: down, Actual: up",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -1260,7 +1258,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The following interfaces have none 0 storm-control drop counters {'Ethernet1': {'broadcast': 666}}"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Ethernet1 - Non-zero storm-control drop counter(s) - broadcast: 666"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -1306,7 +1304,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The following port-channels have inactive port(s): ['Port-Channel42']"]},
|
||||
"expected": {"result": "failure", "messages": ["Port-Channel42 - Inactive port(s) - Ethernet8"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -1362,7 +1360,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["The following port-channels have received illegal LACP packets on the following ports: [{'Port-Channel42': 'Ethernet8'}]"],
|
||||
"messages": ["Port-Channel42 Interface: Ethernet8 - Illegal LACP packets found"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1417,7 +1415,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"Loopback666": {
|
||||
"name": "Loopback666",
|
||||
"interfaceStatus": "connected",
|
||||
"interfaceStatus": "notconnect",
|
||||
"interfaceAddress": {"ipAddr": {"maskLen": 32, "address": "6.6.6.6"}},
|
||||
"ipv4Routable240": False,
|
||||
"lineProtocolStatus": "down",
|
||||
|
@ -1427,7 +1425,13 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"number": 2},
|
||||
"expected": {"result": "failure", "messages": ["The following Loopbacks are not up: ['Loopback666']"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Interface: Loopback666 - Invalid line protocol status - Expected: up Actual: down",
|
||||
"Interface: Loopback666 - Invalid interface status - Expected: connected Actual: notconnect",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-count-loopback",
|
||||
|
@ -1447,7 +1451,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"number": 2},
|
||||
"expected": {"result": "failure", "messages": ["Found 1 Loopbacks when expecting 2"]},
|
||||
"expected": {"result": "failure", "messages": ["Loopback interface(s) count mismatch: Expected 2 Actual: 1"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -1487,7 +1491,13 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The following SVIs are not up: ['Vlan42']"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"SVI: Vlan42 - Invalid line protocol status - Expected: up Actual: lowerLayerDown",
|
||||
"SVI: Vlan42 - Invalid interface status - Expected: connected Actual: notconnect",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -1703,7 +1713,79 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"mtu": 1500},
|
||||
"expected": {"result": "failure", "messages": ["Some interfaces do not have correct MTU configured:\n[{'Ethernet2': 1600}]"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Ethernet2 - Incorrect MTU - Expected: 1500 Actual: 1600"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-specified-interface-mtu",
|
||||
"test": VerifyL3MTU,
|
||||
"eos_data": [
|
||||
{
|
||||
"interfaces": {
|
||||
"Ethernet2": {
|
||||
"name": "Ethernet2",
|
||||
"forwardingModel": "routed",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"hardware": "ethernet",
|
||||
"mtu": 1500,
|
||||
"l3MtuConfigured": True,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Ethernet10": {
|
||||
"name": "Ethernet10",
|
||||
"forwardingModel": "routed",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"hardware": "ethernet",
|
||||
"mtu": 1502,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Management0": {
|
||||
"name": "Management0",
|
||||
"forwardingModel": "routed",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"hardware": "ethernet",
|
||||
"mtu": 1500,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Port-Channel2": {
|
||||
"name": "Port-Channel2",
|
||||
"forwardingModel": "bridged",
|
||||
"lineProtocolStatus": "lowerLayerDown",
|
||||
"interfaceStatus": "notconnect",
|
||||
"hardware": "portChannel",
|
||||
"mtu": 1500,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Loopback0": {
|
||||
"name": "Loopback0",
|
||||
"forwardingModel": "routed",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"hardware": "loopback",
|
||||
"mtu": 65535,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Vxlan1": {
|
||||
"name": "Vxlan1",
|
||||
"forwardingModel": "bridged",
|
||||
"lineProtocolStatus": "down",
|
||||
"interfaceStatus": "notconnect",
|
||||
"hardware": "vxlan",
|
||||
"mtu": 0,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": {"mtu": 1500, "ignored_interfaces": ["Loopback", "Port-Channel", "Management", "Vxlan"], "specific_mtu": [{"Ethernet10": 1501}]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Ethernet10 - Incorrect MTU - Expected: 1501 Actual: 1502"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -1847,7 +1929,85 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"mtu": 1500},
|
||||
"expected": {"result": "failure", "messages": ["Some L2 interfaces do not have correct MTU configured:\n[{'Ethernet10': 9214}, {'Port-Channel2': 9214}]"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Interface: Ethernet10 - Incorrect MTU configured - Expected: 1500 Actual: 9214",
|
||||
"Interface: Port-Channel2 - Incorrect MTU configured - Expected: 1500 Actual: 9214",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-specific-interface",
|
||||
"test": VerifyL2MTU,
|
||||
"eos_data": [
|
||||
{
|
||||
"interfaces": {
|
||||
"Ethernet2": {
|
||||
"name": "Ethernet2",
|
||||
"forwardingModel": "routed",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"hardware": "ethernet",
|
||||
"mtu": 1600,
|
||||
"l3MtuConfigured": True,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Ethernet10": {
|
||||
"name": "Ethernet10",
|
||||
"forwardingModel": "bridged",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"hardware": "ethernet",
|
||||
"mtu": 9214,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Management0": {
|
||||
"name": "Management0",
|
||||
"forwardingModel": "routed",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"hardware": "ethernet",
|
||||
"mtu": 1500,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Port-Channel2": {
|
||||
"name": "Port-Channel2",
|
||||
"forwardingModel": "bridged",
|
||||
"lineProtocolStatus": "lowerLayerDown",
|
||||
"interfaceStatus": "notconnect",
|
||||
"hardware": "portChannel",
|
||||
"mtu": 9214,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Loopback0": {
|
||||
"name": "Loopback0",
|
||||
"forwardingModel": "routed",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"hardware": "loopback",
|
||||
"mtu": 65535,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
"Vxlan1": {
|
||||
"name": "Vxlan1",
|
||||
"forwardingModel": "bridged",
|
||||
"lineProtocolStatus": "down",
|
||||
"interfaceStatus": "notconnect",
|
||||
"hardware": "vxlan",
|
||||
"mtu": 0,
|
||||
"l3MtuConfigured": False,
|
||||
"l2Mru": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": {"specific_mtu": [{"Et10": 9214}, {"Port-Channel2": 10000}]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Port-Channel2 - Incorrect MTU configured - Expected: 10000 Actual: 9214"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -1859,45 +2019,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"name": "Ethernet1",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"mtu": 1500,
|
||||
"interfaceAddressBrief": {"ipAddr": {"address": "10.1.0.0", "maskLen": 31}},
|
||||
"ipv4Routable240": False,
|
||||
"ipv4Routable0": False,
|
||||
"enabled": True,
|
||||
"description": "P2P_LINK_TO_NW-CORE_Ethernet1",
|
||||
"proxyArp": True,
|
||||
"localProxyArp": False,
|
||||
"gratuitousArp": False,
|
||||
"vrf": "default",
|
||||
"urpf": "disable",
|
||||
"addresslessForwarding": "isInvalid",
|
||||
"directedBroadcastEnabled": False,
|
||||
"maxMssIngress": 0,
|
||||
"maxMssEgress": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"interfaces": {
|
||||
"Ethernet2": {
|
||||
"name": "Ethernet2",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"mtu": 1500,
|
||||
"interfaceAddressBrief": {"ipAddr": {"address": "10.1.0.2", "maskLen": 31}},
|
||||
"ipv4Routable240": False,
|
||||
"ipv4Routable0": False,
|
||||
"enabled": True,
|
||||
"description": "P2P_LINK_TO_SW-CORE_Ethernet1",
|
||||
"proxyArp": True,
|
||||
"localProxyArp": False,
|
||||
"gratuitousArp": False,
|
||||
"vrf": "default",
|
||||
"urpf": "disable",
|
||||
"addresslessForwarding": "isInvalid",
|
||||
"directedBroadcastEnabled": False,
|
||||
"maxMssIngress": 0,
|
||||
"maxMssEgress": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1905,6 +2033,24 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"interfaces": ["Ethernet1", "Ethernet2"]},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-interface-not-found",
|
||||
"test": VerifyIPProxyARP,
|
||||
"eos_data": [
|
||||
{
|
||||
"interfaces": {
|
||||
"Ethernet1": {
|
||||
"name": "Ethernet1",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"proxyArp": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": {"interfaces": ["Ethernet1", "Ethernet2"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Ethernet2 - Not found"]},
|
||||
},
|
||||
{
|
||||
"name": "failure",
|
||||
"test": VerifyIPProxyARP,
|
||||
|
@ -1915,51 +2061,19 @@ DATA: list[dict[str, Any]] = [
|
|||
"name": "Ethernet1",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"mtu": 1500,
|
||||
"interfaceAddressBrief": {"ipAddr": {"address": "10.1.0.0", "maskLen": 31}},
|
||||
"ipv4Routable240": False,
|
||||
"ipv4Routable0": False,
|
||||
"enabled": True,
|
||||
"description": "P2P_LINK_TO_NW-CORE_Ethernet1",
|
||||
"proxyArp": True,
|
||||
"localProxyArp": False,
|
||||
"gratuitousArp": False,
|
||||
"vrf": "default",
|
||||
"urpf": "disable",
|
||||
"addresslessForwarding": "isInvalid",
|
||||
"directedBroadcastEnabled": False,
|
||||
"maxMssIngress": 0,
|
||||
"maxMssEgress": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"interfaces": {
|
||||
"Ethernet2": {
|
||||
"name": "Ethernet2",
|
||||
"lineProtocolStatus": "up",
|
||||
"interfaceStatus": "connected",
|
||||
"mtu": 1500,
|
||||
"interfaceAddressBrief": {"ipAddr": {"address": "10.1.0.2", "maskLen": 31}},
|
||||
"ipv4Routable240": False,
|
||||
"ipv4Routable0": False,
|
||||
"enabled": True,
|
||||
"description": "P2P_LINK_TO_SW-CORE_Ethernet1",
|
||||
"proxyArp": False,
|
||||
"localProxyArp": False,
|
||||
"gratuitousArp": False,
|
||||
"vrf": "default",
|
||||
"urpf": "disable",
|
||||
"addresslessForwarding": "isInvalid",
|
||||
"directedBroadcastEnabled": False,
|
||||
"maxMssIngress": 0,
|
||||
"maxMssEgress": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": {"interfaces": ["Ethernet1", "Ethernet2"]},
|
||||
"expected": {"result": "failure", "messages": ["The following interface(s) have Proxy-ARP disabled: ['Ethernet2']"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Ethernet2 - Proxy-ARP disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -1972,17 +2086,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"primaryIp": {"address": "172.30.11.1", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [{"address": "10.10.10.1", "maskLen": 31}, {"address": "10.10.10.10", "maskLen": 31}],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"interfaces": {
|
||||
},
|
||||
"Ethernet12": {
|
||||
"interfaceAddress": {
|
||||
"primaryIp": {"address": "172.30.11.10", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [{"address": "10.10.10.10", "maskLen": 31}, {"address": "10.10.10.20", "maskLen": 31}],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -2005,17 +2115,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"primaryIp": {"address": "172.30.11.0", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"interfaces": {
|
||||
},
|
||||
"Ethernet12": {
|
||||
"interfaceAddress": {
|
||||
"primaryIp": {"address": "172.30.11.10", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -2028,9 +2134,20 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-l3-interface",
|
||||
"name": "failure-interface-not-found",
|
||||
"test": VerifyInterfaceIPv4,
|
||||
"eos_data": [{"interfaces": {"Ethernet2": {"interfaceAddress": {}}}}, {"interfaces": {"Ethernet12": {"interfaceAddress": {}}}}],
|
||||
"eos_data": [
|
||||
{
|
||||
"interfaces": {
|
||||
"Ethernet10": {
|
||||
"interfaceAddress": {
|
||||
"primaryIp": {"address": "172.30.11.0", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"interfaces": [
|
||||
{"name": "Ethernet2", "primary_ip": "172.30.11.0/31", "secondary_ips": ["10.10.10.0/31", "10.10.10.10/31"]},
|
||||
|
@ -2039,7 +2156,22 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["For interface `Ethernet2`, IP address is not configured.", "For interface `Ethernet12`, IP address is not configured."],
|
||||
"messages": ["Interface: Ethernet2 - Not found", "Interface: Ethernet12 - Not found"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-l3-interface",
|
||||
"test": VerifyInterfaceIPv4,
|
||||
"eos_data": [{"interfaces": {"Ethernet2": {"interfaceAddress": {}}, "Ethernet12": {"interfaceAddress": {}}}}],
|
||||
"inputs": {
|
||||
"interfaces": [
|
||||
{"name": "Ethernet2", "primary_ip": "172.30.11.0/31", "secondary_ips": ["10.10.10.0/31", "10.10.10.10/31"]},
|
||||
{"name": "Ethernet12", "primary_ip": "172.30.11.20/31", "secondary_ips": ["10.10.11.0/31", "10.10.11.10/31"]},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Interface: Ethernet2 - IP address is not configured", "Interface: Ethernet12 - IP address is not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -2053,17 +2185,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"primaryIp": {"address": "0.0.0.0", "maskLen": 0},
|
||||
"secondaryIpsOrderedList": [],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"interfaces": {
|
||||
},
|
||||
"Ethernet12": {
|
||||
"interfaceAddress": {
|
||||
"primaryIp": {"address": "0.0.0.0", "maskLen": 0},
|
||||
"secondaryIpsOrderedList": [],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -2076,10 +2204,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"For interface `Ethernet2`, The expected primary IP address is `172.30.11.0/31`, but the actual primary IP address is `0.0.0.0/0`. "
|
||||
"The expected secondary IP addresses are `['10.10.10.0/31', '10.10.10.10/31']`, but the actual secondary IP address is not configured.",
|
||||
"For interface `Ethernet12`, The expected primary IP address is `172.30.11.10/31`, but the actual primary IP address is `0.0.0.0/0`. "
|
||||
"The expected secondary IP addresses are `['10.10.11.0/31', '10.10.11.10/31']`, but the actual secondary IP address is not configured.",
|
||||
"Interface: Ethernet2 - IP address mismatch - Expected: 172.30.11.0/31 Actual: 0.0.0.0/0",
|
||||
"Interface: Ethernet2 - Secondary IP address is not configured",
|
||||
"Interface: Ethernet12 - IP address mismatch - Expected: 172.30.11.10/31 Actual: 0.0.0.0/0",
|
||||
"Interface: Ethernet12 - Secondary IP address is not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -2094,17 +2222,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"primaryIp": {"address": "172.30.11.0", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [{"address": "10.10.10.0", "maskLen": 31}, {"address": "10.10.10.10", "maskLen": 31}],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"interfaces": {
|
||||
},
|
||||
"Ethernet3": {
|
||||
"interfaceAddress": {
|
||||
"primaryIp": {"address": "172.30.10.10", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [{"address": "10.10.11.0", "maskLen": 31}, {"address": "10.11.11.10", "maskLen": 31}],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -2117,12 +2241,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"For interface `Ethernet2`, The expected primary IP address is `172.30.11.2/31`, but the actual primary IP address is `172.30.11.0/31`. "
|
||||
"The expected secondary IP addresses are `['10.10.10.20/31', '10.10.10.30/31']`, but the actual secondary IP addresses are "
|
||||
"`['10.10.10.0/31', '10.10.10.10/31']`.",
|
||||
"For interface `Ethernet3`, The expected primary IP address is `172.30.10.2/31`, but the actual primary IP address is `172.30.10.10/31`. "
|
||||
"The expected secondary IP addresses are `['10.10.11.0/31', '10.10.11.10/31']`, but the actual secondary IP addresses are "
|
||||
"`['10.10.11.0/31', '10.11.11.10/31']`.",
|
||||
"Interface: Ethernet2 - IP address mismatch - Expected: 172.30.11.2/31 Actual: 172.30.11.0/31",
|
||||
"Interface: Ethernet2 - Secondary IP address mismatch - Expected: 10.10.10.20/31, 10.10.10.30/31 Actual: 10.10.10.0/31, 10.10.10.10/31",
|
||||
"Interface: Ethernet3 - IP address mismatch - Expected: 172.30.10.2/31 Actual: 172.30.10.10/31",
|
||||
"Interface: Ethernet3 - Secondary IP address mismatch - Expected: 10.10.11.0/31, 10.10.11.10/31 Actual: 10.10.11.0/31, 10.11.11.10/31",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -2137,17 +2259,13 @@ DATA: list[dict[str, Any]] = [
|
|||
"primaryIp": {"address": "172.30.11.0", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"interfaces": {
|
||||
},
|
||||
"Ethernet3": {
|
||||
"interfaceAddress": {
|
||||
"primaryIp": {"address": "172.30.10.10", "maskLen": 31},
|
||||
"secondaryIpsOrderedList": [{"address": "10.10.11.0", "maskLen": 31}, {"address": "10.11.11.10", "maskLen": 31}],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -2160,11 +2278,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"For interface `Ethernet2`, The expected primary IP address is `172.30.11.2/31`, but the actual primary IP address is `172.30.11.0/31`. "
|
||||
"The expected secondary IP addresses are `['10.10.10.20/31', '10.10.10.30/31']`, but the actual secondary IP address is not configured.",
|
||||
"For interface `Ethernet3`, The expected primary IP address is `172.30.10.2/31`, but the actual primary IP address is `172.30.10.10/31`. "
|
||||
"The expected secondary IP addresses are `['10.10.11.0/31', '10.10.11.10/31']`, but the actual secondary IP addresses are "
|
||||
"`['10.10.11.0/31', '10.11.11.10/31']`.",
|
||||
"Interface: Ethernet2 - IP address mismatch - Expected: 172.30.11.2/31 Actual: 172.30.11.0/31",
|
||||
"Interface: Ethernet2 - Secondary IP address is not configured",
|
||||
"Interface: Ethernet3 - IP address mismatch - Expected: 172.30.10.2/31 Actual: 172.30.10.10/31",
|
||||
"Interface: Ethernet3 - Secondary IP address mismatch - Expected: 10.10.11.0/31, 10.10.11.10/31 Actual: 10.10.11.0/31, 10.11.11.10/31",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -2196,7 +2313,7 @@ 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."]},
|
||||
"expected": {"result": "failure", "messages": ["IP virtual router MAC address: 00:1c:73:00:dc:01 - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -2288,10 +2405,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"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.",
|
||||
"Interface: Ethernet1 - Bandwidth mismatch - Expected: 1.0Gbps Actual: 100Gbps",
|
||||
"Interface: Ethernet1/1/1 - Bandwidth mismatch - Expected: 1.0Gbps Actual: 100Gbps",
|
||||
"Interface: Ethernet3 - Bandwidth mismatch - Expected: 100.0Gbps Actual: 10Gbps",
|
||||
"Interface: Ethernet4 - Bandwidth mismatch - Expected: 2.5Gbps Actual: 25Gbps",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -2340,11 +2457,11 @@ DATA: list[dict[str, Any]] = [
|
|||
"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.",
|
||||
"Interface: Ethernet1 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
"Interface: Ethernet1/2/2 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
"Interface: Ethernet3 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
"Interface: Ethernet3 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
"Interface: Ethernet4 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -2398,10 +2515,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"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.",
|
||||
"Interface: Ethernet1 - Data lanes count mismatch - Expected: 2 Actual: 4",
|
||||
"Interface: Ethernet3 - Data lanes count mismatch - Expected: 8 Actual: 4",
|
||||
"Interface: Ethernet4 - Data lanes count mismatch - Expected: 4 Actual: 6",
|
||||
"Interface: Ethernet4/1/1 - Data lanes count mismatch - Expected: 4 Actual: 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -2440,36 +2557,26 @@ DATA: list[dict[str, Any]] = [
|
|||
],
|
||||
"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.",
|
||||
"Interface: Ethernet1 - Bandwidth mismatch - Expected: 1.0Gbps Actual: 10Gbps",
|
||||
"Interface: Ethernet1 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
"Interface: Ethernet1 - Data lanes count mismatch - Expected: 2 Actual: 4",
|
||||
"Interface: Ethernet2/1/2 - Bandwidth mismatch - Expected: 10.0Gbps Actual: 1Gbps",
|
||||
"Interface: Ethernet2/1/2 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
"Interface: Ethernet3 - Bandwidth mismatch - Expected: 100.0Gbps Actual: 10Gbps",
|
||||
"Interface: Ethernet3 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
"Interface: Ethernet3 - Auto-negotiation mismatch - Expected: success Actual: unknown",
|
||||
"Interface: Ethernet3 - Data lanes count mismatch - Expected: 8 Actual: 6",
|
||||
"Interface: Ethernet4 - Bandwidth mismatch - Expected: 2.5Gbps Actual: 25Gbps",
|
||||
"Interface: Ethernet4 - Duplex mode mismatch - Expected: duplexFull Actual: duplexHalf",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Data for testing anta.tests.lanz."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Data for testing anta.tests.logging."""
|
||||
|
@ -9,6 +9,7 @@ from typing import Any
|
|||
|
||||
from anta.tests.logging import (
|
||||
VerifyLoggingAccounting,
|
||||
VerifyLoggingEntries,
|
||||
VerifyLoggingErrors,
|
||||
VerifyLoggingHostname,
|
||||
VerifyLoggingHosts,
|
||||
|
@ -16,6 +17,7 @@ from anta.tests.logging import (
|
|||
VerifyLoggingPersistent,
|
||||
VerifyLoggingSourceIntf,
|
||||
VerifyLoggingTimestamp,
|
||||
VerifySyslogLogging,
|
||||
)
|
||||
from tests.units.anta_tests import test
|
||||
|
||||
|
@ -96,7 +98,7 @@ DATA: list[dict[str, Any]] = [
|
|||
""",
|
||||
],
|
||||
"inputs": {"interface": "Management0", "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Source-interface 'Management0' is not configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["Source-interface: Management0 VRF: MGMT - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-vrf",
|
||||
|
@ -111,7 +113,7 @@ DATA: list[dict[str, Any]] = [
|
|||
""",
|
||||
],
|
||||
"inputs": {"interface": "Management0", "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Source-interface 'Management0' is not configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["Source-interface: Management0 VRF: MGMT - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -141,7 +143,7 @@ DATA: list[dict[str, Any]] = [
|
|||
""",
|
||||
],
|
||||
"inputs": {"hosts": ["10.22.10.92", "10.22.10.93", "10.22.10.94"], "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Syslog servers ['10.22.10.93', '10.22.10.94'] are not configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["Syslog servers 10.22.10.93, 10.22.10.94 are not configured in VRF MGMT"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-vrf",
|
||||
|
@ -156,7 +158,7 @@ DATA: list[dict[str, Any]] = [
|
|||
""",
|
||||
],
|
||||
"inputs": {"hosts": ["10.22.10.92", "10.22.10.93", "10.22.10.94"], "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Syslog servers ['10.22.10.93', '10.22.10.94'] are not configured in VRF MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["Syslog servers 10.22.10.93, 10.22.10.94 are not configured in VRF MGMT"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -166,14 +168,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"2023-05-10T13:54:21.463497-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
|
||||
"Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingLogsGeneration validation\n",
|
||||
],
|
||||
"inputs": None,
|
||||
"inputs": {"severity_level": "informational"},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure",
|
||||
"test": VerifyLoggingLogsGeneration,
|
||||
"eos_data": ["", "Log Buffer:\n"],
|
||||
"inputs": None,
|
||||
"inputs": {"severity_level": "notifications"},
|
||||
"expected": {"result": "failure", "messages": ["Logs are not generated"]},
|
||||
},
|
||||
{
|
||||
|
@ -185,7 +187,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"2023-05-10T15:41:44.701810-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
|
||||
"Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingHostname validation\n",
|
||||
],
|
||||
"inputs": None,
|
||||
"inputs": {"severity_level": "informational"},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
|
@ -194,10 +196,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"eos_data": [
|
||||
{"hostname": "NW-CORE", "fqdn": "NW-CORE.example.org"},
|
||||
"",
|
||||
"2023-05-10T13:54:21.463497-05:00 NW-CORE ConfigAgent: %SYS-6-LOGMSG_INFO: "
|
||||
"2023-05-10T13:54:21.463497-05:00 NW-CORE ConfigAgent: %SYS-6-LOGMSG_NOTICE: "
|
||||
"Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingLogsHostname validation\n",
|
||||
],
|
||||
"inputs": None,
|
||||
"inputs": {"severity_level": "notifications"},
|
||||
"expected": {"result": "failure", "messages": ["Logs are not generated with the device FQDN"]},
|
||||
},
|
||||
{
|
||||
|
@ -210,7 +212,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"2023-05-10T15:42:44.680813-05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
|
||||
"Other log\n",
|
||||
],
|
||||
"inputs": None,
|
||||
"inputs": {"severity_level": "informational"},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
|
@ -223,7 +225,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"2023-05-10T15:42:44.680813+05:00 NW-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
|
||||
"Other log\n",
|
||||
],
|
||||
"inputs": None,
|
||||
"inputs": {"severity_level": "informational"},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
|
@ -231,10 +233,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyLoggingTimestamp,
|
||||
"eos_data": [
|
||||
"",
|
||||
"May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_INFO: "
|
||||
"May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_ALERT: "
|
||||
"Message from arista on command-api (10.22.1.107): ANTA VerifyLoggingTimestamp validation\n",
|
||||
],
|
||||
"inputs": None,
|
||||
"inputs": {"severity_level": "alerts"},
|
||||
"expected": {"result": "failure", "messages": ["Logs are not generated with the appropriate timestamp format"]},
|
||||
},
|
||||
{
|
||||
|
@ -242,9 +244,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"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",
|
||||
"May 10 13:54:22 NE-CORE.example.org ConfigAgent: %SYS-6-LOGMSG_NOTICE: Message from arista on command-api (10.22.1.107): BLAH\n",
|
||||
],
|
||||
"inputs": None,
|
||||
"inputs": {"severity_level": "notifications"},
|
||||
"expected": {"result": "failure", "messages": ["Logs are not generated with the appropriate timestamp format"]},
|
||||
},
|
||||
{
|
||||
|
@ -277,4 +279,85 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Device has reported syslog messages with a severity of ERRORS or higher"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifySyslogLogging,
|
||||
"eos_data": [
|
||||
"""Syslog logging: enabled
|
||||
Buffer logging: level debugging
|
||||
|
||||
External configuration:
|
||||
active:
|
||||
inactive:
|
||||
|
||||
Facility Severity Effective Severity
|
||||
-------------------- ------------- ------------------
|
||||
aaa debugging debugging
|
||||
accounting debugging debugging""",
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure",
|
||||
"test": VerifySyslogLogging,
|
||||
"eos_data": [
|
||||
"""Syslog logging: disabled
|
||||
Buffer logging: level debugging
|
||||
Console logging: level errors
|
||||
Persistent logging: disabled
|
||||
Monitor logging: level errors
|
||||
|
||||
External configuration:
|
||||
active:
|
||||
inactive:
|
||||
|
||||
Facility Severity Effective Severity
|
||||
-------------------- ------------- ------------------
|
||||
aaa debugging debugging
|
||||
accounting debugging debugging""",
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Syslog logging is disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifyLoggingEntries,
|
||||
"eos_data": [
|
||||
"""Mar 13 04:10:45 s1-leaf1 ProcMgr: %PROCMGR-6-TERMINATE_RUNNING_PROCESS: Terminating deconfigured/reconfigured process 'SystemInitMonitor' (PID=859)
|
||||
Mar 13 04:10:45 s1-leaf1 ProcMgr: %PROCMGR-6-PROCESS_TERMINATED: 'SystemInitMonitor' (PID=859, status=9) has terminated.""",
|
||||
"""Mar 13 04:10:45 s1-leaf1 ProcMgr: %PROCMGR-7-WORKER_WARMSTART_DONE: ProcMgr worker warm start done. (PID=547)""",
|
||||
],
|
||||
"inputs": {
|
||||
"logging_entries": [
|
||||
{"regex_match": ".*PROCMGR-6-PROCESS_TERMINATED:.*", "last_number_messages": 3},
|
||||
{"regex_match": ".*ProcMgr worker warm start.*", "last_number_messages": 2, "severity_level": "debugging"},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-log-str-not-found",
|
||||
"test": VerifyLoggingEntries,
|
||||
"eos_data": [
|
||||
"""Mar 12 04:34:01 s1-leaf1 ProcMgr: %PROCMGR-7-WORKER_WARMSTART_DONE: ProcMgr worker warm start done. (PID=559)
|
||||
Mar 12 04:34:01 s1-leaf1 ProcMgr: %PROCMGR-6-PROCESS_TERMINATED: 'SystemInitMonitor' (PID=867, status=9) has terminated.""",
|
||||
"""Mar 13 03:58:12 s1-leaf1 ConfigAgent: %SYS-5-CONFIG_SESSION_ABORTED: User cvpsystem aborted
|
||||
configuration session capiVerify-612-612b34a2ffbf11ef96ba3a348d538ba0 on TerminAttr (localhost)
|
||||
Mar 13 04:10:45 s1-leaf1 SystemInitMonitor: %SYS-5-SYSTEM_INITIALIZED: System is initialized""",
|
||||
],
|
||||
"inputs": {
|
||||
"logging_entries": [
|
||||
{"regex_match": ".ACCOUNTING-5-EXEC: cvpadmin ssh.", "last_number_messages": 3},
|
||||
{"regex_match": ".*ProcMgr worker warm start.*", "last_number_messages": 10, "severity_level": "debugging"},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Pattern: .ACCOUNTING-5-EXEC: cvpadmin ssh. - Not found in last 3 informational log entries",
|
||||
"Pattern: .*ProcMgr worker warm start.* - Not found in last 10 debugging log entries",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.mlag.py."""
|
||||
|
@ -30,13 +30,33 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {"result": "skipped", "messages": ["MLAG is disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "failure",
|
||||
"name": "failure-negotiation-status",
|
||||
"test": VerifyMlagStatus,
|
||||
"eos_data": [{"state": "active", "negStatus": "connecting", "peerLinkStatus": "up", "localIntfStatus": "up"}],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MLAG negotiation status mismatch - Expected: connected Actual: connecting"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-local-interface",
|
||||
"test": VerifyMlagStatus,
|
||||
"eos_data": [{"state": "active", "negStatus": "connected", "peerLinkStatus": "up", "localIntfStatus": "down"}],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Operational state of the MLAG local interface is not correct - Expected: up Actual: down"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-peer-link",
|
||||
"test": VerifyMlagStatus,
|
||||
"eos_data": [{"state": "active", "negStatus": "connected", "peerLinkStatus": "down", "localIntfStatus": "up"}],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MLAG status is not OK: {'state': 'active', 'negStatus': 'connected', 'localIntfStatus': 'up', 'peerLinkStatus': 'down'}"],
|
||||
"messages": ["Operational state of the MLAG peer link is not correct - Expected: up Actual: down"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -74,7 +94,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MLAG status is not OK: {'Disabled': 0, 'Configured': 0, 'Inactive': 0, 'Active-partial': 1, 'Active-full': 1}"],
|
||||
"messages": ["MLAG status is not ok - Inactive Ports: 0 Partial Active Ports: 1"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -89,7 +109,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MLAG status is not OK: {'Disabled': 0, 'Configured': 0, 'Inactive': 1, 'Active-partial': 1, 'Active-full': 1}"],
|
||||
"messages": ["MLAG status is not ok - Inactive Ports: 1 Partial Active Ports: 1"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -124,12 +144,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"MLAG config-sanity returned inconsistencies: "
|
||||
"{'globalConfiguration': {'mlag': {'globalParameters': "
|
||||
"{'dual-primary-detection-delay': {'localValue': '0', 'peerValue': '200'}}}}, "
|
||||
"'interfaceConfiguration': {}}",
|
||||
],
|
||||
"messages": ["MLAG config-sanity found in global configuration"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -146,12 +161,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"MLAG config-sanity returned inconsistencies: "
|
||||
"{'globalConfiguration': {}, "
|
||||
"'interfaceConfiguration': {'trunk-native-vlan mlag30': "
|
||||
"{'interface': {'Port-Channel30': {'localValue': '123', 'peerValue': '3700'}}}}}",
|
||||
],
|
||||
"messages": ["MLAG config-sanity found in interface configuration"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -177,7 +187,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyMlagReloadDelay,
|
||||
"eos_data": [{"state": "active", "reloadDelay": 400, "reloadDelayNonMlag": 430}],
|
||||
"inputs": {"reload_delay": 300, "reload_delay_non_mlag": 330},
|
||||
"expected": {"result": "failure", "messages": ["The reload-delay parameters are not configured properly: {'reloadDelay': 400, 'reloadDelayNonMlag': 430}"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MLAG reload-delay mismatch - Expected: 300s Actual: 400s", "Delay for non-MLAG ports mismatch - Expected: 330s Actual: 430s"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -236,13 +249,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
(
|
||||
"The dual-primary parameters are not configured properly: "
|
||||
"{'detail.dualPrimaryDetectionDelay': 300, "
|
||||
"'detail.dualPrimaryAction': 'none', "
|
||||
"'dualPrimaryMlagRecoveryDelay': 160, "
|
||||
"'dualPrimaryNonMlagRecoveryDelay': 0}"
|
||||
),
|
||||
"Dual-primary detection delay mismatch - Expected: 200 Actual: 300",
|
||||
"Dual-primary MLAG recovery delay mismatch - Expected: 60 Actual: 160",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -262,15 +270,26 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"detection_delay": 200, "errdisabled": True, "recovery_delay": 60, "recovery_delay_non_mlag": 0},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
(
|
||||
"The dual-primary parameters are not configured properly: "
|
||||
"{'detail.dualPrimaryDetectionDelay': 200, "
|
||||
"'detail.dualPrimaryAction': 'none', "
|
||||
"'dualPrimaryMlagRecoveryDelay': 60, "
|
||||
"'dualPrimaryNonMlagRecoveryDelay': 0}"
|
||||
),
|
||||
],
|
||||
"messages": ["Dual-primary action mismatch - Expected: errdisableAllInterfaces Actual: none"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-non-mlag-delay",
|
||||
"test": VerifyMlagDualPrimary,
|
||||
"eos_data": [
|
||||
{
|
||||
"state": "active",
|
||||
"dualPrimaryDetectionState": "configured",
|
||||
"dualPrimaryPortsErrdisabled": False,
|
||||
"dualPrimaryMlagRecoveryDelay": 60,
|
||||
"dualPrimaryNonMlagRecoveryDelay": 120,
|
||||
"detail": {"dualPrimaryDetectionDelay": 200, "dualPrimaryAction": "errdisableAllInterfaces"},
|
||||
},
|
||||
],
|
||||
"inputs": {"detection_delay": 200, "errdisabled": True, "recovery_delay": 60, "recovery_delay_non_mlag": 60},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Dual-primary non MLAG recovery delay mismatch - Expected: 60 Actual: 120"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -310,7 +329,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"primary_priority": 32767},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["The device is not set as MLAG primary."],
|
||||
"messages": ["The device is not set as MLAG primary"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -325,7 +344,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"primary_priority": 1},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["The device is not set as MLAG primary.", "The primary priority does not match expected. Expected `1`, but found `32767` instead."],
|
||||
"messages": ["The device is not set as MLAG primary", "MLAG primary priority mismatch - Expected: 1 Actual: 32767"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.multicast."""
|
||||
|
@ -104,7 +104,10 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"vlans": {1: False, 42: False}},
|
||||
"expected": {"result": "failure", "messages": ["IGMP state for vlan 1 is enabled", "Supplied vlan 42 is not present on the device."]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["VLAN1 - Incorrect IGMP state - Expected: disabled Actual: enabled", "Supplied vlan 42 is not present on the device"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-state",
|
||||
|
@ -132,7 +135,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"vlans": {1: True}},
|
||||
"expected": {"result": "failure", "messages": ["IGMP state for vlan 1 is disabled"]},
|
||||
"expected": {"result": "failure", "messages": ["VLAN1 - Incorrect IGMP state - Expected: enabled Actual: disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "success-enabled",
|
||||
|
@ -171,6 +174,6 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"enabled": True},
|
||||
"expected": {"result": "failure", "messages": ["IGMP state is not valid: disabled"]},
|
||||
"expected": {"result": "failure", "messages": ["IGMP state is not valid - Expected: enabled Actual: disabled"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
@ -58,7 +58,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{"dpsPeers": {}},
|
||||
],
|
||||
"inputs": {},
|
||||
"expected": {"result": "failure", "messages": ["No path configured for router path-selection."]},
|
||||
"expected": {"result": "failure", "messages": ["No path configured for router path-selection"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-established",
|
||||
|
@ -101,9 +101,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"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`.",
|
||||
"Peer: 10.255.0.1 Path Group: internet - Invalid path state - Expected: ipsecEstablished, routeResolved Actual: ipsecPending",
|
||||
"Peer: 10.255.0.1 Path Group: mpls - Invalid path state - Expected: ipsecEstablished, routeResolved Actual: ipsecPending",
|
||||
"Peer: 10.255.0.2 Path Group: mpls - Invalid path state - Expected: ipsecEstablished, routeResolved Actual: ipsecPending",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -148,9 +148,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"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`.",
|
||||
"Peer: 10.255.0.1 Path Group internet - Telemetry state inactive",
|
||||
"Peer: 10.255.0.1 Path Group mpls - Telemetry state inactive",
|
||||
"Peer: 10.255.0.2 Path Group mpls - Telemetry state inactive",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -158,107 +158,125 @@ DATA: list[dict[str, Any]] = [
|
|||
"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": {
|
||||
"path7": {},
|
||||
"path8": {
|
||||
"source": "172.18.13.2",
|
||||
"destination": "172.18.15.2",
|
||||
"state": "ipsecEstablished",
|
||||
"dpsSessions": {"0": {"active": True}},
|
||||
},
|
||||
}
|
||||
},
|
||||
"internet": {},
|
||||
}
|
||||
},
|
||||
"10.255.0.1": {
|
||||
"dpsGroups": {
|
||||
"internet": {
|
||||
"dpsPaths": {
|
||||
"path6": {
|
||||
"source": "100.64.3.2",
|
||||
"destination": "100.64.1.2",
|
||||
"state": "ipsecEstablished",
|
||||
"source": "172.18.3.2",
|
||||
"destination": "172.18.5.2",
|
||||
"dpsSessions": {"0": {"active": True}},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mpls": {},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
"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.1", "path_group": "internet", "source_address": "100.64.3.2", "destination_address": "100.64.1.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",
|
||||
"name": "failure-expected-path-group-not-found",
|
||||
"test": VerifySpecificPath,
|
||||
"eos_data": [
|
||||
{"dpsPeers": {}},
|
||||
{"dpsPeers": {}},
|
||||
{
|
||||
"dpsPeers": {
|
||||
"10.255.0.2": {
|
||||
"dpsGroups": {"internet": {}},
|
||||
},
|
||||
"10.255.0.1": {"peerName": "", "dpsGroups": {"mpls": {}}},
|
||||
}
|
||||
}
|
||||
],
|
||||
"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.1", "path_group": "internet", "source_address": "100.64.3.2", "destination_address": "100.64.1.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`.",
|
||||
"Peer: 10.255.0.1 PathGroup: internet Source: 100.64.3.2 Destination: 100.64.1.2 - No DPS path found for this peer and path group",
|
||||
"Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - No DPS path found for this peer and path group",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-router-path-configured",
|
||||
"test": VerifySpecificPath,
|
||||
"eos_data": [{"dpsPeers": {}}],
|
||||
"inputs": {"paths": [{"peer": "10.255.0.1", "path_group": "internet", "source_address": "100.64.3.2", "destination_address": "100.64.1.2"}]},
|
||||
"expected": {"result": "failure", "messages": ["Router path-selection not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-specific-peer-configured",
|
||||
"test": VerifySpecificPath,
|
||||
"eos_data": [{"dpsPeers": {"10.255.0.2": {}}}],
|
||||
"inputs": {"paths": [{"peer": "10.255.0.1", "path_group": "internet", "source_address": "172.18.3.2", "destination_address": "172.18.5.2"}]},
|
||||
"expected": {"result": "failure", "messages": ["Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Peer not found"]},
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"path7": {},
|
||||
"path8": {
|
||||
"source": "172.18.13.2",
|
||||
"destination": "172.18.15.2",
|
||||
"dpsSessions": {"0": {"active": False}},
|
||||
}
|
||||
"state": "ipsecPending",
|
||||
"dpsSessions": {"0": {"active": True}},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"internet": {"dpsPaths": {}},
|
||||
}
|
||||
}
|
||||
},
|
||||
"10.255.0.1": {
|
||||
"dpsGroups": {
|
||||
"internet": {
|
||||
"dpsPaths": {
|
||||
"path6": {"source": "172.18.3.2", "destination": "172.18.5.2", "state": "ipsecPending", "dpsSessions": {"0": {"active": True}}}
|
||||
}
|
||||
},
|
||||
"mpls": {"dpsPaths": {}},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"paths": [
|
||||
|
@ -269,8 +287,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"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`.",
|
||||
"Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Invalid state path - Expected: ipsecEstablished, routeResolved "
|
||||
"Actual: ipsecPending",
|
||||
"Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Invalid state path - Expected: ipsecEstablished, routeResolved "
|
||||
"Actual: ipsecPending",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -278,37 +298,33 @@ DATA: list[dict[str, Any]] = [
|
|||
"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",
|
||||
"path8": {
|
||||
"source": "172.18.13.2",
|
||||
"destination": "172.18.15.2",
|
||||
"state": "routeResolved",
|
||||
"dpsSessions": {"0": {"active": False}},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"10.255.0.1": {
|
||||
"dpsGroups": {
|
||||
"internet": {
|
||||
"dpsPaths": {
|
||||
"path6": {"source": "172.18.3.2", "destination": "172.18.5.2", "state": "routeResolved", "dpsSessions": {"0": {"active": False}}}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"paths": [
|
||||
|
@ -319,8 +335,49 @@ DATA: list[dict[str, Any]] = [
|
|||
"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`.",
|
||||
"Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - Telemetry state inactive for this path",
|
||||
"Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - Telemetry state inactive for this path",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-source-destination-not-configured",
|
||||
"test": VerifySpecificPath,
|
||||
"eos_data": [
|
||||
{
|
||||
"dpsPeers": {
|
||||
"10.255.0.2": {
|
||||
"dpsGroups": {
|
||||
"mpls": {
|
||||
"dpsPaths": {
|
||||
"path8": {"source": "172.18.3.2", "destination": "172.8.15.2", "state": "routeResolved", "dpsSessions": {"0": {"active": False}}}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"10.255.0.1": {
|
||||
"dpsGroups": {
|
||||
"internet": {
|
||||
"dpsPaths": {
|
||||
"path6": {"source": "172.8.3.2", "destination": "172.8.5.2", "state": "routeResolved", "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": [
|
||||
"Peer: 10.255.0.1 PathGroup: internet Source: 172.18.3.2 Destination: 172.18.5.2 - No path matching the source and destination found",
|
||||
"Peer: 10.255.0.2 PathGroup: mpls Source: 172.18.13.2 Destination: 172.18.15.2 - No path matching the source and destination found",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.profiles.py."""
|
||||
|
@ -23,7 +23,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyUnifiedForwardingTableMode,
|
||||
"eos_data": [{"uftMode": "2", "urpfEnabled": False, "chipModel": "bcm56870", "l2TableSize": 163840, "l3TableSize": 147456, "lpmTableSize": 32768}],
|
||||
"inputs": {"mode": 3},
|
||||
"expected": {"result": "failure", "messages": ["Device is not running correct UFT mode (expected: 3 / running: 2)"]},
|
||||
"expected": {"result": "failure", "messages": ["Not running the correct UFT mode - Expected: 3 Actual: 2"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Data for testing anta.tests.ptp."""
|
||||
|
@ -39,7 +39,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyPtpModeStatus,
|
||||
"eos_data": [{"ptpMode": "ptpDisabled", "ptpIntfSummaries": {}}],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The device is not configured as a PTP Boundary Clock: 'ptpDisabled'"]},
|
||||
"expected": {"result": "failure", "messages": ["Not configured as a PTP Boundary Clock - Actual: ptpDisabled"]},
|
||||
},
|
||||
{
|
||||
"name": "skipped",
|
||||
|
@ -99,7 +99,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The device is locked to the following Grandmaster: '0x00:1c:73:ff:ff:0a:00:01', which differ from the expected one.",
|
||||
"The device is locked to the incorrect Grandmaster - Expected: 0xec:46:70:ff:fe:00:ff:a8 Actual: 0x00:1c:73:ff:ff:0a:00:01",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -158,7 +158,7 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The device lock is more than 60s old: 157s"]},
|
||||
"expected": {"result": "failure", "messages": ["Lock is more than 60s old - Actual: 157s"]},
|
||||
},
|
||||
{
|
||||
"name": "skipped",
|
||||
|
@ -236,7 +236,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [("The device timing offset from master is greater than +/- 1000ns: {'Ethernet27/1': [1200, -1300]}")],
|
||||
"messages": [
|
||||
"Interface: Ethernet27/1 - Timing offset from master is greater than +/- 1000ns: Actual: 1200, -1300",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -335,6 +337,6 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The following interface(s) are not in a valid PTP state: '['Ethernet53', 'Ethernet1']'"]},
|
||||
"expected": {"result": "failure", "messages": ["The following interface(s) are not in a valid PTP state: Ethernet53, Ethernet1"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.security.py."""
|
||||
|
@ -42,7 +42,7 @@ 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": "failure", "messages": ["Could not find SSH status in returned output."]},
|
||||
"expected": {"result": "failure", "messages": ["Could not find SSH status in returned output"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-ssh-enabled",
|
||||
|
@ -83,14 +83,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifySSHIPv4Acl,
|
||||
"eos_data": [{"ipAclList": {"aclList": []}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Expected 1 SSH IPv4 ACL(s) in vrf MGMT but got 0"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - SSH IPv4 ACL(s) count mismatch - Expected: 1 Actual: 0"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-vrf",
|
||||
"test": VerifySSHIPv4Acl,
|
||||
"eos_data": [{"ipAclList": {"aclList": [{"type": "Ip4Acl", "name": "ACL_IPV4_SSH", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["SSH IPv4 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV4_SSH']"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Following SSH IPv4 ACL(s) not configured or active: ACL_IPV4_SSH"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -104,14 +104,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifySSHIPv6Acl,
|
||||
"eos_data": [{"ipv6AclList": {"aclList": []}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Expected 1 SSH IPv6 ACL(s) in vrf MGMT but got 0"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - SSH IPv6 ACL(s) count mismatch - Expected: 1 Actual: 0"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-vrf",
|
||||
"test": VerifySSHIPv6Acl,
|
||||
"eos_data": [{"ipv6AclList": {"aclList": [{"type": "Ip6Acl", "name": "ACL_IPV6_SSH", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["SSH IPv6 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV6_SSH']"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Following SSH IPv6 ACL(s) not configured or active: ACL_IPV6_SSH"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -192,7 +192,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"profile": "API_SSL_Profile"},
|
||||
"expected": {"result": "failure", "messages": ["eAPI HTTPS server SSL profile (API_SSL_Profile) is not configured"]},
|
||||
"expected": {"result": "failure", "messages": ["eAPI HTTPS server SSL profile API_SSL_Profile is not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-misconfigured-invalid",
|
||||
|
@ -209,7 +209,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"profile": "API_SSL_Profile"},
|
||||
"expected": {"result": "failure", "messages": ["eAPI HTTPS server SSL profile (API_SSL_Profile) is misconfigured or invalid"]},
|
||||
"expected": {"result": "failure", "messages": ["eAPI HTTPS server SSL profile API_SSL_Profile is misconfigured or invalid"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -223,14 +223,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyAPIIPv4Acl,
|
||||
"eos_data": [{"ipAclList": {"aclList": []}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Expected 1 eAPI IPv4 ACL(s) in vrf MGMT but got 0"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - eAPI IPv4 ACL(s) count mismatch - Expected: 1 Actual: 0"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-vrf",
|
||||
"test": VerifyAPIIPv4Acl,
|
||||
"eos_data": [{"ipAclList": {"aclList": [{"type": "Ip4Acl", "name": "ACL_IPV4_API", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["eAPI IPv4 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV4_API']"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Following eAPI IPv4 ACL(s) not configured or active: ACL_IPV4_API"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -244,14 +244,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyAPIIPv6Acl,
|
||||
"eos_data": [{"ipv6AclList": {"aclList": []}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Expected 1 eAPI IPv6 ACL(s) in vrf MGMT but got 0"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - eAPI IPv6 ACL(s) count mismatch - Expected: 1 Actual: 0"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-vrf",
|
||||
"test": VerifyAPIIPv6Acl,
|
||||
"eos_data": [{"ipv6AclList": {"aclList": [{"type": "Ip6Acl", "name": "ACL_IPV6_API", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["eAPI IPv6 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV6_API']"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Following eAPI IPv6 ACL(s) not configured or active: ACL_IPV6_API"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -341,7 +341,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["SSL certificate 'ARISTA_ROOT_CA.crt', is not configured.\n"],
|
||||
"messages": ["Certificate: ARISTA_ROOT_CA.crt - Not found"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -366,13 +366,6 @@ DATA: list[dict[str, Any]] = [
|
|||
],
|
||||
"inputs": {
|
||||
"certificates": [
|
||||
{
|
||||
"certificate_name": "ARISTA_SIGNING_CA.crt",
|
||||
"expiry_threshold": 30,
|
||||
"common_name": "AristaIT-ICA ECDSA Issuing Cert Authority",
|
||||
"encryption_algorithm": "ECDSA",
|
||||
"key_size": 256,
|
||||
},
|
||||
{
|
||||
"certificate_name": "ARISTA_ROOT_CA.crt",
|
||||
"expiry_threshold": 30,
|
||||
|
@ -384,7 +377,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["SSL certificate 'ARISTA_SIGNING_CA.crt', is not configured.\n", "SSL certificate `ARISTA_ROOT_CA.crt` is expired.\n"],
|
||||
"messages": ["Certificate: ARISTA_ROOT_CA.crt - certificate expired"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -403,7 +396,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"ARISTA_SIGNING_CA.crt": {
|
||||
"subject": {"commonName": "AristaIT-ICA ECDSA Issuing Cert Authority"},
|
||||
"notAfter": 1702533518,
|
||||
"notAfter": 1705992709,
|
||||
"publicKey": {
|
||||
"encryptionAlgorithm": "ECDSA",
|
||||
"size": 256,
|
||||
|
@ -435,7 +428,9 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["SSL certificate `ARISTA_SIGNING_CA.crt` is expired.\n", "SSL certificate `ARISTA_ROOT_CA.crt` is about to expire in 25 days."],
|
||||
"messages": [
|
||||
"Certificate: ARISTA_ROOT_CA.crt - set to expire within the threshold - Threshold: 30 days Actual: 25 days",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -487,12 +482,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"SSL certificate `ARISTA_SIGNING_CA.crt` is not configured properly:\n"
|
||||
"Expected `AristaIT-ICA ECDSA Issuing Cert Authority` as the subject.commonName, but found "
|
||||
"`Arista ECDSA Issuing Cert Authority` instead.\n",
|
||||
"SSL certificate `ARISTA_ROOT_CA.crt` is not configured properly:\n"
|
||||
"Expected `Arista Networks Internal IT Root Cert Authority` as the subject.commonName, "
|
||||
"but found `AristaIT-ICA Networks Internal IT Root Cert Authority` instead.\n",
|
||||
"Certificate: ARISTA_SIGNING_CA.crt - incorrect common name - Expected: AristaIT-ICA ECDSA Issuing Cert Authority "
|
||||
"Actual: Arista ECDSA Issuing Cert Authority",
|
||||
"Certificate: ARISTA_ROOT_CA.crt - incorrect common name - Expected: Arista Networks Internal IT Root Cert Authority "
|
||||
"Actual: AristaIT-ICA Networks Internal IT Root Cert Authority",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -545,17 +538,15 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"SSL certificate `ARISTA_SIGNING_CA.crt` is not configured properly:\n"
|
||||
"Expected `ECDSA` as the publicKey.encryptionAlgorithm, but found `RSA` instead.\n"
|
||||
"Expected `256` as the publicKey.size, but found `4096` instead.\n",
|
||||
"SSL certificate `ARISTA_ROOT_CA.crt` is not configured properly:\n"
|
||||
"Expected `RSA` as the publicKey.encryptionAlgorithm, but found `ECDSA` instead.\n"
|
||||
"Expected `4096` as the publicKey.size, but found `256` instead.\n",
|
||||
"Certificate: ARISTA_SIGNING_CA.crt - incorrect encryption algorithm - Expected: ECDSA Actual: RSA",
|
||||
"Certificate: ARISTA_SIGNING_CA.crt - incorrect public key - Expected: 256 Actual: 4096",
|
||||
"Certificate: ARISTA_ROOT_CA.crt - incorrect encryption algorithm - Expected: RSA Actual: ECDSA",
|
||||
"Certificate: ARISTA_ROOT_CA.crt - incorrect public key - Expected: 4096 Actual: 256",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-missing-actual-output",
|
||||
"name": "failure-missing-algorithm-details",
|
||||
"test": VerifyAPISSLCertificate,
|
||||
"eos_data": [
|
||||
{
|
||||
|
@ -595,12 +586,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"SSL certificate `ARISTA_SIGNING_CA.crt` is not configured properly:\n"
|
||||
"Expected `ECDSA` as the publicKey.encryptionAlgorithm, but it was not found in the actual output.\n"
|
||||
"Expected `256` as the publicKey.size, but it was not found in the actual output.\n",
|
||||
"SSL certificate `ARISTA_ROOT_CA.crt` is not configured properly:\n"
|
||||
"Expected `RSA` as the publicKey.encryptionAlgorithm, but it was not found in the actual output.\n"
|
||||
"Expected `4096` as the publicKey.size, but it was not found in the actual output.\n",
|
||||
"Certificate: ARISTA_SIGNING_CA.crt - incorrect encryption algorithm - Expected: ECDSA Actual: Not found",
|
||||
"Certificate: ARISTA_SIGNING_CA.crt - incorrect public key - Expected: 256 Actual: Not found",
|
||||
"Certificate: ARISTA_ROOT_CA.crt - incorrect encryption algorithm - Expected: RSA Actual: Not found",
|
||||
"Certificate: ARISTA_ROOT_CA.crt - incorrect public key - Expected: 4096 Actual: Not found",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -651,12 +640,26 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Expected `Copyright (c) 2023-2024 Arista Networks, Inc.\nUse of this source code is governed by the Apache License 2.0\n"
|
||||
"that can be found in the LICENSE file.` as the login banner, but found `Copyright (c) 2023 Arista Networks, Inc.\nUse of this source code is "
|
||||
"governed by the Apache License 2.0\nthat can be found in the LICENSE file.` instead."
|
||||
"Incorrect login banner configured - Expected: Copyright (c) 2023-2024 Arista Networks, Inc.\n"
|
||||
"Use of this source code is governed by the Apache License 2.0\nthat can be found in the LICENSE file. "
|
||||
"Actual: Copyright (c) 2023 Arista Networks, Inc.\n"
|
||||
"Use of this source code is governed by the Apache License 2.0\nthat can be found in the LICENSE file."
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-login-banner-not-configured",
|
||||
"test": VerifyBannerLogin,
|
||||
"eos_data": [{"loginBanner": ""}],
|
||||
"inputs": {
|
||||
"login_banner": "Copyright (c) 2023-2024 Arista Networks, Inc.\nUse of this source code is governed by the Apache License 2.0\n"
|
||||
"that can be found in the LICENSE file."
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Login banner is not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifyBannerMotd,
|
||||
|
@ -704,12 +707,26 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Expected `Copyright (c) 2023-2024 Arista Networks, Inc.\nUse of this source code is governed by the Apache License 2.0\n"
|
||||
"that can be found in the LICENSE file.` as the motd banner, but found `Copyright (c) 2023 Arista Networks, Inc.\nUse of this source code is "
|
||||
"governed by the Apache License 2.0\nthat can be found in the LICENSE file.` instead."
|
||||
"Incorrect MOTD banner configured - Expected: Copyright (c) 2023-2024 Arista Networks, Inc.\n"
|
||||
"Use of this source code is governed by the Apache License 2.0\nthat can be found in the LICENSE file. "
|
||||
"Actual: Copyright (c) 2023 Arista Networks, Inc.\nUse of this source code is governed by the Apache License 2.0\n"
|
||||
"that can be found in the LICENSE file."
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-login-banner-not-configured",
|
||||
"test": VerifyBannerMotd,
|
||||
"eos_data": [{"motd": ""}],
|
||||
"inputs": {
|
||||
"motd_banner": "Copyright (c) 2023-2024 Arista Networks, Inc.\nUse of this source code is governed by the Apache License 2.0\n"
|
||||
"that can be found in the LICENSE file."
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["MOTD banner is not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifyIPv4ACL,
|
||||
|
@ -717,22 +734,20 @@ DATA: list[dict[str, Any]] = [
|
|||
{
|
||||
"aclList": [
|
||||
{
|
||||
"name": "default-control-plane-acl",
|
||||
"sequence": [
|
||||
{"text": "permit icmp any any", "sequenceNumber": 10},
|
||||
{"text": "permit ip any any tracked", "sequenceNumber": 20},
|
||||
{"text": "permit udp any any eq bfd ttl eq 255", "sequenceNumber": 30},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aclList": [
|
||||
},
|
||||
{
|
||||
"name": "LabTest",
|
||||
"sequence": [
|
||||
{"text": "permit icmp any any", "sequenceNumber": 10},
|
||||
{"text": "permit tcp any any range 5900 5910", "sequenceNumber": 20},
|
||||
],
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
|
@ -754,6 +769,24 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-acl-list",
|
||||
"test": VerifyIPv4ACL,
|
||||
"eos_data": [
|
||||
{"aclList": []},
|
||||
],
|
||||
"inputs": {
|
||||
"ipv4_access_lists": [
|
||||
{
|
||||
"name": "default-control-plane-acl",
|
||||
"entries": [
|
||||
{"sequence": 10, "action": "permit icmp any any"},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["No Access Control List (ACL) configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-acl-not-found",
|
||||
"test": VerifyIPv4ACL,
|
||||
|
@ -761,6 +794,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{
|
||||
"aclList": [
|
||||
{
|
||||
"name": "default-control-plane-acl",
|
||||
"sequence": [
|
||||
{"text": "permit icmp any any", "sequenceNumber": 10},
|
||||
{"text": "permit ip any any tracked", "sequenceNumber": 20},
|
||||
|
@ -769,7 +803,6 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
]
|
||||
},
|
||||
{"aclList": []},
|
||||
],
|
||||
"inputs": {
|
||||
"ipv4_access_lists": [
|
||||
|
@ -787,7 +820,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["LabTest: Not found"]},
|
||||
"expected": {"result": "failure", "messages": ["ACL name: LabTest - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-sequence-not-found",
|
||||
|
@ -796,22 +829,20 @@ DATA: list[dict[str, Any]] = [
|
|||
{
|
||||
"aclList": [
|
||||
{
|
||||
"name": "default-control-plane-acl",
|
||||
"sequence": [
|
||||
{"text": "permit icmp any any", "sequenceNumber": 10},
|
||||
{"text": "permit ip any any tracked", "sequenceNumber": 20},
|
||||
{"text": "permit udp any any eq bfd ttl eq 255", "sequenceNumber": 40},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aclList": [
|
||||
},
|
||||
{
|
||||
"name": "LabTest",
|
||||
"sequence": [
|
||||
{"text": "permit icmp any any", "sequenceNumber": 10},
|
||||
{"text": "permit tcp any any range 5900 5910", "sequenceNumber": 30},
|
||||
],
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
|
@ -833,7 +864,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["default-control-plane-acl:\nSequence number `30` is not found.\n", "LabTest:\nSequence number `20` is not found.\n"],
|
||||
"messages": ["ACL name: default-control-plane-acl Sequence: 30 - Not configured", "ACL name: LabTest Sequence: 20 - Not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -843,22 +874,20 @@ DATA: list[dict[str, Any]] = [
|
|||
{
|
||||
"aclList": [
|
||||
{
|
||||
"name": "default-control-plane-acl",
|
||||
"sequence": [
|
||||
{"text": "permit icmp any any", "sequenceNumber": 10},
|
||||
{"text": "permit ip any any tracked", "sequenceNumber": 20},
|
||||
{"text": "permit tcp any any range 5900 5910", "sequenceNumber": 30},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aclList": [
|
||||
},
|
||||
{
|
||||
"name": "LabTest",
|
||||
"sequence": [
|
||||
{"text": "permit icmp any any", "sequenceNumber": 10},
|
||||
{"text": "permit udp any any eq bfd ttl eq 255", "sequenceNumber": 20},
|
||||
],
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
|
@ -881,9 +910,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"default-control-plane-acl:\n"
|
||||
"Expected `permit udp any any eq bfd ttl eq 255` as sequence number 30 action but found `permit tcp any any range 5900 5910` instead.\n",
|
||||
"LabTest:\nExpected `permit tcp any any range 5900 5910` as sequence number 20 action but found `permit udp any any eq bfd ttl eq 255` instead.\n",
|
||||
"ACL name: default-control-plane-acl Sequence: 30 - action mismatch - Expected: permit udp any any eq bfd ttl eq 255 "
|
||||
"Actual: permit tcp any any range 5900 5910",
|
||||
"ACL name: LabTest Sequence: 20 - action mismatch - Expected: permit tcp any any range 5900 5910 Actual: permit udp any any eq bfd ttl eq 255",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -894,6 +923,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{
|
||||
"aclList": [
|
||||
{
|
||||
"name": "default-control-plane-acl",
|
||||
"sequence": [
|
||||
{"text": "permit icmp any any", "sequenceNumber": 10},
|
||||
{"text": "permit ip any any tracked", "sequenceNumber": 40},
|
||||
|
@ -902,7 +932,6 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
]
|
||||
},
|
||||
{"aclList": []},
|
||||
],
|
||||
"inputs": {
|
||||
"ipv4_access_lists": [
|
||||
|
@ -923,9 +952,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"default-control-plane-acl:\nSequence number `20` is not found.\n"
|
||||
"Expected `permit udp any any eq bfd ttl eq 255` as sequence number 30 action but found `permit tcp any any range 5900 5910` instead.\n",
|
||||
"LabTest: Not found",
|
||||
"ACL name: default-control-plane-acl Sequence: 20 - Not configured",
|
||||
"ACL name: default-control-plane-acl Sequence: 30 - action mismatch - Expected: permit udp any any eq bfd ttl eq 255 "
|
||||
"Actual: permit tcp any any range 5900 5910",
|
||||
"ACL name: LabTest - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -952,7 +982,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyIPSecConnHealth,
|
||||
"eos_data": [{"connections": {}}],
|
||||
"inputs": {},
|
||||
"expected": {"result": "failure", "messages": ["No IPv4 security connection configured."]},
|
||||
"expected": {"result": "failure", "messages": ["No IPv4 security connection configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-established",
|
||||
|
@ -974,9 +1004,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following IPv4 security connections are not established:\n"
|
||||
"source:172.18.3.2 destination:172.18.2.2 vrf:default\n"
|
||||
"source:100.64.3.2 destination:100.64.5.2 vrf:Guest."
|
||||
"Source: 172.18.3.2 Destination: 172.18.2.2 VRF: default - IPv4 security connection not established",
|
||||
"Source: 100.64.3.2 Destination: 100.64.5.2 VRF: Guest - IPv4 security connection not established",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -1127,10 +1156,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established, Actual: Idle",
|
||||
"Peer: 10.255.0.1 VRF: default Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established, Actual: Idle",
|
||||
"Peer: 10.255.0.2 VRF: MGMT Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established, Actual: Idle",
|
||||
"Peer: 10.255.0.2 VRF: MGMT Source: 172.18.2.2 Destination: 172.18.1.2 - Connection down - Expected: Established, Actual: Idle",
|
||||
"Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established Actual: Idle",
|
||||
"Peer: 10.255.0.1 VRF: default Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established Actual: Idle",
|
||||
"Peer: 10.255.0.2 VRF: MGMT Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established Actual: Idle",
|
||||
"Peer: 10.255.0.2 VRF: MGMT Source: 172.18.2.2 Destination: 172.18.1.2 - Connection down - Expected: Established Actual: Idle",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -1190,8 +1219,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established, Actual: Idle",
|
||||
"Peer: 10.255.0.1 VRF: default Source: 100.64.3.2 Destination: 100.64.2.2 - Connection down - Expected: Established, Actual: Idle",
|
||||
"Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established Actual: Idle",
|
||||
"Peer: 10.255.0.1 VRF: default Source: 100.64.3.2 Destination: 100.64.2.2 - Connection down - Expected: Established Actual: Idle",
|
||||
"Peer: 10.255.0.2 VRF: default Source: 100.64.4.2 Destination: 100.64.1.2 - Connection not found.",
|
||||
"Peer: 10.255.0.2 VRF: default Source: 172.18.4.2 Destination: 172.18.1.2 - Connection not found.",
|
||||
],
|
||||
|
@ -1209,7 +1238,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"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."]},
|
||||
"expected": {"result": "failure", "messages": ["Hardware entropy generation is disabled"]},
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.services.py."""
|
||||
|
@ -25,7 +25,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"hostname": "s1-spine1"},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Expected `s1-spine1` as the hostname, but found `s1-spine2` instead."],
|
||||
"messages": ["Incorrect Hostname - Expected: s1-spine1 Actual: s1-spine2"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -88,7 +88,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Server 10.14.0.10 (VRF: default, Priority: 0) - Not configured", "Server 10.14.0.21 (VRF: MGMT, Priority: 1) - Not configured"],
|
||||
"messages": ["Server 10.14.0.10 VRF: default Priority: 0 - Not configured", "Server 10.14.0.21 VRF: MGMT Priority: 1 - Not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -109,9 +109,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Server 10.14.0.1 (VRF: CS, Priority: 0) - Incorrect priority - Priority: 1",
|
||||
"Server 10.14.0.11 (VRF: default, Priority: 0) - Not configured",
|
||||
"Server 10.14.0.110 (VRF: MGMT, Priority: 0) - Not configured",
|
||||
"Server 10.14.0.1 VRF: CS Priority: 0 - Incorrect priority - Priority: 1",
|
||||
"Server 10.14.0.11 VRF: default Priority: 0 - Not configured",
|
||||
"Server 10.14.0.110 VRF: MGMT Priority: 0 - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -147,7 +147,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"reasons": [{"reason": "acl", "interval": 300}, {"reason": "arp-inspection", "interval": 30}, {"reason": "tapagg", "interval": 30}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["`tapagg`: Not found."],
|
||||
"messages": ["Reason: tapagg Status: Enabled Interval: 30 - Not found"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -165,7 +165,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"reasons": [{"reason": "acl", "interval": 300}, {"reason": "arp-inspection", "interval": 30}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["`acl`:\nExpected `Enabled` as the status, but found `Disabled` instead."],
|
||||
"messages": ["Reason: acl Status: Enabled Interval: 300 - Incorrect configuration - Status: Disabled Interval: 300"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -183,7 +183,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"reasons": [{"reason": "acl", "interval": 30}, {"reason": "arp-inspection", "interval": 30}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["`acl`:\nExpected `30` as the interval, but found `300` instead."],
|
||||
"messages": [
|
||||
"Reason: acl Status: Enabled Interval: 30 - Incorrect configuration - Status: Enabled Interval: 300",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -202,9 +204,9 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"`acl`:\nExpected `30` as the interval, but found `300` instead.\nExpected `Enabled` as the status, but found `Disabled` instead.",
|
||||
"`arp-inspection`:\nExpected `300` as the interval, but found `30` instead.",
|
||||
"`tapagg`: Not found.",
|
||||
"Reason: acl Status: Enabled Interval: 30 - Incorrect configuration - Status: Disabled Interval: 300",
|
||||
"Reason: arp-inspection Status: Enabled Interval: 300 - Incorrect configuration - Status: Enabled Interval: 30",
|
||||
"Reason: tapagg Status: Enabled Interval: 30 - Not found",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.snmp.py."""
|
||||
|
@ -10,11 +10,16 @@ from typing import Any
|
|||
from anta.tests.snmp import (
|
||||
VerifySnmpContact,
|
||||
VerifySnmpErrorCounters,
|
||||
VerifySnmpGroup,
|
||||
VerifySnmpHostLogging,
|
||||
VerifySnmpIPv4Acl,
|
||||
VerifySnmpIPv6Acl,
|
||||
VerifySnmpLocation,
|
||||
VerifySnmpNotificationHost,
|
||||
VerifySnmpPDUCounters,
|
||||
VerifySnmpSourceInterface,
|
||||
VerifySnmpStatus,
|
||||
VerifySnmpUser,
|
||||
)
|
||||
from tests.units.anta_tests import test
|
||||
|
||||
|
@ -31,14 +36,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifySnmpStatus,
|
||||
"eos_data": [{"vrfs": {"snmpVrfs": ["default"]}, "enabled": True}],
|
||||
"inputs": {"vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["SNMP agent disabled in vrf MGMT"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - SNMP agent disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-disabled",
|
||||
"test": VerifySnmpStatus,
|
||||
"eos_data": [{"vrfs": {"snmpVrfs": ["default"]}, "enabled": False}],
|
||||
"inputs": {"vrf": "default"},
|
||||
"expected": {"result": "failure", "messages": ["SNMP agent disabled in vrf default"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: default - SNMP agent disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -52,14 +57,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifySnmpIPv4Acl,
|
||||
"eos_data": [{"ipAclList": {"aclList": []}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Expected 1 SNMP IPv4 ACL(s) in vrf MGMT but got 0"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Incorrect SNMP IPv4 ACL(s) - Expected: 1 Actual: 0"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-vrf",
|
||||
"test": VerifySnmpIPv4Acl,
|
||||
"eos_data": [{"ipAclList": {"aclList": [{"type": "Ip4Acl", "name": "ACL_IPV4_SNMP", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["SNMP IPv4 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV4_SNMP']"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Following SNMP IPv4 ACL(s) not configured or active: ACL_IPV4_SNMP"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -73,14 +78,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifySnmpIPv6Acl,
|
||||
"eos_data": [{"ipv6AclList": {"aclList": []}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["Expected 1 SNMP IPv6 ACL(s) in vrf MGMT but got 0"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Incorrect SNMP IPv6 ACL(s) - Expected: 1 Actual: 0"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-vrf",
|
||||
"test": VerifySnmpIPv6Acl,
|
||||
"eos_data": [{"ipv6AclList": {"aclList": [{"type": "Ip6Acl", "name": "ACL_IPV6_SNMP", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
|
||||
"inputs": {"number": 1, "vrf": "MGMT"},
|
||||
"expected": {"result": "failure", "messages": ["SNMP IPv6 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV6_SNMP']"]},
|
||||
"expected": {"result": "failure", "messages": ["VRF: MGMT - Following SNMP IPv6 ACL(s) not configured or active: ACL_IPV6_SNMP"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -104,7 +109,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"location": "New York"},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Expected `New York` as the location, but found `Europe` instead."],
|
||||
"messages": ["Incorrect SNMP location - Expected: New York Actual: Europe"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -118,7 +123,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"location": "New York"},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["SNMP location is not configured."],
|
||||
"messages": ["SNMP location is not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -143,7 +148,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"contact": "Bob@example.com"},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Expected `Bob@example.com` as the contact, but found `Jon@example.com` instead."],
|
||||
"messages": ["Incorrect SNMP contact - Expected: Bob@example.com Actual: Jon@example.com"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -157,7 +162,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"contact": "Bob@example.com"},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["SNMP contact is not configured."],
|
||||
"messages": ["SNMP contact is not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -203,7 +208,7 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
],
|
||||
"inputs": {},
|
||||
"expected": {"result": "failure", "messages": ["SNMP counters not found."]},
|
||||
"expected": {"result": "failure", "messages": ["SNMP counters not found"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-counters",
|
||||
|
@ -222,7 +227,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["The following SNMP PDU counters are not found or have zero PDU counters:\n{'inGetPdus': 0, 'inSetPdus': 0}"],
|
||||
"messages": ["The following SNMP PDU counters are not found or have zero PDU counters: inGetPdus, inSetPdus"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -240,7 +245,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"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'}"],
|
||||
"messages": ["The following SNMP PDU counters are not found or have zero PDU counters: inGetPdus, outTrapPdus"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -292,7 +297,7 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
],
|
||||
"inputs": {},
|
||||
"expected": {"result": "failure", "messages": ["SNMP counters not found."]},
|
||||
"expected": {"result": "failure", "messages": ["SNMP counters not found"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-counters",
|
||||
|
@ -312,10 +317,796 @@ DATA: list[dict[str, Any]] = [
|
|||
}
|
||||
],
|
||||
"inputs": {},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["The following SNMP error counters are not found or have non-zero error counters: inParseErrs, inVersionErrs, outBadValueErrs"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifySnmpHostLogging,
|
||||
"eos_data": [
|
||||
{
|
||||
"logging": {
|
||||
"loggingEnabled": True,
|
||||
"hosts": {
|
||||
"192.168.1.100": {"port": 162, "vrf": ""},
|
||||
"192.168.1.101": {"port": 162, "vrf": "MGMT"},
|
||||
"snmp-server-01": {"port": 162, "vrf": "default"},
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"hosts": [
|
||||
{"hostname": "192.168.1.100", "vrf": "default"},
|
||||
{"hostname": "192.168.1.101", "vrf": "MGMT"},
|
||||
{"hostname": "snmp-server-01", "vrf": "default"},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-logging-disabled",
|
||||
"test": VerifySnmpHostLogging,
|
||||
"eos_data": [{"logging": {"loggingEnabled": False}}],
|
||||
"inputs": {"hosts": [{"hostname": "192.168.1.100", "vrf": "default"}, {"hostname": "192.168.1.101", "vrf": "MGMT"}]},
|
||||
"expected": {"result": "failure", "messages": ["SNMP logging is disabled"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-mismatch-vrf",
|
||||
"test": VerifySnmpHostLogging,
|
||||
"eos_data": [{"logging": {"loggingEnabled": True, "hosts": {"192.168.1.100": {"port": 162, "vrf": "MGMT"}, "192.168.1.101": {"port": 162, "vrf": "Test"}}}}],
|
||||
"inputs": {"hosts": [{"hostname": "192.168.1.100", "vrf": "default"}, {"hostname": "192.168.1.101", "vrf": "MGMT"}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Host: 192.168.1.100 VRF: default - Incorrect VRF - Actual: MGMT", "Host: 192.168.1.101 VRF: MGMT - Incorrect VRF - Actual: Test"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-host-not-configured",
|
||||
"test": VerifySnmpHostLogging,
|
||||
"eos_data": [{"logging": {"loggingEnabled": True, "hosts": {"192.168.1.100": {"port": 162, "vrf": "MGMT"}, "192.168.1.103": {"port": 162, "vrf": "Test"}}}}],
|
||||
"inputs": {"hosts": [{"hostname": "192.168.1.101", "vrf": "default"}, {"hostname": "192.168.1.102", "vrf": "MGMT"}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Host: 192.168.1.101 VRF: default - Not configured", "Host: 192.168.1.102 VRF: MGMT - Not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifySnmpUser,
|
||||
"eos_data": [
|
||||
{
|
||||
"usersByVersion": {
|
||||
"v1": {
|
||||
"users": {
|
||||
"Test1": {
|
||||
"groupName": "TestGroup1",
|
||||
},
|
||||
}
|
||||
},
|
||||
"v2c": {
|
||||
"users": {
|
||||
"Test2": {
|
||||
"groupName": "TestGroup2",
|
||||
},
|
||||
}
|
||||
},
|
||||
"v3": {
|
||||
"users": {
|
||||
"Test3": {
|
||||
"groupName": "TestGroup3",
|
||||
"v3Params": {"authType": "SHA-384", "privType": "AES-128"},
|
||||
},
|
||||
"Test4": {"groupName": "TestGroup3", "v3Params": {"authType": "SHA-512", "privType": "AES-192"}},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_users": [
|
||||
{"username": "Test1", "group_name": "TestGroup1", "version": "v1"},
|
||||
{"username": "Test2", "group_name": "TestGroup2", "version": "v2c"},
|
||||
{"username": "Test3", "group_name": "TestGroup3", "version": "v3", "auth_type": "SHA-384", "priv_type": "AES-128"},
|
||||
{"username": "Test4", "group_name": "TestGroup3", "version": "v3", "auth_type": "SHA-512", "priv_type": "AES-192"},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-configured",
|
||||
"test": VerifySnmpUser,
|
||||
"eos_data": [
|
||||
{
|
||||
"usersByVersion": {
|
||||
"v3": {
|
||||
"users": {
|
||||
"Test3": {
|
||||
"groupName": "TestGroup3",
|
||||
"v3Params": {"authType": "SHA-384", "privType": "AES-128"},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_users": [
|
||||
{"username": "Test1", "group_name": "TestGroup1", "version": "v1"},
|
||||
{"username": "Test2", "group_name": "TestGroup2", "version": "v2c"},
|
||||
{"username": "Test3", "group_name": "TestGroup3", "version": "v3", "auth_type": "SHA-384", "priv_type": "AES-128"},
|
||||
{"username": "Test4", "group_name": "TestGroup3", "version": "v3", "auth_type": "SHA-512", "priv_type": "AES-192"},
|
||||
]
|
||||
},
|
||||
"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}"
|
||||
"User: Test1 Group: TestGroup1 Version: v1 - Not found",
|
||||
"User: Test2 Group: TestGroup2 Version: v2c - Not found",
|
||||
"User: Test4 Group: TestGroup3 Version: v3 - Not found",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-group",
|
||||
"test": VerifySnmpUser,
|
||||
"eos_data": [
|
||||
{
|
||||
"usersByVersion": {
|
||||
"v1": {
|
||||
"users": {
|
||||
"Test1": {
|
||||
"groupName": "TestGroup2",
|
||||
},
|
||||
}
|
||||
},
|
||||
"v2c": {
|
||||
"users": {
|
||||
"Test2": {
|
||||
"groupName": "TestGroup1",
|
||||
},
|
||||
}
|
||||
},
|
||||
"v3": {},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_users": [
|
||||
{"username": "Test1", "group_name": "TestGroup1", "version": "v1"},
|
||||
{"username": "Test2", "group_name": "TestGroup2", "version": "v2c"},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"User: Test1 Group: TestGroup1 Version: v1 - Incorrect user group - Actual: TestGroup2",
|
||||
"User: Test2 Group: TestGroup2 Version: v2c - Incorrect user group - Actual: TestGroup1",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-auth-encryption",
|
||||
"test": VerifySnmpUser,
|
||||
"eos_data": [
|
||||
{
|
||||
"usersByVersion": {
|
||||
"v1": {
|
||||
"users": {
|
||||
"Test1": {
|
||||
"groupName": "TestGroup1",
|
||||
},
|
||||
}
|
||||
},
|
||||
"v2c": {
|
||||
"users": {
|
||||
"Test2": {
|
||||
"groupName": "TestGroup2",
|
||||
},
|
||||
}
|
||||
},
|
||||
"v3": {
|
||||
"users": {
|
||||
"Test3": {
|
||||
"groupName": "TestGroup3",
|
||||
"v3Params": {"authType": "SHA-512", "privType": "AES-192"},
|
||||
},
|
||||
"Test4": {"groupName": "TestGroup4", "v3Params": {"authType": "SHA-384", "privType": "AES-128"}},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_users": [
|
||||
{"username": "Test1", "group_name": "TestGroup1", "version": "v1"},
|
||||
{"username": "Test2", "group_name": "TestGroup2", "version": "v2c"},
|
||||
{"username": "Test3", "group_name": "TestGroup3", "version": "v3", "auth_type": "SHA-384", "priv_type": "AES-128"},
|
||||
{"username": "Test4", "group_name": "TestGroup4", "version": "v3", "auth_type": "SHA-512", "priv_type": "AES-192"},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"User: Test3 Group: TestGroup3 Version: v3 - Incorrect authentication type - Expected: SHA-384 Actual: SHA-512",
|
||||
"User: Test3 Group: TestGroup3 Version: v3 - Incorrect privacy type - Expected: AES-128 Actual: AES-192",
|
||||
"User: Test4 Group: TestGroup4 Version: v3 - Incorrect authentication type - Expected: SHA-512 Actual: SHA-384",
|
||||
"User: Test4 Group: TestGroup4 Version: v3 - Incorrect privacy type - Expected: AES-192 Actual: AES-128",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifySnmpNotificationHost,
|
||||
"eos_data": [
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"port": 162,
|
||||
"vrf": "",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v3",
|
||||
"v3Params": {"user": "public", "securityLevel": "authNoPriv"},
|
||||
},
|
||||
{
|
||||
"hostname": "192.168.1.101",
|
||||
"port": 162,
|
||||
"vrf": "MGMT",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v2c",
|
||||
"v1v2cParams": {"communityString": "public"},
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"notification_hosts": [
|
||||
{"hostname": "192.168.1.100", "vrf": "default", "notification_type": "trap", "version": "v3", "udp_port": 162, "user": "public"},
|
||||
{"hostname": "192.168.1.101", "vrf": "MGMT", "notification_type": "trap", "version": "v2c", "udp_port": 162, "community_string": "public"},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-configured",
|
||||
"test": VerifySnmpNotificationHost,
|
||||
"eos_data": [{"hosts": []}],
|
||||
"inputs": {
|
||||
"notification_hosts": [
|
||||
{"hostname": "192.168.1.100", "vrf": "default", "notification_type": "trap", "version": "v3", "udp_port": 162, "user": "public"},
|
||||
{"hostname": "192.168.1.101", "vrf": "default", "notification_type": "trap", "version": "v2c", "udp_port": 162, "community_string": "public"},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["No SNMP host is configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-details-host-not-found",
|
||||
"test": VerifySnmpNotificationHost,
|
||||
"eos_data": [
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"port": 162,
|
||||
"vrf": "",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v3",
|
||||
"v3Params": {"user": "public", "securityLevel": "authNoPriv"},
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"notification_hosts": [
|
||||
{"hostname": "192.168.1.100", "vrf": "default", "notification_type": "trap", "version": "v3", "udp_port": 162, "user": "public"},
|
||||
{"hostname": "192.168.1.101", "vrf": "default", "notification_type": "trap", "version": "v2c", "udp_port": 162, "community_string": "public"},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["Host: 192.168.1.101 VRF: default Version: v2c - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-notification-type",
|
||||
"test": VerifySnmpNotificationHost,
|
||||
"eos_data": [
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"port": 162,
|
||||
"vrf": "",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v3",
|
||||
"v3Params": {"user": "public", "securityLevel": "authNoPriv"},
|
||||
},
|
||||
{
|
||||
"hostname": "192.168.1.101",
|
||||
"port": 162,
|
||||
"vrf": "",
|
||||
"notificationType": "inform",
|
||||
"protocolVersion": "v2c",
|
||||
"v1v2cParams": {"communityString": "public"},
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"notification_hosts": [
|
||||
{"hostname": "192.168.1.100", "vrf": "default", "notification_type": "inform", "version": "v3", "udp_port": 162, "user": "public"},
|
||||
{"hostname": "192.168.1.101", "vrf": "default", "notification_type": "trap", "version": "v2c", "udp_port": 162, "community_string": "public"},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Host: 192.168.1.100 VRF: default - Incorrect notification type - Expected: inform Actual: trap",
|
||||
"Host: 192.168.1.101 VRF: default - Incorrect notification type - Expected: trap Actual: inform",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-udp-port",
|
||||
"test": VerifySnmpNotificationHost,
|
||||
"eos_data": [
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"port": 163,
|
||||
"vrf": "",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v3",
|
||||
"v3Params": {"user": "public", "securityLevel": "authNoPriv"},
|
||||
},
|
||||
{
|
||||
"hostname": "192.168.1.101",
|
||||
"port": 164,
|
||||
"vrf": "",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v2c",
|
||||
"v1v2cParams": {"communityString": "public"},
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"notification_hosts": [
|
||||
{"hostname": "192.168.1.100", "vrf": "default", "notification_type": "trap", "version": "v3", "udp_port": 162, "user": "public"},
|
||||
{"hostname": "192.168.1.101", "vrf": "default", "notification_type": "trap", "version": "v2c", "udp_port": 162, "community_string": "public"},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Host: 192.168.1.100 VRF: default - Incorrect UDP port - Expected: 162 Actual: 163",
|
||||
"Host: 192.168.1.101 VRF: default - Incorrect UDP port - Expected: 162 Actual: 164",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-community-string-version-v1-v2c",
|
||||
"test": VerifySnmpNotificationHost,
|
||||
"eos_data": [
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"port": 162,
|
||||
"vrf": "",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v1",
|
||||
"v1v2cParams": {"communityString": "private"},
|
||||
},
|
||||
{
|
||||
"hostname": "192.168.1.101",
|
||||
"port": 162,
|
||||
"vrf": "",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v2c",
|
||||
"v1v2cParams": {"communityString": "private"},
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"notification_hosts": [
|
||||
{"hostname": "192.168.1.100", "vrf": "default", "notification_type": "trap", "version": "v1", "udp_port": 162, "community_string": "public"},
|
||||
{"hostname": "192.168.1.101", "vrf": "default", "notification_type": "trap", "version": "v2c", "udp_port": 162, "community_string": "public"},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Host: 192.168.1.100 VRF: default Version: v1 - Incorrect community string - Expected: public Actual: private",
|
||||
"Host: 192.168.1.101 VRF: default Version: v2c - Incorrect community string - Expected: public Actual: private",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-user-for-version-v3",
|
||||
"test": VerifySnmpNotificationHost,
|
||||
"eos_data": [
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"port": 162,
|
||||
"vrf": "",
|
||||
"notificationType": "trap",
|
||||
"protocolVersion": "v3",
|
||||
"v3Params": {"user": "private", "securityLevel": "authNoPriv"},
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"notification_hosts": [
|
||||
{"hostname": "192.168.1.100", "vrf": "default", "notification_type": "trap", "version": "v3", "udp_port": 162, "user": "public"},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "failure", "messages": ["Host: 192.168.1.100 VRF: default Version: v3 - Incorrect user - Expected: public Actual: private"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifySnmpSourceInterface,
|
||||
"eos_data": [
|
||||
{
|
||||
"srcIntf": {"sourceInterfaces": {"default": "Ethernet1", "MGMT": "Management0"}},
|
||||
}
|
||||
],
|
||||
"inputs": {"interfaces": [{"interface": "Ethernet1", "vrf": "default"}, {"interface": "Management0", "vrf": "MGMT"}]},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-not-configured",
|
||||
"test": VerifySnmpSourceInterface,
|
||||
"eos_data": [
|
||||
{
|
||||
"srcIntf": {},
|
||||
}
|
||||
],
|
||||
"inputs": {"interfaces": [{"interface": "Ethernet1", "vrf": "default"}, {"interface": "Management0", "vrf": "MGMT"}]},
|
||||
"expected": {"result": "failure", "messages": ["SNMP source interface(s) not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-interfaces",
|
||||
"test": VerifySnmpSourceInterface,
|
||||
"eos_data": [
|
||||
{
|
||||
"srcIntf": {
|
||||
"sourceInterfaces": {
|
||||
"default": "Management0",
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
"inputs": {"interfaces": [{"interface": "Ethernet1", "vrf": "default"}, {"interface": "Management0", "vrf": "MGMT"}]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Source Interface: Ethernet1 VRF: default - Incorrect source interface - Actual: Management0",
|
||||
"Source Interface: Management0 VRF: MGMT - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifySnmpGroup,
|
||||
"eos_data": [
|
||||
{
|
||||
"groups": {
|
||||
"Group1": {
|
||||
"versions": {
|
||||
"v1": {
|
||||
"secModel": "v1",
|
||||
"readView": "group_read_1",
|
||||
"readViewConfig": True,
|
||||
"writeView": "group_write_1",
|
||||
"writeViewConfig": True,
|
||||
"notifyView": "group_notify_1",
|
||||
"notifyViewConfig": True,
|
||||
}
|
||||
}
|
||||
},
|
||||
"Group2": {
|
||||
"versions": {
|
||||
"v2c": {
|
||||
"secModel": "v2c",
|
||||
"readView": "group_read_2",
|
||||
"readViewConfig": True,
|
||||
"writeView": "group_write_2",
|
||||
"writeViewConfig": True,
|
||||
"notifyView": "group_notify_2",
|
||||
"notifyViewConfig": True,
|
||||
}
|
||||
}
|
||||
},
|
||||
"Group3": {
|
||||
"versions": {
|
||||
"v3": {
|
||||
"secModel": "v3Auth",
|
||||
"readView": "group_read_3",
|
||||
"readViewConfig": True,
|
||||
"writeView": "group_write_3",
|
||||
"writeViewConfig": True,
|
||||
"notifyView": "group_notify_3",
|
||||
"notifyViewConfig": True,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_groups": [
|
||||
{"group_name": "Group1", "version": "v1", "read_view": "group_read_1", "write_view": "group_write_1", "notify_view": "group_notify_1"},
|
||||
{"group_name": "Group2", "version": "v2c", "read_view": "group_read_2", "write_view": "group_write_2", "notify_view": "group_notify_2"},
|
||||
{
|
||||
"group_name": "Group3",
|
||||
"version": "v3",
|
||||
"read_view": "group_read_3",
|
||||
"write_view": "group_write_3",
|
||||
"notify_view": "group_notify_3",
|
||||
"authentication": "auth",
|
||||
},
|
||||
]
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-view",
|
||||
"test": VerifySnmpGroup,
|
||||
"eos_data": [
|
||||
{
|
||||
"groups": {
|
||||
"Group1": {
|
||||
"versions": {
|
||||
"v1": {
|
||||
"secModel": "v1",
|
||||
"readView": "group_read",
|
||||
"readViewConfig": True,
|
||||
"writeView": "group_write",
|
||||
"writeViewConfig": True,
|
||||
"notifyView": "group_notify",
|
||||
"notifyViewConfig": True,
|
||||
}
|
||||
}
|
||||
},
|
||||
"Group2": {
|
||||
"versions": {
|
||||
"v2c": {
|
||||
"secModel": "v2c",
|
||||
"readView": "group_read",
|
||||
"readViewConfig": True,
|
||||
"writeView": "group_write",
|
||||
"writeViewConfig": True,
|
||||
"notifyView": "group_notify",
|
||||
"notifyViewConfig": True,
|
||||
}
|
||||
}
|
||||
},
|
||||
"Group3": {
|
||||
"versions": {
|
||||
"v3": {
|
||||
"secModel": "v3NoAuth",
|
||||
"readView": "group_read",
|
||||
"readViewConfig": True,
|
||||
"writeView": "group_write",
|
||||
"writeViewConfig": True,
|
||||
"notifyView": "group_notify",
|
||||
"notifyViewConfig": True,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_groups": [
|
||||
{"group_name": "Group1", "version": "v1", "read_view": "group_read_1", "write_view": "group_write_1", "notify_view": "group_notify_1"},
|
||||
{"group_name": "Group2", "version": "v2c", "read_view": "group_read_2", "notify_view": "group_notify_2"},
|
||||
{
|
||||
"group_name": "Group3",
|
||||
"version": "v3",
|
||||
"read_view": "group_read_3",
|
||||
"write_view": "group_write_3",
|
||||
"notify_view": "group_notify_3",
|
||||
"authentication": "noauth",
|
||||
},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Group: Group1 Version: v1 - Incorrect Read view - Expected: group_read_1 Actual: group_read",
|
||||
"Group: Group1 Version: v1 - Incorrect Write view - Expected: group_write_1 Actual: group_write",
|
||||
"Group: Group1 Version: v1 - Incorrect Notify view - Expected: group_notify_1 Actual: group_notify",
|
||||
"Group: Group2 Version: v2c - Incorrect Read view - Expected: group_read_2 Actual: group_read",
|
||||
"Group: Group2 Version: v2c - Incorrect Notify view - Expected: group_notify_2 Actual: group_notify",
|
||||
"Group: Group3 Version: v3 - Incorrect Read view - Expected: group_read_3 Actual: group_read",
|
||||
"Group: Group3 Version: v3 - Incorrect Write view - Expected: group_write_3 Actual: group_write",
|
||||
"Group: Group3 Version: v3 - Incorrect Notify view - Expected: group_notify_3 Actual: group_notify",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-view-config-not-found",
|
||||
"test": VerifySnmpGroup,
|
||||
"eos_data": [
|
||||
{
|
||||
"groups": {
|
||||
"Group1": {
|
||||
"versions": {
|
||||
"v1": {
|
||||
"secModel": "v1",
|
||||
"readView": "group_read",
|
||||
"readViewConfig": False,
|
||||
"writeView": "group_write",
|
||||
"writeViewConfig": False,
|
||||
"notifyView": "group_notify",
|
||||
"notifyViewConfig": False,
|
||||
}
|
||||
}
|
||||
},
|
||||
"Group2": {
|
||||
"versions": {
|
||||
"v2c": {
|
||||
"secModel": "v2c",
|
||||
"readView": "group_read",
|
||||
"readViewConfig": False,
|
||||
"writeView": "group_write",
|
||||
"writeViewConfig": False,
|
||||
"notifyView": "group_notify",
|
||||
"notifyViewConfig": False,
|
||||
}
|
||||
}
|
||||
},
|
||||
"Group3": {
|
||||
"versions": {
|
||||
"v3": {
|
||||
"secModel": "v3Priv",
|
||||
"readView": "group_read",
|
||||
"readViewConfig": False,
|
||||
"writeView": "group_write",
|
||||
"writeViewConfig": False,
|
||||
"notifyView": "group_notify",
|
||||
"notifyViewConfig": False,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_groups": [
|
||||
{"group_name": "Group1", "version": "v1", "read_view": "group_read", "write_view": "group_write", "notify_view": "group_notify"},
|
||||
{"group_name": "Group2", "version": "v2c", "read_view": "group_read", "write_view": "group_write", "notify_view": "group_notify"},
|
||||
{
|
||||
"group_name": "Group3",
|
||||
"version": "v3",
|
||||
"write_view": "group_write",
|
||||
"notify_view": "group_notify",
|
||||
"authentication": "priv",
|
||||
},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Group: Group1 Version: v1 Read View: group_read - Not configured",
|
||||
"Group: Group1 Version: v1 Write View: group_write - Not configured",
|
||||
"Group: Group1 Version: v1 Notify View: group_notify - Not configured",
|
||||
"Group: Group2 Version: v2c Read View: group_read - Not configured",
|
||||
"Group: Group2 Version: v2c Write View: group_write - Not configured",
|
||||
"Group: Group2 Version: v2c Notify View: group_notify - Not configured",
|
||||
"Group: Group3 Version: v3 Write View: group_write - Not configured",
|
||||
"Group: Group3 Version: v3 Notify View: group_notify - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-group-version-not-configured",
|
||||
"test": VerifySnmpGroup,
|
||||
"eos_data": [
|
||||
{
|
||||
"groups": {
|
||||
"Group1": {"versions": {"v1": {}}},
|
||||
"Group2": {"versions": {"v2c": {}}},
|
||||
"Group3": {"versions": {"v3": {}}},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_groups": [
|
||||
{"group_name": "Group1", "version": "v1", "read_view": "group_read_1", "write_view": "group_write_1"},
|
||||
{"group_name": "Group2", "version": "v2c", "read_view": "group_read_2", "write_view": "group_write_2", "notify_view": "group_notify_2"},
|
||||
{
|
||||
"group_name": "Group3",
|
||||
"version": "v3",
|
||||
"read_view": "group_read_3",
|
||||
"write_view": "group_write_3",
|
||||
"notify_view": "group_notify_3",
|
||||
"authentication": "auth",
|
||||
},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Group: Group1 Version: v1 - Not configured",
|
||||
"Group: Group2 Version: v2c - Not configured",
|
||||
"Group: Group3 Version: v3 - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-v3-auth-model",
|
||||
"test": VerifySnmpGroup,
|
||||
"eos_data": [
|
||||
{
|
||||
"groups": {
|
||||
"Group3": {
|
||||
"versions": {
|
||||
"v3": {
|
||||
"secModel": "v3Auth",
|
||||
"readView": "group_read",
|
||||
"readViewConfig": True,
|
||||
"writeView": "group_write",
|
||||
"writeViewConfig": True,
|
||||
"notifyView": "group_notify",
|
||||
"notifyViewConfig": True,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_groups": [
|
||||
{
|
||||
"group_name": "Group3",
|
||||
"version": "v3",
|
||||
"read_view": "group_read",
|
||||
"write_view": "group_write",
|
||||
"notify_view": "group_notify",
|
||||
"authentication": "priv",
|
||||
},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Group: Group3 Version: v3 - Incorrect security model - Expected: v3Priv Actual: v3Auth",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-view-not-configured",
|
||||
"test": VerifySnmpGroup,
|
||||
"eos_data": [
|
||||
{
|
||||
"groups": {
|
||||
"Group3": {"versions": {"v3": {"secModel": "v3NoAuth", "readView": "group_read", "readViewConfig": True, "writeView": "", "notifyView": ""}}},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"snmp_groups": [
|
||||
{
|
||||
"group_name": "Group3",
|
||||
"version": "v3",
|
||||
"read_view": "group_read",
|
||||
"write_view": "group_write",
|
||||
"authentication": "noauth",
|
||||
},
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Group: Group3 Version: v3 View: write - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
@ -35,7 +35,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"versions": ["4.27.1F"]},
|
||||
"expected": {"result": "failure", "messages": ["device is running version \"4.27.0F\" not in expected versions: ['4.27.1F']"]},
|
||||
"expected": {"result": "failure", "messages": ["EOS version mismatch - Actual: 4.27.0F not in Expected: 4.27.1F"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -77,9 +77,8 @@ 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']"]},
|
||||
"expected": {"result": "failure", "messages": ["TerminAttr version mismatch - Actual: v1.17.0 not in Expected: v1.17.1, v1.18.1"]},
|
||||
},
|
||||
# TODO: add a test with a real extension?
|
||||
{
|
||||
"name": "success-no-extensions",
|
||||
"test": VerifyEOSExtensions,
|
||||
|
@ -91,11 +90,30 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-empty-extension",
|
||||
"name": "success-extensions",
|
||||
"test": VerifyEOSExtensions,
|
||||
"eos_data": [
|
||||
{"extensions": {}, "extensionStoredDir": "flash:", "warnings": ["No extensions are available"]},
|
||||
{"extensions": [""]},
|
||||
{
|
||||
"extensions": {
|
||||
"AristaCloudGateway-1.0.1-1.swix": {
|
||||
"version": "1.0.1",
|
||||
"release": "1",
|
||||
"presence": "present",
|
||||
"status": "installed",
|
||||
"boot": True,
|
||||
"numPackages": 1,
|
||||
"error": False,
|
||||
"vendor": "",
|
||||
"summary": "Arista Cloud Connect",
|
||||
"installedSize": 60532424,
|
||||
"packages": {"AristaCloudGateway-1.0.1-1.x86_64.rpm": {"version": "1.0.1", "release": "1"}},
|
||||
"description": "An extension for Arista Cloud Connect gateway",
|
||||
"affectedAgents": [],
|
||||
"agentsToRestart": [],
|
||||
},
|
||||
}
|
||||
},
|
||||
{"extensions": ["AristaCloudGateway-1.0.1-1.swix"]},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "success"},
|
||||
|
@ -104,10 +122,80 @@ DATA: list[dict[str, Any]] = [
|
|||
"name": "failure",
|
||||
"test": VerifyEOSExtensions,
|
||||
"eos_data": [
|
||||
{"extensions": {}, "extensionStoredDir": "flash:", "warnings": ["No extensions are available"]},
|
||||
{"extensions": ["dummy"]},
|
||||
{
|
||||
"extensions": {
|
||||
"AristaCloudGateway-1.0.1-1.swix": {
|
||||
"version": "1.0.1",
|
||||
"release": "1",
|
||||
"presence": "present",
|
||||
"status": "installed",
|
||||
"boot": False,
|
||||
"numPackages": 1,
|
||||
"error": False,
|
||||
"vendor": "",
|
||||
"summary": "Arista Cloud Connect",
|
||||
"installedSize": 60532424,
|
||||
"packages": {"AristaCloudGateway-1.0.1-1.x86_64.rpm": {"version": "1.0.1", "release": "1"}},
|
||||
"description": "An extension for Arista Cloud Connect gateway",
|
||||
"affectedAgents": [],
|
||||
"agentsToRestart": [],
|
||||
},
|
||||
}
|
||||
},
|
||||
{"extensions": []},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Missing EOS extensions: installed [] / configured: ['dummy']"]},
|
||||
"expected": {"result": "failure", "messages": ["EOS extensions mismatch - Installed: AristaCloudGateway-1.0.1-1.swix Configured: Not found"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-multiple-extensions",
|
||||
"test": VerifyEOSExtensions,
|
||||
"eos_data": [
|
||||
{
|
||||
"extensions": {
|
||||
"AristaCloudGateway-1.0.1-1.swix": {
|
||||
"version": "1.0.1",
|
||||
"release": "1",
|
||||
"presence": "present",
|
||||
"status": "installed",
|
||||
"boot": False,
|
||||
"numPackages": 1,
|
||||
"error": False,
|
||||
"vendor": "",
|
||||
"summary": "Arista Cloud Connect",
|
||||
"installedSize": 60532424,
|
||||
"packages": {"AristaCloudGateway-1.0.1-1.x86_64.rpm": {"version": "1.0.1", "release": "1"}},
|
||||
"description": "An extension for Arista Cloud Connect gateway",
|
||||
"affectedAgents": [],
|
||||
"agentsToRestart": [],
|
||||
},
|
||||
"EOS-4.33.0F-NDRSensor.swix": {
|
||||
"version": "4.33.0",
|
||||
"release": "39050855.4330F",
|
||||
"presence": "present",
|
||||
"status": "notInstalled",
|
||||
"boot": True,
|
||||
"numPackages": 9,
|
||||
"error": False,
|
||||
"statusDetail": "No RPMs are compatible with current EOS version.",
|
||||
"vendor": "",
|
||||
"summary": "NDR sensor",
|
||||
"installedSize": 0,
|
||||
"packages": {},
|
||||
"description": "NDR sensor provides libraries to generate flow activity records using DPI\nmetadata and IPFIX flow records.",
|
||||
"affectedAgents": [],
|
||||
"agentsToRestart": [],
|
||||
},
|
||||
}
|
||||
},
|
||||
{"extensions": ["AristaCloudGateway-1.0.1-1.swix", "EOS-4.33.0F-NDRSensor.swix"]},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"EOS extensions mismatch - Installed: AristaCloudGateway-1.0.1-1.swix Configured: AristaCloudGateway-1.0.1-1.swix, EOS-4.33.0F-NDRSensor.swix"
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.stp.py."""
|
||||
|
@ -7,7 +7,15 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from anta.tests.stp import VerifySTPBlockedPorts, VerifySTPCounters, VerifySTPForwardingPorts, VerifySTPMode, VerifySTPRootPriority, VerifyStpTopologyChanges
|
||||
from anta.tests.stp import (
|
||||
VerifySTPBlockedPorts,
|
||||
VerifySTPCounters,
|
||||
VerifySTPDisabledVlans,
|
||||
VerifySTPForwardingPorts,
|
||||
VerifySTPMode,
|
||||
VerifySTPRootPriority,
|
||||
VerifyStpTopologyChanges,
|
||||
)
|
||||
from tests.units.anta_tests import test
|
||||
|
||||
DATA: list[dict[str, Any]] = [
|
||||
|
@ -29,7 +37,7 @@ DATA: list[dict[str, Any]] = [
|
|||
{"spanningTreeVlanInstances": {}},
|
||||
],
|
||||
"inputs": {"mode": "rstp", "vlans": [10, 20]},
|
||||
"expected": {"result": "failure", "messages": ["STP mode 'rstp' not configured for the following VLAN(s): [10, 20]"]},
|
||||
"expected": {"result": "failure", "messages": ["VLAN 10 STP mode: rstp - Not configured", "VLAN 20 STP mode: rstp - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-mode",
|
||||
|
@ -39,7 +47,10 @@ DATA: list[dict[str, Any]] = [
|
|||
{"spanningTreeVlanInstances": {"20": {"spanningTreeVlanInstance": {"protocol": "mstp"}}}},
|
||||
],
|
||||
"inputs": {"mode": "rstp", "vlans": [10, 20]},
|
||||
"expected": {"result": "failure", "messages": ["Wrong STP mode configured for the following VLAN(s): [10, 20]"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["VLAN 10 - Incorrect STP mode - Expected: rstp Actual: mstp", "VLAN 20 - Incorrect STP mode - Expected: rstp Actual: mstp"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-both",
|
||||
|
@ -51,7 +62,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"mode": "rstp", "vlans": [10, 20]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["STP mode 'rstp' not configured for the following VLAN(s): [10]", "Wrong STP mode configured for the following VLAN(s): [20]"],
|
||||
"messages": ["VLAN 10 STP mode: rstp - Not configured", "VLAN 20 - Incorrect STP mode - Expected: rstp Actual: mstp"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -66,7 +77,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifySTPBlockedPorts,
|
||||
"eos_data": [{"spanningTreeInstances": {"MST0": {"spanningTreeBlockedPorts": ["Ethernet10"]}, "MST10": {"spanningTreeBlockedPorts": ["Ethernet10"]}}}],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The following ports are blocked by STP: {'MST0': ['Ethernet10'], 'MST10': ['Ethernet10']}"]},
|
||||
"expected": {"result": "failure", "messages": ["STP Instance: MST0 - Blocked ports - Ethernet10", "STP Instance: MST10 - Blocked ports - Ethernet10"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -76,18 +87,44 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure",
|
||||
"name": "failure-bpdu-tagged-error-mismatch",
|
||||
"test": VerifySTPCounters,
|
||||
"eos_data": [
|
||||
{
|
||||
"interfaces": {
|
||||
"Ethernet10": {"bpduSent": 201, "bpduReceived": 0, "bpduTaggedError": 3, "bpduOtherError": 0, "bpduRateLimitCount": 0},
|
||||
"Ethernet11": {"bpduSent": 99, "bpduReceived": 0, "bpduTaggedError": 3, "bpduOtherError": 0, "bpduRateLimitCount": 0},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Interface Ethernet10 - STP BPDU packet tagged errors count mismatch - Expected: 0 Actual: 3",
|
||||
"Interface Ethernet11 - STP BPDU packet tagged errors count mismatch - Expected: 0 Actual: 3",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-bpdu-other-error-mismatch",
|
||||
"test": VerifySTPCounters,
|
||||
"eos_data": [
|
||||
{
|
||||
"interfaces": {
|
||||
"Ethernet10": {"bpduSent": 201, "bpduReceived": 0, "bpduTaggedError": 0, "bpduOtherError": 3, "bpduRateLimitCount": 0},
|
||||
"Ethernet11": {"bpduSent": 99, "bpduReceived": 0, "bpduTaggedError": 0, "bpduOtherError": 6, "bpduRateLimitCount": 0},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The following interfaces have STP BPDU packet errors: ['Ethernet10', 'Ethernet11']"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Interface Ethernet10 - STP BPDU packet other errors count mismatch - Expected: 0 Actual: 3",
|
||||
"Interface Ethernet11 - STP BPDU packet other errors count mismatch - Expected: 0 Actual: 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -126,7 +163,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifySTPForwardingPorts,
|
||||
"eos_data": [{"unmappedVlans": [], "topologies": {}}, {"unmappedVlans": [], "topologies": {}}],
|
||||
"inputs": {"vlans": [10, 20]},
|
||||
"expected": {"result": "failure", "messages": ["STP instance is not configured for the following VLAN(s): [10, 20]"]},
|
||||
"expected": {"result": "failure", "messages": ["VLAN 10 - STP instance is not configured", "VLAN 20 - STP instance is not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure",
|
||||
|
@ -144,7 +181,10 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"vlans": [10, 20]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["The following VLAN(s) have interface(s) that are not in a forwarding state: [{'VLAN 10': ['Ethernet10']}, {'VLAN 20': ['Ethernet10']}]"],
|
||||
"messages": [
|
||||
"VLAN 10 Interface: Ethernet10 - Invalid state - Expected: forwarding Actual: discarding",
|
||||
"VLAN 20 Interface: Ethernet10 - Invalid state - Expected: forwarding Actual: discarding",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -253,6 +293,28 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"priority": 16384, "instances": [0]},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-input-instance-none",
|
||||
"test": VerifySTPRootPriority,
|
||||
"eos_data": [
|
||||
{
|
||||
"instances": {
|
||||
"MST0": {
|
||||
"rootBridge": {
|
||||
"priority": 16384,
|
||||
"systemIdExtension": 0,
|
||||
"macAddress": "02:1c:73:8b:93:ac",
|
||||
"helloTime": 2.0,
|
||||
"maxAge": 20,
|
||||
"forwardDelay": 15,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"inputs": {"priority": 16384},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-instances",
|
||||
"test": VerifySTPRootPriority,
|
||||
|
@ -273,7 +335,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"priority": 32768, "instances": [0]},
|
||||
"expected": {"result": "failure", "messages": ["Unsupported STP instance type: WRONG0"]},
|
||||
"expected": {"result": "failure", "messages": ["STP Instance: WRONG0 - Unsupported STP instance type"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-instance-type",
|
||||
|
@ -282,6 +344,28 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"priority": 32768, "instances": [10, 20]},
|
||||
"expected": {"result": "failure", "messages": ["No STP instances configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-instance-not-found",
|
||||
"test": VerifySTPRootPriority,
|
||||
"eos_data": [
|
||||
{
|
||||
"instances": {
|
||||
"VL10": {
|
||||
"rootBridge": {
|
||||
"priority": 32768,
|
||||
"systemIdExtension": 10,
|
||||
"macAddress": "00:1c:73:27:95:a2",
|
||||
"helloTime": 2.0,
|
||||
"maxAge": 20,
|
||||
"forwardDelay": 15,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {"priority": 32768, "instances": [11, 20]},
|
||||
"expected": {"result": "failure", "messages": ["Instance: VL11 - Not configured", "Instance: VL20 - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-priority",
|
||||
"test": VerifySTPRootPriority,
|
||||
|
@ -322,7 +406,13 @@ 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']"]},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"STP Instance: VL20 - Incorrect root priority - Expected: 32768 Actual: 8196",
|
||||
"STP Instance: VL30 - Incorrect root priority - Expected: 32768 Actual: 8196",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success-mstp",
|
||||
|
@ -462,8 +552,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"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}}}}"
|
||||
"Topology: Cist Interface: Cpu - Number of changes not within the threshold - Expected: 10 Actual: 15",
|
||||
"Topology: Cist Interface: Port-Channel5 - Number of changes not within the threshold - Expected: 10 Actual: 15",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -484,6 +574,50 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"threshold": 10},
|
||||
"expected": {"result": "failure", "messages": ["STP is not configured."]},
|
||||
"expected": {"result": "failure", "messages": ["STP is not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifySTPDisabledVlans,
|
||||
"eos_data": [{"spanningTreeVlanInstances": {"1": {"spanningTreeVlanInstance": {"protocol": "mstp", "bridge": {"priority": 32768}}}, "6": {}, "4094": {}}}],
|
||||
"inputs": {"vlans": ["6", "4094"]},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-stp-not-configured",
|
||||
"test": VerifySTPDisabledVlans,
|
||||
"eos_data": [{"spanningTreeVlanInstances": {}}],
|
||||
"inputs": {"vlans": ["6", "4094"]},
|
||||
"expected": {"result": "failure", "messages": ["STP is not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-vlans-not-found",
|
||||
"test": VerifySTPDisabledVlans,
|
||||
"eos_data": [
|
||||
{
|
||||
"spanningTreeVlanInstances": {
|
||||
"1": {"spanningTreeVlanInstance": {"protocol": "mstp", "bridge": {}}},
|
||||
"6": {"spanningTreeVlanInstance": {"protocol": "mstp", "bridge": {}}},
|
||||
"4094": {"spanningTreeVlanInstance": {"protocol": "mstp", "bridge": {}}},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {"vlans": ["16", "4093"]},
|
||||
"expected": {"result": "failure", "messages": ["VLAN: 16 - Not configured", "VLAN: 4093 - Not configured"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-vlans-enabled",
|
||||
"test": VerifySTPDisabledVlans,
|
||||
"eos_data": [
|
||||
{
|
||||
"spanningTreeVlanInstances": {
|
||||
"1": {"spanningTreeVlanInstance": {"protocol": "mstp", "bridge": {}}},
|
||||
"6": {"spanningTreeVlanInstance": {"protocol": "mstp", "bridge": {}}},
|
||||
"4094": {"spanningTreeVlanInstance": {"protocol": "mstp", "bridge": {}}},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {"vlans": ["6", "4094"]},
|
||||
"expected": {"result": "failure", "messages": ["VLAN: 6 - STP is enabled", "VLAN: 4094 - STP is enabled"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.stun.py."""
|
||||
|
@ -108,7 +108,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Client 100.64.3.2 Port: 4500 - STUN client translation not found.", "Client 172.18.3.2 Port: 4500 - STUN client translation not found."],
|
||||
"messages": ["Client 100.64.3.2 Port: 4500 - STUN client translation not found", "Client 172.18.3.2 Port: 4500 - STUN client translation not found"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -134,7 +134,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Client 100.64.3.2 Port: 4500 - STUN client translation not found.",
|
||||
"Client 100.64.3.2 Port: 4500 - STUN client translation not found",
|
||||
"Client 172.18.3.2 Port: 4500 - Incorrect public-facing address - Expected: 192.118.3.2 Actual: 192.18.3.2",
|
||||
"Client 172.18.3.2 Port: 4500 - Incorrect public-facing port - Expected: 6006 Actual: 4800",
|
||||
],
|
||||
|
@ -163,7 +163,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Client 100.64.3.2 Port: 4500 - STUN client translation not found.",
|
||||
"Client 100.64.3.2 Port: 4500 - STUN client translation not found",
|
||||
"Client 172.18.4.2 Port: 4800 - Incorrect public-facing address - Expected: 192.118.3.2 Actual: 192.18.3.2",
|
||||
"Client 172.18.4.2 Port: 4800 - Incorrect public-facing port - Expected: 6006 Actual: 4800",
|
||||
],
|
||||
|
@ -193,7 +193,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["STUN server status is disabled."],
|
||||
"messages": ["STUN server status is disabled"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -208,7 +208,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["STUN server is not running."],
|
||||
"messages": ["STUN server is not running"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -223,7 +223,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["STUN server status is disabled and not running."],
|
||||
"messages": ["STUN server status is disabled and not running"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.system."""
|
||||
|
@ -12,6 +12,7 @@ from anta.tests.system import (
|
|||
VerifyCoredump,
|
||||
VerifyCPUUtilization,
|
||||
VerifyFileSystemUtilization,
|
||||
VerifyMaintenance,
|
||||
VerifyMemoryUtilization,
|
||||
VerifyNTP,
|
||||
VerifyNTPAssociations,
|
||||
|
@ -33,7 +34,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyUptime,
|
||||
"eos_data": [{"upTime": 665.15, "loadAvg": [0.13, 0.12, 0.09], "users": 1, "currentTime": 1683186659.139859}],
|
||||
"inputs": {"minimum": 666},
|
||||
"expected": {"result": "failure", "messages": ["Device uptime is 665.15 seconds"]},
|
||||
"expected": {"result": "failure", "messages": ["Device uptime is incorrect - Expected: 666s Actual: 665.15s"]},
|
||||
},
|
||||
{
|
||||
"name": "success-no-reload",
|
||||
|
@ -74,7 +75,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Reload cause is: 'Reload after crash.'"]},
|
||||
"expected": {"result": "failure", "messages": ["Reload cause is: Reload after crash."]},
|
||||
},
|
||||
{
|
||||
"name": "success-without-minidump",
|
||||
|
@ -95,14 +96,14 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyCoredump,
|
||||
"eos_data": [{"mode": "compressedDeferred", "coreFiles": ["core.2344.1584483862.Mlag.gz", "core.23101.1584483867.Mlag.gz"]}],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Core dump(s) have been found: ['core.2344.1584483862.Mlag.gz', 'core.23101.1584483867.Mlag.gz']"]},
|
||||
"expected": {"result": "failure", "messages": ["Core dump(s) have been found: core.2344.1584483862.Mlag.gz, core.23101.1584483867.Mlag.gz"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-with-minidump",
|
||||
"test": VerifyCoredump,
|
||||
"eos_data": [{"mode": "compressedDeferred", "coreFiles": ["minidump", "core.2344.1584483862.Mlag.gz", "core.23101.1584483867.Mlag.gz"]}],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Core dump(s) have been found: ['core.2344.1584483862.Mlag.gz', 'core.23101.1584483867.Mlag.gz']"]},
|
||||
"expected": {"result": "failure", "messages": ["Core dump(s) have been found: core.2344.1584483862.Mlag.gz, core.23101.1584483867.Mlag.gz"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -190,7 +191,7 @@ EntityManager::doBackoff waiting for remote sysdb version ...................ok
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Device has reported a high CPU utilization: 75.2%"]},
|
||||
"expected": {"result": "failure", "messages": ["Device has reported a high CPU utilization - Expected: < 75% Actual: 75.2%"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -222,7 +223,7 @@ EntityManager::doBackoff waiting for remote sysdb version ...................ok
|
|||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Device has reported a high memory usage: 95.56%"]},
|
||||
"expected": {"result": "failure", "messages": ["Device has reported a high memory usage - Expected: < 75% Actual: 95.56%"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -253,8 +254,8 @@ none 294M 78M 217M 84% /.overlay
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Mount point /dev/sda2 3.9G 988M 2.9G 84% /mnt/flash is higher than 75%: reported 84%",
|
||||
"Mount point none 294M 78M 217M 84% /.overlay is higher than 75%: reported 84%",
|
||||
"Mount point: /dev/sda2 3.9G 988M 2.9G 84% /mnt/flash - Higher disk space utilization - Expected: 75% Actual: 84%",
|
||||
"Mount point: none 294M 78M 217M 84% /.overlay - Higher disk space utilization - Expected: 75% Actual: 84%",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -278,7 +279,7 @@ poll interval unknown
|
|||
""",
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["The device is not synchronized with the configured NTP server(s): 'unsynchronised'"]},
|
||||
"expected": {"result": "failure", "messages": ["NTP status mismatch - Expected: synchronised Actual: unsynchronised"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -346,6 +347,65 @@ poll interval unknown
|
|||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-ntp-pool-as-input",
|
||||
"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_pool": {"server_addresses": ["1.1.1.1", "2.2.2.2", "3.3.3.3"], "preferred_stratum_range": [1, 2]}},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-ntp-pool-hostname",
|
||||
"test": VerifyNTPAssociations,
|
||||
"eos_data": [
|
||||
{
|
||||
"peers": {
|
||||
"itsys-ntp010p.aristanetworks.com": {
|
||||
"condition": "sys.peer",
|
||||
"peerIpAddr": "1.1.1.1",
|
||||
"stratumLevel": 1,
|
||||
},
|
||||
"itsys-ntp011p.aristanetworks.com": {
|
||||
"condition": "candidate",
|
||||
"peerIpAddr": "2.2.2.2",
|
||||
"stratumLevel": 2,
|
||||
},
|
||||
"itsys-ntp012p.aristanetworks.com": {
|
||||
"condition": "candidate",
|
||||
"peerIpAddr": "3.3.3.3",
|
||||
"stratumLevel": 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"ntp_pool": {
|
||||
"server_addresses": ["itsys-ntp010p.aristanetworks.com", "itsys-ntp011p.aristanetworks.com", "itsys-ntp012p.aristanetworks.com"],
|
||||
"preferred_stratum_range": [1, 2],
|
||||
}
|
||||
},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-ip-dns",
|
||||
"test": VerifyNTPAssociations,
|
||||
|
@ -380,7 +440,7 @@ poll interval unknown
|
|||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure",
|
||||
"name": "failure-ntp-server",
|
||||
"test": VerifyNTPAssociations,
|
||||
"eos_data": [
|
||||
{
|
||||
|
@ -413,9 +473,11 @@ poll interval unknown
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"1.1.1.1 (Preferred: True, Stratum: 1) - Bad association - Condition: candidate, Stratum: 2",
|
||||
"2.2.2.2 (Preferred: False, Stratum: 2) - Bad association - Condition: sys.peer, Stratum: 2",
|
||||
"3.3.3.3 (Preferred: False, Stratum: 2) - Bad association - Condition: sys.peer, Stratum: 3",
|
||||
"NTP Server: 1.1.1.1 Preferred: True Stratum: 1 - Incorrect condition - Expected: sys.peer Actual: candidate",
|
||||
"NTP Server: 1.1.1.1 Preferred: True Stratum: 1 - Incorrect stratum level - Expected: 1 Actual: 2",
|
||||
"NTP Server: 2.2.2.2 Preferred: False Stratum: 2 - Incorrect condition - Expected: candidate Actual: sys.peer",
|
||||
"NTP Server: 3.3.3.3 Preferred: False Stratum: 2 - Incorrect condition - Expected: candidate Actual: sys.peer",
|
||||
"NTP Server: 3.3.3.3 Preferred: False Stratum: 2 - Incorrect stratum level - Expected: 2 Actual: 3",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -463,7 +525,7 @@ poll interval unknown
|
|||
},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["3.3.3.3 (Preferred: False, Stratum: 1) - Not configured"],
|
||||
"messages": ["NTP Server: 3.3.3.3 Preferred: False Stratum: 1 - Not configured"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -490,9 +552,311 @@ poll interval unknown
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"1.1.1.1 (Preferred: True, Stratum: 1) - Bad association - Condition: candidate, Stratum: 1",
|
||||
"2.2.2.2 (Preferred: False, Stratum: 1) - Not configured",
|
||||
"3.3.3.3 (Preferred: False, Stratum: 1) - Not configured",
|
||||
"NTP Server: 1.1.1.1 Preferred: True Stratum: 1 - Incorrect condition - Expected: sys.peer Actual: candidate",
|
||||
"NTP Server: 2.2.2.2 Preferred: False Stratum: 1 - Not configured",
|
||||
"NTP Server: 3.3.3.3 Preferred: False Stratum: 1 - Not configured",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-ntp-pool-as-input",
|
||||
"test": VerifyNTPAssociations,
|
||||
"eos_data": [
|
||||
{
|
||||
"peers": {
|
||||
"ntp1.pool": {
|
||||
"condition": "sys.peer",
|
||||
"peerIpAddr": "1.1.1.1",
|
||||
"stratumLevel": 1,
|
||||
},
|
||||
"ntp2.pool": {
|
||||
"condition": "candidate",
|
||||
"peerIpAddr": "2.2.2.2",
|
||||
"stratumLevel": 2,
|
||||
},
|
||||
"ntp3.pool": {
|
||||
"condition": "candidate",
|
||||
"peerIpAddr": "3.3.3.3",
|
||||
"stratumLevel": 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {"ntp_pool": {"server_addresses": ["1.1.1.1", "2.2.2.2"], "preferred_stratum_range": [1, 2]}},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["NTP Server: 3.3.3.3 Hostname: ntp3.pool - Associated but not part of the provided NTP pool"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-ntp-pool-as-input-bad-association",
|
||||
"test": VerifyNTPAssociations,
|
||||
"eos_data": [
|
||||
{
|
||||
"peers": {
|
||||
"ntp1.pool": {
|
||||
"condition": "sys.peer",
|
||||
"peerIpAddr": "1.1.1.1",
|
||||
"stratumLevel": 1,
|
||||
},
|
||||
"ntp2.pool": {
|
||||
"condition": "candidate",
|
||||
"peerIpAddr": "2.2.2.2",
|
||||
"stratumLevel": 2,
|
||||
},
|
||||
"ntp3.pool": {
|
||||
"condition": "reject",
|
||||
"peerIpAddr": "3.3.3.3",
|
||||
"stratumLevel": 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {"ntp_pool": {"server_addresses": ["1.1.1.1", "2.2.2.2", "3.3.3.3"], "preferred_stratum_range": [1, 2]}},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"NTP Server: 3.3.3.3 Hostname: ntp3.pool - Incorrect condition - Expected: sys.peer, candidate Actual: reject",
|
||||
"NTP Server: 3.3.3.3 Hostname: ntp3.pool - Incorrect stratum level - Expected Stratum Range: 1 to 2 Actual: 3",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-ntp-pool-hostname",
|
||||
"test": VerifyNTPAssociations,
|
||||
"eos_data": [
|
||||
{
|
||||
"peers": {
|
||||
"itsys-ntp010p.aristanetworks.com": {
|
||||
"condition": "sys.peer",
|
||||
"peerIpAddr": "1.1.1.1",
|
||||
"stratumLevel": 5,
|
||||
},
|
||||
"itsys-ntp011p.aristanetworks.com": {
|
||||
"condition": "reject",
|
||||
"peerIpAddr": "2.2.2.2",
|
||||
"stratumLevel": 4,
|
||||
},
|
||||
"itsys-ntp012p.aristanetworks.com": {
|
||||
"condition": "candidate",
|
||||
"peerIpAddr": "3.3.3.3",
|
||||
"stratumLevel": 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": {"ntp_pool": {"server_addresses": ["itsys-ntp010p.aristanetworks.com", "itsys-ntp011p.aristanetworks.com"], "preferred_stratum_range": [1, 2]}},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"NTP Server: 1.1.1.1 Hostname: itsys-ntp010p.aristanetworks.com - Incorrect stratum level - Expected Stratum Range: 1 to 2 Actual: 5",
|
||||
"NTP Server: 2.2.2.2 Hostname: itsys-ntp011p.aristanetworks.com - Incorrect condition - Expected: sys.peer, candidate Actual: reject",
|
||||
"NTP Server: 2.2.2.2 Hostname: itsys-ntp011p.aristanetworks.com - Incorrect stratum level - Expected Stratum Range: 1 to 2 Actual: 4",
|
||||
"NTP Server: 3.3.3.3 Hostname: itsys-ntp012p.aristanetworks.com - Associated but not part of the provided NTP pool",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success-no-maintenance-configured",
|
||||
"test": VerifyMaintenance,
|
||||
"eos_data": [
|
||||
{
|
||||
"units": {},
|
||||
"interfaces": {},
|
||||
"vrfs": {},
|
||||
"warnings": ["Maintenance Mode is disabled."],
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-maintenance-configured-but-not-enabled",
|
||||
"test": VerifyMaintenance,
|
||||
"eos_data": [
|
||||
{
|
||||
"units": {
|
||||
"System": {
|
||||
"state": "active",
|
||||
"adminState": "active",
|
||||
"stateChangeTime": 0.0,
|
||||
"onBootMaintenance": False,
|
||||
"intfsViolatingTrafficThreshold": False,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
}
|
||||
},
|
||||
"interfaces": {},
|
||||
"vrfs": {},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "success-multiple-units-but-not-enabled",
|
||||
"test": VerifyMaintenance,
|
||||
"eos_data": [
|
||||
{
|
||||
"units": {
|
||||
"mlag": {
|
||||
"state": "active",
|
||||
"adminState": "active",
|
||||
"stateChangeTime": 0.0,
|
||||
"onBootMaintenance": False,
|
||||
"intfsViolatingTrafficThreshold": False,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
},
|
||||
"System": {
|
||||
"state": "active",
|
||||
"adminState": "active",
|
||||
"stateChangeTime": 0.0,
|
||||
"onBootMaintenance": False,
|
||||
"intfsViolatingTrafficThreshold": False,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
},
|
||||
},
|
||||
"interfaces": {},
|
||||
"vrfs": {},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-maintenance-enabled",
|
||||
"test": VerifyMaintenance,
|
||||
"eos_data": [
|
||||
{
|
||||
"units": {
|
||||
"mlag": {
|
||||
"state": "underMaintenance",
|
||||
"adminState": "underMaintenance",
|
||||
"stateChangeTime": 1741257120.9532886,
|
||||
"onBootMaintenance": False,
|
||||
"intfsViolatingTrafficThreshold": False,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
},
|
||||
"System": {
|
||||
"state": "active",
|
||||
"adminState": "active",
|
||||
"stateChangeTime": 0.0,
|
||||
"onBootMaintenance": False,
|
||||
"intfsViolatingTrafficThreshold": False,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
},
|
||||
},
|
||||
"interfaces": {},
|
||||
"vrfs": {},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Units under maintenance: 'mlag'.",
|
||||
"Possible causes: 'Quiesce is configured'.",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-multiple-reasons",
|
||||
"test": VerifyMaintenance,
|
||||
"eos_data": [
|
||||
{
|
||||
"units": {
|
||||
"mlag": {
|
||||
"state": "underMaintenance",
|
||||
"adminState": "underMaintenance",
|
||||
"stateChangeTime": 1741257120.9532895,
|
||||
"onBootMaintenance": False,
|
||||
"intfsViolatingTrafficThreshold": False,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
},
|
||||
"System": {
|
||||
"state": "maintenanceModeEnter",
|
||||
"adminState": "underMaintenance",
|
||||
"stateChangeTime": 1741257669.7231765,
|
||||
"onBootMaintenance": False,
|
||||
"intfsViolatingTrafficThreshold": False,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
},
|
||||
},
|
||||
"interfaces": {},
|
||||
"vrfs": {},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Units under maintenance: 'mlag'.",
|
||||
"Units entering maintenance: 'System'.",
|
||||
"Possible causes: 'Quiesce is configured'.",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-onboot-maintenance",
|
||||
"test": VerifyMaintenance,
|
||||
"eos_data": [
|
||||
{
|
||||
"units": {
|
||||
"System": {
|
||||
"state": "underMaintenance",
|
||||
"adminState": "underMaintenance",
|
||||
"stateChangeTime": 1741258774.3756502,
|
||||
"onBootMaintenance": True,
|
||||
"intfsViolatingTrafficThreshold": False,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
}
|
||||
},
|
||||
"interfaces": {},
|
||||
"vrfs": {},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Units under maintenance: 'System'.",
|
||||
"Possible causes: 'On-boot maintenance is configured, Quiesce is configured'.",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-entering-maintenance-interface-violation",
|
||||
"test": VerifyMaintenance,
|
||||
"eos_data": [
|
||||
{
|
||||
"units": {
|
||||
"System": {
|
||||
"state": "maintenanceModeEnter",
|
||||
"adminState": "underMaintenance",
|
||||
"stateChangeTime": 1741257669.7231765,
|
||||
"onBootMaintenance": False,
|
||||
"intfsViolatingTrafficThreshold": True,
|
||||
"aggInBpsRate": 0,
|
||||
"aggOutBpsRate": 0,
|
||||
}
|
||||
},
|
||||
"interfaces": {},
|
||||
"vrfs": {},
|
||||
},
|
||||
],
|
||||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Units entering maintenance: 'System'.",
|
||||
"Possible causes: 'Interface traffic threshold violation, Quiesce is configured'.",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.vlan.py."""
|
||||
|
@ -7,7 +7,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from anta.tests.vlan import VerifyVlanInternalPolicy
|
||||
from anta.tests.vlan import VerifyDynamicVlanSource, VerifyVlanInternalPolicy
|
||||
from tests.units.anta_tests import test
|
||||
|
||||
DATA: list[dict[str, Any]] = [
|
||||
|
@ -23,14 +23,70 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyVlanInternalPolicy,
|
||||
"eos_data": [{"policy": "descending", "startVlanId": 4094, "endVlanId": 1006}],
|
||||
"inputs": {"policy": "ascending", "start_vlan_id": 1006, "end_vlan_id": 4094},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Incorrect VLAN internal allocation policy configured - Expected: ascending Actual: descending"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-incorrect-start-end-id",
|
||||
"test": VerifyVlanInternalPolicy,
|
||||
"eos_data": [{"policy": "ascending", "startVlanId": 4094, "endVlanId": 1006}],
|
||||
"inputs": {"policy": "ascending", "start_vlan_id": 1006, "end_vlan_id": 4094},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The VLAN internal allocation policy is not configured properly:\n"
|
||||
"Expected `ascending` as the policy, but found `descending` instead.\n"
|
||||
"Expected `1006` as the startVlanId, but found `4094` instead.\n"
|
||||
"Expected `4094` as the endVlanId, but found `1006` instead."
|
||||
"VLAN internal allocation policy: ascending - Incorrect start VLAN id configured - Expected: 1006 Actual: 4094",
|
||||
"VLAN internal allocation policy: ascending - Incorrect end VLAN id configured - Expected: 4094 Actual: 1006",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"test": VerifyDynamicVlanSource,
|
||||
"eos_data": [{"dynamicVlans": {"evpn": {"vlanIds": [1199]}, "mlagsync": {"vlanIds": [1401]}, "vccbfd": {"vlanIds": [1501]}}}],
|
||||
"inputs": {"sources": ["evpn", "mlagsync"], "strict": False},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-dynamic-vlan-sources",
|
||||
"test": VerifyDynamicVlanSource,
|
||||
"eos_data": [{"dynamicVlans": {}}],
|
||||
"inputs": {"sources": ["evpn", "mlagsync"], "strict": False},
|
||||
"expected": {"result": "failure", "messages": ["Dynamic VLAN source(s) not found in configuration: evpn, mlagsync"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-dynamic-vlan-sources-mismatch",
|
||||
"test": VerifyDynamicVlanSource,
|
||||
"eos_data": [{"dynamicVlans": {"vccbfd": {"vlanIds": [1500]}, "mlagsync": {"vlanIds": [1501]}}}],
|
||||
"inputs": {"sources": ["evpn", "mlagsync"], "strict": False},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Dynamic VLAN source(s) not found in configuration: evpn"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "success-strict-mode",
|
||||
"test": VerifyDynamicVlanSource,
|
||||
"eos_data": [{"dynamicVlans": {"evpn": {"vlanIds": [1199]}, "mlagsync": {"vlanIds": [1502], "vccbfd": {"vlanIds": []}}}}],
|
||||
"inputs": {"sources": ["evpn", "mlagsync"], "strict": True},
|
||||
"expected": {"result": "success"},
|
||||
},
|
||||
{
|
||||
"name": "failure-all-sources-exact-match-additional-source-found",
|
||||
"test": VerifyDynamicVlanSource,
|
||||
"eos_data": [{"dynamicVlans": {"evpn": {"vlanIds": [1199]}, "mlagsync": {"vlanIds": [1500]}, "vccbfd": {"vlanIds": [1500]}}}],
|
||||
"inputs": {"sources": ["evpn", "mlagsync"], "strict": True},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Strict mode enabled: Unexpected sources have VLANs allocated: vccbfd"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "failure-all-sources-exact-match-expected-source-not-found",
|
||||
"test": VerifyDynamicVlanSource,
|
||||
"eos_data": [{"dynamicVlans": {"evpn": {"vlanIds": [1199]}, "mlagsync": {"vlanIds": []}}}],
|
||||
"inputs": {"sources": ["evpn", "mlagsync"], "strict": True},
|
||||
"expected": {"result": "failure", "messages": ["Dynamic VLAN source(s) exist but have no VLANs allocated: mlagsync"]},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.vxlan.py."""
|
||||
|
@ -23,28 +23,28 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyVxlan1Interface,
|
||||
"eos_data": [{"interfaceDescriptions": {"Loopback0": {"lineProtocolStatus": "up", "interfaceStatus": "up"}}}],
|
||||
"inputs": None,
|
||||
"expected": {"result": "skipped", "messages": ["Vxlan1 interface is not configured"]},
|
||||
"expected": {"result": "skipped", "messages": ["Interface: Vxlan1 - Not configured"]},
|
||||
},
|
||||
{
|
||||
"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"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Vxlan1 - Incorrect Line protocol status/Status - Expected: up/up Actual: down/up"]},
|
||||
},
|
||||
{
|
||||
"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"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Vxlan1 - Incorrect Line protocol status/Status - Expected: up/up Actual: up/down"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-down-down",
|
||||
"test": VerifyVxlan1Interface,
|
||||
"eos_data": [{"interfaceDescriptions": {"Vxlan1": {"lineProtocolStatus": "down", "interfaceStatus": "down"}}}],
|
||||
"inputs": None,
|
||||
"expected": {"result": "failure", "messages": ["Vxlan1 interface is down/down"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Vxlan1 - Incorrect Line protocol status/Status - Expected: up/up Actual: down/down"]},
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
|
@ -176,15 +176,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": None,
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"VXLAN config sanity check is not passing: {'localVtep': {'description': 'Local VTEP Configuration Check', "
|
||||
"'allCheckPass': False, 'detail': '', 'hasWarning': True, 'items': [{'name': 'Loopback IP Address', 'checkPass': True, "
|
||||
"'hasWarning': False, 'detail': ''}, {'name': 'VLAN-VNI Map', 'checkPass': False, 'hasWarning': False, 'detail': "
|
||||
"'No VLAN-VNI mapping in Vxlan1'}, {'name': 'Flood List', 'checkPass': False, 'hasWarning': True, 'detail': "
|
||||
"'No VXLAN VLANs in Vxlan1'}, {'name': 'Routing', 'checkPass': True, 'hasWarning': False, 'detail': ''}, {'name': "
|
||||
"'VNI VRF ACL', 'checkPass': True, 'hasWarning': False, 'detail': ''}, {'name': 'VRF-VNI Dynamic VLAN', 'checkPass': True, "
|
||||
"'hasWarning': False, 'detail': ''}, {'name': 'Decap VRF-VNI Map', 'checkPass': True, 'hasWarning': False, 'detail': ''}]}}",
|
||||
],
|
||||
"messages": ["Vxlan Category: localVtep - Config sanity check is not passing"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -228,7 +220,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"bindings": {10010: 10, 10020: 20, 500: 1199}},
|
||||
"expected": {"result": "failure", "messages": ["The following VNI(s) have no binding: ['10010']"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Vxlan1 VNI: 10010 - Binding not found"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-wrong-binding",
|
||||
|
@ -246,7 +238,7 @@ DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
],
|
||||
"inputs": {"bindings": {10020: 20, 500: 1199}},
|
||||
"expected": {"result": "failure", "messages": ["The following VNI(s) have the wrong VLAN binding: [{'10020': 30}]"]},
|
||||
"expected": {"result": "failure", "messages": ["Interface: Vxlan1 VNI: 10020 VLAN: 20 - Wrong VLAN binding - Actual: 30"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-and-wrong-binding",
|
||||
|
@ -266,7 +258,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"bindings": {10010: 10, 10020: 20, 500: 1199}},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["The following VNI(s) have no binding: ['10010']", "The following VNI(s) have the wrong VLAN binding: [{'10020': 30}]"],
|
||||
"messages": ["Interface: Vxlan1 VNI: 10010 - Binding not found", "Interface: Vxlan1 VNI: 10020 VLAN: 20 - Wrong VLAN binding - Actual: 30"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -288,21 +280,21 @@ DATA: list[dict[str, Any]] = [
|
|||
"test": VerifyVxlanVtep,
|
||||
"eos_data": [{"vteps": {}, "interfaces": {"Vxlan1": {"vteps": ["10.1.1.5", "10.1.1.6"]}}}],
|
||||
"inputs": {"vteps": ["10.1.1.5", "10.1.1.6", "10.1.1.7"]},
|
||||
"expected": {"result": "failure", "messages": ["The following VTEP peer(s) are missing from the Vxlan1 interface: ['10.1.1.7']"]},
|
||||
"expected": {"result": "failure", "messages": ["The following VTEP peer(s) are missing from the Vxlan1 interface: 10.1.1.7"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-vtep",
|
||||
"test": VerifyVxlanVtep,
|
||||
"eos_data": [{"vteps": {}, "interfaces": {"Vxlan1": {"vteps": []}}}],
|
||||
"inputs": {"vteps": ["10.1.1.5", "10.1.1.6"]},
|
||||
"expected": {"result": "failure", "messages": ["The following VTEP peer(s) are missing from the Vxlan1 interface: ['10.1.1.5', '10.1.1.6']"]},
|
||||
"expected": {"result": "failure", "messages": ["The following VTEP peer(s) are missing from the Vxlan1 interface: 10.1.1.5, 10.1.1.6"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-no-input-vtep",
|
||||
"test": VerifyVxlanVtep,
|
||||
"eos_data": [{"vteps": {}, "interfaces": {"Vxlan1": {"vteps": ["10.1.1.5"]}}}],
|
||||
"inputs": {"vteps": []},
|
||||
"expected": {"result": "failure", "messages": ["Unexpected VTEP peer(s) on Vxlan1 interface: ['10.1.1.5']"]},
|
||||
"expected": {"result": "failure", "messages": ["Unexpected VTEP peer(s) on Vxlan1 interface: 10.1.1.5"]},
|
||||
},
|
||||
{
|
||||
"name": "failure-missmatch",
|
||||
|
@ -312,8 +304,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following VTEP peer(s) are missing from the Vxlan1 interface: ['10.1.1.5']",
|
||||
"Unexpected VTEP peer(s) on Vxlan1 interface: ['10.1.1.7', '10.1.1.8']",
|
||||
"The following VTEP peer(s) are missing from the Vxlan1 interface: 10.1.1.5",
|
||||
"Unexpected VTEP peer(s) on Vxlan1 interface: 10.1.1.7, 10.1.1.8",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -345,7 +337,7 @@ DATA: list[dict[str, Any]] = [
|
|||
"inputs": {"source_interface": "lo1", "udp_port": 4789},
|
||||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": ["Source interface is not correct. Expected `Loopback1` as source interface but found `Loopback10` instead."],
|
||||
"messages": ["Interface: Vxlan1 - Incorrect Source interface - Expected: Loopback1 Actual: Loopback10"],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -356,8 +348,8 @@ DATA: list[dict[str, Any]] = [
|
|||
"expected": {
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Source interface is not correct. Expected `Loopback1` as source interface but found `Loopback10` instead.",
|
||||
"UDP port is not correct. Expected `4780` as UDP port but found `4789` instead.",
|
||||
"Interface: Vxlan1 - Incorrect Source interface - Expected: Loopback1 Actual: Loopback10",
|
||||
"Interface: Vxlan1 - Incorrect UDP port - Expected: 4780 Actual: 4789",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Unit tests for the asynceapi client package used by ANTA."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Fixtures for the asynceapi client package."""
|
||||
|
|
42
tests/units/asynceapi/test__constants.py
Normal file
42
tests/units/asynceapi/test__constants.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Unit tests for the asynceapi._constants module."""
|
||||
|
||||
import pytest
|
||||
|
||||
from asynceapi._constants import EapiCommandFormat
|
||||
|
||||
|
||||
class TestEapiCommandFormat:
|
||||
"""Test cases for the EapiCommandFormat enum."""
|
||||
|
||||
def test_enum_values(self) -> None:
|
||||
"""Test that the enum has the expected values."""
|
||||
assert EapiCommandFormat.JSON.value == "json"
|
||||
assert EapiCommandFormat.TEXT.value == "text"
|
||||
|
||||
def test_str_method(self) -> None:
|
||||
"""Test that the __str__ method returns the string value."""
|
||||
assert str(EapiCommandFormat.JSON) == "json"
|
||||
assert str(EapiCommandFormat.TEXT) == "text"
|
||||
|
||||
# Test in string formatting
|
||||
assert f"Format: {EapiCommandFormat.JSON}" == "Format: json"
|
||||
|
||||
def test_string_behavior(self) -> None:
|
||||
"""Test that the enum behaves like a string."""
|
||||
# String methods should work
|
||||
assert EapiCommandFormat.JSON.upper() == "JSON"
|
||||
|
||||
# String comparisons should work
|
||||
assert EapiCommandFormat.JSON == "json"
|
||||
assert EapiCommandFormat.TEXT == "text"
|
||||
|
||||
def test_enum_lookup(self) -> None:
|
||||
"""Test enum lookup by value."""
|
||||
assert EapiCommandFormat("json") is EapiCommandFormat.JSON
|
||||
assert EapiCommandFormat("text") is EapiCommandFormat.TEXT
|
||||
|
||||
with pytest.raises(ValueError, match="'invalid' is not a valid EapiCommandFormat"):
|
||||
EapiCommandFormat("invalid")
|
435
tests/units/asynceapi/test__models.py
Normal file
435
tests/units/asynceapi/test__models.py
Normal file
|
@ -0,0 +1,435 @@
|
|||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Unit tests for the asynceapi._models module."""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
|
||||
from asynceapi._constants import EapiCommandFormat
|
||||
from asynceapi._errors import EapiReponseError
|
||||
from asynceapi._models import EapiCommandResult, EapiRequest, EapiResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from asynceapi._types import EapiComplexCommand, EapiSimpleCommand
|
||||
|
||||
|
||||
class TestEapiRequest:
|
||||
"""Test cases for the EapiRequest class."""
|
||||
|
||||
def test_init_with_defaults(self) -> None:
|
||||
"""Test initialization with just required parameters."""
|
||||
commands: list[EapiSimpleCommand | EapiComplexCommand] = ["show version", "show interfaces"]
|
||||
req = EapiRequest(commands=commands)
|
||||
|
||||
# Check required attributes
|
||||
assert req.commands == commands
|
||||
|
||||
# Check default values
|
||||
assert req.version == "latest"
|
||||
assert req.format == EapiCommandFormat.JSON
|
||||
assert req.timestamps is False
|
||||
assert req.auto_complete is False
|
||||
assert req.expand_aliases is False
|
||||
assert req.stop_on_error is True
|
||||
|
||||
# Check that ID is generated as a UUID hex string
|
||||
try:
|
||||
UUID(str(req.id))
|
||||
is_valid_uuid = True
|
||||
except ValueError:
|
||||
is_valid_uuid = False
|
||||
assert is_valid_uuid
|
||||
|
||||
def test_init_with_custom_values(self) -> None:
|
||||
"""Test initialization with custom parameter values."""
|
||||
commands: list[EapiSimpleCommand | EapiComplexCommand] = [{"cmd": "enable", "input": "password"}, "show version"]
|
||||
req = EapiRequest(
|
||||
commands=commands,
|
||||
version=1,
|
||||
format=EapiCommandFormat.TEXT,
|
||||
timestamps=True,
|
||||
auto_complete=True,
|
||||
expand_aliases=True,
|
||||
stop_on_error=False,
|
||||
id="custom-id-123",
|
||||
)
|
||||
|
||||
# Check all attributes match expected values
|
||||
assert req.commands == commands
|
||||
assert req.version == 1
|
||||
assert req.format == EapiCommandFormat.TEXT
|
||||
assert req.timestamps is True
|
||||
assert req.auto_complete is True
|
||||
assert req.expand_aliases is True
|
||||
assert req.stop_on_error is False
|
||||
assert req.id == "custom-id-123"
|
||||
|
||||
def test_to_jsonrpc(self) -> None:
|
||||
"""Test conversion to JSON-RPC dictionary."""
|
||||
commands: list[EapiSimpleCommand | EapiComplexCommand] = ["show version", "show interfaces"]
|
||||
req = EapiRequest(commands=commands, version=1, format=EapiCommandFormat.TEXT, id="test-id-456")
|
||||
|
||||
jsonrpc = req.to_jsonrpc()
|
||||
|
||||
# Check that structure matches expected JSON-RPC format
|
||||
assert jsonrpc["jsonrpc"] == "2.0"
|
||||
assert jsonrpc["method"] == "runCmds"
|
||||
assert jsonrpc["id"] == "test-id-456"
|
||||
|
||||
# Check params matches our configuration
|
||||
params = jsonrpc["params"]
|
||||
assert params["version"] == 1
|
||||
assert params["cmds"] == commands
|
||||
assert params["format"] == EapiCommandFormat.TEXT
|
||||
assert params["timestamps"] is False
|
||||
assert params["autoComplete"] is False
|
||||
assert params["expandAliases"] is False
|
||||
assert params["stopOnError"] is True
|
||||
|
||||
def test_to_jsonrpc_with_complex_commands(self) -> None:
|
||||
"""Test JSON-RPC conversion with complex commands."""
|
||||
commands: list[EapiSimpleCommand | EapiComplexCommand] = [
|
||||
{"cmd": "enable", "input": "password"},
|
||||
{"cmd": "configure", "input": ""},
|
||||
{"cmd": "hostname test-device"},
|
||||
]
|
||||
req = EapiRequest(commands=commands)
|
||||
|
||||
jsonrpc = req.to_jsonrpc()
|
||||
|
||||
# Verify commands are passed correctly
|
||||
assert jsonrpc["params"]["cmds"] == commands
|
||||
|
||||
def test_immutability(self) -> None:
|
||||
"""Test that the dataclass is truly immutable (frozen)."""
|
||||
commands: list[EapiSimpleCommand | EapiComplexCommand] = ["show version"]
|
||||
req = EapiRequest(commands=commands)
|
||||
|
||||
# Attempting to modify any attribute should raise an error
|
||||
with pytest.raises(AttributeError):
|
||||
req.commands = ["new command"] # type: ignore[misc]
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
req.id = "new-id" # type: ignore[misc]
|
||||
|
||||
|
||||
class TestEapiResponse:
|
||||
"""Test cases for the EapiResponse class."""
|
||||
|
||||
def test_init_and_properties(self) -> None:
|
||||
"""Test basic initialization and properties."""
|
||||
# Create mock command results
|
||||
result1 = EapiCommandResult(command="show version", output={"version": "4.33.2F-40713977.4332F (engineering build)"})
|
||||
result2 = EapiCommandResult(command="show hostname", output={"hostname": "DC1-LEAF1A"})
|
||||
|
||||
# Create response with results
|
||||
results = {0: result1, 1: result2}
|
||||
response = EapiResponse(request_id="test-123", _results=results)
|
||||
|
||||
# Check attributes
|
||||
assert response.request_id == "test-123"
|
||||
assert response.error_code is None
|
||||
assert response.error_message is None
|
||||
|
||||
# Check properties
|
||||
assert response.success is True
|
||||
assert len(response.results) == 2
|
||||
assert response.results[0] == result1
|
||||
assert response.results[1] == result2
|
||||
|
||||
def test_error_response(self) -> None:
|
||||
"""Test initialization with error information."""
|
||||
result = EapiCommandResult(command="show bad command", output=None, errors=["Invalid input (at token 1: 'bad')"], success=False)
|
||||
results = {0: result}
|
||||
response = EapiResponse(
|
||||
request_id="test-456", _results=results, error_code=1002, error_message="CLI command 1 of 1 'show bad command' failed: invalid command"
|
||||
)
|
||||
|
||||
assert response.request_id == "test-456"
|
||||
assert response.error_code == 1002
|
||||
assert response.error_message == "CLI command 1 of 1 'show bad command' failed: invalid command"
|
||||
assert response.success is False
|
||||
assert len(response.results) == 1
|
||||
assert response.results[0].success is False
|
||||
assert "Invalid input (at token 1: 'bad')" in response.results[0].errors
|
||||
|
||||
def test_len_and_iteration(self) -> None:
|
||||
"""Test __len__ and __iter__ methods."""
|
||||
# Create 3 command results
|
||||
results = {
|
||||
0: EapiCommandResult(command="cmd1", output="out1"),
|
||||
1: EapiCommandResult(command="cmd2", output="out2"),
|
||||
2: EapiCommandResult(command="cmd3", output="out3"),
|
||||
}
|
||||
response = EapiResponse(request_id="test-789", _results=results)
|
||||
|
||||
# Test __len__
|
||||
assert len(response) == 3
|
||||
|
||||
# Test __iter__
|
||||
iterated_results = list(response)
|
||||
assert len(iterated_results) == 3
|
||||
assert [r.command for r in iterated_results] == ["cmd1", "cmd2", "cmd3"]
|
||||
|
||||
def test_from_jsonrpc_success(self) -> None:
|
||||
"""Test from_jsonrpc with successful response."""
|
||||
# Mock request
|
||||
request = EapiRequest(commands=["show version", "show hostname"], format=EapiCommandFormat.JSON)
|
||||
|
||||
# Mock response data
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "test-id-123",
|
||||
"result": [{"modelName": "cEOSLab", "version": "4.33.2F-40713977.4332F (engineering build)"}, {"hostname": "DC1-LEAF1A"}],
|
||||
}
|
||||
|
||||
response = EapiResponse.from_jsonrpc(jsonrpc_response, request)
|
||||
|
||||
# Verify response object
|
||||
assert response.request_id == "test-id-123"
|
||||
assert response.success is True
|
||||
assert response.error_code is None
|
||||
assert response.error_message is None
|
||||
|
||||
# Verify results
|
||||
assert len(response) == 2
|
||||
assert response.results[0].command == "show version"
|
||||
assert response.results[0].output == {"modelName": "cEOSLab", "version": "4.33.2F-40713977.4332F (engineering build)"}
|
||||
assert response.results[0].success is True
|
||||
assert response.results[1].command == "show hostname"
|
||||
assert response.results[1].output == {"hostname": "DC1-LEAF1A"}
|
||||
assert response.results[1].success is True
|
||||
|
||||
def test_from_jsonrpc_text_format(self) -> None:
|
||||
"""Test from_jsonrpc with TEXT format responses."""
|
||||
# Mock request with TEXT format
|
||||
request = EapiRequest(commands=["show version", "show hostname"], format=EapiCommandFormat.TEXT)
|
||||
|
||||
# Mock response data
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "text-format-id",
|
||||
"result": [{"output": "Arista cEOSLab\n\nSoftware image version: 4.33.2F-40713977.4332F"}, {"output": "Hostname: DC1-LEAF1A\nFQDN: DC1-LEAF1A\n"}],
|
||||
}
|
||||
|
||||
response = EapiResponse.from_jsonrpc(jsonrpc_response, request)
|
||||
|
||||
# Verify results contain the text output
|
||||
assert len(response) == 2
|
||||
assert response.results[0].output is not None
|
||||
assert "Arista cEOSLab" in response.results[0].output
|
||||
assert response.results[1].output is not None
|
||||
assert "Hostname: DC1-LEAF1A" in response.results[1].output
|
||||
|
||||
def test_from_jsonrpc_with_timestamps(self) -> None:
|
||||
"""Test from_jsonrpc with timestamps enabled."""
|
||||
# Mock request with timestamps
|
||||
request = EapiRequest(commands=["show version"], format=EapiCommandFormat.JSON, timestamps=True)
|
||||
|
||||
# Mock response data with timestamps
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "timestamp-id",
|
||||
"result": [
|
||||
{
|
||||
"modelName": "cEOSLab",
|
||||
"version": "4.33.2F-40713977.4332F (engineering build)",
|
||||
"_meta": {"execStartTime": 1741014072.2534037, "execDuration": 0.0024061203002929688},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
response = EapiResponse.from_jsonrpc(jsonrpc_response, request)
|
||||
|
||||
# Verify timestamp data is processed
|
||||
assert len(response) == 1
|
||||
assert response.results[0].start_time == 1741014072.2534037
|
||||
assert response.results[0].duration == 0.0024061203002929688
|
||||
|
||||
# Verify _meta is removed from output
|
||||
assert response.results[0].output is not None
|
||||
assert "_meta" not in response.results[0].output
|
||||
|
||||
def test_from_jsonrpc_error_stop_on_error_true(self) -> None:
|
||||
"""Test from_jsonrpc with error and stop_on_error=True."""
|
||||
# Mock request with stop_on_error=True
|
||||
request = EapiRequest(commands=["show bad command", "show version", "show hostname"], stop_on_error=True)
|
||||
|
||||
# Mock error response
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "error-id",
|
||||
"error": {
|
||||
"code": 1002,
|
||||
"message": "CLI command 1 of 3 'show bad command' failed: invalid command",
|
||||
"data": [{"errors": ["Invalid input (at token 1: 'bad')"]}],
|
||||
},
|
||||
}
|
||||
|
||||
response = EapiResponse.from_jsonrpc(jsonrpc_response, request)
|
||||
|
||||
# Verify error info
|
||||
assert response.request_id == "error-id"
|
||||
assert response.error_code == 1002
|
||||
assert response.error_message == "CLI command 1 of 3 'show bad command' failed: invalid command"
|
||||
assert response.success is False
|
||||
|
||||
# Verify results - should have entries for all commands
|
||||
assert len(response) == 3
|
||||
|
||||
# First command failed
|
||||
assert response.results[0].command == "show bad command"
|
||||
assert response.results[0].output is None
|
||||
assert response.results[0].success is False
|
||||
assert response.results[0].errors == ["Invalid input (at token 1: 'bad')"]
|
||||
|
||||
# Remaining commands weren't executed due to stop_on_error=True
|
||||
assert response.results[1].command == "show version"
|
||||
assert response.results[1].output is None
|
||||
assert response.results[1].success is False
|
||||
assert "Command not executed due to previous error" in response.results[1].errors
|
||||
assert response.results[1].executed is False
|
||||
|
||||
assert response.results[2].command == "show hostname"
|
||||
assert response.results[2].output is None
|
||||
assert response.results[2].success is False
|
||||
assert "Command not executed due to previous error" in response.results[2].errors
|
||||
assert response.results[2].executed is False
|
||||
|
||||
def test_from_jsonrpc_error_stop_on_error_false(self) -> None:
|
||||
"""Test from_jsonrpc with error and stop_on_error=False."""
|
||||
# Mock request with stop_on_error=False
|
||||
request = EapiRequest(commands=["show bad command", "show version", "show hostname"], stop_on_error=False)
|
||||
|
||||
# Mock response with error for first command but others succeed
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "error-continue-id",
|
||||
"error": {
|
||||
"code": 1002,
|
||||
"message": "CLI command 1 of 3 'show bad command' failed: invalid command",
|
||||
"data": [
|
||||
{"errors": ["Invalid input (at token 1: 'bad')"]},
|
||||
{"modelName": "cEOSLab", "version": "4.33.2F-40713977.4332F (engineering build)"},
|
||||
{"hostname": "DC1-LEAF1A"},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
response = EapiResponse.from_jsonrpc(jsonrpc_response, request)
|
||||
|
||||
# Verify error info
|
||||
assert response.request_id == "error-continue-id"
|
||||
assert response.error_code == 1002
|
||||
assert response.error_message == "CLI command 1 of 3 'show bad command' failed: invalid command"
|
||||
assert response.success is False
|
||||
|
||||
# Verify individual command results
|
||||
assert len(response) == 3
|
||||
|
||||
# First command failed
|
||||
assert response.results[0].command == "show bad command"
|
||||
assert response.results[0].output is None
|
||||
assert response.results[0].success is False
|
||||
assert response.results[0].errors == ["Invalid input (at token 1: 'bad')"]
|
||||
|
||||
# Remaining commands succeeded
|
||||
assert response.results[1].command == "show version"
|
||||
assert response.results[1].output == {"modelName": "cEOSLab", "version": "4.33.2F-40713977.4332F (engineering build)"}
|
||||
assert response.results[1].success is True
|
||||
|
||||
assert response.results[2].command == "show hostname"
|
||||
assert response.results[2].output == {"hostname": "DC1-LEAF1A"}
|
||||
assert response.results[2].success is True
|
||||
|
||||
def test_from_jsonrpc_raise_on_error(self) -> None:
|
||||
"""Test from_jsonrpc with raise_on_error=True."""
|
||||
# Mock request
|
||||
request = EapiRequest(commands=["show bad command"])
|
||||
|
||||
# Mock error response
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "raise-error-id",
|
||||
"error": {
|
||||
"code": 1002,
|
||||
"message": "CLI command 1 of 1 'show bad command' failed: invalid command",
|
||||
"data": [{"errors": ["Invalid input (at token 1: 'bad')"]}],
|
||||
},
|
||||
}
|
||||
|
||||
# Should raise EapiReponseError
|
||||
with pytest.raises(EapiReponseError) as excinfo:
|
||||
EapiResponse.from_jsonrpc(jsonrpc_response, request, raise_on_error=True)
|
||||
|
||||
# Verify the exception contains the response
|
||||
assert excinfo.value.response.request_id == "raise-error-id"
|
||||
assert excinfo.value.response.error_code == 1002
|
||||
assert excinfo.value.response.error_message == "CLI command 1 of 1 'show bad command' failed: invalid command"
|
||||
|
||||
def test_from_jsonrpc_string_data(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test from_jsonrpc with string data response."""
|
||||
caplog.set_level(logging.WARNING)
|
||||
|
||||
# Mock request
|
||||
request = EapiRequest(commands=["show bgp ipv4 unicast summary", "show bad command"])
|
||||
|
||||
# Mock response with JSON string
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "EapiExplorer-1",
|
||||
"error": {
|
||||
"code": 1002,
|
||||
"message": "CLI command 2 of 2 'show bad command' failed: invalid command",
|
||||
"data": [
|
||||
'{"vrfs":{"default":{"vrf":"default","routerId":"10.1.0.11","asn":"65101","peers":{}}}}\n',
|
||||
{"errors": ["Invalid input (at token 1: 'bad')"]},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
response = EapiResponse.from_jsonrpc(jsonrpc_response, request)
|
||||
|
||||
# Verify string was parsed as JSON
|
||||
assert response.results[0].output == {"vrfs": {"default": {"vrf": "default", "routerId": "10.1.0.11", "asn": "65101", "peers": {}}}}
|
||||
|
||||
# Now test with a non-JSON string
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "EapiExplorer-1",
|
||||
"error": {
|
||||
"code": 1002,
|
||||
"message": "CLI command 2 of 2 'show bad command' failed: invalid command",
|
||||
"data": ["This is not JSON", {"errors": ["Invalid input (at token 1: 'bad')"]}],
|
||||
},
|
||||
}
|
||||
|
||||
response = EapiResponse.from_jsonrpc(jsonrpc_response, request)
|
||||
|
||||
# Verify WARNING log message
|
||||
assert "Invalid JSON response for command: show bgp ipv4 unicast summary. Storing as text: This is not JSON" in caplog.text
|
||||
|
||||
# Verify string is kept as is
|
||||
assert response.results[0].output == "This is not JSON"
|
||||
|
||||
def test_from_jsonrpc_complex_commands(self) -> None:
|
||||
"""Test from_jsonrpc with complex command structures."""
|
||||
# Mock request with complex commands
|
||||
request = EapiRequest(commands=[{"cmd": "enable", "input": "password"}, "show version"])
|
||||
|
||||
# Mock response
|
||||
jsonrpc_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "complex-cmd-id",
|
||||
"result": [{}, {"modelName": "cEOSLab", "version": "4.33.2F-40713977.4332F (engineering build)"}],
|
||||
}
|
||||
|
||||
response = EapiResponse.from_jsonrpc(jsonrpc_response, request)
|
||||
|
||||
# Verify command strings are extracted correctly
|
||||
assert response.results[0].command == "enable"
|
||||
assert response.results[1].command == "show version"
|
|
@ -1,9 +1,12 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Unit tests data for the asynceapi client package."""
|
||||
|
||||
SUCCESS_EAPI_RESPONSE = {
|
||||
from asynceapi._constants import EapiCommandFormat
|
||||
from asynceapi._types import EapiJsonOutput, JsonRpc
|
||||
|
||||
SUCCESS_EAPI_RESPONSE: EapiJsonOutput = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "EapiExplorer-1",
|
||||
"result": [
|
||||
|
@ -49,7 +52,7 @@ SUCCESS_EAPI_RESPONSE = {
|
|||
}
|
||||
"""Successful eAPI JSON response."""
|
||||
|
||||
ERROR_EAPI_RESPONSE = {
|
||||
ERROR_EAPI_RESPONSE: EapiJsonOutput = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "EapiExplorer-1",
|
||||
"error": {
|
||||
|
@ -84,5 +87,10 @@ ERROR_EAPI_RESPONSE = {
|
|||
}
|
||||
"""Error eAPI JSON response."""
|
||||
|
||||
JSONRPC_REQUEST_TEMPLATE = {"jsonrpc": "2.0", "method": "runCmds", "params": {"version": 1, "cmds": [], "format": "json"}, "id": "EapiExplorer-1"}
|
||||
JSONRPC_REQUEST_TEMPLATE: JsonRpc = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "runCmds",
|
||||
"params": {"version": 1, "cmds": [], "format": EapiCommandFormat.JSON},
|
||||
"id": "EapiExplorer-1",
|
||||
}
|
||||
"""Template for JSON-RPC eAPI request. `cmds` must be filled by the parametrize decorator."""
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Unit tests the asynceapi.device module."""
|
||||
"""Unit tests for the asynceapi.device module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from httpx import HTTPStatusError
|
||||
|
@ -17,6 +17,8 @@ from .test_data import ERROR_EAPI_RESPONSE, JSONRPC_REQUEST_TEMPLATE, SUCCESS_EA
|
|||
if TYPE_CHECKING:
|
||||
from pytest_httpx import HTTPXMock
|
||||
|
||||
from asynceapi._types import EapiComplexCommand, EapiSimpleCommand
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cmds",
|
||||
|
@ -30,10 +32,10 @@ if TYPE_CHECKING:
|
|||
async def test_jsonrpc_exec_success(
|
||||
asynceapi_device: Device,
|
||||
httpx_mock: HTTPXMock,
|
||||
cmds: list[str | dict[str, Any]],
|
||||
cmds: list[EapiSimpleCommand | EapiComplexCommand],
|
||||
) -> None:
|
||||
"""Test the Device.jsonrpc_exec method with a successful response. Simple and complex commands are tested."""
|
||||
jsonrpc_request: dict[str, Any] = JSONRPC_REQUEST_TEMPLATE.copy()
|
||||
jsonrpc_request = JSONRPC_REQUEST_TEMPLATE.copy()
|
||||
jsonrpc_request["params"]["cmds"] = cmds
|
||||
|
||||
httpx_mock.add_response(json=SUCCESS_EAPI_RESPONSE)
|
||||
|
@ -55,19 +57,18 @@ async def test_jsonrpc_exec_success(
|
|||
async def test_jsonrpc_exec_eapi_command_error(
|
||||
asynceapi_device: Device,
|
||||
httpx_mock: HTTPXMock,
|
||||
cmds: list[str | dict[str, Any]],
|
||||
cmds: list[EapiSimpleCommand | EapiComplexCommand],
|
||||
) -> None:
|
||||
"""Test the Device.jsonrpc_exec method with an error response. Simple and complex commands are tested."""
|
||||
jsonrpc_request: dict[str, Any] = JSONRPC_REQUEST_TEMPLATE.copy()
|
||||
jsonrpc_request = JSONRPC_REQUEST_TEMPLATE.copy()
|
||||
jsonrpc_request["params"]["cmds"] = cmds
|
||||
|
||||
error_eapi_response: dict[str, Any] = ERROR_EAPI_RESPONSE.copy()
|
||||
httpx_mock.add_response(json=error_eapi_response)
|
||||
httpx_mock.add_response(json=ERROR_EAPI_RESPONSE)
|
||||
|
||||
with pytest.raises(EapiCommandError) as exc_info:
|
||||
await asynceapi_device.jsonrpc_exec(jsonrpc=jsonrpc_request)
|
||||
|
||||
assert exc_info.value.passed == [error_eapi_response["error"]["data"][0]]
|
||||
assert exc_info.value.passed == [ERROR_EAPI_RESPONSE["error"]["data"][0]]
|
||||
assert exc_info.value.failed == "bad command"
|
||||
assert exc_info.value.errors == ["Invalid input (at token 1: 'bad')"]
|
||||
assert exc_info.value.errmsg == "CLI command 2 of 3 'bad command' failed: invalid command"
|
||||
|
@ -76,7 +77,7 @@ async def test_jsonrpc_exec_eapi_command_error(
|
|||
|
||||
async def test_jsonrpc_exec_http_status_error(asynceapi_device: Device, httpx_mock: HTTPXMock) -> None:
|
||||
"""Test the Device.jsonrpc_exec method with an HTTPStatusError."""
|
||||
jsonrpc_request: dict[str, Any] = JSONRPC_REQUEST_TEMPLATE.copy()
|
||||
jsonrpc_request = JSONRPC_REQUEST_TEMPLATE.copy()
|
||||
jsonrpc_request["params"]["cmds"] = ["show version"]
|
||||
|
||||
httpx_mock.add_response(status_code=500, text="Internal Server Error")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.cli submodule."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.cli.check submodule."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.check."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.check.commands."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
@ -39,7 +39,7 @@ MOCK_CLI_JSON: dict[str, asynceapi.EapiCommandError | dict[str, Any]] = {
|
|||
errmsg="Invalid command",
|
||||
not_exec=[],
|
||||
),
|
||||
"show interfaces": {},
|
||||
"show interfaces": {"interfaces": {}},
|
||||
}
|
||||
|
||||
MOCK_CLI_TEXT: dict[str, asynceapi.EapiCommandError | str] = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.cli.debug submodule."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.debug."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.debug.commands."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.cli.exec submodule."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.exec."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.exec.commands."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.exec.utils."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.cli.get submodule."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2024-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Module used for test purposes."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.get."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.get.commands."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.get.utils."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.cli.nrfu submodule."""
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.nrfu."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from anta.cli import anta
|
||||
from anta.cli.utils import ExitCode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
DATA_DIR: Path = Path(__file__).parents[3].resolve() / "data"
|
||||
|
||||
# TODO: write unit tests for ignore-status and ignore-error
|
||||
|
||||
|
||||
|
@ -123,3 +127,19 @@ def test_hide(click_runner: CliRunner) -> None:
|
|||
"""Test the `--hide` option of the `anta nrfu` command."""
|
||||
result = click_runner.invoke(anta, ["nrfu", "--hide", "success", "text"])
|
||||
assert "SUCCESS" not in result.output
|
||||
|
||||
|
||||
def test_invalid_inventory(caplog: pytest.LogCaptureFixture, click_runner: CliRunner) -> None:
|
||||
"""Test invalid inventory."""
|
||||
result = click_runner.invoke(anta, ["nrfu", "--inventory", str(DATA_DIR / "invalid_inventory.yml")])
|
||||
assert "CRITICAL" in caplog.text
|
||||
assert "Failed to parse the inventory" in caplog.text
|
||||
assert result.exit_code == ExitCode.USAGE_ERROR
|
||||
|
||||
|
||||
def test_invalid_catalog(caplog: pytest.LogCaptureFixture, click_runner: CliRunner) -> None:
|
||||
"""Test invalid catalog."""
|
||||
result = click_runner.invoke(anta, ["nrfu", "--catalog", str(DATA_DIR / "test_catalog_not_a_list.yml")])
|
||||
assert "CRITICAL" in caplog.text
|
||||
assert "Failed to parse the catalog" in caplog.text
|
||||
assert result.exit_code == ExitCode.USAGE_ERROR
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli.nrfu.commands."""
|
||||
|
@ -82,9 +82,9 @@ def test_anta_nrfu_text_multiple_failures(click_runner: CliRunner) -> None:
|
|||
assert result.exit_code == ExitCode.TESTS_FAILED
|
||||
assert (
|
||||
"""spine1 :: VerifyInterfacesSpeed :: FAILURE
|
||||
Interface `Ethernet2` is not found.
|
||||
Interface `Ethernet3` is not found.
|
||||
Interface `Ethernet4` is not found."""
|
||||
Interface: Ethernet2 - Not found
|
||||
Interface: Ethernet3 - Not found
|
||||
Interface: Ethernet4 - Not found"""
|
||||
in result.output
|
||||
)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli._main."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.cli._main."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models module."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test for anta.input_models.routing submodule."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models.routing.bgp.py."""
|
||||
|
@ -6,24 +6,29 @@
|
|||
# pylint: disable=C0302
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.input_models.routing.bgp import BgpAddressFamily, BgpPeer
|
||||
from anta.input_models.routing.bgp import AddressFamilyConfig, BgpAddressFamily, BgpPeer, BgpRoute, RedistributedRouteConfig
|
||||
from anta.tests.routing.bgp import (
|
||||
VerifyBGPExchangedRoutes,
|
||||
VerifyBGPNlriAcceptance,
|
||||
VerifyBGPPeerCount,
|
||||
VerifyBGPPeerGroup,
|
||||
VerifyBGPPeerMPCaps,
|
||||
VerifyBGPPeerRouteLimit,
|
||||
VerifyBGPPeerTtlMultiHops,
|
||||
VerifyBGPRouteECMP,
|
||||
VerifyBgpRouteMaps,
|
||||
VerifyBGPRoutePaths,
|
||||
VerifyBGPSpecificPeers,
|
||||
VerifyBGPTimers,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anta.custom_types import Afi, Safi
|
||||
from anta.custom_types import Afi, RedistributedAfiSafi, RedistributedProtocol, Safi
|
||||
|
||||
|
||||
class TestBgpAddressFamily:
|
||||
|
@ -116,6 +121,7 @@ class TestVerifyBGPExchangedRoutesInput:
|
|||
[{"peer_address": "172.30.255.5", "vrf": "default", "advertised_routes": ["192.0.254.5/32"], "received_routes": ["192.0.255.4/32"]}],
|
||||
id="valid_both_received_advertised",
|
||||
),
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "advertised_routes": ["192.0.254.5/32"]}], id="valid_advertised_routes"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, bgp_peers: list[BgpPeer]) -> None:
|
||||
|
@ -126,8 +132,6 @@ class TestVerifyBGPExchangedRoutesInput:
|
|||
("bgp_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default"}], id="invalid"),
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "advertised_routes": ["192.0.254.5/32"]}], id="invalid_received_route"),
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "received_routes": ["192.0.254.5/32"]}], id="invalid_advertised_route"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, bgp_peers: list[BgpPeer]) -> None:
|
||||
|
@ -236,3 +240,271 @@ class TestVerifyBGPPeerRouteLimitInput:
|
|||
"""Test VerifyBGPPeerRouteLimit.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyBGPPeerRouteLimit.Input(bgp_peers=bgp_peers)
|
||||
|
||||
|
||||
class TestVerifyBGPPeerGroupInput:
|
||||
"""Test anta.tests.routing.bgp.VerifyBGPPeerGroup.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bgp_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "peer_group": "IPv4-UNDERLAY-PEERS"}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, bgp_peers: list[BgpPeer]) -> None:
|
||||
"""Test VerifyBGPPeerGroup.Input valid inputs."""
|
||||
VerifyBGPPeerGroup.Input(bgp_peers=bgp_peers)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bgp_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, bgp_peers: list[BgpPeer]) -> None:
|
||||
"""Test VerifyBGPPeerGroup.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyBGPPeerGroup.Input(bgp_peers=bgp_peers)
|
||||
|
||||
|
||||
class TestVerifyBGPNlriAcceptanceInput:
|
||||
"""Test anta.tests.routing.bgp.VerifyBGPNlriAcceptance.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bgp_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "capabilities": ["ipv4Unicast"]}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, bgp_peers: list[BgpPeer]) -> None:
|
||||
"""Test VerifyBGPNlriAcceptance.Input valid inputs."""
|
||||
VerifyBGPNlriAcceptance.Input(bgp_peers=bgp_peers)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bgp_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, bgp_peers: list[BgpPeer]) -> None:
|
||||
"""Test VerifyBGPNlriAcceptance.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyBGPNlriAcceptance.Input(bgp_peers=bgp_peers)
|
||||
|
||||
|
||||
class TestVerifyBGPRouteECMPInput:
|
||||
"""Test anta.tests.routing.bgp.VerifyBGPRouteECMP.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bgp_routes"),
|
||||
[
|
||||
pytest.param([{"prefix": "10.100.0.128/31", "vrf": "default", "ecmp_count": 2}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, bgp_routes: list[BgpRoute]) -> None:
|
||||
"""Test VerifyBGPRouteECMP.Input valid inputs."""
|
||||
VerifyBGPRouteECMP.Input(route_entries=bgp_routes)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bgp_routes"),
|
||||
[
|
||||
pytest.param([{"prefix": "10.100.0.128/31", "vrf": "default"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, bgp_routes: list[BgpRoute]) -> None:
|
||||
"""Test VerifyBGPRouteECMP.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyBGPRouteECMP.Input(route_entries=bgp_routes)
|
||||
|
||||
|
||||
class TestVerifyBGPRoutePathsInput:
|
||||
"""Test anta.tests.routing.bgp.VerifyBGPRoutePaths.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("route_entries"),
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"prefix": "10.100.0.128/31",
|
||||
"vrf": "default",
|
||||
"paths": [{"nexthop": "10.100.0.10", "origin": "Igp"}, {"nexthop": "10.100.4.5", "origin": "Incomplete"}],
|
||||
}
|
||||
],
|
||||
id="valid",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_valid(self, route_entries: list[BgpRoute]) -> None:
|
||||
"""Test VerifyBGPRoutePaths.Input valid inputs."""
|
||||
VerifyBGPRoutePaths.Input(route_entries=route_entries)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("route_entries"),
|
||||
[
|
||||
pytest.param([{"prefix": "10.100.0.128/31", "vrf": "default"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, route_entries: list[BgpRoute]) -> None:
|
||||
"""Test VerifyBGPRoutePaths.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyBGPRoutePaths.Input(route_entries=route_entries)
|
||||
|
||||
|
||||
class TestVerifyBGPRedistributedRoute:
|
||||
"""Test anta.input_models.routing.bgp.RedistributedRouteConfig."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("proto", "include_leaked"),
|
||||
[
|
||||
pytest.param("Connected", True, id="proto-valid"),
|
||||
pytest.param("Static", False, id="proto-valid-leaked-false"),
|
||||
pytest.param("User", False, id="proto-User"),
|
||||
],
|
||||
)
|
||||
def test_validate_inputs(self, proto: RedistributedProtocol, include_leaked: bool) -> None:
|
||||
"""Test RedistributedRouteConfig valid inputs."""
|
||||
RedistributedRouteConfig(proto=proto, include_leaked=include_leaked)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("proto", "include_leaked"),
|
||||
[
|
||||
pytest.param("Dynamic", True, id="proto-valid"),
|
||||
pytest.param("User", True, id="proto-valid-leaked-false"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> None:
|
||||
"""Test RedistributedRouteConfig invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
RedistributedRouteConfig(proto=proto, include_leaked=include_leaked)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("proto", "include_leaked", "route_map", "expected"),
|
||||
[
|
||||
pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Include Leaked: True, Route Map: RM-CONN-2-BGP", id="check-all-params"),
|
||||
pytest.param("Static", False, None, "Proto: Static", id="check-proto-include_leaked-false"),
|
||||
pytest.param("User", False, "RM-CONN-2-BGP", "Proto: EOS SDK, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"),
|
||||
pytest.param("Dynamic", False, None, "Proto: Dynamic", id="check-proto-only"),
|
||||
],
|
||||
)
|
||||
def test_valid_str(self, proto: RedistributedProtocol, include_leaked: bool, route_map: str | None, expected: str) -> None:
|
||||
"""Test RedistributedRouteConfig __str__."""
|
||||
assert str(RedistributedRouteConfig(proto=proto, include_leaked=include_leaked, route_map=route_map)) == expected
|
||||
|
||||
|
||||
class TestVerifyBGPAddressFamilyConfig:
|
||||
"""Test anta.input_models.routing.bgp.AddressFamilyConfig."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("afi_safi", "redistributed_routes"),
|
||||
[
|
||||
pytest.param("ipv4Unicast", [{"proto": "OSPFv3 External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-unicast"),
|
||||
pytest.param("ipv6 Multicast", [{"proto": "OSPF Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-multicast"),
|
||||
pytest.param("ipv4-Multicast", [{"proto": "IS-IS", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-multicast"),
|
||||
pytest.param("ipv6_Unicast", [{"proto": "AttachedHost", "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-unicast"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None:
|
||||
"""Test AddressFamilyConfig valid inputs."""
|
||||
AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("afi_safi", "redistributed_routes"),
|
||||
[
|
||||
pytest.param("evpn", [{"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="invalid-address-family"),
|
||||
pytest.param("ipv6 sr-te", [{"proto": "RIP", "route_map": "RM-CONN-2-BGP"}], id="ipv6-invalid-address-family"),
|
||||
pytest.param("iipv6_Unicast", [{"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-unicast-invalid-address-family"),
|
||||
pytest.param("ipv6_Unicastt", [{"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-unicast-invalid-address-family"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None:
|
||||
"""Test AddressFamilyConfig invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("afi_safi", "redistributed_routes"),
|
||||
[
|
||||
pytest.param("ipv4Unicast", [{"proto": "OSPF External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-ospf-external"),
|
||||
pytest.param("ipv4 Unicast", [{"proto": "OSPF Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-ospf-internal"),
|
||||
pytest.param("ipv4-Unicast", [{"proto": "OSPF Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-ospf-nssa-external"),
|
||||
pytest.param("ipv4_Unicast", [{"proto": "OSPFv3 External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-ospfv3-external"),
|
||||
pytest.param("Ipv4Unicast", [{"proto": "OSPFv3 Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-ospfv3-internal"),
|
||||
pytest.param(
|
||||
"ipv4Unicast", [{"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-ospfv3-nssa-external"
|
||||
),
|
||||
pytest.param("ipv4unicast", [{"proto": "AttachedHost", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-attached-host"),
|
||||
pytest.param("IPv4UNiCast", [{"proto": "RIP", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-rip"),
|
||||
pytest.param("IPv4UnicasT", [{"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v4u-proto-bgp"),
|
||||
pytest.param("ipv6_Multicast", [{"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v6m-proto-static"),
|
||||
pytest.param("ipv6 Multicast", [{"proto": "OSPF Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v6m-proto-ospf-internal"),
|
||||
pytest.param("ipv6-Multicast", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v6m-proto-connected"),
|
||||
pytest.param("ipv4-Multicast", [{"proto": "IS-IS", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], id="v4m-proto-isis"),
|
||||
pytest.param("ipv4Multicast", [{"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="v4m-proto-connected"),
|
||||
pytest.param("ipv4_Multicast", [{"proto": "AttachedHost", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], id="v4m-proto-attached-host"),
|
||||
pytest.param("ipv6_Unicast", [{"proto": "AttachedHost", "route_map": "RM-CONN-2-BGP"}], id="v6u-proto-attached-host"),
|
||||
pytest.param("ipv6unicast", [{"proto": "DHCP", "route_map": "RM-CONN-2-BGP"}], id="v6u-proto-dhcp"),
|
||||
pytest.param("ipv6 Unicast", [{"proto": "Dynamic", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], id="v6u-proto-dynamic"),
|
||||
],
|
||||
)
|
||||
def test_validate_afi_safi_supported_routes(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None:
|
||||
"""Test AddressFamilyConfig validate afi-safi supported routes."""
|
||||
AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("afi_safi", "redistributed_routes"),
|
||||
[
|
||||
pytest.param("ipv6_Unicast", [{"proto": "RIP", "route_map": "RM-CONN-2-BGP"}], id="invalid-proto-ipv6-unicast-rip"),
|
||||
pytest.param("ipv6-Unicast", [{"proto": "OSPF Internal", "route_map": "RM-CONN-2-BGP"}], id="invalid-proto-ipv6-unicast-ospf-internal"),
|
||||
pytest.param("ipv4Unicast", [{"proto": "DHCP", "route_map": "RM-CONN-2-BGP"}], id="invalid-proto-ipv4-unicast-dhcp"),
|
||||
pytest.param("ipv4-Multicast", [{"proto": "Bgp", "route_map": "RM-CONN-2-BGP"}], id="invalid-proto-ipv4-multicast-bgp"),
|
||||
pytest.param("ipv4-Multicast", [{"proto": "RIP", "route_map": "RM-CONN-2-BGP"}], id="invalid-proto-ipv4-multicast-rip"),
|
||||
pytest.param("ipv6-Multicast", [{"proto": "Dynamic", "route_map": "RM-CONN-2-BGP"}], id="invalid-proto-ipv4-multicast-dynamic"),
|
||||
pytest.param("ipv6-Multicast", [{"proto": "AttachedHost", "route_map": "RM-CONN-2-BGP"}], id="invalid-proto-ipv6-multicast-attached-host"),
|
||||
],
|
||||
)
|
||||
def test_invalid_afi_safi_supported_routes(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None:
|
||||
"""Test AddressFamilyConfig invalid afi-safi supported routes."""
|
||||
with pytest.raises(ValidationError):
|
||||
AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("afi_safi", "redistributed_routes", "expected"),
|
||||
[
|
||||
pytest.param(
|
||||
"v4u", [{"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv4 Unicast", id="valid-ipv4-unicast"
|
||||
),
|
||||
pytest.param("v4m", [{"proto": "IS-IS", "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv4 Multicast", id="valid-ipv4-multicast"),
|
||||
pytest.param("v6u", [{"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv6 Unicast", id="valid-ipv6-unicast"),
|
||||
pytest.param("v6m", [{"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv6 Multicast", id="valid-ipv6-multicast"),
|
||||
],
|
||||
)
|
||||
def test_valid_str(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any], expected: str) -> None:
|
||||
"""Test AddressFamilyConfig __str__."""
|
||||
assert str(AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)) == expected
|
||||
|
||||
|
||||
class TestVerifyBGPPeerTtlMultiHopsInput:
|
||||
"""Test anta.tests.routing.bgp.VerifyBGPPeerTtlMultiHops.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bgp_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "ttl": 3, "max_ttl_hops": 3}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, bgp_peers: list[BgpPeer]) -> None:
|
||||
"""Test VerifyBGPPeerTtlMultiHops.Input valid inputs."""
|
||||
VerifyBGPPeerTtlMultiHops.Input(bgp_peers=bgp_peers)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bgp_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "172.30.255.5", "vrf": "default", "ttl": None, "max_ttl_hops": 3}], id="invalid-ttl-time"),
|
||||
pytest.param([{"peer_address": "172.30.255.6", "vrf": "default", "ttl": 3, "max_ttl_hops": None}], id="invalid-max-ttl-hops"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, bgp_peers: list[BgpPeer]) -> None:
|
||||
"""Test VerifyBGPPeerTtlMultiHops.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyBGPPeerTtlMultiHops.Input(bgp_peers=bgp_peers)
|
||||
|
|
66
tests/units/input_models/routing/test_generic.py
Normal file
66
tests/units/input_models/routing/test_generic.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models.routing.generic.py."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.tests.routing.generic import VerifyIPv4RouteNextHops, VerifyIPv4RouteType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anta.input_models.routing.generic import IPv4Routes
|
||||
|
||||
|
||||
class TestVerifyRouteEntryInput:
|
||||
"""Test anta.tests.routing.generic.VerifyIPv4RouteNextHops.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("route_entries"),
|
||||
[
|
||||
pytest.param([{"prefix": "10.10.0.1/32", "vrf": "default", "strict": True, "nexthops": ["10.100.0.8", "10.100.0.10"]}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, route_entries: list[IPv4Routes]) -> None:
|
||||
"""Test VerifyIPv4RouteNextHops.Input valid inputs."""
|
||||
VerifyIPv4RouteNextHops.Input(route_entries=route_entries)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("route_entries"),
|
||||
[
|
||||
pytest.param([{"prefix": "10.10.0.1/32", "vrf": "default"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, route_entries: list[IPv4Routes]) -> None:
|
||||
"""Test VerifyIPv4RouteNextHops.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyIPv4RouteNextHops.Input(route_entries=route_entries)
|
||||
|
||||
|
||||
class TestVerifyIPv4RouteTypeInput:
|
||||
"""Test anta.tests.routing.bgp.VerifyIPv4RouteType.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("routes_entries"),
|
||||
[
|
||||
pytest.param([{"prefix": "192.168.0.0/24", "vrf": "default", "route_type": "eBGP"}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, routes_entries: list[IPv4Routes]) -> None:
|
||||
"""Test VerifyIPv4RouteType.Input valid inputs."""
|
||||
VerifyIPv4RouteType.Input(routes_entries=routes_entries)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("routes_entries"),
|
||||
[
|
||||
pytest.param([{"prefix": "192.168.0.0/24", "vrf": "default"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, routes_entries: list[IPv4Routes]) -> None:
|
||||
"""Test VerifyIPv4RouteType.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyIPv4RouteType.Input(routes_entries=routes_entries)
|
101
tests/units/input_models/routing/test_isis.py
Normal file
101
tests/units/input_models/routing/test_isis.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models.routing.isis.py."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.input_models.routing.isis import ISISInstance, TunnelPath
|
||||
from anta.tests.routing.isis import VerifyISISSegmentRoutingAdjacencySegments, VerifyISISSegmentRoutingDataplane
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ipaddress import IPv4Address
|
||||
|
||||
from anta.custom_types import Interface
|
||||
|
||||
|
||||
class TestVerifyISISSegmentRoutingAdjacencySegmentsInput:
|
||||
"""Test anta.tests.routing.isis.VerifyISISSegmentRoutingAdjacencySegments.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("instances"),
|
||||
[
|
||||
pytest.param(
|
||||
[{"name": "CORE-ISIS", "vrf": "default", "segments": [{"interface": "Ethernet2", "address": "10.0.1.3", "sid_origin": "dynamic"}]}], id="valid_vrf"
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_valid(self, instances: list[ISISInstance]) -> None:
|
||||
"""Test VerifyISISSegmentRoutingAdjacencySegments.Input valid inputs."""
|
||||
VerifyISISSegmentRoutingAdjacencySegments.Input(instances=instances)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("instances"),
|
||||
[
|
||||
pytest.param(
|
||||
[{"name": "CORE-ISIS", "vrf": "PROD", "segments": [{"interface": "Ethernet2", "address": "10.0.1.3", "sid_origin": "dynamic"}]}], id="invalid_vrf"
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, instances: list[ISISInstance]) -> None:
|
||||
"""Test VerifyISISSegmentRoutingAdjacencySegments.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyISISSegmentRoutingAdjacencySegments.Input(instances=instances)
|
||||
|
||||
|
||||
class TestVerifyISISSegmentRoutingDataplaneInput:
|
||||
"""Test anta.tests.routing.isis.VerifyISISSegmentRoutingDataplane.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("instances"),
|
||||
[
|
||||
pytest.param([{"name": "CORE-ISIS", "vrf": "default", "dataplane": "MPLS"}], id="valid_vrf"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, instances: list[ISISInstance]) -> None:
|
||||
"""Test VerifyISISSegmentRoutingDataplane.Input valid inputs."""
|
||||
VerifyISISSegmentRoutingDataplane.Input(instances=instances)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("instances"),
|
||||
[
|
||||
pytest.param([{"name": "CORE-ISIS", "vrf": "PROD", "dataplane": "MPLS"}], id="invalid_vrf"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, instances: list[ISISInstance]) -> None:
|
||||
"""Test VerifyISISSegmentRoutingDataplane.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyISISSegmentRoutingDataplane.Input(instances=instances)
|
||||
|
||||
|
||||
class TestTunnelPath:
|
||||
"""Test anta.input_models.routing.isis.TestTunnelPath."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("nexthop", "type", "interface", "tunnel_id", "expected"),
|
||||
[
|
||||
pytest.param("1.1.1.1", None, None, None, "Next-hop: 1.1.1.1", id="nexthop"),
|
||||
pytest.param(None, "ip", None, None, "Type: ip", id="type"),
|
||||
pytest.param(None, None, "Et1", None, "Interface: Ethernet1", id="interface"),
|
||||
pytest.param(None, None, None, "TI-LFA", "Tunnel ID: TI-LFA", id="tunnel_id"),
|
||||
pytest.param("1.1.1.1", "ip", "Et1", "TI-LFA", "Next-hop: 1.1.1.1 Type: ip Interface: Ethernet1 Tunnel ID: TI-LFA", id="all"),
|
||||
pytest.param(None, None, None, None, "", id="None"),
|
||||
],
|
||||
)
|
||||
def test_valid__str__(
|
||||
self,
|
||||
nexthop: IPv4Address | None,
|
||||
type: Literal["ip", "tunnel"] | None, # noqa: A002
|
||||
interface: Interface | None,
|
||||
tunnel_id: Literal["TI-LFA", "ti-lfa", "unset"] | None,
|
||||
expected: str,
|
||||
) -> None:
|
||||
"""Test TunnelPath __str__."""
|
||||
assert str(TunnelPath(nexthop=nexthop, type=type, interface=interface, tunnel_id=tunnel_id)) == expected
|
68
tests/units/input_models/test_bfd.py
Normal file
68
tests/units/input_models/test_bfd.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models.bfd.py."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.tests.bfd import VerifyBFDPeersIntervals, VerifyBFDPeersRegProtocols
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anta.input_models.bfd import BFDPeer
|
||||
|
||||
|
||||
class TestVerifyBFDPeersIntervalsInput:
|
||||
"""Test anta.tests.bfd.VerifyBFDPeersIntervals.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bfd_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "10.0.0.1", "vrf": "default", "tx_interval": 1200, "rx_interval": 1200, "multiplier": 3}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, bfd_peers: list[BFDPeer]) -> None:
|
||||
"""Test VerifyBFDPeersIntervals.Input valid inputs."""
|
||||
VerifyBFDPeersIntervals.Input(bfd_peers=bfd_peers)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bfd_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "10.0.0.1", "vrf": "default", "tx_interval": 1200}], id="invalid-tx-interval"),
|
||||
pytest.param([{"peer_address": "10.0.0.1", "vrf": "default", "rx_interval": 1200}], id="invalid-rx-interval"),
|
||||
pytest.param([{"peer_address": "10.0.0.1", "vrf": "default", "tx_interval": 1200, "rx_interval": 1200}], id="invalid-multiplier"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, bfd_peers: list[BFDPeer]) -> None:
|
||||
"""Test VerifyBFDPeersIntervals.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyBFDPeersIntervals.Input(bfd_peers=bfd_peers)
|
||||
|
||||
|
||||
class TestVerifyBFDPeersRegProtocolsInput:
|
||||
"""Test anta.tests.bfd.VerifyBFDPeersRegProtocols.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bfd_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "10.0.0.1", "vrf": "default", "protocols": ["bgp"]}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, bfd_peers: list[BFDPeer]) -> None:
|
||||
"""Test VerifyBFDPeersRegProtocols.Input valid inputs."""
|
||||
VerifyBFDPeersRegProtocols.Input(bfd_peers=bfd_peers)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("bfd_peers"),
|
||||
[
|
||||
pytest.param([{"peer_address": "10.0.0.1", "vrf": "default"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, bfd_peers: list[BFDPeer]) -> None:
|
||||
"""Test VerifyBFDPeersRegProtocols.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyBFDPeersRegProtocols.Input(bfd_peers=bfd_peers)
|
43
tests/units/input_models/test_connectivity.py
Normal file
43
tests/units/input_models/test_connectivity.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models.connectivity.py."""
|
||||
|
||||
# pylint: disable=C0302
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.tests.connectivity import VerifyReachability
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anta.input_models.connectivity import Host
|
||||
|
||||
|
||||
class TestVerifyReachabilityInput:
|
||||
"""Test anta.tests.connectivity.VerifyReachability.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("hosts"),
|
||||
[
|
||||
pytest.param([{"destination": "fd12:3456:789a:1::2", "source": "fd12:3456:789a:1::1"}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, hosts: list[Host]) -> None:
|
||||
"""Test VerifyReachability.Input valid inputs."""
|
||||
VerifyReachability.Input(hosts=hosts)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("hosts"),
|
||||
[
|
||||
pytest.param([{"destination": "fd12:3456:789a:1::2", "source": "192.168.0.10"}], id="invalid-source"),
|
||||
pytest.param([{"destination": "192.168.0.10", "source": "fd12:3456:789a:1::2"}], id="invalid-destination"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, hosts: list[Host]) -> None:
|
||||
"""Test VerifyReachability.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyReachability.Input(hosts=hosts)
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models.interfaces.py."""
|
||||
|
@ -9,8 +9,10 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.input_models.interfaces import InterfaceState
|
||||
from anta.tests.interfaces import VerifyInterfaceIPv4, VerifyInterfacesSpeed, VerifyInterfacesStatus, VerifyLACPInterfacesStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anta.custom_types import Interface, PortChannelInterface
|
||||
|
@ -31,3 +33,103 @@ class TestInterfaceState:
|
|||
def test_valid__str__(self, name: Interface, portchannel: PortChannelInterface | None, expected: str) -> None:
|
||||
"""Test InterfaceState __str__."""
|
||||
assert str(InterfaceState(name=name, portchannel=portchannel)) == expected
|
||||
|
||||
|
||||
class TestVerifyInterfacesStatusInput:
|
||||
"""Test anta.tests.interfaces.VerifyInterfacesStatus.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("interfaces"),
|
||||
[
|
||||
pytest.param([{"name": "Ethernet1", "status": "up"}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, interfaces: list[InterfaceState]) -> None:
|
||||
"""Test VerifyInterfacesStatus.Input valid inputs."""
|
||||
VerifyInterfacesStatus.Input(interfaces=interfaces)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("interfaces"),
|
||||
[
|
||||
pytest.param([{"name": "Ethernet1"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, interfaces: list[InterfaceState]) -> None:
|
||||
"""Test VerifyInterfacesStatus.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyInterfacesStatus.Input(interfaces=interfaces)
|
||||
|
||||
|
||||
class TestVerifyLACPInterfacesStatusInput:
|
||||
"""Test anta.tests.interfaces.VerifyLACPInterfacesStatus.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("interfaces"),
|
||||
[
|
||||
pytest.param([{"name": "Ethernet1", "portchannel": "Port-Channel100"}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, interfaces: list[InterfaceState]) -> None:
|
||||
"""Test VerifyLACPInterfacesStatus.Input valid inputs."""
|
||||
VerifyLACPInterfacesStatus.Input(interfaces=interfaces)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("interfaces"),
|
||||
[
|
||||
pytest.param([{"name": "Ethernet1"}], id="invalid"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, interfaces: list[InterfaceState]) -> None:
|
||||
"""Test VerifyLACPInterfacesStatus.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyLACPInterfacesStatus.Input(interfaces=interfaces)
|
||||
|
||||
|
||||
class TestVerifyInterfaceIPv4Input:
|
||||
"""Test anta.tests.interfaces.VerifyInterfaceIPv4.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("interfaces"),
|
||||
[
|
||||
pytest.param([{"name": "Ethernet1", "primary_ip": "172.30.11.1/31"}], id="valid"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, interfaces: list[InterfaceState]) -> None:
|
||||
"""Test VerifyInterfaceIPv4.Input valid inputs."""
|
||||
VerifyInterfaceIPv4.Input(interfaces=interfaces)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("interfaces"),
|
||||
[
|
||||
pytest.param([{"name": "Ethernet1"}], id="invalid-no-primary-ip"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, interfaces: list[InterfaceState]) -> None:
|
||||
"""Test VerifyInterfaceIPv4.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyInterfaceIPv4.Input(interfaces=interfaces)
|
||||
|
||||
|
||||
class TestVerifyInterfacesSpeedInput:
|
||||
"""Test anta.tests.interfaces.VerifyInterfacesSpeed.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("interfaces"),
|
||||
[
|
||||
pytest.param([{"name": "Ethernet1", "speed": 10}], id="valid-speed-is-given"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, interfaces: list[InterfaceState]) -> None:
|
||||
"""Test VerifyInterfacesSpeed.Input valid inputs."""
|
||||
VerifyInterfacesSpeed.Input(interfaces=interfaces)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("interfaces"),
|
||||
[
|
||||
pytest.param([{"name": "Ethernet1"}], id="invalid-speed-is-not-given"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, interfaces: list[InterfaceState]) -> None:
|
||||
"""Test VerifyInterfacesSpeed.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyInterfacesSpeed.Input(interfaces=interfaces)
|
||||
|
|
192
tests/units/input_models/test_snmp.py
Normal file
192
tests/units/input_models/test_snmp.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models.snmp.py."""
|
||||
|
||||
# pylint: disable=C0302
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.input_models.snmp import SnmpGroup
|
||||
from anta.tests.snmp import VerifySnmpNotificationHost, VerifySnmpUser
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anta.custom_types import SnmpVersion, SnmpVersionV3AuthType
|
||||
from anta.input_models.snmp import SnmpHost, SnmpUser
|
||||
|
||||
|
||||
class TestVerifySnmpUserInput:
|
||||
"""Test anta.tests.snmp.VerifySnmpUser.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("snmp_users"),
|
||||
[
|
||||
pytest.param([{"username": "test", "group_name": "abc", "version": "v1", "auth_type": None, "priv_type": None}], id="valid-v1"),
|
||||
pytest.param([{"username": "test", "group_name": "abc", "version": "v2c", "auth_type": None, "priv_type": None}], id="valid-v2c"),
|
||||
pytest.param([{"username": "test", "group_name": "abc", "version": "v3", "auth_type": "SHA", "priv_type": "AES-128"}], id="valid-v3"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, snmp_users: list[SnmpUser]) -> None:
|
||||
"""Test VerifySnmpUser.Input valid inputs."""
|
||||
VerifySnmpUser.Input(snmp_users=snmp_users)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("snmp_users"),
|
||||
[
|
||||
pytest.param([{"username": "test", "group_name": "abc", "version": "v3", "auth_type": None, "priv_type": None}], id="invalid-v3"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, snmp_users: list[SnmpUser]) -> None:
|
||||
"""Test VerifySnmpUser.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifySnmpUser.Input(snmp_users=snmp_users)
|
||||
|
||||
|
||||
class TestSnmpHost:
|
||||
"""Test anta.input_models.snmp.SnmpHost."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("notification_hosts"),
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"vrf": "test",
|
||||
"notification_type": "trap",
|
||||
"version": "v1",
|
||||
"udp_port": 162,
|
||||
"community_string": "public",
|
||||
"user": None,
|
||||
}
|
||||
],
|
||||
id="valid-v1",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"vrf": "test",
|
||||
"notification_type": "trap",
|
||||
"version": "v2c",
|
||||
"udp_port": 162,
|
||||
"community_string": "public",
|
||||
"user": None,
|
||||
}
|
||||
],
|
||||
id="valid-v2c",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"vrf": "test",
|
||||
"notification_type": "trap",
|
||||
"version": "v3",
|
||||
"udp_port": 162,
|
||||
"community_string": None,
|
||||
"user": "public",
|
||||
}
|
||||
],
|
||||
id="valid-v3",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_valid(self, notification_hosts: list[SnmpHost]) -> None:
|
||||
"""Test VerifySnmpNotificationHost.Input valid inputs."""
|
||||
VerifySnmpNotificationHost.Input(notification_hosts=notification_hosts)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("notification_hosts"),
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"vrf": "test",
|
||||
"notification_type": "trap",
|
||||
"version": None,
|
||||
"udp_port": 162,
|
||||
"community_string": None,
|
||||
"user": None,
|
||||
}
|
||||
],
|
||||
id="invalid-version",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"vrf": "test",
|
||||
"notification_type": "trap",
|
||||
"version": "v1",
|
||||
"udp_port": 162,
|
||||
"community_string": None,
|
||||
"user": None,
|
||||
}
|
||||
],
|
||||
id="invalid-community-string-version-v1",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"vrf": "test",
|
||||
"notification_type": "trap",
|
||||
"version": "v2c",
|
||||
"udp_port": 162,
|
||||
"community_string": None,
|
||||
"user": None,
|
||||
}
|
||||
],
|
||||
id="invalid-community-string-version-v2c",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"hostname": "192.168.1.100",
|
||||
"vrf": "test",
|
||||
"notification_type": "trap",
|
||||
"version": "v3",
|
||||
"udp_port": 162,
|
||||
"community_string": None,
|
||||
"user": None,
|
||||
}
|
||||
],
|
||||
id="invalid-user-version-v3",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, notification_hosts: list[SnmpHost]) -> None:
|
||||
"""Test VerifySnmpNotificationHost.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifySnmpNotificationHost.Input(notification_hosts=notification_hosts)
|
||||
|
||||
|
||||
class TestSnmpGroupInput:
|
||||
"""Test anta.input_models.snmp.SnmpGroup."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("group_name", "version", "read_view", "write_view", "notify_view", "authentication"),
|
||||
[
|
||||
pytest.param("group1", "v3", "", "write_1", None, "auth", id="snmp-auth"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, group_name: str, read_view: str, version: SnmpVersion, write_view: str, notify_view: str, authentication: SnmpVersionV3AuthType) -> None:
|
||||
"""Test SnmpGroup valid inputs."""
|
||||
SnmpGroup(group_name=group_name, version=version, read_view=read_view, write_view=write_view, notify_view=notify_view, authentication=authentication)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("group_name", "version", "read_view", "write_view", "notify_view", "authentication"),
|
||||
[
|
||||
pytest.param("group1", "v3", "", "write_1", None, None, id="snmp-invalid-auth"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, group_name: str, read_view: str, version: SnmpVersion, write_view: str, notify_view: str, authentication: SnmpVersionV3AuthType) -> None:
|
||||
"""Test SnmpGroup invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
SnmpGroup(group_name=group_name, version=version, read_view=read_view, write_view=write_view, notify_view=notify_view, authentication=authentication)
|
48
tests/units/input_models/test_system.py
Normal file
48
tests/units/input_models/test_system.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Tests for anta.input_models.system.py."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from anta.tests.system import VerifyNTPAssociations
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anta.input_models.system import NTPPool, NTPServer
|
||||
|
||||
|
||||
class TestVerifyNTPAssociationsInput:
|
||||
"""Test anta.tests.system.VerifyNTPAssociations.Input."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ntp_servers", "ntp_pool"),
|
||||
[
|
||||
pytest.param([{"server_address": "1.1.1.1", "preferred": True, "stratum": 1}], None, id="valid-ntp-server"),
|
||||
pytest.param(None, {"server_addresses": ["1.1.1.1"], "preferred_stratum_range": [1, 3]}, id="valid-ntp-pool"),
|
||||
],
|
||||
)
|
||||
def test_valid(self, ntp_servers: list[NTPServer], ntp_pool: NTPPool) -> None:
|
||||
"""Test VerifyNTPAssociations.Input valid inputs."""
|
||||
VerifyNTPAssociations.Input(ntp_servers=ntp_servers, ntp_pool=ntp_pool)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ntp_servers", "ntp_pool"),
|
||||
[
|
||||
pytest.param(
|
||||
[{"server_address": "1.1.1.1", "preferred": True, "stratum": 1}],
|
||||
{"server_addresses": ["1.1.1.1"], "preferred_stratum_range": [1, 3]},
|
||||
id="invalid-both-server-pool",
|
||||
),
|
||||
pytest.param(None, {"server_addresses": ["1.1.1.1"], "preferred_stratum_range": [1, 3, 6]}, id="invalid-ntp-pool-stratum"),
|
||||
pytest.param(None, None, id="invalid-both-none"),
|
||||
],
|
||||
)
|
||||
def test_invalid(self, ntp_servers: list[NTPServer], ntp_pool: NTPPool) -> None:
|
||||
"""Test VerifyNTPAssociations.Input invalid inputs."""
|
||||
with pytest.raises(ValidationError):
|
||||
VerifyNTPAssociations.Input(ntp_servers=ntp_servers, ntp_pool=ntp_pool)
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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 inventory submodule."""
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""ANTA Inventory unit tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
@ -15,11 +15,10 @@ from anta.inventory import AntaInventory
|
|||
from anta.inventory.exceptions import InventoryIncorrectSchemaError, InventoryRootKeyError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
from _pytest.mark.structures import ParameterSet
|
||||
|
||||
FILE_DIR: Path = Path(__file__).parent.parent.resolve() / "data" / "inventory"
|
||||
|
||||
|
||||
INIT_VALID_PARAMS: list[ParameterSet] = [
|
||||
pytest.param(
|
||||
{"anta_inventory": {"hosts": [{"host": "192.168.0.17"}, {"host": "192.168.0.2"}, {"host": "my.awesome.host.com"}]}},
|
||||
|
@ -76,3 +75,15 @@ class TestAntaInventory:
|
|||
"""Parse invalid YAML file to create ANTA inventory."""
|
||||
with pytest.raises((InventoryIncorrectSchemaError, InventoryRootKeyError, ValidationError)):
|
||||
AntaInventory.parse(filename=yaml_file, username="arista", password="arista123")
|
||||
|
||||
def test_parse_wrong_format(self) -> None:
|
||||
"""Use wrong file format to parse the ANTA inventory."""
|
||||
with pytest.raises(ValueError, match=" is not a valid format for an AntaInventory file. Only 'yaml' and 'json' are supported."):
|
||||
AntaInventory.parse(filename="dummy.yml", username="arista", password="arista123", file_format="wrong") # type: ignore[arg-type]
|
||||
|
||||
def test_parse_os_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Use wrong file name to parse the ANTA inventory."""
|
||||
caplog.set_level(logging.INFO)
|
||||
with pytest.raises(OSError, match="No such file or directory"):
|
||||
_ = AntaInventory.parse(filename="dummy.yml", username="arista", password="arista123")
|
||||
assert "Unable to parse ANTA Device Inventory file" in caplog.records[0].message
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""ANTA Inventory models unit tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
from yaml import safe_load
|
||||
|
||||
from anta.inventory.models import AntaInventoryHost, AntaInventoryNetwork, AntaInventoryRange
|
||||
from anta.inventory.models import AntaInventoryHost, AntaInventoryInput, AntaInventoryNetwork, AntaInventoryRange
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.mark.structures import ParameterSet
|
||||
|
||||
FILE_DIR: Path = Path(__file__).parents[2] / "data"
|
||||
|
||||
INVENTORY_HOST_VALID_PARAMS: list[ParameterSet] = [
|
||||
pytest.param(None, "1.1.1.1", None, None, None, id="IPv4"),
|
||||
pytest.param(None, "fe80::cc62:a9ff:feef:932a", None, None, None, id="IPv6"),
|
||||
|
@ -164,3 +169,33 @@ class TestAntaInventoryRange:
|
|||
"""Invalid model parameters."""
|
||||
with pytest.raises(ValidationError):
|
||||
AntaInventoryRange.model_validate({"start": start, "end": end, "tags": tags, "disable_cache": disable_cache})
|
||||
|
||||
|
||||
class TestAntaInventoryInputs:
|
||||
"""Test anta.inventory.models.AntaInventoryInputs."""
|
||||
|
||||
def test_dump_to_json(self) -> None:
|
||||
"""Load a YAML file, dump it to JSON and verify it works."""
|
||||
input_yml_path = FILE_DIR / "test_inventory_with_tags.yml"
|
||||
expected_json_path = FILE_DIR / "test_inventory_with_tags.json"
|
||||
with input_yml_path.open("r") as f:
|
||||
data = safe_load(f)
|
||||
anta_inventory_input = AntaInventoryInput(**data["anta_inventory"])
|
||||
|
||||
with expected_json_path.open("r") as f:
|
||||
expected_data = json.load(f)
|
||||
|
||||
assert anta_inventory_input.to_json() == json.dumps(expected_data["anta_inventory"], indent=2)
|
||||
|
||||
def test_dump_to_yaml(self) -> None:
|
||||
"""Load a JSON file, dump it to YAML and verify it works."""
|
||||
input_json_path = FILE_DIR / "test_inventory_medium.json"
|
||||
expected_yml_path = FILE_DIR / "test_inventory_medium.yml"
|
||||
with input_json_path.open("r") as f:
|
||||
data = json.load(f)
|
||||
anta_inventory_input = AntaInventoryInput(**data["anta_inventory"])
|
||||
|
||||
with expected_yml_path.open("r") as f:
|
||||
expected_data = safe_load(f)
|
||||
|
||||
assert safe_load(anta_inventory_input.yaml()) == expected_data["anta_inventory"]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.reporter submodule."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.report.__init__.py."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.report.csv_reporter.py."""
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.reporter.md_reporter.py."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO, TextIOWrapper
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
@ -22,7 +22,7 @@ def test_md_report_generate(tmp_path: Path, result_manager: ResultManager) -> No
|
|||
expected_report = "test_md_report.md"
|
||||
|
||||
# Generate the Markdown report
|
||||
MDReportGenerator.generate(result_manager, md_filename)
|
||||
MDReportGenerator.generate(result_manager.sort(sort_by=["name", "categories", "test"]), md_filename)
|
||||
assert md_filename.exists()
|
||||
|
||||
# Load the existing Markdown report to compare with the generated one
|
||||
|
@ -46,7 +46,7 @@ def test_md_report_base() -> None:
|
|||
|
||||
results = ResultManager()
|
||||
|
||||
with TextIOWrapper(BytesIO(b"1 2 3")) as mock_file:
|
||||
with StringIO() as mock_file:
|
||||
report = FakeMDReportBase(mock_file, results)
|
||||
assert report.generate_heading_name() == "Fake MD Report Base"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.result_manager submodule."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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."""
|
||||
|
@ -34,12 +34,12 @@ def result_manager_factory(list_result_factory: Callable[[int], list[TestResult]
|
|||
def result_manager() -> ResultManager:
|
||||
"""Return a ResultManager with 30 random tests loaded from a JSON file.
|
||||
|
||||
Devices: DC1-SPINE1, DC1-LEAF1A
|
||||
Devices: s1-spine1
|
||||
|
||||
- Total tests: 30
|
||||
- Success: 7
|
||||
- Skipped: 2
|
||||
- Failure: 19
|
||||
- Success: 4
|
||||
- Skipped: 9
|
||||
- Failure: 15
|
||||
- Error: 2
|
||||
|
||||
See `tests/units/result_manager/test_md_report_results.json` for details.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""Test anta.result_manager.__init__.py."""
|
||||
|
@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
|||
from anta.result_manager.models import TestResult
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
class TestResultManager:
|
||||
"""Test ResultManager class."""
|
||||
|
||||
|
@ -73,8 +74,8 @@ class TestResultManager:
|
|||
assert test.get("custom_field") is None
|
||||
assert test.get("result") == "success"
|
||||
|
||||
def test_sorted_category_stats(self, list_result_factory: Callable[[int], list[TestResult]]) -> None:
|
||||
"""Test ResultManager.sorted_category_stats."""
|
||||
def test_category_stats(self, list_result_factory: Callable[[int], list[TestResult]]) -> None:
|
||||
"""Test ResultManager.category_stats."""
|
||||
result_manager = ResultManager()
|
||||
results = list_result_factory(4)
|
||||
|
||||
|
@ -86,13 +87,9 @@ class TestResultManager:
|
|||
|
||||
result_manager.results = results
|
||||
|
||||
# Check the current categories order
|
||||
expected_order = ["ospf", "bgp", "vxlan", "system"]
|
||||
assert list(result_manager.category_stats.keys()) == expected_order
|
||||
|
||||
# Check the sorted categories order
|
||||
# Check that category_stats returns sorted order by default
|
||||
expected_order = ["bgp", "ospf", "system", "vxlan"]
|
||||
assert list(result_manager.sorted_category_stats.keys()) == expected_order
|
||||
assert list(result_manager.category_stats.keys()) == expected_order
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("starting_status", "test_status", "expected_status", "expected_raise"),
|
||||
|
@ -198,12 +195,12 @@ class TestResultManager:
|
|||
"""Test ResultManager.get_results."""
|
||||
# Check for single status
|
||||
success_results = result_manager.get_results(status={AntaTestStatus.SUCCESS})
|
||||
assert len(success_results) == 7
|
||||
assert len(success_results) == 4
|
||||
assert all(r.result == "success" for r in success_results)
|
||||
|
||||
# Check for multiple statuses
|
||||
failure_results = result_manager.get_results(status={AntaTestStatus.FAILURE, AntaTestStatus.ERROR})
|
||||
assert len(failure_results) == 21
|
||||
assert len(failure_results) == 17
|
||||
assert all(r.result in {"failure", "error"} for r in failure_results)
|
||||
|
||||
# Check all results
|
||||
|
@ -215,19 +212,18 @@ class TestResultManager:
|
|||
# Check all results with sort_by result
|
||||
all_results = result_manager.get_results(sort_by=["result"])
|
||||
assert len(all_results) == 30
|
||||
assert [r.result for r in all_results] == ["error"] * 2 + ["failure"] * 19 + ["skipped"] * 2 + ["success"] * 7
|
||||
assert [r.result for r in all_results] == ["error"] * 2 + ["failure"] * 15 + ["skipped"] * 9 + ["success"] * 4
|
||||
|
||||
# Check all results with sort_by device (name)
|
||||
all_results = result_manager.get_results(sort_by=["name"])
|
||||
assert len(all_results) == 30
|
||||
assert all_results[0].name == "DC1-LEAF1A"
|
||||
assert all_results[-1].name == "DC1-SPINE1"
|
||||
assert all_results[0].name == "s1-spine1"
|
||||
|
||||
# Check multiple statuses with sort_by categories
|
||||
success_skipped_results = result_manager.get_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.SKIPPED}, sort_by=["categories"])
|
||||
assert len(success_skipped_results) == 9
|
||||
assert success_skipped_results[0].categories == ["Interfaces"]
|
||||
assert success_skipped_results[-1].categories == ["VXLAN"]
|
||||
assert len(success_skipped_results) == 13
|
||||
assert success_skipped_results[0].categories == ["avt"]
|
||||
assert success_skipped_results[-1].categories == ["vxlan"]
|
||||
|
||||
# Check all results with bad sort_by
|
||||
with pytest.raises(
|
||||
|
@ -244,14 +240,14 @@ class TestResultManager:
|
|||
assert result_manager.get_total_results() == 30
|
||||
|
||||
# Test single status
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS}) == 7
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.FAILURE}) == 19
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS}) == 4
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.FAILURE}) == 15
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.ERROR}) == 2
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SKIPPED}) == 2
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SKIPPED}) == 9
|
||||
|
||||
# Test multiple statuses
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE}) == 26
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE, AntaTestStatus.ERROR}) == 28
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE}) == 19
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE, AntaTestStatus.ERROR}) == 21
|
||||
assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE, AntaTestStatus.ERROR, AntaTestStatus.SKIPPED}) == 30
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -465,7 +461,6 @@ class TestResultManager:
|
|||
with caplog.at_level(logging.INFO):
|
||||
_ = result_manager.category_stats
|
||||
_ = result_manager.test_stats
|
||||
_ = result_manager.sorted_category_stats
|
||||
assert "Computing statistics" not in caplog.text
|
||||
|
||||
# Add another result - should mark stats as unsynced
|
||||
|
@ -480,3 +475,89 @@ class TestResultManager:
|
|||
_ = result_manager.device_stats
|
||||
assert "Computing statistics for all results" in caplog.text
|
||||
assert result_manager._stats_in_sync is True
|
||||
|
||||
def test_sort_by_result(self, test_result_factory: Callable[[], TestResult]) -> None:
|
||||
"""Test sorting by result."""
|
||||
result_manager = ResultManager()
|
||||
test1 = test_result_factory()
|
||||
test1.result = AntaTestStatus.SUCCESS
|
||||
test2 = test_result_factory()
|
||||
test2.result = AntaTestStatus.FAILURE
|
||||
test3 = test_result_factory()
|
||||
test3.result = AntaTestStatus.ERROR
|
||||
|
||||
result_manager.results = [test1, test2, test3]
|
||||
sorted_manager = result_manager.sort(["result"])
|
||||
assert [r.result for r in sorted_manager.results] == ["error", "failure", "success"]
|
||||
|
||||
def test_sort_by_name(self, test_result_factory: Callable[[], TestResult]) -> None:
|
||||
"""Test sorting by name."""
|
||||
result_manager = ResultManager()
|
||||
test1 = test_result_factory()
|
||||
test1.name = "Device3"
|
||||
test2 = test_result_factory()
|
||||
test2.name = "Device1"
|
||||
test3 = test_result_factory()
|
||||
test3.name = "Device2"
|
||||
|
||||
result_manager.results = [test1, test2, test3]
|
||||
sorted_manager = result_manager.sort(["name"])
|
||||
assert [r.name for r in sorted_manager.results] == ["Device1", "Device2", "Device3"]
|
||||
|
||||
def test_sort_by_categories(self, test_result_factory: Callable[[], TestResult]) -> None:
|
||||
"""Test sorting by categories."""
|
||||
result_manager = ResultManager()
|
||||
test1 = test_result_factory()
|
||||
test1.categories = ["VXLAN", "networking"]
|
||||
test2 = test_result_factory()
|
||||
test2.categories = ["BGP", "routing"]
|
||||
test3 = test_result_factory()
|
||||
test3.categories = ["system", "hardware"]
|
||||
|
||||
result_manager.results = [test1, test2, test3]
|
||||
sorted_manager = result_manager.sort(["categories"])
|
||||
results = sorted_manager.results
|
||||
|
||||
assert results[0].categories == ["BGP", "routing"]
|
||||
assert results[1].categories == ["VXLAN", "networking"]
|
||||
assert results[2].categories == ["system", "hardware"]
|
||||
|
||||
def test_sort_multiple_fields(self, test_result_factory: Callable[[], TestResult]) -> None:
|
||||
"""Test sorting by multiple fields."""
|
||||
result_manager = ResultManager()
|
||||
test1 = test_result_factory()
|
||||
test1.result = AntaTestStatus.ERROR
|
||||
test1.test = "Test3"
|
||||
test2 = test_result_factory()
|
||||
test2.result = AntaTestStatus.ERROR
|
||||
test2.test = "Test1"
|
||||
test3 = test_result_factory()
|
||||
test3.result = AntaTestStatus.FAILURE
|
||||
test3.test = "Test2"
|
||||
|
||||
result_manager.results = [test1, test2, test3]
|
||||
sorted_manager = result_manager.sort(["result", "test"])
|
||||
results = sorted_manager.results
|
||||
|
||||
assert results[0].result == "error"
|
||||
assert results[0].test == "Test1"
|
||||
assert results[1].result == "error"
|
||||
assert results[1].test == "Test3"
|
||||
assert results[2].result == "failure"
|
||||
assert results[2].test == "Test2"
|
||||
|
||||
def test_sort_invalid_field(self) -> None:
|
||||
"""Test that sort method raises ValueError for invalid sort_by fields."""
|
||||
result_manager = ResultManager()
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=re.escape(
|
||||
"Invalid sort_by fields: ['bad_field']. Accepted fields are: ['name', 'test', 'categories', 'description', 'result', 'messages', 'custom_field']",
|
||||
),
|
||||
):
|
||||
result_manager.sort(["bad_field"])
|
||||
|
||||
def test_sort_is_chainable(self) -> None:
|
||||
"""Test that the sort method is chainable."""
|
||||
result_manager = ResultManager()
|
||||
assert isinstance(result_manager.sort(["name"]), ResultManager)
|
||||
|
|
|
@ -1,378 +1,389 @@
|
|||
[
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyTacacsSourceIntf",
|
||||
"categories": [
|
||||
"AAA"
|
||||
],
|
||||
"description": "Verifies TACACS source-interface for a specified VRF.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Source-interface Management0 is not configured in VRF default"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyLLDPNeighbors",
|
||||
"categories": [
|
||||
"Connectivity"
|
||||
],
|
||||
"description": "Verifies that the provided LLDP neighbors are connected properly.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Wrong LLDP neighbor(s) on port(s):\n Ethernet1\n DC1-LEAF1A_Ethernet1\n Ethernet2\n DC1-LEAF1B_Ethernet1\nPort(s) not configured:\n Ethernet7"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyBGPPeerCount",
|
||||
"categories": [
|
||||
"BGP"
|
||||
],
|
||||
"description": "Verifies the count of BGP peers.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'PROD': 'Not Configured', 'default': 'Expected: 3, Actual: 4'}}, {'afi': 'ipv4', 'safi': 'multicast', 'vrfs': {'DEV': 'Not Configured'}}, {'afi': 'evpn', 'vrfs': {'default': 'Expected: 2, Actual: 4'}}]"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifySTPMode",
|
||||
"categories": [
|
||||
"STP"
|
||||
],
|
||||
"description": "Verifies the configured STP mode for a provided list of VLAN(s).",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"STP mode 'rapidPvst' not configured for the following VLAN(s): [10, 20]"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifySnmpStatus",
|
||||
"categories": [
|
||||
"SNMP"
|
||||
],
|
||||
"description": "Verifies if the SNMP agent is enabled.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"SNMP agent disabled in vrf default"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyRoutingTableEntry",
|
||||
"categories": [
|
||||
"Routing"
|
||||
],
|
||||
"description": "Verifies that the provided routes are present in the routing table of a specified VRF.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The following route(s) are missing from the routing table of VRF default: ['10.1.0.2']"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyInterfaceUtilization",
|
||||
"categories": [
|
||||
"Interfaces"
|
||||
],
|
||||
"description": "Verifies that the utilization of interfaces is below a certain threshold.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyMlagStatus",
|
||||
"categories": [
|
||||
"MLAG"
|
||||
],
|
||||
"description": "Verifies the health status of the MLAG configuration.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"MLAG is disabled"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyVxlan1Interface",
|
||||
"categories": [
|
||||
"VXLAN"
|
||||
],
|
||||
"description": "Verifies the Vxlan1 interface status.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"Vxlan1 interface is not configured"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyBFDSpecificPeers",
|
||||
"categories": [
|
||||
"BFD"
|
||||
],
|
||||
"description": "Verifies the IPv4 BFD peer's sessions and remote disc in the specified VRF.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Following BFD peers are not configured, status is not up or remote disc is zero:\n{'192.0.255.8': {'default': 'Not Configured'}, '192.0.255.7': {'default': 'Not Configured'}}"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyNTP",
|
||||
"categories": [
|
||||
"System"
|
||||
],
|
||||
"description": "Verifies if NTP is synchronised.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The device is not synchronized with the configured NTP server(s): 'NTP is disabled.'"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyReachability",
|
||||
"categories": [
|
||||
"Connectivity"
|
||||
],
|
||||
"description": "Test the network reachability to one or many destination IP(s).",
|
||||
"result": "error",
|
||||
"messages": [
|
||||
"ping vrf MGMT 1.1.1.1 source Management1 repeat 2 has failed: No source interface Management1"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyTelnetStatus",
|
||||
"categories": [
|
||||
"Security"
|
||||
],
|
||||
"description": "Verifies if Telnet is disabled in the default VRF.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyEOSVersion",
|
||||
"categories": [
|
||||
"Software"
|
||||
],
|
||||
"description": "Verifies the EOS version of the device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"device is running version \"4.31.1F-34554157.4311F (engineering build)\" not in expected versions: ['4.25.4M', '4.26.1F']"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-SPINE1",
|
||||
"test": "VerifyHostname",
|
||||
"categories": [
|
||||
"Services"
|
||||
],
|
||||
"description": "Verifies the hostname of a device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Expected `s1-spine1` as the hostname, but found `DC1-SPINE1` instead."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyTacacsSourceIntf",
|
||||
"categories": [
|
||||
"AAA"
|
||||
],
|
||||
"description": "Verifies TACACS source-interface for a specified VRF.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Source-interface Management0 is not configured in VRF default"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyLLDPNeighbors",
|
||||
"categories": [
|
||||
"Connectivity"
|
||||
],
|
||||
"description": "Verifies that the provided LLDP neighbors are connected properly.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Wrong LLDP neighbor(s) on port(s):\n Ethernet1\n DC1-SPINE1_Ethernet1\n Ethernet2\n DC1-SPINE2_Ethernet1\nPort(s) not configured:\n Ethernet7"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyBGPPeerCount",
|
||||
"categories": [
|
||||
"BGP"
|
||||
],
|
||||
"description": "Verifies the count of BGP peers.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'PROD': 'Expected: 2, Actual: 1'}}, {'afi': 'ipv4', 'safi': 'multicast', 'vrfs': {'DEV': 'Expected: 3, Actual: 0'}}]"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifySTPMode",
|
||||
"categories": [
|
||||
"STP"
|
||||
],
|
||||
"description": "Verifies the configured STP mode for a provided list of VLAN(s).",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Wrong STP mode configured for the following VLAN(s): [10, 20]"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifySnmpStatus",
|
||||
"categories": [
|
||||
"SNMP"
|
||||
],
|
||||
"description": "Verifies if the SNMP agent is enabled.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"SNMP agent disabled in vrf default"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyRoutingTableEntry",
|
||||
"categories": [
|
||||
"Routing"
|
||||
],
|
||||
"description": "Verifies that the provided routes are present in the routing table of a specified VRF.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyInterfaceUtilization",
|
||||
"categories": [
|
||||
"Interfaces"
|
||||
],
|
||||
"description": "Verifies that the utilization of interfaces is below a certain threshold.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyMlagStatus",
|
||||
"categories": [
|
||||
"MLAG"
|
||||
],
|
||||
"description": "Verifies the health status of the MLAG configuration.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyVxlan1Interface",
|
||||
"categories": [
|
||||
"VXLAN"
|
||||
],
|
||||
"description": "Verifies the Vxlan1 interface status.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyBFDSpecificPeers",
|
||||
"categories": [
|
||||
"BFD"
|
||||
],
|
||||
"description": "Verifies the IPv4 BFD peer's sessions and remote disc in the specified VRF.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Following BFD peers are not configured, status is not up or remote disc is zero:\n{'192.0.255.8': {'default': 'Not Configured'}, '192.0.255.7': {'default': 'Not Configured'}}"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyNTP",
|
||||
"categories": [
|
||||
"System"
|
||||
],
|
||||
"description": "Verifies if NTP is synchronised.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"The device is not synchronized with the configured NTP server(s): 'NTP is disabled.'"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyReachability",
|
||||
"categories": [
|
||||
"Connectivity"
|
||||
],
|
||||
"description": "Test the network reachability to one or many destination IP(s).",
|
||||
"result": "error",
|
||||
"messages": [
|
||||
"ping vrf MGMT 1.1.1.1 source Management1 repeat 2 has failed: No source interface Management1"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyTelnetStatus",
|
||||
"categories": [
|
||||
"Security"
|
||||
],
|
||||
"description": "Verifies if Telnet is disabled in the default VRF.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyEOSVersion",
|
||||
"categories": [
|
||||
"Software"
|
||||
],
|
||||
"description": "Verifies the EOS version of the device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"device is running version \"4.31.1F-34554157.4311F (engineering build)\" not in expected versions: ['4.25.4M', '4.26.1F']"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "DC1-LEAF1A",
|
||||
"test": "VerifyHostname",
|
||||
"categories": [
|
||||
"Services"
|
||||
],
|
||||
"description": "Verifies the hostname of a device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Expected `s1-spine1` as the hostname, but found `DC1-LEAF1A` instead."
|
||||
],
|
||||
"custom_field": null
|
||||
}
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyMlagDualPrimary",
|
||||
"categories": [
|
||||
"mlag"
|
||||
],
|
||||
"description": "Verifies the MLAG dual-primary detection parameters.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Dual-primary detection is disabled"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyHostname",
|
||||
"categories": [
|
||||
"services"
|
||||
],
|
||||
"description": "Verifies the hostname of a device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Incorrect Hostname - Expected: s1-spine1 Actual: leaf1-dc1"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyBGPAdvCommunities",
|
||||
"categories": [
|
||||
"bgp"
|
||||
],
|
||||
"description": "Verifies that advertised communities are standard, extended and large for BGP IPv4 peer(s).",
|
||||
"result": "error",
|
||||
"messages": [
|
||||
"show bgp neighbors vrf all has failed: The command is only supported in the multi-agent routing protocol model., The command is only supported in the multi-agent routing protocol model., The command is only supported in the multi-agent routing protocol model., The command is only supported in the multi-agent routing protocol model."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyStunClient",
|
||||
"categories": [
|
||||
"stun"
|
||||
],
|
||||
"description": "(Deprecated) Verifies the translation for a source address on a STUN client.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Client 172.18.3.2 Port: 4500 - STUN client translation not found."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyBannerLogin",
|
||||
"categories": [
|
||||
"security"
|
||||
],
|
||||
"description": "Verifies the login banner of a device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Expected `# Copyright (c) 2023-2024 Arista Networks, Inc.\n# Use of this source code is governed by the Apache License 2.0\n# that can be found in the LICENSE file.\n` as the login banner, but found `` instead."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyISISNeighborState",
|
||||
"categories": [
|
||||
"isis"
|
||||
],
|
||||
"description": "Verifies the health of IS-IS neighbors.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"IS-IS not configured"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyEOSVersion",
|
||||
"categories": [
|
||||
"software"
|
||||
],
|
||||
"description": "Verifies the EOS version of the device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"EOS version mismatch - Actual: 4.31.0F-33804048.4310F (engineering build) not in Expected: 4.25.4M, 4.26.1F"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyTcamProfile",
|
||||
"categories": [
|
||||
"profiles"
|
||||
],
|
||||
"description": "Verifies the device TCAM profile.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"VerifyTcamProfile test is not supported on cEOSLab."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyPathsHealth",
|
||||
"categories": [
|
||||
"path-selection"
|
||||
],
|
||||
"description": "Verifies the path and telemetry state of all paths under router path-selection.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"VerifyPathsHealth test is not supported on cEOSLab."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyBannerMotd",
|
||||
"categories": [
|
||||
"security"
|
||||
],
|
||||
"description": "Verifies the motd banner of a device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Expected `# Copyright (c) 2023-2024 Arista Networks, Inc.\n# Use of this source code is governed by the Apache License 2.0\n# that can be found in the LICENSE file.\n` as the motd banner, but found `` instead."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyFieldNotice44Resolution",
|
||||
"categories": [
|
||||
"field notices"
|
||||
],
|
||||
"description": "Verifies that the device is using the correct Aboot version per FN0044.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"VerifyFieldNotice44Resolution test is not supported on cEOSLab."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyLoggingHosts",
|
||||
"categories": [
|
||||
"logging"
|
||||
],
|
||||
"description": "Verifies logging hosts (syslog servers) for a specified VRF.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Syslog servers 1.1.1.1, 2.2.2.2 are not configured in VRF default"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyAVTPathHealth",
|
||||
"categories": [
|
||||
"avt"
|
||||
],
|
||||
"description": "Verifies the status of all AVT paths for all VRFs.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"VerifyAVTPathHealth test is not supported on cEOSLab."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyTemperature",
|
||||
"categories": [
|
||||
"hardware"
|
||||
],
|
||||
"description": "Verifies if the device temperature is within acceptable limits.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"VerifyTemperature test is not supported on cEOSLab."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyNTPAssociations",
|
||||
"categories": [
|
||||
"system"
|
||||
],
|
||||
"description": "Verifies the Network Time Protocol (NTP) associations.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"NTP Server: 1.1.1.1 Preferred: True Stratum: 1 - Not configured",
|
||||
"NTP Server: 2.2.2.2 Preferred: False Stratum: 2 - Not configured",
|
||||
"NTP Server: 3.3.3.3 Preferred: False Stratum: 2 - Not configured"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyDynamicVlanSource",
|
||||
"categories": [
|
||||
"vlan"
|
||||
],
|
||||
"description": "Verifies dynamic VLAN allocation for specified VLAN sources.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Dynamic VLAN source(s) exist but have no VLANs allocated: mlagsync"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyActiveCVXConnections",
|
||||
"categories": [
|
||||
"cvx"
|
||||
],
|
||||
"description": "Verifies the number of active CVX Connections.",
|
||||
"result": "error",
|
||||
"messages": [
|
||||
"show cvx connections brief has failed: Unavailable command (controller not ready) (at token 2: 'connections')"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyIPv4RouteNextHops",
|
||||
"categories": [
|
||||
"routing"
|
||||
],
|
||||
"description": "Verifies the next-hops of the IPv4 prefixes.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyVxlan1ConnSettings",
|
||||
"categories": [
|
||||
"vxlan"
|
||||
],
|
||||
"description": "Verifies the interface vxlan1 source interface and UDP port.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyStunClientTranslation",
|
||||
"categories": [
|
||||
"stun"
|
||||
],
|
||||
"description": "Verifies the translation for a source address on a STUN client.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Client 172.18.3.2 Port: 4500 - STUN client translation not found.",
|
||||
"Client 100.64.3.2 Port: 4500 - STUN client translation not found."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyPtpGMStatus",
|
||||
"categories": [
|
||||
"ptp"
|
||||
],
|
||||
"description": "Verifies that the device is locked to a valid PTP Grandmaster.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"VerifyPtpGMStatus test is not supported on cEOSLab."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyRunningConfigDiffs",
|
||||
"categories": [
|
||||
"configuration"
|
||||
],
|
||||
"description": "Verifies there is no difference between the running-config and the startup-config.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyBFDPeersHealth",
|
||||
"categories": [
|
||||
"bfd"
|
||||
],
|
||||
"description": "Verifies the health of IPv4 BFD peers across all VRFs.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"No IPv4 BFD peers are configured for any VRF."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyIPProxyARP",
|
||||
"categories": [
|
||||
"interfaces"
|
||||
],
|
||||
"description": "Verifies if Proxy ARP is enabled.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Interface: Ethernet1 - Proxy-ARP disabled",
|
||||
"Interface: Ethernet2 - Proxy-ARP disabled"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifySnmpContact",
|
||||
"categories": [
|
||||
"snmp"
|
||||
],
|
||||
"description": "Verifies the SNMP contact of a device.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"SNMP contact is not configured."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyLLDPNeighbors",
|
||||
"categories": [
|
||||
"connectivity"
|
||||
],
|
||||
"description": "Verifies the connection status of the specified LLDP (Link Layer Discovery Protocol) neighbors.",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"Port: Ethernet1 Neighbor: DC1-SPINE1 Neighbor Port: Ethernet1 - Wrong LLDP neighbors: spine1-dc1.fun.aristanetworks.com/Ethernet3",
|
||||
"Port: Ethernet2 Neighbor: DC1-SPINE2 Neighbor Port: Ethernet1 - Wrong LLDP neighbors: spine2-dc1.fun.aristanetworks.com/Ethernet3"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyAcctConsoleMethods",
|
||||
"categories": [
|
||||
"aaa"
|
||||
],
|
||||
"description": "Verifies the AAA accounting console method lists for different accounting types (system, exec, commands, dot1x).",
|
||||
"result": "failure",
|
||||
"messages": [
|
||||
"AAA console accounting is not configured for commands, exec, system, dot1x"
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyOSPFMaxLSA",
|
||||
"categories": [
|
||||
"ospf"
|
||||
],
|
||||
"description": "Verifies all OSPF instances did not cross the maximum LSA threshold.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"No OSPF instance found."
|
||||
],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifySTPBlockedPorts",
|
||||
"categories": [
|
||||
"stp"
|
||||
],
|
||||
"description": "Verifies there is no STP blocked ports.",
|
||||
"result": "success",
|
||||
"messages": [],
|
||||
"custom_field": null
|
||||
},
|
||||
{
|
||||
"name": "s1-spine1",
|
||||
"test": "VerifyLANZ",
|
||||
"categories": [
|
||||
"lanz"
|
||||
],
|
||||
"description": "Verifies if LANZ is enabled.",
|
||||
"result": "skipped",
|
||||
"messages": [
|
||||
"VerifyLANZ test is not supported on cEOSLab."
|
||||
],
|
||||
"custom_field": null
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""ANTA Result Manager models unit tests."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""test anta.device.py."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.custom_types`.
|
||||
|
@ -15,12 +15,7 @@ import re
|
|||
import pytest
|
||||
|
||||
from anta.custom_types import (
|
||||
REGEX_BGP_IPV4_MPLS_VPN,
|
||||
REGEX_BGP_IPV4_UNICAST,
|
||||
REGEX_TYPE_PORTCHANNEL,
|
||||
REGEXP_BGP_IPV4_MPLS_LABELS,
|
||||
REGEXP_BGP_L2VPN_AFI,
|
||||
REGEXP_EOS_BLACKLIST_CMDS,
|
||||
REGEXP_INTERFACE_ID,
|
||||
REGEXP_PATH_MARKERS,
|
||||
REGEXP_TYPE_EOS_INTERFACE,
|
||||
|
@ -30,6 +25,7 @@ from anta.custom_types import (
|
|||
bgp_multiprotocol_capabilities_abbreviations,
|
||||
interface_autocomplete,
|
||||
interface_case_sensitivity,
|
||||
snmp_v3_prefix,
|
||||
validate_regex,
|
||||
)
|
||||
|
||||
|
@ -51,40 +47,6 @@ def test_regexp_path_markers() -> None:
|
|||
assert re.search(REGEXP_PATH_MARKERS, ".[]?<>") is None
|
||||
|
||||
|
||||
def test_regexp_bgp_l2vpn_afi() -> None:
|
||||
"""Test REGEXP_BGP_L2VPN_AFI."""
|
||||
# Test strings that should match the pattern
|
||||
assert re.search(REGEXP_BGP_L2VPN_AFI, "l2vpn-evpn") is not None
|
||||
assert re.search(REGEXP_BGP_L2VPN_AFI, "l2 vpn evpn") is not None
|
||||
assert re.search(REGEXP_BGP_L2VPN_AFI, "l2-vpn evpn") is not None
|
||||
assert re.search(REGEXP_BGP_L2VPN_AFI, "l2vpn evpn") is not None
|
||||
assert re.search(REGEXP_BGP_L2VPN_AFI, "l2vpnevpn") is not None
|
||||
assert re.search(REGEXP_BGP_L2VPN_AFI, "l2 vpnevpn") is not None
|
||||
|
||||
# Test strings that should not match the pattern
|
||||
assert re.search(REGEXP_BGP_L2VPN_AFI, "al2vpn evpn") is None
|
||||
assert re.search(REGEXP_BGP_L2VPN_AFI, "l2vpn-evpna") is None
|
||||
|
||||
|
||||
def test_regexp_bgp_ipv4_mpls_labels() -> None:
|
||||
"""Test REGEXP_BGP_IPV4_MPLS_LABELS."""
|
||||
assert re.search(REGEXP_BGP_IPV4_MPLS_LABELS, "ipv4-mpls-label") is not None
|
||||
assert re.search(REGEXP_BGP_IPV4_MPLS_LABELS, "ipv4 mpls labels") is not None
|
||||
assert re.search(REGEXP_BGP_IPV4_MPLS_LABELS, "ipv4Mplslabel") is None
|
||||
|
||||
|
||||
def test_regex_bgp_ipv4_mpls_vpn() -> None:
|
||||
"""Test REGEX_BGP_IPV4_MPLS_VPN."""
|
||||
assert re.search(REGEX_BGP_IPV4_MPLS_VPN, "ipv4-mpls-vpn") is not None
|
||||
assert re.search(REGEX_BGP_IPV4_MPLS_VPN, "ipv4_mplsvpn") is None
|
||||
|
||||
|
||||
def test_regex_bgp_ipv4_unicast() -> None:
|
||||
"""Test REGEX_BGP_IPV4_UNICAST."""
|
||||
assert re.search(REGEX_BGP_IPV4_UNICAST, "ipv4-uni-cast") is not None
|
||||
assert re.search(REGEX_BGP_IPV4_UNICAST, "ipv4+unicast") is None
|
||||
|
||||
|
||||
def test_regexp_type_interface_id() -> None:
|
||||
"""Test REGEXP_INTERFACE_ID."""
|
||||
intf_id_re = re.compile(f"{REGEXP_INTERFACE_ID}")
|
||||
|
@ -174,35 +136,6 @@ def test_regexp_type_hostname() -> None:
|
|||
assert re.match(REGEXP_TYPE_HOSTNAME, "hostname..com") is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("test_string", "expected"),
|
||||
[
|
||||
("reload", True), # matches "^reload.*"
|
||||
("reload now", True), # matches "^reload.*"
|
||||
("configure terminal", True), # matches "^conf\w*\s*(terminal|session)*"
|
||||
("conf t", True), # matches "^conf\w*\s*(terminal|session)*"
|
||||
("write memory", True), # matches "^wr\w*\s*\w+"
|
||||
("wr mem", True), # matches "^wr\w*\s*\w+"
|
||||
("show running-config", False), # does not match any regex
|
||||
("no shutdown", False), # does not match any regex
|
||||
("", False), # empty string does not match any regex
|
||||
],
|
||||
)
|
||||
def test_regexp_eos_blacklist_cmds(test_string: str, expected: bool) -> None:
|
||||
"""Test REGEXP_EOS_BLACKLIST_CMDS."""
|
||||
|
||||
def matches_any_regex(string: str, regex_list: list[str]) -> bool:
|
||||
"""Check if a string matches at least one regular expression in a list.
|
||||
|
||||
:param string: The string to check.
|
||||
:param regex_list: A list of regular expressions.
|
||||
:return: True if the string matches at least one regular expression, False otherwise.
|
||||
"""
|
||||
return any(re.match(regex, string) for regex in regex_list)
|
||||
|
||||
assert matches_any_regex(test_string, REGEXP_EOS_BLACKLIST_CMDS) == expected
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST custom_types.py functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -219,6 +152,7 @@ def test_interface_autocomplete_success() -> None:
|
|||
assert interface_autocomplete("lo4") == "Loopback4"
|
||||
assert interface_autocomplete("Po1000") == "Port-Channel1000"
|
||||
assert interface_autocomplete("Po 1000") == "Port-Channel1000"
|
||||
assert interface_autocomplete("Vl1000") == "Vlan1000"
|
||||
|
||||
|
||||
def test_interface_autocomplete_no_alias() -> None:
|
||||
|
@ -238,13 +172,29 @@ def test_interface_autocomplete_failure() -> None:
|
|||
("str_input", "expected_output"),
|
||||
[
|
||||
pytest.param("L2VPNEVPN", "l2VpnEvpn", id="l2VpnEvpn"),
|
||||
pytest.param("ipv4-mplsLabels", "ipv4MplsLabels", id="ipv4MplsLabels"),
|
||||
pytest.param("IPv4 Labeled Unicast", "ipv4MplsLabels", id="ipv4MplsLabels"),
|
||||
pytest.param("ipv4-mpls-vpn", "ipv4MplsVpn", id="ipv4MplsVpn"),
|
||||
pytest.param("ipv4-unicast", "ipv4Unicast", id="ipv4Unicast"),
|
||||
pytest.param("BLAH", "BLAH", id="unmatched"),
|
||||
pytest.param("ipv4_unicast", "ipv4Unicast", id="ipv4Unicast"),
|
||||
pytest.param("ipv4 Mvpn", "ipv4Mvpn", id="ipv4Mvpn"),
|
||||
pytest.param("ipv4_Flow-Spec Vpn", "ipv4FlowSpecVpn", id="ipv4FlowSpecVpn"),
|
||||
pytest.param("Dynamic-Path-Selection", "dps", id="dps"),
|
||||
pytest.param("ipv6unicast", "ipv6Unicast", id="ipv6Unicast"),
|
||||
pytest.param("IPv4-Multicast", "ipv4Multicast", id="ipv4Multicast"),
|
||||
pytest.param("IPv6_multicast", "ipv6Multicast", id="ipv6Multicast"),
|
||||
pytest.param("ipv6_Mpls-Labels", "ipv6MplsLabels", id="ipv6MplsLabels"),
|
||||
pytest.param("IPv4_SR_TE", "ipv4SrTe", id="ipv4SrTe"),
|
||||
pytest.param("iPv6-sR-tE", "ipv6SrTe", id="ipv6SrTe"),
|
||||
pytest.param("ipv6_mpls-vpn", "ipv6MplsVpn", id="ipv6MplsVpn"),
|
||||
pytest.param("IPv4 Flow-spec", "ipv4FlowSpec", id="ipv4FlowSpec"),
|
||||
pytest.param("IPv6Flow_spec", "ipv6FlowSpec", id="ipv6FlowSpec"),
|
||||
pytest.param("ipv6 Flow-Spec Vpn", "ipv6FlowSpecVpn", id="ipv6FlowSpecVpn"),
|
||||
pytest.param("L2VPN VPLS", "l2VpnVpls", id="l2VpnVpls"),
|
||||
pytest.param("link-state", "linkState", id="linkState"),
|
||||
pytest.param("RT_Membership", "rtMembership", id="rtMembership"),
|
||||
pytest.param("ipv4-RT_Membership", "rtMembership", id="rtMembership"),
|
||||
],
|
||||
)
|
||||
def test_bgp_multiprotocol_capabilities_abbreviationsh(str_input: str, expected_output: str) -> None:
|
||||
def test_bgp_multiprotocol_capabilities_abbreviations(str_input: str, expected_output: str) -> None:
|
||||
"""Test bgp_multiprotocol_capabilities_abbreviations."""
|
||||
assert bgp_multiprotocol_capabilities_abbreviations(str_input) == expected_output
|
||||
|
||||
|
@ -286,11 +236,7 @@ def test_interface_case_sensitivity_uppercase() -> None:
|
|||
@pytest.mark.parametrize(
|
||||
"str_input",
|
||||
[
|
||||
REGEX_BGP_IPV4_MPLS_VPN,
|
||||
REGEX_BGP_IPV4_UNICAST,
|
||||
REGEX_TYPE_PORTCHANNEL,
|
||||
REGEXP_BGP_IPV4_MPLS_LABELS,
|
||||
REGEXP_BGP_L2VPN_AFI,
|
||||
REGEXP_INTERFACE_ID,
|
||||
REGEXP_PATH_MARKERS,
|
||||
REGEXP_TYPE_EOS_INTERFACE,
|
||||
|
@ -314,3 +260,10 @@ def test_validate_regex_invalid(str_input: str, error: str) -> None:
|
|||
"""Test validate_regex with invalid regex."""
|
||||
with pytest.raises(ValueError, match=error):
|
||||
validate_regex(str_input)
|
||||
|
||||
|
||||
def test_snmp_v3_prefix_valid_input() -> None:
|
||||
"""Test snmp_v3_prefix with valid authentication type."""
|
||||
assert snmp_v3_prefix("auth") == "v3Auth"
|
||||
assert snmp_v3_prefix("noauth") == "v3NoAuth"
|
||||
assert snmp_v3_prefix("priv") == "v3Priv"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""test anta.decorators.py."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""test anta.device.py."""
|
||||
|
@ -589,11 +589,11 @@ class TestAntaDevice:
|
|||
if expected["cache_hit"] is True:
|
||||
assert cmd.output == cached_output
|
||||
assert current_cached_data == cached_output
|
||||
assert device.cache.hit_miss_ratio["hits"] == 2
|
||||
assert device.cache.stats["hits"] == 2
|
||||
else:
|
||||
assert cmd.output == COMMAND_OUTPUT
|
||||
assert current_cached_data == COMMAND_OUTPUT
|
||||
assert device.cache.hit_miss_ratio["hits"] == 1
|
||||
assert device.cache.stats["hits"] == 1
|
||||
else: # command is not allowed to use cache
|
||||
device._collect.assert_called_once_with(command=cmd, collection_id=None) # type: ignore[attr-defined]
|
||||
assert cmd.output == COMMAND_OUTPUT
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.logger."""
|
||||
|
@ -6,11 +6,54 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from anta.logger import anta_log_exception, exc_to_str, tb_to_str
|
||||
from anta.logger import Log, LogLevel, _get_file_handler, _get_rich_handler, anta_log_exception, exc_to_str, setup_logging, tb_to_str
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("level", "path", "debug_value"),
|
||||
[
|
||||
pytest.param(Log.INFO, None, False, id="INFO no file"),
|
||||
pytest.param(Log.DEBUG, None, False, id="DEBUG no file"),
|
||||
pytest.param(Log.INFO, Path("/tmp/file.log"), False, id="INFO file"),
|
||||
pytest.param(Log.DEBUG, Path("/tmp/file.log"), False, id="DEBUG file"),
|
||||
pytest.param(Log.INFO, None, True, id="INFO no file __DEBUG__ set"),
|
||||
pytest.param(Log.DEBUG, None, True, id="INFO no file __DEBUG__ set"),
|
||||
],
|
||||
)
|
||||
def test_setup_logging(level: LogLevel, path: Path | None, debug_value: bool) -> None:
|
||||
"""Test setup_logging."""
|
||||
# Clean up any logger on root
|
||||
root = logging.getLogger()
|
||||
if root.hasHandlers():
|
||||
root.handlers = []
|
||||
|
||||
with patch("anta.logger.__DEBUG__", new=debug_value):
|
||||
setup_logging(level, path)
|
||||
|
||||
rich_handler = _get_rich_handler(root)
|
||||
assert rich_handler is not None
|
||||
|
||||
# When __DEBUG__ is True, the log level is overwritten to DEBUG
|
||||
if debug_value:
|
||||
assert root.level == logging.DEBUG
|
||||
if path is not None:
|
||||
assert rich_handler.level == logging.INFO
|
||||
|
||||
if path is not None:
|
||||
assert _get_file_handler(root, path) is not None
|
||||
expected_handlers = 2
|
||||
else:
|
||||
expected_handlers = 1
|
||||
assert len(root.handlers) == expected_handlers
|
||||
|
||||
# Check idempotency
|
||||
setup_logging(level, path)
|
||||
assert len(root.handlers) == expected_handlers
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""test anta.models.py."""
|
||||
|
@ -515,7 +515,7 @@ ANTATEST_DATA: list[dict[str, Any]] = [
|
|||
},
|
||||
]
|
||||
|
||||
BLACKLIST_COMMANDS_PARAMS = ["reload", "reload --force", "write", "wr mem"]
|
||||
BLACKLIST_COMMANDS_PARAMS = ["reload", "reload now", "reload --force", "write", "wr mem", "write memory", "conf t", "configure terminal", "configure session"]
|
||||
|
||||
|
||||
class TestAntaTest:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 Arista Networks, Inc.
|
||||
# Use of this source code is governed by the Apache License 2.0
|
||||
# that can be found in the LICENSE file.
|
||||
"""test anta.runner.py."""
|
||||
|
@ -67,7 +67,7 @@ async def test_no_selected_device(caplog: pytest.LogCaptureFixture, inventory: A
|
|||
caplog.set_level(logging.WARNING)
|
||||
manager = ResultManager()
|
||||
await main(manager, inventory, FAKE_CATALOG, tags=tags, devices=devices)
|
||||
msg = f'No reachable device {f"matching the tags {tags} " if tags else ""}was found.{f" Selected devices: {devices} " if devices is not None else ""}'
|
||||
msg = f"No reachable device {f'matching the tags {tags} ' if tags else ''}was found.{f' Selected devices: {devices} ' if devices is not None else ''}"
|
||||
assert msg in caplog.messages
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2023-2024 Arista Networks, Inc.
|
||||
# Copyright (c) 2023-2025 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.tools`."""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue