From ef604c30e0d73d321edf50b7d08f5ae4177885c6 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 01/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- library/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 library/__init__.py diff --git a/library/__init__.py b/library/__init__.py new file mode 100644 index 0000000..e69de29 From ea66575d88141ad08223cf139f053c1e06393f30 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 02/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- library/ios_facts.py | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 library/ios_facts.py diff --git a/library/ios_facts.py b/library/ios_facts.py new file mode 100644 index 0000000..99bff3f --- /dev/null +++ b/library/ios_facts.py @@ -0,0 +1,112 @@ +#!/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 ios_facts +""" + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network'} + +NETWORK_OS = "ios" +RESOURCE = "facts" +COPYRIGHT = "Copyright 2019 Red Hat" + +DOCUMENTATION = """ +--- +module: ios_facts +version_added: 2.9 +short_description: Get facts about Cisco ios devices. +description: + - Collects facts from network devices running the ios 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, min, hardware, config, legacy, and interfaces. 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" + gather_network_resources: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all and the resources like interfaces, vlans etc. + 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 + version_added: "2.9" +""" + +EXAMPLES = """ +# Gather all facts +- ios_facts: + gather_subset: all + gather_network_resources: all +# Collect only the ios facts +- ios_facts: + gather_subset: + - !all + - !min + gather_network_resources: + - ios +# Do not collect ios facts +- ios_facts: + gather_network_resources: + - "!ios" +# Collect ios and minimal default facts +- ios_facts: + gather_subset: min + gather_network_resources: ios +""" + +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.ios.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) + 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 7275e41377b7926286a9f65abc9a80e868d097bf Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 03/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- library/ios_l3_interface.py | 312 ++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 library/ios_l3_interface.py diff --git a/library/ios_l3_interface.py b/library/ios_l3_interface.py new file mode 100644 index 0000000..78e5416 --- /dev/null +++ b/library/ios_l3_interface.py @@ -0,0 +1,312 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################## +################# WARNING #################### +############################################## +### +### This file is auto generated by the resource +### module builder playbook. +### +### Do not edit this file manually. +### +### Changes to this file will be over written +### by the resource module builder. +### +### Changes should be made in the model used to +### generate this file or in the resource module +### builder template. +### +############################################## +############################################## +############################################## + +""" +The module file for ios_l3_interfaces +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +# {{ rm|to_doc(model) }} + +GENERATOR_VERSION = '1.0' + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network'} + +NETWORK_OS = "ios" +RESOURCE = "l3_interfaces" +COPYRIGHT = "Copyright 2019 Red Hat" + +DOCUMENTATION = """ +--- + module: ios_l3_interfaces + version_added: 2.9 + short_description: Manage Layer-3 interface on Cisco IOS-XR devices + description: This module provides declarative management of a 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: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description This is test +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured by Ansible Network +# no ip address + +- name: Merge provided configuration with device configuration + ios_interfaces: + config: + - name: GigabitEthernet0/2 + ipv4: 192.168.0.1/24 + - name: GigabitEthernet0/3 + ipv6: fd5d:12c9:2201:1::1/64 + operation: merged +# +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description This is test +# ip address 192.168.0.1 255.255.255.0 +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured by Ansible Network +# ipv6 address FD5D:12C9:2201:1::1/64 + +# Using replaced + +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description This is test +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured by Ansible Network +# ip address 192.168.2.0 255.255.255.0 + +- name: Replaces device configuration of listed interfaces with provided configuration + ios_interfaces: + config: + - name: GigabitEthernet0/2 + ipv4: 192.168.2.0/24 + - name: GigabitEthernet0/3 + ipv4: dhcp + operation: replaced + +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description This is test +# ip address 192.168.2.0 255.255.255.0 +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured by Ansible Network +# ip address dhcp + +# Using overridden + +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# ip address 192.168.0.1 255.255.255.0 +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description This is test +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured by Ansible Network +# ipv6 address FD5D:12C9:2201:1::1/64 + +- name: Override device configuration of all interfaces with provided configuration + ios_interfaces: + config: + - name: GigabitEthernet0/2 + ipv4: 192.168.0.1/24 + operation: overridden + +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description This is test +# ip address 192.168.0.1 255.255.255.0 +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured by Ansible Network + +# Using Deleted + +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# ip address 192.0.2.10 255.255.255.0 +# shutdown +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured by Ansible Network +# ip address 192.168.1.0 255.255.255.0 +# interface GigabitEthernet0/3 +# description Configured by Ansible Network +# ip address 192.168.0.1 255.255.255.0 +# shutdown +# duplex full +# speed 10 +# ipv6 address FD5D:12C9:2201:1::1/64 + +- name: Delete attributes of given interfaces (Note: This won't delete the interface itself) + ios_interfaces: + config: + - name: GigabitEthernet0/2 + - name: GigabitEthernet0/2 + - name: GigabitEthernet0/3 + operation: deleted + +# After state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# no ip address +# shutdown +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured by Ansible Network +# no ip address +# interface GigabitEthernet0/3 +# description Configured by Ansible Network +# no ip address +# shutdown +# duplex full +# speed 10 + +""" + +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.ios.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 adea4fc72a0c38d724a865903fef85134f02d8a0 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 04/36] Draft ios 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 174103ea0aa65d4cacbb7af75d223c06cd75f536 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 05/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/__init__.py diff --git a/module_utils/ios/__init__.py b/module_utils/ios/__init__.py new file mode 100644 index 0000000..e69de29 From cf44ac60a6fc39252ffe28a4aedd5ed4138081c7 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 06/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/argspec/__init__.py diff --git a/module_utils/ios/argspec/__init__.py b/module_utils/ios/argspec/__init__.py new file mode 100644 index 0000000..e69de29 From d1e39be87168ac1a4784af1fcd98402f80afecda Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 07/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/facts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/argspec/facts/__init__.py diff --git a/module_utils/ios/argspec/facts/__init__.py b/module_utils/ios/argspec/facts/__init__.py new file mode 100644 index 0000000..e69de29 From b864c1f5941afd14e08bfedff9a0adf39bb83c64 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 08/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/facts/facts.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 module_utils/ios/argspec/facts/facts.py diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/ios/argspec/facts/facts.py new file mode 100644 index 0000000..4bf2243 --- /dev/null +++ b/module_utils/ios/argspec/facts/facts.py @@ -0,0 +1,24 @@ +#!/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 ios facts module. +""" + +class FactsArgs(object): #pylint: disable=R0903 + """ The arg spec for the ios 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 62615f9e3e4f2d320147c4b21d898f19a4c62626 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 09/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/l3_interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/argspec/l3_interfaces/__init__.py diff --git a/module_utils/ios/argspec/l3_interfaces/__init__.py b/module_utils/ios/argspec/l3_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From 4b482506be72abcf6d6adcf9bc1140d5806c8e56 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 10/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- .../argspec/l3_interfaces/l3_interfaces.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 module_utils/ios/argspec/l3_interfaces/l3_interfaces.py diff --git a/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py b/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..e4d6fe2 --- /dev/null +++ b/module_utils/ios/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 ios_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) + } \ No newline at end of file From a747c39299a1ff5cd6c38ffed9e4a5ff5c6a1ae9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 11/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/resource/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/argspec/resource/__init__.py diff --git a/module_utils/ios/argspec/resource/__init__.py b/module_utils/ios/argspec/resource/__init__.py new file mode 100644 index 0000000..e69de29 From 958e0542d3b00cadaaf4596f230714a43d399411 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 12/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/resource/resource.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 module_utils/ios/argspec/resource/resource.py diff --git a/module_utils/ios/argspec/resource/resource.py b/module_utils/ios/argspec/resource/resource.py new file mode 100644 index 0000000..0734995 --- /dev/null +++ b/module_utils/ios/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 b776b985c2a5c263db954f0acd361a78d39e249c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 13/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/config/__init__.py diff --git a/module_utils/ios/config/__init__.py b/module_utils/ios/config/__init__.py new file mode 100644 index 0000000..e69de29 From dd5d6d9b4d7a701f250f8879ff33b10eda7a23f6 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 14/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/base.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 module_utils/ios/config/base.py diff --git a/module_utils/ios/config/base.py b/module_utils/ios/config/base.py new file mode 100644 index 0000000..7e3e468 --- /dev/null +++ b/module_utils/ios/config/base.py @@ -0,0 +1,24 @@ +#!/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 ios resource modules +""" + +from ansible.module_utils.connection import Connection + +class ConfigBase(object): #pylint: disable=R0205,R0903 + """ The base class for all ios 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 \ No newline at end of file From 650a7276e49288521def1d0f9a457c69710a5058 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 15/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/l3_interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/config/l3_interfaces/__init__.py diff --git a/module_utils/ios/config/l3_interfaces/__init__.py b/module_utils/ios/config/l3_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From 97832528016bceed5c972ea5b77d5a1017743b2e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:33 +0530 Subject: [PATCH 16/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- .../ios/config/l3_interfaces/l3_interfaces.py | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 module_utils/ios/config/l3_interfaces/l3_interfaces.py diff --git a/module_utils/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/ios/config/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..fa5252a --- /dev/null +++ b/module_utils/ios/config/l3_interfaces/l3_interfaces.py @@ -0,0 +1,287 @@ +#!/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.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs +from ansible.module_utils.ios.config.base import ConfigBase +from ansible.module_utils.ios.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 + +'library/__init__.py', +'library/ios_facts.py', +'library/ios_l3_interface.py', +'module_utils/__init__.py', +'module_utils/ios/__init__.py', +'module_utils/ios/argspec/__init__.py', +'module_utils/ios/argspec/facts/__init__.py', +'module_utils/ios/argspec/facts/facts.py', +'module_utils/ios/argspec/l3_interfaces/__init__.py', +'module_utils/ios/argspec/l3_interfaces/l3_interfaces.py', +'module_utils/ios/argspec/resource/__init__.py', +'module_utils/ios/argspec/resource/resource.py', +'module_utils/ios/config/__init__.py', +'module_utils/ios/config/base.py', +'module_utils/ios/config/l3_interfaces/__init__.py', +'module_utils/ios/config/l3_interfaces/l3_interfaces.py', +'module_utils/ios/config/resource/__init__.py', +'module_utils/ios/config/resource/resource.py', +'module_utils/ios/facts/__init__.py', +'module_utils/ios/facts/base.py', +'module_utils/ios/facts/facts.py', +'module_utils/ios/facts/l3_interfacs/__init__.py', +'module_utils/ios/facts/l3_interfacs/l3_interfaces.py', +'module_utils/ios/facts/resource/__init__.py', +'module_utils/ios/facts/resource/resource.py', +'module_utils/ios/utils/__init__.py', +'module_utils/ios/utils/utils.py' \ No newline at end of file From 84bafa8e670e50bb0e3ce4c1d04b7dee2ca248bb Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 17/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/resource/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/config/resource/__init__.py diff --git a/module_utils/ios/config/resource/__init__.py b/module_utils/ios/config/resource/__init__.py new file mode 100644 index 0000000..e69de29 From 96fe05f7819b9e4f755e46c887e77af6a17e4120 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 18/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/resource/resource.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/config/resource/resource.py diff --git a/module_utils/ios/config/resource/resource.py b/module_utils/ios/config/resource/resource.py new file mode 100644 index 0000000..e69de29 From 276e46e77d10dcccdbf27cad34a10172febd0cf4 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 19/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/facts/__init__.py diff --git a/module_utils/ios/facts/__init__.py b/module_utils/ios/facts/__init__.py new file mode 100644 index 0000000..e69de29 From 6bafc1f362d8d54d41766a30c81f1d37a3e7b133 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 20/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/base.py | 106 +++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 module_utils/ios/facts/base.py diff --git a/module_utils/ios/facts/base.py b/module_utils/ios/facts/base.py new file mode 100644 index 0000000..48f6210 --- /dev/null +++ b/module_utils/ios/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 ios 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): #pylint: disable=R0205 + """ + The ios 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 72375c90cfbd6008710e0021f103d863d274acba Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 21/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/facts.py | 116 ++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 module_utils/ios/facts/facts.py diff --git a/module_utils/ios/facts/facts.py b/module_utils/ios/facts/facts.py new file mode 100644 index 0000000..6e28927 --- /dev/null +++ b/module_utils/ios/facts/facts.py @@ -0,0 +1,116 @@ +#!/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 ios +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.ios.argspec.facts.facts import FactsArgs +from ansible.module_utils.ios.facts.base import FactsBase +from ansible.module_utils.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs +from ansible.module_utils.ios.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts + + +FACT_SUBSETS = {} + +class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 + """ The fact class for ios + """ + + 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 ios + :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 d89c1c8f47964e33b239e90a9081e9dc92421c08 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 22/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/l3_interfacs/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/facts/l3_interfacs/__init__.py diff --git a/module_utils/ios/facts/l3_interfacs/__init__.py b/module_utils/ios/facts/l3_interfacs/__init__.py new file mode 100644 index 0000000..e69de29 From eb223fb3f5a19716c63e487b6a96c2387cee0f17 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 23/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- .../ios/facts/l3_interfacs/l3_interfaces.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 module_utils/ios/facts/l3_interfacs/l3_interfaces.py diff --git a/module_utils/ios/facts/l3_interfacs/l3_interfaces.py b/module_utils/ios/facts/l3_interfacs/l3_interfaces.py new file mode 100644 index 0000000..ba1bf9c --- /dev/null +++ b/module_utils/ios/facts/l3_interfacs/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 ios 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.ios.facts.base import FactsBase +from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface + + +class InterfacesFacts(FactsBase): + """ The ios 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) \ No newline at end of file From 1b530a956a2dfc14e2fc56178877289fcde55937 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 24/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/resource/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/facts/resource/__init__.py diff --git a/module_utils/ios/facts/resource/__init__.py b/module_utils/ios/facts/resource/__init__.py new file mode 100644 index 0000000..e69de29 From 9d2e00650c59852a495da05b5f382e0c0a001861 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 25/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/resource/resource.py | 61 +++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 module_utils/ios/facts/resource/resource.py diff --git a/module_utils/ios/facts/resource/resource.py b/module_utils/ios/facts/resource/resource.py new file mode 100644 index 0000000..b6bbcda --- /dev/null +++ b/module_utils/ios/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.ios.facts.base import FactsBase + +class Facts(FactsBase): + """ The {{ network_os }} {{ resource }} fact class + """ + + def populate_facts(self, module, connection, data=None): + """ Populate the facts for {{ resource }} + :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['{{ resource }}'] = 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 a2c6a92e5681e9ef4547a40d4fb5d73334ea7fa0 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 26/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/utils/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/utils/__init__.py diff --git a/module_utils/ios/utils/__init__.py b/module_utils/ios/utils/__init__.py new file mode 100644 index 0000000..e69de29 From 6e338ed3de0399022a5d40d7593fb5e1c9aeecc3 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 17:34:34 +0530 Subject: [PATCH 27/36] Draft ios l3 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/utils/utils.py | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 module_utils/ios/utils/utils.py diff --git a/module_utils/ios/utils/utils.py b/module_utils/ios/utils/utils.py new file mode 100644 index 0000000..4cffe84 --- /dev/null +++ b/module_utils/ios/utils/utils.py @@ -0,0 +1,99 @@ +#!/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('te'): + if_type = 'TenGigabitEthernet' + elif name.lower().startswith('fa'): + if_type = 'FastEthernet' + elif name.lower().startswith('fo'): + if_type = 'FortyGigabitEthernet' + elif name.lower().startswith('long'): + if_type = 'LongReachEthernet' + 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('TE'): + return 'TenGigabitEthernet' + elif interface.upper().startswith('FA'): + return 'FastEthernet' + elif interface.upper().startswith('FO'): + return 'FortyGigabitEthernet' + elif interface.upper().startswith('LON'): + return 'LongReachEthernet' + 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' + else: + return 'unknown' \ No newline at end of file From 1e1c943579f82082079c3d8da1b400d38716f007 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 29 May 2019 18:02:16 +0530 Subject: [PATCH 28/36] remove unnecessary lines Signed-off-by: Sumit Jaiswal --- .../ios/config/l3_interfaces/l3_interfaces.py | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/module_utils/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/ios/config/l3_interfaces/l3_interfaces.py index fa5252a..c892c5b 100644 --- a/module_utils/ios/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/config/l3_interfaces/l3_interfaces.py @@ -257,31 +257,3 @@ def clear_interface(**kwargs): L3_Interfaces._remove_command_from_interface(interface, 'no ipv6 address', commands) return commands - -'library/__init__.py', -'library/ios_facts.py', -'library/ios_l3_interface.py', -'module_utils/__init__.py', -'module_utils/ios/__init__.py', -'module_utils/ios/argspec/__init__.py', -'module_utils/ios/argspec/facts/__init__.py', -'module_utils/ios/argspec/facts/facts.py', -'module_utils/ios/argspec/l3_interfaces/__init__.py', -'module_utils/ios/argspec/l3_interfaces/l3_interfaces.py', -'module_utils/ios/argspec/resource/__init__.py', -'module_utils/ios/argspec/resource/resource.py', -'module_utils/ios/config/__init__.py', -'module_utils/ios/config/base.py', -'module_utils/ios/config/l3_interfaces/__init__.py', -'module_utils/ios/config/l3_interfaces/l3_interfaces.py', -'module_utils/ios/config/resource/__init__.py', -'module_utils/ios/config/resource/resource.py', -'module_utils/ios/facts/__init__.py', -'module_utils/ios/facts/base.py', -'module_utils/ios/facts/facts.py', -'module_utils/ios/facts/l3_interfacs/__init__.py', -'module_utils/ios/facts/l3_interfacs/l3_interfaces.py', -'module_utils/ios/facts/resource/__init__.py', -'module_utils/ios/facts/resource/resource.py', -'module_utils/ios/utils/__init__.py', -'module_utils/ios/utils/utils.py' \ No newline at end of file From 4bc582e4f9ddbb07b3541237db73a702fbd8d1cc Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 5 Jun 2019 20:03:58 +0530 Subject: [PATCH 29/36] ios l3 interface Signed-off-by: Sumit Jaiswal --- library/ios_facts.py | 1 + library/ios_l3_interface.py | 123 ++++++++++++++---- module_utils/ios/argspec/facts/facts.py | 1 + .../argspec/l3_interfaces/l3_interfaces.py | 13 +- .../ios/config/l3_interfaces/l3_interfaces.py | 93 +++++++++++-- module_utils/ios/facts/facts.py | 3 +- .../__init__.py | 0 .../l3_interfaces.py | 48 +++++-- 8 files changed, 231 insertions(+), 51 deletions(-) rename module_utils/ios/facts/{l3_interfacs => l3_interfaces}/__init__.py (100%) rename module_utils/ios/facts/{l3_interfacs => l3_interfaces}/l3_interfaces.py (53%) diff --git a/library/ios_facts.py b/library/ios_facts.py index 99bff3f..47cfa0c 100644 --- a/library/ios_facts.py +++ b/library/ios_facts.py @@ -84,6 +84,7 @@ from ansible.module_utils.connection import Connection from ansible.module_utils.ios.facts.facts import Facts + def main(): """ Main entry point for module execution diff --git a/library/ios_l3_interface.py b/library/ios_l3_interface.py index 78e5416..6c6ec0d 100644 --- a/library/ios_l3_interface.py +++ b/library/ios_l3_interface.py @@ -31,8 +31,6 @@ __metaclass__ = type -# {{ rm|to_doc(model) }} - GENERATOR_VERSION = '1.0' ANSIBLE_METADATA = {'metadata_version': '1.1', @@ -47,8 +45,8 @@ --- module: ios_l3_interfaces version_added: 2.9 - short_description: Manage Layer-3 interface on Cisco IOS-XR devices - description: This module provides declarative management of a Layer-3 interface on Cisco IOS-XR devices. + short_description: Manage Layer-3 interface on Cisco IOS devices. + description: This module provides declarative management of Layer-3 interface on Cisco IOS devices. author: Sumit Jaiswal (@justjais) options: config: @@ -66,13 +64,43 @@ - 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 + suboptions: + address: + description: + - Configures the IPv4 address for Interface. + type: str + secondary: + description: + - Configures the IP address as a secondary address. + type: bool + dhcp_client: + description: + - Configures and specifies client-id to use over DHCP ip. Note, This option shall + work only when dhcp is configured as IP. + type: str + dhcp_hostname: + description: + - Configures and specifies value for hostname option over DHCP ip. Note, This option shall + work only when dhcp is configured as IP. + 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 IPv6 address for Interface. + type: str + autoconfig: + description: + - Obtain address using autoconfiguration. + type: bool + dhcp: + description: + - Obtain a ipv6 address using dhcp. + type: bool state: choices: - merged @@ -95,7 +123,7 @@ # vios#show running-config | section ^interface # interface GigabitEthernet0/1 # description Configured by Ansible -# no ip address +# ip address 10.1.1.1 255.255.255.0 # duplex auto # speed auto # interface GigabitEthernet0/2 @@ -106,43 +134,58 @@ # interface GigabitEthernet0/3 # description Configured by Ansible Network # no ip address +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 - name: Merge provided configuration with device configuration ios_interfaces: config: + - name: GigabitEthernet0/1 + ipv4: + - address: 192.168.0.1/24 + secondary: True - name: GigabitEthernet0/2 - ipv4: 192.168.0.1/24 + ipv4: + - address: 192.168.0.2/24 - name: GigabitEthernet0/3 - ipv6: fd5d:12c9:2201:1::1/64 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + - name: GigabitEthernet0/3.100 + ipv4: + - address: 192.168.0.3/24 operation: merged -# + # After state: # ------------ # # vios#show running-config | section ^interface # interface GigabitEthernet0/1 # description Configured by Ansible -# no ip address +# ip address 10.1.1.1 255.255.255.0 +# ip address 192.168.0.1 255.255.255.0 secondary # duplex auto # speed auto # interface GigabitEthernet0/2 # description This is test -# ip address 192.168.0.1 255.255.255.0 +# ip address 192.168.0.2 255.255.255.0 # duplex auto # speed 1000 # interface GigabitEthernet0/3 # description Configured by Ansible Network # ipv6 address FD5D:12C9:2201:1::1/64 +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 +# ip address 192.168.0.3 255.255.255.0 # Using replaced - +# # Before state: # ------------- # # vios#show running-config | section ^interface # interface GigabitEthernet0/1 # description Configured by Ansible -# no ip address +# ip address 10.1.1.1 255.255.255.0 # duplex auto # speed auto # interface GigabitEthernet0/2 @@ -153,14 +196,25 @@ # interface GigabitEthernet0/3 # description Configured by Ansible Network # ip address 192.168.2.0 255.255.255.0 +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 +# ip address 192.168.0.2 255.255.255.0 - name: Replaces device configuration of listed interfaces with provided configuration ios_interfaces: config: - name: GigabitEthernet0/2 - ipv4: 192.168.2.0/24 + ipv4: + - address: 192.168.2.0/24 - name: GigabitEthernet0/3 - ipv4: dhcp + ipv4: + - address: dhcp + dhcp_client: 2 + dhcp_hostname: test.com + - name: GigabitEthernet0/3.100 + ipv4: + - address: 192.168.0.3/24 + secondary: True operation: replaced # After state: @@ -169,43 +223,54 @@ # vios#show running-config | section ^interface # interface GigabitEthernet0/1 # description Configured by Ansible -# no ip address +# ip address 10.1.1.1 255.255.255.0 # duplex auto # speed auto # interface GigabitEthernet0/2 # description This is test -# ip address 192.168.2.0 255.255.255.0 +# ip address 192.168.2.1 255.255.255.0 # duplex auto # speed 1000 # interface GigabitEthernet0/3 # description Configured by Ansible Network -# ip address dhcp +# ip address dhcp client-id GigabitEthernet0/2 hostname test.com +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 +# ip address 192.168.0.2 255.255.255.0 +# ip address 192.168.0.3 255.255.255.0 secondary # Using overridden - +# # Before state: # ------------- # # vios#show running-config | section ^interface # interface GigabitEthernet0/1 # description Configured by Ansible -# ip address 192.168.0.1 255.255.255.0 +# ip address 10.1.1.1 255.255.255.0 # duplex auto # speed auto # interface GigabitEthernet0/2 # description This is test -# no ip address +# ip address 192.168.2.1 255.255.255.0 # duplex auto # speed 1000 # interface GigabitEthernet0/3 # description Configured by Ansible Network # ipv6 address FD5D:12C9:2201:1::1/64 +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 +# ip address 192.168.0.2 255.255.255.0 - name: Override device configuration of all interfaces with provided configuration ios_interfaces: config: - name: GigabitEthernet0/2 - ipv4: 192.168.0.1/24 + ipv4: + - address: 192.168.0.1/24 + - name: GigabitEthernet0/3.100 + ipv6: + - autoconfig: True operation: overridden # After state: @@ -224,9 +289,12 @@ # speed 1000 # interface GigabitEthernet0/3 # description Configured by Ansible Network +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 +# ipv6 address autoconfig # Using Deleted - +# # Before state: # ------------- # @@ -246,13 +314,16 @@ # duplex full # speed 10 # ipv6 address FD5D:12C9:2201:1::1/64 +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 +# ip address 192.168.0.2 255.255.255.0 - name: Delete attributes of given interfaces (Note: This won't delete the interface itself) ios_interfaces: config: - - name: GigabitEthernet0/2 - name: GigabitEthernet0/2 - name: GigabitEthernet0/3 + - name: GigabitEthernet0/3.100 operation: deleted # After state: @@ -273,6 +344,8 @@ # shutdown # duplex full # speed 10 +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 """ diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/ios/argspec/facts/facts.py index 4bf2243..d05fa7f 100644 --- a/module_utils/ios/argspec/facts/facts.py +++ b/module_utils/ios/argspec/facts/facts.py @@ -6,6 +6,7 @@ The arg spec for the ios facts module. """ + class FactsArgs(object): #pylint: disable=R0903 """ The arg spec for the ios facts module """ diff --git a/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py b/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py index e4d6fe2..dfc40a0 100644 --- a/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py @@ -31,10 +31,19 @@ class L3_InterfacesArgs(object): def __init__(self, **kwargs): pass + ipv4addr_spec = dict(address=dict(type=str), + secondary=dict(type=bool), + dhcp_client=dict(type=str), + dhcp_hostname=dict(type=str)) + + ipv6addr_spec = dict(address=dict(type=str), + autoconfig=dict(type=bool), + dhcp=dict(type=bool)) + 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 = { diff --git a/module_utils/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/ios/config/l3_interfaces/l3_interfaces.py index c892c5b..d75f3f0 100644 --- a/module_utils/ios/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/config/l3_interfaces/l3_interfaces.py @@ -11,6 +11,7 @@ """ 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.six import iteritems from ansible.module_utils.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs @@ -29,7 +30,7 @@ class L3_Interfaces(ConfigBase, L3_InterfacesArgs): ] gather_network_resources = [ - 'l3_interfaces', + 'l3_interfaces' ] def get_l3_interfaces_facts(self): @@ -125,8 +126,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 +151,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 +181,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} @@ -231,6 +226,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 +256,61 @@ def set_interface(**kwargs): clear_cmds = [] if kwargs.get('commands'): clear_cmds = kwargs['commands'] + interface = 'interface ' + want['name'] + + # To handle Sub-Interface if encapsulation is not already configured + if '.' in want['name']: + if not have.get('encapsulation'): + module.fail_json(msg='IP routing on a LAN Sub-Interface is only allowed if Encapsulation' + ' is configured over respective Sub-Interface'.format(want['name'])) + # To handle L3 IPV4 configuration + ipv4 = want.get('ipv4') + if ipv4: + for each_ip in ipv4: + if each_ip.get('address') == 'dhcp': + if each_ip.get('dhcp_client') and (each_ip.get('dhcp_client') != have('dhcp_client').split('/')[1] + or 'no ip address' in clear_cmds): + if each_ip.get('dhcp_hostname') and each_ip.get('dhcp_hostname') != have('dhcp_hostname'): + cmd = 'ip address dhcp client-id GigabitEthernet0/{0} hostname {1}'.format + (each_ip.get('dhcp_client'), each_ip.get('dhcp_hostname')) + L3_Interfaces._add_command_to_interface(interface, cmd, commands) + else: + cmd = 'ip address dhcp client-id GigabitEthernet0/{}'.format(each_ip.get('dhcp_client')) + L3_Interfaces._add_command_to_interface(interface, cmd, commands) + if each_ip.get('dhcp_hostname') and (each_ip.get('dhcp_hostname') != have('dhcp_hostname') + or 'no ip address' in clear_cmds): + cmd = 'ip address dhcp hostname {}'.format(each_ip.get('dhcp_hostname')) + L3_Interfaces._add_command_to_interface(interface, cmd, commands) + else: + 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: + if each_ip.get('dhcp') and (have('dhcp') or 'no ipv6 address' in clear_cmds): + cmd = 'ipv6 address dhcp' + L3_Interfaces._add_command_to_interface(interface, cmd, commands) + elif each_ip.get('autoconfig') and (have('autoconfig') or 'no ipv6 address' in clear_cmds): + cmd = 'ipv6 address autoconfig' + L3_Interfaces._add_command_to_interface(interface, cmd, commands) + else: + ipv6_addr = each_ip.get('address') + L3_Interfaces.validate_ipv6(ipv6_addr, module) + if have.get('ipv6') != ipv6_addr.upper() 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 +321,10 @@ 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, 'ip 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/ios/facts/facts.py b/module_utils/ios/facts/facts.py index 6e28927..90c9f3b 100644 --- a/module_utils/ios/facts/facts.py +++ b/module_utils/ios/facts/facts.py @@ -19,8 +19,9 @@ FACT_SUBSETS = {} + class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 - """ The fact class for ios + """ The fact class for ios l3 interface """ VALID_GATHER_SUBSETS = frozenset(FACT_SUBSETS.keys()) diff --git a/module_utils/ios/facts/l3_interfacs/__init__.py b/module_utils/ios/facts/l3_interfaces/__init__.py similarity index 100% rename from module_utils/ios/facts/l3_interfacs/__init__.py rename to module_utils/ios/facts/l3_interfaces/__init__.py diff --git a/module_utils/ios/facts/l3_interfacs/l3_interfaces.py b/module_utils/ios/facts/l3_interfaces/l3_interfaces.py similarity index 53% rename from module_utils/ios/facts/l3_interfacs/l3_interfaces.py rename to module_utils/ios/facts/l3_interfaces/l3_interfaces.py index ba1bf9c..08cd307 100644 --- a/module_utils/ios/facts/l3_interfacs/l3_interfaces.py +++ b/module_utils/ios/facts/l3_interfaces/l3_interfaces.py @@ -16,12 +16,12 @@ from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface -class InterfacesFacts(FactsBase): - """ The ios interfaces fact class +class L3_interfacesFacts(FactsBase): + """ The ios l3 interfaces fact class """ def populate_facts(self, module, connection, data=None): - """ Populate the facts for interfaces + """ Populate the facts for l3 interfaces :param module: the module instance :param connection: the device connection :param data: previously collected conf @@ -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,35 @@ 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"ip address (\S+.*)", conf) + for each in ipv4: + if 'secondary' in each: + config['secondary'] = True + config['secondary_ipv4'] = each.split(' secondary')[0] + elif 'dhcp' in each: + config["ipv4"] = 'dhcp' + if 'hostname' in each and 'client-id' in each: + config['dhcp_client'] = each.split(' hostname ')[0].split('client-id ')[-1] + config["dhcp_hostname"] = each.split(' hostname ')[-1] + elif 'hostname' in each: + config["dhcp_hostname"] = each.split(' hostname ')[-1] + elif 'client-id' in each: + config['dhcp_client'] = ipv4.split(' client-id ')[-1] + else: + config["ipv4"] = each - return self.generate_final_config(config) \ No newline at end of file + # Get the configured IPV6 details + ipv6 = re.findall(r"ipv6 address (\S+)", conf) + for each in ipv6: + config["ipv6"] = each + if 'autoconfig' in config["ipv6"]: + config['autoconfig'] = True + elif 'dhcp' in config['ipv6']: + config['dhcp'] = True + + encapsulation = re.search(r"encapsulation (\S+)", conf) + if encapsulation: + config['encapsulation'] = True + + return self.generate_final_config(config) From fa9201d8f5ac3d10d442d9479bd2b368dea857ac Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 6 Jun 2019 17:29:08 +0530 Subject: [PATCH 30/36] fix scondary ip bug Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/l3_interfaces/l3_interfaces.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/module_utils/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/ios/config/l3_interfaces/l3_interfaces.py index d75f3f0..ba86caa 100644 --- a/module_utils/ios/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/config/l3_interfaces/l3_interfaces.py @@ -326,5 +326,8 @@ def clear_interface(**kwargs): L3_Interfaces._remove_command_from_interface(interface, 'ip 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 = 'ip address {} secondary'.format(have.get('secondary_ipv4')) + L3_Interfaces._remove_command_from_interface(interface, cmd, commands) return commands From 359d0742ae062f7ea5a6a8f28ad14058e924fe1f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 6 Jun 2019 18:09:10 +0530 Subject: [PATCH 31/36] fix issues Signed-off-by: Sumit Jaiswal --- .../ios/config/l3_interfaces/l3_interfaces.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/module_utils/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/ios/config/l3_interfaces/l3_interfaces.py index ba86caa..e1528d1 100644 --- a/module_utils/ios/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/config/l3_interfaces/l3_interfaces.py @@ -268,16 +268,16 @@ def set_interface(**kwargs): if ipv4: for each_ip in ipv4: if each_ip.get('address') == 'dhcp': - if each_ip.get('dhcp_client') and (each_ip.get('dhcp_client') != have('dhcp_client').split('/')[1] - or 'no ip address' in clear_cmds): - if each_ip.get('dhcp_hostname') and each_ip.get('dhcp_hostname') != have('dhcp_hostname'): - cmd = 'ip address dhcp client-id GigabitEthernet0/{0} hostname {1}'.format - (each_ip.get('dhcp_client'), each_ip.get('dhcp_hostname')) + if each_ip.get('dhcp_client') and (each_ip.get('dhcp_client') != have.get('dhcp_client') + or 'no ip address' in clear_cmds): + if each_ip.get('dhcp_hostname') and each_ip.get('dhcp_hostname') == have.get('dhcp_hostname'): + cmd = 'ip address dhcp client-id GigabitEthernet0/{} hostname {}'.\ + format(each_ip.get('dhcp_client'), each_ip.get('dhcp_hostname')) L3_Interfaces._add_command_to_interface(interface, cmd, commands) else: cmd = 'ip address dhcp client-id GigabitEthernet0/{}'.format(each_ip.get('dhcp_client')) L3_Interfaces._add_command_to_interface(interface, cmd, commands) - if each_ip.get('dhcp_hostname') and (each_ip.get('dhcp_hostname') != have('dhcp_hostname') + if each_ip.get('dhcp_hostname') and (each_ip.get('dhcp_hostname') != have.get('dhcp_hostname') or 'no ip address' in clear_cmds): cmd = 'ip address dhcp hostname {}'.format(each_ip.get('dhcp_hostname')) L3_Interfaces._add_command_to_interface(interface, cmd, commands) @@ -322,12 +322,12 @@ def clear_interface(**kwargs): have = kwargs['have'] interface = 'interface ' + want['name'] + if have.get('secondary') and not want.get('secondary'): + cmd = 'ip address {} secondary'.format(have.get('secondary_ipv4')) + 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, 'ip 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 = 'ip address {} secondary'.format(have.get('secondary_ipv4')) - L3_Interfaces._remove_command_from_interface(interface, cmd, commands) return commands From f5e423d62859fb57b5c10486efb0fef8d272b053 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 6 Jun 2019 18:09:25 +0530 Subject: [PATCH 32/36] fix issues Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/l3_interfaces/l3_interfaces.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/module_utils/ios/facts/l3_interfaces/l3_interfaces.py b/module_utils/ios/facts/l3_interfaces/l3_interfaces.py index 08cd307..51aaffa 100644 --- a/module_utils/ios/facts/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/facts/l3_interfaces/l3_interfaces.py @@ -14,7 +14,7 @@ from ansible.module_utils.ios.facts.base import FactsBase from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface - +import q class L3_interfacesFacts(FactsBase): """ The ios l3 interfaces fact class @@ -72,12 +72,12 @@ def render_config(self, spec, conf): elif 'dhcp' in each: config["ipv4"] = 'dhcp' if 'hostname' in each and 'client-id' in each: - config['dhcp_client'] = each.split(' hostname ')[0].split('client-id ')[-1] + config['dhcp_client'] = each.split(' hostname ')[0].split('/')[-1] config["dhcp_hostname"] = each.split(' hostname ')[-1] - elif 'hostname' in each: + if 'hostname' in each and not config["dhcp_hostname"]: config["dhcp_hostname"] = each.split(' hostname ')[-1] - elif 'client-id' in each: - config['dhcp_client'] = ipv4.split(' client-id ')[-1] + if 'client-id' in each and not config['dhcp_client']: + config['dhcp_client'] = each.split('/')[-1] else: config["ipv4"] = each From 84b3b21d102155fcbda5320d9842536106acf41a Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 18 Jun 2019 16:16:16 +0530 Subject: [PATCH 33/36] ios l3 interface update Signed-off-by: Sumit Jaiswal --- library/ios_l3_interface.py | 18 +-- .../ios/config/l3_interfaces/l3_interfaces.py | 122 ++++++++++-------- .../ios/facts/l3_interfaces/l3_interfaces.py | 53 ++++---- .../ios_l3_interface/default/main.yaml | 2 + .../targets/ios_l3_interface/meta/main.yaml | 2 + .../targets/ios_l3_interface/tasks/cli.yaml | 16 +++ .../targets/ios_l3_interface/tasks/main.yaml | 2 + .../ios_l3_interface/tests/cli/deleted.yaml | 29 +++++ .../ios_l3_interface/tests/cli/merged.yaml | 48 +++++++ .../tests/cli/overridden.yaml | 44 +++++++ .../ios_l3_interface/tests/cli/replaced.yaml | 44 +++++++ .../tests/cli/reset_config.yaml | 37 ++++++ 12 files changed, 330 insertions(+), 87 deletions(-) create mode 100644 tests/integration/targets/ios_l3_interface/default/main.yaml create mode 100644 tests/integration/targets/ios_l3_interface/meta/main.yaml create mode 100644 tests/integration/targets/ios_l3_interface/tasks/cli.yaml create mode 100644 tests/integration/targets/ios_l3_interface/tasks/main.yaml create mode 100644 tests/integration/targets/ios_l3_interface/tests/cli/deleted.yaml create mode 100644 tests/integration/targets/ios_l3_interface/tests/cli/merged.yaml create mode 100644 tests/integration/targets/ios_l3_interface/tests/cli/overridden.yaml create mode 100644 tests/integration/targets/ios_l3_interface/tests/cli/replaced.yaml create mode 100644 tests/integration/targets/ios_l3_interface/tests/cli/reset_config.yaml diff --git a/library/ios_l3_interface.py b/library/ios_l3_interface.py index 6c6ec0d..338ad85 100644 --- a/library/ios_l3_interface.py +++ b/library/ios_l3_interface.py @@ -93,14 +93,6 @@ description: - Configures the IPv6 address for Interface. type: str - autoconfig: - description: - - Obtain address using autoconfiguration. - type: bool - dhcp: - description: - - Obtain a ipv6 address using dhcp. - type: bool state: choices: - merged @@ -138,7 +130,7 @@ # encapsulation dot1Q 20 - name: Merge provided configuration with device configuration - ios_interfaces: + ios_l3_interfaces: config: - name: GigabitEthernet0/1 ipv4: @@ -201,7 +193,7 @@ # ip address 192.168.0.2 255.255.255.0 - name: Replaces device configuration of listed interfaces with provided configuration - ios_interfaces: + ios_l3_interfaces: config: - name: GigabitEthernet0/2 ipv4: @@ -263,14 +255,14 @@ # ip address 192.168.0.2 255.255.255.0 - name: Override device configuration of all interfaces with provided configuration - ios_interfaces: + ios_l3_interfaces: config: - name: GigabitEthernet0/2 ipv4: - address: 192.168.0.1/24 - name: GigabitEthernet0/3.100 ipv6: - - autoconfig: True + - address: autoconfig operation: overridden # After state: @@ -319,7 +311,7 @@ # ip address 192.168.0.2 255.255.255.0 - name: Delete attributes of given interfaces (Note: This won't delete the interface itself) - ios_interfaces: + ios_l3_interfaces: config: - name: GigabitEthernet0/2 - name: GigabitEthernet0/3 diff --git a/module_utils/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/ios/config/l3_interfaces/l3_interfaces.py index e1528d1..1b71dfd 100644 --- a/module_utils/ios/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/config/l3_interfaces/l3_interfaces.py @@ -17,7 +17,7 @@ from ansible.module_utils.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs from ansible.module_utils.ios.config.base import ConfigBase from ansible.module_utils.ios.facts.facts import Facts - +import q class L3_Interfaces(ConfigBase, L3_InterfacesArgs): """ @@ -132,6 +132,8 @@ 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) return commands @@ -162,6 +164,8 @@ 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 @@ -246,6 +250,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 @@ -253,9 +282,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 Sub-Interface if encapsulation is not already configured @@ -264,67 +290,59 @@ def set_interface(**kwargs): module.fail_json(msg='IP routing on a LAN Sub-Interface is only allowed if Encapsulation' ' is configured over respective Sub-Interface'.format(want['name'])) # To handle L3 IPV4 configuration - ipv4 = want.get('ipv4') - if ipv4: - for each_ip in ipv4: - if each_ip.get('address') == 'dhcp': - if each_ip.get('dhcp_client') and (each_ip.get('dhcp_client') != have.get('dhcp_client') - or 'no ip address' in clear_cmds): - if each_ip.get('dhcp_hostname') and each_ip.get('dhcp_hostname') == have.get('dhcp_hostname'): - cmd = 'ip address dhcp client-id GigabitEthernet0/{} hostname {}'.\ - format(each_ip.get('dhcp_client'), each_ip.get('dhcp_hostname')) - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - else: - cmd = 'ip address dhcp client-id GigabitEthernet0/{}'.format(each_ip.get('dhcp_client')) - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - if each_ip.get('dhcp_hostname') and (each_ip.get('dhcp_hostname') != have.get('dhcp_hostname') - or 'no ip address' in clear_cmds): - cmd = 'ip address dhcp hostname {}'.format(each_ip.get('dhcp_hostname')) - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - else: - 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) + if want.get("ipv4"): + for each in want.get("ipv4"): + if each.get('address') != 'dhcp': + ip_addr_want = L3_Interfaces.validate_n_expand_ipv4(module, each) + each['address'] = ip_addr_want + + want_ipv4 = set(tuple({k:v for k,v in iteritems(address) if v is not None}.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) + if address.get('address') != 'dhcp': + cmd = "ip address {}".format(address["address"]) + if address.get("secondary"): + cmd += " secondary" + elif address.get('address') == 'dhcp': + if address.get('dhcp_client') and address.get('dhcp_hostname'): + cmd = "ip address dhcp client-id GigabitEthernet 0/{} hostname {}".format\ + (address.get('dhcp_client'),address.get('dhcp_hostname')) + elif address.get('dhcp_client') and not address.get('dhcp_hostname'): + cmd = "ip address dhcp client-id GigabitEthernet 0/{}".format(address.get('dhcp_client')) + elif not address.get('dhcp_client') and address.get('dhcp_hostname'): + cmd = "ip address dhcp hostname {}".format(address.get('dhcp_client')) + + 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: - if each_ip.get('dhcp') and (have('dhcp') or 'no ipv6 address' in clear_cmds): - cmd = 'ipv6 address dhcp' - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - elif each_ip.get('autoconfig') and (have('autoconfig') or 'no ipv6 address' in clear_cmds): - cmd = 'ipv6 address autoconfig' - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - else: - ipv6_addr = each_ip.get('address') - L3_Interfaces.validate_ipv6(ipv6_addr, module) - if have.get('ipv6') != ipv6_addr.upper() or 'no ipv6 address' in clear_cmds: - cmd = 'ipv6 address {}'.format(ipv6_addr) - L3_Interfaces._add_command_to_interface(interface, cmd, commands) + want_ipv6 = set(tuple({k:v for k,v in iteritems(address) if v is not None}.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) + L3_Interfaces.validate_ipv6(address.get('address'), module) + cmd = "ipv6 address {}".format(address.get('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('secondary') and not want.get('secondary'): - cmd = 'ip address {} secondary'.format(have.get('secondary_ipv4')) - L3_Interfaces._remove_command_from_interface(interface, cmd, commands) + 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, 'ip address', commands) if have.get('ipv6') and not want.get('ipv6'): diff --git a/module_utils/ios/facts/l3_interfaces/l3_interfaces.py b/module_utils/ios/facts/l3_interfaces/l3_interfaces.py index 51aaffa..4b33d1f 100644 --- a/module_utils/ios/facts/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/facts/l3_interfaces/l3_interfaces.py @@ -54,6 +54,7 @@ def render_config(self, spec, conf): :rtype: dictionary :returns: The generated config """ + import q config = deepcopy(spec) match = re.search(r'^(\S+)', conf) intf = match.group(1) @@ -63,32 +64,40 @@ def render_config(self, spec, conf): # populate the facts from the configuration config['name'] = normalize_interface(intf) - # Get the configured IPV4 details - ipv4 = re.findall(r"ip address (\S+.*)", conf) - for each in ipv4: - if 'secondary' in each: - config['secondary'] = True - config['secondary_ipv4'] = each.split(' secondary')[0] + ipv4 = [] + ipv4_all = re.findall(r"ip address (\S+.*)", conf) + for each in ipv4_all: + each_ipv4 = dict() + if 'secondary' not in each and 'dhcp' not in each: + each_ipv4['address'] = each + elif 'secondary' in each: + each_ipv4['secondary'] = True + each_ipv4['address'] = each.split(' secondary')[0] elif 'dhcp' in each: - config["ipv4"] = 'dhcp' + each_ipv4['address'] = 'dhcp' if 'hostname' in each and 'client-id' in each: - config['dhcp_client'] = each.split(' hostname ')[0].split('/')[-1] - config["dhcp_hostname"] = each.split(' hostname ')[-1] - if 'hostname' in each and not config["dhcp_hostname"]: - config["dhcp_hostname"] = each.split(' hostname ')[-1] - if 'client-id' in each and not config['dhcp_client']: - config['dhcp_client'] = each.split('/')[-1] - else: - config["ipv4"] = each + each_ipv4["dhcp_hostname"] = each.split(' hostname ')[-1] + each_ipv4['dhcp_client'] = each.split(' hostname ')[0].split('/')[-1] + if 'client-id' in each and not each_ipv4['dhcp_client']: + each_ipv4['dhcp_client'] = each.split('/')[-1] + if 'hostname' in each and not each_ipv4["dhcp_hostname"]: + each_ipv4["dhcp_hostname"] = each.split(' hostname ')[-1] + + 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 - if 'autoconfig' in config["ipv6"]: - config['autoconfig'] = True - elif 'dhcp' in config['ipv6']: - config['dhcp'] = True + ipv6 = [] + ipv6_all = re.findall(r"ipv6 address (\S+)", conf) + for each in ipv6_all: + each_ipv6 = dict() + if 'autoconfig' in each: + each_ipv6['autoconfig'] = True + if 'dhcp' in each: + each_ipv6['dhcp'] = True + each_ipv6['address'] = each.lower() + ipv6.append(each_ipv6) + config['ipv6'] = ipv6 encapsulation = re.search(r"encapsulation (\S+)", conf) if encapsulation: diff --git a/tests/integration/targets/ios_l3_interface/default/main.yaml b/tests/integration/targets/ios_l3_interface/default/main.yaml new file mode 100644 index 0000000..5f709c5 --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/default/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/tests/integration/targets/ios_l3_interface/meta/main.yaml b/tests/integration/targets/ios_l3_interface/meta/main.yaml new file mode 100644 index 0000000..159cea8 --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/meta/main.yaml @@ -0,0 +1,2 @@ +dependencies: + - prepare_ios_tests diff --git a/tests/integration/targets/ios_l3_interface/tasks/cli.yaml b/tests/integration/targets/ios_l3_interface/tasks/cli.yaml new file mode 100644 index 0000000..ea5c8c3 --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/tasks/cli.yaml @@ -0,0 +1,16 @@ +--- +- 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 diff --git a/tests/integration/targets/ios_l3_interface/tasks/main.yaml b/tests/integration/targets/ios_l3_interface/tasks/main.yaml new file mode 100644 index 0000000..415c99d --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/tests/integration/targets/ios_l3_interface/tests/cli/deleted.yaml b/tests/integration/targets/ios_l3_interface/tests/cli/deleted.yaml new file mode 100644 index 0000000..4e835b9 --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/tests/cli/deleted.yaml @@ -0,0 +1,29 @@ +--- +- debug: + msg: "START ios_l3_interfaces Deleted integration tests on connection={{ ansible_connection }}" + +- 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/1 + - name: GigabitEthernet0/2 + - name: GigabitEthernet0/3 + - name: GigabitEthernet0/3.100 + state: deleted + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + - name: GigabitEthernet0/2 + - name: GigabitEthernet0/3 + - name: GigabitEthernet0/3.100 +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" + +- include_tasks: reset_config.yaml diff --git a/tests/integration/targets/ios_l3_interface/tests/cli/merged.yaml b/tests/integration/targets/ios_l3_interface/tests/cli/merged.yaml new file mode 100644 index 0000000..0a79437 --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/tests/cli/merged.yaml @@ -0,0 +1,48 @@ +--- +- debug: + msg: "START ios_l3_interfaces Merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Merge provided configuration with device configuration + ios_l3_interfaces: + config: + - name: GigabitEthernet0/1 + ipv4: + - address: 192.168.0.1/24 + - name: GigabitEthernet0/2 + ipv4: + - address: 192.168.2.1/24 + secondary: True + - name: GigabitEthernet0/3 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + - name: GigabitEthernet0/3.100 + ipv6: + - address: dhcp + state: merged + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + ipv4: + - address: 192.168.0.1 255.255.255.0 + - name: GigabitEthernet0/2 + ipv4: + - address: 192.168.2.1 255.255.255.0 + secondary: true + - name: GigabitEthernet0/3 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + - name: GigabitEthernet0/3.100 + ipv6: + - address: dhcp + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" + +- include_tasks: reset_config.yaml diff --git a/tests/integration/targets/ios_l3_interface/tests/cli/overridden.yaml b/tests/integration/targets/ios_l3_interface/tests/cli/overridden.yaml new file mode 100644 index 0000000..c37318a --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/tests/cli/overridden.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: "START ios_l3_interfaces Overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Override device configuration of all interfaces with provided configuration + ios_l3_interfaces: + config: + - name: GigabitEthernet0/1 + ipv4: + - address: 192.168.0.1/24 + - name: GigabitEthernet0/2 + ipv4: + - address: dhcp + dhcp_client: 2 + dhcp_hostname: test.com + - name: GigabitEthernet0/3 + ipv6: + - address: autoconfig + state: overridden + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + ipv4: + - address: 192.168.0.1 255.255.255.0 + - name: GigabitEthernet0/2 + ipv4: + - address: dhcp + dhcp_client: 2 + dhcp_hostname: test.com + - name: GigabitEthernet0/3 + ipv6: + - address: autoconfig + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" + +- include_tasks: reset_config.yaml diff --git a/tests/integration/targets/ios_l3_interface/tests/cli/replaced.yaml b/tests/integration/targets/ios_l3_interface/tests/cli/replaced.yaml new file mode 100644 index 0000000..4242b65 --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/tests/cli/replaced.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: "START ios_l3_interfaces Replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Replaces device configuration of listed interfaces with provided configuration + ios_l3_interfaces: + config: + - name: GigabitEthernet0/1 + ipv4: + - address: 192.168.3.1/24 + secondary: True + - name: GigabitEthernet0/3.100 + ipv6: + - address: autoconfig + state: replaced + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + ipv4: + - address: 192.168.0.1 255.255.255.0 + - address: 192.168.3.1 255.255.255.0 + secondary: True + - name: GigabitEthernet0/2 + ipv4: + - address: 192.168.2.1 255.255.255.0 + secondary: true + - name: GigabitEthernet0/3 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + - name: GigabitEthernet0/3.100 + ipv6: + - address: autoconfig + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" + +- include_tasks: reset_config.yaml diff --git a/tests/integration/targets/ios_l3_interface/tests/cli/reset_config.yaml b/tests/integration/targets/ios_l3_interface/tests/cli/reset_config.yaml new file mode 100644 index 0000000..ad796ad --- /dev/null +++ b/tests/integration/targets/ios_l3_interface/tests/cli/reset_config.yaml @@ -0,0 +1,37 @@ +--- +- name: Reset initial config + cli_config: + config: | + interface GigabitEthernet0/1 + ip address 192.168.0.1 255.255.255.0 + interface GigabitEthernet0/2 + ip address 192.168.2.1 255.255.255.0 + ip address 192.168.3.1 255.255.255.0 secondary + interface GigabitEthernet0/3 + ipv6 address fd5d:12c9:2201:1::1/64 + interface GigabitEthernet0/3.100 + ipv6 address dhcp + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + ipv4: + - address: 192.168.0.1/24 + - name: GigabitEthernet0/2 + ipv4: + - address: 192.168.2.1/24 + - address: 192.168.3.1/24 + secondary: True + - name: GigabitEthernet0/3 + ipv6: + - address: fd5d:12c9:2201:1::1/64 + - name: GigabitEthernet0/3.100 + ipv6: + - address: dhcp + +- assert: + that: + - "ansible_facts.net_configuration.l3_interfaces == expected_output" From 93d57d8248ba216f375cd4bc511a8b5e9e926b85 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 28 Jun 2019 16:48:17 +0530 Subject: [PATCH 34/36] linters fix Signed-off-by: Sumit Jaiswal --- library/ios_facts.py | 1 - library/ios_l3_interface.py | 70 ++++++++++--------- module_utils/ios/argspec/facts/facts.py | 2 +- .../argspec/l3_interfaces/l3_interfaces.py | 45 ++++++------ module_utils/ios/argspec/resource/resource.py | 31 ++++---- module_utils/ios/config/base.py | 7 +- .../ios/config/l3_interfaces/l3_interfaces.py | 17 +++-- module_utils/ios/facts/base.py | 4 +- module_utils/ios/facts/facts.py | 2 +- .../ios/facts/l3_interfaces/l3_interfaces.py | 4 +- module_utils/ios/facts/resource/resource.py | 9 +-- module_utils/ios/utils/utils.py | 2 +- tox.ini | 7 ++ 13 files changed, 106 insertions(+), 95 deletions(-) create mode 100644 tox.ini diff --git a/library/ios_facts.py b/library/ios_facts.py index 47cfa0c..b87568a 100644 --- a/library/ios_facts.py +++ b/library/ios_facts.py @@ -110,4 +110,3 @@ def main(): if __name__ == '__main__': main() - diff --git a/library/ios_l3_interface.py b/library/ios_l3_interface.py index 338ad85..78794e7 100644 --- a/library/ios_l3_interface.py +++ b/library/ios_l3_interface.py @@ -1,26 +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) +# 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. +# +# ############################################## """ @@ -46,7 +45,9 @@ module: ios_l3_interfaces version_added: 2.9 short_description: Manage Layer-3 interface on Cisco IOS devices. - description: This module provides declarative management of Layer-3 interface on Cisco IOS devices. + description: + - This module provides declarative management of Layer-3 interface + on Cisco IOS devices. author: Sumit Jaiswal (@justjais) options: config: @@ -56,14 +57,15 @@ suboptions: name: description: - - Full name of the interface excluding any logical unit number, i.e. GigabitEthernet0/1. + - 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 + - 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: @@ -75,19 +77,21 @@ type: bool dhcp_client: description: - - Configures and specifies client-id to use over DHCP ip. Note, This option shall - work only when dhcp is configured as IP. + - Configures and specifies client-id to use over DHCP ip. + Note, This option shall work only when dhcp is configured + as IP. type: str dhcp_hostname: description: - - Configures and specifies value for hostname option over DHCP ip. Note, This option shall - work only when dhcp is configured as IP. + - Configures and specifies value for hostname option over + DHCP ip. Note, This option shall work only when dhcp is + configured as IP. 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 + - 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: @@ -133,11 +137,11 @@ ios_l3_interfaces: config: - name: GigabitEthernet0/1 - ipv4: + ipv4: - address: 192.168.0.1/24 secondary: True - name: GigabitEthernet0/2 - ipv4: + ipv4: - address: 192.168.0.2/24 - name: GigabitEthernet0/3 ipv6: @@ -199,7 +203,7 @@ ipv4: - address: 192.168.2.0/24 - name: GigabitEthernet0/3 - ipv4: + ipv4: - address: dhcp dhcp_client: 2 dhcp_hostname: test.com diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/ios/argspec/facts/facts.py index d05fa7f..0bee97b 100644 --- a/module_utils/ios/argspec/facts/facts.py +++ b/module_utils/ios/argspec/facts/facts.py @@ -7,7 +7,7 @@ """ -class FactsArgs(object): #pylint: disable=R0903 +class FactsArgs(object): """ The arg spec for the ios facts module """ diff --git a/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py b/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py index dfc40a0..b129fc4 100644 --- a/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py @@ -1,38 +1,37 @@ #!/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 arg spec for the ios_l3_interfaces module """ + class L3_InterfacesArgs(object): def __init__(self, **kwargs): pass ipv4addr_spec = dict(address=dict(type=str), - secondary=dict(type=bool), + secondary=dict(type=bool, default=True), dhcp_client=dict(type=str), dhcp_hostname=dict(type=str)) @@ -42,11 +41,11 @@ def __init__(self, **kwargs): 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) + '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) - } \ No newline at end of file + } diff --git a/module_utils/ios/argspec/resource/resource.py b/module_utils/ios/argspec/resource/resource.py index 0734995..2395fcc 100644 --- a/module_utils/ios/argspec/resource/resource.py +++ b/module_utils/ios/argspec/resource/resource.py @@ -4,23 +4,20 @@ # 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 arg spec for the ios_l3_interfaces module diff --git a/module_utils/ios/config/base.py b/module_utils/ios/config/base.py index 7e3e468..f3f6638 100644 --- a/module_utils/ios/config/base.py +++ b/module_utils/ios/config/base.py @@ -8,7 +8,8 @@ from ansible.module_utils.connection import Connection -class ConfigBase(object): #pylint: disable=R0205,R0903 + +class ConfigBase(object): """ The base class for all ios resource modules """ _connection = None @@ -20,5 +21,5 @@ def __init__(self, module): def _get_connection(self): if self._connection: return self._connection - self._connection = Connection(self._module._socket_path) #pylint: disable=W0212 - return self._connection \ No newline at end of file + self._connection = Connection(self._module._socket_path) + return self._connection diff --git a/module_utils/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/ios/config/l3_interfaces/l3_interfaces.py index 1b71dfd..63dfab4 100644 --- a/module_utils/ios/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/config/l3_interfaces/l3_interfaces.py @@ -11,13 +11,13 @@ """ 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.network.common.utils import is_masklen, to_netmask from ansible.module_utils.six import iteritems from ansible.module_utils.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs from ansible.module_utils.ios.config.base import ConfigBase from ansible.module_utils.ios.facts.facts import Facts -import q + class L3_Interfaces(ConfigBase, L3_InterfacesArgs): """ @@ -296,7 +296,8 @@ def set_interface(**kwargs): ip_addr_want = L3_Interfaces.validate_n_expand_ipv4(module, each) each['address'] = ip_addr_want - want_ipv4 = set(tuple({k:v for k,v in iteritems(address) if v is not None}.items()) for address in want.get("ipv4") or []) + want_ipv4 = set(tuple({k: v for k, v in iteritems(address) if v is not None}.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: @@ -307,17 +308,19 @@ def set_interface(**kwargs): cmd += " secondary" elif address.get('address') == 'dhcp': if address.get('dhcp_client') and address.get('dhcp_hostname'): - cmd = "ip address dhcp client-id GigabitEthernet 0/{} hostname {}".format\ - (address.get('dhcp_client'),address.get('dhcp_hostname')) + cmd = "ip address dhcp client-id GigabitEthernet 0/{} hostname {}".\ + format(address.get('dhcp_client'), address.get('dhcp_hostname')) elif address.get('dhcp_client') and not address.get('dhcp_hostname'): - cmd = "ip address dhcp client-id GigabitEthernet 0/{}".format(address.get('dhcp_client')) + cmd = "ip address dhcp client-id GigabitEthernet 0/{}".\ + format(address.get('dhcp_client')) elif not address.get('dhcp_client') and address.get('dhcp_hostname'): cmd = "ip address dhcp hostname {}".format(address.get('dhcp_client')) L3_Interfaces._add_command_to_interface(interface, cmd, commands) # To handle L3 IPV6 configuration - want_ipv6 = set(tuple({k:v for k,v in iteritems(address) if v is not None}.items()) for address in want.get("ipv6") or []) + want_ipv6 = set(tuple({k: v for k, v in iteritems(address) if v is not None}.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: diff --git a/module_utils/ios/facts/base.py b/module_utils/ios/facts/base.py index 48f6210..895dd49 100644 --- a/module_utils/ios/facts/base.py +++ b/module_utils/ios/facts/base.py @@ -12,7 +12,7 @@ from ansible.module_utils.six import iteritems -class FactsBase(object): #pylint: disable=R0205 +class FactsBase(object): """ The ios facts base class """ @@ -102,5 +102,5 @@ def generate_final_config(cfg_dict): for key, val in iteritems(cfg_dict): if val: - final_cfg.update({key:val}) + final_cfg.update({key: val}) return final_cfg diff --git a/module_utils/ios/facts/facts.py b/module_utils/ios/facts/facts.py index 90c9f3b..ae222b7 100644 --- a/module_utils/ios/facts/facts.py +++ b/module_utils/ios/facts/facts.py @@ -20,7 +20,7 @@ FACT_SUBSETS = {} -class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 +class Facts(FactsArgs, FactsBase): """ The fact class for ios l3 interface """ diff --git a/module_utils/ios/facts/l3_interfaces/l3_interfaces.py b/module_utils/ios/facts/l3_interfaces/l3_interfaces.py index 4b33d1f..b37f393 100644 --- a/module_utils/ios/facts/l3_interfaces/l3_interfaces.py +++ b/module_utils/ios/facts/l3_interfaces/l3_interfaces.py @@ -14,7 +14,7 @@ from ansible.module_utils.ios.facts.base import FactsBase from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface -import q + class L3_interfacesFacts(FactsBase): """ The ios l3 interfaces fact class @@ -54,7 +54,6 @@ def render_config(self, spec, conf): :rtype: dictionary :returns: The generated config """ - import q config = deepcopy(spec) match = re.search(r'^(\S+)', conf) intf = match.group(1) @@ -70,6 +69,7 @@ def render_config(self, spec, conf): each_ipv4 = dict() if 'secondary' not in each and 'dhcp' not in each: each_ipv4['address'] = each + each_ipv4['secondary'] = False elif 'secondary' in each: each_ipv4['secondary'] = True each_ipv4['address'] = each.split(' secondary')[0] diff --git a/module_utils/ios/facts/resource/resource.py b/module_utils/ios/facts/resource/resource.py index b6bbcda..5898b42 100644 --- a/module_utils/ios/facts/resource/resource.py +++ b/module_utils/ios/facts/resource/resource.py @@ -11,6 +11,7 @@ from copy import deepcopy from ansible.module_utils.ios.facts.base import FactsBase + class Facts(FactsBase): """ The {{ network_os }} {{ resource }} fact class """ @@ -23,16 +24,16 @@ def populate_facts(self, module, connection, data=None): :rtype: dictionary :returns: facts """ - if module: #just for linting purposes + if module: pass - if connection: #just for linting purposes + if connection: pass if not data: - data = "foo" # connection.get('show running-config | section ^interface') + data = "foo" # operate on a collection of resource x - config = [data] # data.split('interface ') + config = [data] objs = [] for conf in config: if conf: diff --git a/module_utils/ios/utils/utils.py b/module_utils/ios/utils/utils.py index 4cffe84..040639d 100644 --- a/module_utils/ios/utils/utils.py +++ b/module_utils/ios/utils/utils.py @@ -96,4 +96,4 @@ def get_interface_type(interface): elif interface.upper().startswith('HU'): return 'HundredGigE' else: - return 'unknown' \ No newline at end of file + return 'unknown' diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..0637ba7 --- /dev/null +++ b/tox.ini @@ -0,0 +1,7 @@ +[flake8] +# These are ignored intentionally; +# please don't submit patches that solely correct them or enable them. +ignore = E501,E125,E129,E402 +show-source = True +exclude = .venv,.tox,dist,doc,build,*.egg +max-line-length = 100 From e300b39950e68de1597a4c63a0afb06cbc8459c1 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 12 Aug 2019 16:27:22 +0530 Subject: [PATCH 35/36] adhere to ansible/ansible --- library/ios_l3_interface.py | 67 +++- .../argspec/l3_interfaces/l3_interfaces.py | 51 --- module_utils/ios/argspec/resource/resource.py | 24 -- module_utils/ios/config/base.py | 25 -- .../ios/config/l3_interfaces/l3_interfaces.py | 354 ------------------ module_utils/ios/config/resource/resource.py | 0 module_utils/ios/facts/base.py | 106 ------ module_utils/ios/facts/facts.py | 117 ------ .../ios/facts/l3_interfaces/__init__.py | 0 module_utils/ios/facts/resource/__init__.py | 0 module_utils/ios/facts/resource/resource.py | 62 --- module_utils/ios/utils/__init__.py | 0 module_utils/ios/utils/utils.py | 99 ----- module_utils/{ => network}/ios/__init__.py | 0 .../{ => network}/ios/argspec/__init__.py | 0 .../ios/argspec/facts/__init__.py | 0 .../{ => network}/ios/argspec/facts/facts.py | 16 +- .../ios/argspec/l3_interfaces/__init__.py | 0 .../argspec/l3_interfaces/l3_interfaces.py | 55 +++ .../ios/config}/__init__.py | 0 .../ios/config/l3_interfaces}/__init__.py | 0 .../ios/config/l3_interfaces/l3_interfaces.py | 276 ++++++++++++++ .../ios/facts}/__init__.py | 0 module_utils/network/ios/facts/facts.py | 61 +++ .../ios/facts/l3_interfaces}/__init__.py | 0 .../ios/facts/l3_interfaces/l3_interfaces.py | 70 ++-- .../facts => network/ios/utils}/__init__.py | 0 module_utils/network/ios/utils/utils.py | 203 ++++++++++ 28 files changed, 712 insertions(+), 874 deletions(-) delete mode 100644 module_utils/ios/argspec/l3_interfaces/l3_interfaces.py delete mode 100644 module_utils/ios/argspec/resource/resource.py delete mode 100644 module_utils/ios/config/base.py delete mode 100644 module_utils/ios/config/l3_interfaces/l3_interfaces.py delete mode 100644 module_utils/ios/config/resource/resource.py delete mode 100644 module_utils/ios/facts/base.py delete mode 100644 module_utils/ios/facts/facts.py delete mode 100644 module_utils/ios/facts/l3_interfaces/__init__.py delete mode 100644 module_utils/ios/facts/resource/__init__.py delete mode 100644 module_utils/ios/facts/resource/resource.py delete mode 100644 module_utils/ios/utils/__init__.py delete mode 100644 module_utils/ios/utils/utils.py rename module_utils/{ => network}/ios/__init__.py (100%) rename module_utils/{ => network}/ios/argspec/__init__.py (100%) rename module_utils/{ => network}/ios/argspec/facts/__init__.py (100%) rename module_utils/{ => network}/ios/argspec/facts/facts.py (56%) rename module_utils/{ => network}/ios/argspec/l3_interfaces/__init__.py (100%) create mode 100644 module_utils/network/ios/argspec/l3_interfaces/l3_interfaces.py rename module_utils/{ios/argspec/resource => network/ios/config}/__init__.py (100%) rename module_utils/{ios/config => network/ios/config/l3_interfaces}/__init__.py (100%) create mode 100644 module_utils/network/ios/config/l3_interfaces/l3_interfaces.py rename module_utils/{ios/config/l3_interfaces => network/ios/facts}/__init__.py (100%) create mode 100644 module_utils/network/ios/facts/facts.py rename module_utils/{ios/config/resource => network/ios/facts/l3_interfaces}/__init__.py (100%) rename module_utils/{ => network}/ios/facts/l3_interfaces/l3_interfaces.py (58%) rename module_utils/{ios/facts => network/ios/utils}/__init__.py (100%) create mode 100644 module_utils/network/ios/utils/utils.py diff --git a/library/ios_l3_interface.py b/library/ios_l3_interface.py index 78794e7..6d35686 100644 --- a/library/ios_l3_interface.py +++ b/library/ios_l3_interface.py @@ -30,15 +30,11 @@ __metaclass__ = type -GENERATOR_VERSION = '1.0' ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'network'} -NETWORK_OS = "ios" -RESOURCE = "l3_interfaces" -COPYRIGHT = "Copyright 2019 Red Hat" DOCUMENTATION = """ --- @@ -80,6 +76,7 @@ - Configures and specifies client-id to use over DHCP ip. Note, This option shall work only when dhcp is configured as IP. + - GigabitEthernet interface number type: str dhcp_hostname: description: @@ -314,11 +311,10 @@ # encapsulation dot1Q 20 # ip address 192.168.0.2 255.255.255.0 -- name: Delete attributes of given interfaces (Note: This won't delete the interface itself) +- name: Delete attributes of given interfaces (NOTE: This won't delete the interface itself) ios_l3_interfaces: config: - name: GigabitEthernet0/2 - - name: GigabitEthernet0/3 - name: GigabitEthernet0/3.100 operation: deleted @@ -336,7 +332,57 @@ # no ip address # interface GigabitEthernet0/3 # description Configured by Ansible Network +# ip address 192.168.0.1 255.255.255.0 +# shutdown +# duplex full +# speed 10 +# ipv6 address FD5D:12C9:2201:1::1/64 +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 + +# Using Deleted without config +# +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# ip address 192.0.2.10 255.255.255.0 +# shutdown +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured by Ansible Network +# ip address 192.168.1.0 255.255.255.0 +# interface GigabitEthernet0/3 +# description Configured by Ansible Network +# ip address 192.168.0.1 255.255.255.0 +# shutdown +# duplex full +# speed 10 +# ipv6 address FD5D:12C9:2201:1::1/64 +# interface GigabitEthernet0/3.100 +# encapsulation dot1Q 20 +# ip address 192.168.0.2 255.255.255.0 + +- name: "Delete L3 attributes of all interfaces together (NOTE: This won't delete the interface itself)" + ios_l3_interfaces: + operation: deleted + +# After state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# no ip address +# shutdown +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured by Ansible Network # no ip address +# interface GigabitEthernet0/3 +# description Configured by Ansible Network # shutdown # duplex full # speed 10 @@ -349,20 +395,23 @@ 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/1', 'command 2', 'command 3'] """ from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.ios.config.l3_interfaces.l3_interfaces import L3_Interfaces +from ansible.module_utils.network.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs +from ansible.module_utils.network.ios.config.l3_interfaces.l3_interfaces import L3_Interfaces def main(): @@ -370,7 +419,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/ios/argspec/l3_interfaces/l3_interfaces.py b/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py deleted file mode 100644 index b129fc4..0000000 --- a/module_utils/ios/argspec/l3_interfaces/l3_interfaces.py +++ /dev/null @@ -1,51 +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 ios_l3_interfaces module -""" - - -class L3_InterfacesArgs(object): - - def __init__(self, **kwargs): - pass - - ipv4addr_spec = dict(address=dict(type=str), - secondary=dict(type=bool, default=True), - dhcp_client=dict(type=str), - dhcp_hostname=dict(type=str)) - - ipv6addr_spec = dict(address=dict(type=str), - autoconfig=dict(type=bool), - dhcp=dict(type=bool)) - - 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/ios/argspec/resource/resource.py b/module_utils/ios/argspec/resource/resource.py deleted file mode 100644 index 2395fcc..0000000 --- a/module_utils/ios/argspec/resource/resource.py +++ /dev/null @@ -1,24 +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 ios_l3_interfaces module -""" diff --git a/module_utils/ios/config/base.py b/module_utils/ios/config/base.py deleted file mode 100644 index f3f6638..0000000 --- a/module_utils/ios/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 ios resource modules -""" - -from ansible.module_utils.connection import Connection - - -class ConfigBase(object): - """ The base class for all ios 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) - return self._connection diff --git a/module_utils/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/ios/config/l3_interfaces/l3_interfaces.py deleted file mode 100644 index 63dfab4..0000000 --- a/module_utils/ios/config/l3_interfaces/l3_interfaces.py +++ /dev/null @@ -1,354 +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 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_masklen, to_netmask -from ansible.module_utils.six import iteritems - -from ansible.module_utils.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs -from ansible.module_utils.ios.config.base import ConfigBase -from ansible.module_utils.ios.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 - 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 - 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 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 Sub-Interface if encapsulation is not already configured - if '.' in want['name']: - if not have.get('encapsulation'): - module.fail_json(msg='IP routing on a LAN Sub-Interface is only allowed if Encapsulation' - ' is configured over respective Sub-Interface'.format(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 = L3_Interfaces.validate_n_expand_ipv4(module, each) - each['address'] = ip_addr_want - - want_ipv4 = set(tuple({k: v for k, v in iteritems(address) if v is not None}.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) - if address.get('address') != 'dhcp': - cmd = "ip address {}".format(address["address"]) - if address.get("secondary"): - cmd += " secondary" - elif address.get('address') == 'dhcp': - if address.get('dhcp_client') and address.get('dhcp_hostname'): - cmd = "ip address dhcp client-id GigabitEthernet 0/{} hostname {}".\ - format(address.get('dhcp_client'), address.get('dhcp_hostname')) - elif address.get('dhcp_client') and not address.get('dhcp_hostname'): - cmd = "ip address dhcp client-id GigabitEthernet 0/{}".\ - format(address.get('dhcp_client')) - elif not address.get('dhcp_client') and address.get('dhcp_hostname'): - cmd = "ip address dhcp hostname {}".format(address.get('dhcp_client')) - - L3_Interfaces._add_command_to_interface(interface, cmd, commands) - - # To handle L3 IPV6 configuration - want_ipv6 = set(tuple({k: v for k, v in iteritems(address) if v is not None}.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) - L3_Interfaces.validate_ipv6(address.get('address'), module) - cmd = "ipv6 address {}".format(address.get('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, 'ip 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/ios/config/resource/resource.py b/module_utils/ios/config/resource/resource.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/ios/facts/base.py b/module_utils/ios/facts/base.py deleted file mode 100644 index 895dd49..0000000 --- a/module_utils/ios/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 ios 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 ios 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/ios/facts/facts.py b/module_utils/ios/facts/facts.py deleted file mode 100644 index ae222b7..0000000 --- a/module_utils/ios/facts/facts.py +++ /dev/null @@ -1,117 +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 ios -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.ios.argspec.facts.facts import FactsArgs -from ansible.module_utils.ios.facts.base import FactsBase -from ansible.module_utils.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs -from ansible.module_utils.ios.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts - - -FACT_SUBSETS = {} - - -class Facts(FactsArgs, FactsBase): - """ The fact class for ios l3 interface - """ - - 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 ios - :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/ios/facts/l3_interfaces/__init__.py b/module_utils/ios/facts/l3_interfaces/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/ios/facts/resource/__init__.py b/module_utils/ios/facts/resource/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/ios/facts/resource/resource.py b/module_utils/ios/facts/resource/resource.py deleted file mode 100644 index 5898b42..0000000 --- a/module_utils/ios/facts/resource/resource.py +++ /dev/null @@ -1,62 +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.ios.facts.base import FactsBase - - -class Facts(FactsBase): - """ The {{ network_os }} {{ resource }} fact class - """ - - def populate_facts(self, module, connection, data=None): - """ Populate the facts for {{ resource }} - :param module: the module instance - :param connection: the device connection - :param data: previously collected conf - :rtype: dictionary - :returns: facts - """ - if module: - pass - if connection: - pass - - if not data: - data = "foo" - - # operate on a collection of resource x - config = [data] - objs = [] - for conf in config: - if conf: - obj = self.render_config(self.generated_spec, conf) - if obj: - objs.append(obj) - facts = {} - if objs: - facts['{{ resource }}'] = 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/ios/utils/__init__.py b/module_utils/ios/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/ios/utils/utils.py b/module_utils/ios/utils/utils.py deleted file mode 100644 index 040639d..0000000 --- a/module_utils/ios/utils/utils.py +++ /dev/null @@ -1,99 +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('te'): - if_type = 'TenGigabitEthernet' - elif name.lower().startswith('fa'): - if_type = 'FastEthernet' - elif name.lower().startswith('fo'): - if_type = 'FortyGigabitEthernet' - elif name.lower().startswith('long'): - if_type = 'LongReachEthernet' - 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('TE'): - return 'TenGigabitEthernet' - elif interface.upper().startswith('FA'): - return 'FastEthernet' - elif interface.upper().startswith('FO'): - return 'FortyGigabitEthernet' - elif interface.upper().startswith('LON'): - return 'LongReachEthernet' - 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' - else: - return 'unknown' diff --git a/module_utils/ios/__init__.py b/module_utils/network/ios/__init__.py similarity index 100% rename from module_utils/ios/__init__.py rename to module_utils/network/ios/__init__.py diff --git a/module_utils/ios/argspec/__init__.py b/module_utils/network/ios/argspec/__init__.py similarity index 100% rename from module_utils/ios/argspec/__init__.py rename to module_utils/network/ios/argspec/__init__.py diff --git a/module_utils/ios/argspec/facts/__init__.py b/module_utils/network/ios/argspec/facts/__init__.py similarity index 100% rename from module_utils/ios/argspec/facts/__init__.py rename to module_utils/network/ios/argspec/facts/__init__.py diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/network/ios/argspec/facts/facts.py similarity index 56% rename from module_utils/ios/argspec/facts/facts.py rename to module_utils/network/ios/argspec/facts/facts.py index 0bee97b..3163064 100644 --- a/module_utils/ios/argspec/facts/facts.py +++ b/module_utils/network/ios/argspec/facts/facts.py @@ -1,11 +1,15 @@ -#!/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 arg spec for the ios facts module. """ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + class FactsArgs(object): """ The arg spec for the ios facts module @@ -16,7 +20,13 @@ def __init__(self, **kwargs): choices = [ 'all', + '!all', + 'interfaces', + '!interfaces' + 'l2_interfaces', + '!l2_interfaces', 'l3_interfaces', + '!l3_interfaces' ] argument_spec = { diff --git a/module_utils/ios/argspec/l3_interfaces/__init__.py b/module_utils/network/ios/argspec/l3_interfaces/__init__.py similarity index 100% rename from module_utils/ios/argspec/l3_interfaces/__init__.py rename to module_utils/network/ios/argspec/l3_interfaces/__init__.py diff --git a/module_utils/network/ios/argspec/l3_interfaces/l3_interfaces.py b/module_utils/network/ios/argspec/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..58e204c --- /dev/null +++ b/module_utils/network/ios/argspec/l3_interfaces/l3_interfaces.py @@ -0,0 +1,55 @@ +# +# -*- 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'}, + 'dhcp_client': {'type': int}, + 'dhcp_hostname': {'type': 'str'}}}, + 'ipv6': {'element': 'dict', + 'type': 'list', + 'options': {'address': {'type': 'str'}, + 'autoconfig': {'type': 'bool'}, + 'dhcp': {'type': 'str'}}} + }, + 'type': 'list'}, + 'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} diff --git a/module_utils/ios/argspec/resource/__init__.py b/module_utils/network/ios/config/__init__.py similarity index 100% rename from module_utils/ios/argspec/resource/__init__.py rename to module_utils/network/ios/config/__init__.py diff --git a/module_utils/ios/config/__init__.py b/module_utils/network/ios/config/l3_interfaces/__init__.py similarity index 100% rename from module_utils/ios/config/__init__.py rename to module_utils/network/ios/config/l3_interfaces/__init__.py diff --git a/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py new file mode 100644 index 0000000..4f8741d --- /dev/null +++ b/module_utils/network/ios/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 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 __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.six import iteritems +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.ios.facts.facts import Facts +from ansible.module_utils.network.ios.utils.utils import get_interface_type, dict_diff +from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list +from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface +from ansible.module_utils.network.ios.utils.utils import validate_n_expand_ipv4, validate_ipv6 +import q + +class L3_Interfaces(ConfigBase): + """ + 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 + """ + 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: + if '.' in interface['name']: + 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 = [] + + 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(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)) + # 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: + if '.' in interface['name']: + 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" + elif ipv4_dict.get('address') == 'dhcp': + if ipv4_dict.get('dhcp_client') is not None and ipv4_dict.get('dhcp_hostname'): + cmd = "ip address dhcp client-id GigabitEthernet 0/{0} hostname {1}"\ + .format(ipv4_dict.get('dhcp_client'), ipv4_dict.get('dhcp_hostname')) + elif ipv4_dict.get('dhcp_client') and not ipv4_dict.get('dhcp_hostname'): + cmd = "ip address dhcp client-id GigabitEthernet 0/{0}"\ + .format(ipv4_dict.get('dhcp_client')) + elif not ipv4_dict.get('dhcp_client') and ipv4_dict.get('dhcp_hostname'): + cmd = "ip address dhcp hostname {}".format(ipv4_dict.get('dhcp_client')) + 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, 'ip 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/ios/config/l3_interfaces/__init__.py b/module_utils/network/ios/facts/__init__.py similarity index 100% rename from module_utils/ios/config/l3_interfaces/__init__.py rename to module_utils/network/ios/facts/__init__.py diff --git a/module_utils/network/ios/facts/facts.py b/module_utils/network/ios/facts/facts.py new file mode 100644 index 0000000..ff12676 --- /dev/null +++ b/module_utils/network/ios/facts/facts.py @@ -0,0 +1,61 @@ +# +# -*- 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.ios.facts.interfaces.interfaces import InterfacesFacts +from ansible.module_utils.network.ios.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts +from ansible.module_utils.network.ios.facts.legacy.base import Default, Hardware, Interfaces, Config + + +FACT_LEGACY_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config +) + +FACT_RESOURCE_SUBSETS = dict( + interfaces=InterfacesFacts, + 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 \ No newline at end of file diff --git a/module_utils/ios/config/resource/__init__.py b/module_utils/network/ios/facts/l3_interfaces/__init__.py similarity index 100% rename from module_utils/ios/config/resource/__init__.py rename to module_utils/network/ios/facts/l3_interfaces/__init__.py diff --git a/module_utils/ios/facts/l3_interfaces/l3_interfaces.py b/module_utils/network/ios/facts/l3_interfaces/l3_interfaces.py similarity index 58% rename from module_utils/ios/facts/l3_interfaces/l3_interfaces.py rename to module_utils/network/ios/facts/l3_interfaces/l3_interfaces.py index b37f393..ffe2124 100644 --- a/module_utils/ios/facts/l3_interfaces/l3_interfaces.py +++ b/module_utils/network/ios/facts/l3_interfaces/l3_interfaces.py @@ -1,7 +1,8 @@ -#!/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 ios interfaces fact class It is in this file the configuration is collected from the device @@ -9,21 +10,39 @@ based on the configuration. """ -import re -from copy import deepcopy +from __future__ import absolute_import, division, print_function +__metaclass__ = type + -from ansible.module_utils.ios.facts.base import FactsBase -from ansible.module_utils.ios.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.ios.utils.utils import get_interface_type, normalize_interface +from ansible.module_utils.network.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs -class L3_interfacesFacts(FactsBase): +class L3_InterfacesFacts(object): """ The ios 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 l3 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): """ @@ -69,20 +92,19 @@ def render_config(self, spec, conf): each_ipv4 = dict() if 'secondary' not in each and 'dhcp' not in each: each_ipv4['address'] = each - each_ipv4['secondary'] = False elif 'secondary' in each: - each_ipv4['secondary'] = True each_ipv4['address'] = each.split(' secondary')[0] + each_ipv4['secondary'] = True elif 'dhcp' in each: each_ipv4['address'] = 'dhcp' - if 'hostname' in each and 'client-id' in each: + if 'hostname' in each: each_ipv4["dhcp_hostname"] = each.split(' hostname ')[-1] - each_ipv4['dhcp_client'] = each.split(' hostname ')[0].split('/')[-1] - if 'client-id' in each and not each_ipv4['dhcp_client']: - each_ipv4['dhcp_client'] = each.split('/')[-1] + if 'client-id' in each: + each_ipv4['dhcp_client'] = int(each.split(' hostname ')[0].split('/')[-1]) + if 'client-id' in each and each_ipv4['dhcp_client'] is None: + each_ipv4['dhcp_client'] = int(each.split('/')[-1]) if 'hostname' in each and not each_ipv4["dhcp_hostname"]: each_ipv4["dhcp_hostname"] = each.split(' hostname ')[-1] - ipv4.append(each_ipv4) config['ipv4'] = ipv4 @@ -99,8 +121,8 @@ def render_config(self, spec, conf): ipv6.append(each_ipv6) config['ipv6'] = ipv6 - encapsulation = re.search(r"encapsulation (\S+)", conf) - if encapsulation: - config['encapsulation'] = True + # encapsulation = re.search(r"encapsulation (\S+)", conf) + # if encapsulation: + # config['encapsulation'] = True - return self.generate_final_config(config) + return utils.remove_empties(config) diff --git a/module_utils/ios/facts/__init__.py b/module_utils/network/ios/utils/__init__.py similarity index 100% rename from module_utils/ios/facts/__init__.py rename to module_utils/network/ios/utils/__init__.py diff --git a/module_utils/network/ios/utils/utils.py b/module_utils/network/ios/utils/utils.py new file mode 100644 index 0000000..1b24444 --- /dev/null +++ b/module_utils/network/ios/utils/utils.py @@ -0,0 +1,203 @@ +#!/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 __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.common.utils import is_masklen, to_netmask + + +def remove_command_from_config_list(interface, cmd, commands): + # 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('te'): + if_type = 'TenGigabitEthernet' + elif name.lower().startswith('fa'): + if_type = 'FastEthernet' + elif name.lower().startswith('fo'): + if_type = 'FortyGigabitEthernet' + elif name.lower().startswith('long'): + if_type = 'LongReachEthernet' + 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('TE'): + return 'TenGigabitEthernet' + elif interface.upper().startswith('FA'): + return 'FastEthernet' + elif interface.upper().startswith('FO'): + return 'FortyGigabitEthernet' + elif interface.upper().startswith('LON'): + return 'LongReachEthernet' + 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' + else: + return 'unknown' From 0f383e267ad69ef38c2e359a85a3df1d6c48c5f6 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 19 Aug 2019 17:06:14 +0530 Subject: [PATCH 36/36] fix l3 issue --- ...s_l3_interface.py => ios_l3_interfaces.py} | 0 .../ios/config/l3_interfaces/l3_interfaces.py | 4 ++- module_utils/network/ios/utils/utils.py | 25 +++++++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) rename library/{ios_l3_interface.py => ios_l3_interfaces.py} (100%) diff --git a/library/ios_l3_interface.py b/library/ios_l3_interfaces.py similarity index 100% rename from library/ios_l3_interface.py rename to library/ios_l3_interfaces.py diff --git a/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py b/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py index 4f8741d..330e50a 100644 --- a/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py +++ b/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py @@ -125,11 +125,13 @@ def _state_replaced(self, want, have, module): commands.extend(self._set_config(interface, each, module)) continue have_dict = filter_dict_having_none_value(interface, each) + q(have_dict) commands.extend(self._clear_config(dict(), have_dict)) commands.extend(self._set_config(interface, each, module)) # Remove the duplicate interface call commands = remove_duplicate_interface(commands) - + q(commands) + commands=[] return commands def _state_overridden(self, want, have, module): diff --git a/module_utils/network/ios/utils/utils.py b/module_utils/network/ios/utils/utils.py index 1b24444..ebf11ab 100644 --- a/module_utils/network/ios/utils/utils.py +++ b/module_utils/network/ios/utils/utils.py @@ -60,13 +60,34 @@ def filter_dict_having_none_value(want, have): test_dict = dict() test_key_dict = dict() test_dict['name'] = want.get('name') - for k, v in iteritems(want): + diff_ip = False + want_ip = '' + for k, v in want.items(): if isinstance(v, dict): - for key, value in iteritems(v): + for key, value in v.items(): if value is None: dict_val = have.get(k).get(key) test_key_dict.update({key: dict_val}) test_dict.update({k: test_key_dict}) + if isinstance(v, list): + for key, value in v[0].items(): + if value is None: + dict_val = have.get(k).get(key) + test_key_dict.update({key: dict_val}) + # below conditions are added to check if secondary + # IP is configured, if yes then delete the already + # configured IP if want and have IP is different + # else if it's same no need to delete + if isinstance(value, str): + want_ip = value.split('/') + have_ip = have.get('ipv4') + if len(want_ip) > 1 and have_ip: + have_ip = have_ip[0]['address'].split(' ')[0] + if have_ip != want_ip[0]: + diff_ip = True + if key == 'secondary' and value == True and diff_ip == True: + test_key_dict.update({key: value}) + test_dict.update({k: test_key_dict}) if v is None: val = have.get(k) test_dict.update({k: val})