Last active
March 11, 2021 00:04
-
-
Save cidrblock/575966b578f204adc15ab05f14eaf3fd to your computer and use it in GitHub Desktop.
Add or remove vlans from a trunk port as needed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{# ############################################## fail if add, no longer supported #} | |
{% if 'add' in interface['value']['switchport']['trunk']['allowed_vlans'] %} | |
{{ you_cant_use_the_add_statement_anymore }} | |
{% endif %} | |
{# ############################################## remediate the trunk list #} | |
{% set trunk_vlan_changes = configuration_prechange['matches']['sections'][0]['section_content']|trunk_vlan_remediate(interface['value']['switchport']['trunk']['allowed_vlans']['vlans'],20) %} | |
{% if 'vlans' in trunk_vlan_changes and trunk_vlan_changes['vlans'] %} | |
{% set _ = desired.append("switchport trunk allowed vlan %s" % (trunk_vlan_changes['vlans'])) %} | |
{% endif %}{# vlans #} | |
{% for entry in trunk_vlan_changes['vlans_add'] %} | |
{% set _ = desired.append("switchport trunk allowed vlan add %s" % (entry)) %} | |
{% endfor %}{# vlans_add #} | |
{% for entry in trunk_vlan_changes['vlans_remove'] %} | |
{% set _ = desired.append("switchport trunk allowed vlan remove %s" % (entry)) %} | |
{% endfor %}{# vlans_remove #} | |
{% endif %}{# allowed-vlans #} | |
{% endif %}{# switchport trunk #} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
########################################## | |
- name: "TEST 1> Test just parsing existing" | |
set_fact: | |
prefix: 'TEST 1>' | |
- name: "{{ prefix }} Set working configuration" | |
set_fact: | |
current_config: | |
- interface port-channel3 | |
- description sealb1srt002-channel-po3 | |
- switchport | |
- switchport mode trunk | |
- switchport trunk native vlan 5 | |
- switchport trunk allowed vlan 99-1000,2100-2105,2195-2196,2200-2205 | |
- switchport trunk allowed vlan add 2295-2296,2300-2305,2395-2396,2400-2405 | |
- switchport trunk allowed vlan add 2495-2496,2500-2505,2595-2596,2600-2605 | |
- spanning-tree port type network | |
- vpc peer-link | |
- name: "{{ prefix }} Set expected results" | |
set_fact: | |
result: "{{ current_config|trunk_vlan_list }}" | |
expected_list: "99-1000,2100-2105,2195-2196,2200-2205,2295-2296,2300-2305,2395-2396,2400-2405,2495-2496,2500-2505,2595-2596,2600-2605" | |
- name: "{{ prefix }} Assert results as expected" | |
assert: | |
that: result == expected_list | |
########################################## | |
- name: "TEST 2> Test both the addition and removal of vlans to an exisiting port" | |
set_fact: | |
prefix: 'TEST 2>' | |
- name: "{{ prefix }} Set working configuration" | |
set_fact: | |
vlans: 1,2,3,99-1000,2100-2105,2195-2196,2200-2205,2295-2296 | |
current_config: | |
- interface port-channel3 | |
- description sealb1srt002-channel-po3 | |
- switchport | |
- switchport mode trunk | |
- switchport trunk native vlan 5 | |
- switchport trunk allowed vlan 99-1000,2100-2105,2195-2196,2200-2205 | |
- switchport trunk allowed vlan add 2295-2296,2300-2305,2395-2396,2400-2405 | |
- switchport trunk allowed vlan add 2495-2496,2500-2505,2595-2596,2600-2605 | |
- spanning-tree port type network | |
- vpc peer-link | |
- name: "{{ prefix }} Set expected results" | |
set_fact: | |
result: "{{ current_config|trunk_vlan_remediate(vlans,20) }}" | |
expected_vlans: null | |
expected_vlans_add: | |
- "1-3" | |
expected_vlans_remove: | |
- "2300-2305,2395-2396" | |
- "2400-2405,2495-2496" | |
- "2500-2505,2595-2596" | |
- "2600-2605" | |
- name: "{{ prefix }} Assert results as expected" | |
assert: | |
that: result['vlans'] == expected_vlans and | |
result['vlans_add'] == expected_vlans_add and | |
result['vlans_remove'] == expected_vlans_remove | |
########################################## | |
- name: "TEST 3> Test total removal of vlans from an exisiting port" | |
set_fact: | |
prefix: 'TEST 3>' | |
- name: "{{ prefix }} Set working configuration" | |
set_fact: | |
vlans: | |
current_config: | |
- interface port-channel3 | |
- description sealb1srt002-channel-po3 | |
- switchport | |
- switchport mode trunk | |
- switchport trunk native vlan 5 | |
- switchport trunk allowed vlan 99-1000,2100-2105,2195-2196,2200-2205 | |
- switchport trunk allowed vlan add 2295-2296,2300-2305,2395-2396,2400-2405 | |
- switchport trunk allowed vlan add 2495-2496,2500-2505,2595-2596,2600-2605 | |
- spanning-tree port type network | |
- vpc peer-link | |
- name: "{{ prefix }} Set expected results" | |
set_fact: | |
result: "{{ current_config|trunk_vlan_remediate(vlans,20) }}" | |
expected_vlans: null | |
expected_vlans_add: [] | |
expected_vlans_remove: | |
- "99-1000,2100-2105" | |
- "2195-2196,2200-2205" | |
- "2295-2296,2300-2305" | |
- "2395-2396,2400-2405" | |
- "2495-2496,2500-2505" | |
- "2595-2596,2600-2605" | |
- name: "{{ prefix }} Assert results as expected" | |
assert: | |
that: result['vlans'] == expected_vlans and | |
result['vlans_add'] == expected_vlans_add and | |
result['vlans_remove'] == expected_vlans_remove | |
########################################## | |
- name: "TEST 4> Test adding all vlans to a new port" | |
set_fact: | |
prefix: 'TEST 4>' | |
- name: "{{ prefix }} Set working configuration" | |
set_fact: | |
vlans: 1,2,3,99-1000,2100-2105,2195-2196,2200-2205,2295-2296 | |
current_config: | |
- interface port-channel3 | |
- description sealb1srt002-channel-po3 | |
- switchport | |
- switchport mode trunk | |
- switchport trunk native vlan 5 | |
- name: "{{ prefix }} Set expected results" | |
set_fact: | |
result: "{{ current_config|trunk_vlan_remediate(vlans,20) }}" | |
expected_vlans: "1-3,99-1000" | |
expected_vlans_add: | |
- "2100-2105,2195-2196" | |
- "2200-2205,2295-2296" | |
expected_vlans_remove: [] | |
- name: "{{ prefix }} Assert results as expected" | |
assert: | |
that: result['vlans'] == expected_vlans and | |
result['vlans_add'] == expected_vlans_add and | |
result['vlans_remove'] == expected_vlans_remove |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import itertools | |
def flatten(string): | |
""" Given a string of vlans, return a list of ints | |
Args: | |
string (str): A string of vlans | |
Returns: | |
list: A list of ints | |
""" | |
new_list = [] | |
if isinstance(string, basestring): | |
if "," in string: | |
vlans = string.split(',') | |
else: | |
vlans = [string] | |
for entry in vlans: | |
if '-' in entry: | |
parts = entry.split('-') | |
flat_range = list(range(int(parts[0]), int(parts[1]) + 1)) | |
new_list.extend(flat_range) | |
else: | |
try: | |
new_list.append(int(entry)) | |
except ValueError: | |
return False | |
elif type(string) is int: | |
try: | |
new_list.append(string) | |
except ValueError: | |
return False | |
else: | |
return [] | |
return new_list | |
def ranges(i): | |
""" Build ranges of consecutive numbers | |
Args: | |
i (list): a list of integers | |
Returns: | |
list: a list of tuples | |
""" | |
for a, b in itertools.groupby(enumerate(sorted(set(i))), lambda (x, y): y - x): | |
b = list(b) | |
yield b[0][1], b[-1][1] | |
def string_from_ranges(ranged): | |
""" Build a vlan list string from a list of tuples | |
Args: | |
ranged (list): A list of tuples | |
Returns: | |
str: A string of comma delimited vlans | |
""" | |
new_list = [] | |
for entry in ranged: | |
if entry[0] == entry[1]: | |
new_list.append(str(entry[0])) | |
else: | |
new_list.append("%s-%s" % (entry[0], entry[1])) | |
return (",").join(new_list) | |
def splitter(text, length): | |
""" Split a comma delimited string into chunks | |
Args: | |
text (str): A comma delimited string | |
length (int): The maximum length of any line | |
Return: | |
list: A list of line, each at or smaller than the maximum | |
""" | |
words = iter(text.split(',')) | |
lines, current = [], next(words) | |
for word in words: | |
if len(current) + 1 + len(word) > length: | |
lines.append(current) | |
current = word | |
else: | |
current += "," + word | |
lines.append(current) | |
return lines | |
def trunk_vlan_list(current_config): | |
""" Returns a list of vlans from a config | |
Args: | |
current_config (list): A list of lines making up the configuration | |
Returns: | |
str: A comma delimited list of the vlans | |
""" | |
match_lines = [] | |
regex = re.compile(r'^switchport trunk allowed vlan (?:add\s)?(?P<vlans>[\d\-,]+)$') | |
for line in current_config: | |
result = re.match(regex, line) | |
if result: | |
match_lines.append(result.groupdict()['vlans']) | |
if match_lines: | |
current_vlan_list = ",".join(match_lines) | |
else: | |
current_vlan_list = "" | |
return current_vlan_list | |
def trunk_vlan_remediate(current_config, vlan_list, chunk_size): | |
""" Propose remediation to right a configuration to the data | |
Args: | |
current_config (list): The current configuration | |
vlan_list (str): The comma delimited list of vlans from the data | |
chunk_size (int): The maximum size of each line | |
Returns: | |
dict: | |
vlans: A string to be applied with the | |
switchport trunk allowed vlan command | |
vlans_add: A list of strings to be applied with the | |
switchport trunk allowed vlan add command | |
vlans_remove: A list of strings to be applied with the | |
switchport trunk allowed vlan remove command | |
""" | |
current_vlan_list = trunk_vlan_list(current_config) | |
if current_vlan_list: | |
current_vlan_list_flat = flatten(current_vlan_list) | |
else: | |
current_vlan_list_flat = [] | |
if vlan_list: | |
desired_vlan_list_flat = flatten(vlan_list) | |
else: | |
desired_vlan_list_flat = [] | |
add_vlans = [x for x in desired_vlan_list_flat if x not in current_vlan_list_flat] | |
if add_vlans: | |
add_vlans_with_ranges = string_from_ranges(ranges(add_vlans)) | |
add_vlan_strings = splitter(add_vlans_with_ranges, chunk_size) | |
else: | |
add_vlan_strings = [] | |
remove_vlans = [x for x in current_vlan_list_flat if x not in desired_vlan_list_flat] | |
if remove_vlans: | |
remove_vlans_with_ranges = string_from_ranges(ranges(remove_vlans)) | |
remove_vlan_strings = splitter(remove_vlans_with_ranges, chunk_size) | |
else: | |
remove_vlan_strings = [] | |
response = {} | |
if current_vlan_list: | |
response['vlans'] = None | |
response['vlans_add'] = add_vlan_strings | |
response['vlans_remove'] = remove_vlan_strings | |
else: | |
response['vlans'] = add_vlan_strings[0] | |
response['vlans_add'] = add_vlan_strings[1:] | |
response['vlans_remove'] = remove_vlan_strings | |
return response | |
class FilterModule(object): | |
def filters(self): | |
return { | |
'trunk_vlan_remediate': trunk_vlan_remediate, | |
'trunk_vlan_list': trunk_vlan_list | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment