Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(anta): Refactor VerifyInterfacesStatus test for nicer failure message #899

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions anta/input_models/interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.
"""Module containing input models for interface tests."""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel

from anta.custom_types import Interface


class InterfaceState(BaseModel):
"""Model for an interface state."""

name: Interface
carl-baillargeon marked this conversation as resolved.
Show resolved Hide resolved
"""Interface to validate."""
status: Literal["up", "down", "adminDown"]
"""Expected status of the interface."""
line_protocol_status: Literal["up", "down", "testing", "unknown", "dormant", "notPresent", "lowerLayerDown"] | None = None
"""Expected line protocol status of the interface."""
53 changes: 21 additions & 32 deletions anta/tests/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@

import re
from ipaddress import IPv4Network
from typing import Any, ClassVar, Literal
from typing import Any, ClassVar

from pydantic import BaseModel, Field
from pydantic_extra_types.mac_address import MacAddress

from anta import GITHUB_SUGGESTION
from anta.custom_types import EthernetInterface, Interface, Percent, PortChannelInterface, PositiveInteger
from anta.decorators import skip_on_platforms
from anta.input_models.interfaces import InterfaceState
from anta.models import AntaCommand, AntaTemplate, AntaTest
from anta.tools import custom_division, get_failed_logs, get_item, get_value

Expand Down Expand Up @@ -183,16 +184,20 @@ def test(self) -> None:


class VerifyInterfacesStatus(AntaTest):
"""Verifies if the provided list of interfaces are all in the expected state.
"""Verifies the operational states of specified interfaces to ensure they match expected configurations.

- If line protocol status is provided, prioritize checking against both status and line protocol status
- If line protocol status is not provided and interface status is "up", expect both status and line protocol to be "up"
- If interface status is not "up", check only the interface status without considering line protocol status
This test performs the following checks for each specified interface:
carl-baillargeon marked this conversation as resolved.
Show resolved Hide resolved

1. If `line_protocol_status` is defined, both `status` and `line_protocol_status` are verified for the specified interface.
2. If `line_protocol_status` is not provided but the `status` is "up", it is assumed that both the status and line protocol should be "up".
3. If the interface `status` is not "up", only the interface's status is validated, with no line protocol check performed.

Expected Results
----------------
* Success: The test will pass if the provided interfaces are all in the expected state.
* Failure: The test will fail if any interface is not in the expected state.
* Success: If the interface status and line protocol status matches the expected operational state for all specified interfaces.
* Failure: If any of the following occur:
- The specified interface is not configured.
- The specified interface status and line protocol status does not match the expected operational state for any interface.

Examples
--------
Expand All @@ -219,30 +224,17 @@ class Input(AntaTest.Input):

interfaces: list[InterfaceState]
"""List of interfaces with their expected state."""

class InterfaceState(BaseModel):
"""Model for an interface state."""

name: Interface
"""Interface to validate."""
status: Literal["up", "down", "adminDown"]
"""Expected status of the interface."""
line_protocol_status: Literal["up", "down", "testing", "unknown", "dormant", "notPresent", "lowerLayerDown"] | None = None
"""Expected line protocol status of the interface."""
InterfaceState: ClassVar[type[InterfaceState]] = InterfaceState
carl-baillargeon marked this conversation as resolved.
Show resolved Hide resolved

carl-baillargeon marked this conversation as resolved.
Show resolved Hide resolved
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyInterfacesStatus."""
command_output = self.instance_commands[0].json_output

self.result.is_success()

intf_not_configured = []
intf_wrong_state = []

command_output = self.instance_commands[0].json_output
for interface in self.inputs.interfaces:
if (intf_status := get_value(command_output["interfaceDescriptions"], interface.name, separator="..")) is None:
intf_not_configured.append(interface.name)
self.result.is_failure(f"{interface.name} - Not configured")
continue

status = "up" if intf_status["interfaceStatus"] in {"up", "connected"} else intf_status["interfaceStatus"]
Expand All @@ -251,18 +243,15 @@ 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:
intf_wrong_state.append(f"{interface.name} is {status}/{proto}")
actual_state = f"Expected: {interface.status}/{interface.line_protocol_status}, Actual: {status}/{proto}"
self.result.is_failure(f"{interface.name} - {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")) or (interface.status != status):
intf_wrong_state.append(f"{interface.name} is {status}/{proto}")

if intf_not_configured:
self.result.is_failure(f"The following interface(s) are not configured: {intf_not_configured}")

if intf_wrong_state:
self.result.is_failure(f"The following interface(s) are not in the expected state: {intf_wrong_state}")
elif interface.status == "up" and (status != "up" or proto != "up"):
self.result.is_failure(f"{interface.name} - Expected: up/up, Actual: {status}/{proto}")
elif interface.status != status:
self.result.is_failure(f"{interface.name} - Expected: {interface.status}, Actual: {status}")


class VerifyStormControlDrops(AntaTest):
Expand Down
15 changes: 15 additions & 0 deletions docs/api/tests.interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ anta_title: ANTA catalog for interfaces tests
~ that can be found in the LICENSE file.
-->

# Tests

::: anta.tests.interfaces

options:
show_root_heading: false
show_root_toc_entry: false
Expand All @@ -18,3 +21,15 @@ anta_title: ANTA catalog for interfaces tests
filters:
- "!test"
- "!render"

# Input models

::: anta.input_models.interfaces

options:
show_root_heading: false
show_root_toc_entry: false
show_bases: false
anta_hide_test_module_description: true
show_labels: true
filters: ["!^__str__"]
41 changes: 36 additions & 5 deletions tests/units/anta_tests/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@
"inputs": {"interfaces": [{"name": "Ethernet2", "status": "up"}, {"name": "Ethernet8", "status": "up"}, {"name": "Ethernet3", "status": "up"}]},
"expected": {
"result": "failure",
"messages": ["The following interface(s) are not configured: ['Ethernet8']"],
"messages": ["Ethernet8 - Not configured"],
},
},
{
Expand All @@ -1126,7 +1126,7 @@
"inputs": {"interfaces": [{"name": "Ethernet2", "status": "up"}, {"name": "Ethernet8", "status": "up"}, {"name": "Ethernet3", "status": "up"}]},
"expected": {
"result": "failure",
"messages": ["The following interface(s) are not in the expected state: ['Ethernet8 is down/down'"],
"messages": ["Ethernet8 - Expected: up/up, Actual: down/down"],
},
},
{
Expand All @@ -1150,7 +1150,7 @@
},
"expected": {
"result": "failure",
"messages": ["The following interface(s) are not in the expected state: ['Ethernet8 is up/down'"],
"messages": ["Ethernet8 - Expected: up/up, Actual: up/down"],
},
},
{
Expand All @@ -1166,7 +1166,7 @@
"inputs": {"interfaces": [{"name": "PortChannel100", "status": "up"}]},
"expected": {
"result": "failure",
"messages": ["The following interface(s) are not in the expected state: ['Port-Channel100 is down/lowerLayerDown'"],
"messages": ["Port-Channel100 - Expected: up/up, Actual: down/lowerLayerDown"],
},
},
{
Expand All @@ -1190,7 +1190,38 @@
},
"expected": {
"result": "failure",
"messages": ["The following interface(s) are not in the expected state: ['Ethernet2 is up/unknown'"],
"messages": [
"Ethernet2 - Expected: up/down, Actual: up/unknown",
"Ethernet8 - Expected: up/up, Actual: up/down",
carl-baillargeon marked this conversation as resolved.
Show resolved Hide resolved
],
},
},
{
"name": "failure-interface-status-down",
"test": VerifyInterfacesStatus,
"eos_data": [
{
"interfaceDescriptions": {
"Ethernet8": {"interfaceStatus": "up", "description": "", "lineProtocolStatus": "down"},
"Ethernet2": {"interfaceStatus": "up", "description": "", "lineProtocolStatus": "unknown"},
"Ethernet3": {"interfaceStatus": "up", "description": "", "lineProtocolStatus": "up"},
}
}
],
"inputs": {
"interfaces": [
{"name": "Ethernet2", "status": "down"},
{"name": "Ethernet8", "status": "down"},
{"name": "Ethernet3", "status": "down"},
]
},
"expected": {
"result": "failure",
"messages": [
"Ethernet2 - Expected: down, Actual: up",
"Ethernet8 - Expected: down, Actual: up",
"Ethernet3 - Expected: down, Actual: up",
],
},
},
{
Expand Down
Loading