Resource module for iosxr_l3_interfaces (#60464)

* iosxr_l3_interfaces module
This commit is contained in:
Sumit Jaiswal 2019-08-29 17:58:48 +05:30 committed by GitHub
parent 68f34351fc
commit 4d73c7b397
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1415 additions and 44 deletions

View file

@ -33,7 +33,9 @@ class FactsArgs(object): # pylint: disable=R0903
'l2_interfaces',
'!l2_interfaces',
'lag_interfaces',
'!lag_interfaces'
'!lag_interfaces',
'l3_interfaces',
'!l3_interfaces',
]
argument_spec = {

View file

@ -0,0 +1,51 @@
#
# -*- 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_l3_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class L3_InterfacesArgs(object):
def __init__(self, **kwargs):
pass
argument_spec = {'config': {'elements': 'dict',
'options': {'name': {'type': 'str', 'required': True},
'ipv4': {'element': 'dict',
'type': 'list',
'options': {'address': {'type': 'str'},
'secondary': {'type': 'bool'}}},
'ipv6': {'element': 'dict',
'type': 'list',
'options': {'address': {'type': 'str'}}}
},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

View file

@ -124,8 +124,7 @@ class L2_Interfaces(ConfigBase):
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._clear_config(dict(), have_dict))
commands.extend(self._set_config(interface, each, module))
# Remove the duplicate interface call
commands = remove_duplicate_interface(commands)
@ -156,8 +155,7 @@ class L2_Interfaces(ConfigBase):
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._clear_config(dict(), 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):
@ -260,10 +258,9 @@ class L2_Interfaces(ConfigBase):
if l2transport or l2protocol:
for each in l2protocol:
each = dict(each)
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])
cmd = 'l2transport l2protocol {0} {1}'.format(list(each.keys())[0], list(each.values())[0])
add_command_to_config_list(interface, cmd, commands)
if propagate and not have.get('propagate'):
cmd = 'l2transport propagate remote-status'

View file

