Resource module for iosxr_l2_interfaces (#59964)

* iosxr_l2 resource
pull/61303/head
Sumit Jaiswal 5 years ago committed by GitHub
parent dbd3ace53e
commit b53bc94cc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -29,7 +29,9 @@ class FactsArgs(object): # pylint: disable=R0903
'lldp_interfaces',
'!lldp_interfaces',
'interfaces',
'!interfaces'
'!interfaces',
'l2_interfaces',
'!l2_interfaces',
]
argument_spec = {

@ -0,0 +1,57 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The arg spec for the iosxr_l2_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class L2_InterfacesArgs(object):
def __init__(self, **kwargs):
pass
argument_spec = {'config': {'elements': 'dict',
'options': {'name': {'type': 'str', 'required': True},
'native_vlan': {'type': 'int'},
'l2transport': {'type': 'bool'},
'l2protocol': {'element': 'dict',
'type': 'list',
'options': {'cdp': {'type': 'str',
'choices': ['drop', 'forward', 'tunnel']},
'pvst': {'type': 'str',
'choices': ['drop', 'forward', 'tunnel']},
'stp': {'type': 'str',
'choices': ['drop', 'forward', 'tunnel']},
'vtp': {'type': 'str',
'choices': ['drop', 'forward', 'tunnel']},
}},
'q_vlan': {'type': 'list'},
'propagate': {'type': 'bool'}},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

@ -16,7 +16,7 @@ __metaclass__ = type
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.iosxr.facts.facts import Facts
from ansible.module_utils.network.iosxr.utils.utils import get_interface_type, dict_diff
from ansible.module_utils.network.iosxr.utils.utils import get_interface_type, dict_to_set
from ansible.module_utils.network.iosxr.utils.utils import remove_command_from_config_list, add_command_to_config_list
from ansible.module_utils.network.iosxr.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
@ -218,8 +218,8 @@ class Interfaces(ConfigBase):
interface = 'interface ' + want['name']
# Get the diff b/w want and have
want_dict = dict_diff(want)
have_dict = dict_diff(have)
want_dict = dict_to_set(want)
have_dict = dict_to_set(have)
diff = want_dict - have_dict
if diff:

@ -0,0 +1,304 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The iosxr_l2_interfaces class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.iosxr.facts.facts import Facts
from ansible.module_utils.network.iosxr.utils.utils import normalize_interface, dict_to_set
from ansible.module_utils.network.iosxr.utils.utils import remove_command_from_config_list, add_command_to_config_list
from ansible.module_utils.network.iosxr.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
class L2_Interfaces(ConfigBase):
"""
The iosxr_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'l2_interfaces',
]
def get_l2_interfaces_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
l2_interfaces_facts = facts['ansible_network_resources'].get('l2_interfaces')
if not l2_interfaces_facts:
return []
return l2_interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
existing_l2_interfaces_facts = self.get_l2_interfaces_facts()
commands.extend(self.set_config(existing_l2_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_l2_interfaces_facts = self.get_l2_interfaces_facts()
result['before'] = existing_l2_interfaces_facts
if result['changed']:
result['after'] = changed_l2_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_l2_interfaces_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_l2_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
""" Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
state = self._module.params['state']
if state == 'overridden':
commands = self._state_overridden(want, have, self._module)
elif state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged':
commands = self._state_merged(want, have, self._module)
elif state == 'replaced':
commands = self._state_replaced(want, have, self._module)
return commands
def _state_replaced(self, want, have, module):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for interface in want:
interface['name'] = normalize_interface(interface['name'])
for each in have:
if each['name'] == interface['name']:
break
else:
commands.extend(self._set_config(interface, {}, module))
continue
have_dict = filter_dict_having_none_value(interface, each)
new_want = dict()
commands.extend(self._clear_config(new_want, have_dict))
commands.extend(self._set_config(interface, each, module))
# Remove the duplicate interface call
commands = remove_duplicate_interface(commands)
return commands
def _state_overridden(self, want, have, module):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
not_in_have = set()
in_have = set()
for each in have:
for interface in want:
interface['name'] = normalize_interface(interface['name'])
if each['name'] == interface['name']:
in_have.add(interface['name'])
break
elif interface['name'] != each['name']:
not_in_have.add(interface['name'])
else:
# We didn't find a matching desired state, which means we can
# pretend we recieved an empty desired state.
interface = dict(name=each['name'])
commands.extend(self._clear_config(interface, each))
continue
have_dict = filter_dict_having_none_value(interface, each)
new_want = dict()
commands.extend(self._clear_config(new_want, have_dict))
commands.extend(self._set_config(interface, each, module))
# Add the want interface that's not already configured in have interface
for each in (not_in_have - in_have):
for every in want:
interface = 'interface {0}'.format(every['name'])
if each and interface not in commands:
commands.extend(self._set_config(every, {}, module))
# Remove the duplicate interface call
commands = remove_duplicate_interface(commands)
return commands
def _state_merged(self, want, have, module):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
for interface in want:
interface['name'] = normalize_interface(interface['name'])
for each in have:
if each['name'] == interface['name']:
break
elif interface['name'] in each['name']:
break
else:
commands.extend(self._set_config(interface, {}, module))
continue
commands.extend(self._set_config(interface, each, module))
return commands
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
for interface in want:
interface['name'] = normalize_interface(interface['name'])
for each in have:
if each['name'] == interface['name']:
break
elif interface['name'] in each['name']:
break
else:
continue
interface = dict(name=interface['name'])
commands.extend(self._clear_config(interface, each))
else:
for each in have:
want = dict()
commands.extend(self._clear_config(want, each))
return commands
def _set_config(self, want, have, module):
# Set the interface config based on the want and have config
commands = []
interface = 'interface ' + want['name']
l2_protocol_bool = False
# Get the diff b/w want and have
want_dict = dict_to_set(want)
have_dict = dict_to_set(have)
diff = want_dict - have_dict
if diff:
# For merging with already configured l2protocol
if have.get('l2protocol') and len(have.get('l2protocol')) > 1:
l2_protocol_diff = []
for each in want.get('l2protocol'):
for every in have.get('l2protocol'):
if every == each:
break
if each not in have.get('l2protocol'):
l2_protocol_diff.append(each)
l2_protocol_bool = True
l2protocol = tuple(l2_protocol_diff)
else:
l2protocol = {}
diff = dict(diff)
wants_native = diff.get('native_vlan')
l2transport = diff.get('l2transport')
q_vlan = diff.get('q_vlan')
propagate = diff.get('propagate')
if l2_protocol_bool is False:
l2protocol = diff.get('l2protocol')
if wants_native:
cmd = 'dot1q native vlan {0}'.format(wants_native)
add_command_to_config_list(interface, cmd, commands)
if l2transport or l2protocol:
for each in l2protocol:
if isinstance(each, dict):
cmd = 'l2transport l2protocol {0} {1}'.format(each.keys()[0], each.values()[0])
else:
cmd = 'l2transport l2protocol {0} {1}'.format(each[0], each[1])
add_command_to_config_list(interface, cmd, commands)
if propagate and not have.get('propagate'):
cmd = 'l2transport propagate remote-status'
add_command_to_config_list(interface, cmd, commands)
elif want.get('l2transport') is False and (want.get('l2protocol') or want.get('propagate')):
module.fail_json(msg='L2transport L2protocol or Propagate can only be configured when '
'L2transport set to True!')
if q_vlan and '.' in interface:
q_vlans = (" ".join(map(str, want.get('q_vlan'))))
if q_vlans != have.get('q_vlan'):
cmd = 'dot1q vlan {0}'.format(q_vlans)
add_command_to_config_list(interface, cmd, commands)
return commands
def _clear_config(self, want, have):
# Delete the interface config based on the want and have config
commands = []
if want.get('name'):
interface = 'interface ' + want['name']
else:
interface = 'interface ' + have['name']
if have.get('native_vlan'):
remove_command_from_config_list(interface, 'dot1q native vlan', commands)
if have.get('q_vlan'):
remove_command_from_config_list(interface, 'encapsulation dot1q', commands)
if have.get('l2protocol') and (want.get('l2protocol') is None or want.get('propagate') is None):
if 'no l2transport' not in commands:
remove_command_from_config_list(interface, 'l2transport', commands)
elif have.get('l2transport') and have.get('l2transport') != want.get('l2transport'):
if 'no l2transport' not in commands:
remove_command_from_config_list(interface, 'l2transport', commands)
return commands

@ -21,6 +21,9 @@ from ansible.module_utils.network.iosxr.facts.lldp_global.lldp_global import Lld
from ansible.module_utils.network.iosxr.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible.module_utils.network.iosxr.facts.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.iosxr.facts.legacy.base import Default, Hardware, Interfaces, Config
from ansible.module_utils.network.iosxr.facts.l2_interfaces.l2_interfaces import L2_InterfacesFacts
from ansible.module_utils.network.iosxr.facts.legacy.\
base import Default, Hardware, Interfaces, Config
FACT_LEGACY_SUBSETS = dict(
@ -35,6 +38,7 @@ FACT_RESOURCE_SUBSETS = dict(
lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts,
interfaces=InterfacesFacts,
l2_interfaces=L2_InterfacesFacts,
)

@ -0,0 +1,125 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The iosxr l2_interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.iosxr.utils.utils import get_interface_type
from ansible.module_utils.network.iosxr.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs
class L2_InterfacesFacts(object):
""" The iosxr l2_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = L2_InterfacesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for l2_interfaces
:param module: the module instance
:param connection: the device connection
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
objs = []
if not data:
data = connection.get('show running-config interface')
# operate on a collection of resource x
config = data.split('interface ')
for conf in config:
if conf:
obj = self.render_config(self.generated_spec, conf)
if obj:
objs.append(obj)
facts = {}
if objs:
facts['l2_interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['l2_interfaces'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def render_config(self, spec, conf):
"""
Render config as dictionary structure and delete keys from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
config = deepcopy(spec)
match = re.search(r'^(\S+)', conf)
intf = match.group(1)
if match.group(1).lower() == "preconfigure":
match = re.search(r'^(\S+) (.*)', conf)
if match:
intf = match.group(2)
if get_interface_type(intf) == 'unknown':
return {}
if intf.lower().startswith('gi'):
config['name'] = intf
# populate the facts from the configuration
native_vlan = re.search(r"dot1q native vlan (\d+)", conf)
if native_vlan:
config["native_vlan"] = int(native_vlan.group(1))
dot1q = utils.parse_conf_arg(conf, 'encapsulation dot1q')
config['q_vlan'] = []
if dot1q:
config['q_vlan'].append(int(dot1q.split(' ')[0]))
if len(dot1q.split(' ')) > 1:
config['q_vlan'].append(int(dot1q.split(' ')[2]))
if utils.parse_conf_cmd_arg(conf, 'l2transport', True):
config['l2transport'] = True
if utils.parse_conf_arg(conf, 'propagate'):
config['propagate'] = True
config['l2protocol'] = []
cdp = utils.parse_conf_arg(conf, 'l2protocol cdp')
pvst = utils.parse_conf_arg(conf, 'l2protocol pvst')
stp = utils.parse_conf_arg(conf, 'l2protocol stp')
vtp = utils.parse_conf_arg(conf, 'l2protocol vtp')
if cdp:
config['l2protocol'].append({'cdp': cdp})
if pvst:
config['l2protocol'].append({'pvst': pvst})
if stp:
config['l2protocol'].append({'stp': stp})
if vtp:
config['l2protocol'].append({'vtp': vtp})
return utils.remove_empties(config)

@ -26,11 +26,29 @@ def add_command_to_config_list(interface, cmd, commands):
commands.append(cmd)
def dict_diff(sample_dict):
def dict_to_set(sample_dict):
# Generate a set with passed dictionary for comparison
test_dict = {}
for k, v in iteritems(sample_dict):
if v is not None:
if isinstance(v, list):
if isinstance(v[0], dict):
li = []
for each in v:
for key, value in iteritems(each):
if isinstance(value, list):
each[key] = tuple(value)
li.extend(tuple(each.items()))
v = tuple(li)
else:
v = tuple(v)
elif isinstance(v, dict):
li = []
for key, value in iteritems(v):
if isinstance(value, list):
v[key] = tuple(value)
li.extend(tuple(v.items()))
v = tuple(li)
test_dict.update({k: v})
return_set = set(tuple(test_dict.items()))
return return_set
@ -41,6 +59,9 @@ def filter_dict_having_none_value(want, have):
test_dict = dict()
test_dict['name'] = want.get('name')
for k, v in iteritems(want):
if k == 'l2protocol':
if want[k] != have.get('l2protocol') and have.get('l2protocol'):
test_dict.update({k: v})
if v is None:
val = have.get(k)
test_dict.update({k: val})

@ -54,7 +54,8 @@ options:
can also be used with an initial C(M(!)) to specify that a
specific subset should not be collected.
required: false
choices: ['all', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces', 'interfaces', '!interfaces']
choices: ['all', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces', 'lldp_global', '!lldp_global',
'lldp_interfaces', '!lldp_interfaces', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces']
version_added: "2.9"
"""
@ -99,6 +100,7 @@ EXAMPLES = """
- "!min"
gather_network_resources:
- interfaces
- l2_interfaces
"""
RETURN = """

@ -0,0 +1,424 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The module file for iosxr_l2_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
GENERATOR_VERSION = '1.0'
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: iosxr_l2_interfaces
version_added: 2.9
short_description: Manage Layer-2 interface on Cisco IOS-XR devices
description: This module manages the Layer-2 interface attributes on Cisco IOS-XR devices.
author: Sumit Jaiswal (@justjais)
notes:
- Tested against Cisco IOS-XRv Version 6.1.3 on VIRL.
- This module works with connection C(network_cli).
See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
options:
config:
description: A dictionary of Layer-2 interface options
type: list
elements: dict
suboptions:
name:
description:
- Full name of the interface/sub-interface excluding any logical unit number,
e.g. GigabitEthernet0/0/0/1 or GigabitEthernet0/0/0/1.100.
type: str
required: True
native_vlan:
description:
- Configure a native VLAN ID for the trunk
type: int
l2transport:
description:
- Switchport mode access command to configure the interface as a layer 2 access
type: bool
l2protocol:
description:
- Configures Layer 2 protocol tunneling and protocol data unit (PDU) filtering on an interface.
type: list
suboptions:
cdp:
description:
- Cisco Discovery Protocol (CDP) tunneling and data unit parameters.
choices: ['drop','forward', 'tunnel']
type: str
pvst:
description:
- Configures the per-VLAN Spanning Tree Protocol (PVST) tunneling and data unit parameters.
choices: ['drop','forward', 'tunnel']
type: str
stp:
description:
- Spanning Tree Protocol (STP) tunneling and data unit parameters.
choices: ['drop','forward', 'tunnel']
type: str
vtp:
description:
- VLAN Trunk Protocol (VTP) tunneling and data unit parameters.
choices: ['drop','forward', 'tunnel']
type: str
q_vlan:
description:
- 802.1Q VLAN configuration. Note that it can accept either 2 VLAN IDs when configuring Q-in-Q VLAN,
or it will accept 1 VLAN ID and 'any' as input list when configuring Q-in-any vlan as input. Note, that
this option is valid only with respect to Sub-Interface and is not valid when configuring for Interface.
type: list
propagate:
description:
- Propagate Layer 2 transport events. Note that it will work only when the I(l2tranport) option is set
to TRUE
type: bool
state:
choices:
- merged
- replaced
- overridden
- deleted
default: merged
description:
- The state the configuration should be left in
type: str
"""
EXAMPLES = """
---
# Using merged
#
# Before state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# !
- name: Merge provided configuration with device configuration
iosxr_l2_interfaces:
config:
- name: GigabitEthernet0/0/0/3
native_vlan: 20
- name: GigabitEthernet0/0/0/4
native_vlan: 40
l2transport: True
l2protocol:
- stp: tunnel
- name: GigabitEthernet0/0/0/3.900
l2transport: True
q_vlan:
- 20
- 40
state: merged
# After state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# dot1q native vlan 20
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# dot1q native vlan 10
# l2transport
# l2protocol stp tunnel
# !
# !
# interface GigabitEthernet0/0/0/3.900 l2transport
# dot1q vlan 20 40
# !
# Using replaced
#
# Before state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# dot1q native vlan 20
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# dot1q native vlan 10
# l2transport
# l2protocol stp tunnel
# !
# !
# interface GigabitEthernet0/0/0/3.900 l2transport
# dot1q vlan 20 40
# !
- name: Replaces device configuration of listed interfaces with provided configuration
iosxr_l2_interfaces:
config:
- name: GigabitEthernet0/0/0/4
native_vlan: 40
l2transport: True
l2protocol:
- stp: forward
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 20
- any
state: replaced
# After state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# dot1q native vlan 20
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# dot1q native vlan 40
# l2transport
# l2protocol stp forward
# !
# !
# interface GigabitEthernet0/0/0/3.900 l2transport
# dot1q vlan 20 any
# !
# Using overridden
#
# Before state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# dot1q native vlan 20
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# dot1q native vlan 10
# l2transport
# l2protocol stp tunnel
# !
# !
# interface GigabitEthernet0/0/0/3.900 l2transport
# dot1q vlan 20 40
# !
- name: Override device configuration of all interfaces with provided configuration
iosxr_l2_interfaces:
config:
- name: GigabitEthernet0/0/0/4
native_vlan: 40
l2transport: True
l2protocol:
- stp: forward
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 20
- any
state: overridden
# After state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# dot1q native vlan 40
# l2transport
# l2protocol stp forward
# !
# !
# interface GigabitEthernet0/0/0/3.900
# dot1q vlan 20 any
# !
# Using deleted
#
# Before state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# dot1q native vlan 20
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# dot1q native vlan 10
# l2transport
# l2protocol stp tunnel
# !
# !
#
- name: "Delete L2 attributes of given interfaces (Note: This won't delete the interface itself)"
iosxr_l2_interfaces:
config:
- name: GigabitEthernet0/0/0/4
state: deleted
# After state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# dot1q native vlan 20
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# !
# Using Deleted without any config passed
# "(NOTE: This will delete all of configured resource module attributes from each configured interface)"
#
# Before state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# dot1q native vlan 20
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# dot1q native vlan 10
# l2transport
# l2protocol stp tunnel
# !
# !
- name: "Delete L2 attributes of all interfaces (Note: This won't delete the interface itself)"
iosxr_l2_interfaces:
state: deleted
# After state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/3
# description Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# !
# interface GigabitEthernet0/0/0/4
# description Test description
# !
"""
RETURN = """
before:
description: The configuration prior to the model invocation
returned: always
type: list
sample: The configuration returned will alwys be in the same format of the paramters above.
after:
description: The resulting configuration model invocation
returned: when changed
type: list
sample: The configuration returned will alwys be in the same format of the paramters above.
commands:
description: The set of commands pushed to the remote device
returned: always
type: list
sample: ['interface GigabitEthernet0/0/0/2', 'command 2', 'command 3']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs
from ansible.module_utils.network.iosxr.config.l2_interfaces.l2_interfaces import L2_Interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=L2_InterfacesArgs.argument_spec,
supports_check_mode=True)
result = L2_Interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -0,0 +1,3 @@
---
testcase: "[^_].*"
test_items: []

@ -0,0 +1,20 @@
---
- name: Collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
use_regex: true
register: test_cases
delegate_to: localhost
- name: Set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
delegate_to: localhost
- name: Run test case (connection=network_cli)
include: "{{ test_case_to_run }}"
vars:
ansible_connection: network_cli
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -0,0 +1,2 @@
---
- { include: cli.yaml, tags: ['cli'] }

@ -0,0 +1,15 @@
---
- name: Populate Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
interface GigabitEthernet 0/0/0/1
dot1q native vlan 10
l2transport l2protocol cdp forward
l2transport propagate remote-status
interface GigabitEthernet 0/0/0/4
dot1q native vlan 20
l2transport l2protocol vtp tunnel
interface GigabitEthernet 0/0/0/3.900
dot1q vlan 40 60

@ -0,0 +1,16 @@
---
- name: Remove Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
interface GigabitEthernet 0/0/0/1
no dot1q native vlan
no l2transport
no interface GigabitEthernet 0/0/0/2
no interface GigabitEthernet 0/0/0/3
no interface GigabitEthernet 0/0/0/3.900
no interface GigabitEthernet 0/0/0/4
no interface preconfigure GigabitEthernet 0/0/0/3
no interface preconfigure GigabitEthernet 0/0/0/3.900
no interface preconfigure GigabitEthernet 0/0/0/4

@ -0,0 +1,40 @@
---
- debug:
msg: "Start Deleted integration state for ios_interfaces ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Delete L2 interfaces attributes of all configured interfaces
iosxr_l2_interfaces: &deleted
state: deleted
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ deleted['before'] | symmetric_difference(result['before']) | length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ deleted['after'] | symmetric_difference(result['after']) | length == 0 }}"
- name: Delete L2 interfaces attributes of all configured interfaces (IDEMPOTENT)
iosxr_l2_interfaces: *deleted
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,52 @@
---
- debug:
msg: "START Merged ios_interfaces state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Merge provided L2 configuration with device configuration
iosxr_l2_interfaces: &merged
config:
- name: GigabitEthernet0/0/0/1
native_vlan: 10
l2transport: True
l2protocol:
- pvst: tunnel
- cdp: forward
propagate: True
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 20
- 40
- name: GigabitEthernet0/0/0/4
native_vlan: 40
state: merged
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ merged['before'] | symmetric_difference(result['before']) | length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['after']) | length == 0 }}"
- name: Merge provided L2 configuration with device configuration (IDEMPOTENT)
iosxr_l2_interfaces: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,56 @@
---
- debug:
msg: "START Overridden ios_interfaces state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Override device L2 configuration of all interfaces with provided configuration
iosxr_l2_interfaces: &overridden
config:
- name: GigabitEthernet0/0/0/4
native_vlan: 40
l2transport: True
l2protocol:
- stp: forward
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 20
- 40
state: overridden
register: result
- debug:
msg:
- "{{ overridden['commands'] | symmetric_difference(result['commands']) }}"
- "{{ overridden['before'] | symmetric_difference(result['before']) }}"
- "{{ overridden['after'] | symmetric_difference(result['after']) }}"
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ overridden['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ overridden['before'] | symmetric_difference(result['before']) | length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['after']) | length == 0 }}"
- name: Override device L2 configuration of all interfaces with provided configuration (IDEMPOTENT)
iosxr_l2_interfaces: *overridden
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,46 @@
---
- debug:
msg: "START Replaced ios_interfaces state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Replaces device L2 configuration of listed interfaces with provided configuration
iosxr_l2_interfaces: &replaced
config:
- name: GigabitEthernet0/0/0/1
native_vlan: 40
l2transport: True
l2protocol:
- vtp: tunnel
state: replaced
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ replaced['before'] | symmetric_difference(result['before']) | length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ replaced['after'] | symmetric_difference(result['after']) | length == 0 }}"
- name: Replaces device L2 configuration of listed interfaces with provided configuration (IDEMPOTENT)
iosxr_l2_interfaces: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,142 @@
---
merged:
before: []
commands:
- interface GigabitEthernet0/0/0/1
- dot1q native vlan 10
- l2transport l2protocol pvst tunnel
- l2transport l2protocol cdp forward
- l2transport propagate remote-status
- interface GigabitEthernet0/0/0/3.900
- dot1q vlan 20 40
- interface GigabitEthernet0/0/0/4
- dot1q native vlan 40
after:
- l2protocol:
- cdp: forward
- pvst: tunnel
l2transport: true
name: GigabitEthernet0/0/0/1
native_vlan: 10
propagate: true
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 20
- 40
- name: GigabitEthernet0/0/0/4
native_vlan: 40
replaced:
before:
- l2protocol:
- cdp: forward
l2transport: true
name: GigabitEthernet0/0/0/1
native_vlan: 10
propagate: true
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 40
- 60
- l2protocol:
- vtp: tunnel
l2transport: true
name: GigabitEthernet0/0/0/4
native_vlan: 20
commands:
- interface GigabitEthernet0/0/0/1
- no l2transport
- dot1q native vlan 40
- l2transport l2protocol vtp tunnel
after:
- l2protocol:
- vtp: tunnel
l2transport: true
name: GigabitEthernet0/0/0/1
native_vlan: 40
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 40
- 60
- l2protocol:
- vtp: tunnel
l2transport: true
name: GigabitEthernet0/0/0/4
native_vlan: 20
overridden:
before:
- l2protocol:
- cdp: forward
l2transport: true
name: GigabitEthernet0/0/0/1
native_vlan: 10
propagate: true
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 40
- 60
- l2protocol:
- vtp: tunnel
l2transport: true
name: GigabitEthernet0/0/0/4
native_vlan: 20
commands:
- interface GigabitEthernet0/0/0/1
- no dot1q native vlan
- no l2transport
- interface GigabitEthernet0/0/0/3.900
- dot1q vlan 20 40
- interface GigabitEthernet0/0/0/4
- no l2transport
- dot1q native vlan 40
- l2transport l2protocol stp forward
after:
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 20
- 40
- l2protocol:
- stp: forward
l2transport: true
name: GigabitEthernet0/0/0/4
native_vlan: 40
deleted:
before:
- l2protocol:
- cdp: forward
l2transport: true
name: GigabitEthernet0/0/0/1
native_vlan: 10
propagate: true
- name: GigabitEthernet0/0/0/3.900
q_vlan:
- 40
- 60
- l2protocol:
- vtp: tunnel
l2transport: true
name: GigabitEthernet0/0/0/4
native_vlan: 20
commands:
- interface GigabitEthernet0/0/0/1
- no dot1q native vlan
- no l2transport
- interface GigabitEthernet0/0/0/3.900
- no encapsulation dot1q
- interface GigabitEthernet0/0/0/4
- no dot1q native vlan
- no l2transport
after:
- name: GigabitEthernet0/0/0/4
- name: GigabitEthernet0/0/0/3.900
Loading…
Cancel
Save