From d2429cbd6a0354eee44a6a6db7e62484818ddc26 Mon Sep 17 00:00:00 2001 From: sushma-alethea <52454757+sushma-alethea@users.noreply.github.com> Date: Tue, 27 Aug 2019 08:31:17 +0530 Subject: [PATCH] icx: new module icx_vlan (#59985) * new module * new terminal * new terminal * new cliconf * cliconf * icx cliconf * icx_cliconf * icx test units module * icx units module * icx banner unit test * PR changes resolved * changes resolved * Changes Resolved * check_running_config changes resolved * added notes * removed icx rst * new changes * deleted icx rst * icx .rst * modified platform_index.rst * modified platform_index.rst * changes resolved * PR comments resolved * Update platform_index.rst PR comment resolved * test * Revert "test" This reverts commit 99b72c661431bb2514253acaee246acb85260451. * new module icx_vlan * new changes * new fixes * new fixes * new fixes * new fixes * new fixes * new fixes * Fixed bugs * Fixed bot bugs * Fixed bot bugs * Fixed bot bugs * Fixed bot bugs * Fixed bot bugs * Fixed bot bugs * Fixed bot errors * notes updated --- lib/ansible/modules/network/icx/icx_vlan.py | 784 ++++++++++++++++++ .../network/icx/fixtures/icx_vlan_config | 32 + .../modules/network/icx/test_icx_vlan.py | 279 +++++++ 3 files changed, 1095 insertions(+) create mode 100644 lib/ansible/modules/network/icx/icx_vlan.py create mode 100644 test/units/modules/network/icx/fixtures/icx_vlan_config create mode 100644 test/units/modules/network/icx/test_icx_vlan.py diff --git a/lib/ansible/modules/network/icx/icx_vlan.py b/lib/ansible/modules/network/icx/icx_vlan.py new file mode 100644 index 00000000000..6844bdb0641 --- /dev/null +++ b/lib/ansible/modules/network/icx/icx_vlan.py @@ -0,0 +1,784 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +--- +module: icx_vlan +version_added: "2.9" +author: "Ruckus Wireless (@Commscope)" +short_description: Manage VLANs on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of VLANs + on ICX network devices. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + name: + description: + - Name of the VLAN. + type: str + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + type: int + interfaces: + description: + - List of ethernet ports or LAGS to be added as access(untagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + tagged: + description: + - List of ethernet ports or LAGS to be added as trunk(tagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + ip_dhcp_snooping: + description: + - Enables DHCP snooping on a VLAN. + type: bool + ip_arp_inspection: + description: + - Enables dynamic ARP inspection on a VLAN. + type: bool + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for given vlan C(name) + for associated interfaces. If the value in the C(associated_interfaces) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + associated_tagged: + description: + - This is a intent option and checks the operational state of given vlan C(name) + for associated tagged ports and lags. If the value in the C(associated_tagged) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + delay: + description: + - Delay the play should wait to check for declarative intent params values. + default: 10 + type: int + stp: + description: + - Enable spanning-tree 802-1w/rstp for this vlan. + suboptions: + type: + description: + - Specifiy the type of spanning-tree + type: str + default: 802-1w + choices: ['802-1w','rstp'] + priority: + description: + - Configures the priority of the bridge. The value ranges from + 0 through 65535. A lower numerical value means the bridge has + a higher priority. Thus, the highest priority is 0. The default is 32768. + type: str + enabled: + description: + - Manage the state(Enable/Disable) of the spanning_tree_802_1w in the current vlan + type: bool + type: dict + aggregate: + description: + - List of VLANs definitions. + type: list + suboptions: + name: + description: + - Name of the VLAN. + type: str + vlan_id: + description: + - ID of the VLAN. Range 1-4094. + required: true + type: str + ip_dhcp_snooping: + description: + - Enables DHCP snooping on a VLAN. + type: bool + ip_arp_inspection: + description: + - Enables dynamic ARP inspection on a VLAN. + type: bool + tagged: + description: + - List of ethernet ports or LAGS to be added as trunk(tagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + interfaces: + description: + - List of ethernet ports or LAGS to be added as access(untagged) ports to the vlan. + To add a range of ports use 'to' keyword. See the example. + suboptions: + name: + description: + - Name of the interface or lag + type: list + purge: + description: + - Purge interfaces not defined in the I(name) + type: bool + type: dict + delay: + description: + - Delay the play should wait to check for declarative intent params values. + type: int + stp: + description: + - Enable spanning-tree 802-1w/rstp for this vlan. + suboptions: + type: + description: + - Specifiy the type of spanning-tree + type: str + default: 802-1w + choices: ['802-1w','rstp'] + priority: + description: + - Configures the priority of the bridge. The value ranges from + 0 through 65535. A lower numerical value means the bridge has + a higher priority. Thus, the highest priority is 0. The default is 32768. + type: str + enabled: + description: + - Manage the state(Enable/Disable) of the spanning_tree_802_1w in the current vlan + type: bool + type: dict + state: + description: + - State of the VLAN configuration. + type: str + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overriden, by specifying it as module parameter. + type: bool + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for given vlan C(name) + for associated interfaces. If the value in the C(associated_interfaces) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + associated_tagged: + description: + - This is a intent option and checks the operational state of given vlan C(name) + for associated tagged ports and lags. If the value in the C(associated_tagged) does not match with + the operational state of vlan interfaces on device it will result in failure. + type: list + purge: + description: + - Purge VLANs not defined in the I(aggregate) parameter. + default: no + type: bool + state: + description: + - State of the VLAN configuration. + type: str + default: present + choices: ['present', 'absent'] + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overriden, by specifying it as module parameter. + type: bool + default: yes +""" + +EXAMPLES = """ +- name: Add a single ethernet 1/1/48 as access(untagged) port to vlan 20 + icx_vlan: + name: test-vlan + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/48 + +- name: Add a single LAG 10 as access(untagged) port to vlan 20 + icx_vlan: + vlan_id: 20 + interfaces: + name: + - lag 10 + +- name: Add a range of ethernet ports as trunk(tagged) ports to vlan 20 by port + icx_vlan: + vlan_id: 20 + tagged: + name: + - ethernet 1/1/40 to 1/1/48 + +- name: Add discontinuous lags, ethernet ports as access(untagged) and trunk(tagged) port to vlan 20. + icx_vlan: + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/40 to 1/1/48 + - ethernet 2/1/1 + - lag 1 + - lag 3 to 5 + tagged: + name: + - ethernet 1/1/20 to 1/1/25 + - lag 1 to 3 + +- name: Remove an access and range of trunk ports from vlan + icx_vlan: + vlan_id: 20 + interfaces: + name: + - ethernet 1/1/40 + tagged: + name: + - ethernet 1/1/39 to 1/1/70 + +- name: Enable dhcp snooping, disable arp inspection in vlan + icx_vlan: + vlan_id: 20 + ip_dhcp_snooping: present + ip_arp_inspection: absent + +- name: Create vlan 20. Enable arp inspection in vlan. Purge all other vlans. + icx_vlan: + vlan_id: 20 + ip_arp_inspection: present + purge: present + +- name: Remove vlan 20. + icx_vlan: + vlan_id: 20 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan 100 + - name test-vlan +""" + +import re +from time import sleep +import itertools +from copy import deepcopy +from time import sleep +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.network.common.config import NetworkConfig +from ansible.module_utils.network.icx.icx import load_config, get_config +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible.module_utils.network.common.utils import conditional, remove_default_spec + + +def search_obj_in_list(vlan_id, lst): + obj = list() + for o in lst: + if str(o['vlan_id']) == vlan_id: + return o + + +def parse_vlan_brief(module, vlan_id): + command = 'show run vlan %s' % vlan_id + rc, out, err = exec_command(module, command) + lines = out.split('\n') + untagged_ports = list() + untagged_lags = list() + tagged_ports = list() + tagged_lags = list() + + for line in lines: + if 'tagged' in line.split(): + lags = line.split(" lag ") + ports = lags[0].split(" ethe ") + del ports[0] + del lags[0] + for port in ports: + if "to" in port: + p = port.split(" to ") + pr = int(p[1].split('/')[2]) - int(p[0].split('/')[2]) + for i in range(0, pr + 1): + tagged_ports.append((int(p[0].split('/')[2]) + i)) + else: + tagged_ports.append(int(port.split('/')[2])) + for lag in lags: + if "to" in lag: + l = lag.split(" to ") + lr = int(l[1]) - int(l[0]) + for i in range(0, lr + 1): + tagged_lags.append((int(l[0]) + i)) + else: + tagged_lags.append(int(lag)) + if 'untagged' in line.split(): + lags = line.split(" lag ") + ports = lags[0].split(" ethe ") + del ports[0] + del lags[0] + for port in ports: + if "to" in port: + p = port.split(" to ") + pr = int(p[1].split('/')[2]) - int(p[0].split('/')[2]) + for i in range(0, pr + 1): + untagged_ports.append((int(p[0].split('/')[2]) + i)) + else: + untagged_ports.append(int(port.split('/')[2])) + for lag in lags: + if "to" in lag: + l = lag.split(" to ") + lr = int(l[1]) - int(l[0]) + for i in range(0, lr + 1): + untagged_lags.append((int(l[0]) + i)) + else: + untagged_lags.append(int(lag)) + + return untagged_ports, untagged_lags, tagged_ports, tagged_lags + + +def extract_list_from_interface(interface): + if 'ethernet' in interface: + if 'to' in interface: + s = re.search(r"\d+\/\d+/(?P\d+)\sto\s+\d+\/\d+/(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('high')) + else: + s = re.search(r"\d+\/\d+/(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('low')) + elif 'lag' in interface: + if 'to' in interface: + s = re.search(r"(?P\d+)\sto\s(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('high')) + else: + s = re.search(r"(?P\d+)", interface) + low = int(s.group('low')) + high = int(s.group('low')) + + return low, high + + +def parse_vlan_id(module): + vlans = [] + command = 'show vlan brief' + rc, out, err = exec_command(module, command) + lines = out.split('\n') + for line in lines: + if 'VLANs Configured :' in line: + values = line.split(':')[1] + vlans = [s for s in values.split() if s.isdigit()] + s = re.findall(r"(?P\d+)\sto\s(?P\d+)", values) + for ranges in s: + low = int(ranges[0]) + 1 + high = int(ranges[1]) + while(high > low): + vlans.append(str(low)) + low = low + 1 + return vlans + + +def spanning_tree(module, stp): + stp_cmd = list() + if stp.get('enabled') is False: + if stp.get('type') == '802-1w': + stp_cmd.append('no spanning-tree' + ' ' + stp.get('type')) + stp_cmd.append('no spanning-tree') + + elif stp.get('type'): + stp_cmd.append('spanning-tree' + ' ' + stp.get('type')) + if stp.get('priority') and stp.get('type') == 'rstp': + module.fail_json(msg='spanning-tree 802-1w only can have priority') + elif stp.get('priority'): + stp_cmd.append('spanning-tree' + ' ' + stp.get('type') + ' ' + 'priority' + ' ' + stp.get('priority')) + + return stp_cmd + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + stp = item.get('stp') + if stp: + stp_cmd = spanning_tree(module, stp) + item.update({'stp': stp_cmd}) + + d = item.copy() + + obj.append(d) + + else: + params = { + 'name': module.params['name'], + 'vlan_id': module.params['vlan_id'], + 'interfaces': module.params['interfaces'], + 'tagged': module.params['tagged'], + 'associated_interfaces': module.params['associated_interfaces'], + 'associated_tagged': module.params['associated_tagged'], + 'delay': module.params['delay'], + 'ip_dhcp_snooping': module.params['ip_dhcp_snooping'], + 'ip_arp_inspection': module.params['ip_arp_inspection'], + 'state': module.params['state'], + } + + stp = module.params.get('stp') + if stp: + stp_cmd = spanning_tree(module, stp) + params.update({'stp': stp_cmd}) + + obj.append(params) + + return obj + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + purge = module.params['purge'] + + for w in want: + vlan_id = w['vlan_id'] + state = w['state'] + name = w['name'] + interfaces = w.get('interfaces') + tagged = w.get('tagged') + dhcp = w.get('ip_dhcp_snooping') + arp = w.get('ip_arp_inspection') + stp = w.get('stp') + obj_in_have = search_obj_in_list(str(vlan_id), have) + + if state == 'absent': + if have == []: + commands.append('no vlan {0}'.format(vlan_id)) + if obj_in_have: + commands.append('no vlan {0}'.format(vlan_id)) + + elif state == 'present': + if not obj_in_have: + commands.append('vlan {0}'.format(vlan_id)) + if name: + commands.append('vlan {0} name {1}'.format(vlan_id, name)) + + if interfaces: + if interfaces['name']: + for item in interfaces['name']: + commands.append('untagged {0}'.format(item)) + + if tagged: + if tagged['name']: + for item in tagged['name']: + commands.append('tagged {0}'.format(item)) + + if dhcp is True: + commands.append('ip dhcp snooping vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip dhcp snooping vlan {0}'.format(vlan_id)) + + if arp is True: + commands.append('ip arp inspection vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip arp inspection vlan {0}'.format(vlan_id)) + + if stp: + if w.get('stp'): + [commands.append(cmd) for cmd in w['stp']] + + else: + commands.append('vlan {0}'.format(vlan_id)) + if name: + if name != obj_in_have['name']: + commands.append('vlan {0} name {1}'.format(vlan_id, name)) + + if interfaces: + if interfaces['name']: + have_interfaces = list() + for interface in interfaces['name']: + low, high = extract_list_from_interface(interface) + + while(high >= low): + if 'ethernet' in interface: + have_interfaces.append('ethernet 1/1/{0}'.format(low)) + if 'lag' in interface: + have_interfaces.append('lag {0}'.format(low)) + low = low + 1 + + if interfaces['purge'] is True: + remove_interfaces = list(set(obj_in_have['interfaces']) - set(have_interfaces)) + for item in remove_interfaces: + commands.append('no untagged {0}'.format(item)) + + if interfaces['name']: + add_interfaces = list(set(have_interfaces) - set(obj_in_have['interfaces'])) + for item in add_interfaces: + commands.append('untagged {0}'.format(item)) + + if tagged: + if tagged['name']: + have_tagged = list() + for tag in tagged['name']: + low, high = extract_list_from_interface(tag) + + while(high >= low): + if 'ethernet' in tag: + have_tagged.append('ethernet 1/1/{0}'.format(low)) + if 'lag' in tag: + have_tagged.append('lag {0}'.format(low)) + low = low + 1 + if tagged['purge'] is True: + remove_tagged = list(set(obj_in_have['tagged']) - set(have_tagged)) + for item in remove_tagged: + commands.append('no tagged {0}'.format(item)) + + if tagged['name']: + add_tagged = list(set(have_tagged) - set(obj_in_have['tagged'])) + for item in add_tagged: + commands.append('tagged {0}'.format(item)) + + if dhcp != obj_in_have['ip_dhcp_snooping']: + if dhcp is True: + commands.append('ip dhcp snooping vlan {0}'.format(vlan_id)) + elif dhcp is False: + commands.append('no ip dhcp snooping vlan {0}'.format(vlan_id)) + + if arp != obj_in_have['ip_arp_inspection']: + if arp is True: + commands.append('ip arp inspection vlan {0}'.format(vlan_id)) + elif arp is False: + commands.append('no ip arp inspection vlan {0}'.format(vlan_id)) + + if stp: + if w.get('stp'): + [commands.append(cmd) for cmd in w['stp']] + + if len(commands) == 1 and 'vlan ' + str(vlan_id) in commands: + commands = [] + + if purge: + commands = [] + vlans = parse_vlan_id(module) + for h in vlans: + obj_in_want = search_obj_in_list(h, want) + if not obj_in_want and h != '1': + commands.append('no vlan {0}'.format(h)) + + return commands + + +def parse_name_argument(module, item): + command = 'show vlan {0}'.format(item) + rc, out, err = exec_command(module, command) + match = re.search(r"Name (\S+),", out) + if match: + return match.group(1) + + +def parse_interfaces_argument(module, item, port_type): + untagged_ports, untagged_lags, tagged_ports, tagged_lags = parse_vlan_brief(module, item) + ports = list() + if port_type == "interfaces": + if untagged_ports: + for port in untagged_ports: + ports.append('ethernet 1/1/' + str(port)) + if untagged_lags: + for port in untagged_lags: + ports.append('lag ' + str(port)) + + elif port_type == "tagged": + if tagged_ports: + for port in tagged_ports: + ports.append('ethernet 1/1/' + str(port)) + if tagged_lags: + for port in tagged_lags: + ports.append('lag ' + str(port)) + + return ports + + +def parse_config_argument(config, arg): + match = re.search(arg, config, re.M) + if match: + return True + else: + return False + + +def map_config_to_obj(module): + config = get_config(module) + vlans = parse_vlan_id(module) + instance = list() + + for item in set(vlans): + obj = { + 'vlan_id': item, + 'name': parse_name_argument(module, item), + 'interfaces': parse_interfaces_argument(module, item, 'interfaces'), + 'tagged': parse_interfaces_argument(module, item, 'tagged'), + 'ip_dhcp_snooping': parse_config_argument(config, 'ip dhcp snooping vlan {0}'.format(item)), + 'ip_arp_inspection': parse_config_argument(config, 'ip arp inspection vlan {0}'.format(item)), + } + instance.append(obj) + return instance + + +def check_fail(module, output): + error = [ + re.compile(br"^error", re.I) + ] + for x in output: + for regex in error: + if regex.search(x): + module.fail_json(msg=x) + + +def check_declarative_intent_params(want, module, result): + def parse_ports(interfaces, ports, lags): + for interface in interfaces: + low, high = extract_list_from_interface(interface) + + while(high >= low): + if 'ethernet' in interface: + if not (low in ports): + module.fail_json(msg='One or more conditional statements have not been satisfied ' + interface) + if 'lag' in interface: + if not (low in lags): + module.fail_json(msg='One or more conditional statements have not been satisfied ' + interface) + low = low + 1 + + is_delay = False + low = 0 + high = 0 + for w in want: + if w.get('associated_interfaces') is None and w.get('associated_tagged') is None: + continue + + if result['changed'] and not is_delay: + sleep(module.params['delay']) + is_delay = True + + untagged_ports, untagged_lags, tagged_ports, tagged_lags = parse_vlan_brief(module, w['vlan_id']) + + if w['associated_interfaces']: + parse_ports(w.get('associated_interfaces'), untagged_ports, untagged_lags) + + if w['associated_tagged']: + parse_ports(w.get('associated_tagged'), tagged_ports, tagged_lags) + + +def main(): + """ main entry point for module execution + """ + stp_spec = dict( + type=dict(default='802-1w', choices=['802-1w', 'rstp']), + priority=dict(), + enabled=dict(type='bool'), + ) + inter_spec = dict( + name=dict(type='list'), + purge=dict(type='bool') + ) + tagged_spec = dict( + name=dict(type='list'), + purge=dict(type='bool') + ) + element_spec = dict( + vlan_id=dict(type='int'), + name=dict(), + interfaces=dict(type='dict', options=inter_spec), + tagged=dict(type='dict', options=tagged_spec), + ip_dhcp_snooping=dict(type='bool'), + ip_arp_inspection=dict(type='bool'), + associated_interfaces=dict(type='list'), + associated_tagged=dict(type='list'), + delay=dict(default=10, type='int'), + stp=dict(type='dict', options=stp_spec), + state=dict(default='present', choices=['present', 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) + ) + aggregate_spec = deepcopy(element_spec) + aggregate_spec['vlan_id'] = dict(required=True) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + argument_spec.update(element_spec) + required_one_of = [['vlan_id', 'aggregate']] + mutually_exclusive = [['vlan_id', 'aggregate']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + warnings = list() + result = {} + result['changed'] = False + if warnings: + result['warnings'] = warnings + exec_command(module, 'skip') + want = map_params_to_obj(module) + if module.params['check_running_config'] is False: + have = [] + else: + have = map_config_to_obj(module) + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + if not module.check_mode: + output = load_config(module, commands) + if output: + check_fail(module, output) + result['output'] = output + result['changed'] = True + + check_declarative_intent_params(want, module, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/network/icx/fixtures/icx_vlan_config b/test/units/modules/network/icx/fixtures/icx_vlan_config new file mode 100644 index 00000000000..b42e30f31b2 --- /dev/null +++ b/test/units/modules/network/icx/fixtures/icx_vlan_config @@ -0,0 +1,32 @@ +************show vlan brief***************** +System-max vlan Params: Max(4095) Default(64) Current(64) +Default vlan Id :1 +Total Number of Vlan Configured :5 +VLANs Configured :1 3 6 10 21 + + +************* show vlan id********** +Maximum PORT-VLAN entries: 64 + +Legend: [Stk=Stack-Id, S=Slot] + +PORT-VLAN 3, Name vlan, Priority level0, Spanning tree On + Untagged Ports: (U1/M1) 1 2 3 4 5 7 20 21 22 23 24 25 + Untagged Ports: (U1/M1) 26 27 28 + Untagged Ports: (LAG) 11 12 15 + Tagged Ports: (U1/M1) 9 10 11 31 + Tagged Ports: (LAG) 13 + Uplink Ports: None + DualMode Ports: None + Mac-Vlan Ports: None + Monitoring: Disabled + +************* show run vlan id********** +vlan 3 name vlan by port + tagged ethe 1/1/31 ethe 1/1/9 to 1/1/11 lag 13 + untagged ethe 1/1/27 ethe 1/1/20 to 1/1/22 lag 11 to 12 + spanning-tree +! +! + + diff --git a/test/units/modules/network/icx/test_icx_vlan.py b/test/units/modules/network/icx/test_icx_vlan.py new file mode 100644 index 00000000000..42fff68eecb --- /dev/null +++ b/test/units/modules/network/icx/test_icx_vlan.py @@ -0,0 +1,279 @@ +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +from units.compat.mock import patch +from ansible.modules.network.icx import icx_vlan +from units.modules.utils import set_module_args +from .icx_module import TestICXModule, load_fixture + + +class TestICXVlanModule(TestICXModule): + + module = icx_vlan + + def setUp(self): + super(TestICXVlanModule, self).setUp() + self.mock_exec_command = patch('ansible.modules.network.icx.icx_vlan.exec_command') + self.exec_command = self.mock_exec_command.start() + + self.mock_load_config = patch('ansible.modules.network.icx.icx_vlan.load_config') + self.load_config = self.mock_load_config.start() + + self.mock_get_config = patch('ansible.modules.network.icx.icx_vlan.get_config') + self.get_config = self.mock_get_config.start() + + self.set_running_config() + + def tearDown(self): + super(TestICXVlanModule, self).tearDown() + self.mock_exec_command.stop() + self.mock_load_config.stop() + self.mock_get_config.stop() + + def load_fixtures(self, commands=None): + compares = None + + def load_file(*args, **kwargs): + module = args + for arg in args: + if arg.params['check_running_config'] is True: + self.exec_command.return_value = (0, load_fixture('icx_vlan_config').strip(), None) + return load_fixture('icx_banner_show_banner.txt').strip() + else: + self.exec_command.return_value = (0, ''.strip(), None) + return '' + + self.get_config.side_effect = load_file + self.load_config.return_value = None + + def test_icx_vlan_set_tagged_port(self): + set_module_args(dict(name='test_vlan', vlan_id=5, tagged=dict(name=['ethernet 1/1/40 to 1/1/43', 'lag 44']))) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 5', + 'vlan 5 name test_vlan', + 'tagged ethernet 1/1/40 to 1/1/43', + 'tagged lag 44' + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 5', + 'vlan 5 name test_vlan', + 'tagged ethernet 1/1/40 to 1/1/43', + 'tagged lag 44' + ] + self.assertEqual(result['commands'], expected_commands) + + def test_icx_vlan_add_untagged_port(self): + set_module_args(dict(name='test_vlan', vlan_id=3, interfaces=dict(name=['ethernet 1/1/10', 'lag 5']))) + + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'vlan 3 name test_vlan', + 'untagged lag 5', + 'untagged ethernet 1/1/10' + ] + self.assertEqual(set(result['commands']), set(expected_commands)) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'vlan 3 name test_vlan', + 'untagged lag 5', + 'untagged ethernet 1/1/10' + ] + self.assertEqual(set(result['commands']), set(expected_commands)) + + def test_icx_vlan_purge_tagged_port(self): + set_module_args(dict(vlan_id=3, tagged=dict(name=['ethernet 1/1/40 to 1/1/42', 'lag 44'], purge=True))) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'tagged ethernet 1/1/40 to 1/1/43', + 'tagged lag 44' + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'no tagged ethernet 1/1/31', + 'no tagged ethernet 1/1/9', + 'no tagged ethernet 1/1/11', + 'no tagged lag 13', + 'no tagged ethernet 1/1/10', + 'tagged ethernet 1/1/40', + 'tagged ethernet 1/1/41', + 'tagged ethernet 1/1/42', + 'tagged lag 44' + ] + self.assertEqual(set(result['commands']), set(expected_commands)) + + def test_icx_vlan_enable_ip_arp_inspection(self): + set_module_args(dict(vlan_id=5, ip_arp_inspection=True)) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 5', + 'ip arp inspection vlan 5' + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 5', + 'ip arp inspection vlan 5' + ] + self.assertEqual(result['commands'], expected_commands) + + def test_icx_vlan_enable_ip_dhcp_snooping(self): + set_module_args(dict(vlan_id=5, ip_dhcp_snooping=True)) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 5', + 'ip dhcp snooping vlan 5' + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 5', + 'ip dhcp snooping vlan 5' + ] + self.assertEqual(result['commands'], expected_commands) + + def test_icx_vlan_aggregate(self): + aggregate = [ + dict(vlan_id=9, name='vlan_9', interfaces=dict(name=['ethernet 1/1/40 to 1/1/43', 'ethernet 1/1/44']), ip_arp_inspection=True), + dict(vlan_id=7, name='vlan_7', interfaces=dict(name=['ethernet 1/1/20 to 1/1/23', 'ethernet 1/1/24']), ip_dhcp_snooping=True), + ] + set_module_args(dict(aggregate=aggregate)) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 9', + 'vlan 9 name vlan_9', + 'untagged ethernet 1/1/40 to 1/1/43', + 'untagged ethernet 1/1/44', + 'ip arp inspection vlan 9', + 'vlan 7', + 'vlan 7 name vlan_7', + 'untagged ethernet 1/1/20 to 1/1/23', + 'untagged ethernet 1/1/24', + 'ip dhcp snooping vlan 7', + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 9', + 'vlan 9 name vlan_9', + 'untagged ethernet 1/1/40 to 1/1/43', + 'untagged ethernet 1/1/44', + 'ip arp inspection vlan 9', + 'vlan 7', + 'vlan 7 name vlan_7', + 'untagged ethernet 1/1/20 to 1/1/23', + 'untagged ethernet 1/1/24', + 'ip dhcp snooping vlan 7', + ] + self.assertEqual(result['commands'], expected_commands) + + def test_icx_vlan_interfaces_cndt(self): + set_module_args(dict(vlan_id=3, associated_interfaces=['ethernet 1/1/20 to 1/1/22', 'ethernet 1/1/27', 'lag 11 to 12'])) + if not self.ENV_ICX_USE_DIFF: + self.execute_module(failed=True) + else: + self.execute_module(changed=False) + + def test_icx_vlan_tagged_cndt(self): + set_module_args(dict(vlan_id=3, associated_tagged=['ethernet 1/1/9 to 1/1/11', 'ethernet 1/1/31', 'lag 13'])) + if not self.ENV_ICX_USE_DIFF: + self.execute_module(failed=True) + else: + self.execute_module(changed=False) + + def test_icx_vlan_purge(self): + set_module_args(dict(vlan_id=3, purge=True)) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=False) + expected_commands = [] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'no vlan 6', + 'no vlan 10', + 'no vlan 21' + ] + self.assertEqual(result['commands'], expected_commands) + + def test_icx_vlan_stp_802_1w(self): + stp_spec = dict(dict(type='802-1w', priority='20', enabled=True)) + set_module_args(dict(vlan_id=3, interfaces=dict(name=['ethernet 1/1/40']), stp=stp_spec)) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'untagged ethernet 1/1/40', + 'spanning-tree 802-1w', + 'spanning-tree 802-1w priority 20' + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'untagged ethernet 1/1/40', + 'spanning-tree 802-1w', + 'spanning-tree 802-1w priority 20' + ] + self.assertEqual(result['commands'], expected_commands) + + def test_icx_vlan_stp_rstp_absent(self): + stp_spec = dict(dict(type='rstp', enabled=False)) + set_module_args(dict(vlan_id=3, interfaces=dict(name=['ethernet 1/1/40']), stp=stp_spec)) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'untagged ethernet 1/1/40', + 'no spanning-tree' + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'untagged ethernet 1/1/40', + 'no spanning-tree' + ] + self.assertEqual(result['commands'], expected_commands) + + def test_icx_vlan_stp_802_1w_absent(self): + stp_spec = dict(dict(type='802-1w', enabled=False)) + set_module_args(dict(vlan_id=3, stp=stp_spec)) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'no spanning-tree 802-1w', + 'no spanning-tree' + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'no spanning-tree 802-1w', + 'no spanning-tree' + ] + self.assertEqual(result['commands'], expected_commands)