Skip to content

Commit

Permalink
refactor(anta): Refactor VerifyLLDPNeighbors test for nicer failure m…
Browse files Browse the repository at this point in the history
…essage (#915)

---------

Co-authored-by: VitthalMagadum <[email protected]>
Co-authored-by: Guillaume Mulocher <[email protected]>
  • Loading branch information
3 people authored Nov 13, 2024
1 parent 2931fe1 commit 8ac4477
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 83 deletions.
42 changes: 42 additions & 0 deletions anta/input_models/connectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from __future__ import annotations

from ipaddress import IPv4Address
from typing import Any
from warnings import warn

from pydantic import BaseModel, ConfigDict

Expand Down Expand Up @@ -39,3 +41,43 @@ def __str__(self) -> str:
"""
df_status = ", df-bit: enabled" if self.df_bit else ""
return f"Host {self.destination} (src: {self.source}, vrf: {self.vrf}, size: {self.size}B, repeat: {self.repeat}{df_status})"


class LLDPNeighbor(BaseModel):
"""LLDP (Link Layer Discovery Protocol) model representing the port details and neighbor information."""

model_config = ConfigDict(extra="forbid")
port: Interface
"""The LLDP port for the local device."""
neighbor_device: str
"""The system name of the LLDP neighbor device."""
neighbor_port: Interface
"""The LLDP port on the neighboring device."""

def __str__(self) -> str:
"""Return a human-readable string representation of the LLDPNeighbor for reporting.
Examples
--------
Port Ethernet1 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet2)
"""
return f"Port {self.port} (Neighbor: {self.neighbor_device}, Neighbor Port: {self.neighbor_port})"


class Neighbor(LLDPNeighbor): # pragma: no cover
"""Alias for the LLDPNeighbor model to maintain backward compatibility.
When initialized, it will emit a deprecation warning and call the LLDPNeighbor model.
TODO: Remove this class in ANTA v2.0.0.
"""

def __init__(self, **data: Any) -> None: # noqa: ANN401
"""Initialize the LLDPNeighbor class, emitting a depreciation warning."""
warn(
message="Neighbor model is deprecated and will be removed in ANTA v2.0.0. Use the LLDPNeighbor model instead.",
category=DeprecationWarning,
stacklevel=2,
)
super().__init__(**data)
63 changes: 22 additions & 41 deletions anta/tests/connectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@

from typing import ClassVar

from pydantic import BaseModel

from anta.custom_types import Interface
from anta.input_models.connectivity import Host
from anta.input_models.connectivity import Host, LLDPNeighbor, Neighbor
from anta.models import AntaCommand, AntaTemplate, AntaTest


Expand Down Expand Up @@ -77,14 +74,20 @@ def test(self) -> None:


class VerifyLLDPNeighbors(AntaTest):
"""Verifies that the provided LLDP neighbors are present and connected with the correct configuration.
"""Verifies the connection status of the specified LLDP (Link Layer Discovery Protocol) neighbors.
This test performs the following checks for each specified LLDP neighbor:
1. Confirming matching ports on both local and neighboring devices.
2. Ensuring compatibility of device names and interface identifiers.
3. Verifying neighbor configurations match expected values per interface; extra neighbors are ignored.
Expected Results
----------------
* Success: The test will pass if each of the provided LLDP neighbors is present and connected to the specified port and device.
* Success: The test will pass if all the provided LLDP neighbors are present and correctly connected to the specified port and device.
* Failure: The test will fail if any of the following conditions are met:
- The provided LLDP neighbor is not found.
- The system name or port of the LLDP neighbor does not match the provided information.
- The provided LLDP neighbor is not found in the LLDP table.
- The system name or port of the LLDP neighbor does not match the expected information.
Examples
--------
Expand All @@ -108,52 +111,30 @@ class VerifyLLDPNeighbors(AntaTest):
class Input(AntaTest.Input):
"""Input model for the VerifyLLDPNeighbors test."""

neighbors: list[Neighbor]
neighbors: list[LLDPNeighbor]
"""List of LLDP neighbors."""

class Neighbor(BaseModel):
"""Model for an LLDP neighbor."""

port: Interface
"""LLDP port."""
neighbor_device: str
"""LLDP neighbor device."""
neighbor_port: Interface
"""LLDP neighbor port."""
Neighbor: ClassVar[type[Neighbor]] = Neighbor

@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyLLDPNeighbors."""
failures: dict[str, list[str]] = {}
self.result.is_success()

output = self.instance_commands[0].json_output["lldpNeighbors"]

for neighbor in self.inputs.neighbors:
if neighbor.port not in output:
failures.setdefault("Port(s) not configured", []).append(neighbor.port)
self.result.is_failure(f"{neighbor} - Port not found")
continue

if len(lldp_neighbor_info := output[neighbor.port]["lldpNeighborInfo"]) == 0:
failures.setdefault("No LLDP neighbor(s) on port(s)", []).append(neighbor.port)
self.result.is_failure(f"{neighbor} - No LLDP neighbors")
continue

