8000 feat(anta.tests): Added fix for VerifyL3MTU and VerifyL2MTU -Interface Ignorance for Specific or All Interfaces of a Given Keyword by geetanjalimanegslab · Pull Request #1145 · aristanetworks/anta · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(anta.tests): Added fix for VerifyL3MTU and VerifyL2MTU -Interface Ignorance for Specific or All Interfaces of a Given Keyword #1145

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

Merged
Merged
9 changes: 8 additions & 1 deletion anta/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
"""Match directory path from string."""
REGEXP_INTERFACE_ID = r"\d+(\/\d+)*(\.\d+)?"
"""Match Interface ID lilke 1/1.1."""
REGEXP_TYPE_EOS_INTERFACE = r"^(Dps|Ethernet|Fabric|Loopback|Management|Port-Channel|Tunnel|Vlan|Vxlan)[0-9]+(\/[0-9]+)*(\.[0-9]+)?$"
REGEXP_TYPE_EOS_INTERFACE = r"^(Dps|Ethernet|Fabric|Loopback|Management|Port-Channel|Recirc-Channel|Tunnel|Vlan|Vxlan)[0-9]+(\/[0-9]+)*(\.[0-9]+)?$"
"""Match EOS interface types like Ethernet1/1, Vlan1, Loopback1, etc."""
REGEXP_TYPE_VXLAN_SRC_INTERFACE = r"^(Loopback)([0-9]|[1-9][0-9]{1,2}|[1-7][0-9]{3}|8[01][0-9]{2}|819[01])$"
"""Match Vxlan source interface like Loopback10."""
REGEX_TYPE_PORTCHANNEL = r"^Port-Channel[0-9]{1,6}$"
"""Match Port Channel interface like Port-Channel5."""
REGEXP_EOS_INTERFACE_TYPE = r"^(Dps|Ethernet|Fabric|Loopback|Management|Port-Channel|Recirc-Channel|Tunnel|Vlan|Vxlan)$"
"""Match an EOS interface type like Ethernet or Loopback."""
REGEXP_TYPE_HOSTNAME = r"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$"
"""Match hostname like `my-hostname`, `my-hostname-1`, `my-hostname-1-2`."""

Expand Down Expand Up @@ -233,6 +235,11 @@ def convert_reload_cause(value: str) -> str:
BeforeValidator(interface_autocomplete),
BeforeValidator(interface_case_sensitivity),
]
InterfaceType = Annotated[
str,
Field(pattern=REGEXP_EOS_INTERFACE_TYPE),
BeforeValidator(interface_case_sensitivity),
]
Afi = Literal["ipv4", "ipv6", "vpn-ipv4", "vpn-ipv6", "evpn", "rt-membership", "path-selection", "link-state"]
Safi = Literal["unicast", "multicast", "labeled-unicast", "sr-te"]
EncryptionAlgorithm = Literal["RSA", "ECDSA"]
Expand Down
134 changes: 87 additions & 47 deletions anta/tests/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from pydantic import Field, field_validator
from pydantic_extra_types.mac_address import MacAddress

from anta.custom_types import Interface, Percent, PositiveInteger
from anta.custom_types import Interface, InterfaceType, Percent, PositiveInteger
from anta.decorators import skip_on_platforms
from anta.input_models.interfaces import InterfaceDetail, InterfaceState
from anta.models import AntaCommand, AntaTemplate, AntaTest
Expand All @@ -25,6 +25,46 @@
T = TypeVar("T", bound=InterfaceState)


def _is_interface_ignored(interface: str, ignored_interfaces: list[str] | None = None) -> bool | None:
"""Verify if an interface is present in the ignored interfaces list.

Parameters
----------
interface
This is a string containing the interface name.
ignored_interfaces
A list containing the interfaces or interface types to ignore.

Returns
-------
bool
True if the interface is in the list of ignored interfaces, false otherwise.
Example
-------
>>> _is_interface_ignored(interface="Ethernet1", ignored_interfaces=["Ethernet", "Port-Channel1"])
True
>>> _is_interface_ignored(interface="Ethernet2", ignored_interfaces=["Ethernet1", "Port-Channel"])
False
>>> _is_interface_ignored(interface="Port-Channel1", ignored_interfaces=["Ethernet1", "Port-Channel"])
True
>>> _is_interface_ignored(interface="Ethernet1/1", ignored_interfaces: ["Ethernet1/1", "Port-Channel"])
True
>>> _is_interface_ignored(interface="Ethernet1/1", ignored_interfaces: ["Ethernet1", "Port-Channel"])
False
>>> _is_interface_ignored(interface="Ethernet1.100", ignored_interfaces: ["Ethernet1.100", "Port-Channel"])
True
"""
interface_prefix = re.findall(r"^[a-zA-Z-]+", interface, re.IGNORECASE)[0]
interface_exact_match = False
if ignored_interfaces:
for ignored_interface in ignored_interfaces:
if interface == ignored_interface:
interface_exact_match = True
break
return bool(any([interface_exact_match, interface_prefix in ignored_interfaces]))
return None