@ -0,0 +1,285 @@
# -*- 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_l3_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
from ansible.module_utils.network.iosxr.utils.utils import validate_n_expand_ipv4, validate_ipv6
class L3_Interfaces(ConfigBase):
"""
The iosxr_l3_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'l3_interfaces',
]
def get_l3_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)
l3_interfaces_facts = facts['ansible_network_resources'].get('l3_interfaces')
if not l3_interfaces_facts:
return []
return l3_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_l3_interfaces_facts = self.get_l3_interfaces_facts()
commands.extend(self.set_config(existing_l3_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_l3_interfaces_facts = self.get_l3_interfaces_facts()
result['before'] = existing_l3_interfaces_facts
if result['changed']:
result['after'] = changed_l3_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_l3_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_l3_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, dict(), module))
continue
have_dict = filter_dict_having_none_value(interface, each)
commands.extend(self._clear_config(dict(), 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'])
kwargs = {'want': interface, 'have': each}
commands.extend(self._clear_config(**kwargs))
continue
have_dict = filter_dict_having_none_value(interface, each)
commands.extend(self._clear_config(dict(), 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
else:
commands.extend(self._set_config(interface, dict(), 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']
# To handle L3 IPV4 configuration
if want.get("ipv4"):
for each in want.get("ipv4"):
if each.get('address') != 'dhcp':
ip_addr_want = validate_n_expand_ipv4(module, each)
each['address'] = ip_addr_want
# Get the diff b/w want and have
want_dict = dict_to_set(want)
have_dict = dict_to_set(have)
# To handle L3 IPV4 configuration
if dict(want_dict).get('ipv4'):
if dict(have_dict).get('ipv4'):
diff_ipv4 = set(dict(want_dict).get('ipv4')) - set(dict(have_dict).get('ipv4'))
else:
diff_ipv4 = set(dict(want_dict).get('ipv4'))
for each in diff_ipv4:
ipv4_dict = dict(each)
if ipv4_dict.get('address') != 'dhcp':
cmd = "ipv4 address {0}".format(ipv4_dict['address'])
if ipv4_dict.get("secondary"):
cmd += " secondary"
add_command_to_config_list(interface, cmd, commands)
# To handle L3 IPV6 configuration
if dict(want_dict).get('ipv6'):
if dict(have_dict).get('ipv6'):
diff_ipv6 = set(dict(want_dict).get('ipv6')) - set(dict(have_dict).get('ipv6'))
else:
diff_ipv6 = set(dict(want_dict).get('ipv6'))
for each in diff_ipv6:
ipv6_dict = dict(each)
validate_ipv6(ipv6_dict.get('address'), module)
cmd = "ipv6 address {0}".format(ipv6_dict.get('address'))
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
count = 0
commands = []
if want.get('name'):
interface = 'interface ' + want['name']
else:
interface = 'interface ' + have['name']
if have.get('ipv4') and want.get('ipv4'):
for each in have.get('ipv4'):
if each.get('secondary') and not (want.get('ipv4')[count].get('secondary')):
cmd = 'ipv4 address {0} secondary'.format(each.get('address'))
remove_command_from_config_list(interface, cmd, commands)
count += 1
if have.get('ipv4') and not (want.get('ipv4')):
remove_command_from_config_list(interface, 'ipv4 address', commands)
if have.get('ipv6') and not (want.get('ipv6')):
remove_command_from_config_list(interface, 'ipv6 address', commands)
return commands

View file

@ -15,14 +15,15 @@ __metaclass__ = type
from ansible.module_utils.network.iosxr.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.iosxr.facts.legacy.base import Default, Hardware, Interfaces, Config
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.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.iosxr.facts.lag_interfaces.lag_interfaces import Lag_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.l3_interfaces.l3_interfaces import L3_InterfacesFacts
FACT_LEGACY_SUBSETS = dict(
@ -38,7 +39,8 @@ FACT_RESOURCE_SUBSETS = dict(
lldp_interfaces=Lldp_interfacesFacts,
interfaces=InterfacesFacts,
l2_interfaces=L2_InterfacesFacts,
lag_interfaces=Lag_interfacesFacts
lag_interfaces=Lag_interfacesFacts,
l3_interfaces=L3_InterfacesFacts
)

View file

@ -0,0 +1,117 @@
#
# -*- 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)
"""
The iosxr_l3_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.l3_interfaces.l3_interfaces import L3_InterfacesArgs
class L3_InterfacesFacts(object):
""" The iosxr_l3_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = L3_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 connection: the device connection
:param ansible_facts: Facts dictionary
: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['l3_interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['l3_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 {}
# populate the facts from the configuration
config['name'] = intf
# Get the configured IPV4 details
ipv4 = []
ipv4_all = re.findall(r"ipv4 address (\S+.*)", conf)
for each in ipv4_all:
each_ipv4 = dict()
if 'secondary' in each:
each_ipv4['address'] = each.split(' secondary')[0]
each_ipv4['secondary'] = True
else:
each_ipv4['address'] = each
ipv4.append(each_ipv4)
config['ipv4'] = ipv4
# Get the configured IPV6 details
ipv6 = []
ipv6_all = re.findall(r"ipv6 address (\S+)", conf)
for each in ipv6_all:
each_ipv6 = dict()
each_ipv6['address'] = each
ipv6.append(each_ipv6)
config['ipv6'] = ipv6
return utils.remove_empties(config)

View file

@ -9,6 +9,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import is_masklen, to_netmask
def remove_command_from_config_list(interface, cmd, commands):
@ -29,36 +30,72 @@ def add_command_to_config_list(interface, cmd, commands):
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):
if isinstance(sample_dict, 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.append(tuple(iteritems(each)))
v = tuple(li)
else:
v = tuple(v)
elif isinstance(v, 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()))
for key, value in iteritems(v):
if isinstance(value, list):
v[key] = tuple(value)
li.extend(tuple(iteritems(v)))
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()))
test_dict.update({k: v})
return_set = set(tuple(iteritems(test_dict)))
else:
return_set = set(sample_dict)
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')
test_key_dict = dict()
name = want.get('name')
if name:
test_dict['name'] = name
diff_ip = False
want_ip = ''
for k, v in iteritems(want):
if isinstance(v, dict):
for key, value in iteritems(v):
if value is None:
dict_val = have.get(k).get(key)
test_key_dict.update({key: dict_val})
test_dict.update({k: test_key_dict})
if isinstance(v, list) and isinstance(v[0], dict):
for key, value in iteritems(v[0]):
if value is None:
dict_val = have.get(k).get(key)
test_key_dict.update({key: dict_val})
test_dict.update({k: test_key_dict})
# below conditions checks are added to check if
# secondary IP is configured, if yes then delete
# the already configured IP if want and have IP
# is different else if it's same no need to delete
for each in v:
if each.get('secondary'):
want_ip = each.get('address').split('/')
have_ip = have.get('ipv4')
for each in have_ip:
if len(want_ip) > 1 and each.get('secondary'):
have_ip = each.get('address').split(' ')[0]
if have_ip != want_ip[0]:
diff_ip = True
if each.get('secondary') and diff_ip is True:
test_key_dict.update({'secondary': True})
test_dict.update({'ipv4': test_key_dict})
if k == 'l2protocol':
if want[k] != have.get('l2protocol') and have.get('l2protocol'):
test_dict.update({k: v})
@ -147,6 +184,39 @@ def diff_list_of_dicts(w, h):
return diff
def validate_ipv4(value, module):
if value:
address = value.split('/')
if len(address) != 2:
module.fail_json(msg='address format is <ipv4 address>/<mask>, got invalid format {0}'.format(value))
if not is_masklen(address[1]):
module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-32'.format(address[1]))
def validate_ipv6(value, module):
if value:
address = value.split('/')
if len(address) != 2:
module.fail_json(msg='address format is <ipv6 address>/<mask>, got invalid format {0}'.format(value))
else:
if not 0 <= int(address[1]) <= 128:
module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-128'.format(address[1]))
def validate_n_expand_ipv4(module, want):
# Check if input IPV4 is valid IP and expand IPV4 with its subnet mask
ip_addr_want = want.get('address')
if len(ip_addr_want.split(' ')) > 1:
return ip_addr_want
validate_ipv4(ip_addr_want, module)
ip = ip_addr_want.split('/')
if len(ip) == 2:
ip_addr_want = '{0} {1}'.format(ip[0], to_netmask(ip[1]))
return ip_addr_want
def normalize_interface(name):
"""Return the normalized interface name
"""

View file

@ -56,7 +56,7 @@ options:
required: false
choices: ['all', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces', 'lldp_global', '!lldp_global',
'lldp_interfaces', '!lldp_interfaces', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces',
'lag_interfaces', '!lag_interfaces']
'lag_interfaces', '!lag_interfaces', 'l3_interfaces', '!l3_interfaces']
version_added: "2.9"
"""

