From ee6ab5d5aafb27378913d2a6b7915ed36bf07684 Mon Sep 17 00:00:00 2001 From: Steve Dodd Date: Wed, 17 Oct 2018 09:20:28 -0600 Subject: [PATCH] Add support for IOS vlan parsing filter. (#40555) * Add support for IOS vlan parsing filter. Example usage below: {% set parsed_vlans = vlans | vlan_parser %} switchport trunk allowed vlan {{ parsed_vlans[0] }} {% for i in range (1, parsed_vlans | count) %} switchport trunk allowed vlan add {{ parsed_vlans[i] }} * Update test_network.py Add import statement for filter * Fixed PEP8 issues relating to comments * Fix PEP8 issues related to blank lines * Removed magic numbers for line lengths. This should generalize support to other IOS-like NOS that use similar methods for listing vlans. The default arguments for line lengths will still be specific to Cisco IOS. The unit tests for line length are still specific to Cisco IOS. --- lib/ansible/plugins/filter/network.py | 76 ++++++++++++++++++++++- test/units/plugins/filter/test_network.py | 21 ++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/lib/ansible/plugins/filter/network.py b/lib/ansible/plugins/filter/network.py index 07d6ecbd9d0..14d3920cc0e 100644 --- a/lib/ansible/plugins/filter/network.py +++ b/lib/ansible/plugins/filter/network.py @@ -399,6 +399,79 @@ def comp_type5(unencrypted_password, encrypted_password, return_original=False): return False +def vlan_parser(vlan_list, first_line_len=48, other_line_len=44): + + ''' + Input: Unsorted list of vlan integers + Output: Sorted string list of integers according to IOS-like vlan list rules + + 1. Vlans are listed in ascending order + 2. Runs of 3 or more consecutive vlans are listed with a dash + 3. The first line of the list can be first_line_len characters long + 4. Subsequent list lines can be other_line_len characters + ''' + + # Sort and remove duplicates + sorted_list = sorted(set(vlan_list)) + + if sorted_list[0] < 1 or sorted_list[-1] > 4094: + raise AnsibleFilterError('Valid VLAN range is 1-4094') + + parse_list = [] + idx = 0 + while idx < len(sorted_list): + start = idx + end = start + while end < len(sorted_list) - 1: + if sorted_list[end + 1] - sorted_list[end] == 1: + end += 1 + else: + break + + if start == end: + # Single VLAN + parse_list.append(str(sorted_list[idx])) + elif start + 1 == end: + # Run of 2 VLANs + parse_list.append(str(sorted_list[start])) + parse_list.append(str(sorted_list[end])) + else: + # Run of 3 or more VLANs + parse_list.append(str(sorted_list[start]) + '-' + str(sorted_list[end])) + idx = end + 1 + + line_count = 0 + result = [''] + for vlans in parse_list: + # First line (" switchport trunk allowed vlan ") + if line_count == 0: + if len(result[line_count] + vlans) > first_line_len: + result.append('') + line_count += 1 + result[line_count] += vlans + ',' + else: + result[line_count] += vlans + ',' + + # Subsequent lines (" switchport trunk allowed vlan add ") + else: + if len(result[line_count] + vlans) > other_line_len: + result.append('') + line_count += 1 + result[line_count] += vlans + ',' + else: + result[line_count] += vlans + ',' + + # Remove trailing orphan commas + for idx in range(0, len(result)): + result[idx] = result[idx].rstrip(',') + + # Sometimes text wraps to next line, but there are no remaining VLANs + if '' in result: + result.remove('') + + return result + + class FilterModule(object): """Filters for working with output from network devices""" @@ -408,7 +481,8 @@ class FilterModule(object): 'parse_xml': parse_xml, 'type5_pw': type5_pw, 'hash_salt': hash_salt, - 'comp_type5': comp_type5 + 'comp_type5': comp_type5, + 'vlan_parser': vlan_parser } def filters(self): diff --git a/test/units/plugins/filter/test_network.py b/test/units/plugins/filter/test_network.py index 3174b867563..508174ca0c9 100644 --- a/test/units/plugins/filter/test_network.py +++ b/test/units/plugins/filter/test_network.py @@ -23,7 +23,8 @@ import sys import pytest from units.compat import unittest -from ansible.plugins.filter.network import parse_xml, type5_pw, hash_salt, comp_type5 +from ansible.plugins.filter.network import parse_xml, type5_pw, hash_salt, comp_type5, vlan_parser + from ansible.errors import AnsibleFilterError fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'network') @@ -165,3 +166,21 @@ class TestCompareType5(unittest.TestCase): encrypted_password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.' parsed = comp_type5(unencrypted_password, encrypted_password) self.assertEqual(parsed, False) + + +class TestVlanParser(unittest.TestCase): + + def test_compression(self): + raw_list = [1, 2, 3] + parsed_list = ['1-3'] + self.assertEqual(vlan_parser(raw_list), parsed_list) + + def test_single_line(self): + raw_list = [100, 1688, 3002, 3003, 3004, 3005, 3102, 3103, 3104, 3105, 3802, 3900, 3998, 3999] + parsed_list = ['100,1688,3002-3005,3102-3105,3802,3900,3998,3999'] + self.assertEqual(vlan_parser(raw_list), parsed_list) + + def test_multi_line(self): + raw_list = [100, 1688, 3002, 3004, 3005, 3050, 3102, 3104, 3105, 3151, 3802, 3900, 3998, 3999] + parsed_list = ['100,1688,3002,3004,3005,3050,3102,3104,3105,3151', '3802,3900,3998,3999'] + self.assertEqual(vlan_parser(raw_list), parsed_list)