class VerifyInterfaceUtilization(AntaTest):
"""Verifies that the utilization of interfaces is below a certain threshold.

Expand Down Expand Up @@ -441,11 +481,9 @@ def test(self) -> None:


class VerifyL3MTU(AntaTest):
"""Verifies the global layer 3 Maximum Transfer Unit (MTU) for all L3 interfaces.

Test that L3 interfaces are configured with the correct MTU. It supports Ethernet, Port Channel and VLAN interfaces.
"""Verifies the L3 MTU of routed interfaces.

You can define a global MTU to check, or an MTU per interface and you can also ignored some interfaces.
Test that layer 3 (routed) interfaces are configured with the correct MTU.

Expected Results
----------------
Expand All @@ -459,9 +497,11 @@ class VerifyL3MTU(AntaTest):
- VerifyL3MTU:
mtu: 1500
ignored_interfaces:
- Vxlan1
- Management # Ignore all Management interfaces
- Ethernet2.100
- Ethernet1/1
specific_mtu:
- Ethernet1: 2500
- Ethernet10: 9200
```
"""

Expand All @@ -473,33 +513,31 @@ class Input(AntaTest.Input):
"""Input model for the VerifyL3MTU test."""

mtu: int = 1500
"""Default MTU we should have configured on all non-excluded interfaces. Defaults to 1500."""
ignored_interfaces: list[str] = Field(default=["Management", "Loopback", "Vxlan", "Tunnel"])
"""A list of L3 interfaces to ignore"""
specific_mtu: list[dict[str, int]] = Field(default=[])
"""A list of dictionary of L3 interfaces with their specific MTU configured"""
"""Expected L3 MTU configured on all non-excluded interfaces."""
ignored_interfaces: list[InterfaceType | Interface] = Field(default=["Dps", "Fabric", "Loopback", "Management", "Recirc-Channel", "Tunnel", "Vxlan"])
"""A list of L3 interfaces or interfaces types like Loopback, Tunnel which will ignore all Loopback and Tunnel interfaces.

Takes precedence over the `specific_mtu` field."""
specific_mtu: list[dict[Interface, int]] = Field(default=[])
"""A list of dictionary of L3 interfaces with their expected L3 MTU configured."""

@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyL3MTU."""
self.result.is_success()
command_output = self.instance_commands[0].json_output
# Set list of interfaces with specific settings
specific_interfaces: list[str] = []
if self.inputs.specific_mtu:
for d in self.inputs.specific_mtu:
specific_interfaces.extend(d)
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:
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:
self.result.is_failure(f"Interface: {interface} - Incorrect MTU - Expected: {self.inputs.mtu} Actual: {values['mtu']}")
specific_interfaces = {intf: mtu for intf_mtu in self.inputs.specific_mtu for intf, mtu in intf_mtu.items()}

for interface, details in command_output["interfaces"].items():
# Verification is skipped if the interface is in the ignored interfaces list
if _is_interface_ignored(interface, self.inputs.ignored_interfaces) or details["forwardingModel"] != "routed":
continue

actual_mtu = details["mtu"]
expected_mtu = specific_interfaces.get(interface, self.inputs.mtu)

if (actual_mtu := details["mtu"]) != expected_mtu:
self.result.is_failure(f"Interface: {interface} - Incorrect MTU - Expected: {expected_mtu} Actual: {actual_mtu}")


class VerifyIPProxyARP(AntaTest):
Expand Down Expand Up @@ -546,10 +584,9 @@ def test(self) -> None:


class VerifyL2MTU(AntaTest):
"""Verifies the global layer 2 Maximum Transfer Unit (MTU) for all L2 interfaces.
"""Verifies the L2 MTU of bridged interfaces.

Test that L2 interfaces are configured with the correct MTU. It supports Ethernet, Port Channel and VLAN interfaces.
You can define a global MTU to check and also an MTU per interface and also ignored some interfaces.
Test that layer 2 (bridged) interfaces are configured with the correct MTU.

Expected Results
----------------
Expand All @@ -561,10 +598,10 @@ class VerifyL2MTU(AntaTest):
```yaml
anta.tests.interfaces:
- VerifyL2MTU:
mtu: 1500
mtu: 9214
ignored_interfaces:
- Management1
- Vxlan1
- Ethernet2/1
- Port-Channel # Ignore all Port-Channel interfaces
specific_mtu:
- Ethernet1/1: 1500
```
Expand All @@ -578,28 +615,31 @@ class Input(AntaTest.Input):
"""Input model for the VerifyL2MTU test."""

mtu: int = 9214
"""Default MTU we should have configured on all non-excluded interfaces. Defaults to 9214."""
ignored_interfaces: list[str] = Field(default=["Management", "Loopback", "Vxlan", "Tunnel"])
"""A list of L2 interfaces to ignore. Defaults to ["Management", "Loopback", "Vxlan", "Tunnel"]"""
"""Expected L2 MTU configured on all non-excluded interfaces."""
ignored_interfaces: list[InterfaceType | Interface] = Field(default=["Dps", "Fabric", "Loopback", "Management", "Recirc-Channel", "Tunnel", "Vlan", "Vxlan"])
"""A list of L2 interfaces or interface types like Ethernet, Port-Channel which will ignore all Ethernet and Port-Channel interfaces.