View file

@ -0,0 +1,419 @@
#!/usr/bin/python
# -*- 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)
##############################################
# 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 ios_l3_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_l3_interfaces
version_added: 2.9
short_description: Manage Layer-3 interface on Cisco IOS-XR devices.
description: This module provides declarative management of Layer-3 interface 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-3 interface options
type: list
elements: dict
suboptions:
name:
description:
- Full name of the interface excluding any logical unit number, i.e. GigabitEthernet0/1.
type: str
required: True
ipv4:
description:
- IPv4 address to be set for the Layer-3 interface mentioned in I(name) option.
- The address format is <ipv4 address>/<mask>, the mask is number in range 0-32
eg. 192.168.0.1/24
type: list
suboptions:
address:
description:
- Configures the IPv4 address for Interface.
type: str
secondary:
description:
- Configures the IP address as a secondary address.
type: bool
ipv6:
description:
- IPv6 address to be set for the Layer-3 interface mentioned in I(name) option.
- The address format is <ipv6 address>/<mask>, the mask is number in range 0-128
eg. fd5d:12c9:2201:1::1/64
type: list
suboptions:
address:
description:
- Configures the IPv6 address for Interface.
type: str
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
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# ipv4 address 192.168.0.2 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# !
# interface GigabitEthernet0/0/0/4
# ipv6 address fd5d:12c9:2201:1::1/64
# shutdown
# !
- name: Merge provided configuration with device configuration
iosxr_l3_interfaces:
config:
- name: GigabitEthernet0/0/0/2
ipv4:
- address: 192.168.0.1/24
- name: GigabitEthernet0/0/0/3
ipv4:
- address: 192.168.2.1/24
secondary: True
operation: merged
# After state:
# ------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# ipv4 address 192.168.0.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# ipv4 address 192.168.1.0 255.255.255.0
# ipv4 address 192.168.2.1 255.255.255.0 secondary
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# !
# interface GigabitEthernet0/0/0/4
# ipv6 address fd5d:12c9:2201:1::1/64
# shutdown
# !
# Using overridden
# Before state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# ipv4 address 192.168.0.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# ipv4 address 192.168.1.0 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# !
# interface GigabitEthernet0/0/0/4
# ipv6 address fd5d:12c9:2201:1::1/64
# shutdown
# !
- name: Override device configuration of all interfaces with provided configuration
iosxr_l3_interfaces:
config:
- name: GigabitEthernet0/0/0/3
ipv4:
- address: 192.168.0.1/24
- name: GigabitEthernet0/0/0/3.700
ipv4:
- address: 192.168.0.2/24
- address: 192.168.2.1/24
secondary: True
operation: overridden
# After state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# ipv4 address 192.168.0.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# ipv4 address 192.168.0.2 255.255.255.0
# ipv4 address 192.168.2.1 255.255.255.0 secondary
# !
# interface GigabitEthernet0/0/0/4
# shutdown
# !
# Using replaced
# Before state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# ipv4 address 192.168.0.2 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# ipv4 address 192.168.0.1 255.255.255.0
# !
# interface GigabitEthernet0/0/0/4
# ipv6 address fd5d:12c9:2201:1::1/64
# shutdown
# !
- name: Replaces device configuration of listed interfaces with provided configuration
iosxr_l3_interfaces:
config:
- name: GigabitEthernet0/0/0/3
ipv6:
- address: fd5d:12c9:2201:1::1/64
- name: GigabitEthernet0/0/0/4
ipv4:
- address: 192.168.0.2/24
operation: replaced
# After state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# ipv6 address fd5d:12c9:2201:1::1/64
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# ipv4 address 192.168.0.1 255.255.255.0
# !
# interface GigabitEthernet0/0/0/4
# ipv4 address 192.168.0.2 255.255.255.0
# shutdown
# !
# Using deleted
# Before state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# ipv4 address 192.168.2.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# ipv4 address 192.168.3.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# ipv4 address 192.168.0.2 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# ipv4 address 192.168.0.1 255.255.255.0
# !
# interface GigabitEthernet0/0/0/4
# ipv6 address fd5d:12c9:2201:1::1/64
# shutdown
# !
- name: "Delete L3 attributes of given interfaces (Note: This won't delete the interface itself)"
iosxr_l3_interfaces:
config:
- name: GigabitEthernet0/0/0/3
- name: GigabitEthernet0/0/0/4
- name: GigabitEthernet0/0/0/3.700
operation: deleted
# After state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# ipv4 address 192.168.2.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# ipv4 address 192.168.3.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# !
# interface GigabitEthernet0/0/0/4
# shutdown
# !
# 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/1
# ipv4 address 192.168.2.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# ipv4 address 192.168.3.1 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# ipv4 address 192.168.0.2 255.255.255.0
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# ipv4 address 192.168.0.1 255.255.255.0
# !
# interface GigabitEthernet0/0/0/4
# ipv6 address fd5d:12c9:2201:1::1/64
# shutdown
# !
- name: "Delete L3 attributes of all interfaces (Note: This won't delete the interface itself)"
iosxr_l3_interfaces:
operation: deleted
# After state:
# -------------
#
# viosxr#show running-config interface
# interface GigabitEthernet0/0/0/1
# shutdown
# !
# interface GigabitEthernet0/0/0/2
# shutdown
# !
# interface GigabitEthernet0/0/0/3
# shutdown
# !
# interface GigabitEthernet0/0/0/3.700
# !
# interface GigabitEthernet0/0/0/4
# shutdown
# !
"""
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/1', 'ipv4 address 192.168.0.1 255.255.255.0']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs
from ansible.module_utils.network.iosxr.config.l3_interfaces.l3_interfaces import L3_Interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=L3_InterfacesArgs.argument_spec,
supports_check_mode=True)
result = L3_Interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -18,7 +18,6 @@
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
- GigabitEthernet 0/0/0/2
- GigabitEthernet 0/0/0/3
ignore_errors: yes

View file

@ -11,6 +11,3 @@
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

View file

@ -22,12 +22,6 @@
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:

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/1
ipv4 address 198.51.100.1 255.255.255.0
interface GigabitEthernet 0/0/0/2
ipv4 address 192.0.2.1 255.255.255.0
ipv4 address 192.0.2.2 255.255.255.0 secondary
interface GigabitEthernet 0/0/0/3
ipv4 address 203.0.110.1 255.255.255.0
ipv6 address 2001:db8::/32
interface GigabitEthernet 0/0/0/3.100
ipv4 address 203.0.113.1 255.255.255.0

View file

@ -0,0 +1,13 @@
---
- name: Remove Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
interface GigabitEthernet 0/0/0/1
no ipv4 address
no ipv6 address
no interface GigabitEthernet 0/0/0/2
no interface GigabitEthernet 0/0/0/3
no interface GigabitEthernet 0/0/0/3.100
no interface GigabitEthernet 0/0/0/4

View file

@ -0,0 +1,45 @@
---
- debug:
msg: "Start Deleted integration state for iosxr_l3_interfaces ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Delete attributes of all configured interfaces
iosxr_l3_interfaces: &deleted
config:
- name: GigabitEthernet0/0/0/1
- name: GigabitEthernet0/0/0/2
- name: GigabitEthernet0/0/0/3
- name: GigabitEthernet0/0/0/3.100
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_l3_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,59 @@
---
- debug:
msg: "START Merged iosxr_l3_interfaces state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Merge provided configuration with device configuration
iosxr_l3_interfaces: &merged
config:
- name: GigabitEthernet0/0/0/1
ipv4:
- address: 198.51.100.1/24
- name: GigabitEthernet0/0/0/2
ipv6:
- address: 2001:db8:0:3::/64
ipv4:
- address: 192.0.2.1/24
- address: 192.0.2.2/24
secondary: True
- name: GigabitEthernet0/0/0/3
ipv4:
- address: 203.0.110.1/24
- address: 203.0.110.5/24
secondary: True
- name: GigabitEthernet0/0/0/3.100
ipv4:
- address: 198.51.100.14/24
ipv6:
- address: 2001:db8:0:4::/64
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_l3_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,49 @@
---
- debug:
msg: "START Overridden iosxr_l3_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_l3_interfaces: &overridden
config:
- name: GigabitEthernet0/0/0/3
ipv4:
- address: 203.0.112.24/24
- name: GigabitEthernet0/0/0/3.100
ipv4:
- address: 198.51.102.1/24
ipv6:
- address: 2001:db8:1::/64
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_l3_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,52 @@
---
- debug:
msg: "START Replaced iosxr_l3_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_l3_interfaces: &replaced
config:
- name: GigabitEthernet0/0/0/1
ipv4:
- address: 203.0.113.27/24
- name: GigabitEthernet0/0/0/2
ipv4:
- address: 203.0.115.2/24
- address: 203.0.114.1/24
secondary: True
- name: GigabitEthernet0/0/0/3.100
ipv4:
- address: 192.0.2.10/24
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_l3_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,178 @@
---
merged:
before:
- name: Loopback888
- name: Loopback999
commands:
- "interface GigabitEthernet0/0/0/1"
- "ipv4 address 198.51.100.1 255.255.255.0"
- "interface GigabitEthernet0/0/0/2"
- "ipv4 address 192.0.2.2 255.255.255.0 secondary"
- "ipv4 address 192.0.2.1 255.255.255.0"
- "ipv6 address 2001:db8:0:3::/64"
- "interface GigabitEthernet0/0/0/3"
- "ipv4 address 203.0.110.1 255.255.255.0"
- "ipv4 address 203.0.110.5 255.255.255.0 secondary"
- "interface GigabitEthernet0/0/0/3.100"
- "ipv4 address 198.51.100.14 255.255.255.0"
- "ipv6 address 2001:db8:0:4::/64"
after:
- name: Loopback888
- name: Loopback999
- ipv4:
- address: 198.51.100.1 255.255.255.0
name: GigabitEthernet0/0/0/1
- ipv4:
- address: 192.0.2.1 255.255.255.0
- address: 192.0.2.2 255.255.255.0
secondary: true
ipv6:
- address: 2001:db8:0:3::/64
name: GigabitEthernet0/0/0/2
- ipv4:
- address: 203.0.110.1 255.255.255.0
- address: 203.0.110.5 255.255.255.0
secondary: true
name: GigabitEthernet0/0/0/3
- ipv4:
- address: 198.51.100.14 255.255.255.0
ipv6:
- address: 2001:db8:0:4::/64
name: GigabitEthernet0/0/0/3.100
replaced:
before:
- name: Loopback888
- name: Loopback999
- ipv4:
- address: 198.51.100.1 255.255.255.0
name: GigabitEthernet0/0/0/1
- ipv4:
- address: 192.0.2.1 255.255.255.0
- address: 192.0.2.2 255.255.255.0
secondary: true
name: GigabitEthernet0/0/0/2
- ipv4:
- address: 203.0.110.1 255.255.255.0
ipv6:
- address: 2001:db8::/32
name: GigabitEthernet0/0/0/3
- ipv4:
- address: 203.0.113.1 255.255.255.0
name: GigabitEthernet0/0/0/3.100
commands:
- "interface GigabitEthernet0/0/0/1"
- "ipv4 address 203.0.113.27 255.255.255.0"
- "interface GigabitEthernet0/0/0/2"
- "no ipv4 address"
- "ipv4 address 203.0.115.2 255.255.255.0"
- "ipv4 address 203.0.114.1 255.255.255.0 secondary"
- "interface GigabitEthernet0/0/0/3.100"
- "ipv4 address 192.0.2.10 255.255.255.0"
after:
- name: Loopback888
- name: Loopback999
- ipv4:
- address: 203.0.113.27 255.255.255.0
name: GigabitEthernet0/0/0/1
- ipv4:
- address: 203.0.115.2 255.255.255.0
- address: 203.0.114.1 255.255.255.0
secondary: true
name: GigabitEthernet0/0/0/2
- ipv4:
- address: 203.0.110.1 255.255.255.0
ipv6:
- address: 2001:db8::/32
name: GigabitEthernet0/0/0/3
- ipv4:
- address: 192.0.2.10 255.255.255.0
name: GigabitEthernet0/0/0/3.100
overridden:
before:
- name: Loopback888
- name: Loopback999
- ipv4:
- address: 198.51.100.1 255.255.255.0
name: GigabitEthernet0/0/0/1
- ipv4:
- address: 192.0.2.1 255.255.255.0
- address: 192.0.2.2 255.255.255.0
secondary: true
name: GigabitEthernet0/0/0/2
- ipv4:
- address: 203.0.110.1 255.255.255.0
ipv6:
- address: 2001:db8::/32
name: GigabitEthernet0/0/0/3
- ipv4:
- address: 203.0.113.1 255.255.255.0
name: GigabitEthernet0/0/0/3.100
commands:
- "interface GigabitEthernet0/0/0/1"
- "no ipv4 address"
- "interface GigabitEthernet0/0/0/2"
- "no ipv4 address"
- "interface GigabitEthernet0/0/0/3"
- "no ipv6 address"
- "ipv4 address 203.0.112.24 255.255.255.0"
- "interface GigabitEthernet0/0/0/3.100"
- "ipv4 address 198.51.102.1 255.255.255.0"
- "ipv6 address 2001:db8:1::/64"
after:
- name: Loopback888
- name: Loopback999
- name: GigabitEthernet0/0/0/2
- ipv4:
- address: 203.0.112.24 255.255.255.0
name: GigabitEthernet0/0/0/3
- ipv4:
- address: 198.51.102.1 255.255.255.0
ipv6:
- address: 2001:db8:1::/64
name: GigabitEthernet0/0/0/3.100
deleted:
before:
- name: Loopback888
- name: Loopback999
- ipv4:
- address: 198.51.100.1 255.255.255.0
name: GigabitEthernet0/0/0/1
- ipv4:
- address: 192.0.2.1 255.255.255.0
- address: 192.0.2.2 255.255.255.0
secondary: true
name: GigabitEthernet0/0/0/2
- ipv4:
- address: 203.0.110.1 255.255.255.0
ipv6:
- address: 2001:db8::/32
name: GigabitEthernet0/0/0/3
- ipv4:
- address: 203.0.113.1 255.255.255.0
name: GigabitEthernet0/0/0/3.100
commands:
- "interface GigabitEthernet0/0/0/1"
- "no ipv4 address"
- "interface GigabitEthernet0/0/0/2"
- "no ipv4 address"
- "interface GigabitEthernet0/0/0/3"
- "no ipv4 address"
- "no ipv6 address"
- "interface GigabitEthernet0/0/0/3.100"
- "no ipv4 address"
after:
- name: Loopback888
- name: Loopback999
- name: GigabitEthernet0/0/0/3
- name: GigabitEthernet0/0/0/3.100