if not any(
# Check if the system name and neighbor port matches
match_found = any(
info["systemName"] == neighbor.neighbor_device and info["neighborInterfaceInfo"]["interfaceId_v2"] == neighbor.neighbor_port
for info in lldp_neighbor_info
):
neighbors = "\n ".join(
[
f"{neighbor[0]}_{neighbor[1]}"
for neighbor in [(info["systemName"], info["neighborInterfaceInfo"]["interfaceId_v2"]) for info in lldp_neighbor_info]
]
)
failures.setdefault("Wrong LLDP neighbor(s) on port(s)", []).append(f"{neighbor.port}\n {neighbors}")

if not failures:
self.result.is_success()
else:
failure_messages = []
for failure_type, ports in failures.items():
ports_str = "\n ".join(ports)
failure_messages.append(f"{failure_type}:\n {ports_str}")
self.result.is_failure("\n".join(failure_messages))
)
if not match_found:
failure_msg = [f"{info['systemName']}/{info['neighborInterfaceInfo']['interfaceId_v2']}" for info in lldp_neighbor_info]
self.result.is_failure(f"{neighbor} - Wrong LLDP neighbors: {', '.join(failure_msg)}")
90 changes: 48 additions & 42 deletions tests/units/anta_tests/test_connectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,6 @@
{
"name": "success",
"test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
"eos_data": [
{
"lldpNeighbors": {
Expand Down Expand Up @@ -256,16 +250,17 @@
},
},
],
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
"expected": {"result": "success"},
},
{
"name": "success-multiple-neighbors",
"test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
"eos_data": [
{
"lldpNeighbors": {
Expand Down Expand Up @@ -298,17 +293,16 @@
},
},
],
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
"expected": {"result": "success"},
},
{
"name": "failure-port-not-configured",
"test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
"eos_data": [
{
"lldpNeighbors": {
Expand All @@ -330,17 +324,17 @@
},
},
],
"expected": {"result": "failure", "messages": ["Port(s) not configured:\n Ethernet2"]},
},
{
"name": "failure-no-neighbor",
"test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
"expected": {"result": "failure", "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - Port not found"]},
},
{
"name": "failure-no-neighbor",
"test": VerifyLLDPNeighbors,
"eos_data": [
{
"lldpNeighbors": {
Expand All @@ -363,17 +357,17 @@
},
},
],
"expected": {"result": "failure", "messages": ["No LLDP neighbor(s) on port(s):\n Ethernet2"]},
},
{
"name": "failure-wrong-neighbor",
"test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
],
},
"expected": {"result": "failure", "messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - No LLDP neighbors"]},
},
{
"name": "failure-wrong-neighbor",
"test": VerifyLLDPNeighbors,
"eos_data": [
{
"lldpNeighbors": {
Expand Down Expand Up @@ -410,18 +404,20 @@
},
},
],
"expected": {"result": "failure", "messages": ["Wrong LLDP neighbor(s) on port(s):\n Ethernet2\n DC1-SPINE2_Ethernet2"]},
},
{
"name": "failure-multiple",
"test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
{"port": "Ethernet3", "neighbor_device": "DC1-SPINE3", "neighbor_port": "Ethernet1"},
],
},
"expected": {
"result": "failure",
"messages": ["Port Ethernet2 (Neighbor: DC1-SPINE2, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE2/Ethernet2"],
},
},
{
"name": "failure-multiple",
"test": VerifyLLDPNeighbors,
"eos_data": [
{
"lldpNeighbors": {
Expand All @@ -444,23 +440,25 @@
},
},
],
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE1", "neighbor_port": "Ethernet1"},
{"port": "Ethernet2", "neighbor_device": "DC1-SPINE2", "neighbor_port": "Ethernet1"},
{"port": "Ethernet3", "neighbor_device": "DC1-SPINE3", "neighbor_port": "Ethernet1"},
],
},
"expected": {
"result": "failure",
"messages": [
"Wrong LLDP neighbor(s) on port(s):\n Ethernet1\n DC1-SPINE1_Ethernet2\n"
"No LLDP neighbor(s) on port(s):\n Ethernet2\n"
"Port(s) not configured:\n Ethernet3"
"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",
],
},
},
{
"name": "failure-multiple-neighbors",
"test": VerifyLLDPNeighbors,
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE3", "neighbor_port": "Ethernet1"},
],
},
"eos_data": [
{
"lldpNeighbors": {
Expand Down Expand Up @@ -493,6 +491,14 @@
},
},
],
"expected": {"result": "failure", "messages": ["Wrong LLDP neighbor(s) on port(s):\n Ethernet1\n DC1-SPINE1_Ethernet1\n DC1-SPINE2_Ethernet1"]},
"inputs": {
"neighbors": [
{"port": "Ethernet1", "neighbor_device": "DC1-SPINE3", "neighbor_port": "Ethernet1"},
],
},
"expected": {
"result": "failure",
"messages": ["Port Ethernet1 (Neighbor: DC1-SPINE3, Neighbor Port: Ethernet1) - Wrong LLDP neighbors: DC1-SPINE1/Ethernet1, DC1-SPINE2/Ethernet1"],
},
},
]

0 comments on commit 8ac4477

Please sign in to comment.