#!/usr/bin/python # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # from __future__ import (absolute_import, division, print_function) __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' --- module: ce_switchport short_description: Manages Layer 2 switchport interfaces on HUAWEI CloudEngine switches. description: - Manages Layer 2 switchport interfaces on HUAWEI CloudEngine switches. author: QijunPan (@QijunPan) notes: - When C(state=absent), VLANs can be added/removed from trunk links and the existing access VLAN can be 'unconfigured' to just having VLAN 1 on that interface. - When working with trunks VLANs the keywords add/remove are always sent in the C(port trunk allow-pass vlan) command. Use verbose mode to see commands sent. - When C(state=unconfigured), the interface will result with having a default Layer 2 interface, i.e. vlan 1 in access mode. - This module requires the netconf system service be enabled on the remote device being managed. - Recommended connection is C(netconf). - This module also works with C(local) connections for legacy playbooks. options: interface: description: - Full name of the interface, i.e. 40GE1/0/22. required: true mode: description: - The link type of an interface. choices: ['access','trunk', 'hybrid', 'dot1qtunnel'] default_vlan: description: - If C(mode=access, or mode=dot1qtunnel), used as the access VLAN ID, in the range from 1 to 4094. pvid_vlan: description: - If C(mode=trunk, or mode=hybrid), used as the trunk native VLAN ID, in the range from 1 to 4094. trunk_vlans: description: - If C(mode=trunk), used as the VLAN range to ADD or REMOVE from the trunk, such as 2-10 or 2,5,10-15, etc. untagged_vlans: description: - If C(mode=hybrid), used as the VLAN range to ADD or REMOVE from the trunk, such as 2-10 or 2,5,10-15, etc. tagged_vlans: description: - If C(mode=hybrid), used as the VLAN range to ADD or REMOVE from the trunk, such as 2-10 or 2,5,10-15, etc. state: description: - Manage the state of the resource. default: present choices: ['present', 'absent', 'unconfigured'] ''' EXAMPLES = ''' - name: switchport module test hosts: cloudengine connection: local gather_facts: no vars: cli: host: "{{ inventory_hostname }}" port: "{{ ansible_ssh_port }}" username: "{{ username }}" password: "{{ password }}" transport: cli tasks: - name: Ensure 10GE1/0/22 is in its default switchport state ce_switchport: interface: 10GE1/0/22 state: unconfigured provider: '{{ cli }}' - name: Ensure 10GE1/0/22 is configured for access vlan 20 ce_switchport: interface: 10GE1/0/22 mode: access default_vlan: 20 provider: '{{ cli }}' - name: Ensure 10GE1/0/22 only has vlans 5-10 as trunk vlans ce_switchport: interface: 10GE1/0/22 mode: trunk pvid_vlan: 10 trunk_vlans: 5-10 provider: '{{ cli }}' - name: Ensure 10GE1/0/22 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged) ce_switchport: interface: 10GE1/0/22 mode: trunk pvid_vlan: 10 trunk_vlans: 2-50 provider: '{{ cli }}' - name: Ensure these VLANs are not being tagged on the trunk ce_switchport: interface: 10GE1/0/22 mode: trunk trunk_vlans: 51-4000 state: absent provider: '{{ cli }}' ''' RETURN = ''' proposed: description: k/v pairs of parameters passed into module returned: always type: dict sample: {"default_vlan": "20", "interface": "10GE1/0/22", "mode": "access"} existing: description: k/v pairs of existing switchport returned: always type: dict sample: {"default_vlan": "10", "interface": "10GE1/0/22", "mode": "access", "switchport": "enable"} end_state: description: k/v pairs of switchport after module execution returned: always type: dict sample: {"default_vlan": "20", "interface": "10GE1/0/22", "mode": "access", "switchport": "enable"} updates: description: command string sent to the device returned: always type: list sample: ["10GE1/0/22", "port default vlan 20"] changed: description: check to see if a change was made on the device returned: always type: bool sample: true ''' import re from xml.etree import ElementTree as ET from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec CE_NC_GET_PORT_ATTR = """ %s """ CE_NC_SET_PORT = """ %s %s %s %s %s """ CE_NC_SET_PORT_MODE = """ %s %s """ CE_NC_SET_DEFAULT_PORT = """ %s access 1 """ SWITCH_PORT_TYPE = ('ge', '10ge', '25ge', '4x10ge', '40ge', '100ge', 'eth-trunk') def get_interface_type(interface): """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" if interface is None: return None iftype = None if interface.upper().startswith('GE'): iftype = 'ge' elif interface.upper().startswith('10GE'): iftype = '10ge' elif interface.upper().startswith('25GE'): iftype = '25ge' elif interface.upper().startswith('4X10GE'): iftype = '4x10ge' elif interface.upper().startswith('40GE'): iftype = '40ge' elif interface.upper().startswith('100GE'): iftype = '100ge' elif interface.upper().startswith('VLANIF'): iftype = 'vlanif' elif interface.upper().startswith('LOOPBACK'): iftype = 'loopback' elif interface.upper().startswith('METH'): iftype = 'meth' elif interface.upper().startswith('ETH-TRUNK'): iftype = 'eth-trunk' elif interface.upper().startswith('VBDIF'): iftype = 'vbdif' elif interface.upper().startswith('NVE'): iftype = 'nve' elif interface.upper().startswith('TUNNEL'): iftype = 'tunnel' elif interface.upper().startswith('ETHERNET'): iftype = 'ethernet' elif interface.upper().startswith('FCOE-PORT'): iftype = 'fcoe-port' elif interface.upper().startswith('FABRIC-PORT'): iftype = 'fabric-port' elif interface.upper().startswith('STACK-PORT'): iftype = 'stack-port' elif interface.upper().startswith('NULL'): iftype = 'null' else: return None return iftype.lower() def is_portswitch_enalbed(iftype): """"[undo] portswitch""" return bool(iftype in SWITCH_PORT_TYPE) def vlan_bitmap_undo(bitmap): """convert vlan bitmap to undo bitmap""" vlan_bit = ['F'] * 1024 if not bitmap or len(bitmap) == 0: return ''.join(vlan_bit) bit_len = len(bitmap) for num in range(bit_len): undo = (~int(bitmap[num], 16)) & 0xF vlan_bit[num] = hex(undo)[2] return ''.join(vlan_bit) def is_vlan_bitmap_empty(bitmap): """check vlan bitmap empty""" if not bitmap or len(bitmap) == 0: return True bit_len = len(bitmap) for num in range(bit_len): if bitmap[num] != '0': return False return True class SwitchPort(object): """ Manages Layer 2 switchport interfaces. """ def __init__(self, argument_spec): self.spec = argument_spec self.module = None self.init_module() # interface and vlan info self.interface = self.module.params['interface'] self.mode = self.module.params['mode'] self.state = self.module.params['state'] self.default_vlan = self.module.params['default_vlan'] self.pvid_vlan = self.module.params['pvid_vlan'] self.trunk_vlans = self.module.params['trunk_vlans'] self.untagged_vlans = self.module.params['untagged_vlans'] self.tagged_vlans = self.module.params['tagged_vlans'] # host info self.host = self.module.params['host'] self.username = self.module.params['username'] self.port = self.module.params['port'] # state self.changed = False self.updates_cmd = list() self.results = dict() self.proposed = dict() self.existing = dict() self.end_state = dict() self.intf_info = dict() # interface vlan info self.intf_type = None # loopback tunnel ... def init_module(self): """ init module """ required_if = [('state', 'absent', ['mode']), ('state', 'present', ['mode'])] mutually_exclusive = [['default_vlan', 'trunk_vlans'], ['default_vlan', 'pvid_vlan'], ['default_vlan', 'untagged_vlans'], ['trunk_vlans', 'untagged_vlans'], ['trunk_vlans', 'tagged_vlans'], ['default_vlan', 'tagged_vlans']] self.module = AnsibleModule( argument_spec=self.spec, required_if=required_if, supports_check_mode=True, mutually_exclusive=mutually_exclusive) def check_response(self, xml_str, xml_name): """Check if response message is already succeed.""" if "" not in xml_str: self.module.fail_json(msg='Error: %s failed.' % xml_name) def get_interface_dict(self, ifname): """ get one interface attributes dict.""" intf_info = dict() conf_str = CE_NC_GET_PORT_ATTR % ifname xml_str = get_nc_config(self.module, conf_str) if "" in xml_str: return intf_info xml_str = xml_str.replace('\r', '').replace('\n', '').\ replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\ replace('xmlns="http://www.huawei.com/netconf/vrp"', "") tree = ET.fromstring(xml_str) l2Enable = tree.find('ethernet/ethernetIfs/ethernetIf/l2Enable') intf_info["l2Enable"] = l2Enable.text port_type = tree.find('ethernet/ethernetIfs/ethernetIf/l2Attribute') for pre in port_type: intf_info[pre.tag] = pre.text intf_info["ifName"] = ifname if intf_info["trunkVlans"] is None: intf_info["trunkVlans"] = "" if intf_info["untagVlans"] is None: intf_info["untagVlans"] = "" return intf_info def is_l2switchport(self): """Check layer2 switch port""" return bool(self.intf_info["l2Enable"] == "enable") def merge_access_vlan(self, ifname, default_vlan): """Merge access interface vlan""" change = False conf_str = "" self.updates_cmd.append("interface %s" % ifname) if self.state == "present": if self.intf_info["linkType"] == "access": if default_vlan and self.intf_info["pvid"] != default_vlan: self.updates_cmd.append( "port default vlan %s" % default_vlan) conf_str = CE_NC_SET_PORT % (ifname, "access", default_vlan, "", "") change = True else: # not access self.updates_cmd.append("port link-type access") if default_vlan: self.updates_cmd.append( "port default vlan %s" % default_vlan) conf_str = CE_NC_SET_PORT % (ifname, "access", default_vlan, "", "") else: conf_str = CE_NC_SET_PORT % (ifname, "access", "1", "", "") change = True elif self.state == "absent": if self.intf_info["linkType"] == "access": if default_vlan and self.intf_info["pvid"] == default_vlan and default_vlan != "1": self.updates_cmd.append( "undo port default vlan %s" % default_vlan) conf_str = CE_NC_SET_PORT % (ifname, "access", "1", "", "") change = True if not change: self.updates_cmd.pop() # remove interface return conf_str = "" + conf_str + "" rcv_xml = set_nc_config(self.module, conf_str) self.check_response(rcv_xml, "MERGE_ACCESS_PORT") self.changed = True def merge_trunk_vlan(self, ifname, pvid_vlan, trunk_vlans): """Merge trunk interface vlan""" change = False xmlstr = "" pvid = "" trunk = "" self.updates_cmd.append("interface %s" % ifname) if trunk_vlans: vlan_list = self.vlan_range_to_list(trunk_vlans) vlan_map = self.vlan_list_to_bitmap(vlan_list) if self.state == "present": if self.intf_info["linkType"] == "trunk": if pvid_vlan and self.intf_info["pvid"] != pvid_vlan: self.updates_cmd.append( "port trunk pvid vlan %s" % pvid_vlan) pvid = pvid_vlan change = True if trunk_vlans: add_vlans = self.vlan_bitmap_add( self.intf_info["trunkVlans"], vlan_map) if not is_vlan_bitmap_empty(add_vlans): self.updates_cmd.append( "port trunk allow-pass %s" % trunk_vlans.replace(',', ' ').replace('-', ' to ')) trunk = "%s:%s" % (add_vlans, add_vlans) change = True if pvid or trunk: xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") if not pvid: xmlstr = xmlstr.replace("", "") if not trunk: xmlstr = xmlstr.replace("", "") else: # not trunk self.updates_cmd.append("port link-type trunk") change = True if pvid_vlan: self.updates_cmd.append( "port trunk pvid vlan %s" % pvid_vlan) pvid = pvid_vlan if trunk_vlans: self.updates_cmd.append( "port trunk allow-pass %s" % trunk_vlans.replace(',', ' ').replace('-', ' to ')) trunk = "%s:%s" % (vlan_map, vlan_map) if pvid or trunk: xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") if not pvid: xmlstr = xmlstr.replace("", "") if not trunk: xmlstr = xmlstr.replace("", "") if not pvid_vlan and not trunk_vlans: xmlstr += CE_NC_SET_PORT_MODE % (ifname, "trunk") self.updates_cmd.append( "undo port trunk allow-pass vlan 1") elif self.state == "absent": if self.intf_info["linkType"] == "trunk": if pvid_vlan and self.intf_info["pvid"] == pvid_vlan and pvid_vlan != '1': self.updates_cmd.append( "undo port trunk pvid vlan %s" % pvid_vlan) pvid = "1" change = True if trunk_vlans: del_vlans = self.vlan_bitmap_del( self.intf_info["trunkVlans"], vlan_map) if not is_vlan_bitmap_empty(del_vlans): self.updates_cmd.append( "undo port trunk allow-pass %s" % trunk_vlans.replace(',', ' ').replace('-', ' to ')) undo_map = vlan_bitmap_undo(del_vlans) trunk = "%s:%s" % (undo_map, del_vlans) change = True if pvid or trunk: xmlstr += CE_NC_SET_PORT % (ifname, "trunk", pvid, trunk, "") if not pvid: xmlstr = xmlstr.replace("", "") if not trunk: xmlstr = xmlstr.replace("", "") if not change: self.updates_cmd.pop() return conf_str = "" + xmlstr + "" rcv_xml = set_nc_config(self.module, conf_str) self.check_response(rcv_xml, "MERGE_TRUNK_PORT") self.changed = True def merge_hybrid_vlan(self, ifname, pvid_vlan, tagged_vlans, untagged_vlans): """Merge hybrid interface vlan""" change = False xmlstr = "" pvid = "" tagged = "" untagged = "" self.updates_cmd.append("interface %s" % ifname) if tagged_vlans: vlan_targed_list = self.vlan_range_to_list(tagged_vlans) vlan_targed_map = self.vlan_list_to_bitmap(vlan_targed_list) if untagged_vlans: vlan_untarged_list = self.vlan_range_to_list(untagged_vlans) vlan_untarged_map = self.vlan_list_to_bitmap(vlan_untarged_list) if self.state == "present": if self.intf_info["linkType"] == "hybrid": if pvid_vlan and self.intf_info["pvid"] != pvid_vlan: self.updates_cmd.append( "port hybrid pvid vlan %s" % pvid_vlan) pvid = pvid_vlan change = True if tagged_vlans: add_vlans = self.vlan_bitmap_add( self.intf_info["trunkVlans"], vlan_targed_map) if not is_vlan_bitmap_empty(add_vlans): self.updates_cmd.append( "port hybrid tagged vlan %s" % tagged_vlans.replace(',', ' ').replace('-', ' to ')) tagged = "%s:%s" % (add_vlans, add_vlans) change = True if untagged_vlans: add_vlans = self.vlan_bitmap_add( self.intf_info["untagVlans"], vlan_untarged_map) if not is_vlan_bitmap_empty(add_vlans): self.updates_cmd.append( "port hybrid untagged vlan %s" % untagged_vlans.replace(',', ' ').replace('-', ' to ')) untagged = "%s:%s" % (add_vlans, add_vlans) change = True if pvid or tagged or untagged: xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) if not pvid: xmlstr = xmlstr.replace("", "") if not tagged: xmlstr = xmlstr.replace("", "") if not untagged: xmlstr = xmlstr.replace("", "") else: self.updates_cmd.append("port link-type hybrid") change = True if pvid_vlan: self.updates_cmd.append( "port hybrid pvid vlan %s" % pvid_vlan) pvid = pvid_vlan if tagged_vlans: self.updates_cmd.append( "port hybrid tagged vlan %s" % tagged_vlans.replace(',', ' ').replace('-', ' to ')) tagged = "%s:%s" % (vlan_targed_map, vlan_targed_map) if untagged_vlans: self.updates_cmd.append( "port hybrid untagged vlan %s" % untagged_vlans.replace(',', ' ').replace('-', ' to ')) untagged = "%s:%s" % (vlan_untarged_map, vlan_untarged_map) if pvid or tagged or untagged: xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) if not pvid: xmlstr = xmlstr.replace("", "") if not tagged: xmlstr = xmlstr.replace("", "") if not untagged: xmlstr = xmlstr.replace("", "") if not pvid_vlan and not tagged_vlans and not untagged_vlans: xmlstr += CE_NC_SET_PORT_MODE % (ifname, "hybrid") self.updates_cmd.append( "undo port hybrid untagged vlan 1") elif self.state == "absent": if self.intf_info["linkType"] == "hybrid": if pvid_vlan and self.intf_info["pvid"] == pvid_vlan and pvid_vlan != '1': self.updates_cmd.append( "undo port hybrid pvid vlan %s" % pvid_vlan) pvid = "1" change = True if tagged_vlans: del_vlans = self.vlan_bitmap_del( self.intf_info["trunkVlans"], vlan_targed_map) if not is_vlan_bitmap_empty(del_vlans): self.updates_cmd.append( "undo port hybrid tagged vlan %s" % tagged_vlans.replace(',', ' ').replace('-', ' to ')) undo_map = vlan_bitmap_undo(del_vlans) tagged = "%s:%s" % (undo_map, del_vlans) change = True if untagged_vlans: del_vlans = self.vlan_bitmap_del( self.intf_info["untagVlans"], vlan_untarged_map) if not is_vlan_bitmap_empty(del_vlans): self.updates_cmd.append( "undo port hybrid untagged vlan %s" % untagged_vlans.replace(',', ' ').replace('-', ' to ')) undo_map = vlan_bitmap_undo(del_vlans) untagged = "%s:%s" % (undo_map, del_vlans) change = True if pvid or tagged or untagged: xmlstr += CE_NC_SET_PORT % (ifname, "hybrid", pvid, tagged, untagged) if not pvid: xmlstr = xmlstr.replace("", "") if not tagged: xmlstr = xmlstr.replace("", "") if not untagged: xmlstr = xmlstr.replace("", "") if not change: self.updates_cmd.pop() return conf_str = "" + xmlstr + "" rcv_xml = set_nc_config(self.module, conf_str) self.check_response(rcv_xml, "MERGE_HYBRID_PORT") self.changed = True def merge_dot1qtunnel_vlan(self, ifname, default_vlan): """Merge dot1qtunnel""" change = False conf_str = "" self.updates_cmd.append("interface %s" % ifname) if self.state == "present": if self.intf_info["linkType"] == "dot1qtunnel": if default_vlan and self.intf_info["pvid"] != default_vlan: self.updates_cmd.append( "port default vlan %s" % default_vlan) conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", default_vlan, "", "") change = True else: self.updates_cmd.append("port link-type dot1qtunnel") if default_vlan: self.updates_cmd.append( "port default vlan %s" % default_vlan) conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", default_vlan, "", "") else: conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", "1", "", "") change = True elif self.state == "absent": if self.intf_info["linkType"] == "dot1qtunnel": if default_vlan and self.intf_info["pvid"] == default_vlan and default_vlan != "1": self.updates_cmd.append( "undo port default vlan %s" % default_vlan) conf_str = CE_NC_SET_PORT % (ifname, "dot1qtunnel", "1", "", "") change = True if not change: self.updates_cmd.pop() # remove interface return conf_str = "" + conf_str + "" rcv_xml = set_nc_config(self.module, conf_str) self.check_response(rcv_xml, "MERGE_DOT1QTUNNEL_PORT") self.changed = True def default_switchport(self, ifname): """Set interface default or unconfigured""" change = False if self.intf_info["linkType"] != "access": self.updates_cmd.append("interface %s" % ifname) self.updates_cmd.append("port link-type access") self.updates_cmd.append("port default vlan 1") change = True else: if self.intf_info["pvid"] != "1": self.updates_cmd.append("interface %s" % ifname) self.updates_cmd.append("port default vlan 1") change = True if not change: return conf_str = CE_NC_SET_DEFAULT_PORT % ifname rcv_xml = set_nc_config(self.module, conf_str) self.check_response(rcv_xml, "DEFAULT_INTF_VLAN") self.changed = True def vlan_series(self, vlanid_s): """ convert vlan range to vlan list """ vlan_list = [] peerlistlen = len(vlanid_s) if peerlistlen != 2: self.module.fail_json(msg='Error: Format of vlanid is invalid.') for num in range(peerlistlen): if not vlanid_s[num].isdigit(): self.module.fail_json( msg='Error: Format of vlanid is invalid.') if int(vlanid_s[0]) > int(vlanid_s[1]): self.module.fail_json(msg='Error: Format of vlanid is invalid.') elif int(vlanid_s[0]) == int(vlanid_s[1]): vlan_list.append(str(vlanid_s[0])) return vlan_list for num in range(int(vlanid_s[0]), int(vlanid_s[1])): vlan_list.append(str(num)) vlan_list.append(vlanid_s[1]) return vlan_list def vlan_region(self, vlanid_list): """ convert vlan range to vlan list """ vlan_list = [] peerlistlen = len(vlanid_list) for num in range(peerlistlen): if vlanid_list[num].isdigit(): vlan_list.append(vlanid_list[num]) else: vlan_s = self.vlan_series(vlanid_list[num].split('-')) vlan_list.extend(vlan_s) return vlan_list def vlan_range_to_list(self, vlan_range): """ convert vlan range to vlan list """ vlan_list = self.vlan_region(vlan_range.split(',')) return vlan_list def vlan_list_to_bitmap(self, vlanlist): """ convert vlan list to vlan bitmap """ vlan_bit = ['0'] * 1024 bit_int = [0] * 1024 vlan_list_len = len(vlanlist) for num in range(vlan_list_len): tagged_vlans = int(vlanlist[num]) if tagged_vlans <= 0 or tagged_vlans > 4094: self.module.fail_json( msg='Error: Vlan id is not in the range from 1 to 4094.') j = tagged_vlans // 4 bit_int[j] |= 0x8 >> (tagged_vlans % 4) vlan_bit[j] = hex(bit_int[j])[2] vlan_xml = ''.join(vlan_bit) return vlan_xml def vlan_bitmap_add(self, oldmap, newmap): """vlan add bitmap""" vlan_bit = ['0'] * 1024 if len(newmap) != 1024: self.module.fail_json(msg='Error: New vlan bitmap is invalid.') if len(oldmap) != 1024 and len(oldmap) != 0: self.module.fail_json(msg='Error: old vlan bitmap is invalid.') if len(oldmap) == 0: return newmap for num in range(1024): new_tmp = int(newmap[num], 16) old_tmp = int(oldmap[num], 16) add = (~(new_tmp & old_tmp)) & new_tmp vlan_bit[num] = hex(add)[2] vlan_xml = ''.join(vlan_bit) return vlan_xml def vlan_bitmap_del(self, oldmap, delmap): """vlan del bitmap""" vlan_bit = ['0'] * 1024 if not oldmap or len(oldmap) == 0: return ''.join(vlan_bit) if len(oldmap) != 1024 or len(delmap) != 1024: self.module.fail_json(msg='Error: vlan bitmap is invalid.') for num in range(1024): tmp = int(delmap[num], 16) & int(oldmap[num], 16) vlan_bit[num] = hex(tmp)[2] vlan_xml = ''.join(vlan_bit) return vlan_xml def check_params(self): """Check all input params""" # interface type check if self.interface: self.intf_type = get_interface_type(self.interface) if not self.intf_type: self.module.fail_json( msg='Error: Interface name of %s is error.' % self.interface) if not self.intf_type or not is_portswitch_enalbed(self.intf_type): self.module.fail_json(msg='Error: Interface %s is error.') # check default_vlan if self.default_vlan: if not self.default_vlan.isdigit(): self.module.fail_json(msg='Error: Access vlan id is invalid.') if int(self.default_vlan) <= 0 or int(self.default_vlan) > 4094: self.module.fail_json( msg='Error: Access vlan id is not in the range from 1 to 4094.') # check pvid_vlan if self.pvid_vlan: if not self.pvid_vlan.isdigit(): self.module.fail_json(msg='Error: Pvid vlan id is invalid.') if int(self.pvid_vlan) <= 0 or int(self.pvid_vlan) > 4094: self.module.fail_json( msg='Error: Pvid vlan id is not in the range from 1 to 4094.') # get interface info self.intf_info = self.get_interface_dict(self.interface) if not self.intf_info: self.module.fail_json(msg='Error: Interface does not exist.') if not self.is_l2switchport(): self.module.fail_json( msg='Error: Interface is not layer2 switch port.') if self.state == "unconfigured": if any([self.mode, self.default_vlan, self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): self.module.fail_json( msg='Error: When state is unconfigured, only interface name exists.') else: if self.mode == "access": if any([self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): self.module.fail_json( msg='Error: When mode is access, only default_vlan can be supported.') elif self.mode == "trunk": if any([self.default_vlan, self.untagged_vlans, self.tagged_vlans]): self.module.fail_json( msg='Error: When mode is trunk, only pvid_vlan and trunk_vlans can exist.') elif self.mode == "hybrid": if any([self.default_vlan, self.trunk_vlans]): self.module.fail_json( msg='Error: When mode is hybrid, default_vlan and trunk_vlans cannot exist') else: if any([self.pvid_vlan, self.trunk_vlans, self.untagged_vlans, self.tagged_vlans]): self.module.fail_json( msg='Error: When mode is dot1qtunnel, only default_vlan can be supported.') def get_proposed(self): """get proposed info""" self.proposed['state'] = self.state self.proposed['interface'] = self.interface self.proposed['mode'] = self.mode if self.mode: if self.mode == "access": self.proposed['access_pvid'] = self.default_vlan elif self.mode == "trunk": self.proposed['pvid_vlan'] = self.pvid_vlan self.proposed['trunk_vlans'] = self.trunk_vlans elif self.mode == "hybrid": self.proposed['pvid_vlan'] = self.pvid_vlan self.proposed['untagged_vlans'] = self.untagged_vlans self.proposed['tagged_vlans'] = self.tagged_vlans else: self.proposed['dot1qtunnel_pvid'] = self.default_vlan def get_existing(self): """get existing info""" if self.intf_info: self.existing["interface"] = self.intf_info["ifName"] self.existing["switchport"] = self.intf_info["l2Enable"] self.existing["mode"] = self.intf_info["linkType"] if self.intf_info["linkType"] == "access": self.existing['access_pvid'] = self.intf_info["pvid"] elif self.intf_info["linkType"] == "trunk": self.existing['trunk_pvid'] = self.intf_info["pvid"] self.existing['trunk_vlans'] = self.intf_info["trunkVlans"] elif self.intf_info["linkType"] == "hybrid": self.existing['hybrid_pvid'] = self.intf_info["pvid"] self.existing['hybrid_untagged_vlans'] = self.intf_info["untagVlans"] self.existing['hybrid_tagged_vlans'] = self.intf_info["trunkVlans"] else: self.existing['dot1qtunnel_pvid'] = self.intf_info["pvid"] def get_end_state(self): """get end state info""" end_info = self.get_interface_dict(self.interface) if end_info: self.end_state["interface"] = end_info["ifName"] self.end_state["switchport"] = end_info["l2Enable"] self.end_state["mode"] = end_info["linkType"] if end_info["linkType"] == "access": self.end_state['access_pvid'] = end_info["pvid"] elif end_info["linkType"] == "trunk": self.end_state['trunk_pvid'] = end_info["pvid"] self.end_state['trunk_vlans'] = end_info["trunkVlans"] elif end_info["linkType"] == "hybrid": self.end_state['hybrid_pvid'] = end_info["pvid"] self.end_state['hybrid_untagged_vlans'] = end_info["untagVlans"] self.end_state['hybrid_tagged_vlans'] = end_info["trunkVlans"] else: self.end_state['dot1qtunnel_pvid'] = end_info["pvid"] if self.end_state == self.existing: self.changed = False def work(self): """worker""" self.check_params() if not self.intf_info: self.module.fail_json(msg='Error: interface does not exist.') self.get_existing() self.get_proposed() # present or absent if self.state == "present" or self.state == "absent": if self.mode == "access": self.merge_access_vlan(self.interface, self.default_vlan) elif self.mode == "trunk": self.merge_trunk_vlan( self.interface, self.pvid_vlan, self.trunk_vlans) elif self.mode == "hybrid": self.merge_hybrid_vlan(self.interface, self.pvid_vlan, self.tagged_vlans, self.untagged_vlans) else: self.merge_dot1qtunnel_vlan(self.interface, self.default_vlan) # unconfigured else: self.default_switchport(self.interface) self.get_end_state() self.results['changed'] = self.changed self.results['proposed'] = self.proposed self.results['existing'] = self.existing self.results['end_state'] = self.end_state if self.changed: self.results['updates'] = self.updates_cmd else: self.results['updates'] = list() self.module.exit_json(**self.results) def main(): """Module main""" argument_spec = dict( interface=dict(required=True, type='str'), mode=dict(choices=['access', 'trunk', 'dot1qtunnel', 'hybrid'], required=False), default_vlan=dict(type='str', required=False), pvid_vlan=dict(type='str', required=False), trunk_vlans=dict(type='str', required=False), untagged_vlans=dict(type='str', required=False), tagged_vlans=dict(type='str', required=False), state=dict(choices=['absent', 'present', 'unconfigured'], default='present') ) argument_spec.update(ce_argument_spec) switchport = SwitchPort(argument_spec) switchport.work() if __name__ == '__main__': main()