diff --git a/changelogs/fragments/l2_interfaces_multiline_trunk.yml b/changelogs/fragments/l2_interfaces_multiline_trunk.yml new file mode 100644 index 000000000..dfe86afd1 --- /dev/null +++ b/changelogs/fragments/l2_interfaces_multiline_trunk.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - l2_interfaces - If a large number of VLANs are affected, the configuration will now be correctly split into several commands. diff --git a/plugins/module_utils/network/ios/config/l2_interfaces/l2_interfaces.py b/plugins/module_utils/network/ios/config/l2_interfaces/l2_interfaces.py index 653b837eb..2cec3779f 100644 --- a/plugins/module_utils/network/ios/config/l2_interfaces/l2_interfaces.py +++ b/plugins/module_utils/network/ios/config/l2_interfaces/l2_interfaces.py @@ -31,6 +31,7 @@ L2_interfacesTemplate, ) from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import ( + generate_switchport_trunk, normalize_interface, vlan_list_to_range, vlan_range_to_list, @@ -136,11 +137,10 @@ def compare_list(self, want, have): ), ) if self.state != "deleted" and cmd_always: # add configuration needed - add = "add " if have.get("trunk", {}).get(vlan, []) else "" - self.commands.append( - "switchport trunk {0} vlan {1}{2}".format( + self.commands.extend( + generate_switchport_trunk( vlan.split("_", maxsplit=1)[0], - add, + have.get("trunk", {}).get(vlan, []), vlan_list_to_range(sorted(cmd_always)), ), ) diff --git a/plugins/module_utils/network/ios/utils/utils.py b/plugins/module_utils/network/ios/utils/utils.py index 1deea535b..fbe9f1fab 100644 --- a/plugins/module_utils/network/ios/utils/utils.py +++ b/plugins/module_utils/network/ios/utils/utils.py @@ -422,3 +422,35 @@ def sort_dict(dictionary): else: sorted_dict[key] = value return sorted_dict + + +def generate_switchport_trunk(type, add, vlans_range): + """ + Generates a list of switchport commands based on the trunk type and VLANs range. + Ensures that the length of VLANs lexeme in a command does not exceed 220 characters. + """ + + def append_command(): + command_prefix = f"switchport trunk {type} vlan " + if add or commands: + command_prefix += "add " + commands.append(command_prefix + ",".join(current_chunk)) + + commands = [] + current_chunk = [] + current_length = 0 + + for vrange in vlans_range.split(","): + next_addition = vrange if not current_chunk else "," + vrange + if current_length + len(next_addition) <= 220: + current_chunk.append(vrange) + current_length += len(next_addition) + else: + append_command() + current_chunk = [vrange] + current_length = len(vrange) + + if current_chunk: + append_command() + + return commands diff --git a/tests/unit/modules/network/ios/test_ios_l2_interfaces.py b/tests/unit/modules/network/ios/test_ios_l2_interfaces.py index e84846aeb..a1cee74ad 100644 --- a/tests/unit/modules/network/ios/test_ios_l2_interfaces.py +++ b/tests/unit/modules/network/ios/test_ios_l2_interfaces.py @@ -793,3 +793,188 @@ def test_ios_l2_interfaces_fiveGibBit(self): result = self.execute_module(changed=True) self.maxDiff = None self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_l2_interfaces_trunk_multiline_merge(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["60-70"] + [str(vlan) for vlan in range(101, 500, 2)], + encapsulation="isl", + native_vlan=20, + pruning_vlans=["10", "20", "30-40"], + ), + ), + ], + state="merged", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "switchport trunk encapsulation isl", + "switchport trunk native vlan 20", + "switchport trunk allowed vlan add 60-70,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,131,133,135,137,139,141,143,145,147," + + "149,151,153,155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,195,197,199,201,203,205", + "switchport trunk allowed vlan add 207,209,211,213,215,217,219,221,223,225,227,229,231,233,235,237,239,241,243,245,247,249,251,253,255,257," + + "259,261,263,265,267,269,271,273,275,277,279,281,283,285,287,289,291,293,295,297,299,301,303,305,307,309,311,313,315", + "switchport trunk allowed vlan add 317,319,321,323,325,327,329,331,333,335,337,339,341,343,345,347,349,351,353,355,357,359,361,363,365,367," + + "369,371,373,375,377,379,381,383,385,387,389,391,393,395,397,399,401,403,405,407,409,411,413,415,417,419,421,423,425", + "switchport trunk allowed vlan add 427,429,431,433,435,437,439,441,443,445,447,449,451,453,455,457,459,461,463,465,467,469,471,473,475,477," + + "479,481,483,485,487,489,491,493,495,497,499", + "switchport trunk pruning vlan add 30-40", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_trunk_multiline_replace(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["60-70"] + [str(vlan) for vlan in range(101, 500, 2)], + encapsulation="isl", + native_vlan=20, + pruning_vlans=["10", "20", "30-40"], + ), + ), + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "switchport trunk encapsulation isl", + "switchport trunk native vlan 20", + "switchport trunk allowed vlan remove 10-20,40", + "switchport trunk allowed vlan add 60-70,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,131,133,135,137,139,141,143,145,147," + + "149,151,153,155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,195,197,199,201,203,205", + "switchport trunk allowed vlan add 207,209,211,213,215,217,219,221,223,225,227,229,231,233,235,237,239,241,243,245,247,249,251,253,255,257," + + "259,261,263,265,267,269,271,273,275,277,279,281,283,285,287,289,291,293,295,297,299,301,303,305,307,309,311,313,315", + "switchport trunk allowed vlan add 317,319,321,323,325,327,329,331,333,335,337,339,341,343,345,347,349,351,353,355,357,359,361,363,365,367," + + "369,371,373,375,377,379,381,383,385,387,389,391,393,395,397,399,401,403,405,407,409,411,413,415,417,419,421,423,425", + "switchport trunk allowed vlan add 427,429,431,433,435,437,439,441,443,445,447,449,451,453,455,457,459,461,463,465,467,469,471,473,475,477," + + "479,481,483,485,487,489,491,493,495,497,499", + "switchport trunk pruning vlan add 30-40", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_trunk_multiline_replace_init(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["60-70"] + [str(vlan) for vlan in range(101, 500, 2)], + encapsulation="isl", + native_vlan=20, + pruning_vlans=["10", "20", "30-40"], + ), + ), + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "switchport trunk encapsulation isl", + "switchport trunk native vlan 20", + "switchport trunk allowed vlan 60-70,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,131,133,135,137,139,141,143,145,147,149,151,153," + + "155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,195,197,199,201,203,205", + "switchport trunk allowed vlan add 207,209,211,213,215,217,219,221,223,225,227,229,231,233,235,237,239,241,243,245,247,249,251,253,255,257,259," + + "261,263,265,267,269,271,273,275,277,279,281,283,285,287,289,291,293,295,297,299,301,303,305,307,309,311,313,315", + "switchport trunk allowed vlan add 317,319,321,323,325,327,329,331,333,335,337,339,341,343,345,347,349,351,353,355,357,359,361,363,365,367,369," + + "371,373,375,377,379,381,383,385,387,389,391,393,395,397,399,401,403,405,407,409,411,413,415,417,419,421,423,425", + "switchport trunk allowed vlan add 427,429,431,433,435,437,439,441,443,445,447,449,451,453,455,457,459,461,463,465,467,469,471,473,475,477,479," + + "481,483,485,487,489,491,493,495,497,499", + "switchport trunk pruning vlan add 30-40", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_trunk_multiline_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["60-70"] + [str(vlan) for vlan in range(101, 500, 2)], + encapsulation="isl", + native_vlan=20, + pruning_vlans=["10", "20", "30-40"], + ), + ), + ], + state="overridden", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "switchport trunk encapsulation isl", + "switchport trunk native vlan 20", + "switchport trunk allowed vlan remove 10-20,40", + "switchport trunk allowed vlan add 60-70,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,131,133,135,137,139,141,143,145,147,149,151," + + "153,155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,195,197,199,201,203,205", + "switchport trunk allowed vlan add 207,209,211,213,215,217,219,221,223,225,227,229,231,233,235,237,239,241,243,245,247,249,251,253,255,257,259," + + "261,263,265,267,269,271,273,275,277,279,281,283,285,287,289,291,293,295,297,299,301,303,305,307,309,311,313,315", + "switchport trunk allowed vlan add 317,319,321,323,325,327,329,331,333,335,337,339,341,343,345,347,349,351,353,355,357,359,361,363,365,367,369," + + "371,373,375,377,379,381,383,385,387,389,391,393,395,397,399,401,403,405,407,409,411,413,415,417,419,421,423,425", + "switchport trunk allowed vlan add 427,429,431,433,435,437,439,441,443,445,447,449,451,453,455,457,459,461,463,465,467,469,471,473,475,477,479," + + "481,483,485,487,489,491,493,495,497,499", + "switchport trunk pruning vlan add 30-40", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands)