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

Add generate_switchport_trunk func #1062

Merged
merged 9 commits into from
Jun 11, 2024
3 changes: 3 additions & 0 deletions changelogs/fragments/l2_interfaces_multiline_trunk.yml
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)),
),
)
Expand Down
32 changes: 32 additions & 0 deletions plugins/module_utils/network/ios/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
185 changes: 185 additions & 0 deletions tests/unit/modules/network/ios/test_ios_l2_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)