diff --git a/cloudshell/networking/cisco/command_actions/iface_actions.py b/cloudshell/networking/cisco/command_actions/iface_actions.py index 7639b7f..71d032b 100644 --- a/cloudshell/networking/cisco/command_actions/iface_actions.py +++ b/cloudshell/networking/cisco/command_actions/iface_actions.py @@ -12,6 +12,9 @@ iface, ) +# Pattern to match VLAN 1 configuration (default state that should be preserved) +VLAN_1_PATTERN = r"^\s*switchport\s+trunk\s+allowed\s+vlan\s+1\s*$" + class IFaceActions: def __init__(self, cli_service, logger): @@ -114,6 +117,9 @@ def clean_interface_switchport_config( for line in current_config.splitlines(): if line.strip(" ").startswith("switchport "): + # Skip removing "switchport trunk allowed vlan 1" as it's the default state + if re.match(VLAN_1_PATTERN, line, re.IGNORECASE): + continue line_to_remove = re.sub(r"\s+\d+[-\d+,]+", "", line).strip(" ") CommandTemplateExecutor( self._cli_service, diff --git a/tests/networking/cisco/command_actions/test_iface_actions.py b/tests/networking/cisco/command_actions/test_iface_actions.py index 996013d..bbcf294 100644 --- a/tests/networking/cisco/command_actions/test_iface_actions.py +++ b/tests/networking/cisco/command_actions/test_iface_actions.py @@ -58,3 +58,103 @@ def test_get_no_l2_protocol_tunnel_cmd(self, vlan_templates_mock, cte_mock): error_map=None, ) self.assertEqual(result, cte_mock.return_value) + + @patch( + "cloudshell.networking.cisco.command_actions.iface_actions" + ".CommandTemplateExecutor" + ) + def test_clean_interface_switchport_config_preserves_vlan_1(self, cte_mock): + """Test that switchport trunk allowed vlan 1 is not removed (default state).""" + current_config = """Building configuration... + +Current configuration : 144 bytes +! +interface GigabitEthernet110/1/0/6 + description KG-255X-06-PT + switchport + switchport trunk allowed vlan 1 + switchport mode dynamic auto +end +""" + executor_mock = MagicMock() + cte_mock.return_value = executor_mock + + self._handler.clean_interface_switchport_config(current_config) + + # Verify that execute_command was called for other switchport lines but not for vlan 1 + calls = executor_mock.execute_command.call_args_list + # Should be called once for "switchport mode dynamic auto" + # but NOT for "switchport trunk allowed vlan 1" + # Note: "switchport" alone (without trailing space) is not matched by the pattern + self.assertEqual(len(calls), 1) + + # Verify the command that was issued + called_commands = [call[1]["command"] for call in calls] + self.assertIn("switchport mode dynamic auto", called_commands) + # Ensure vlan 1 line was NOT processed (would appear as "switchport trunk allowed vlan" after regex) + for cmd in called_commands: + self.assertNotIn("trunk allowed vlan", cmd) + + @patch( + "cloudshell.networking.cisco.command_actions.iface_actions" + ".CommandTemplateExecutor" + ) + def test_clean_interface_switchport_config_removes_other_vlans(self, cte_mock): + """Test that switchport trunk allowed vlan with other VLANs are removed.""" + current_config = """Building configuration... + +Current configuration : 144 bytes +! +interface GigabitEthernet110/1/0/6 + description KG-255X-06-PT + switchport + switchport trunk allowed vlan 100 + switchport mode trunk +end +""" + executor_mock = MagicMock() + cte_mock.return_value = executor_mock + + self._handler.clean_interface_switchport_config(current_config) + + # Verify that execute_command was called for all switchport lines including vlan 100 + calls = executor_mock.execute_command.call_args_list + # Should be called twice: "switchport trunk allowed vlan", "switchport mode trunk" + # Note: "switchport" alone (without trailing space) is not matched by the pattern + self.assertEqual(len(calls), 2) + + # Verify the commands that were issued + called_commands = [call[1]["command"] for call in calls] + self.assertIn("switchport trunk allowed vlan", called_commands) + self.assertIn("switchport mode trunk", called_commands) + + @patch( + "cloudshell.networking.cisco.command_actions.iface_actions" + ".CommandTemplateExecutor" + ) + def test_clean_interface_switchport_config_removes_vlan_1_in_range(self, cte_mock): + """Test that VLAN 1 in a range or list is still removed (not default state).""" + current_config = """Building configuration... + +Current configuration : 144 bytes +! +interface GigabitEthernet110/1/0/6 + description Test Port + switchport trunk allowed vlan 1,2,3 + switchport mode trunk +end +""" + executor_mock = MagicMock() + cte_mock.return_value = executor_mock + + self._handler.clean_interface_switchport_config(current_config) + + # Verify that execute_command was called for VLAN range including vlan 1 + calls = executor_mock.execute_command.call_args_list + # Should be called twice: "switchport trunk allowed vlan", "switchport mode trunk" + self.assertEqual(len(calls), 2) + + # Verify the commands that were issued - vlan 1,2,3 should be removed + called_commands = [call[1]["command"] for call in calls] + self.assertIn("switchport trunk allowed vlan", called_commands) + self.assertIn("switchport mode trunk", called_commands)