Takes precedence over the `specific_mtu` field."""
specific_mtu: list[dict[Interface, int]] = Field(default=[])
"""A list of dictionary of L2 interfaces with their specific MTU configured"""
"""A list of dictionary of L2 interfaces with their expected L2 MTU configured."""

@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyL2MTU."""
self.result.is_success()
interface_output = self.instance_commands[0].json_output["interfaces"]
specific_interfaces = {key: value for details in self.inputs.specific_mtu for key, value in details.items()}
specific_interfaces = {intf: mtu for intf_mtu in self.inputs.specific_mtu for intf, mtu in intf_mtu.items()}

for interface, details in interface_output.items():
catch_interface = re.findall(r"^[e,p][a-zA-Z]+[-,a-zA-Z]*\d+\/*\d*", interface, re.IGNORECASE)
if catch_interface and catch_interface not in self.inputs.ignored_interfaces and details["forwardingModel"] == "bridged":
if interface in specific_interfaces:
if (mtu := specific_interfaces[interface]) != (act_mtu := details["mtu"]):
self.result.is_failure(f"Interface: {interface} - Incorrect MTU configured - Expected: {mtu} Actual: {act_mtu}")

elif (act_mtu := details["mtu"]) != self.inputs.mtu:
self.result.is_failure(f"Interface: {interface} - Incorrect MTU configured - Expected: {self.inputs.mtu} Actual: {act_mtu}")
# Verification is skipped if the interface is in the ignored interfaces list
if _is_interface_ignored(interface, self.inputs.ignored_interfaces) or details["forwardingModel"] != "bridged":
continue

actual_mtu = details["mtu"]
expected_mtu = specific_interfaces.get(interface, self.inputs.mtu)

if (actual_mtu := details["mtu"]) != expected_mtu:
self.result.is_failure(f"Interface: {interface} - Incorrect MTU - Expected: {expected_mtu} Actual: {actual_mtu}")


class VerifyInterfaceIPv4(AntaTest):
Expand Down
12 changes: 7 additions & 5 deletions examples/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -293,19 +293,21 @@ anta.tests.interfaces:
mac_address: 00:1c:73:00:dc:01
- VerifyL2MTU:
# Verifies the global L2 MTU of all L2 interfaces.
mtu: 1500
mtu: 9214
ignored_interfaces:
- Management1
- Vxlan1
- Ethernet2/1
- Port-Channel # Ignore all Port-Channel interfaces
specific_mtu:
- Ethernet1/1: 1500
- VerifyL3MTU:
# Verifies the global L3 MTU of all L3 interfaces.
mtu: 1500
ignored_interfaces:
- Vxlan1
- Management # Ignore all Management interfaces
- Ethernet2.100
- Ethernet1/1
specific_mtu:
- Ethernet1: 2500
- Ethernet10: 9200
- VerifyLACPInterfacesStatus:
# Verifies the Link Aggregation Control Protocol (LACP) status of the interface.
interfaces:
Expand Down
2 changes: 1 addition & 1 deletion tests/units/anta_tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test(device: AntaDevice, data: dict[str, Any]) -> None:
# Run the test() method
asyncio.run(test_instance.test())
# Assert expected result
assert test_instance.result.result == data["expected"]["result"], f"Expected '{data['expected']['result']}' result, got '{test_instance.result.result}'"
assert test_instance.result.result == data["expected"]["result"], f"Expected '{data['expected']['result']}' result, got '{test_instance.result.messages}'"
if "messages" in data["expected"]:
# We expect messages in test result
assert len(test_instance.result.messages) == len(data["expected"]["messages"])
Expand Down
Loading
0