diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 69a40ad95..6e9430db3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -46,7 +46,7 @@ repos:
- ""
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.9.7
+ rev: v0.9.9
hooks:
- id: ruff
name: Run Ruff linter
diff --git a/anta/reporter/md_reporter.py b/anta/reporter/md_reporter.py
index 16db69f9d..2d2d88245 100644
--- a/anta/reporter/md_reporter.py
+++ b/anta/reporter/md_reporter.py
@@ -177,8 +177,8 @@ def safe_markdown(self, text: str | None) -> str:
if text is None:
return ""
- # Replace newlines with spaces to keep content on one line
- text = text.replace("\n", " ")
+ # Replace newlines with
to preserve line breaks in HTML
+ text = text.replace("\n", "
")
# Replace backticks with single quotes
return text.replace("`", "'")
@@ -286,7 +286,7 @@ class TestResults(MDReportBase):
def generate_rows(self) -> Generator[str, None, None]:
"""Generate the rows of the all test results table."""
for result in self.results.results:
- messages = self.safe_markdown(", ".join(result.messages))
+ messages = self.safe_markdown(result.messages[0]) if len(result.messages) == 1 else self.safe_markdown("
".join(result.messages))
categories = ", ".join(sorted(convert_categories(result.categories)))
yield (
f"| {result.name or '-'} | {categories or '-'} | {result.test or '-'} "
diff --git a/anta/tests/hardware.py b/anta/tests/hardware.py
index 8ffe4e50d..f74c640bb 100644
--- a/anta/tests/hardware.py
+++ b/anta/tests/hardware.py
@@ -49,14 +49,14 @@ class Input(AntaTest.Input):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyTransceiversManufacturers."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
- wrong_manufacturers = {
- interface: value["mfgName"] for interface, value in command_output["xcvrSlots"].items() if value["mfgName"] not in self.inputs.manufacturers
- }
- if not wrong_manufacturers:
- self.result.is_success()
- else:
- self.result.is_failure(f"Some transceivers are from unapproved manufacturers: {wrong_manufacturers}")
+ for interface, value in command_output["xcvrSlots"].items():
+ if value["mfgName"] not in self.inputs.manufacturers:
+ self.result.is_failure(
+ f"Interface: {interface} - Transceiver is from unapproved manufacturers - Expected: {', '.join(self.inputs.manufacturers)}"
+ f" Actual: {value['mfgName']}"
+ )
class VerifyTemperature(AntaTest):
@@ -82,12 +82,11 @@ class VerifyTemperature(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyTemperature."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
temperature_status = command_output.get("systemStatus", "")
- if temperature_status == "temperatureOk":
- self.result.is_success()
- else:
- self.result.is_failure(f"Device temperature exceeds acceptable limits. Current system status: '{temperature_status}'")
+ if temperature_status != "temperatureOk":
+ self.result.is_failure(f"Device temperature exceeds acceptable limits - Expected: temperatureOk Actual: {temperature_status}")
class VerifyTransceiversTemperature(AntaTest):
@@ -113,20 +112,14 @@ class VerifyTransceiversTemperature(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyTransceiversTemperature."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
sensors = command_output.get("tempSensors", "")
- wrong_sensors = {
- sensor["name"]: {
- "hwStatus": sensor["hwStatus"],
- "alertCount": sensor["alertCount"],
- }
- for sensor in sensors
- if sensor["hwStatus"] != "ok" or sensor["alertCount"] != 0
- }
- if not wrong_sensors:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following sensors are operating outside the acceptable temperature range or have raised alerts: {wrong_sensors}")
+ for sensor in sensors:
+ if sensor["hwStatus"] != "ok":
+ self.result.is_failure(f"Sensor: {sensor['name']} - Invalid hardware state - Expected: ok Actual: {sensor['hwStatus']}")
+ if sensor["alertCount"] != 0:
+ self.result.is_failure(f"Sensor: {sensor['name']} - Non-zero alert counter - Actual: {sensor['alertCount']}")
class VerifyEnvironmentSystemCooling(AntaTest):
@@ -156,7 +149,7 @@ def test(self) -> None:
sys_status = command_output.get("systemStatus", "")
self.result.is_success()
if sys_status != "coolingOk":
- self.result.is_failure(f"Device system cooling is not OK: '{sys_status}'")
+ self.result.is_failure(f"Device system cooling status invalid - Expected: coolingOk Actual: {sys_status}")
class VerifyEnvironmentCooling(AntaTest):
@@ -177,8 +170,6 @@ class VerifyEnvironmentCooling(AntaTest):
```
"""
- name = "VerifyEnvironmentCooling"
- description = "Verifies the status of power supply fans and all fan trays."
categories: ClassVar[list[str]] = ["hardware"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show system environment cooling", revision=1)]
@@ -198,12 +189,16 @@ def test(self) -> None:
for power_supply in command_output.get("powerSupplySlots", []):
for fan in power_supply.get("fans", []):
if (state := fan["status"]) not in self.inputs.states:
- self.result.is_failure(f"Fan {fan['label']} on PowerSupply {power_supply['label']} is: '{state}'")
+ self.result.is_failure(
+ f"Power Slot: {power_supply['label']} Fan: {fan['label']} - Invalid state - Expected: {', '.join(self.inputs.states)} Actual: {state}"
+ )
# Then go through fan trays
for fan_tray in command_output.get("fanTraySlots", []):
for fan in fan_tray.get("fans", []):
if (state := fan["status"]) not in self.inputs.states:
- self.result.is_failure(f"Fan {fan['label']} on Fan Tray {fan_tray['label']} is: '{state}'")
+ self.result.is_failure(
+ f"Fan Tray: {fan_tray['label']} Fan: {fan['label']} - Invalid state - Expected: {', '.join(self.inputs.states)} Actual: {state}"
+ )
class VerifyEnvironmentPower(AntaTest):
@@ -237,19 +232,16 @@ class Input(AntaTest.Input):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyEnvironmentPower."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
power_supplies = command_output.get("powerSupplies", "{}")
- wrong_power_supplies = {
- powersupply: {"state": value["state"]} for powersupply, value in dict(power_supplies).items() if value["state"] not in self.inputs.states
- }
- if not wrong_power_supplies:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following power supplies status are not in the accepted states list: {wrong_power_supplies}")
+ for power_supply, value in dict(power_supplies).items():
+ if (state := value["state"]) not in self.inputs.states:
+ self.result.is_failure(f"Power Slot: {power_supply} - Invalid power supplies state - Expected: {', '.join(self.inputs.states)} Actual: {state}")
class VerifyAdverseDrops(AntaTest):
- """Verifies there are no adverse drops on DCS-7280 and DCS-7500 family switches (Arad/Jericho chips).
+ """Verifies there are no adverse drops on DCS-7280 and DCS-7500 family switches.
Expected Results
----------------
@@ -264,7 +256,6 @@ class VerifyAdverseDrops(AntaTest):
```
"""
- description = "Verifies there are no adverse drops on DCS-7280 and DCS-7500 family switches."
categories: ClassVar[list[str]] = ["hardware"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show hardware counter drop", revision=1)]
@@ -272,9 +263,8 @@ class VerifyAdverseDrops(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyAdverseDrops."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
total_adverse_drop = command_output.get("totalAdverseDrops", "")
- if total_adverse_drop == 0:
- self.result.is_success()
- else:
- self.result.is_failure(f"Device totalAdverseDrops counter is: '{total_adverse_drop}'")
+ if total_adverse_drop != 0:
+ self.result.is_failure(f"Non-zero total adverse drops counter - Actual: {total_adverse_drop}")
diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py
index 5701839de..052bbe1c7 100644
--- a/anta/tests/interfaces.py
+++ b/anta/tests/interfaces.py
@@ -8,7 +8,7 @@
from __future__ import annotations
import re
-from typing import Any, ClassVar, TypeVar
+from typing import ClassVar, TypeVar
from pydantic import BaseModel, Field, field_validator
from pydantic_extra_types.mac_address import MacAddress
@@ -61,8 +61,8 @@ class Input(AntaTest.Input):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyInterfaceUtilization."""
+ self.result.is_success()
duplex_full = "duplexFull"
- failed_interfaces: dict[str, dict[str, float]] = {}
rates = self.instance_commands[0].json_output
interfaces = self.instance_commands[1].json_output
@@ -78,15 +78,13 @@ def test(self) -> None:
self.logger.debug("Interface %s has been ignored due to null bandwidth value", intf)
continue
+ # If one or more interfaces have a usage above the threshold, test fails.
for bps_rate in ("inBpsRate", "outBpsRate"):
usage = rate[bps_rate] / bandwidth * 100
if usage > self.inputs.threshold:
- failed_interfaces.setdefault(intf, {})[bps_rate] = usage
-
- if not failed_interfaces:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following interfaces have a usage > {self.inputs.threshold}%: {failed_interfaces}")
+ self.result.is_failure(
+ f"Interface: {intf} BPS Rate: {bps_rate} - Usage exceeds the threshold - Expected: < {self.inputs.threshold}% Actual: {usage}%"
+ )
class VerifyInterfaceErrors(AntaTest):
@@ -111,15 +109,12 @@ class VerifyInterfaceErrors(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyInterfaceErrors."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
- wrong_interfaces: list[dict[str, dict[str, int]]] = []
for interface, counters in command_output["interfaceErrorCounters"].items():
- if any(value > 0 for value in counters.values()) and all(interface not in wrong_interface for wrong_interface in wrong_interfaces):
- wrong_interfaces.append({interface: counters})
- if not wrong_interfaces:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following interface(s) have non-zero error counters: {wrong_interfaces}")
+ counters_data = [f"{counter}: {value}" for counter, value in counters.items() if value > 0]
+ if counters_data:
+ self.result.is_failure(f"Interface: {interface} - Non-zero error counter(s) - {', '.join(counters_data)}")
class VerifyInterfaceDiscards(AntaTest):
@@ -144,14 +139,12 @@ class VerifyInterfaceDiscards(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyInterfaceDiscards."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
- wrong_interfaces: list[dict[str, dict[str, int]]] = []
- for interface, outer_v in command_output["interfaces"].items():
- wrong_interfaces.extend({interface: outer_v} for value in outer_v.values() if value > 0)
- if not wrong_interfaces:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following interfaces have non 0 discard counter(s): {wrong_interfaces}")
+ for interface, interface_data in command_output["interfaces"].items():
+ counters_data = [f"{counter}: {value}" for counter, value in interface_data.items() if value > 0]
+ if counters_data:
+ self.result.is_failure(f"Interface: {interface} - Non-zero discard counter(s): {', '.join(counters_data)}")
class VerifyInterfaceErrDisabled(AntaTest):
@@ -176,12 +169,11 @@ class VerifyInterfaceErrDisabled(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyInterfaceErrDisabled."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
- errdisabled_interfaces = [interface for interface, value in command_output["interfaceStatuses"].items() if value["linkStatus"] == "errdisabled"]
- if errdisabled_interfaces:
- self.result.is_failure(f"The following interfaces are in error disabled state: {errdisabled_interfaces}")
- else:
- self.result.is_success()
+ for interface, value in command_output["interfaceStatuses"].items():
+ if value["linkStatus"] == "errdisabled":
+ self.result.is_failure(f"Interface: {interface} - Link status Error disabled")
class VerifyInterfacesStatus(AntaTest):
@@ -253,16 +245,16 @@ def test(self) -> None:
# If line protocol status is provided, prioritize checking against both status and line protocol status
if interface.line_protocol_status:
- if interface.status != status or interface.line_protocol_status != proto:
+ if any([interface.status != status, interface.line_protocol_status != proto]):
actual_state = f"Expected: {interface.status}/{interface.line_protocol_status}, Actual: {status}/{proto}"
- self.result.is_failure(f"{interface.name} - {actual_state}")
+ self.result.is_failure(f"{interface.name} - Status mismatch - {actual_state}")
# If line protocol status is not provided and interface status is "up", expect both status and proto to be "up"
# If interface status is not "up", check only the interface status without considering line protocol status
- elif interface.status == "up" and (status != "up" or proto != "up"):
- self.result.is_failure(f"{interface.name} - Expected: up/up, Actual: {status}/{proto}")
+ elif all([interface.status == "up", status != "up" or proto != "up"]):
+ self.result.is_failure(f"{interface.name} - Status mismatch - Expected: up/up, Actual: {status}/{proto}")
elif interface.status != status:
- self.result.is_failure(f"{interface.name} - Expected: {interface.status}, Actual: {status}")
+ self.result.is_failure(f"{interface.name} - Status mismatch - Expected: {interface.status}, Actual: {status}")
class VerifyStormControlDrops(AntaTest):
@@ -289,16 +281,15 @@ class VerifyStormControlDrops(AntaTest):
def test(self) -> None:
"""Main test function for VerifyStormControlDrops."""
command_output = self.instance_commands[0].json_output
- storm_controlled_interfaces: dict[str, dict[str, Any]] = {}
+ storm_controlled_interfaces = []
+ self.result.is_success()
+
for interface, interface_dict in command_output["interfaces"].items():
for traffic_type, traffic_type_dict in interface_dict["trafficTypes"].items():
if "drop" in traffic_type_dict and traffic_type_dict["drop"] != 0:
- storm_controlled_interface_dict = storm_controlled_interfaces.setdefault(interface, {})
- storm_controlled_interface_dict.update({traffic_type: traffic_type_dict["drop"]})
- if not storm_controlled_interfaces:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following interfaces have none 0 storm-control drop counters {storm_controlled_interfaces}")
+ storm_controlled_interfaces.append(f"{traffic_type}: {traffic_type_dict['drop']}")
+ if storm_controlled_interfaces:
+ self.result.is_failure(f"Interface: {interface} - Non-zero storm-control drop counter(s) - {', '.join(storm_controlled_interfaces)}")
class VerifyPortChannels(AntaTest):
@@ -323,15 +314,12 @@ class VerifyPortChannels(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyPortChannels."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
- po_with_inactive_ports: list[dict[str, str]] = []
- for portchannel, portchannel_dict in command_output["portChannels"].items():
- if len(portchannel_dict["inactivePorts"]) != 0:
- po_with_inactive_ports.extend({portchannel: portchannel_dict["inactivePorts"]})
- if not po_with_inactive_ports:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following port-channels have inactive port(s): {po_with_inactive_ports}")
+ for port_channel, port_channel_details in command_output["portChannels"].items():
+ # Verify that the no inactive ports in all port channels.
+ if inactive_ports := port_channel_details["inactivePorts"]:
+ self.result.is_failure(f"{port_channel} - Inactive port(s) - {', '.join(inactive_ports.keys())}")
class VerifyIllegalLACP(AntaTest):
@@ -356,16 +344,13 @@ class VerifyIllegalLACP(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyIllegalLACP."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
- po_with_illegal_lacp: list[dict[str, dict[str, int]]] = []
- for portchannel, portchannel_dict in command_output["portChannels"].items():
- po_with_illegal_lacp.extend(
- {portchannel: interface} for interface, interface_dict in portchannel_dict["interfaces"].items() if interface_dict["illegalRxCount"] != 0
- )
- if not po_with_illegal_lacp:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following port-channels have received illegal LACP packets on the following ports: {po_with_illegal_lacp}")
+ for port_channel, port_channel_dict in command_output["portChannels"].items():
+ for interface, interface_details in port_channel_dict["interfaces"].items():
+ # Verify that the no illegal LACP packets in all port channels.
+ if interface_details["illegalRxCount"] != 0:
+ self.result.is_failure(f"{port_channel} Interface: {interface} - Illegal LACP packets found")
class VerifyLoopbackCount(AntaTest):
@@ -398,23 +383,20 @@ class Input(AntaTest.Input):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyLoopbackCount."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
loopback_count = 0
- down_loopback_interfaces = []
- for interface in command_output["interfaces"]:
- interface_dict = command_output["interfaces"][interface]
+ for interface, interface_details in command_output["interfaces"].items():
if "Loopback" in interface:
loopback_count += 1
- if not (interface_dict["lineProtocolStatus"] == "up" and interface_dict["interfaceStatus"] == "connected"):
- down_loopback_interfaces.append(interface)
- if loopback_count == self.inputs.number and len(down_loopback_interfaces) == 0:
- self.result.is_success()
- else:
- self.result.is_failure()
- if loopback_count != self.inputs.number:
- self.result.is_failure(f"Found {loopback_count} Loopbacks when expecting {self.inputs.number}")
- elif len(down_loopback_interfaces) != 0: # pragma: no branch
- self.result.is_failure(f"The following Loopbacks are not up: {down_loopback_interfaces}")
+ if (status := interface_details["lineProtocolStatus"]) != "up":
+ self.result.is_failure(f"Interface: {interface} - Invalid line protocol status - Expected: up Actual: {status}")
+
+ if (status := interface_details["interfaceStatus"]) != "connected":
+ self.result.is_failure(f"Interface: {interface} - Invalid interface status - Expected: connected Actual: {status}")
+
+ if loopback_count != self.inputs.number:
+ self.result.is_failure(f"Loopback interface(s) count mismatch: Expected {self.inputs.number} Actual: {loopback_count}")
class VerifySVI(AntaTest):
@@ -439,16 +421,13 @@ class VerifySVI(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySVI."""
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
- down_svis = []
- for interface in command_output["interfaces"]:
- interface_dict = command_output["interfaces"][interface]
- if "Vlan" in interface and not (interface_dict["lineProtocolStatus"] == "up" and interface_dict["interfaceStatus"] == "connected"):
- down_svis.append(interface)
- if len(down_svis) == 0:
- self.result.is_success()
- else:
- self.result.is_failure(f"The following SVIs are not up: {down_svis}")
+ for interface, int_data in command_output["interfaces"].items():
+ if "Vlan" in interface and (status := int_data["lineProtocolStatus"]) != "up":
+ self.result.is_failure(f"SVI: {interface} - Invalid line protocol status - Expected: up Actual: {status}")
+ if "Vlan" in interface and int_data["interfaceStatus"] != "connected":
+ self.result.is_failure(f"SVI: {interface} - Invalid interface status - Expected: connected Actual: {int_data['interfaceStatus']}")
class VerifyL3MTU(AntaTest):
@@ -493,8 +472,7 @@ class Input(AntaTest.Input):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyL3MTU."""
- # Parameter to save incorrect interface settings
- wrong_l3mtu_intf: list[dict[str, int]] = []
+ self.result.is_success()
command_output = self.instance_commands[0].json_output
# Set list of interfaces with specific settings
specific_interfaces: list[str] = []
@@ -504,14 +482,14 @@ def test(self) -> None:
for interface, values in command_output["interfaces"].items():
if re.findall(r"[a-z]+", interface, re.IGNORECASE)[0] not in self.inputs.ignored_interfaces and values["forwardingModel"] == "routed":
if interface in specific_interfaces:
- wrong_l3mtu_intf.extend({interface: values["mtu"]} for custom_data in self.inputs.specific_mtu if values["mtu"] != custom_data[interface])
+ invalid_mtu = next(
+ (values["mtu"] for custom_data in self.inputs.specific_mtu if values["mtu"] != (expected_mtu := custom_data[interface])), None
+ )
+ if invalid_mtu:
+ self.result.is_failure(f"Interface: {interface} - Incorrect MTU - Expected: {expected_mtu} Actual: {invalid_mtu}")
# Comparison with generic setting
elif values["mtu"] != self.inputs.mtu:
- wrong_l3mtu_intf.append({interface: values["mtu"]})
- if wrong_l3mtu_intf:
- self.result.is_failure(f"Some interfaces do not have correct MTU configured:\n{wrong_l3mtu_intf}")
- else:
- self.result.is_success()
+ self.result.is_failure(f"Interface: {interface} - Incorrect MTU - Expected: {self.inputs.mtu} Actual: {values['mtu']}")
class VerifyIPProxyARP(AntaTest):
diff --git a/anta/tests/routing/ospf.py b/anta/tests/routing/ospf.py
index 08bed8f89..a99ac18af 100644
--- a/anta/tests/routing/ospf.py
+++ b/anta/tests/routing/ospf.py
@@ -7,90 +7,15 @@
# mypy: disable-error-code=attr-defined
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, ClassVar
+from typing import TYPE_CHECKING, ClassVar
from anta.models import AntaCommand, AntaTest
+from anta.tools import get_value
if TYPE_CHECKING:
from anta.models import AntaTemplate
-def _count_ospf_neighbor(ospf_neighbor_json: dict[str, Any]) -> int:
- """Count the number of OSPF neighbors.
-
- Parameters
- ----------
- ospf_neighbor_json
- The JSON output of the `show ip ospf neighbor` command.
-
- Returns
- -------
- int
- The number of OSPF neighbors.
-
- """
- count = 0
- for vrf_data in ospf_neighbor_json["vrfs"].values():
- for instance_data in vrf_data["instList"].values():
- count += len(instance_data.get("ospfNeighborEntries", []))
- return count
-
-
-def _get_not_full_ospf_neighbors(ospf_neighbor_json: dict[str, Any]) -> list[dict[str, Any]]:
- """Return the OSPF neighbors whose adjacency state is not `full`.
-
- Parameters
- ----------
- ospf_neighbor_json
- The JSON output of the `show ip ospf neighbor` command.
-
- Returns
- -------
- list[dict[str, Any]]
- A list of OSPF neighbors whose adjacency state is not `full`.
-
- """
- return [
- {
- "vrf": vrf,
- "instance": instance,
- "neighbor": neighbor_data["routerId"],
- "state": state,
- }
- for vrf, vrf_data in ospf_neighbor_json["vrfs"].items()
- for instance, instance_data in vrf_data["instList"].items()
- for neighbor_data in instance_data.get("ospfNeighborEntries", [])
- if (state := neighbor_data["adjacencyState"]) != "full"
- ]
-
-
-def _get_ospf_max_lsa_info(ospf_process_json: dict[str, Any]) -> list[dict[str, Any]]:
- """Return information about OSPF instances and their LSAs.
-
- Parameters
- ----------
- ospf_process_json
- OSPF process information in JSON format.
-
- Returns
- -------
- list[dict[str, Any]]
- A list of dictionaries containing OSPF LSAs information.
-
- """
- return [
- {
- "vrf": vrf,
- "instance": instance,
- "maxLsa": instance_data.get("maxLsaInformation", {}).get("maxLsa"),
- "maxLsaThreshold": instance_data.get("maxLsaInformation", {}).get("maxLsaThreshold"),
- "numLsa": instance_data.get("lsaInformation", {}).get("numLsa"),
- }
- for vrf, vrf_data in ospf_process_json.get("vrfs", {}).items()
- for instance, instance_data in vrf_data.get("instList", {}).items()
- ]
-
-
class VerifyOSPFNeighborState(AntaTest):
"""Verifies all OSPF neighbors are in FULL state.
@@ -115,14 +40,29 @@ class VerifyOSPFNeighborState(AntaTest):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyOSPFNeighborState."""
- command_output = self.instance_commands[0].json_output
- if _count_ospf_neighbor(command_output) == 0:
- self.result.is_skipped("no OSPF neighbor found")
- return
self.result.is_success()
- not_full_neighbors = _get_not_full_ospf_neighbors(command_output)
- if not_full_neighbors:
- self.result.is_failure(f"Some neighbors are not correctly configured: {not_full_neighbors}.")
+
+ # If OSPF is not configured on device, test skipped.
+ if not (command_output := get_value(self.instance_commands[0].json_output, "vrfs")):
+ self.result.is_skipped("OSPF not configured")
+ return
+
+ no_neighbor = True
+ for vrf, vrf_data in command_output.items():
+ for instance, instance_data in vrf_data["instList"].items():
+ neighbors = instance_data["ospfNeighborEntries"]
+ if not neighbors:
+ continue
+ no_neighbor = False
+ interfaces = [(neighbor["routerId"], state) for neighbor in neighbors if (state := neighbor["adjacencyState"]) != "full"]
+ for interface in interfaces:
+ self.result.is_failure(
+ f"Instance: {instance} VRF: {vrf} Interface: {interface[0]} - Incorrect adjacency state - Expected: Full Actual: {interface[1]}"
+ )
+
+ # If OSPF neighbors are not configured on device, test skipped.
+ if no_neighbor:
+ self.result.is_skipped("No OSPF neighbor detected")
class VerifyOSPFNeighborCount(AntaTest):
@@ -156,20 +96,34 @@ class Input(AntaTest.Input):
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyOSPFNeighborCount."""
- command_output = self.instance_commands[0].json_output
- if (neighbor_count := _count_ospf_neighbor(command_output)) == 0:
- self.result.is_skipped("no OSPF neighbor found")
- return
self.result.is_success()
- if neighbor_count != self.inputs.number:
- self.result.is_failure(f"device has {neighbor_count} neighbors (expected {self.inputs.number})")
- not_full_neighbors = _get_not_full_ospf_neighbors(command_output)
- if not_full_neighbors:
- self.result.is_failure(f"Some neighbors are not correctly configured: {not_full_neighbors}.")
+ # If OSPF is not configured on device, test skipped.
+ if not (command_output := get_value(self.instance_commands[0].json_output, "vrfs")):
+ self.result.is_skipped("OSPF not configured")
+ return
+
+ no_neighbor = True
+ interfaces = []
+ for vrf_data in command_output.values():
+ for instance_data in vrf_data["instList"].values():
+ neighbors = instance_data["ospfNeighborEntries"]
+ if not neighbors:
+ continue
+ no_neighbor = False
+ interfaces.extend([neighbor["routerId"] for neighbor in neighbors if neighbor["adjacencyState"] == "full"])
+
+ # If OSPF neighbors are not configured on device, test skipped.
+ if no_neighbor:
+ self.result.is_skipped("No OSPF neighbor detected")
+ return
+
+ # If the number of OSPF neighbors expected to be in the FULL state does not match with actual one, test fails.
+ if len(interfaces) != self.inputs.number:
+ self.result.is_failure(f"Neighbor count mismatch - Expected: {self.inputs.number} Actual: {len(interfaces)}")
class VerifyOSPFMaxLSA(AntaTest):
- """Verifies LSAs present in the OSPF link state database did not cross the maximum LSA Threshold.
+ """Verifies all OSPF instances did not cross the maximum LSA threshold.
Expected Results
----------------
@@ -186,23 +140,23 @@ class VerifyOSPFMaxLSA(AntaTest):
```
"""
- description = "Verifies all OSPF instances did not cross the maximum LSA threshold."
categories: ClassVar[list[str]] = ["ospf"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show ip ospf", revision=1)]
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyOSPFMaxLSA."""
- command_output = self.instance_commands[0].json_output
- ospf_instance_info = _get_ospf_max_lsa_info(command_output)
- if not ospf_instance_info:
- self.result.is_skipped("No OSPF instance found.")
+ self.result.is_success()
+
+ # If OSPF is not configured on device, test skipped.
+ if not (command_output := get_value(self.instance_commands[0].json_output, "vrfs")):
+ self.result.is_skipped("OSPF not configured")
return
- all_instances_within_threshold = all(instance["numLsa"] <= instance["maxLsa"] * (instance["maxLsaThreshold"] / 100) for instance in ospf_instance_info)
- if all_instances_within_threshold:
- self.result.is_success()
- else:
- exceeded_instances = [
- instance["instance"] for instance in ospf_instance_info if instance["numLsa"] > instance["maxLsa"] * (instance["maxLsaThreshold"] / 100)
- ]
- self.result.is_failure(f"OSPF Instances {exceeded_instances} crossed the maximum LSA threshold.")
+
+ for vrf_data in command_output.values():
+ for instance, instance_data in vrf_data.get("instList", {}).items():
+ max_lsa = instance_data["maxLsaInformation"]["maxLsa"]
+ max_lsa_threshold = instance_data["maxLsaInformation"]["maxLsaThreshold"]
+ num_lsa = get_value(instance_data, "lsaInformation.numLsa")
+ if num_lsa > (max_lsa_threshold := round(max_lsa * (max_lsa_threshold / 100))):
+ self.result.is_failure(f"Instance: {instance} - Crossed the maximum LSA threshold - Expected: < {max_lsa_threshold} Actual: {num_lsa}")
diff --git a/tests/data/test_md_report.md b/tests/data/test_md_report.md
index db8d47f9a..1b1acd1a1 100644
--- a/tests/data/test_md_report.md
+++ b/tests/data/test_md_report.md
@@ -15,65 +15,78 @@
| Total Tests | Total Tests Success | Total Tests Skipped | Total Tests Failure | Total Tests Error |
| ----------- | ------------------- | ------------------- | ------------------- | ------------------|
-| 30 | 7 | 2 | 19 | 2 |
+| 30 | 4 | 9 | 15 | 2 |
### Summary Totals Device Under Test
| Device Under Test | Total Tests | Tests Success | Tests Skipped | Tests Failure | Tests Error | Categories Skipped | Categories Failed |
| ------------------| ----------- | ------------- | ------------- | ------------- | ----------- | -------------------| ------------------|
-| DC1-LEAF1A | 15 | 5 | 0 | 9 | 1 | - | AAA, BFD, BGP, Connectivity, SNMP, STP, Services, Software, System |
-| DC1-SPINE1 | 15 | 2 | 2 | 10 | 1 | MLAG, VXLAN | AAA, BFD, BGP, Connectivity, Routing, SNMP, STP, Services, Software, System |
+| s1-spine1 | 30 | 4 | 9 | 15 | 2 | AVT, Field Notices, Hardware, ISIS, LANZ, OSPF, PTP, Path-Selection, Profiles | AAA, BFD, BGP, Connectivity, Cvx, Interfaces, Logging, MLAG, SNMP, STUN, Security, Services, Software, System, VLAN |
### Summary Totals Per Category
| Test Category | Total Tests | Tests Success | Tests Skipped | Tests Failure | Tests Error |
| ------------- | ----------- | ------------- | ------------- | ------------- | ----------- |
-| AAA | 2 | 0 | 0 | 2 | 0 |
-| BFD | 2 | 0 | 0 | 2 | 0 |
-| BGP | 2 | 0 | 0 | 2 | 0 |
-| Connectivity | 4 | 0 | 0 | 2 | 2 |
-| Interfaces | 2 | 2 | 0 | 0 | 0 |
-| MLAG | 2 | 1 | 1 | 0 | 0 |
-| Routing | 2 | 1 | 0 | 1 | 0 |
-| SNMP | 2 | 0 | 0 | 2 | 0 |
-| STP | 2 | 0 | 0 | 2 | 0 |
-| Security | 2 | 2 | 0 | 0 | 0 |
-| Services | 2 | 0 | 0 | 2 | 0 |
-| Software | 2 | 0 | 0 | 2 | 0 |
-| System | 2 | 0 | 0 | 2 | 0 |
-| VXLAN | 2 | 1 | 1 | 0 | 0 |
+| AAA | 1 | 0 | 0 | 1 | 0 |
+| AVT | 1 | 0 | 1 | 0 | 0 |
+| BFD | 1 | 0 | 0 | 1 | 0 |
+| BGP | 1 | 0 | 0 | 0 | 1 |
+| Configuration | 1 | 1 | 0 | 0 | 0 |
+| Connectivity | 1 | 0 | 0 | 1 | 0 |
+| Cvx | 1 | 0 | 0 | 0 | 1 |
+| Field Notices | 1 | 0 | 1 | 0 | 0 |
+| Hardware | 1 | 0 | 1 | 0 | 0 |
+| Interfaces | 1 | 0 | 0 | 1 | 0 |
+| ISIS | 1 | 0 | 1 | 0 | 0 |
+| LANZ | 1 | 0 | 1 | 0 | 0 |
+| Logging | 1 | 0 | 0 | 1 | 0 |
+| MLAG | 1 | 0 | 0 | 1 | 0 |
+| OSPF | 1 | 0 | 1 | 0 | 0 |
+| Path-Selection | 1 | 0 | 1 | 0 | 0 |
+| Profiles | 1 | 0 | 1 | 0 | 0 |
+| PTP | 1 | 0 | 1 | 0 | 0 |
+| Routing | 1 | 1 | 0 | 0 | 0 |
+| Security | 2 | 0 | 0 | 2 | 0 |
+| Services | 1 | 0 | 0 | 1 | 0 |
+| SNMP | 1 | 0 | 0 | 1 | 0 |
+| Software | 1 | 0 | 0 | 1 | 0 |
+| STP | 1 | 1 | 0 | 0 | 0 |
+| STUN | 2 | 0 | 0 | 2 | 0 |
+| System | 1 | 0 | 0 | 1 | 0 |
+| VLAN | 1 | 0 | 0 | 1 | 0 |
+| VXLAN | 1 | 1 | 0 | 0 | 0 |
## Test Results
| Device Under Test | Categories | Test | Description | Custom Field | Result | Messages |
| ----------------- | ---------- | ---- | ----------- | ------------ | ------ | -------- |
-| DC1-LEAF1A | AAA | VerifyTacacsSourceIntf | Verifies TACACS source-interface for a specified VRF. | - | failure | Source-interface Management0 is not configured in VRF default |
-| DC1-LEAF1A | BFD | VerifyBFDSpecificPeers | Verifies the IPv4 BFD peer's sessions and remote disc in the specified VRF. | - | failure | Following BFD peers are not configured, status is not up or remote disc is zero: {'192.0.255.8': {'default': 'Not Configured'}, '192.0.255.7': {'default': 'Not Configured'}} |
-| DC1-LEAF1A | BGP | VerifyBGPPeerCount | Verifies the count of BGP peers. | - | failure | Failures: [{'afi': 'ipv4', 'safi': 'unicast', 'vrfs': {'PROD': 'Expected: 2, Actual: 1'}}, {'afi': 'ipv4', 'safi': 'multicast', 'vrfs': {'DEV': 'Expected: 3, Actual: 0'}}] |
-| DC1-LEAF1A | Connectivity | VerifyLLDPNeighbors | Verifies that the provided LLDP neighbors are connected properly. | - | failure | Wrong LLDP neighbor(s) on port(s): Ethernet1 DC1-SPINE1_Ethernet1 Ethernet2 DC1-SPINE2_Ethernet1 Port(s) not configured: Ethernet7 |
-| DC1-LEAF1A | Connectivity | VerifyReachability | Test the network reachability to one or many destination IP(s). | - | error | ping vrf MGMT 1.1.1.1 source Management1 repeat 2 has failed: No source interface Management1 |
-| DC1-LEAF1A | Interfaces | VerifyInterfaceUtilization | Verifies that the utilization of interfaces is below a certain threshold. | - | success | - |
-| DC1-LEAF1A | MLAG | VerifyMlagStatus | Verifies the health status of the MLAG configuration. | - | success | - |
-| DC1-LEAF1A | Routing | VerifyRoutingTableEntry | Verifies that the provided routes are present in the routing table of a specified VRF. | - | success | - |
-| DC1-LEAF1A | SNMP | VerifySnmpStatus | Verifies if the SNMP agent is enabled. | - | failure | SNMP agent disabled in vrf default |
-| DC1-LEAF1A | STP | VerifySTPMode | Verifies the configured STP mode for a provided list of VLAN(s). | - | failure | Wrong STP mode configured for the following VLAN(s): [10, 20] |
-| DC1-LEAF1A | Security | VerifyTelnetStatus | Verifies if Telnet is disabled in the default VRF. | - | success | - |
-| DC1-LEAF1A | Services | VerifyHostname | Verifies the hostname of a device. | - | failure | Expected 's1-spine1' as the hostname, but found 'DC1-LEAF1A' instead. |
-| DC1-LEAF1A | Software | VerifyEOSVersion | Verifies the EOS version of the device. | - | failure | device is running version "4.31.1F-34554157.4311F (engineering build)" not in expected versions: ['4.25.4M', '4.26.1F'] |
-| DC1-LEAF1A | System | VerifyNTP | Verifies if NTP is synchronised. | - | failure | The device is not synchronized with the configured NTP server(s): 'NTP is disabled.' |
-| DC1-LEAF1A | VXLAN | VerifyVxlan1Interface | Verifies the Vxlan1 interface status. | - | success | - |
-| DC1-SPINE1 | AAA | VerifyTacacsSourceIntf | Verifies TACACS source-interface for a specified VRF. | - | failure | Source-interface Management0 is not configured in VRF default |
-| DC1-SPINE1 | BFD | VerifyBFDSpecificPeers | Verifies the IPv4 BFD peer's sessions and remote disc in the specified VRF. | - | failure | Following BFD peers are not configured, status is not up or remote disc is zero: {'192.0.255.8': {'default': 'Not Configured'}, '192.0.255.7': {'default': 'Not Configured'}} |
-| DC1-SPINE1 | BGP | VerifyBGPPeerCount | Verifies the count of BGP peers. | - | failure | 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'}}] |
-| DC1-SPINE1 | Connectivity | VerifyLLDPNeighbors | Verifies that the provided LLDP neighbors are connected properly. | - | failure | Wrong LLDP neighbor(s) on port(s): Ethernet1 DC1-LEAF1A_Ethernet1 Ethernet2 DC1-LEAF1B_Ethernet1 Port(s) not configured: Ethernet7 |
-| DC1-SPINE1 | Connectivity | VerifyReachability | Test the network reachability to one or many destination IP(s). | - | error | ping vrf MGMT 1.1.1.1 source Management1 repeat 2 has failed: No source interface Management1 |
-| DC1-SPINE1 | Interfaces | VerifyInterfaceUtilization | Verifies that the utilization of interfaces is below a certain threshold. | - | success | - |
-| DC1-SPINE1 | MLAG | VerifyMlagStatus | Verifies the health status of the MLAG configuration. | - | skipped | MLAG is disabled |
-| DC1-SPINE1 | Routing | VerifyRoutingTableEntry | Verifies that the provided routes are present in the routing table of a specified VRF. | - | failure | The following route(s) are missing from the routing table of VRF default: ['10.1.0.2'] |
-| DC1-SPINE1 | SNMP | VerifySnmpStatus | Verifies if the SNMP agent is enabled. | - | failure | SNMP agent disabled in vrf default |
-| DC1-SPINE1 | STP | VerifySTPMode | Verifies the configured STP mode for a provided list of VLAN(s). | - | failure | STP mode 'rapidPvst' not configured for the following VLAN(s): [10, 20] |
-| DC1-SPINE1 | Security | VerifyTelnetStatus | Verifies if Telnet is disabled in the default VRF. | - | success | - |
-| DC1-SPINE1 | Services | VerifyHostname | Verifies the hostname of a device. | - | failure | Expected 's1-spine1' as the hostname, but found 'DC1-SPINE1' instead. |
-| DC1-SPINE1 | Software | VerifyEOSVersion | Verifies the EOS version of the device. | - | failure | device is running version "4.31.1F-34554157.4311F (engineering build)" not in expected versions: ['4.25.4M', '4.26.1F'] |
-| DC1-SPINE1 | System | VerifyNTP | Verifies if NTP is synchronised. | - | failure | The device is not synchronized with the configured NTP server(s): 'NTP is disabled.' |
-| DC1-SPINE1 | VXLAN | VerifyVxlan1Interface | Verifies the Vxlan1 interface status. | - | skipped | Vxlan1 interface is not configured |
+| s1-spine1 | AAA | VerifyAcctConsoleMethods | Verifies the AAA accounting console method lists for different accounting types (system, exec, commands, dot1x). | - | failure | AAA console accounting is not configured for commands, exec, system, dot1x |
+| s1-spine1 | AVT | VerifyAVTPathHealth | Verifies the status of all AVT paths for all VRFs. | - | skipped | VerifyAVTPathHealth test is not supported on cEOSLab. |
+| s1-spine1 | BFD | VerifyBFDPeersHealth | Verifies the health of IPv4 BFD peers across all VRFs. | - | failure | No IPv4 BFD peers are configured for any VRF. |
+| s1-spine1 | BGP | VerifyBGPAdvCommunities | Verifies that advertised communities are standard, extended and large for BGP IPv4 peer(s). | - | error | 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. |
+| s1-spine1 | Configuration | VerifyRunningConfigDiffs | Verifies there is no difference between the running-config and the startup-config. | - | success | - |
+| s1-spine1 | Connectivity | VerifyLLDPNeighbors | Verifies the connection status of the specified LLDP (Link Layer Discovery Protocol) neighbors. | - | failure | 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 |
+| s1-spine1 | Cvx | VerifyActiveCVXConnections | Verifies the number of active CVX Connections. | - | error | show cvx connections brief has failed: Unavailable command (controller not ready) (at token 2: 'connections') |
+| s1-spine1 | Field Notices | VerifyFieldNotice44Resolution | Verifies that the device is using the correct Aboot version per FN0044. | - | skipped | VerifyFieldNotice44Resolution test is not supported on cEOSLab. |
+| s1-spine1 | Hardware | VerifyTemperature | Verifies if the device temperature is within acceptable limits. | - | skipped | VerifyTemperature test is not supported on cEOSLab. |
+| s1-spine1 | Interfaces | VerifyIPProxyARP | Verifies if Proxy ARP is enabled. | - | failure | Interface: Ethernet1 - Proxy-ARP disabled
Interface: Ethernet2 - Proxy-ARP disabled |
+| s1-spine1 | ISIS | VerifyISISNeighborState | Verifies the health of IS-IS neighbors. | - | skipped | IS-IS not configured |
+| s1-spine1 | LANZ | VerifyLANZ | Verifies if LANZ is enabled. | - | skipped | VerifyLANZ test is not supported on cEOSLab. |
+| s1-spine1 | Logging | VerifyLoggingHosts | Verifies logging hosts (syslog servers) for a specified VRF. | - | failure | Syslog servers 1.1.1.1, 2.2.2.2 are not configured in VRF default |
+| s1-spine1 | MLAG | VerifyMlagDualPrimary | Verifies the MLAG dual-primary detection parameters. | - | failure | Dual-primary detection is disabled |
+| s1-spine1 | OSPF | VerifyOSPFMaxLSA | Verifies all OSPF instances did not cross the maximum LSA threshold. | - | skipped | No OSPF instance found. |
+| s1-spine1 | Path-Selection | VerifyPathsHealth | Verifies the path and telemetry state of all paths under router path-selection. | - | skipped | VerifyPathsHealth test is not supported on cEOSLab. |
+| s1-spine1 | Profiles | VerifyTcamProfile | Verifies the device TCAM profile. | - | skipped | VerifyTcamProfile test is not supported on cEOSLab. |
+| s1-spine1 | PTP | VerifyPtpGMStatus | Verifies that the device is locked to a valid PTP Grandmaster. | - | skipped | VerifyPtpGMStatus test is not supported on cEOSLab. |
+| s1-spine1 | Routing | VerifyIPv4RouteNextHops | Verifies the next-hops of the IPv4 prefixes. | - | success | - |
+| s1-spine1 | Security | VerifyBannerLogin | Verifies the login banner of a device. | - | failure | Expected '# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
' as the login banner, but found '' instead. |
+| s1-spine1 | Security | VerifyBannerMotd | Verifies the motd banner of a device. | - | failure | Expected '# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
' as the motd banner, but found '' instead. |
+| s1-spine1 | Services | VerifyHostname | Verifies the hostname of a device. | - | failure | Incorrect Hostname - Expected: s1-spine1 Actual: leaf1-dc1 |
+| s1-spine1 | SNMP | VerifySnmpContact | Verifies the SNMP contact of a device. | - | failure | SNMP contact is not configured. |
+| s1-spine1 | Software | VerifyEOSVersion | Verifies the EOS version of the device. | - | failure | EOS version mismatch - Actual: 4.31.0F-33804048.4310F (engineering build) not in Expected: 4.25.4M, 4.26.1F |
+| s1-spine1 | STP | VerifySTPBlockedPorts | Verifies there is no STP blocked ports. | - | success | - |
+| s1-spine1 | STUN | VerifyStunClient | (Deprecated) Verifies the translation for a source address on a STUN client. | - | failure | Client 172.18.3.2 Port: 4500 - STUN client translation not found. |
+| s1-spine1 | STUN | VerifyStunClientTranslation | Verifies the translation for a source address on a STUN client. | - | failure | Client 172.18.3.2 Port: 4500 - STUN client translation not found.
Client 100.64.3.2 Port: 4500 - STUN client translation not found. |
+| s1-spine1 | System | VerifyNTPAssociations | Verifies the Network Time Protocol (NTP) associations. | - | failure | 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 |
+| s1-spine1 | VLAN | VerifyDynamicVlanSource | Verifies dynamic VLAN allocation for specified VLAN sources. | - | failure | Dynamic VLAN source(s) exist but have no VLANs allocated: mlagsync |
+| s1-spine1 | VXLAN | VerifyVxlan1ConnSettings | Verifies the interface vxlan1 source interface and UDP port. | - | success | - |
diff --git a/tests/units/anta_tests/routing/test_ospf.py b/tests/units/anta_tests/routing/test_ospf.py
index 644cd76fa..0c736dcd0 100644
--- a/tests/units/anta_tests/routing/test_ospf.py
+++ b/tests/units/anta_tests/routing/test_ospf.py
@@ -122,13 +122,13 @@
"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 @@
},
],
"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 @@
"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 @@
"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 @@
},
],
"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 @@
"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 @@
},
],
"inputs": None,
- "expected": {"result": "skipped", "messages": ["No OSPF instance found."]},
+ "expected": {"result": "skipped", "messages": ["OSPF not configured"]},
},
]
diff --git a/tests/units/anta_tests/test_hardware.py b/tests/units/anta_tests/test_hardware.py
index d6993c5f2..0dffe6925 100644
--- a/tests/units/anta_tests/test_hardware.py
+++ b/tests/units/anta_tests/test_hardware.py
@@ -45,7 +45,13 @@
},
],
"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 @@
"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 @@
"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 @@
"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 - Non-zero alert counter - Actual: 1"],
},
},
{
@@ -227,7 +225,7 @@
},
],
"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 @@
},
],
"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 @@
},
],
"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 @@
},
],
"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 @@
"test": VerifyAdverseDrops,
"eos_data": [{"totalAdverseDrops": 10}],
"inputs": None,
- "expected": {"result": "failure", "messages": ["Device totalAdverseDrops counter is: '10'"]},
+ "expected": {"result": "failure", "messages": ["Non-zero total adverse drops counter - Actual: 10"]},
},
]
diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py
index 4d2bcb4ad..65f60006f 100644
--- a/tests/units/anta_tests/test_interfaces.py
+++ b/tests/units/anta_tests/test_interfaces.py
@@ -508,7 +508,10 @@
"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 @@
"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 @@
},
"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 @@
"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 @@
"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 @@
"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 @@
"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 @@
"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 @@
},
],
"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 @@
"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 @@
},
"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 @@
"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 @@
"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 @@
"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 @@
},
],
"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 @@
},
],
"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 @@
"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 @@
},
"Loopback666": {
"name": "Loopback666",
- "interfaceStatus": "connected",
+ "interfaceStatus": "notconnect",
"interfaceAddress": {"ipAddr": {"maskLen": 32, "address": "6.6.6.6"}},
"ipv4Routable240": False,
"lineProtocolStatus": "down",
@@ -1427,7 +1425,13 @@
},
],
"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 @@
},
],
"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 @@
},
],
"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 @@
},
],
"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",
diff --git a/tests/units/result_manager/conftest.py b/tests/units/result_manager/conftest.py
index 0586d63cb..e414c4655 100644
--- a/tests/units/result_manager/conftest.py
+++ b/tests/units/result_manager/conftest.py
@@ -34,12 +34,12 @@ def _factory(number: int = 0) -> ResultManager:
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.
diff --git a/tests/units/result_manager/test__init__.py b/tests/units/result_manager/test__init__.py
index 1ac309fd2..e70dbf9e5 100644
--- a/tests/units/result_manager/test__init__.py
+++ b/tests/units/result_manager/test__init__.py
@@ -195,12 +195,12 @@ def test_get_results(self, result_manager: ResultManager) -> None:
"""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
@@ -212,19 +212,18 @@ def test_get_results_sort_by(self, result_manager: ResultManager) -> None:
# 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(
@@ -241,14 +240,14 @@ def test_get_total_results(self, result_manager: ResultManager) -> None:
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(
diff --git a/tests/units/result_manager/test_files/test_md_report_results.json b/tests/units/result_manager/test_files/test_md_report_results.json
index b9ecc0c57..ab932dc5b 100644
--- a/tests/units/result_manager/test_files/test_md_report_results.json
+++ b/tests/units/result_manager/test_files/test_md_report_results.json
@@ -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
+ }
]