mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
new network module: edgeswitch_vlan (#48041)
* initial commit * better commands generation
This commit is contained in:
parent
1580afbd62
commit
134c1a624e
7 changed files with 995 additions and 1 deletions
7
.github/BOTMETA.yml
vendored
7
.github/BOTMETA.yml
vendored
|
@ -223,6 +223,7 @@ files:
|
||||||
$modules/network/dellos6/: abirami-n skg-net
|
$modules/network/dellos6/: abirami-n skg-net
|
||||||
$modules/network/dellos9/: Dhivyap skg-net
|
$modules/network/dellos9/: Dhivyap skg-net
|
||||||
$modules/network/edgeos/: samdoran
|
$modules/network/edgeos/: samdoran
|
||||||
|
$modules/network/edgeswitch/: f-bor
|
||||||
$modules/network/enos/: amuraleedhar
|
$modules/network/enos/: amuraleedhar
|
||||||
$modules/network/eos/: trishnaguha
|
$modules/network/eos/: trishnaguha
|
||||||
$modules/network/exos/: rdvencioneck
|
$modules/network/exos/: rdvencioneck
|
||||||
|
@ -549,6 +550,8 @@ files:
|
||||||
maintainers: skg-net
|
maintainers: skg-net
|
||||||
$module_utils/network/dellos10:
|
$module_utils/network/dellos10:
|
||||||
maintainers: skg-net
|
maintainers: skg-net
|
||||||
|
$module_utils/network/edgeswitch:
|
||||||
|
maintainers: f-bor
|
||||||
$module_utils/network/enos:
|
$module_utils/network/enos:
|
||||||
maintainers: amuraleedhar
|
maintainers: amuraleedhar
|
||||||
$module_utils/network/eos:
|
$module_utils/network/eos:
|
||||||
|
@ -845,6 +848,8 @@ files:
|
||||||
|
|
||||||
$plugins/cliconf/:
|
$plugins/cliconf/:
|
||||||
labels: networking
|
labels: networking
|
||||||
|
$plugins/cliconf/edgeswitch.py:
|
||||||
|
maintainers: f-bor
|
||||||
$plugins/cliconf/eos.py:
|
$plugins/cliconf/eos.py:
|
||||||
support: network
|
support: network
|
||||||
maintainers: $team_networking
|
maintainers: $team_networking
|
||||||
|
@ -1036,6 +1041,8 @@ files:
|
||||||
maintainers: skg-net
|
maintainers: skg-net
|
||||||
$plugins/terminal/edgeos.py:
|
$plugins/terminal/edgeos.py:
|
||||||
maintainers: samdoran
|
maintainers: samdoran
|
||||||
|
$plugins/terminal/edgeswitch.py:
|
||||||
|
maintainers: f-bor
|
||||||
$plugins/terminal/eos.py:
|
$plugins/terminal/eos.py:
|
||||||
support: network
|
support: network
|
||||||
maintainers: $team_networking
|
maintainers: $team_networking
|
||||||
|
|
|
@ -47,7 +47,8 @@ def build_aggregate_spec(element_spec, required, *extra_spec):
|
||||||
aggregate=dict(type='list', elements='dict', options=aggregate_spec)
|
aggregate=dict(type='list', elements='dict', options=aggregate_spec)
|
||||||
)
|
)
|
||||||
argument_spec.update(element_spec)
|
argument_spec.update(element_spec)
|
||||||
argument_spec.update(*extra_spec)
|
for elt in extra_spec:
|
||||||
|
argument_spec.update(elt)
|
||||||
return argument_spec
|
return argument_spec
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
# This code is part of Ansible, but is an independent component.
|
||||||
|
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||||
|
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||||
|
# still belong to the author of the module, and may assign their own license
|
||||||
|
# to the complete work.
|
||||||
|
#
|
||||||
|
# (c) 2018 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceConfiguration:
|
||||||
|
def __init__(self):
|
||||||
|
self.commands = []
|
||||||
|
self.merged = False
|
||||||
|
|
||||||
|
def has_same_commands(self, interface):
|
||||||
|
len1 = len(self.commands)
|
||||||
|
len2 = len(interface.commands)
|
||||||
|
return len1 == len2 and len1 == len(frozenset(self.commands).intersection(interface.commands))
|
||||||
|
|
||||||
|
|
||||||
|
def merge_interfaces(interfaces):
|
||||||
|
""" to reduce commands generated by an edgeswitch module
|
||||||
|
we take interfaces one by one and we try to merge them with neighbors if everyone has same commands to run
|
||||||
|
"""
|
||||||
|
merged = {}
|
||||||
|
|
||||||
|
for i, interface in interfaces.items():
|
||||||
|
if interface.merged:
|
||||||
|
continue
|
||||||
|
interface.merged = True
|
||||||
|
|
||||||
|
match = re.match(r'(\d+)\/(\d+)', i)
|
||||||
|
group = int(match.group(1))
|
||||||
|
start = int(match.group(2))
|
||||||
|
end = start
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
start = start - 1
|
||||||
|
key = '{0}/{1}'.format(group, start)
|
||||||
|
neighbor = interfaces[key]
|
||||||
|
if not neighbor.merged and interface.has_same_commands(neighbor):
|
||||||
|
neighbor.merged = True
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
except KeyError:
|
||||||
|
break
|
||||||
|
start = start + 1
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
end = end + 1
|
||||||
|
key = '{0}/{1}'.format(group, end)
|
||||||
|
neighbor = interfaces[key]
|
||||||
|
if not neighbor.merged and interface.has_same_commands(neighbor):
|
||||||
|
neighbor.merged = True
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
except KeyError:
|
||||||
|
break
|
||||||
|
end = end - 1
|
||||||
|
|
||||||
|
if end == start:
|
||||||
|
key = '{0}/{1}'.format(group, start)
|
||||||
|
else:
|
||||||
|
key = '{0}/{1}-{2}/{3}'.format(group, start, group, end)
|
||||||
|
|
||||||
|
merged[key] = interface
|
||||||
|
return merged
|
498
lib/ansible/modules/network/edgeswitch/edgeswitch_vlan.py
Normal file
498
lib/ansible/modules/network/edgeswitch/edgeswitch_vlan.py
Normal file
|
@ -0,0 +1,498 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# (c) 2018, Ansible by Red Hat, inc
|
||||||
|
# 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: edgeswitch_vlan
|
||||||
|
version_added: "2.8"
|
||||||
|
author: "Frederic Bor (@f-bor)"
|
||||||
|
short_description: Manage VLANs on Ubiquiti Edgeswitch network devices
|
||||||
|
description:
|
||||||
|
- This module provides declarative management of VLANs
|
||||||
|
on Ubiquiti Edgeswitch network devices.
|
||||||
|
notes:
|
||||||
|
- Tested against edgeswitch 1.7.4
|
||||||
|
- This module use native Ubiquiti vlan syntax and does not support switchport compatibility syntax.
|
||||||
|
For clarity, it is strongly advised to not use both syntaxes on the same interface.
|
||||||
|
- Edgeswitch does not support deleting or changing name of VLAN 1
|
||||||
|
- As auto_tag, auto_untag and auto_exclude are a kind of default setting for all interfaces, they are mutually exclusive
|
||||||
|
|
||||||
|
options:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the VLAN.
|
||||||
|
vlan_id:
|
||||||
|
description:
|
||||||
|
- ID of the VLAN. Range 1-4093.
|
||||||
|
tagged_interfaces:
|
||||||
|
description:
|
||||||
|
- List of interfaces that should accept and transmit tagged frames for the VLAN.
|
||||||
|
Accept range of interfaces.
|
||||||
|
untagged_interfaces:
|
||||||
|
description:
|
||||||
|
- List of interfaces that should accept untagged frames and transmit them tagged for the VLAN.
|
||||||
|
Accept range of interfaces.
|
||||||
|
excluded_interfaces:
|
||||||
|
description:
|
||||||
|
- List of interfaces that should be excluded of the VLAN.
|
||||||
|
Accept range of interfaces.
|
||||||
|
auto_tag:
|
||||||
|
description:
|
||||||
|
- Each of the switch interfaces will be set to accept and transmit
|
||||||
|
untagged frames for I(vlan_id) unless defined in I(*_interfaces).
|
||||||
|
This is a default setting for all switch interfaces.
|
||||||
|
type: bool
|
||||||
|
auto_untag:
|
||||||
|
description:
|
||||||
|
- Each of the switch interfaces will be set to accept untagged frames and
|
||||||
|
transmit them tagged for I(vlan_id) unless defined in I(*_interfaces).
|
||||||
|
This is a default setting for all switch interfaces.
|
||||||
|
type: bool
|
||||||
|
auto_exclude:
|
||||||
|
description:
|
||||||
|
- Each of the switch interfaces will be excluded from I(vlan_id)
|
||||||
|
unless defined in I(*_interfaces).
|
||||||
|
This is a default setting for all switch interfaces.
|
||||||
|
type: bool
|
||||||
|
aggregate:
|
||||||
|
description: List of VLANs definitions.
|
||||||
|
purge:
|
||||||
|
description:
|
||||||
|
- Purge VLANs not defined in the I(aggregate) parameter.
|
||||||
|
default: no
|
||||||
|
type: bool
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- action on the VLAN configuration.
|
||||||
|
default: present
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Create vlan
|
||||||
|
edgeswitch_vlan:
|
||||||
|
vlan_id: 100
|
||||||
|
name: voice
|
||||||
|
action: present
|
||||||
|
|
||||||
|
- name: Add interfaces to VLAN
|
||||||
|
edgeswitch_vlan:
|
||||||
|
vlan_id: 100
|
||||||
|
tagged_interfaces:
|
||||||
|
- 0/1
|
||||||
|
- 0/4-0/6
|
||||||
|
|
||||||
|
- name: setup three vlans and delete the rest
|
||||||
|
edgeswitch_vlan:
|
||||||
|
purge: true
|
||||||
|
aggregate:
|
||||||
|
- { vlan_id: 1, name: default, auto_untag: true, excluded_interfaces: 0/45-0/48 }
|
||||||
|
- { vlan_id: 100, name: voice, auto_tag: true }
|
||||||
|
- { vlan_id: 200, name: video, auto_exclude: true, untagged_interfaces: 0/45-0/48, tagged_interfaces: 0/49 }
|
||||||
|
|
||||||
|
- name: Delete vlan
|
||||||
|
edgeswitch_vlan:
|
||||||
|
vlan_id: 100
|
||||||
|
state: absent
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
commands:
|
||||||
|
description: The list of configuration mode commands to send to the device
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample:
|
||||||
|
- vlan database
|
||||||
|
- vlan 100
|
||||||
|
- vlan name 100 "test vlan"
|
||||||
|
- exit
|
||||||
|
- interface 0/1
|
||||||
|
- vlan pvid 50
|
||||||
|
- vlan participation include 50,100
|
||||||
|
- vlan tagging 100
|
||||||
|
- vlan participation exclude 200
|
||||||
|
- no vlan tagging 200
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.network.edgeswitch.edgeswitch import load_config, run_commands
|
||||||
|
from ansible.module_utils.network.edgeswitch.edgeswitch import build_aggregate_spec, map_params_to_obj
|
||||||
|
from ansible.module_utils.network.edgeswitch.edgeswitch_interface import InterfaceConfiguration, merge_interfaces
|
||||||
|
|
||||||
|
|
||||||
|
def search_obj_in_list(vlan_id, lst):
|
||||||
|
for o in lst:
|
||||||
|
if o['vlan_id'] == vlan_id:
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def map_vlans_to_commands(want, have, module):
|
||||||
|
commands = []
|
||||||
|
vlans_added = []
|
||||||
|
vlans_removed = []
|
||||||
|
vlans_names = []
|
||||||
|
|
||||||
|
for w in want:
|
||||||
|
vlan_id = w['vlan_id']
|
||||||
|
name = w['name']
|
||||||
|
state = w['state']
|
||||||
|
|
||||||
|
obj_in_have = search_obj_in_list(vlan_id, have)
|
||||||
|
|
||||||
|
if state == 'absent':
|
||||||
|
if obj_in_have:
|
||||||
|
vlans_removed.append(vlan_id)
|
||||||
|
|
||||||
|
elif state == 'present':
|
||||||
|
if not obj_in_have:
|
||||||
|
vlans_added.append(vlan_id)
|
||||||
|
if name:
|
||||||
|
vlans_names.append('vlan name {0} "{1}"'.format(vlan_id, name))
|
||||||
|
else:
|
||||||
|
if name:
|
||||||
|
if name != obj_in_have['name']:
|
||||||
|
vlans_names.append('vlan name {0} "{1}"'.format(vlan_id, name))
|
||||||
|
|
||||||
|
if module.params['purge']:
|
||||||
|
for h in have:
|
||||||
|
obj_in_want = search_obj_in_list(h['vlan_id'], want)
|
||||||
|
# you can't delete vlan 1 on Edgeswitch
|
||||||
|
if not obj_in_want and h['vlan_id'] != '1':
|
||||||
|
vlans_removed.append(h['vlan_id'])
|
||||||
|
|
||||||
|
if vlans_removed:
|
||||||
|
commands.append('no vlan {0}'.format(','.join(vlans_removed)))
|
||||||
|
|
||||||
|
if vlans_added:
|
||||||
|
commands.append('vlan {0}'.format(','.join(vlans_added)))
|
||||||
|
|
||||||
|
if vlans_names:
|
||||||
|
commands.extend(vlans_names)
|
||||||
|
|
||||||
|
if commands:
|
||||||
|
commands.insert(0, 'vlan database')
|
||||||
|
commands.append('exit')
|
||||||
|
|
||||||
|
return commands
|
||||||
|
|
||||||
|
|
||||||
|
class VlanInterfaceConfiguration(InterfaceConfiguration):
|
||||||
|
""" class holding vlan definitions for a given interface
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
InterfaceConfiguration.__init__(self)
|
||||||
|
self.tagged = []
|
||||||
|
self.untagged = []
|
||||||
|
self.excluded = []
|
||||||
|
|
||||||
|
def set_vlan(self, vlan_id, type):
|
||||||
|
try:
|
||||||
|
self.tagged.remove(vlan_id)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.untagged.remove(vlan_id)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.excluded.remove(vlan_id)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
f = getattr(self, type)
|
||||||
|
f.append(vlan_id)
|
||||||
|
|
||||||
|
def gen_commands(self, port, module):
|
||||||
|
""" to reduce commands generated by this module
|
||||||
|
we group vlans changes to have a max of 5 vlan commands by interface
|
||||||
|
"""
|
||||||
|
exclude = []
|
||||||
|
include = []
|
||||||
|
tag = []
|
||||||
|
untag = []
|
||||||
|
pvid = []
|
||||||
|
|
||||||
|
for vlan_id in self.excluded:
|
||||||
|
if vlan_id not in port['forbidden_vlans']:
|
||||||
|
exclude.append(vlan_id)
|
||||||
|
|
||||||
|
if vlan_id in port['tagged_vlans']:
|
||||||
|
untag.append(vlan_id)
|
||||||
|
|
||||||
|
for vlan_id in self.untagged:
|
||||||
|
if vlan_id in port['forbidden_vlans'] or vlan_id not in port['untagged_vlans'] and vlan_id not in port['tagged_vlans']:
|
||||||
|
include.append(vlan_id)
|
||||||
|
|
||||||
|
if vlan_id in port['tagged_vlans']:
|
||||||
|
untag.append(vlan_id)
|
||||||
|
|
||||||
|
if vlan_id != port['pvid_mode']:
|
||||||
|
pvid.append(vlan_id)
|
||||||
|
|
||||||
|
for vlan_id in self.tagged:
|
||||||
|
if vlan_id not in port['tagged_vlans']:
|
||||||
|
tag.append(vlan_id)
|
||||||
|
include.append(vlan_id)
|
||||||
|
|
||||||
|
if include:
|
||||||
|
self.commands.append('vlan participation include {0}'.format(','.join(include)))
|
||||||
|
|
||||||
|
if pvid:
|
||||||
|
if len(pvid) > 1:
|
||||||
|
module.fail_json(msg='{0} can\'t have more than one untagged vlan')
|
||||||
|
return
|
||||||
|
self.commands.append('vlan pvid {0}'.format(pvid[0]))
|
||||||
|
|
||||||
|
if untag:
|
||||||
|
self.commands.append('no vlan tagging {0}'.format(','.join(untag)))
|
||||||
|
|
||||||
|
if tag:
|
||||||
|
self.commands.append('vlan tagging {0}'.format(','.join(tag)))
|
||||||
|
|
||||||
|
if exclude:
|
||||||
|
self.commands.append('vlan participation exclude {0}'.format(','.join(exclude)))
|
||||||
|
|
||||||
|
|
||||||
|
def set_interfaces_vlan(interfaces_param, interfaces, vlan_id, type):
|
||||||
|
""" set vlan_id type for each interface in interfaces_param on interfaces
|
||||||
|
unrange interfaces_param if needed
|
||||||
|
"""
|
||||||
|
if interfaces_param:
|
||||||
|
for i in interfaces_param:
|
||||||
|
match = re.search(r'(\d+)\/(\d+)-(\d+)\/(\d+)', i)
|
||||||
|
if match:
|
||||||
|
group = match.group(1)
|
||||||
|
start = int(match.group(2))
|
||||||
|
end = int(match.group(4))
|
||||||
|
for x in range(start, end + 1):
|
||||||
|
key = '{0}/{1}'.format(group, x)
|
||||||
|
interfaces[key].set_vlan(vlan_id, type)
|
||||||
|
else:
|
||||||
|
interfaces[i].set_vlan(vlan_id, type)
|
||||||
|
|
||||||
|
|
||||||
|
def map_interfaces_to_commands(want, ports, module):
|
||||||
|
commands = list()
|
||||||
|
|
||||||
|
# generate a configuration for each interface
|
||||||
|
interfaces = {}
|
||||||
|
for key, value in ports.items():
|
||||||
|
interfaces[key] = VlanInterfaceConfiguration()
|
||||||
|
|
||||||
|
for w in want:
|
||||||
|
state = w['state']
|
||||||
|
if state != 'present':
|
||||||
|
continue
|
||||||
|
|
||||||
|
auto_tag = w['auto_tag']
|
||||||
|
auto_untag = w['auto_untag']
|
||||||
|
auto_exclude = w['auto_exclude']
|
||||||
|
vlan_id = w['vlan_id']
|
||||||
|
tagged_interfaces = w['tagged_interfaces']
|
||||||
|
untagged_interfaces = w['untagged_interfaces']
|
||||||
|
excluded_interfaces = w['excluded_interfaces']
|
||||||
|
|
||||||
|
# set the default type, if any
|
||||||
|
for key, value in ports.items():
|
||||||
|
if auto_tag:
|
||||||
|
interfaces[key].tagged.append(vlan_id)
|
||||||
|
elif auto_exclude:
|
||||||
|
interfaces[key].excluded.append(vlan_id)
|
||||||
|
elif auto_untag:
|
||||||
|
interfaces[key].untagged.append(vlan_id)
|
||||||
|
|
||||||
|
# set explicit definitions
|
||||||
|
set_interfaces_vlan(tagged_interfaces, interfaces, vlan_id, 'tagged')
|
||||||
|
set_interfaces_vlan(untagged_interfaces, interfaces, vlan_id, 'untagged')
|
||||||
|
set_interfaces_vlan(excluded_interfaces, interfaces, vlan_id, 'excluded')
|
||||||
|
|
||||||
|
# generate commands for each interface
|
||||||
|
for i, interface in interfaces.items():
|
||||||
|
port = ports[i]
|
||||||
|
interface.gen_commands(port, module)
|
||||||
|
|
||||||
|
# reduce them using range syntax when possible
|
||||||
|
interfaces = merge_interfaces(interfaces)
|
||||||
|
|
||||||
|
# final output
|
||||||
|
for i, interface in interfaces.items():
|
||||||
|
if len(interface.commands) > 0:
|
||||||
|
commands.append('interface {0}'.format(i))
|
||||||
|
commands.extend(interface.commands)
|
||||||
|
|
||||||
|
return commands
|
||||||
|
|
||||||
|
|
||||||
|
def parse_vlan_brief(vlan_out):
|
||||||
|
have = []
|
||||||
|
for line in vlan_out.split('\n'):
|
||||||
|
obj = re.match(r'(?P<vlan_id>\d+)\s+(?P<name>[^\s]+)\s+', line)
|
||||||
|
if obj:
|
||||||
|
have.append(obj.groupdict())
|
||||||
|
return have
|
||||||
|
|
||||||
|
|
||||||
|
def unrange(vlans):
|
||||||
|
res = []
|
||||||
|
for vlan in vlans:
|
||||||
|
match = re.match(r'(\d+)-(\d+)', vlan)
|
||||||
|
if match:
|
||||||
|
start = int(match.group(1))
|
||||||
|
end = int(match.group(2))
|
||||||
|
for vlan_id in range(start, end + 1):
|
||||||
|
res.append(str(vlan_id))
|
||||||
|
else:
|
||||||
|
res.append(vlan)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def parse_interfaces_switchport(cmd_out):
|
||||||
|
ports = dict()
|
||||||
|
objs = re.findall(
|
||||||
|
r'Port: (\d+\/\d+)\n'
|
||||||
|
'VLAN Membership Mode:(.*)\n'
|
||||||
|
'Access Mode VLAN:(.*)\n'
|
||||||
|
'General Mode PVID:(.*)\n'
|
||||||
|
'General Mode Ingress Filtering:(.*)\n'
|
||||||
|
'General Mode Acceptable Frame Type:(.*)\n'
|
||||||
|
'General Mode Dynamically Added VLANs:(.*)\n'
|
||||||
|
'General Mode Untagged VLANs:(.*)\n'
|
||||||
|
'General Mode Tagged VLANs:(.*)\n'
|
||||||
|
'General Mode Forbidden VLANs:(.*)\n', cmd_out)
|
||||||
|
for o in objs:
|
||||||
|
port = {
|
||||||
|
'interface': o[0],
|
||||||
|
'pvid_mode': o[3].replace("(default)", "").strip(),
|
||||||
|
'untagged_vlans': unrange(o[7].strip().split(',')),
|
||||||
|
'tagged_vlans': unrange(o[8].strip().split(',')),
|
||||||
|
'forbidden_vlans': unrange(o[9].strip().split(','))
|
||||||
|
}
|
||||||
|
ports[port['interface']] = port
|
||||||
|
return ports
|
||||||
|
|
||||||
|
|
||||||
|
def map_ports_to_obj(module):
|
||||||
|
return parse_interfaces_switchport(run_commands(module, ['show interfaces switchport'])[0])
|
||||||
|
|
||||||
|
|
||||||
|
def map_config_to_obj(module):
|
||||||
|
return parse_vlan_brief(run_commands(module, ['show vlan brief'])[0])
|
||||||
|
|
||||||
|
|
||||||
|
def check_params(module, want):
|
||||||
|
""" Deeper checks on parameters
|
||||||
|
"""
|
||||||
|
def check_parmams_interface(interfaces):
|
||||||
|
if interfaces:
|
||||||
|
for i in interfaces:
|
||||||
|
match = re.search(r'(\d+)\/(\d+)-(\d+)\/(\d+)', i)
|
||||||
|
if match:
|
||||||
|
if match.group(1) != match.group(3):
|
||||||
|
module.fail_json(msg="interface range must be within same group: " + i)
|
||||||
|
else:
|
||||||
|
match = re.search(r'(\d+)\/(\d+)', i)
|
||||||
|
if not match:
|
||||||
|
module.fail_json(msg="wrong interface format: " + i)
|
||||||
|
|
||||||
|
for w in want:
|
||||||
|
auto_tag = w['auto_tag']
|
||||||
|
auto_untag = w['auto_untag']
|
||||||
|
auto_exclude = w['auto_exclude']
|
||||||
|
|
||||||
|
c = 0
|
||||||
|
if auto_tag:
|
||||||
|
c = c + 1
|
||||||
|
|
||||||
|
if auto_untag:
|
||||||
|
c = c + 1
|
||||||
|
|
||||||
|
if auto_exclude:
|
||||||
|
c = c + 1
|
||||||
|
|
||||||
|
if c > 1:
|
||||||
|
module.fail_json(msg="parameters are mutually exclusive: auto_tag, auto_untag, auto_exclude")
|
||||||
|
return
|
||||||
|
|
||||||
|
check_parmams_interface(w['tagged_interfaces'])
|
||||||
|
check_parmams_interface(w['untagged_interfaces'])
|
||||||
|
check_parmams_interface(w['excluded_interfaces'])
|
||||||
|
w['vlan_id'] = str(w['vlan_id'])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
""" main entry point for module execution
|
||||||
|
"""
|
||||||
|
element_spec = dict(
|
||||||
|
vlan_id=dict(type='int'),
|
||||||
|
name=dict(),
|
||||||
|
tagged_interfaces=dict(type='list'),
|
||||||
|
untagged_interfaces=dict(type='list'),
|
||||||
|
excluded_interfaces=dict(type='list'),
|
||||||
|
auto_tag=dict(type='bool'),
|
||||||
|
auto_exclude=dict(type='bool'),
|
||||||
|
auto_untag=dict(type='bool'),
|
||||||
|
state=dict(default='present',
|
||||||
|
choices=['present', 'absent'])
|
||||||
|
)
|
||||||
|
|
||||||
|
argument_spec = build_aggregate_spec(
|
||||||
|
element_spec,
|
||||||
|
['vlan_id'],
|
||||||
|
dict(purge=dict(default=False, type='bool'))
|
||||||
|
)
|
||||||
|
|
||||||
|
required_one_of = [['vlan_id', 'aggregate']]
|
||||||
|
mutually_exclusive = [
|
||||||
|
['vlan_id', 'aggregate'],
|
||||||
|
['auto_tag', 'auto_untag', 'auto_exclude']]
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
required_one_of=required_one_of,
|
||||||
|
mutually_exclusive=mutually_exclusive,
|
||||||
|
supports_check_mode=True)
|
||||||
|
result = {'changed': False}
|
||||||
|
|
||||||
|
want = map_params_to_obj(module)
|
||||||
|
have = map_config_to_obj(module)
|
||||||
|
|
||||||
|
check_params(module, want)
|
||||||
|
|
||||||
|
# vlans are not created/deleted in configure mode
|
||||||
|
commands = map_vlans_to_commands(want, have, module)
|
||||||
|
result['commands'] = commands
|
||||||
|
|
||||||
|
if commands:
|
||||||
|
if not module.check_mode:
|
||||||
|
run_commands(module, commands, check_rc=False)
|
||||||
|
result['changed'] = True
|
||||||
|
|
||||||
|
ports = map_ports_to_obj(module)
|
||||||
|
|
||||||
|
# interfaces vlan are set in configure mode
|
||||||
|
commands = map_interfaces_to_commands(want, ports, module)
|
||||||
|
result['commands'].extend(commands)
|
||||||
|
|
||||||
|
if commands:
|
||||||
|
if not module.check_mode:
|
||||||
|
load_config(module, commands)
|
||||||
|
result['changed'] = True
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,239 @@
|
||||||
|
Port: 0/1
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/2
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/3
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs:
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs: 1
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/4
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs:
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs: 1
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/5
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 100
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 100
|
||||||
|
General Mode Tagged VLANs:
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/6
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs:
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/7
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs:
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/8
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs:
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/9
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 0/10
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 3/1
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 3/2
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 3/3
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 3/4
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 3/5
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
||||||
|
|
||||||
|
Port: 3/6
|
||||||
|
VLAN Membership Mode: General
|
||||||
|
Access Mode VLAN: 1 (default)
|
||||||
|
General Mode PVID: 1 (default)
|
||||||
|
General Mode Ingress Filtering: Disabled
|
||||||
|
General Mode Acceptable Frame Type: Admit all
|
||||||
|
General Mode Dynamically Added VLANs:
|
||||||
|
General Mode Untagged VLANs: 1
|
||||||
|
General Mode Tagged VLANs: 100
|
||||||
|
General Mode Forbidden VLANs:
|
||||||
|
Trunking Mode Native VLAN: 1 (default)
|
||||||
|
Trunking Mode Native VLAN tagging: Disable
|
||||||
|
Trunking Mode VLANs Enabled: All
|
||||||
|
Protected Port: False
|
|
@ -0,0 +1,4 @@
|
||||||
|
VLAN ID VLAN Name VLAN Type
|
||||||
|
------- -------------------------------- -------------------
|
||||||
|
1 default Default
|
||||||
|
100 voice Static
|
154
test/units/modules/network/edgeswitch/test_edgeswitch_vlan.py
Normal file
154
test/units/modules/network/edgeswitch/test_edgeswitch_vlan.py
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
# (c) 2018 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from units.compat.mock import patch
|
||||||
|
from ansible.modules.network.edgeswitch import edgeswitch_vlan
|
||||||
|
from ansible.modules.network.edgeswitch.edgeswitch_vlan import parse_vlan_brief, parse_interfaces_switchport
|
||||||
|
from units.modules.utils import set_module_args
|
||||||
|
from .edgeswitch_module import TestEdgeswitchModule, load_fixture
|
||||||
|
|
||||||
|
|
||||||
|
class TestEdgeswitchVlanModule(TestEdgeswitchModule):
|
||||||
|
|
||||||
|
module = edgeswitch_vlan
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestEdgeswitchVlanModule, self).setUp()
|
||||||
|
|
||||||
|
self.mock_run_commands = patch('ansible.modules.network.edgeswitch.edgeswitch_vlan.run_commands')
|
||||||
|
self.run_commands = self.mock_run_commands.start()
|
||||||
|
|
||||||
|
self.mock_load_config = patch('ansible.modules.network.edgeswitch.edgeswitch_vlan.load_config')
|
||||||
|
self.load_config = self.mock_load_config.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestEdgeswitchVlanModule, self).tearDown()
|
||||||
|
self.mock_run_commands.stop()
|
||||||
|
self.mock_load_config.stop()
|
||||||
|
|
||||||
|
def load_fixtures(self, commands=None):
|
||||||
|
def load_from_file(*args, **kwargs):
|
||||||
|
module, commands = args
|
||||||
|
output = list()
|
||||||
|
|
||||||
|
for command in commands:
|
||||||
|
if command.startswith('vlan ') or command == 'exit':
|
||||||
|
output.append('')
|
||||||
|
else:
|
||||||
|
filename = str(command).split(' | ')[0].replace(' ', '_')
|
||||||
|
output.append(load_fixture('edgeswitch_vlan_%s' % filename))
|
||||||
|
return output
|
||||||
|
|
||||||
|
self.run_commands.side_effect = load_from_file
|
||||||
|
self.load_config.return_value = {}
|
||||||
|
|
||||||
|
def test_edgeswitch_vlan_create(self):
|
||||||
|
set_module_args({'vlan_id': '200', 'name': 'video', 'state': 'present'})
|
||||||
|
result = self.execute_module(changed=True)
|
||||||
|
expected_commands = [
|
||||||
|
'vlan database',
|
||||||
|
'vlan 200',
|
||||||
|
'vlan name 200 \"video\"',
|
||||||
|
'exit'
|
||||||
|
]
|
||||||
|
self.assertEqual(result['commands'], expected_commands)
|
||||||
|
|
||||||
|
def test_edgeswitch_vlan_id_startwith_100(self):
|
||||||
|
set_module_args({'vlan_id': '100', 'name': 'voice', 'state': 'present'})
|
||||||
|
result = self.execute_module(changed=False)
|
||||||
|
expected_commands = []
|
||||||
|
self.assertEqual(result['commands'], expected_commands)
|
||||||
|
|
||||||
|
def test_edgeswitch_vlan_rename(self):
|
||||||
|
set_module_args({'vlan_id': '100', 'name': 'video', 'state': 'present'})
|
||||||
|
result = self.execute_module(changed=True)
|
||||||
|
expected_commands = [
|
||||||
|
'vlan database',
|
||||||
|
'vlan name 100 \"video\"',
|
||||||
|
'exit'
|
||||||
|
]
|
||||||
|
self.assertEqual(result['commands'], expected_commands)
|
||||||
|
|
||||||
|
def test_edgeswitch_vlan_with_interfaces_range(self):
|
||||||
|
set_module_args({'vlan_id': '100', 'name': 'voice', 'state': 'present', 'tagged_interfaces': ['0/6-0/8']})
|
||||||
|
result = self.execute_module(changed=True)
|
||||||
|
expected_commands = [
|
||||||
|
'interface 0/6-0/8',
|
||||||
|
'vlan participation include 100',
|
||||||
|
'vlan tagging 100',
|
||||||
|
]
|
||||||
|
self.assertEqual(result['commands'], expected_commands)
|
||||||
|
|
||||||
|
def test_edgeswitch_vlan_with_interfaces_and_newvlan(self):
|
||||||
|
set_module_args({'vlan_id': '3', 'name': 'vlan3', 'state': 'present', 'untagged_interfaces': ['0/8', '0/7']})
|
||||||
|
result = self.execute_module(changed=True)
|
||||||
|
expected_commands = [
|
||||||
|
'vlan database',
|
||||||
|
'vlan 3',
|
||||||
|
'vlan name 3 \"vlan3\"',
|
||||||
|
'exit',
|
||||||
|
'interface 0/7-0/8',
|
||||||
|
'vlan participation include 3',
|
||||||
|
'vlan pvid 3',
|
||||||
|
]
|
||||||
|
self.assertEqual(result['commands'], expected_commands)
|
||||||
|
|
||||||
|
def test_parse_interfaces_switchport(self):
|
||||||
|
result = parse_interfaces_switchport(load_fixture('edgeswitch_vlan_show_interfaces_switchport'))
|
||||||
|
i1 = {
|
||||||
|
'interface': '0/1',
|
||||||
|
'pvid_mode': '1',
|
||||||
|
'untagged_vlans': ['1'],
|
||||||
|
'tagged_vlans': ['100'],
|
||||||
|
'forbidden_vlans': [''],
|
||||||
|
}
|
||||||
|
i3 = {
|
||||||
|
'interface': '0/3',
|
||||||
|
'pvid_mode': '1',
|
||||||
|
'untagged_vlans': [''],
|
||||||
|
'tagged_vlans': ['100'],
|
||||||
|
'forbidden_vlans': ['1'],
|
||||||
|
}
|
||||||
|
i5 = {
|
||||||
|
'interface': '0/5',
|
||||||
|
'pvid_mode': '100',
|
||||||
|
'untagged_vlans': ['100'],
|
||||||
|
'tagged_vlans': [''],
|
||||||
|
'forbidden_vlans': [''],
|
||||||
|
}
|
||||||
|
self.assertEqual(result['0/1'], i1)
|
||||||
|
self.assertEqual(result['0/3'], i3)
|
||||||
|
self.assertEqual(result['0/5'], i5)
|
||||||
|
|
||||||
|
def test_parse_vlan_brief(self):
|
||||||
|
result = parse_vlan_brief(load_fixture('edgeswitch_vlan_show_vlan_brief'))
|
||||||
|
obj = [
|
||||||
|
{
|
||||||
|
'vlan_id': '1',
|
||||||
|
'name': 'default'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'vlan_id': '100',
|
||||||
|
'name': 'voice'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.assertEqual(result, obj)
|
Loading…
Reference in a new issue