Resource module for iosxr_interfaces (#59853)

* iosxr interfaces resource

Signed-off-by: Sumit Jaiswal <sjaiswal@redhat.com>
This commit is contained in:
Sumit Jaiswal 2019-08-22 21:27:41 +05:30 committed by GitHub
parent 5487f5dc84
commit 47243b0b2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1338 additions and 12 deletions

View file

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

View file

@ -0,0 +1,47 @@
#
# -*- 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 ios_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class InterfacesArgs(object):
def __init__(self, **kwargs):
pass
argument_spec = {'config': {'elements': 'dict',
'options': {'name': {'type': 'str', 'required': True},
'description': {'type': 'str'},
'enabled': {'default': True, 'type': 'bool'},
'speed': {'type': 'int'},
'mtu': {'type': 'int'},
'duplex': {'type': 'str', 'choices': ['full', 'half']}},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

View file

@ -0,0 +1,263 @@
# -*- 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_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 get_interface_type, dict_diff
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 Interfaces(ConfigBase):
"""
The iosxr_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'interfaces',
]
params = ('description', 'mtu', 'speed', 'duplex')
def __init__(self, module):
super(Interfaces, self).__init__(module)
def get_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)
interfaces_facts = facts['ansible_network_resources'].get('interfaces')
if not interfaces_facts:
return []
return 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_interfaces_facts = self.get_interfaces_facts()
commands.extend(self.set_config(existing_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_interfaces_facts = self.get_interfaces_facts()
result['before'] = existing_interfaces_facts
if result['changed']:
result['after'] = changed_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_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_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)
elif state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged':
commands = self._state_merged(want, have)
elif state == 'replaced':
commands = self._state_replaced(want, have)
return commands
def _state_replaced(self, want, have):
""" 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:
for each in have:
if each['name'] == interface['name']:
break
elif interface['name'] in each['name']:
break
else:
continue
have_dict = filter_dict_having_none_value(interface, each)
want = dict()
commands.extend(self._clear_config(want, have_dict))
commands.extend(self._set_config(interface, each))
# Remove the duplicate interface call
commands = remove_duplicate_interface(commands)
return commands
def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for each in have:
for interface in want:
if each['name'] == interface['name']:
break
elif interface['name'] in each['name']:
break
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)
want = dict()
commands.extend(self._clear_config(want, have_dict))
commands.extend(self._set_config(interface, each))
# Remove the duplicate interface call
commands = remove_duplicate_interface(commands)
return commands
def _state_merged(self, want, have):
""" 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:
for each in have:
if each['name'] == interface['name']:
break
elif interface['name'] in each['name']:
break
else:
continue
commands.extend(self._set_config(interface, each))
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:
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):
# Set the interface config based on the want and have config
commands = []
interface = 'interface ' + want['name']
# Get the diff b/w want and have
want_dict = dict_diff(want)
have_dict = dict_diff(have)
diff = want_dict - have_dict
if diff:
diff = dict(diff)
for item in self.params:
if diff.get(item):
cmd = item + ' ' + str(want.get(item))
add_command_to_config_list(interface, cmd, commands)
if diff.get('enabled'):
add_command_to_config_list(interface, 'no shutdown', commands)
elif diff.get('enabled') is False:
add_command_to_config_list(interface, 'shutdown', 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_type = get_interface_type(want['name'])
interface = 'interface ' + want['name']
else:
interface_type = get_interface_type(have['name'])
interface = 'interface ' + have['name']
if have.get('description') and want.get('description') != have.get('description'):
remove_command_from_config_list(interface, 'description', commands)
if not have.get('enabled') and want.get('enabled') != have.get('enabled'):
# if enable is False set enable as True which is the default behavior
remove_command_from_config_list(interface, 'shutdown', commands)
if interface_type.lower() == 'gigabitethernet':
if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'):
remove_command_from_config_list(interface, 'speed', commands)
if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'):
remove_command_from_config_list(interface, 'duplex', commands)
if have.get('mtu') and want.get('mtu') != have.get('mtu'):
remove_command_from_config_list(interface, 'mtu', commands)
return commands

View file

@ -19,8 +19,8 @@ from ansible.module_utils.network.iosxr.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.iosxr.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts
from ansible.module_utils.network.iosxr.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.iosxr.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible.module_utils.network.iosxr.facts.legacy.\
base import Default, Hardware, Interfaces, Config
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
FACT_LEGACY_SUBSETS = dict(
@ -33,7 +33,8 @@ FACT_RESOURCE_SUBSETS = dict(
lacp=LacpFacts,
lacp_interfaces=Lacp_interfacesFacts,
lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts
lldp_interfaces=Lldp_interfacesFacts,
interfaces=InterfacesFacts,
)

View file

@ -0,0 +1,98 @@
#
# -*- 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 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, normalize_interface
from ansible.module_utils.network.iosxr.argspec.interfaces.interfaces import InterfacesArgs
class InterfacesFacts(object):
""" The iosxr interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = 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 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['interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['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)
if match.group(1).lower() == "preconfigure":
match = re.search(r'^(\S+ \S+)', conf)
intf = match.group(1)
if get_interface_type(intf) == 'unknown':
return {}
# populate the facts from the configuration
config['name'] = normalize_interface(intf)
config['description'] = utils.parse_conf_arg(conf, 'description')
if utils.parse_conf_arg(conf, 'speed'):
config['speed'] = int(utils.parse_conf_arg(conf, 'speed'))
if utils.parse_conf_arg(conf, 'mtu'):
config['mtu'] = int(utils.parse_conf_arg(conf, 'mtu'))
config['duplex'] = utils.parse_conf_arg(conf, 'duplex')
enabled = utils.parse_conf_cmd_arg(conf, 'shutdown', False)
config['enabled'] = enabled if enabled is not None else True
return utils.remove_empties(config)

View file

@ -18,6 +18,55 @@ def search_obj_in_list(name, lst, key='name'):
return None
def remove_command_from_config_list(interface, cmd, commands):
# To delete the passed config
if interface not in commands:
commands.insert(0, interface)
commands.append('no %s' % cmd)
return commands
def add_command_to_config_list(interface, cmd, commands):
# To set the passed config
if interface not in commands:
commands.insert(0, interface)
commands.append(cmd)
def dict_diff(sample_dict):
# Generate a set with passed dictionary for comparison
test_dict = {}
for k, v in iteritems(sample_dict):
if v is not None:
test_dict.update({k: v})
return_set = set(tuple(test_dict.items()))
return return_set
def filter_dict_having_none_value(want, have):
# Generate dict with have dict value which is None in want dict
test_dict = dict()
test_dict['name'] = want.get('name')
for k, v in iteritems(want):
if v is None:
val = have.get(k)
test_dict.update({k: val})
return test_dict
def remove_duplicate_interface(commands):
# Remove duplicate interface from commands
set_cmd = []
for each in commands:
if 'interface' in each:
if each not in set_cmd:
set_cmd.append(each)
else:
set_cmd.append(each)
return set_cmd
def flatten_dict(x):
result = {}
if not isinstance(x, dict):
@ -61,3 +110,83 @@ def dict_delete(base, comparable):
def pad_commands(commands, interface):
commands.insert(0, 'interface {0}'.format(interface))
def normalize_interface(name):
"""Return the normalized interface name
"""
if not name:
return
def _get_number(name):
digits = ''
for char in name:
if char.isdigit() or char in '/.':
digits += char
return digits
if name.lower().startswith('gi'):
if_type = 'GigabitEthernet'
elif name.lower().startswith('fa'):
if_type = 'FastEthernet'
elif name.lower().startswith('fo'):
if_type = 'FortyGigE'
elif name.lower().startswith('et'):
if_type = 'Ethernet'
elif name.lower().startswith('vl'):
if_type = 'Vlan'
elif name.lower().startswith('lo'):
if_type = 'loopback'
elif name.lower().startswith('po'):
if_type = 'port-channel'
elif name.lower().startswith('nv'):
if_type = 'nve'
elif name.lower().startswith('twe'):
if_type = 'TwentyFiveGigE'
elif name.lower().startswith('hu'):
if_type = 'HundredGigE'
else:
if_type = None
number_list = name.split(' ')
if len(number_list) == 2:
number = number_list[-1].strip()
else:
number = _get_number(name)
if if_type:
proper_interface = if_type + number
else:
proper_interface = name
return proper_interface
def get_interface_type(interface):
"""Gets the type of interface
"""
if interface.upper().startswith('GI'):
return 'GigabitEthernet'
elif interface.upper().startswith('FA'):
return 'FastEthernet'
elif interface.upper().startswith('FO'):
return 'FortyGigE'
elif interface.upper().startswith('ET'):
return 'Ethernet'
elif interface.upper().startswith('VL'):
return 'Vlan'
elif interface.upper().startswith('LO'):
return 'loopback'
elif interface.upper().startswith('PO'):
return 'port-channel'
elif interface.upper().startswith('NV'):
return 'nve'
elif interface.upper().startswith('TWE'):
return 'TwentyFiveGigE'
elif interface.upper().startswith('HU'):
return 'HundredGigE'
elif interface.upper().startswith('PRE'):
return 'preconfigure'
else:
return 'unknown'

View file

@ -9,7 +9,7 @@ __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'status': ['deprecated'],
'supported_by': 'network'}
@ -24,6 +24,10 @@ short_description: Manage Interface on Cisco IOS XR network devices
description:
- This module provides declarative management of Interfaces
on Cisco IOS XR network devices.
deprecated:
removed_in: '2.13'
alternative: iosxr_interfaces
why: Newer and updated modules released with more functionality in Ansible 2.9
extends_documentation_fragment: iosxr
notes:
- Tested against IOS XRv 6.1.2

View file

@ -51,7 +51,7 @@ 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']
choices: ['all', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces', 'interfaces', '!interfaces']
version_added: "2.9"
"""
@ -88,6 +88,14 @@ EXAMPLES = """
- iosxr_facts:
gather_subset: min
gather_network_resources: lacp
# Collect only the interfaces facts
- iosxr_facts:
gather_subset:
- "!all"
- "!min"
gather_network_resources:
- interfaces
"""
RETURN = """

View file

@ -0,0 +1,360 @@
#!/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_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
module: iosxr_interfaces
version_added: 2.9
short_description: Manage interface attributes on Cisco IOS-XR network devices
description: This module manages the interface attributes on Cisco IOS-XR network 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).
options:
config:
description: A dictionary of interface options
type: list
elements: dict
suboptions:
name:
description:
- Full name of the interface to configure in C(type + path) format. e.g. C(GigabitEthernet0/0/0/0)
type: str
required: True
description:
description:
- Interface description.
type: str
enabled:
default: True
description:
- Administrative state of the interface.
- Set the value to C(True) to administratively enable the interface or C(False) to disable it.
type: bool
speed:
description:
- Configure the speed for an interface. Default is auto-negotiation when not configured.
type: int
mtu:
description:
- Sets the MTU value for the interface. Applicable for Ethernet interfaces only.
- Refer to vendor documentation for valid values.
type: int
duplex:
description:
- Configures the interface duplex mode. Default is auto-negotiation when not configured.
type: str
choices: ['full', 'half']
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/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# vrf custB
# ipv4 address 178.18.169.23 255.255.255.0
# dot1q native vlan 30
# !
# interface GigabitEthernet0/0/0/3
# description Replaced by Ansible Team
# mtu 2000
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# dot1q native vlan 1021
# !
- name: Configure Ethernet interfaces
iosxr_interfaces:
config:
- name: GigabitEthernet0/0/0/2
description: 'Configured by Ansible'
enabled: True
- name: GigabitEthernet0/0/0/3
description: 'Configured by Ansible Network'
enabled: False
duplex: full
state: merged
# After state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# description Configured and Merged by Ansible Network
# vrf custB
# ipv4 address 178.18.169.23 255.255.255.0
# dot1q native vlan 30
# !
# interface GigabitEthernet0/0/0/3
# description Configured and Merged by Ansible Network
# mtu 2600
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex full
# shutdown
# dot1q native vlan 1021
# !
# Using replaced
# Before state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# description Configured by Ansible
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# description Test
# vrf custB
# ipv4 address 178.18.169.23 255.255.255.0
# dot1q native vlan 30
# !
# interface GigabitEthernet0/0/0/3
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# dot1q native vlan 1021
# !
- name: Configure following interfaces and replace their existing config
iosxr_interfaces:
config:
- name: GigabitEthernet0/0/0/2
description: Configured by Ansible
enabled: True
mtu: 2000
- name: GigabitEthernet0/0/0/3
description: 'Configured by Ansible Network'
enabled: False
duplex: auto
state: replaced
# After state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# description Configured by Ansible
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# description Configured and Replaced by Ansible
# mtu 2000
# vrf custB
# ipv4 address 178.18.169.23 255.255.255.0
# dot1q native vlan 30
# !
# interface GigabitEthernet0/0/0/3
# description Configured and Replaced by Ansible Network
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex half
# shutdown
# dot1q native vlan 1021
# !
# Using overridden
# Before state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# description Configured by Ansible
# vrf custB
# ipv4 address 178.18.169.23 255.255.255.0
# dot1q native vlan 30
# !
# interface GigabitEthernet0/0/0/3
# description Configured by Ansible
# mtu 2600
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex full
# shutdown
# dot1q native vlan 1021
# !
- name: Override interfaces
iosxr_interfaces:
config:
- name: GigabitEthernet0/0/0/2
description: 'Configured by Ansible'
enabled: True
duplex: auto
- name: GigabitEthernet0/0/0/3
description: 'Configured by Ansible Network'
enabled: False
speed: 1000
state: overridden
# After state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# description Configured and Overridden by Ansible Network
# vrf custB
# ipv4 address 178.18.169.23 255.255.255.0
# speed 1000
# dot1q native vlan 30
# !
# interface GigabitEthernet0/0/0/3
# description Configured and Overridden by Ansible Network
# mtu 2000
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex full
# shutdown
# dot1q native vlan 1021
# !
# Using deleted
# Before state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# description Configured and Overridden by Ansible Network
# vrf custB
# ipv4 address 178.18.169.23 255.255.255.0
# speed 1000
# dot1q native vlan 30
# !
# interface GigabitEthernet0/0/0/3
# description Configured and Overridden by Ansible Network
# mtu 2000
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# duplex full
# shutdown
# dot1q native vlan 1021
# !
- name: Delete IOSXR interfaces as in given arguments
iosxr_interfaces:
config:
- name: GigabitEthernet0/0/0/2
- name: GigabitEthernet0/0/0/3
state: deleted
# After state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# vrf custB
# ipv4 address 178.18.169.23 255.255.255.0
# dot1q native vlan 30
# !
# interface GigabitEthernet0/0/0/3
# vrf custB
# ipv4 address 10.10.0.2 255.255.255.0
# dot1q native vlan 1021
# !
"""
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', 'description: Configured by Ansible', 'shutdown']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.argspec.interfaces.interfaces import InterfacesArgs
from ansible.module_utils.network.iosxr.config.interfaces.interfaces import Interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec,
supports_check_mode=True)
result = Interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

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

View file

@ -0,0 +1 @@
dependencies: []

View file

@ -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

View file

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

View file

@ -0,0 +1,16 @@
---
- name: Populate Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
interface GigabitEthernet 0/0/0/0
description this is interface0
mtu 65
speed 10
no shutdown
interface GigabitEthernet 0/0/0/1
description this is interface1
mtu 65
speed 10
no shutdown

View file

@ -0,0 +1,24 @@
---
- name: Remove Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
interface loopback888
no description
no shutdown
interface loopback999
no description
no shutdown
- name: Remove interfaces from config before actual testing
iosxr_config:
lines:
- "no interface {{ item }}"
loop:
- GigabitEthernet 0/0/0/0
- GigabitEthernet 0/0/0/1
- preconfigure GigabitEthernet 0/0/0/1
- preconfigure GigabitEthernet 0/0/0/2
- preconfigure GigabitEthernet 0/0/0/3
ignore_errors: yes

View file

@ -0,0 +1,40 @@
---
- debug:
msg: "Start Deleted integration state for iosxr_interfaces ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Delete attributes of all configured interfaces
iosxr_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 attributes of all configured interfaces (IDEMPOTENT)
iosxr_interfaces: *deleted
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,52 @@
---
- debug:
msg: "START Merged iosxr_interfaces state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Merge provided configuration with device configuration
iosxr_interfaces: &merged
config:
- name: GigabitEthernet0/0/0/0
description: 'Configured and Merged by Ansible-Network'
mtu: 110
enabled: True
duplex: half
- name: GigabitEthernet0/0/0/1
description: 'Configured and Merged by Ansible-Network'
mtu: 2800
enabled: False
speed: 100
duplex: full
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 configuration with device configuration (IDEMPOTENT)
iosxr_interfaces: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,47 @@
---
- debug:
msg: "START Overridden iosxr_interfaces state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Override device configuration of all interfaces with provided configuration
iosxr_interfaces: &overridden
config:
- name: GigabitEthernet0/0/0/1
description: 'Configured and Overridden by Ansible-Network'
enabled: False
duplex: full
mtu: 2000
speed: 100
state: overridden
register: result
- 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 configuration of all interfaces with provided configuration (IDEMPOTENT)
iosxr_interfaces: *overridden
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,47 @@
---
- debug:
msg: "START Replaced iosxr_interfaces state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Replaces device configuration of listed interfaces with provided configuration
iosxr_interfaces: &replaced
config:
- name: GigabitEthernet0/0/0/0
description: 'Configured and Replaced by Ansible-Network'
mtu: 110
- name: GigabitEthernet0/0/0/1
description: 'Configured and Replaced by Ansible-Network'
speed: 100
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 configuration of listed interfaces with provided configuration (IDEMPOTENT)
iosxr_interfaces: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,162 @@
---
merged:
before:
- enabled: true
name: loopback888
- enabled: true
name: loopback999
- description: this is interface0
enabled: true
mtu: 65
name: GigabitEthernet0/0/0/0
speed: 10
- description: this is interface1
enabled: true
mtu: 65
name: GigabitEthernet0/0/0/1
speed: 10
commands:
- "interface GigabitEthernet0/0/0/0"
- "description Configured and Merged by Ansible-Network"
- "mtu 110"
- "duplex half"
- "interface GigabitEthernet0/0/0/1"
- "description Configured and Merged by Ansible-Network"
- "mtu 2800"
- "speed 100"
- "duplex full"
- "shutdown"
after:
- enabled: true
name: loopback888
- enabled: true
name: loopback999
- description: Configured and Merged by Ansible-Network
duplex: half
enabled: true
mtu: 110
name: GigabitEthernet0/0/0/0
speed: 10
- description: Configured and Merged by Ansible-Network
duplex: full
enabled: false
mtu: 2800
name: GigabitEthernet0/0/0/1
speed: 100
replaced:
before:
- enabled: true
name: loopback888
- enabled: true
name: loopback999
- description: this is interface0
enabled: true
mtu: 65
name: GigabitEthernet0/0/0/0
speed: 10
- description: this is interface1
enabled: true
mtu: 65
name: GigabitEthernet0/0/0/1
speed: 10
commands:
- "interface GigabitEthernet0/0/0/0"
- "no speed"
- "description Configured and Replaced by Ansible-Network"
- "mtu 110"
- "interface GigabitEthernet0/0/0/1"
- "no mtu"
- "description Configured and Replaced by Ansible-Network"
- "speed 100"
after:
- enabled: true
name: loopback888
- enabled: true
name: loopback999
- description: Configured and Replaced by Ansible-Network
enabled: true
mtu: 110
name: GigabitEthernet0/0/0/0
- description: Configured and Replaced by Ansible-Network
enabled: true
name: GigabitEthernet0/0/0/1
speed: 100
overridden:
before:
- enabled: true
name: loopback888
- enabled: true
name: loopback999
- description: this is interface0
enabled: true
mtu: 65
name: GigabitEthernet0/0/0/0
speed: 10
- description: this is interface1
enabled: true
mtu: 65
name: GigabitEthernet0/0/0/1
speed: 10
commands:
- "interface GigabitEthernet0/0/0/0"
- "no description"
- "no speed"
- "no mtu"
- "interface GigabitEthernet0/0/0/1"
- "description Configured and Overridden by Ansible-Network"
- "mtu 2000"
- "duplex full"
- "speed 100"
- "shutdown"
after:
- enabled: true
name: loopback888
- enabled: true
name: loopback999
- description: Configured and Overridden by Ansible-Network
duplex: full
enabled: false
mtu: 2000
name: GigabitEthernet0/0/0/1
speed: 100
deleted:
before:
- enabled: true
name: loopback888
- enabled: true
name: loopback999
- description: this is interface0
enabled: true
mtu: 65
name: GigabitEthernet0/0/0/0
speed: 10
- description: this is interface1
enabled: true
mtu: 65
name: GigabitEthernet0/0/0/1
speed: 10
commands:
- "interface GigabitEthernet0/0/0/0"
- "no description"
- "no speed"
- "no mtu"
- "interface GigabitEthernet0/0/0/1"
- "no description"
- "no speed"
- "no mtu"
after:
- enabled: true
name: loopback888
- enabled: true
name: loopback999

View file

@ -4093,12 +4093,12 @@ lib/ansible/modules/network/iosxr/iosxr_facts.py validate-modules:E322
lib/ansible/modules/network/iosxr/iosxr_facts.py validate-modules:E324
lib/ansible/modules/network/iosxr/iosxr_facts.py validate-modules:E326
lib/ansible/modules/network/iosxr/iosxr_facts.py validate-modules:E337
lib/ansible/modules/network/iosxr/iosxr_interface.py validate-modules:E322
lib/ansible/modules/network/iosxr/iosxr_interface.py validate-modules:E324
lib/ansible/modules/network/iosxr/iosxr_interface.py validate-modules:E326
lib/ansible/modules/network/iosxr/iosxr_interface.py validate-modules:E337
lib/ansible/modules/network/iosxr/iosxr_interface.py validate-modules:E338
lib/ansible/modules/network/iosxr/iosxr_interface.py validate-modules:E340
lib/ansible/modules/network/iosxr/_iosxr_interface.py validate-modules:E322
lib/ansible/modules/network/iosxr/_iosxr_interface.py validate-modules:E324
lib/ansible/modules/network/iosxr/_iosxr_interface.py validate-modules:E326
lib/ansible/modules/network/iosxr/_iosxr_interface.py validate-modules:E337
lib/ansible/modules/network/iosxr/_iosxr_interface.py validate-modules:E338
lib/ansible/modules/network/iosxr/_iosxr_interface.py validate-modules:E340
lib/ansible/modules/network/iosxr/iosxr_logging.py validate-modules:E322
lib/ansible/modules/network/iosxr/iosxr_logging.py validate-modules:E324
lib/ansible/modules/network/iosxr/iosxr_logging.py validate-modules:E326