From 72eff5ee45b237964aabf56bf725b0bd93cd0003 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 01/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- library/iosxr_facts.py | 102 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 library/iosxr_facts.py diff --git a/library/iosxr_facts.py b/library/iosxr_facts.py new file mode 100644 index 0000000..3e85675 --- /dev/null +++ b/library/iosxr_facts.py @@ -0,0 +1,102 @@ +#!/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) +""" +The module file for iosxr_facts +""" + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network'} + +NETWORK_OS = "iosxr" +RESOURCE = "facts" +COPYRIGHT = "Copyright 2019 Red Hat" + +DOCUMENTATION = """ +--- +module: iosxr_facts +version_added: 2.9 +short_description: Get facts about Cisco IOS-XR devices. +description: + - Collects facts from network devices running the Cisco IOS-XR operating + system. This module places the facts gathered in the fact tree keyed by the + respective resource name. The facts module will always collect a + base set of facts from the device and can enable or disable + collection of additional facts. +author: [u'Sumit Jaiswal (@justjais)'] +notes: + - Tested against iosv Version 6.1.3 on VIRL +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, and net_configuration_. Can specify a + list of values to include a larger subset. Values can also be used + with an initial C(M(!)) to specify that a specific subset should + not be collected. + required: false + default: 'all' + version_added: "2.2" +""" + +EXAMPLES = """ +# Gather all facts +- iosxr_facts: + gather_subset: all + gather_network_resources: all +# Collect only the iosxr facts +- iosxr_facts: + gather_subset: + - !all + - !min + gather_network_resources: + - iosxr +# Do not collect iosxr facts +- iosxr_facts: + gather_network_resources: + - "!iosxr" +# Collect iosxr and minimal default facts +- iosxr_facts: + gather_subset: min + gather_network_resources: iosxr +""" + +RETURN = """ +See the respective resource module parameters for the tree. +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.iosxr.facts.facts import Facts + + +def main(): + """ + Main entry point for module execution + + :returns: ansible_facts + """ + module = AnsibleModule(argument_spec=Facts.argument_spec, + supports_check_mode=True) + warnings = ['default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards'] + + connection = Connection(module._socket_path) #pylint: disable=W0212 + gather_subset = module.params['gather_subset'] + gather_network_resources = module.params['gather_network_resources'] + result = Facts().get_facts(module, connection, gather_subset, gather_network_resources) + + try: + ansible_facts, warning = result + warnings.extend(warning) + except (TypeError, KeyError): + ansible_facts = result + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + +if __name__ == '__main__': + main() From 01a55a0267c157ac9e20ef79c460b69730a613aa Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 02/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- library/iosxr_l3_interface.py | 315 ++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 library/iosxr_l3_interface.py diff --git a/library/iosxr_l3_interface.py b/library/iosxr_l3_interface.py new file mode 100644 index 0000000..133e158 --- /dev/null +++ b/library/iosxr_l3_interface.py @@ -0,0 +1,315 @@ +#!/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 iosxr_l3_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'} + +NETWORK_OS = "iosxr" +RESOURCE = "l3_interfaces" +COPYRIGHT = "Copyright 2019 Red Hat" + +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) + 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 /, the mask is number in range 0-32 + eg. 192.168.0.1/24 + type: str + ipv6: + description: + - IPv6 address to be set for the Layer-3 interface mentioned in I(name) option. + The address format is /, the mask is number in range 0-128 + eg. fd5d:12c9:2201:1::1/64 + 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/4 +# ipv6 address fd5d:12c9:2201:1::1/64 +# shutdown +# ! + +- name: Merge provided configuration with device configuration + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/2 + ipv4: 192.168.0.1/24 + - name: GigabitEthernet0/0/0/3 + ipv4: 192.168.1.0/24 + 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 +# shutdown +# ! +# 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/4 +# ipv6 address fd5d:12c9:2201:1::1/64 +# shutdown +# ! + +- name: Override device configuration of all interfaces with provided configuration + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/3 + ipv4: 192.168.0.1/24 + 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/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/4 +# ipv6 address fd5d:12c9:2201:1::1/64 +# shutdown +# ! + +- name: Replaces device configuration of listed interfaces with provided configuration + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/3 + ipv6: fd5d:12c9:2201:1::1/64 + - name: GigabitEthernet0/0/0/4 + ipv4: 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/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 +# 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/4 +# ipv6 address fd5d:12c9:2201:1::1/64 +# shutdown +# ! + +- name: Delete attributes of given interfaces (Note: This won't delete the interface itself) + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/2 + - name: GigabitEthernet0/0/0/3 + 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/4 +# shutdown +# ! + +""" + +RETURN = """ +before: + description: The configuration prior to the model invocation + returned: always + 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 + 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: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.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_Interfaces.argument_spec, + supports_check_mode=True) + + result = L3_Interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() + From 7a8c77f2cba86a37372078bd7740f506913a5cb9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 03/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/__init__.py diff --git a/module_utils/__init__.py b/module_utils/__init__.py new file mode 100644 index 0000000..e69de29 From 39c4dd3447cad0892bff221965654b5c7accdb21 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 04/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/__init__.py diff --git a/module_utils/iosxr/__init__.py b/module_utils/iosxr/__init__.py new file mode 100644 index 0000000..e69de29 From 87631982bb693ae9d44f7598fe134f01e9eb5925 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 05/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/argspec/__init__.py diff --git a/module_utils/iosxr/argspec/__init__.py b/module_utils/iosxr/argspec/__init__.py new file mode 100644 index 0000000..e69de29 From 4b961da26893bc8cfa533d1e89e970eeca04abba Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 06/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/facts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/argspec/facts/__init__.py diff --git a/module_utils/iosxr/argspec/facts/__init__.py b/module_utils/iosxr/argspec/facts/__init__.py new file mode 100644 index 0000000..e69de29 From c9b8889d6ddccb1e6db65335de053cb728a7707a Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 07/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/facts/facts.py | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 module_utils/iosxr/argspec/facts/facts.py diff --git a/module_utils/iosxr/argspec/facts/facts.py b/module_utils/iosxr/argspec/facts/facts.py new file mode 100644 index 0000000..2f8b7dc --- /dev/null +++ b/module_utils/iosxr/argspec/facts/facts.py @@ -0,0 +1,25 @@ +#!/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) +""" +The arg spec for the iosxr facts module. +""" + +class FactsArgs(object): + """ The arg spec for the iosxr facts module + """ + + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'l3_interfaces', + ] + + argument_spec = { + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(default=['all'], choices=choices, type='list'), + } + From 8a4e3e2a65be4acf265053a59eef7fb4b9cdc955 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 08/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/l3_interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/argspec/l3_interfaces/__init__.py diff --git a/module_utils/iosxr/argspec/l3_interfaces/__init__.py b/module_utils/iosxr/argspec/l3_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From cef820e091380a0885ea53e2d758957dcb7e5b45 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 09/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- .../argspec/l3_interfaces/l3_interfaces.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py diff --git a/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..4b48659 --- /dev/null +++ b/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py @@ -0,0 +1,43 @@ +#!/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 arg spec for the iosxr_l3_interfaces module +""" + +class L3_InterfacesArgs(object): + + def __init__(self, **kwargs): + pass + + config_spec = { + 'name': dict(type='str', required=True), + 'ipv4':dict(type='str'), + 'ipv6':dict(type='str') + } + + argument_spec = { + 'state': dict(default='merged', choices=['merged', 'replaced', 'overridden', 'deleted']), + 'config': dict(type='list', elements='dict', options=config_spec) + } From 3d487e16ee7ba177fc309d500c199a727be05000 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 10/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/resource/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/argspec/resource/__init__.py diff --git a/module_utils/iosxr/argspec/resource/__init__.py b/module_utils/iosxr/argspec/resource/__init__.py new file mode 100644 index 0000000..e69de29 From 16853eb060ab002c2b6cbc4c295e8724236198bf Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 11/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- .../iosxr/argspec/resource/resource.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 module_utils/iosxr/argspec/resource/resource.py diff --git a/module_utils/iosxr/argspec/resource/resource.py b/module_utils/iosxr/argspec/resource/resource.py new file mode 100644 index 0000000..0734995 --- /dev/null +++ b/module_utils/iosxr/argspec/resource/resource.py @@ -0,0 +1,27 @@ +#!/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 arg spec for the ios_l3_interfaces module +""" From dd0ab2232e7df502af7f9dd397c085547b566a7b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 12/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/config/__init__.py diff --git a/module_utils/iosxr/config/__init__.py b/module_utils/iosxr/config/__init__.py new file mode 100644 index 0000000..e69de29 From c7fd8e1af2fb41c4c383e044b46b70708fc69339 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 13/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/base.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 module_utils/iosxr/config/base.py diff --git a/module_utils/iosxr/config/base.py b/module_utils/iosxr/config/base.py new file mode 100644 index 0000000..c8e3c51 --- /dev/null +++ b/module_utils/iosxr/config/base.py @@ -0,0 +1,25 @@ +#!/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) +""" +The base class for all iosxr resource modules +""" + +from ansible.module_utils.connection import Connection + + +class ConfigBase(object): + """ The base class for all iosxr resource modules + """ + _connection = None + + def __init__(self, module): + self._module = module + self._connection = self._get_connection() + + def _get_connection(self): + if self._connection: + return self._connection + self._connection = Connection(self._module._socket_path) #pylint: disable=W0212 + return self._connection From a46fb557fa1f6f142d34f018bf00cfd0ec6084ad Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 14/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/l3_interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/config/l3_interfaces/__init__.py diff --git a/module_utils/iosxr/config/l3_interfaces/__init__.py b/module_utils/iosxr/config/l3_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From 81fefafd516bc71b1315f284df207afd2e873457 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 15/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- .../config/l3_interfaces/l3_interfaces.py | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 module_utils/iosxr/config/l3_interfaces/l3_interfaces.py diff --git a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..aca6e30 --- /dev/null +++ b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py @@ -0,0 +1,259 @@ +#!/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) +""" +The ios_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 ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.six import iteritems + +from ansible.module_utils.iosxr.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs +from ansible.module_utils.iosxr.config.base import ConfigBase +from ansible.module_utils.iosxr.facts.facts import Facts + + +class L3_Interfaces(ConfigBase, L3_InterfacesArgs): + """ + The ios_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 + """ + result = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) + facts = result + 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': + kwargs = {'want': want, 'have': have, 'module': self._module} + commands = self._state_overridden(**kwargs) + elif state == 'deleted': + kwargs = {'want': want, 'have': have, 'module': self._module} + commands = self._state_deleted(**kwargs) + elif state == 'merged': + kwargs = {'want': want, 'have': have, 'module': self._module} + commands = self._state_merged(**kwargs) + elif state == 'replaced': + kwargs = {'want': want, 'have': have, 'module': self._module} + commands = self._state_replaced(**kwargs) + + return commands + + @staticmethod + def _state_replaced(**kwargs): + """ The command generator when state is replaced + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + module = kwargs['module'] + + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each, } + commands.extend(L3_Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} + commands.extend(L3_Interfaces.set_interface(**kwargs)) + + return commands + + @staticmethod + def _state_overridden(**kwargs): + """ The command generator when state is overridden + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + module = kwargs['module'] + + 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']) + kwargs = {'want': interface, 'have': each} + commands.extend(L3_Interfaces.clear_interface(**kwargs)) + continue + kwargs = {'want': interface, 'have': each} + commands.extend(L3_Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} + commands.extend(L3_Interfaces.set_interface(**kwargs)) + + return commands + + @staticmethod + def _state_merged(**kwargs): + """ The command generator when state is merged + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + module = kwargs['module'] + + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each, 'module': module} + commands.extend(L3_Interfaces.set_interface(**kwargs)) + + return commands + + @staticmethod + def _state_deleted(**kwargs): + """ The command generator when state is deleted + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + + 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']) + kwargs = {'want': interface, 'have': each} + commands.extend(L3_Interfaces.clear_interface(**kwargs)) + + return commands + + @staticmethod + def _remove_command_from_interface(interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + commands.append('no %s' % cmd) + return commands + + @staticmethod + def _add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + commands.append(cmd) + + @staticmethod + def set_interface(**kwargs): + # Set the interface config based on the want and have config + commands = [] + want = kwargs['want'] + have = kwargs['have'] + module = kwargs['module'] + clear_cmds = [] + if kwargs.get('commands'): + clear_cmds = kwargs['commands'] + + return commands + + @staticmethod + def clear_interface(**kwargs): + # Delete the interface config based on the want and have config + commands = [] + want = kwargs['want'] + have = kwargs['have'] + interface = 'interface ' + want['name'] + if 'ip address' in have: + L3_Interfaces._remove_command_from_interface(interface, 'no ip address', commands) + if 'ipv6 address' in have: + L3_Interfaces._remove_command_from_interface(interface, 'no ipv6 address', commands) + + return commands From 088da0047b8ad38c4175d595cacbf261043cac8b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 16/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/resource/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/config/resource/__init__.py diff --git a/module_utils/iosxr/config/resource/__init__.py b/module_utils/iosxr/config/resource/__init__.py new file mode 100644 index 0000000..e69de29 From e3bed4763f7a7b8981b5081f059ce012f695839e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 17/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/resource/resource.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/config/resource/resource.py diff --git a/module_utils/iosxr/config/resource/resource.py b/module_utils/iosxr/config/resource/resource.py new file mode 100644 index 0000000..e69de29 From f2e138dfd436f95d96fe9cfbd3b0c74bff53a1b9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 18/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/facts/__init__.py diff --git a/module_utils/iosxr/facts/__init__.py b/module_utils/iosxr/facts/__init__.py new file mode 100644 index 0000000..e69de29 From b7289b87bfb30857857146e469258d94782ace7c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 19/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/base.py | 106 +++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 module_utils/iosxr/facts/base.py diff --git a/module_utils/iosxr/facts/base.py b/module_utils/iosxr/facts/base.py new file mode 100644 index 0000000..aee42ff --- /dev/null +++ b/module_utils/iosxr/facts/base.py @@ -0,0 +1,106 @@ +#!/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) +""" +The iosxr facts base class +this contains methods common to all facts subsets +""" + +import re +from copy import deepcopy +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + """ + The iosxr facts base class + """ + generated_spec = {} + ansible_facts = {'ansible_network_resources': {}} + + def __init__(self, argspec, subspec=None, options=None): + spec = deepcopy(argspec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = self.generate_dict(facts_argument_spec) + + @staticmethod + def generate_dict(spec): + """ + Generate dictionary which is in sync with argspec + :param spec: A dictionary which the argspec of module + :rtype: A dictionary + :returns: A dictionary in sync with argspec with default value + """ + obj = {} + if not spec: + return obj + + for key, val in iteritems(spec): + if 'default' in val: + dct = {key: val['default']} + else: + dct = {key: None} + obj.update(dct) + + return obj + + @staticmethod + def parse_conf_arg(cfg, arg): + """ + Parse config based on argument + :param cfg: A text string which is a line of configuration. + :param arg: A text string which is to be matched. + :rtype: A text string + :returns: A text string if match is found + """ + match = re.search(r'%s (.+)(\n|$)' % arg, cfg, re.M) + if match: + result = match.group(1).strip() + else: + result = None + return result + + @staticmethod + def parse_conf_cmd_arg(cfg, cmd, res1, res2=None): + """ + Parse config based on command + :param cfg: A text string which is a line of configuration. + :param cmd: A text string which is the command to be matched + :param res1: A text string to be returned if the command is present + :param res2: A text string to be returned if the negate command is present + :rtype: A text string + :returns: A text string if match is found + """ + match = re.search(r'\n\s+%s(\n|$)' % cmd, cfg) + if match: + return res1 + if res2 is not None: + match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) + if match: + return res2 + return None + + @staticmethod + def generate_final_config(cfg_dict): + """ + Generate final config dictionary + :param cfg_dict: A dictionary parsed in the facts system + :rtype: A dictionary + :returns: A dictionary by eliminating keys that have null values + """ + final_cfg = {} + if not cfg_dict: + return final_cfg + + for key, val in iteritems(cfg_dict): + if val: + final_cfg.update({key:val}) + return final_cfg From 1a72d3b6eff81071a95a8ad61e369f81fd533478 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 20/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/facts.py | 119 ++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 module_utils/iosxr/facts/facts.py diff --git a/module_utils/iosxr/facts/facts.py b/module_utils/iosxr/facts/facts.py new file mode 100644 index 0000000..812a329 --- /dev/null +++ b/module_utils/iosxr/facts/facts.py @@ -0,0 +1,119 @@ +#!/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) +""" +The facts class for iosxr +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + + +from ansible.module_utils.six import iteritems + +from ansible.module_utils.iosxr.argspec.facts.facts import FactsArgs +from ansible.module_utils.iosxr.facts.base import FactsBase +from ansible.module_utils.iosxr.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs +from ansible.module_utils.iosxr.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts + + +FACT_SUBSETS = {} + + +class Facts(FactsArgs, FactsBase): + """ The fact class for iosxr + """ + + VALID_GATHER_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + def generate_runable_subsets(self, module, subsets, valid_subsets): + runable_subsets = set() + exclude_subsets = set() + minimal_gather_subset = frozenset(['default']) + + for subset in subsets: + if subset == 'all': + runable_subsets.update(valid_subsets) + continue + if subset == 'min' and minimal_gather_subset: + runable_subsets.update(minimal_gather_subset) + continue + if subset.startswith('!'): + subset = subset[1:] + if subset == 'min': + exclude_subsets.update(minimal_gather_subset) + continue + if subset == 'all': + exclude_subsets.update(valid_subsets - minimal_gather_subset) + continue + exclude = True + else: + exclude = False + + if subset not in valid_subsets: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(valid_subsets) + runable_subsets.difference_update(exclude_subsets) + + return runable_subsets + + def get_facts(self, module, connection, gather_subset=['!config'], gather_network_resources=['all']): + """ Collect the facts for iosxr + :param module: The module instance + :param connection: The device connection + :param gather_subset: The facts subset to collect + :param gather_network_resources: The resource subset to collect + :rtype: dict + :returns: the facts gathered + """ + warnings = [] + self.ansible_facts['gather_network_resources'] = list() + self.ansible_facts['gather_subset'] = list() + + valid_network_resources_subsets = self.argument_spec['gather_network_resources'].get('choices', []) + if valid_network_resources_subsets and 'all' in valid_network_resources_subsets: + valid_network_resources_subsets.remove('all') + + if valid_network_resources_subsets: + resources_runable_subsets = self.generate_runable_subsets(module, gather_network_resources, + valid_network_resources_subsets) + if resources_runable_subsets: + self.ansible_facts['gather_network_resources'] = list(resources_runable_subsets) + for attr in resources_runable_subsets: + getattr(self, '_get_%s' % attr, {})(module, connection) + if self.VALID_GATHER_SUBSETS: + runable_subsets = self.generate_runable_subsets(module, gather_subset, self.VALID_GATHER_SUBSETS) + + if runable_subsets: + facts = dict() + self.ansible_facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + warnings.extend(inst.warnings) + + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + self.ansible_facts[key] = value + + if warnings: + return self.ansible_facts, warnings + else: + return self.ansible_facts + + @staticmethod + def _get_l3_interfaces(module, connection): + return L3_interfacesFacts(L3_InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, + connection) From 8d3ad8cf3b381ad2ec5d426c6549668c3bacde47 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 21/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/l3_interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/facts/l3_interfaces/__init__.py diff --git a/module_utils/iosxr/facts/l3_interfaces/__init__.py b/module_utils/iosxr/facts/l3_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From 3a5447787be9c74a70fcc3deb61d82b1eace33a6 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 22/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/resource/resource.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 module_utils/iosxr/facts/resource/resource.py diff --git a/module_utils/iosxr/facts/resource/resource.py b/module_utils/iosxr/facts/resource/resource.py new file mode 100644 index 0000000..c42e76d --- /dev/null +++ b/module_utils/iosxr/facts/resource/resource.py @@ -0,0 +1,61 @@ +#!/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) +""" +The ios_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 copy import deepcopy +from ansible.module_utils.iosxr.facts.base import FactsBase + +class Facts(FactsBase): + """ The iosxr l3 interface fact class + """ + + def populate_facts(self, module, connection, data=None): + """ Populate the facts for iosxr l3 interface + :param module: the module instance + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if module: #just for linting purposes + pass + if connection: #just for linting purposes + pass + + if not data: + data = "foo" # connection.get('show running-config | section ^interface') + + # operate on a collection of resource x + config = [data] # data.split('interface ') + objs = [] + 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'] = objs + self.ansible_facts['net_configuration'].update(facts) + return self.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 + """ + if conf: + pass + config = deepcopy(spec) + # populate the facts from the configuration + config = {"some": "value"} + return self.generate_final_config(config) From fbb8d96c75bfd1c8488b7ffa31c5c4178d2803a9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 23/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/utils/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/utils/__init__.py diff --git a/module_utils/iosxr/utils/__init__.py b/module_utils/iosxr/utils/__init__.py new file mode 100644 index 0000000..e69de29 From d48e1533d79ab1731fd3479a36ac27922d330a84 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:55:44 +0530 Subject: [PATCH 24/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/utils/utils.py | 93 +++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 module_utils/iosxr/utils/utils.py diff --git a/module_utils/iosxr/utils/utils.py b/module_utils/iosxr/utils/utils.py new file mode 100644 index 0000000..aaceab2 --- /dev/null +++ b/module_utils/iosxr/utils/utils.py @@ -0,0 +1,93 @@ +#!/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) + +# utils + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + return None + + +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' From 6c09779735ff4bb3107be275cf8eae26c7e41a84 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:56:43 +0530 Subject: [PATCH 25/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- .../facts/l3_interfaces/l3_interfaces.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py diff --git a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..0be084b --- /dev/null +++ b/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py @@ -0,0 +1,73 @@ +#!/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) +""" +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. +""" + +import re +from copy import deepcopy + +from ansible.module_utils.iosxr.facts.base import FactsBase +from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface + + +class InterfacesFacts(FactsBase): + """ The iosxr l3 interfaces fact class + """ + + def populate_facts(self, module, connection, 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 | section ^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'] = objs + self.ansible_facts['net_configuration'].update(facts) + return self.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 get_interface_type(intf) == 'unknown': + return {} + # populate the facts from the configuration + config['name'] = normalize_interface(intf) + + ipv4 = re.search(r"ip address (\S+.)*", conf) + if ipv4: + config["ipv4"] = ipv4.group().split(' ') + ipv6 = re.search(r"ipv6 address (\S+)", conf) + if ipv6: + config["ipv6"] = ipv6.group(1) + + return self.generate_final_config(config) From 88c667c11f1e0d769a6c6ebfa33b8d400b0069c5 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 12:56:55 +0530 Subject: [PATCH 26/38] draft iosxr l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/resource/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/facts/resource/__init__.py diff --git a/module_utils/iosxr/facts/resource/__init__.py b/module_utils/iosxr/facts/resource/__init__.py new file mode 100644 index 0000000..e69de29 From 27fb4aa6a3c400afc99b28c8307ad24c3ad5cc79 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 6 Jun 2019 15:34:19 +0530 Subject: [PATCH 27/38] iosxr l3 interface update Signed-off-by: Sumit Jaiswal --- library/iosxr_l3_interface.py | 72 ++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/library/iosxr_l3_interface.py b/library/iosxr_l3_interface.py index 133e158..af7e728 100644 --- a/library/iosxr_l3_interface.py +++ b/library/iosxr_l3_interface.py @@ -63,13 +63,25 @@ - IPv4 address to be set for the Layer-3 interface mentioned in I(name) option. The address format is /, the mask is number in range 0-32 eg. 192.168.0.1/24 - type: str - ipv6: - description: - - IPv6 address to be set for the Layer-3 interface mentioned in I(name) option. - The address format is /, the mask is number in range 0-128 - eg. fd5d:12c9:2201:1::1/64 - type: str + 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 /, the mask is number in range 0-128 + eg. fd5d:12c9:2201:1::1/64 + suboptions: + address: + description: + - Configures the IPv6 address for Interface. + type: str state: choices: - merged @@ -100,6 +112,8 @@ # 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 @@ -109,9 +123,12 @@ iosxr_interfaces: config: - name: GigabitEthernet0/0/0/2 - ipv4: 192.168.0.1/24 + ipv4: + - address: 192.168.0.1/24 - name: GigabitEthernet0/0/0/3 - ipv4: 192.168.1.0/24 + ipv4: + - address: 192.168.2.1/24 + secondary: True operation: merged # After state: @@ -127,8 +144,11 @@ # ! # 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 @@ -151,6 +171,8 @@ # 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 @@ -160,7 +182,13 @@ iosxr_interfaces: config: - name: GigabitEthernet0/0/0/3 - ipv4: 192.168.0.1/24 + 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: @@ -177,6 +205,10 @@ # 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 # ! @@ -197,6 +229,9 @@ # 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 @@ -206,9 +241,11 @@ iosxr_interfaces: config: - name: GigabitEthernet0/0/0/3 - ipv6: fd5d:12c9:2201:1::1/64 + ipv6: + - address: fd5d:12c9:2201:1::1/64 - name: GigabitEthernet0/0/0/4 - ipv4: 192.168.0.2/24 + ipv4: + - address: 192.168.0.2/24 operation: replaced # After state: @@ -225,6 +262,9 @@ # 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 @@ -246,6 +286,9 @@ # 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 @@ -254,8 +297,9 @@ - name: Delete attributes of given interfaces (Note: This won't delete the interface itself) iosxr_interfaces: config: - - name: GigabitEthernet0/0/0/2 - name: GigabitEthernet0/0/0/3 + - name: GigabitEthernet0/0/0/4 + - name: GigabitEthernet0/0/0/3.700 operation: deleted # After state: @@ -271,6 +315,8 @@ # interface GigabitEthernet0/0/0/3 # shutdown # ! +# interface GigabitEthernet0/0/0/3.700 +# ! # interface GigabitEthernet0/0/0/4 # shutdown # ! From 91a3962b69bb89602175e60eba67f6928f731723 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 6 Jun 2019 15:34:19 +0530 Subject: [PATCH 28/38] iosxr l3 interface update Signed-off-by: Sumit Jaiswal --- .../iosxr/argspec/l3_interfaces/l3_interfaces.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py index 4b48659..c15641f 100644 --- a/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py +++ b/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py @@ -26,18 +26,25 @@ The arg spec for the iosxr_l3_interfaces module """ + class L3_InterfacesArgs(object): def __init__(self, **kwargs): pass + ipv4addr_spec = dict(address=dict(type=str), + secondary=dict(type=bool)) + + ipv6addr_spec = dict(address=dict(type=str)) + config_spec = { 'name': dict(type='str', required=True), - 'ipv4':dict(type='str'), - 'ipv6':dict(type='str') + 'ipv4': dict(type='list', elements='dict', options=ipv4addr_spec), + 'ipv6': dict(type='list', elements='dict', options=ipv6addr_spec) } argument_spec = { 'state': dict(default='merged', choices=['merged', 'replaced', 'overridden', 'deleted']), 'config': dict(type='list', elements='dict', options=config_spec) } + From dfca09f1567dac586a3744f3cb6537b1bc27e8c8 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 6 Jun 2019 15:34:19 +0530 Subject: [PATCH 29/38] iosxr l3 interface update Signed-off-by: Sumit Jaiswal --- .../config/l3_interfaces/l3_interfaces.py | 76 +++++++++++++++---- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py index aca6e30..69d754c 100644 --- a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py @@ -11,7 +11,7 @@ """ from ansible.module_utils.network.common.utils import to_list -from ansible.module_utils.six import iteritems +from ansible.module_utils.network.common.utils import is_netmask, is_masklen, to_netmask, to_masklen from ansible.module_utils.iosxr.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs from ansible.module_utils.iosxr.config.base import ConfigBase @@ -125,8 +125,6 @@ def _state_replaced(**kwargs): for each in have: if each['name'] == interface['name']: break - elif interface['name'] in each['name']: - break else: continue kwargs = {'want': interface, 'have': each, } @@ -152,8 +150,6 @@ def _state_overridden(**kwargs): 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. @@ -184,8 +180,6 @@ def _state_merged(**kwargs): for each in have: if each['name'] == interface['name']: break - elif interface['name'] in each['name']: - break else: continue kwargs = {'want': interface, 'have': each, 'module': module} @@ -208,8 +202,6 @@ def _state_deleted(**kwargs): for each in have: if each['name'] == interface['name']: break - elif interface['name'] in each['name']: - break else: continue interface = dict(name=interface['name']) @@ -231,6 +223,26 @@ def _add_command_to_interface(interface, cmd, commands): commands.insert(0, interface) commands.append(cmd) + @staticmethod + def validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format {}'.format(value)) + + if not is_masklen(address[1]): + module.fail_json(msg='invalid value for mask: {}, mask should be in range 0-32'.format(address[1])) + + @staticmethod + def validate_ipv6(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format {}'.format(value)) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json(msg='invalid value for mask: {}, mask should be in range 0-128'.format(address[1])) + @staticmethod def set_interface(**kwargs): # Set the interface config based on the want and have config @@ -241,6 +253,40 @@ def set_interface(**kwargs): clear_cmds = [] if kwargs.get('commands'): clear_cmds = kwargs['commands'] + interface = 'interface ' + want['name'] + + # To handle Interface L3 config if L2transport is configured + if have.get('l2transport'): + module.fail_json(msg='L3 configuration is not allowed under a L2 subinterface {}'.format(want['name'])) + elif 'l2transport' in want['name']: + module.fail_json(msg='L3 configuration is not allowed under a L2 subinterface {}'.format(want['name'])) + + # To handle L3 IPV4 configuration + ipv4 = want.get('ipv4') + if ipv4: + for each_ip in ipv4: + ip_addr = each_ip.get('address') + L3_Interfaces.validate_ipv4(ip_addr, module) + ip = ip_addr.split('/') + if len(ip) == 2: + ip_addr = '{0} {1}'.format(ip[0], to_netmask(ip[1])) + if each_ip.get('secondary'): + if ip_addr != have.get('secondary_ipv4') or 'no ip address' in clear_cmds: + cmd = 'ip address {} secondary'.format(ip_addr) + L3_Interfaces._add_command_to_interface(interface, cmd, commands) + elif have.get('ipv4') != ip_addr or 'no ip address' in clear_cmds: + cmd = 'ip address {}'.format(ip_addr) + L3_Interfaces._add_command_to_interface(interface, cmd, commands) + + # To handle L3 IPV6 configuration + ipv6 = want.get('ipv6') + if ipv6: + for each_ip in ipv6: + ipv6_addr = each_ip.get('address') + L3_Interfaces.validate_ipv6(ipv6_addr, module) + if have.get('ipv6') != ipv6_addr or 'no ipv6 address' in clear_cmds: + cmd = 'ipv6 address {}'.format(ipv6_addr) + L3_Interfaces._add_command_to_interface(interface, cmd, commands) return commands @@ -251,9 +297,13 @@ def clear_interface(**kwargs): want = kwargs['want'] have = kwargs['have'] interface = 'interface ' + want['name'] - if 'ip address' in have: - L3_Interfaces._remove_command_from_interface(interface, 'no ip address', commands) - if 'ipv6 address' in have: - L3_Interfaces._remove_command_from_interface(interface, 'no ipv6 address', commands) + + if have.get('ipv4') and not want.get('ipv4'): + L3_Interfaces._remove_command_from_interface(interface, 'ipv4 address', commands) + if have.get('ipv6') and not want.get('ipv6'): + L3_Interfaces._remove_command_from_interface(interface, 'ipv6 address', commands) + if have.get('secondary') and not want.get('secondary'): + cmd = 'ipv4 address {} secondary'.format(have.get('secondary_ipv4')) + L3_Interfaces._remove_command_from_interface(interface, cmd, commands) return commands From 9239b61d755c553453435e7095dbb24337e55d13 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 6 Jun 2019 15:34:19 +0530 Subject: [PATCH 30/38] iosxr l3 interface update Signed-off-by: Sumit Jaiswal --- .../facts/l3_interfaces/l3_interfaces.py | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py index 0be084b..53c48e9 100644 --- a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py +++ b/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py @@ -16,7 +16,7 @@ from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface -class InterfacesFacts(FactsBase): +class L3_interfacesFacts(FactsBase): """ The iosxr l3 interfaces fact class """ @@ -31,7 +31,7 @@ def populate_facts(self, module, connection, data=None): objs = [] if not data: - data = connection.get('show running-config | section ^interface') + data = connection.get('show running-config interface') # operate on a collection of resource x config = data.split('interface ') for conf in config: @@ -42,8 +42,8 @@ def populate_facts(self, module, connection, data=None): facts = {} if objs: - facts['interfaces'] = objs - self.ansible_facts['net_configuration'].update(facts) + facts['l3_interfaces'] = objs + self.ansible_facts['ansible_network_resources'].update(facts) return self.ansible_facts def render_config(self, spec, conf): @@ -63,11 +63,29 @@ def render_config(self, spec, conf): # populate the facts from the configuration config['name'] = normalize_interface(intf) - ipv4 = re.search(r"ip address (\S+.)*", conf) - if ipv4: - config["ipv4"] = ipv4.group().split(' ') - ipv6 = re.search(r"ipv6 address (\S+)", conf) - if ipv6: - config["ipv6"] = ipv6.group(1) + # Get the configured IPV4 details + ipv4 = re.findall(r"ipv4 address (\S+.*)", conf) + for each in ipv4: + if 'secondary' in each: + config['secondary'] = True + config['secondary_ipv4'] = each.split(' secondary')[0] + else: + config["ipv4"] = each + + # Get the configured IPV6 details + ipv6 = re.findall(r"ipv6 address (\S+)", conf) + for each in ipv6: + config["ipv6"] = each + + # To verify if L2transport is configured on the interface + l2transport = False + interface_name = re.search(r'^(\S+) (\S.*)', conf) + if interface_name: + if 'l2transport' in interface_name.group(): + l2transport = True + elif re.search(r"l2transport", conf): + l2transport = True + if l2transport: + config['l2transport'] = True return self.generate_final_config(config) From 9a4b0a655d6d1a8db8bc8085d0302ebf8c4241fc Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 14 Jun 2019 23:48:09 +0530 Subject: [PATCH 31/38] review comment Signed-off-by: Sumit Jaiswal --- library/iosxr_l3_interfaces.py | 361 +++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 library/iosxr_l3_interfaces.py diff --git a/library/iosxr_l3_interfaces.py b/library/iosxr_l3_interfaces.py new file mode 100644 index 0000000..af7e728 --- /dev/null +++ b/library/iosxr_l3_interfaces.py @@ -0,0 +1,361 @@ +#!/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 iosxr_l3_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'} + +NETWORK_OS = "iosxr" +RESOURCE = "l3_interfaces" +COPYRIGHT = "Copyright 2019 Red Hat" + +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) + 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 /, the mask is number in range 0-32 + eg. 192.168.0.1/24 + 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 /, the mask is number in range 0-128 + eg. fd5d:12c9:2201:1::1/64 + 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_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_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_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 +# 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: Delete attributes of given interfaces (Note: This won't delete the interface itself) + iosxr_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 +# 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 + 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 + 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: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.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_Interfaces.argument_spec, + supports_check_mode=True) + + result = L3_Interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() + From a600af2da045f92471532e6ce0c7055d33da14cf Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 14 Jun 2019 23:48:16 +0530 Subject: [PATCH 32/38] review comment Signed-off-by: Sumit Jaiswal --- library/iosxr_l3_interface.py | 361 ---------------------------------- 1 file changed, 361 deletions(-) delete mode 100644 library/iosxr_l3_interface.py diff --git a/library/iosxr_l3_interface.py b/library/iosxr_l3_interface.py deleted file mode 100644 index af7e728..0000000 --- a/library/iosxr_l3_interface.py +++ /dev/null @@ -1,361 +0,0 @@ -#!/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 iosxr_l3_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'} - -NETWORK_OS = "iosxr" -RESOURCE = "l3_interfaces" -COPYRIGHT = "Copyright 2019 Red Hat" - -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) - 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 /, the mask is number in range 0-32 - eg. 192.168.0.1/24 - 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 /, the mask is number in range 0-128 - eg. fd5d:12c9:2201:1::1/64 - 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_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_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_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 -# 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: Delete attributes of given interfaces (Note: This won't delete the interface itself) - iosxr_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 -# 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 - 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 - 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: ['command 1', 'command 2', 'command 3'] -""" - - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.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_Interfaces.argument_spec, - supports_check_mode=True) - - result = L3_Interfaces(module).execute_module() - module.exit_json(**result) - - -if __name__ == '__main__': - main() - From 52a466ce0975fb7e8fbb72093cec6d3738584e61 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Sun, 16 Jun 2019 17:45:56 +0530 Subject: [PATCH 33/38] iosxr l3 interface update Signed-off-by: Sumit Jaiswal --- library/iosxr_facts.py | 2 +- library/iosxr_l3_interfaces.py | 8 +- .../iosxr/argspec/resource/resource.py | 2 +- .../config/l3_interfaces/l3_interfaces.py | 97 ++++++++++++------- .../facts/l3_interfaces/l3_interfaces.py | 29 ++++-- .../iosxr_l3_interface/default/main.yaml | 2 + .../targets/iosxr_l3_interface/meta/main.yaml | 2 + .../targets/iosxr_l3_interface/tasks/cli.yaml | 22 +++++ .../iosxr_l3_interface/tasks/main.yaml | 2 + .../iosxr_l3_interface/test/cli/deleted.yaml | 30 ++++++ .../iosxr_l3_interface/test/cli/merged.yaml | 42 ++++++++ .../test/cli/overridden.yaml | 30 ++++++ .../iosxr_l3_interface/test/cli/replaced.yaml | 39 ++++++++ .../test/cli/reset_config.yaml | 31 ++++++ 14 files changed, 286 insertions(+), 52 deletions(-) create mode 100644 tests/integration/targets/iosxr_l3_interface/default/main.yaml create mode 100644 tests/integration/targets/iosxr_l3_interface/meta/main.yaml create mode 100644 tests/integration/targets/iosxr_l3_interface/tasks/cli.yaml create mode 100644 tests/integration/targets/iosxr_l3_interface/tasks/main.yaml create mode 100644 tests/integration/targets/iosxr_l3_interface/test/cli/deleted.yaml create mode 100644 tests/integration/targets/iosxr_l3_interface/test/cli/merged.yaml create mode 100644 tests/integration/targets/iosxr_l3_interface/test/cli/overridden.yaml create mode 100644 tests/integration/targets/iosxr_l3_interface/test/cli/replaced.yaml create mode 100644 tests/integration/targets/iosxr_l3_interface/test/cli/reset_config.yaml diff --git a/library/iosxr_facts.py b/library/iosxr_facts.py index 3e85675..afb924e 100644 --- a/library/iosxr_facts.py +++ b/library/iosxr_facts.py @@ -29,7 +29,7 @@ collection of additional facts. author: [u'Sumit Jaiswal (@justjais)'] notes: - - Tested against iosv Version 6.1.3 on VIRL + - Tested against IOSXRv Version 6.1.3 on VIRL options: gather_subset: description: diff --git a/library/iosxr_l3_interfaces.py b/library/iosxr_l3_interfaces.py index af7e728..8e70e3d 100644 --- a/library/iosxr_l3_interfaces.py +++ b/library/iosxr_l3_interfaces.py @@ -120,7 +120,7 @@ # ! - name: Merge provided configuration with device configuration - iosxr_interfaces: + iosxr_l3_interfaces: config: - name: GigabitEthernet0/0/0/2 ipv4: @@ -179,7 +179,7 @@ # ! - name: Override device configuration of all interfaces with provided configuration - iosxr_interfaces: + iosxr_l3_interfaces: config: - name: GigabitEthernet0/0/0/3 ipv4: @@ -238,7 +238,7 @@ # ! - name: Replaces device configuration of listed interfaces with provided configuration - iosxr_interfaces: + iosxr_l3_interfaces: config: - name: GigabitEthernet0/0/0/3 ipv6: @@ -295,7 +295,7 @@ # ! - name: Delete attributes of given interfaces (Note: This won't delete the interface itself) - iosxr_interfaces: + iosxr_l3_interfaces: config: - name: GigabitEthernet0/0/0/3 - name: GigabitEthernet0/0/0/4 diff --git a/module_utils/iosxr/argspec/resource/resource.py b/module_utils/iosxr/argspec/resource/resource.py index 0734995..7403671 100644 --- a/module_utils/iosxr/argspec/resource/resource.py +++ b/module_utils/iosxr/argspec/resource/resource.py @@ -23,5 +23,5 @@ ############################################## ############################################## """ -The arg spec for the ios_l3_interfaces module +The arg spec for the iosxr_l3_interfaces module """ diff --git a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py index 69d754c..643862d 100644 --- a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py @@ -3,7 +3,7 @@ # Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ -The ios_l3_interfaces class +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 @@ -20,7 +20,7 @@ class L3_Interfaces(ConfigBase, L3_InterfacesArgs): """ - The ios_l3_interfaces class + The iosxr_l3_interfaces class """ gather_subset = [ @@ -127,11 +127,12 @@ def _state_replaced(**kwargs): break else: continue - kwargs = {'want': interface, 'have': each, } + kwargs = {'want': interface, 'have': each} commands.extend(L3_Interfaces.clear_interface(**kwargs)) kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} commands.extend(L3_Interfaces.set_interface(**kwargs)) + commands = L3_Interfaces._remove_command_from_interface(commands) return commands @staticmethod @@ -162,6 +163,7 @@ def _state_overridden(**kwargs): kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} commands.extend(L3_Interfaces.set_interface(**kwargs)) + commands = L3_Interfaces._remove_duplicate_interface(commands) return commands @staticmethod @@ -243,6 +245,31 @@ def validate_ipv6(value, module): if not 0 <= int(address[1]) <= 128: module.fail_json(msg='invalid value for mask: {}, mask should be in range 0-128'.format(address[1])) + @staticmethod + 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') + L3_Interfaces.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 + + @staticmethod + def _remove_duplicate_interface(commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if 'interface' in each: + interface = each + if interface not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + @staticmethod def set_interface(**kwargs): # Set the interface config based on the want and have config @@ -250,9 +277,6 @@ def set_interface(**kwargs): want = kwargs['want'] have = kwargs['have'] module = kwargs['module'] - clear_cmds = [] - if kwargs.get('commands'): - clear_cmds = kwargs['commands'] interface = 'interface ' + want['name'] # To handle Interface L3 config if L2transport is configured @@ -261,32 +285,29 @@ def set_interface(**kwargs): elif 'l2transport' in want['name']: module.fail_json(msg='L3 configuration is not allowed under a L2 subinterface {}'.format(want['name'])) - # To handle L3 IPV4 configuration - ipv4 = want.get('ipv4') - if ipv4: - for each_ip in ipv4: - ip_addr = each_ip.get('address') - L3_Interfaces.validate_ipv4(ip_addr, module) - ip = ip_addr.split('/') - if len(ip) == 2: - ip_addr = '{0} {1}'.format(ip[0], to_netmask(ip[1])) - if each_ip.get('secondary'): - if ip_addr != have.get('secondary_ipv4') or 'no ip address' in clear_cmds: - cmd = 'ip address {} secondary'.format(ip_addr) - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - elif have.get('ipv4') != ip_addr or 'no ip address' in clear_cmds: - cmd = 'ip address {}'.format(ip_addr) - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - - # To handle L3 IPV6 configuration - ipv6 = want.get('ipv6') - if ipv6: - for each_ip in ipv6: - ipv6_addr = each_ip.get('address') - L3_Interfaces.validate_ipv6(ipv6_addr, module) - if have.get('ipv6') != ipv6_addr or 'no ipv6 address' in clear_cmds: - cmd = 'ipv6 address {}'.format(ipv6_addr) - L3_Interfaces._add_command_to_interface(interface, cmd, commands) + if want.get("ipv4"): + for each in want.get("ipv4"): + if each.get('address'): + ip_addr_want = L3_Interfaces.validate_n_expand_ipv4(module, each) + each['address'] = ip_addr_want + + want_ipv4 = set(tuple(address.items()) for address in want.get("ipv4") or []) + have_ipv4 = set(tuple(address.items()) for address in have.get("ipv4") or []) + + diff = want_ipv4 - have_ipv4 + for address in diff: + address = dict(address) + cmd = "ip address {}".format(address["address"]) + if address["secondary"]: + cmd += " secondary" + L3_Interfaces._add_command_to_interface(interface, cmd, commands) + + want_ipv6 = set(tuple(address.items()) for address in want.get("ipv6") or []) + have_ipv6 = set(tuple(address.items()) for address in have.get("ipv6") or []) + for address in want_ipv6 - have_ipv6: + address = dict(address) + cmd = "ipv6 address {}".format(address["address"]) + L3_Interfaces._add_command_to_interface(interface, cmd, commands) return commands @@ -298,12 +319,14 @@ def clear_interface(**kwargs): have = kwargs['have'] interface = 'interface ' + want['name'] - if have.get('ipv4') and not want.get('ipv4'): + if have.get('ipv4') and want.get('ipv4'): + for each in have.get('ipv4'): + if each.get('secondary') and not (want.get('ipv4')[0].get('secondary')): + cmd = 'ipv4 address {} secondary'.format(each.get('address')) + L3_Interfaces._remove_command_from_interface(interface, cmd, commands) + if have.get('ipv4') and not (want.get('ipv4')): L3_Interfaces._remove_command_from_interface(interface, 'ipv4 address', commands) - if have.get('ipv6') and not want.get('ipv6'): + if have.get('ipv6') and not (want.get('ipv6')): L3_Interfaces._remove_command_from_interface(interface, 'ipv6 address', commands) - if have.get('secondary') and not want.get('secondary'): - cmd = 'ipv4 address {} secondary'.format(have.get('secondary_ipv4')) - L3_Interfaces._remove_command_from_interface(interface, cmd, commands) return commands diff --git a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py index 53c48e9..5babce9 100644 --- a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py +++ b/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py @@ -56,26 +56,37 @@ def render_config(self, spec, conf): """ config = deepcopy(spec) match = re.search(r'^(\S+)', conf) - intf = match.group(1) + 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) # Get the configured IPV4 details - ipv4 = re.findall(r"ipv4 address (\S+.*)", conf) - for each in ipv4: + ipv4 = [] + ipv4_all = re.findall(r"ipv4 address (\S+.*)", conf) + for each in ipv4_all: + each_ipv4 = dict() if 'secondary' in each: - config['secondary'] = True - config['secondary_ipv4'] = each.split(' secondary')[0] + each_ipv4['address'] = each.split(' secondary')[0] + each_ipv4['secondary'] = True else: - config["ipv4"] = each + each_ipv4['address'] = each + each_ipv4['secondary'] = None + ipv4.append(each_ipv4) + config['ipv4'] = ipv4 # Get the configured IPV6 details - ipv6 = re.findall(r"ipv6 address (\S+)", conf) - for each in ipv6: - config["ipv6"] = each + 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 # To verify if L2transport is configured on the interface l2transport = False diff --git a/tests/integration/targets/iosxr_l3_interface/default/main.yaml b/tests/integration/targets/iosxr_l3_interface/default/main.yaml new file mode 100644 index 0000000..55a93fc --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/default/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" \ No newline at end of file diff --git a/tests/integration/targets/iosxr_l3_interface/meta/main.yaml b/tests/integration/targets/iosxr_l3_interface/meta/main.yaml new file mode 100644 index 0000000..87f87c4 --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/meta/main.yaml @@ -0,0 +1,2 @@ +dependencies: + - prepare_iosxr_tests \ No newline at end of file diff --git a/tests/integration/targets/iosxr_l3_interface/tasks/cli.yaml b/tests/integration/targets/iosxr_l3_interface/tasks/cli.yaml new file mode 100644 index 0000000..303af40 --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/tasks/cli.yaml @@ -0,0 +1,22 @@ +--- +- name: collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + register: test_cases + delegate_to: localhost + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test cases (connection=network_cli) + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run + +- name: run test case (connection=local) + include: "{{ test_case_to_run }} ansible_connection=local" + with_first_found: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/iosxr_l3_interface/tasks/main.yaml b/tests/integration/targets/iosxr_l3_interface/tasks/main.yaml new file mode 100644 index 0000000..d4898c2 --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } \ No newline at end of file diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/deleted.yaml b/tests/integration/targets/iosxr_l3_interface/test/cli/deleted.yaml new file mode 100644 index 0000000..c50385a --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/test/cli/deleted.yaml @@ -0,0 +1,30 @@ +--- +- debug: + msg: "START iosxr_l3_interfaces Deleted integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- include_tasks: reset_config.yaml + +- name: Delete attributes of given interfaces (Note: This won't delete the interface itself) + ios_l3_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + - name: GigabitEthernet0/0/0/2 + - name: GigabitEthernet0/0/0/3 + state: deleted + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/1 + - name: GigabitEthernet0/0/0/2 + - name: GigabitEthernet0/0/0/3 + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" + +- include_tasks: reset_config.yaml \ No newline at end of file diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/merged.yaml b/tests/integration/targets/iosxr_l3_interface/test/cli/merged.yaml new file mode 100644 index 0000000..fecbe6a --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/test/cli/merged.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: "START iosxr_l3_interfaces Merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Merge provided configuration with device configuration + iosxr_l3_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + ipv4: + - address: 192.168.0.1/24 + - name: GigabitEthernet0/0/0/2 + ipv4: + - address: 192.168.2.1/24 + secondary: True + - name: GigabitEthernet0/0/0/3 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + state: merged + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/1 + ipv4: + - address: 192.168.0.1 255.255.255.0 + - name: GigabitEthernet0/0/0/2 + ipv4: + - address: 192.168.2.1 255.255.255.0 + secondary: true + - name: GigabitEthernet0/0/0/3 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" + +- include_tasks: reset_config.yaml \ No newline at end of file diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/overridden.yaml b/tests/integration/targets/iosxr_l3_interface/test/cli/overridden.yaml new file mode 100644 index 0000000..2ae36ed --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/test/cli/overridden.yaml @@ -0,0 +1,30 @@ +--- +- debug: + msg: "START iosxr_l3_interfaces Overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Override device configuration of all interfaces with provided configuration + iosxr_l3_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + ipv4: + - address: 192.168.0.1/24 + state: overridden + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/1 + ipv4: + - address: 192.168.0.1 255.255.255.0 + - name: GigabitEthernet0/0/0/2 + - name: GigabitEthernet0/0/0/3 + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" + +- include_tasks: reset_config.yaml \ No newline at end of file diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/replaced.yaml b/tests/integration/targets/iosxr_l3_interface/test/cli/replaced.yaml new file mode 100644 index 0000000..62189dd --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/test/cli/replaced.yaml @@ -0,0 +1,39 @@ +--- +- debug: + msg: "START iosxr_l3_interfaces Replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Replaces device configuration of listed interfaces with provided configuration + iosxr_l3_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + ipv4: + - address: 192.168.3.1/24 + secondary: True + state: replaced + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/1 + ipv4: + - address: 192.168.0.1 255.255.255.0 + - address: 192.168.3.1 255.255.255.0 + secondary: True + - name: GigabitEthernet0/0/0/2 + ipv4: + - address: 192.168.2.1 255.255.255.0 + secondary: true + - name: GigabitEthernet0/0/0/3 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" + +- include_tasks: reset_config.yaml \ No newline at end of file diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/reset_config.yaml b/tests/integration/targets/iosxr_l3_interface/test/cli/reset_config.yaml new file mode 100644 index 0000000..63ca402 --- /dev/null +++ b/tests/integration/targets/iosxr_l3_interface/test/cli/reset_config.yaml @@ -0,0 +1,31 @@ +--- +- name: Reset initial config + cli_config: + config: | + interface GigabitEthernet0/0/0/3 + ipv4 address 192.168.0.1 255.255.255.0 + interface GigabitEthernet0/0/0/3 + ipv4 address 192.168.2.1 255.255.255.0 + ipv4 address 192.168.3.1 255.255.255.0 secondary + interface GigabitEthernet0/0/0/3 + ipv6 address fd5d:12c9:2201:1::1/64 +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/3 + ipv4: + - address: 192.168.0.1/24 + - name: GigabitEthernet0/0/0/3 + ipv4: + - address: 192.168.2.1/24 + - address: 192.168.3.1/24 + secondary: True + - name: GigabitEthernet0/0/0/3 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" \ No newline at end of file From 4202b4e6c393e81e5e178b8b23fd0e01a6c46ef0 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 18 Jun 2019 12:27:24 +0530 Subject: [PATCH 34/38] bug fix Signed-off-by: Sumit Jaiswal --- .../iosxr/config/l3_interfaces/l3_interfaces.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py index 643862d..93633df 100644 --- a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py @@ -131,8 +131,9 @@ def _state_replaced(**kwargs): commands.extend(L3_Interfaces.clear_interface(**kwargs)) kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} commands.extend(L3_Interfaces.set_interface(**kwargs)) + # Remove the duplicate interface call + commands = L3_Interfaces._remove_duplicate_interface(commands) - commands = L3_Interfaces._remove_command_from_interface(commands) return commands @staticmethod @@ -162,8 +163,9 @@ def _state_overridden(**kwargs): commands.extend(L3_Interfaces.clear_interface(**kwargs)) kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} commands.extend(L3_Interfaces.set_interface(**kwargs)) - + # Remove the duplicate interface call commands = L3_Interfaces._remove_duplicate_interface(commands) + return commands @staticmethod @@ -293,7 +295,6 @@ def set_interface(**kwargs): want_ipv4 = set(tuple(address.items()) for address in want.get("ipv4") or []) have_ipv4 = set(tuple(address.items()) for address in have.get("ipv4") or []) - diff = want_ipv4 - have_ipv4 for address in diff: address = dict(address) @@ -304,7 +305,8 @@ def set_interface(**kwargs): want_ipv6 = set(tuple(address.items()) for address in want.get("ipv6") or []) have_ipv6 = set(tuple(address.items()) for address in have.get("ipv6") or []) - for address in want_ipv6 - have_ipv6: + diff = want_ipv6 - have_ipv6 + for address in diff: address = dict(address) cmd = "ipv6 address {}".format(address["address"]) L3_Interfaces._add_command_to_interface(interface, cmd, commands) @@ -314,6 +316,7 @@ def set_interface(**kwargs): @staticmethod def clear_interface(**kwargs): # Delete the interface config based on the want and have config + count = 0 commands = [] want = kwargs['want'] have = kwargs['have'] @@ -321,9 +324,10 @@ def clear_interface(**kwargs): if have.get('ipv4') and want.get('ipv4'): for each in have.get('ipv4'): - if each.get('secondary') and not (want.get('ipv4')[0].get('secondary')): + if each.get('secondary') and not (want.get('ipv4')[count].get('secondary')): cmd = 'ipv4 address {} secondary'.format(each.get('address')) L3_Interfaces._remove_command_from_interface(interface, cmd, commands) + count += 1 if have.get('ipv4') and not (want.get('ipv4')): L3_Interfaces._remove_command_from_interface(interface, 'ipv4 address', commands) if have.get('ipv6') and not (want.get('ipv6')): From 6d44d0831efc840645636580e6ebbce8c7bafb3a Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 18 Jun 2019 17:07:51 +0530 Subject: [PATCH 35/38] iosxr l3 minor update Signed-off-by: Sumit Jaiswal --- .../targets/iosxr_l3_interface/default/main.yaml | 2 +- .../targets/iosxr_l3_interface/meta/main.yaml | 2 +- .../targets/iosxr_l3_interface/tasks/cli.yaml | 10 ++-------- .../targets/iosxr_l3_interface/tasks/main.yaml | 2 +- .../{test => tests}/cli/deleted.yaml | 2 -- .../iosxr_l3_interface/{test => tests}/cli/merged.yaml | 2 +- .../{test => tests}/cli/overridden.yaml | 2 +- .../{test => tests}/cli/replaced.yaml | 2 +- .../{test => tests}/cli/reset_config.yaml | 10 +++++----- 9 files changed, 13 insertions(+), 21 deletions(-) rename tests/integration/targets/iosxr_l3_interface/{test => tests}/cli/deleted.yaml (95%) rename tests/integration/targets/iosxr_l3_interface/{test => tests}/cli/merged.yaml (96%) rename tests/integration/targets/iosxr_l3_interface/{test => tests}/cli/overridden.yaml (95%) rename tests/integration/targets/iosxr_l3_interface/{test => tests}/cli/replaced.yaml (96%) rename tests/integration/targets/iosxr_l3_interface/{test => tests}/cli/reset_config.yaml (81%) diff --git a/tests/integration/targets/iosxr_l3_interface/default/main.yaml b/tests/integration/targets/iosxr_l3_interface/default/main.yaml index 55a93fc..5f709c5 100644 --- a/tests/integration/targets/iosxr_l3_interface/default/main.yaml +++ b/tests/integration/targets/iosxr_l3_interface/default/main.yaml @@ -1,2 +1,2 @@ --- -testcase: "*" \ No newline at end of file +testcase: "*" diff --git a/tests/integration/targets/iosxr_l3_interface/meta/main.yaml b/tests/integration/targets/iosxr_l3_interface/meta/main.yaml index 87f87c4..d4da833 100644 --- a/tests/integration/targets/iosxr_l3_interface/meta/main.yaml +++ b/tests/integration/targets/iosxr_l3_interface/meta/main.yaml @@ -1,2 +1,2 @@ dependencies: - - prepare_iosxr_tests \ No newline at end of file + - prepare_iosxr_tests diff --git a/tests/integration/targets/iosxr_l3_interface/tasks/cli.yaml b/tests/integration/targets/iosxr_l3_interface/tasks/cli.yaml index 303af40..8ca9225 100644 --- a/tests/integration/targets/iosxr_l3_interface/tasks/cli.yaml +++ b/tests/integration/targets/iosxr_l3_interface/tasks/cli.yaml @@ -1,5 +1,5 @@ --- -- name: collect all cli test cases +- name: collect all cli tests cases find: paths: "{{ role_path }}/tests/cli" patterns: "{{ testcase }}.yaml" @@ -9,14 +9,8 @@ - name: set test_items set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" -- name: run test cases (connection=network_cli) +- name: run tests cases (connection=network_cli) include: "{{ test_case_to_run }}" with_items: "{{ test_items }}" loop_control: loop_var: test_case_to_run - -- name: run test case (connection=local) - include: "{{ test_case_to_run }} ansible_connection=local" - with_first_found: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/tests/integration/targets/iosxr_l3_interface/tasks/main.yaml b/tests/integration/targets/iosxr_l3_interface/tasks/main.yaml index d4898c2..415c99d 100644 --- a/tests/integration/targets/iosxr_l3_interface/tasks/main.yaml +++ b/tests/integration/targets/iosxr_l3_interface/tasks/main.yaml @@ -1,2 +1,2 @@ --- -- { include: cli.yaml, tags: ['cli'] } \ No newline at end of file +- { include: cli.yaml, tags: ['cli'] } diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/deleted.yaml b/tests/integration/targets/iosxr_l3_interface/tests/cli/deleted.yaml similarity index 95% rename from tests/integration/targets/iosxr_l3_interface/test/cli/deleted.yaml rename to tests/integration/targets/iosxr_l3_interface/tests/cli/deleted.yaml index c50385a..1f735fe 100644 --- a/tests/integration/targets/iosxr_l3_interface/test/cli/deleted.yaml +++ b/tests/integration/targets/iosxr_l3_interface/tests/cli/deleted.yaml @@ -4,8 +4,6 @@ - include_tasks: reset_config.yaml -- include_tasks: reset_config.yaml - - name: Delete attributes of given interfaces (Note: This won't delete the interface itself) ios_l3_interfaces: config: diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/merged.yaml b/tests/integration/targets/iosxr_l3_interface/tests/cli/merged.yaml similarity index 96% rename from tests/integration/targets/iosxr_l3_interface/test/cli/merged.yaml rename to tests/integration/targets/iosxr_l3_interface/tests/cli/merged.yaml index fecbe6a..03aa0fc 100644 --- a/tests/integration/targets/iosxr_l3_interface/test/cli/merged.yaml +++ b/tests/integration/targets/iosxr_l3_interface/tests/cli/merged.yaml @@ -39,4 +39,4 @@ that: - "ansible_facts.net_configuration.l3_interfaces == expected_output" -- include_tasks: reset_config.yaml \ No newline at end of file +- include_tasks: reset_config.yaml diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/overridden.yaml b/tests/integration/targets/iosxr_l3_interface/tests/cli/overridden.yaml similarity index 95% rename from tests/integration/targets/iosxr_l3_interface/test/cli/overridden.yaml rename to tests/integration/targets/iosxr_l3_interface/tests/cli/overridden.yaml index 2ae36ed..d8e25c0 100644 --- a/tests/integration/targets/iosxr_l3_interface/test/cli/overridden.yaml +++ b/tests/integration/targets/iosxr_l3_interface/tests/cli/overridden.yaml @@ -27,4 +27,4 @@ that: - "ansible_facts.net_configuration.l3_interfaces == expected_output" -- include_tasks: reset_config.yaml \ No newline at end of file +- include_tasks: reset_config.yaml diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/replaced.yaml b/tests/integration/targets/iosxr_l3_interface/tests/cli/replaced.yaml similarity index 96% rename from tests/integration/targets/iosxr_l3_interface/test/cli/replaced.yaml rename to tests/integration/targets/iosxr_l3_interface/tests/cli/replaced.yaml index 62189dd..f03deac 100644 --- a/tests/integration/targets/iosxr_l3_interface/test/cli/replaced.yaml +++ b/tests/integration/targets/iosxr_l3_interface/tests/cli/replaced.yaml @@ -36,4 +36,4 @@ that: - "ansible_facts.net_configuration.l3_interfaces == expected_output" -- include_tasks: reset_config.yaml \ No newline at end of file +- include_tasks: reset_config.yaml diff --git a/tests/integration/targets/iosxr_l3_interface/test/cli/reset_config.yaml b/tests/integration/targets/iosxr_l3_interface/tests/cli/reset_config.yaml similarity index 81% rename from tests/integration/targets/iosxr_l3_interface/test/cli/reset_config.yaml rename to tests/integration/targets/iosxr_l3_interface/tests/cli/reset_config.yaml index 63ca402..5faffe7 100644 --- a/tests/integration/targets/iosxr_l3_interface/test/cli/reset_config.yaml +++ b/tests/integration/targets/iosxr_l3_interface/tests/cli/reset_config.yaml @@ -2,9 +2,9 @@ - name: Reset initial config cli_config: config: | - interface GigabitEthernet0/0/0/3 + interface GigabitEthernet0/0/0/1 ipv4 address 192.168.0.1 255.255.255.0 - interface GigabitEthernet0/0/0/3 + interface GigabitEthernet0/0/0/2 ipv4 address 192.168.2.1 255.255.255.0 ipv4 address 192.168.3.1 255.255.255.0 secondary interface GigabitEthernet0/0/0/3 @@ -14,10 +14,10 @@ - set_fact: expected_output: - - name: GigabitEthernet0/0/0/3 + - name: GigabitEthernet0/0/0/1 ipv4: - address: 192.168.0.1/24 - - name: GigabitEthernet0/0/0/3 + - name: GigabitEthernet0/0/0/2 ipv4: - address: 192.168.2.1/24 - address: 192.168.3.1/24 @@ -28,4 +28,4 @@ - assert: that: - - "ansible_facts.net_configuration.l3_interfaces == expected_output" \ No newline at end of file + - "ansible_facts.net_configuration.l3_interfaces == expected_output" From b7cd38788bb70b309b93376d9d808708193b7fc7 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 28 Jun 2019 14:33:55 +0530 Subject: [PATCH 36/38] secondary default set Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py index c15641f..2d8acc9 100644 --- a/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py +++ b/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py @@ -33,7 +33,7 @@ def __init__(self, **kwargs): pass ipv4addr_spec = dict(address=dict(type=str), - secondary=dict(type=bool)) + secondary=dict(type=bool, default=False)) ipv6addr_spec = dict(address=dict(type=str)) From 68fca47ce4864a9de7eb886c836ee66d13a8c9ff Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 28 Jun 2019 14:34:05 +0530 Subject: [PATCH 37/38] secondary default set Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py index 5babce9..e8322b5 100644 --- a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py +++ b/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py @@ -75,7 +75,7 @@ def render_config(self, spec, conf): each_ipv4['secondary'] = True else: each_ipv4['address'] = each - each_ipv4['secondary'] = None + each_ipv4['secondary'] = False ipv4.append(each_ipv4) config['ipv4'] = ipv4 From dddb9c98d5c02d3a92c414a727f5d734a605b651 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 13 Aug 2019 16:30:24 +0530 Subject: [PATCH 38/38] fix iosxr l3 --- library/iosxr_l3_interfaces.py | 107 ++++-- .../argspec/l3_interfaces/l3_interfaces.py | 50 --- .../iosxr/argspec/resource/resource.py | 27 -- module_utils/iosxr/config/base.py | 25 -- .../config/l3_interfaces/l3_interfaces.py | 336 ------------------ .../iosxr/config/resource/resource.py | 0 module_utils/iosxr/facts/base.py | 106 ------ module_utils/iosxr/facts/facts.py | 119 ------- .../iosxr/facts/l3_interfaces/__init__.py | 0 module_utils/iosxr/facts/resource/__init__.py | 0 module_utils/iosxr/facts/resource/resource.py | 61 ---- module_utils/iosxr/utils/__init__.py | 0 module_utils/iosxr/utils/utils.py | 93 ----- module_utils/{ => network}/iosxr/__init__.py | 0 .../{ => network}/iosxr/argspec/__init__.py | 0 .../iosxr/argspec/facts/__init__.py | 0 .../iosxr/argspec/facts/facts.py | 7 + .../iosxr/argspec/l3_interfaces/__init__.py | 0 .../argspec/l3_interfaces/l3_interfaces.py | 51 +++ .../iosxr/config}/__init__.py | 0 .../iosxr/config/l3_interfaces}/__init__.py | 0 .../config/l3_interfaces/l3_interfaces.py | 276 ++++++++++++++ .../iosxr/facts}/__init__.py | 0 module_utils/network/iosxr/facts/facts.py | 59 +++ .../iosxr/facts/l3_interfaces}/__init__.py | 0 .../facts/l3_interfaces/l3_interfaces.py | 67 ++-- .../facts => network/iosxr/utils}/__init__.py | 0 module_utils/network/iosxr/utils/utils.py | 194 ++++++++++ 28 files changed, 705 insertions(+), 873 deletions(-) delete mode 100644 module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py delete mode 100644 module_utils/iosxr/argspec/resource/resource.py delete mode 100644 module_utils/iosxr/config/base.py delete mode 100644 module_utils/iosxr/config/l3_interfaces/l3_interfaces.py delete mode 100644 module_utils/iosxr/config/resource/resource.py delete mode 100644 module_utils/iosxr/facts/base.py delete mode 100644 module_utils/iosxr/facts/facts.py delete mode 100644 module_utils/iosxr/facts/l3_interfaces/__init__.py delete mode 100644 module_utils/iosxr/facts/resource/__init__.py delete mode 100644 module_utils/iosxr/facts/resource/resource.py delete mode 100644 module_utils/iosxr/utils/__init__.py delete mode 100644 module_utils/iosxr/utils/utils.py rename module_utils/{ => network}/iosxr/__init__.py (100%) rename module_utils/{ => network}/iosxr/argspec/__init__.py (100%) rename module_utils/{ => network}/iosxr/argspec/facts/__init__.py (100%) rename module_utils/{ => network}/iosxr/argspec/facts/facts.py (78%) rename module_utils/{ => network}/iosxr/argspec/l3_interfaces/__init__.py (100%) create mode 100644 module_utils/network/iosxr/argspec/l3_interfaces/l3_interfaces.py rename module_utils/{iosxr/argspec/resource => network/iosxr/config}/__init__.py (100%) rename module_utils/{iosxr/config => network/iosxr/config/l3_interfaces}/__init__.py (100%) create mode 100644 module_utils/network/iosxr/config/l3_interfaces/l3_interfaces.py rename module_utils/{iosxr/config/l3_interfaces => network/iosxr/facts}/__init__.py (100%) create mode 100644 module_utils/network/iosxr/facts/facts.py rename module_utils/{iosxr/config/resource => network/iosxr/facts/l3_interfaces}/__init__.py (100%) rename module_utils/{ => network}/iosxr/facts/l3_interfaces/l3_interfaces.py (59%) rename module_utils/{iosxr/facts => network/iosxr/utils}/__init__.py (100%) create mode 100644 module_utils/network/iosxr/utils/utils.py diff --git a/library/iosxr_l3_interfaces.py b/library/iosxr_l3_interfaces.py index 8e70e3d..94eb220 100644 --- a/library/iosxr_l3_interfaces.py +++ b/library/iosxr_l3_interfaces.py @@ -1,45 +1,39 @@ -#!/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) +# 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. -### -############################################## -############################################## +# 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_l3_interfaces +The module file for ios_l3_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'} -NETWORK_OS = "iosxr" -RESOURCE = "l3_interfaces" -COPYRIGHT = "Copyright 2019 Red Hat" DOCUMENTATION = """ module: iosxr_l3_interfaces @@ -277,9 +271,11 @@ # # 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 @@ -294,7 +290,7 @@ # shutdown # ! -- name: Delete attributes of given interfaces (Note: This won't delete the interface itself) +- name: "Delete L3 attributes of given interfaces (Note: This won't delete the interface itself)" iosxr_l3_interfaces: config: - name: GigabitEthernet0/0/0/3 @@ -302,6 +298,58 @@ - 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 + +# 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: # ------------- # @@ -327,21 +375,24 @@ 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: ['command 1', 'command 2', 'command 3'] + 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.iosxr.config.l3_interfaces.l3_interfaces import L3_Interfaces +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(): @@ -349,7 +400,7 @@ def main(): Main entry point for module execution :returns: the result form module invocation """ - module = AnsibleModule(argument_spec=L3_Interfaces.argument_spec, + module = AnsibleModule(argument_spec=L3_InterfacesArgs.argument_spec, supports_check_mode=True) result = L3_Interfaces(module).execute_module() diff --git a/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py deleted file mode 100644 index 2d8acc9..0000000 --- a/module_utils/iosxr/argspec/l3_interfaces/l3_interfaces.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/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 arg spec for the iosxr_l3_interfaces module -""" - - -class L3_InterfacesArgs(object): - - def __init__(self, **kwargs): - pass - - ipv4addr_spec = dict(address=dict(type=str), - secondary=dict(type=bool, default=False)) - - ipv6addr_spec = dict(address=dict(type=str)) - - config_spec = { - 'name': dict(type='str', required=True), - 'ipv4': dict(type='list', elements='dict', options=ipv4addr_spec), - 'ipv6': dict(type='list', elements='dict', options=ipv6addr_spec) - } - - argument_spec = { - 'state': dict(default='merged', choices=['merged', 'replaced', 'overridden', 'deleted']), - 'config': dict(type='list', elements='dict', options=config_spec) - } - diff --git a/module_utils/iosxr/argspec/resource/resource.py b/module_utils/iosxr/argspec/resource/resource.py deleted file mode 100644 index 7403671..0000000 --- a/module_utils/iosxr/argspec/resource/resource.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/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 arg spec for the iosxr_l3_interfaces module -""" diff --git a/module_utils/iosxr/config/base.py b/module_utils/iosxr/config/base.py deleted file mode 100644 index c8e3c51..0000000 --- a/module_utils/iosxr/config/base.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/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) -""" -The base class for all iosxr resource modules -""" - -from ansible.module_utils.connection import Connection - - -class ConfigBase(object): - """ The base class for all iosxr resource modules - """ - _connection = None - - def __init__(self, module): - self._module = module - self._connection = self._get_connection() - - def _get_connection(self): - if self._connection: - return self._connection - self._connection = Connection(self._module._socket_path) #pylint: disable=W0212 - return self._connection diff --git a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py b/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py deleted file mode 100644 index 93633df..0000000 --- a/module_utils/iosxr/config/l3_interfaces/l3_interfaces.py +++ /dev/null @@ -1,336 +0,0 @@ -#!/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) -""" -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 ansible.module_utils.network.common.utils import to_list -from ansible.module_utils.network.common.utils import is_netmask, is_masklen, to_netmask, to_masklen - -from ansible.module_utils.iosxr.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs -from ansible.module_utils.iosxr.config.base import ConfigBase -from ansible.module_utils.iosxr.facts.facts import Facts - - -class L3_Interfaces(ConfigBase, L3_InterfacesArgs): - """ - 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 - """ - result = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) - facts = result - 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': - kwargs = {'want': want, 'have': have, 'module': self._module} - commands = self._state_overridden(**kwargs) - elif state == 'deleted': - kwargs = {'want': want, 'have': have, 'module': self._module} - commands = self._state_deleted(**kwargs) - elif state == 'merged': - kwargs = {'want': want, 'have': have, 'module': self._module} - commands = self._state_merged(**kwargs) - elif state == 'replaced': - kwargs = {'want': want, 'have': have, 'module': self._module} - commands = self._state_replaced(**kwargs) - - return commands - - @staticmethod - def _state_replaced(**kwargs): - """ The command generator when state is replaced - :rtype: A list - :returns: the commands necessary to migrate the current configuration - to the desired configuration - """ - commands = [] - want = kwargs['want'] - have = kwargs['have'] - module = kwargs['module'] - - for interface in want: - for each in have: - if each['name'] == interface['name']: - break - else: - continue - kwargs = {'want': interface, 'have': each} - commands.extend(L3_Interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} - commands.extend(L3_Interfaces.set_interface(**kwargs)) - # Remove the duplicate interface call - commands = L3_Interfaces._remove_duplicate_interface(commands) - - return commands - - @staticmethod - def _state_overridden(**kwargs): - """ The command generator when state is overridden - :rtype: A list - :returns: the commands necessary to migrate the current configuration - to the desired configuration - """ - commands = [] - want = kwargs['want'] - have = kwargs['have'] - module = kwargs['module'] - - for each in have: - for interface in want: - if each['name'] == interface['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']) - kwargs = {'want': interface, 'have': each} - commands.extend(L3_Interfaces.clear_interface(**kwargs)) - continue - kwargs = {'want': interface, 'have': each} - commands.extend(L3_Interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} - commands.extend(L3_Interfaces.set_interface(**kwargs)) - # Remove the duplicate interface call - commands = L3_Interfaces._remove_duplicate_interface(commands) - - return commands - - @staticmethod - def _state_merged(**kwargs): - """ The command generator when state is merged - :rtype: A list - :returns: the commands necessary to merge the provided into - the current configuration - """ - commands = [] - want = kwargs['want'] - have = kwargs['have'] - module = kwargs['module'] - - for interface in want: - for each in have: - if each['name'] == interface['name']: - break - else: - continue - kwargs = {'want': interface, 'have': each, 'module': module} - commands.extend(L3_Interfaces.set_interface(**kwargs)) - - return commands - - @staticmethod - def _state_deleted(**kwargs): - """ The command generator when state is deleted - :rtype: A list - :returns: the commands necessary to remove the current configuration - of the provided objects - """ - commands = [] - want = kwargs['want'] - have = kwargs['have'] - - for interface in want: - for each in have: - if each['name'] == interface['name']: - break - else: - continue - interface = dict(name=interface['name']) - kwargs = {'want': interface, 'have': each} - commands.extend(L3_Interfaces.clear_interface(**kwargs)) - - return commands - - @staticmethod - def _remove_command_from_interface(interface, cmd, commands): - if interface not in commands: - commands.insert(0, interface) - commands.append('no %s' % cmd) - return commands - - @staticmethod - def _add_command_to_interface(interface, cmd, commands): - if interface not in commands: - commands.insert(0, interface) - commands.append(cmd) - - @staticmethod - def validate_ipv4(value, module): - if value: - address = value.split('/') - if len(address) != 2: - module.fail_json(msg='address format is /, got invalid format {}'.format(value)) - - if not is_masklen(address[1]): - module.fail_json(msg='invalid value for mask: {}, mask should be in range 0-32'.format(address[1])) - - @staticmethod - def validate_ipv6(value, module): - if value: - address = value.split('/') - if len(address) != 2: - module.fail_json(msg='address format is /, got invalid format {}'.format(value)) - else: - if not 0 <= int(address[1]) <= 128: - module.fail_json(msg='invalid value for mask: {}, mask should be in range 0-128'.format(address[1])) - - @staticmethod - 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') - L3_Interfaces.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 - - @staticmethod - def _remove_duplicate_interface(commands): - # Remove duplicate interface from commands - set_cmd = [] - for each in commands: - if 'interface' in each: - interface = each - if interface not in set_cmd: - set_cmd.append(each) - else: - set_cmd.append(each) - - return set_cmd - - @staticmethod - def set_interface(**kwargs): - # Set the interface config based on the want and have config - commands = [] - want = kwargs['want'] - have = kwargs['have'] - module = kwargs['module'] - interface = 'interface ' + want['name'] - - # To handle Interface L3 config if L2transport is configured - if have.get('l2transport'): - module.fail_json(msg='L3 configuration is not allowed under a L2 subinterface {}'.format(want['name'])) - elif 'l2transport' in want['name']: - module.fail_json(msg='L3 configuration is not allowed under a L2 subinterface {}'.format(want['name'])) - - if want.get("ipv4"): - for each in want.get("ipv4"): - if each.get('address'): - ip_addr_want = L3_Interfaces.validate_n_expand_ipv4(module, each) - each['address'] = ip_addr_want - - want_ipv4 = set(tuple(address.items()) for address in want.get("ipv4") or []) - have_ipv4 = set(tuple(address.items()) for address in have.get("ipv4") or []) - diff = want_ipv4 - have_ipv4 - for address in diff: - address = dict(address) - cmd = "ip address {}".format(address["address"]) - if address["secondary"]: - cmd += " secondary" - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - - want_ipv6 = set(tuple(address.items()) for address in want.get("ipv6") or []) - have_ipv6 = set(tuple(address.items()) for address in have.get("ipv6") or []) - diff = want_ipv6 - have_ipv6 - for address in diff: - address = dict(address) - cmd = "ipv6 address {}".format(address["address"]) - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - - return commands - - @staticmethod - def clear_interface(**kwargs): - # Delete the interface config based on the want and have config - count = 0 - commands = [] - want = kwargs['want'] - have = kwargs['have'] - interface = 'interface ' + want['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 {} secondary'.format(each.get('address')) - L3_Interfaces._remove_command_from_interface(interface, cmd, commands) - count += 1 - if have.get('ipv4') and not (want.get('ipv4')): - L3_Interfaces._remove_command_from_interface(interface, 'ipv4 address', commands) - if have.get('ipv6') and not (want.get('ipv6')): - L3_Interfaces._remove_command_from_interface(interface, 'ipv6 address', commands) - - return commands diff --git a/module_utils/iosxr/config/resource/resource.py b/module_utils/iosxr/config/resource/resource.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/iosxr/facts/base.py b/module_utils/iosxr/facts/base.py deleted file mode 100644 index aee42ff..0000000 --- a/module_utils/iosxr/facts/base.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/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) -""" -The iosxr facts base class -this contains methods common to all facts subsets -""" - -import re -from copy import deepcopy -from ansible.module_utils.six import iteritems - - -class FactsBase(object): - """ - The iosxr facts base class - """ - generated_spec = {} - ansible_facts = {'ansible_network_resources': {}} - - def __init__(self, argspec, subspec=None, options=None): - spec = deepcopy(argspec) - if subspec: - if options: - facts_argument_spec = spec[subspec][options] - else: - facts_argument_spec = spec[subspec] - else: - facts_argument_spec = spec - - self.generated_spec = self.generate_dict(facts_argument_spec) - - @staticmethod - def generate_dict(spec): - """ - Generate dictionary which is in sync with argspec - :param spec: A dictionary which the argspec of module - :rtype: A dictionary - :returns: A dictionary in sync with argspec with default value - """ - obj = {} - if not spec: - return obj - - for key, val in iteritems(spec): - if 'default' in val: - dct = {key: val['default']} - else: - dct = {key: None} - obj.update(dct) - - return obj - - @staticmethod - def parse_conf_arg(cfg, arg): - """ - Parse config based on argument - :param cfg: A text string which is a line of configuration. - :param arg: A text string which is to be matched. - :rtype: A text string - :returns: A text string if match is found - """ - match = re.search(r'%s (.+)(\n|$)' % arg, cfg, re.M) - if match: - result = match.group(1).strip() - else: - result = None - return result - - @staticmethod - def parse_conf_cmd_arg(cfg, cmd, res1, res2=None): - """ - Parse config based on command - :param cfg: A text string which is a line of configuration. - :param cmd: A text string which is the command to be matched - :param res1: A text string to be returned if the command is present - :param res2: A text string to be returned if the negate command is present - :rtype: A text string - :returns: A text string if match is found - """ - match = re.search(r'\n\s+%s(\n|$)' % cmd, cfg) - if match: - return res1 - if res2 is not None: - match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) - if match: - return res2 - return None - - @staticmethod - def generate_final_config(cfg_dict): - """ - Generate final config dictionary - :param cfg_dict: A dictionary parsed in the facts system - :rtype: A dictionary - :returns: A dictionary by eliminating keys that have null values - """ - final_cfg = {} - if not cfg_dict: - return final_cfg - - for key, val in iteritems(cfg_dict): - if val: - final_cfg.update({key:val}) - return final_cfg diff --git a/module_utils/iosxr/facts/facts.py b/module_utils/iosxr/facts/facts.py deleted file mode 100644 index 812a329..0000000 --- a/module_utils/iosxr/facts/facts.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/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) -""" -The facts class for iosxr -this file validates each subset of facts and selectively -calls the appropriate facts gathering function -""" - - -from ansible.module_utils.six import iteritems - -from ansible.module_utils.iosxr.argspec.facts.facts import FactsArgs -from ansible.module_utils.iosxr.facts.base import FactsBase -from ansible.module_utils.iosxr.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs -from ansible.module_utils.iosxr.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts - - -FACT_SUBSETS = {} - - -class Facts(FactsArgs, FactsBase): - """ The fact class for iosxr - """ - - VALID_GATHER_SUBSETS = frozenset(FACT_SUBSETS.keys()) - - def generate_runable_subsets(self, module, subsets, valid_subsets): - runable_subsets = set() - exclude_subsets = set() - minimal_gather_subset = frozenset(['default']) - - for subset in subsets: - if subset == 'all': - runable_subsets.update(valid_subsets) - continue - if subset == 'min' and minimal_gather_subset: - runable_subsets.update(minimal_gather_subset) - continue - if subset.startswith('!'): - subset = subset[1:] - if subset == 'min': - exclude_subsets.update(minimal_gather_subset) - continue - if subset == 'all': - exclude_subsets.update(valid_subsets - minimal_gather_subset) - continue - exclude = True - else: - exclude = False - - if subset not in valid_subsets: - module.fail_json(msg='Bad subset') - - if exclude: - exclude_subsets.add(subset) - else: - runable_subsets.add(subset) - - if not runable_subsets: - runable_subsets.update(valid_subsets) - runable_subsets.difference_update(exclude_subsets) - - return runable_subsets - - def get_facts(self, module, connection, gather_subset=['!config'], gather_network_resources=['all']): - """ Collect the facts for iosxr - :param module: The module instance - :param connection: The device connection - :param gather_subset: The facts subset to collect - :param gather_network_resources: The resource subset to collect - :rtype: dict - :returns: the facts gathered - """ - warnings = [] - self.ansible_facts['gather_network_resources'] = list() - self.ansible_facts['gather_subset'] = list() - - valid_network_resources_subsets = self.argument_spec['gather_network_resources'].get('choices', []) - if valid_network_resources_subsets and 'all' in valid_network_resources_subsets: - valid_network_resources_subsets.remove('all') - - if valid_network_resources_subsets: - resources_runable_subsets = self.generate_runable_subsets(module, gather_network_resources, - valid_network_resources_subsets) - if resources_runable_subsets: - self.ansible_facts['gather_network_resources'] = list(resources_runable_subsets) - for attr in resources_runable_subsets: - getattr(self, '_get_%s' % attr, {})(module, connection) - if self.VALID_GATHER_SUBSETS: - runable_subsets = self.generate_runable_subsets(module, gather_subset, self.VALID_GATHER_SUBSETS) - - if runable_subsets: - facts = dict() - self.ansible_facts['gather_subset'] = list(runable_subsets) - - instances = list() - for key in runable_subsets: - instances.append(FACT_SUBSETS[key](module)) - - for inst in instances: - inst.populate() - facts.update(inst.facts) - warnings.extend(inst.warnings) - - for key, value in iteritems(facts): - key = 'ansible_net_%s' % key - self.ansible_facts[key] = value - - if warnings: - return self.ansible_facts, warnings - else: - return self.ansible_facts - - @staticmethod - def _get_l3_interfaces(module, connection): - return L3_interfacesFacts(L3_InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, - connection) diff --git a/module_utils/iosxr/facts/l3_interfaces/__init__.py b/module_utils/iosxr/facts/l3_interfaces/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/iosxr/facts/resource/__init__.py b/module_utils/iosxr/facts/resource/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/iosxr/facts/resource/resource.py b/module_utils/iosxr/facts/resource/resource.py deleted file mode 100644 index c42e76d..0000000 --- a/module_utils/iosxr/facts/resource/resource.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/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) -""" -The ios_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 copy import deepcopy -from ansible.module_utils.iosxr.facts.base import FactsBase - -class Facts(FactsBase): - """ The iosxr l3 interface fact class - """ - - def populate_facts(self, module, connection, data=None): - """ Populate the facts for iosxr l3 interface - :param module: the module instance - :param connection: the device connection - :param data: previously collected conf - :rtype: dictionary - :returns: facts - """ - if module: #just for linting purposes - pass - if connection: #just for linting purposes - pass - - if not data: - data = "foo" # connection.get('show running-config | section ^interface') - - # operate on a collection of resource x - config = [data] # data.split('interface ') - objs = [] - 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'] = objs - self.ansible_facts['net_configuration'].update(facts) - return self.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 - """ - if conf: - pass - config = deepcopy(spec) - # populate the facts from the configuration - config = {"some": "value"} - return self.generate_final_config(config) diff --git a/module_utils/iosxr/utils/__init__.py b/module_utils/iosxr/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/iosxr/utils/utils.py b/module_utils/iosxr/utils/utils.py deleted file mode 100644 index aaceab2..0000000 --- a/module_utils/iosxr/utils/utils.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/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) - -# utils - - -def search_obj_in_list(name, lst): - for o in lst: - if o['name'] == name: - return o - return None - - -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' diff --git a/module_utils/iosxr/__init__.py b/module_utils/network/iosxr/__init__.py similarity index 100% rename from module_utils/iosxr/__init__.py rename to module_utils/network/iosxr/__init__.py diff --git a/module_utils/iosxr/argspec/__init__.py b/module_utils/network/iosxr/argspec/__init__.py similarity index 100% rename from module_utils/iosxr/argspec/__init__.py rename to module_utils/network/iosxr/argspec/__init__.py diff --git a/module_utils/iosxr/argspec/facts/__init__.py b/module_utils/network/iosxr/argspec/facts/__init__.py similarity index 100% rename from module_utils/iosxr/argspec/facts/__init__.py rename to module_utils/network/iosxr/argspec/facts/__init__.py diff --git a/module_utils/iosxr/argspec/facts/facts.py b/module_utils/network/iosxr/argspec/facts/facts.py similarity index 78% rename from module_utils/iosxr/argspec/facts/facts.py rename to module_utils/network/iosxr/argspec/facts/facts.py index 2f8b7dc..c4c0a60 100644 --- a/module_utils/iosxr/argspec/facts/facts.py +++ b/module_utils/network/iosxr/argspec/facts/facts.py @@ -15,7 +15,14 @@ def __init__(self, **kwargs): choices = [ 'all', + 'lacp', + '!lacp', + 'lacp_interfaces', + '!lacp_interfaces', + 'lldp_global', + '!lldp_global', 'l3_interfaces', + '!l3_interfaces' ] argument_spec = { diff --git a/module_utils/iosxr/argspec/l3_interfaces/__init__.py b/module_utils/network/iosxr/argspec/l3_interfaces/__init__.py similarity index 100% rename from module_utils/iosxr/argspec/l3_interfaces/__init__.py rename to module_utils/network/iosxr/argspec/l3_interfaces/__init__.py diff --git a/module_utils/network/iosxr/argspec/l3_interfaces/l3_interfaces.py b/module_utils/network/iosxr/argspec/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..0de627a --- /dev/null +++ b/module_utils/network/iosxr/argspec/l3_interfaces/l3_interfaces.py @@ -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'}} diff --git a/module_utils/iosxr/argspec/resource/__init__.py b/module_utils/network/iosxr/config/__init__.py similarity index 100% rename from module_utils/iosxr/argspec/resource/__init__.py rename to module_utils/network/iosxr/config/__init__.py diff --git a/module_utils/iosxr/config/__init__.py b/module_utils/network/iosxr/config/l3_interfaces/__init__.py similarity index 100% rename from module_utils/iosxr/config/__init__.py rename to module_utils/network/iosxr/config/l3_interfaces/__init__.py diff --git a/module_utils/network/iosxr/config/l3_interfaces/l3_interfaces.py b/module_utils/network/iosxr/config/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..edee44a --- /dev/null +++ b/module_utils/network/iosxr/config/l3_interfaces/l3_interfaces.py @@ -0,0 +1,276 @@ +# -*- 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 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 +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: + for each in have: + if each['name'] == interface['name']: + break + else: + commands.extend(self._set_config(interface, each, 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: + if each['name'] == 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: + for each in have: + if each['name'] == interface['name']: + break + else: + commands.extend(self._set_config(interface, each, 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: + 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_diff(want) + have_dict = dict_diff(have) + diff = want_dict - have_dict + + # To handle L3 IPV4 configuration + ipv4 = dict(diff).get('ipv4') + if ipv4: + for each in ipv4: + ipv4_dict = dict(each) + if ipv4_dict.get('address') != 'dhcp': + cmd = "ip 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 + ipv6 = dict(diff).get('ipv6') + if ipv6: + for each in 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 {} 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 diff --git a/module_utils/iosxr/config/l3_interfaces/__init__.py b/module_utils/network/iosxr/facts/__init__.py similarity index 100% rename from module_utils/iosxr/config/l3_interfaces/__init__.py rename to module_utils/network/iosxr/facts/__init__.py diff --git a/module_utils/network/iosxr/facts/facts.py b/module_utils/network/iosxr/facts/facts.py new file mode 100644 index 0000000..0ecb375 --- /dev/null +++ b/module_utils/network/iosxr/facts/facts.py @@ -0,0 +1,59 @@ +# +# -*- 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 facts class for ios +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.network.ios.argspec.facts.facts import FactsArgs +from ansible.module_utils.network.common.facts.facts import FactsBase +from ansible.module_utils.network.iosxr.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts +from ansible.module_utils.network.iosxr.facts.legacy.base import Default, Hardware, Interfaces, Config + + +FACT_LEGACY_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config +) + +FACT_RESOURCE_SUBSETS = dict( + l3_interfaces=L3_InterfacesFacts, +) + + +class Facts(FactsBase): + """ The fact class for ios + """ + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) + + def __init__(self, module): + super(Facts, self).__init__(module) + + def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None): + """ Collect the facts for ios + :param legacy_facts_type: List of legacy facts types + :param resource_facts_type: List of resource fact types + :param data: previously collected conf + :rtype: dict + :return: the facts gathered + """ + netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', []) + if self.VALID_RESOURCE_SUBSETS: + self.get_network_resources_facts(netres_choices, FACT_RESOURCE_SUBSETS, resource_facts_type, data) + + if self.VALID_LEGACY_GATHER_SUBSETS: + self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type) + + return self.ansible_facts, self._warnings diff --git a/module_utils/iosxr/config/resource/__init__.py b/module_utils/network/iosxr/facts/l3_interfaces/__init__.py similarity index 100% rename from module_utils/iosxr/config/resource/__init__.py rename to module_utils/network/iosxr/facts/l3_interfaces/__init__.py diff --git a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py b/module_utils/network/iosxr/facts/l3_interfaces/l3_interfaces.py similarity index 59% rename from module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py rename to module_utils/network/iosxr/facts/l3_interfaces/l3_interfaces.py index e8322b5..f7391d7 100644 --- a/module_utils/iosxr/facts/l3_interfaces/l3_interfaces.py +++ b/module_utils/network/iosxr/facts/l3_interfaces/l3_interfaces.py @@ -1,29 +1,48 @@ -#!/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) +# 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 +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. """ -import re -from copy import deepcopy +from __future__ import absolute_import, division, print_function +__metaclass__ = type + -from ansible.module_utils.iosxr.facts.base import FactsBase -from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface +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(FactsBase): - """ The iosxr l3 interfaces fact class +class L3_InterfacesFacts(object): + """ The iosxr_l3_interfaces fact class """ - def populate_facts(self, module, connection, data=None): + 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 module: the module instance :param connection: the device connection + :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts @@ -42,9 +61,13 @@ def populate_facts(self, module, connection, data=None): facts = {} if objs: - facts['l3_interfaces'] = objs - self.ansible_facts['ansible_network_resources'].update(facts) - return self.ansible_facts + 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): """ @@ -75,7 +98,6 @@ def render_config(self, spec, conf): each_ipv4['secondary'] = True else: each_ipv4['address'] = each - each_ipv4['secondary'] = False ipv4.append(each_ipv4) config['ipv4'] = ipv4 @@ -88,15 +110,4 @@ def render_config(self, spec, conf): ipv6.append(each_ipv6) config['ipv6'] = ipv6 - # To verify if L2transport is configured on the interface - l2transport = False - interface_name = re.search(r'^(\S+) (\S.*)', conf) - if interface_name: - if 'l2transport' in interface_name.group(): - l2transport = True - elif re.search(r"l2transport", conf): - l2transport = True - if l2transport: - config['l2transport'] = True - - return self.generate_final_config(config) + return utils.remove_empties(config) diff --git a/module_utils/iosxr/facts/__init__.py b/module_utils/network/iosxr/utils/__init__.py similarity index 100% rename from module_utils/iosxr/facts/__init__.py rename to module_utils/network/iosxr/utils/__init__.py diff --git a/module_utils/network/iosxr/utils/utils.py b/module_utils/network/iosxr/utils/utils.py new file mode 100644 index 0000000..02ab92b --- /dev/null +++ b/module_utils/network/iosxr/utils/utils.py @@ -0,0 +1,194 @@ +#!/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) + +# utils + +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): + # 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: + 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(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 + + +def filter_dict_having_none_value(want, have): + # Generate dict with have dict value which is None in want dict + test_dict = dict() + test_key_dict = dict() + test_dict['name'] = want.get('name') + 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 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 validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format {}'.format(value)) + + if not is_masklen(address[1]): + module.fail_json(msg='invalid value for mask: {}, 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 /, got invalid format {}'.format(value)) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json(msg='invalid value for mask: {}, 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') + 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 + """ + 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'