diff --git a/anta/custom_types.py b/anta/custom_types.py index 894fe0bf4..0af77f3ac 100644 --- a/anta/custom_types.py +++ b/anta/custom_types.py @@ -21,7 +21,7 @@ """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,3}$" +REGEX_TYPE_PORTCHANNEL = r"^Port-Channel[0-9]{1,6}$" """Match Port Channel interface like Port-Channel5.""" 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`.""" diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py index 1116aac2e..9ff1cf357 100644 --- a/anta/tests/interfaces.py +++ b/anta/tests/interfaces.py @@ -891,7 +891,7 @@ class VerifyLACPInterfacesStatus(AntaTest): - Verifies that the interface is a member of the LACP port channel. - Ensures that the synchronization is established. - Ensures the interfaces are in the correct state for collecting and distributing traffic. - - Validates that LACP settings, such as timeouts, are correctly configured. + - Validates that LACP settings, such as timeouts, are correctly configured. (i.e The long timeout mode, also known as "slow" mode, is the default setting.) Expected Results ---------------- @@ -937,6 +937,9 @@ def test(self) -> None: """Main test function for VerifyLACPInterfacesStatus.""" self.result.is_success() + # Member port verification parameters. + member_port_details = ["activity", "aggregation", "synchronization", "collecting", "distributing", "timeout"] + # Iterating over command output for different interfaces for command, input_entry in zip(self.instance_commands, self.inputs.interfaces): interface = input_entry.name @@ -950,7 +953,7 @@ def test(self) -> None: # Verify the interface is bundled in port channel. actor_port_status = interface_details.get("actorPortStatus") if actor_port_status != "bundled": - message = f"For Interface {interface}:\nExpected `bundled` as the `local port status`, but found `{actor_port_status}` instead.\n" + message = f"For Interface {interface}:\nExpected `bundled` as the local port status, but found `{actor_port_status}` instead.\n" self.result.is_failure(message) continue @@ -960,43 +963,13 @@ def test(self) -> None: # Collecting actual interface details actual_interface_output = { - "actor_port_details": { - "activity": actor_port_details.get("activity", "NotFound"), - "aggregation": actor_port_details.get("aggregation", "NotFound"), - "synchronization": actor_port_details.get("synchronization", "NotFound"), - "collecting": actor_port_details.get("collecting", "NotFound"), - "distributing": actor_port_details.get("distributing", "NotFound"), - "timeout": actor_port_details.get("timeout", "NotFound"), - }, - "partner_port_details": { - "activity": partner_port_details.get("activity", "NotFound"), - "aggregation": partner_port_details.get("aggregation", "NotFound"), - "synchronization": partner_port_details.get("synchronization", "NotFound"), - "collecting": partner_port_details.get("collecting", "NotFound"), - "distributing": partner_port_details.get("distributing", "NotFound"), - "timeout": partner_port_details.get("timeout", "NotFound"), - }, + "actor_port_details": {param: actor_port_details.get(param, "NotFound") for param in member_port_details}, + "partner_port_details": {param: partner_port_details.get(param, "NotFound") for param in member_port_details}, } # Forming expected interface details - expected_interface_output = { - "actor_port_details": { - "activity": True, - "aggregation": True, - "synchronization": True, - "collecting": True, - "distributing": True, - "timeout": False, - }, - "partner_port_details": { - "activity": True, - "aggregation": True, - "synchronization": True, - "collecting": True, - "distributing": True, - "timeout": False, - }, - } + expected_details = {param: param != "timeout" for param in member_port_details} + expected_interface_output = {"actor_port_details": expected_details, "partner_port_details": expected_details} # Forming failure message if actual_interface_output != expected_interface_output: diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py index ae258d19c..16a2f9e76 100644 --- a/tests/units/anta_tests/test_interfaces.py +++ b/tests/units/anta_tests/test_interfaces.py @@ -2501,10 +2501,10 @@ "orphanPorts": {}, } ], - "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Port-Channel5"}]}, + "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Po5"}]}, "expected": { "result": "failure", - "messages": ["For Interface Ethernet5:\nExpected `bundled` as the `local port status`, but found `No Aggregate` instead.\n"], + "messages": ["For Interface Ethernet5:\nExpected `bundled` as the local port status, but found `No Aggregate` instead.\n"], }, }, { @@ -2515,7 +2515,7 @@ "portChannels": {"Port-Channel5": {"interfaces": {}}}, } ], - "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Port-Channel5"}]}, + "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Po 5"}]}, "expected": { "result": "failure", "messages": ["Interface 'Ethernet5' is not configured to be a member of LACP 'Port-Channel5'."], @@ -2559,7 +2559,7 @@ "orphanPorts": {}, } ], - "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "Port-Channel5"}]}, + "inputs": {"interfaces": [{"name": "Ethernet5", "portchannel": "port-channel 5"}]}, "expected": { "result": "failure", "messages": [ diff --git a/tests/units/test_custom_types.py b/tests/units/test_custom_types.py index e0ce7c9d5..e3dc09d25 100644 --- a/tests/units/test_custom_types.py +++ b/tests/units/test_custom_types.py @@ -147,11 +147,11 @@ def test_regexp_type_portchannel() -> None: assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel5") is not None assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel100") is not None assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel999") is not None + assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel1000") is not None # Test strings that should not match the pattern assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel") is None assert re.match(REGEX_TYPE_PORTCHANNEL, "Port_Channel") is None - assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel1000") is None assert re.match(REGEX_TYPE_PORTCHANNEL, "Port_Channel1000") is None assert re.match(REGEX_TYPE_PORTCHANNEL, "Port_Channel5/1") is None assert re.match(REGEX_TYPE_PORTCHANNEL, "Port-Channel-100") is None @@ -217,6 +217,8 @@ def test_interface_autocomplete_success() -> None: assert interface_autocomplete("eth2") == "Ethernet2" assert interface_autocomplete("po3") == "Port-Channel3" assert interface_autocomplete("lo4") == "Loopback4" + assert interface_autocomplete("Po1000") == "Port-Channel1000" + assert interface_autocomplete("Po 1000") == "Port-Channel1000" def test_interface_autocomplete_no_alias() -> None: