From 287a7a0a57092b453e08818dc5cd4fc039febab1 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:25:10 +0530 Subject: [PATCH 01/36] iosl2 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 99a41d9fe4191f39a2aa2c1e88be898b7a8c374c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:25:20 +0530 Subject: [PATCH 02/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- library/ios_facts.py | 85 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 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..d10e541 --- /dev/null +++ b/library/ios_facts.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# {{ rm['COPYRIGHT'] }} +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The module file for {{ network_os }}_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: Collect facts from remote devices running Cisco IOS. +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 IOS Version 15.6(3)M2 on VIRL +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, and net_configuration_. Can specify a + list of values to include a larger subset. Values can also be used + with an initial C(M(!)) to specify that a specific subset should + not be collected. + required: false + default: 'all' + version_added: "2.2" +""" + +EXAMPLES = """ +# Gather all facts +- ios_facts: + gather_subset: all +# Collect only the interfaces and default facts +- ios_facts: + gather_subset: + - config +# Do not collect interfaces facts +- ios_facts: + gather_subset: + - "!hardware" +""" + +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 = list() + + connection = Connection(module._socket_path) #pylint: disable=W0212 + gather_subset = module.params['gather_subset'] + ansible_facts = Facts().get_facts(module, connection, gather_subset) + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + +if __name__ == '__main__': + main() From 9ac6f4c27a121e189737b1b45dfb543b7db7d54e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:25:35 +0530 Subject: [PATCH 03/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- library/ios_l2_interface.py | 333 ++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 library/ios_l2_interface.py diff --git a/library/ios_l2_interface.py b/library/ios_l2_interface.py new file mode 100644 index 0000000..eaf5ed2 --- /dev/null +++ b/library/ios_l2_interface.py @@ -0,0 +1,333 @@ +#!/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_l2_resource +""" + +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 = "l2_interfaces" +COPYRIGHT = "Copyright 2019 Red Hat" + +DOCUMENTATION = """ +--- + module: ios_l2_interfaces + version_added: 2.9 + short_description: Manage Layer-2 interface on Cisco IOS devices. + description: This module provides declarative management of Layer-2 interface on Cisco IOS devices. + author: Sumit Jaiswal (@justjais) + options: + config: + description: A dictionary of Layer-2 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 + access: + description: + - Switchport mode access command to configure the interface as a layer 2 access. + suboptions: + vlan: + description: + - Configure given VLAN in access port. It's used as the access VLAN ID. + type: str + trunk: + description: + - Switchport mode trunk command to configure the interface as a Layer 2 trunk. + Note The encapsulation is always set to dot1q. + suboptions: + allowed_vlan: + description: + - List of allowed VLANs in a given trunk port. These are the only VLANs that will be + configured on the trunk. + type: list + native_vlan: + description: + - Native VLAN to be configured in trunk port. It's used as the trunk native VLAN ID. + type: str + encapsulation: + description: + - Trunking encapsulation when interface is in trunking mode. + choices: ['dot1q','isl','negotiate'] + type: str + pruning_vlan: + description: + - Pruning VLAN to be configured in trunk port. It's used as the trunk pruning VLAN ID. + type: list + state: + choices: + - merged + - replaced + - overridden + - deleted + default: merged + description: + - The state the configuration should be left in + type: str +""" + +EXAMPLES = """ +--- + +# Using merged + +# Before state: +# ------------- +# +# viosl2#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# negotiation auto +# interface GigabitEthernet0/2 +# description This is test +# switchport access vlan 20 +# switchport mode access +# media-type rj45 +# negotiation auto + +- name: Merge provided configuration with device configuration + ios_interfaces: + config: + - name: GigabitEthernet0/1 + access: + - vlan: 10 + - name: GigabitEthernet0/2 + trunk: + - allowed_vlan: + - 20 + - 40 + native_vlan: 20 + pruning_vlan: + - 10 + encapsulation: dot1q + state: merged + +# After state: +# ------------ +# +# viosl2#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# switchport access vlan 10 +# switchport mode access +# negotiation auto +# interface GigabitEthernet0/2 +# description This is test +# switchport trunk allowed vlan 20,40 +# switchport trunk encapsulation dot1q +# switchport trunk native vlan 20 +# switchport trunk pruning vlan 10 +# switchport mode trunk +# media-type rj45 +# negotiation auto + +# Using replaced + +# Before state: +# ------------- +# +# viosl2#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# switchport access vlan 20 +# switchport mode access +# negotiation auto +# interface GigabitEthernet0/2 +# description This is test +# switchport access vlan 20 +# switchport mode access +# media-type rj45 +# negotiation auto + +- name: Replaces device configuration of listed l2 interfaces with provided configuration + ios_interfaces: + config: + - name: GigabitEthernet0/2 + trunk: + - allowed_vlan: + - 20 + - 40 + native_vlan: 20 + pruning_vlan: + - 10 + encapsulation: isl + state: replaced + +# After state: +# ------------- +# +# viosl2#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# switchport access vlan 20 +# switchport mode access +# negotiation auto +# interface GigabitEthernet0/2 +# description This is test +# switchport trunk allowed vlan 20,40 +# switchport trunk encapsulation isl +# switchport trunk native vlan 20 +# switchport trunk pruning vlan 10 +# switchport mode trunk +# media-type rj45 +# negotiation auto + +# Using overridden + +# Before state: +# ------------- +# +# viosl2#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# switchport trunk encapsulation dot1q +# switchport trunk native vlan 20 +# switchport mode trunk +# negotiation auto +# interface GigabitEthernet0/2 +# description This is test +# switchport access vlan 20 +# switchport trunk encapsulation dot1q +# switchport trunk native vlan 20 +# switchport mode trunk +# media-type rj45 +# negotiation auto + +- name: Override device configuration of all l2 interfaces with provided configuration + ios_interfaces: + config: + - name: GigabitEthernet0/2 + access: + - vlan: 20 + state: overridden + +# After state: +# ------------- +# +# viosl2#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# negotiation auto +# interface GigabitEthernet0/2 +# description This is test +# switchport access vlan 20 +# switchport mode access +# media-type rj45 +# negotiation auto + +# Using Deleted + +# Before state: +# ------------- +# +# viosl2#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# switchport access vlan 20 +# switchport mode access +# negotiation auto +# interface GigabitEthernet0/2 +# description This is test +# switchport access vlan 20 +# switchport trunk allowed vlan 20,40,60,80 +# switchport trunk encapsulation dot1q +# switchport trunk native vlan 10 +# switchport trunk pruning vlan 10 +# switchport mode trunk +# media-type rj45 +# negotiation auto + +- name: Delete IOS L2 interfaces as in given arguments + ios_interfaces: + config: + - name: GigabitEthernet0/1 + - name: GigabitEthernet0/2 + state: deleted + +# After state: +# ------------- +# +# viosl2#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# negotiation auto +# interface GigabitEthernet0/2 +# description This is test +# media-type rj45 +# negotiation auto + +""" + +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.l2_interfaces.l2_interfaces import L2_Interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=L2_Interfaces.argument_spec, + supports_check_mode=True) + + result = L2_Interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() From 6206be3bf4c3239f65d99ea64e73baf86ca99270 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:25:46 +0530 Subject: [PATCH 04/36] iosl2 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 d80ba0b22935b22b20ddbb19b638cbc6a86e4035 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:25:54 +0530 Subject: [PATCH 05/36] iosl2 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 d08f735f72ea034b63f76407cde07f86e5ae4da1 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:26:05 +0530 Subject: [PATCH 06/36] iosl2 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 622c17ad9c2bda7971b26fd42d35352618d1b40e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:26:14 +0530 Subject: [PATCH 07/36] iosl2 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 6bed031e161afaecc364de63c4d5acb7be21e17f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:26:28 +0530 Subject: [PATCH 08/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/facts/facts.py | 23 +++++++++++++++++++++++ 1 file changed, 23 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..d1dbaea --- /dev/null +++ b/module_utils/ios/argspec/facts/facts.py @@ -0,0 +1,23 @@ +#!/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 {{ network_os }} facts module. +""" + +class FactsArgs(object): #pylint: disable=R0903 + """ The arg spec for the {{ network_os }} facts module + """ + + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'net_configuration_interfaces', + ] + + argument_spec = { + 'gather_subset': dict(default=['all'], choices=choices, type='list') + } From ad549bf0564e299b14e5a476a509c77996ca0234 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:26:40 +0530 Subject: [PATCH 09/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/l2_interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/argspec/l2_interfaces/__init__.py diff --git a/module_utils/ios/argspec/l2_interfaces/__init__.py b/module_utils/ios/argspec/l2_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From 5838a5c25dde381fdcd7c4488ed9e2c9895f00b4 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:26:49 +0530 Subject: [PATCH 10/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- .../argspec/l2_interfaces/l2_interfaces.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 module_utils/ios/argspec/l2_interfaces/l2_interfaces.py diff --git a/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py b/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py new file mode 100644 index 0000000..b1226f2 --- /dev/null +++ b/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py @@ -0,0 +1,51 @@ +#!/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_interfaces module +""" + +class L2_InterfacesArgs(object): + + def __init__(self, **kwargs): + pass + + access_arg_spec = dict(vlan=dict(type=str, required=True)) + trunk_arg_spec = dict(allowed_vlan=dict(type=list, required=True), + encapsulation=dict(type=str), + native_vlan=dict(type=str), + pruning_vlan=dict(type=list) + ) + + config_spec = { + 'name': dict(type='str', required=True), + 'access': dict(type=list, element=dict, options=access_arg_spec), + 'trunk': dict(element=dict, type=list, options=trunk_arg_spec) + } + + argument_spec = { + 'state': dict(default='merged', choices=['merged', 'replaced', 'overridden', 'deleted']), + 'config': dict(type='list', elements='dict', options=config_spec) + } + From b9c630684afb95df673d8ba58c39de4c5346b7e0 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:26:58 +0530 Subject: [PATCH 11/36] iosl2 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 6c4271f7f98aba66379ef18f7abbbe87ead8dfe0 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:27:16 +0530 Subject: [PATCH 12/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/resource/resource.py | 29 +++++++++++++++++++ 1 file changed, 29 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..3b4e191 --- /dev/null +++ b/module_utils/ios/argspec/resource/resource.py @@ -0,0 +1,29 @@ +#!/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 {{ network_os }}_{{ resource }} module +""" + + From d3d9f4deed25d96ff99e2479b51569031aac3cc8 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:27:27 +0530 Subject: [PATCH 13/36] iosl2 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 49b7ee55d177ca8511aea8ea31bfee58d19ad59f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:27:36 +0530 Subject: [PATCH 14/36] iosl2 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..7d80ea6 --- /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 {{ network_os }} resource modules +""" + +from ansible.module_utils.connection import Connection + +class ConfigBase(object): #pylint: disable=R0205,R0903 + """ The base class for all {{ network_os }} resource modules + """ + _connection = None + + def __init__(self, module): + self._module = module + self._connection = self._get_connection() + + def _get_connection(self): + if self._connection: + return self._connection + self._connection = Connection(self._module._socket_path) #pylint: disable=W0212 + return self._connection From f71da16701899a6c458e3c504f2039a142981cf9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:27:46 +0530 Subject: [PATCH 15/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/l2_interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/config/l2_interfaces/__init__.py diff --git a/module_utils/ios/config/l2_interfaces/__init__.py b/module_utils/ios/config/l2_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From ae96b0d4811b562ec788d6ab798361b785497b3e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:27:55 +0530 Subject: [PATCH 16/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- .../ios/config/l2_interfaces/l2_interfaces.py | 338 ++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 module_utils/ios/config/l2_interfaces/l2_interfaces.py diff --git a/module_utils/ios/config/l2_interfaces/l2_interfaces.py b/module_utils/ios/config/l2_interfaces/l2_interfaces.py new file mode 100644 index 0000000..b3a80bf --- /dev/null +++ b/module_utils/ios/config/l2_interfaces/l2_interfaces.py @@ -0,0 +1,338 @@ +#!/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 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.six import iteritems +from ansible.module_utils.network.common.utils import to_list + +from ansible.module_utils.ios.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs +from ansible.module_utils.ios.config.base import ConfigBase +from ansible.module_utils.ios.facts.facts import Facts +from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list + + +class L2_Interfaces(ConfigBase, L2_InterfacesArgs): + """ + The ios_interfaces class + """ + + gather_subset = [ + 'net_configuration_interfaces', + ] + + params = ('access', 'trunk') + trunk_params = ('encapsulation', 'pruning_vlan', 'native_vlan', 'allowed_vlan') + trunk_cmds = {'encapsulation': 'switchport trunk encapsulation', 'pruning_vlan': 'switchport trunk pruning vlan', + 'native_vlan': 'switchport trunk native vlan', 'allowed_vlan': 'switchport trunk allowed vlan'} + + def get_interfaces_facts(self): + """ Get the 'facts' (the current configuration) + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts = Facts().get_facts(self._module, self._connection, self.gather_subset) + interfaces_facts = facts['net_configuration'].get('interfaces') + if not interfaces_facts: + return [] + return interfaces_facts + + def execute_module(self): + """ Execute the module + :rtype: A dictionary + :returns: The result from moduel execution + """ + result = {'changed': False} + commands = [] + warnings = [] + + existing_facts = self.get_interfaces_facts() + commands.extend(self.set_config(existing_facts)) + result['before'] = existing_facts + if commands: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + result['commands'] = commands + + interfaces_facts = self.get_interfaces_facts() + + if result['changed']: + result['after'] = interfaces_facts + result['warnings'] = warnings + return result + + def set_config(self, existing_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 deisred configuration + """ + want = self._module.params['config'] + + for w in want: + w.update({'name': normalize_interface(w['name'])}) + have = existing_facts#self.get_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 deisred configuration + """ + commands = [] + state = self._module.params['state'] + + if state == 'overridden': + kwargs = {'want': want, 'have': have, 'module': self._module} + commands = L2_Interfaces._state_overridden(**kwargs) + else: + for w in want: + name = w['name'] + interface_type = get_interface_type(name) + obj_in_have = search_obj_in_list(name, have) + kwargs = {'want': w, 'have': obj_in_have, 'type': interface_type, 'module': self._module} + if state == 'deleted': + commands.extend(L2_Interfaces._state_deleted(**kwargs)) + + if state == 'merged': + commands.extend(L2_Interfaces._state_merged(**kwargs)) + + if state == 'replaced': + commands.extend(L2_Interfaces._state_replaced(**kwargs)) + + return commands + + @staticmethod + def _state_replaced(**kwargs): + """ The command generator when state is replaced + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :param interface_type: interface type + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the deisred configuration + """ + commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + name = want['name'] + + if name: + interface = 'interface ' + name + if obj_in_have: + for item in L2_Interfaces.params: + candidate = want.get(item) + if item == 'trunk' and candidate: + for each in L2_Interfaces.trunk_params: + if each == 'allowed_vlan': + if candidate[0].get(each): + proposed = (",".join(map(str, candidate[0].get(each)))).split(',') + value = obj_in_have.get(each).split(',') + else: + proposed = candidate[0].get(each) + value = obj_in_have.get(each) + if value and proposed != value: + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds.get(each), + commands) + commands.extend(L2_Interfaces._state_merged(**kwargs)) + + return commands + + @staticmethod + def _state_overridden(**kwargs): + """ The command generator when state is overridden + :param want: the desired configuration as a dictionary + :param obj_in_have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + interface_want = 'interface ' + want[0]['name'] + + for have in obj_in_have: + name = have['name'] + obj_in_want = search_obj_in_list(name, want) + if not obj_in_want: + interface_type = get_interface_type(name) + if interface_type.lower() == 'loopback': + commands.append('interface ' + name) + commands.append('no description') + elif interface_type.lower() == 'gigabitethernet': + default = True + if have['enabled'] is True: + for k, v in iteritems(have): + if k in L2_Interfaces.params: + if have[k] is not None: + default = False + break + else: + default = False + if default is False: + # Delete the configurable params by interface module + interface = 'interface ' + name + for each in L2_Interfaces.params: + if interface not in commands: + commands.append(interface) + commands.append('no {0}'.format(each)) + else: + changed = False + # Delete the wanted interface to be replaced with provided values + for k, v in iteritems(have): + if obj_in_want[k] != have[k] and have[k] != "auto": + if interface_want not in commands: + changed = True + commands.append(interface_want) + commands.append('no {0}'.format(k)) + if not changed: + break + + if interface_want in commands: + # if there's change in interface_want then extend the commands + for w in want: + name = w['name'] + have = search_obj_in_list(name, obj_in_have) + kwargs = {'want': w, 'have': have} + commands.extend(L2_Interfaces._state_merged(**kwargs)) + else: + # if there's no change in inteface_want then maintain idempotency + commands = [] + + return commands + + @staticmethod + def _state_merged(**kwargs): + """ The command generator when state is merged + :param want: the additive configuration as a dictionary + :param obj_in_have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + name = want['name'] + module = kwargs['module'] + state = module.params['state'] + if name: + interface = 'interface ' + name + + if obj_in_have: + for item in L2_Interfaces.params: + candidate = want.get(item) + if item == 'access' and candidate: + for each in candidate: + if each.get('vlan') and str(each.get('vlan')) != obj_in_have.get('access_vlan'): + if 'switchport mode access' not in commands: + L2_Interfaces._add_command_to_interface(interface,'switchport mode access', commands) + cmd = 'switchport access vlan {}'.format(each.get('vlan')) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + elif item == 'trunk' and candidate: + if obj_in_have.get('encapsulation') or candidate[0].get('encapsulation'): + L2_Interfaces._add_interface_switchport_trunk_cmd(state, candidate, obj_in_have, interface, commands) + else: + msg = interface + " switchport trunk cannot be configured with negotiate trunking encapsulation" + module.fail_json(msg=msg) + + return commands + + @staticmethod + def _state_deleted(**kwargs): + """ The command generator when state is deleted + :param want: the objects from which the configuration should be removed + :param obj_in_have: the current configuration as a dictionary + :param interface_type: interface type + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + interface_type = kwargs['type'] + if not obj_in_have or interface_type == 'unknown': + return commands + + interface = 'interface ' + want['name'] + + if obj_in_have.get('mode') and obj_in_have['mode'] == 'access': + # delete switchport with access mode and its properties + L2_Interfaces._remove_command_from_interface(interface, 'switchport mode', commands) + if 'access_vlan' in obj_in_have: + L2_Interfaces._remove_command_from_interface(interface, 'switchport access vlan', commands) + elif obj_in_have.get('mode') and obj_in_have['mode'] == 'trunk': + # delete switchport with trunk mode and its properties + L2_Interfaces._remove_command_from_interface(interface, 'switchport mode', commands) + if 'allowed_vlan' in obj_in_have: + L2_Interfaces._remove_command_from_interface(interface, 'switchport trunk allowed vlan', commands) + if 'native_vlan' in obj_in_have: + L2_Interfaces._remove_command_from_interface(interface, 'switchport trunk native vlan', commands) + if 'pruning_vlan' in obj_in_have: + L2_Interfaces._remove_command_from_interface(interface, 'switchport trunk pruning vlan', commands) + if 'encapsulation' in obj_in_have: + L2_Interfaces._remove_command_from_interface(interface, 'switchport trunk encapsulation', commands) + + 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 _add_interface_switchport_trunk_cmd(state, candidate, obj_in_have, interface, commands): + for each in candidate: + if each.get('encapsulation') and each.get('encapsulation') != obj_in_have.get('encapsulation'): + cmd = 'switchport trunk encapsulation {}'.format(each.get('encapsulation')) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + L2_Interfaces._add_command_to_interface(interface, 'switchport mode trunk', commands) + if each.get('native_vlan') and str(each.get('native_vlan')) != obj_in_have.get('native_vlan'): + cmd = 'switchport trunk native vlan {}'.format(each.get('native_vlan')) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + if each.get('pruning_vlan'): + pruning_vlans = (",".join(map(str, each.get('pruning_vlan')))) + if pruning_vlans != obj_in_have.get('pruning_vlan'): + cmd = 'switchport trunk pruning vlan {}'.format(pruning_vlans) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + if each.get('allowed_vlan'): + allowed_vlan = (",".join(map(str, each.get('allowed_vlan')))) + existing_vlans = vlans_to_add = None + if obj_in_have.get('allowed_vlan'): + existing_vlans = obj_in_have.get('allowed_vlan').split(',') + vlans_to_add = set(allowed_vlan.split(',')).difference(existing_vlans) + vlans_to_remove = set(existing_vlans).difference(allowed_vlan.split(',')) + if not existing_vlans and each.get('allowed_vlan'): + cmd = 'switchport trunk allowed vlan {}'.format(allowed_vlan) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + elif vlans_to_add and state == 'merged': + cmd = 'switchport trunk allowed vlan add {}'.format(allowed_vlan) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + elif vlans_to_remove and state == 'replaced' or state == 'overridden': + cmd = 'switchport trunk allowed vlan {}'.format(allowed_vlan) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) From bcd4200053d054809c5ae98d2ad740cb8915766c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:28:04 +0530 Subject: [PATCH 17/36] iosl2 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 b74f9adad567183cb57ba349f2c73d82f6c7efb9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:28:16 +0530 Subject: [PATCH 18/36] iosl2 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 b68f6c8f017593c3b430f00dae8aaa94109aaa49 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:28:25 +0530 Subject: [PATCH 19/36] iosl2 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 e4a019f56ba50df8b8fd83c5370e9e88d9670b54 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:28:37 +0530 Subject: [PATCH 20/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/base.py | 110 +++++++++++++++++++++++++++++++++ 1 file changed, 110 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..d1a6e22 --- /dev/null +++ b/module_utils/ios/facts/base.py @@ -0,0 +1,110 @@ +#!/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 {{ network_os }} 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 {{ network_os }} facts base class + """ + generated_spec = {} + ansible_facts = {'net_configuration': {}} + + 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 is not None: + final_cfg.update({key:val}) + return final_cfg From 726603d430e1fffab920cb06ee02c25b7e5ecf78 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:28:47 +0530 Subject: [PATCH 21/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/facts.py | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 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..05aa1fa --- /dev/null +++ b/module_utils/ios/facts/facts.py @@ -0,0 +1,71 @@ +#!/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 {{ network_os }} +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +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.l2_interfaces.l2_interfaces import L2_InterfacesArgs +from ansible.module_utils.ios.facts.l2_interfaces.l2_interfaces import InterfacesFacts + +class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 + """ The fact class for {{ network_os }} + """ + + def get_facts(self, module, connection, gather_subset=['all']): + """ Collect the facts for {{ network_os }} + + :param module: The module instance + :param connection: The device connection + :param gather_subset: The facts subset to collect + :rtype: dict + :returns: the facts gathered + """ + valid_subsets = self.argument_spec['gather_subset'].get('choices', []) + if valid_subsets and 'all' in valid_subsets: + valid_subsets.remove('all') + + runable_subsets = set() + exclude_subsets = set() + if not gather_subset: + gather_subset = ['all'] + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(valid_subsets) + continue + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(valid_subsets) + 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) + self.ansible_facts['gather_subset'] = list(runable_subsets) + + for attr in runable_subsets: + getattr(self, '_get_%s' % attr, {})(module, connection) + + return self.ansible_facts + + @staticmethod + def _get_net_configuration_interfaces(module, connection): + return InterfacesFacts(L2_InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) From 0f9472b91d5643b8e906ae704779b64a8db5e777 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:28:57 +0530 Subject: [PATCH 22/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/l2_interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/facts/l2_interfaces/__init__.py diff --git a/module_utils/ios/facts/l2_interfaces/__init__.py b/module_utils/ios/facts/l2_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From d5bc3deaf84fd5e7f6dc21b18f093d5ef4629d02 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:29:08 +0530 Subject: [PATCH 23/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- .../ios/facts/l2_interfaces/l2_interfaces.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 module_utils/ios/facts/l2_interfaces/l2_interfaces.py diff --git a/module_utils/ios/facts/l2_interfaces/l2_interfaces.py b/module_utils/ios/facts/l2_interfaces/l2_interfaces.py new file mode 100644 index 0000000..88e8f14 --- /dev/null +++ b/module_utils/ios/facts/l2_interfaces/l2_interfaces.py @@ -0,0 +1,72 @@ +#!/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 +import q + +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) + config['encapsulation'] = self.parse_conf_arg(conf, 'switchport trunk encapsulation') + config['pruning_vlan'] = self.parse_conf_arg(conf, 'switchport trunk pruning vlan') + config['mode'] = self.parse_conf_arg(conf, 'switchport mode') + config['native_vlan'] = self.parse_conf_arg(conf, 'switchport trunk native vlan') + config['access_vlan'] = self.parse_conf_arg(conf, 'switchport access vlan') + config['allowed_vlan'] = self.parse_conf_arg(conf, 'switchport trunk allowed vlan') + + return self.generate_final_config(config) From d884fd1d1ee0af7f541cee92c9fd6a8c07436285 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:29:17 +0530 Subject: [PATCH 24/36] iosl2 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 b00e44ec85d8f04d87996399c928388b8b4462bf Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:29:26 +0530 Subject: [PATCH 25/36] iosl2 interface Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/resource/resource.py | 63 +++++++++++++++++++++ 1 file changed, 63 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..59b462d --- /dev/null +++ b/module_utils/ios/facts/resource/resource.py @@ -0,0 +1,63 @@ +#!/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 {{ network_os }} {{ resource }} 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 f31ab53345622015bad31f57719f57aacbfeb63a Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:29:41 +0530 Subject: [PATCH 26/36] iosl2 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 82a81f1f242d0ea8a3ad6fcccc154178e5409e24 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 20 May 2019 18:29:56 +0530 Subject: [PATCH 27/36] iosl2 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..040639d --- /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' From 7ffc94d89716ae20f6b9f281dff771b8024c6d99 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 22 May 2019 12:14:34 +0530 Subject: [PATCH 28/36] fix review comment Signed-off-by: Sumit Jaiswal --- .../ios/config/l2_interfaces/l2_interfaces.py | 171 +++++++++--------- 1 file changed, 82 insertions(+), 89 deletions(-) diff --git a/module_utils/ios/config/l2_interfaces/l2_interfaces.py b/module_utils/ios/config/l2_interfaces/l2_interfaces.py index b3a80bf..21b1b8f 100644 --- a/module_utils/ios/config/l2_interfaces/l2_interfaces.py +++ b/module_utils/ios/config/l2_interfaces/l2_interfaces.py @@ -31,8 +31,10 @@ class L2_Interfaces(ConfigBase, L2_InterfacesArgs): params = ('access', 'trunk') trunk_params = ('encapsulation', 'pruning_vlan', 'native_vlan', 'allowed_vlan') + access_cmds = {'access_vlan': 'switchport access vlan', 'mode': 'switchport mode access'} trunk_cmds = {'encapsulation': 'switchport trunk encapsulation', 'pruning_vlan': 'switchport trunk pruning vlan', - 'native_vlan': 'switchport trunk native vlan', 'allowed_vlan': 'switchport trunk allowed vlan'} + 'native_vlan': 'switchport trunk native vlan', 'allowed_vlan': 'switchport trunk allowed vlan', + 'mode': 'switchport mode trunk'} def get_interfaces_facts(self): """ Get the 'facts' (the current configuration) @@ -139,10 +141,11 @@ def _state_replaced(**kwargs): candidate = want.get(item) if item == 'trunk' and candidate: for each in L2_Interfaces.trunk_params: - if each == 'allowed_vlan': + if each == 'allowed_vlan' or each == 'pruning_vlan': if candidate[0].get(each): proposed = (",".join(map(str, candidate[0].get(each)))).split(',') - value = obj_in_have.get(each).split(',') + if obj_in_have.get(each): + value = obj_in_have.get(each).split(',') else: proposed = candidate[0].get(each) value = obj_in_have.get(each) @@ -165,56 +168,49 @@ def _state_overridden(**kwargs): commands = [] want = kwargs['want'] obj_in_have = kwargs['have'] - interface_want = 'interface ' + want[0]['name'] + module = kwargs['module'] + # Get the user's input interface name to be configured + interface_want = [] + + for each in want: + interface_want.append(each.get('name')) for have in obj_in_have: name = have['name'] obj_in_want = search_obj_in_list(name, want) + # To delete the L2 option already configured on other interface if not obj_in_want: interface_type = get_interface_type(name) - if interface_type.lower() == 'loopback': - commands.append('interface ' + name) - commands.append('no description') - elif interface_type.lower() == 'gigabitethernet': - default = True - if have['enabled'] is True: - for k, v in iteritems(have): - if k in L2_Interfaces.params: - if have[k] is not None: - default = False - break - else: - default = False - if default is False: - # Delete the configurable params by interface module - interface = 'interface ' + name - for each in L2_Interfaces.params: - if interface not in commands: - commands.append(interface) - commands.append('no {0}'.format(each)) + if interface_type.lower() == 'gigabitethernet': + for k, v in iteritems(have): + if have.get('mode') == 'access' and k != 'name': + interface = 'interface ' + name + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.access_cmds[k], + commands) + elif have.get('mode') == 'trunk' and k != 'name': + interface = 'interface ' + name + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds[k], + commands) + # To delete the L2 option already configured on input interface else: - changed = False - # Delete the wanted interface to be replaced with provided values + cmd = [] for k, v in iteritems(have): - if obj_in_want[k] != have[k] and have[k] != "auto": - if interface_want not in commands: - changed = True - commands.append(interface_want) - commands.append('no {0}'.format(k)) - if not changed: - break - - if interface_want in commands: - # if there's change in interface_want then extend the commands - for w in want: - name = w['name'] - have = search_obj_in_list(name, obj_in_have) - kwargs = {'want': w, 'have': have} - commands.extend(L2_Interfaces._state_merged(**kwargs)) - else: - # if there's no change in inteface_want then maintain idempotency - commands = [] - + interface = 'interface ' + name + if obj_in_want.get('access') and k != 'name': + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.access_cmds[k], cmd) + elif obj_in_want.get('trunk') and k != 'name': + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds[k], cmd) + for each in cmd: + commands.append(each) + for w in want: + name = w['name'] + have = search_obj_in_list(name, obj_in_have) + kwargs = {'want': w, 'have': have, 'module': module} + cmd = L2_Interfaces._state_merged(**kwargs) + if cmd: + commands.extend(cmd) + else: + commands = [] return commands @staticmethod @@ -234,24 +230,26 @@ def _state_merged(**kwargs): state = module.params['state'] if name: interface = 'interface ' + name - if obj_in_have: for item in L2_Interfaces.params: candidate = want.get(item) if item == 'access' and candidate: for each in candidate: - if each.get('vlan') and str(each.get('vlan')) != obj_in_have.get('access_vlan'): - if 'switchport mode access' not in commands: - L2_Interfaces._add_command_to_interface(interface,'switchport mode access', commands) - cmd = 'switchport access vlan {}'.format(each.get('vlan')) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) + if each.get('vlan') and str(each.get('vlan')) != obj_in_have.get('access_vlan')\ + or state == 'overridden': + for k,v in iteritems(L2_Interfaces.access_cmds): + if k == 'access_vlan': + v = v + ' {}'.format(each.get('vlan')) + L2_Interfaces._add_command_to_interface(interface, v, commands) elif item == 'trunk' and candidate: if obj_in_have.get('encapsulation') or candidate[0].get('encapsulation'): - L2_Interfaces._add_interface_switchport_trunk_cmd(state, candidate, obj_in_have, interface, commands) + L2_Interfaces._add_interface_switchport_trunk_cmd(state, candidate, obj_in_have, interface, + commands) + if commands and 'switchport mode trunk' not in commands: + L2_Interfaces._add_command_to_interface(interface, 'switchport mode trunk', commands) else: msg = interface + " switchport trunk cannot be configured with negotiate trunking encapsulation" module.fail_json(msg=msg) - return commands @staticmethod @@ -280,22 +278,17 @@ def _state_deleted(**kwargs): L2_Interfaces._remove_command_from_interface(interface, 'switchport access vlan', commands) elif obj_in_have.get('mode') and obj_in_have['mode'] == 'trunk': # delete switchport with trunk mode and its properties - L2_Interfaces._remove_command_from_interface(interface, 'switchport mode', commands) - if 'allowed_vlan' in obj_in_have: - L2_Interfaces._remove_command_from_interface(interface, 'switchport trunk allowed vlan', commands) - if 'native_vlan' in obj_in_have: - L2_Interfaces._remove_command_from_interface(interface, 'switchport trunk native vlan', commands) - if 'pruning_vlan' in obj_in_have: - L2_Interfaces._remove_command_from_interface(interface, 'switchport trunk pruning vlan', commands) - if 'encapsulation' in obj_in_have: - L2_Interfaces._remove_command_from_interface(interface, 'switchport trunk encapsulation', commands) - + for item in L2_Interfaces.trunk_params: + if obj_in_have.get(item): + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds[item], commands) return commands @staticmethod def _remove_command_from_interface(interface, cmd, commands): if interface not in commands: commands.insert(0, interface) + if 'no switchport mode' not in commands: + commands.insert(1, 'no switchport mode') commands.append('no %s' % cmd) return commands @@ -308,31 +301,31 @@ def _add_command_to_interface(interface, cmd, commands): @staticmethod def _add_interface_switchport_trunk_cmd(state, candidate, obj_in_have, interface, commands): for each in candidate: - if each.get('encapsulation') and each.get('encapsulation') != obj_in_have.get('encapsulation'): - cmd = 'switchport trunk encapsulation {}'.format(each.get('encapsulation')) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - L2_Interfaces._add_command_to_interface(interface, 'switchport mode trunk', commands) - if each.get('native_vlan') and str(each.get('native_vlan')) != obj_in_have.get('native_vlan'): - cmd = 'switchport trunk native vlan {}'.format(each.get('native_vlan')) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - if each.get('pruning_vlan'): - pruning_vlans = (",".join(map(str, each.get('pruning_vlan')))) - if pruning_vlans != obj_in_have.get('pruning_vlan'): - cmd = 'switchport trunk pruning vlan {}'.format(pruning_vlans) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - if each.get('allowed_vlan'): - allowed_vlan = (",".join(map(str, each.get('allowed_vlan')))) - existing_vlans = vlans_to_add = None - if obj_in_have.get('allowed_vlan'): - existing_vlans = obj_in_have.get('allowed_vlan').split(',') - vlans_to_add = set(allowed_vlan.split(',')).difference(existing_vlans) - vlans_to_remove = set(existing_vlans).difference(allowed_vlan.split(',')) - if not existing_vlans and each.get('allowed_vlan'): - cmd = 'switchport trunk allowed vlan {}'.format(allowed_vlan) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - elif vlans_to_add and state == 'merged': - cmd = 'switchport trunk allowed vlan add {}'.format(allowed_vlan) + for item in L2_Interfaces.trunk_params: + if item == 'encapsulation' and each.get(item) and each.get(item) != obj_in_have.get(item): + cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(each.get('encapsulation')) L2_Interfaces._add_command_to_interface(interface, cmd, commands) - elif vlans_to_remove and state == 'replaced' or state == 'overridden': - cmd = 'switchport trunk allowed vlan {}'.format(allowed_vlan) + if item == 'native_vlan' and each.get(item) and str(each.get(item)) != obj_in_have.get(item): + cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(each.get(item)) L2_Interfaces._add_command_to_interface(interface, cmd, commands) + if item == 'pruning_vlan' and each.get(item): + pruning_vlans = (",".join(map(str, each.get(item)))) + if pruning_vlans != obj_in_have.get(item): + cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(pruning_vlans) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + if item == 'allowed_vlan' and each.get(item): + allowed_vlan = (",".join(map(str, each.get(item)))) + existing_vlans = vlans_to_add = None + if obj_in_have.get(item): + existing_vlans = obj_in_have.get(item).split(',') + vlans_to_add = set(allowed_vlan.split(',')).difference(existing_vlans) + vlans_to_remove = set(existing_vlans).difference(allowed_vlan.split(',')) + if not existing_vlans and each.get(item): + cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(allowed_vlan) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + elif vlans_to_add and state == 'merged': + cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(allowed_vlan) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + elif vlans_to_remove and state == 'replaced': + cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(allowed_vlan) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) From f7bddfd54458b9949cd990b3790d229b8d06faab Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 22 May 2019 12:14:48 +0530 Subject: [PATCH 29/36] fix review comment Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/l2_interfaces/l2_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/ios/facts/l2_interfaces/l2_interfaces.py b/module_utils/ios/facts/l2_interfaces/l2_interfaces.py index 88e8f14..a9c91f9 100644 --- a/module_utils/ios/facts/l2_interfaces/l2_interfaces.py +++ b/module_utils/ios/facts/l2_interfaces/l2_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 InterfacesFacts(FactsBase): """ The ios interfaces fact class From a0406845121cbbbc8c43dd1615105e0dcda929e6 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 23 May 2019 17:15:30 +0530 Subject: [PATCH 30/36] fix idempotency Signed-off-by: Sumit Jaiswal --- .../ios/config/l2_interfaces/l2_interfaces.py | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/module_utils/ios/config/l2_interfaces/l2_interfaces.py b/module_utils/ios/config/l2_interfaces/l2_interfaces.py index 21b1b8f..0dc0150 100644 --- a/module_utils/ios/config/l2_interfaces/l2_interfaces.py +++ b/module_utils/ios/config/l2_interfaces/l2_interfaces.py @@ -31,10 +31,9 @@ class L2_Interfaces(ConfigBase, L2_InterfacesArgs): params = ('access', 'trunk') trunk_params = ('encapsulation', 'pruning_vlan', 'native_vlan', 'allowed_vlan') - access_cmds = {'access_vlan': 'switchport access vlan', 'mode': 'switchport mode access'} + access_cmds = {'access_vlan': 'switchport access vlan'} trunk_cmds = {'encapsulation': 'switchport trunk encapsulation', 'pruning_vlan': 'switchport trunk pruning vlan', - 'native_vlan': 'switchport trunk native vlan', 'allowed_vlan': 'switchport trunk allowed vlan', - 'mode': 'switchport mode trunk'} + 'native_vlan': 'switchport trunk native vlan', 'allowed_vlan': 'switchport trunk allowed vlan'} def get_interfaces_facts(self): """ Get the 'facts' (the current configuration) @@ -146,8 +145,10 @@ def _state_replaced(**kwargs): proposed = (",".join(map(str, candidate[0].get(each)))).split(',') if obj_in_have.get(each): value = obj_in_have.get(each).split(',') + else: + value = '' else: - proposed = candidate[0].get(each) + proposed = str(candidate[0].get(each)) value = obj_in_have.get(each) if value and proposed != value: L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds.get(each), @@ -171,10 +172,11 @@ def _state_overridden(**kwargs): module = kwargs['module'] # Get the user's input interface name to be configured interface_want = [] + have_cmd = [] + want_cmd = [] for each in want: interface_want.append(each.get('name')) - for have in obj_in_have: name = have['name'] obj_in_want = search_obj_in_list(name, want) @@ -183,34 +185,35 @@ def _state_overridden(**kwargs): interface_type = get_interface_type(name) if interface_type.lower() == 'gigabitethernet': for k, v in iteritems(have): - if have.get('mode') == 'access' and k != 'name': + if have.get('mode') == 'access' and k != 'name' and k != 'mode': interface = 'interface ' + name L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.access_cmds[k], - commands) - elif have.get('mode') == 'trunk' and k != 'name': + have_cmd) + elif have.get('mode') == 'trunk' and k != 'name' and k != 'mode': interface = 'interface ' + name L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds[k], - commands) + have_cmd) # To delete the L2 option already configured on input interface else: cmd = [] for k, v in iteritems(have): interface = 'interface ' + name - if obj_in_want.get('access') and k != 'name': + if obj_in_want.get('access') and k != 'name' and k != 'mode': L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.access_cmds[k], cmd) - elif obj_in_want.get('trunk') and k != 'name': + elif obj_in_want.get('trunk') and k != 'name' and k != 'mode': L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds[k], cmd) - for each in cmd: - commands.append(each) + want_cmd.extend(cmd) for w in want: name = w['name'] + interface = 'interface ' + name have = search_obj_in_list(name, obj_in_have) kwargs = {'want': w, 'have': have, 'module': module} cmd = L2_Interfaces._state_merged(**kwargs) - if cmd: - commands.extend(cmd) - else: - commands = [] + if want_cmd and cmd and want_cmd[0] == interface: + commands.extend(want_cmd) + commands.extend(cmd) + if have_cmd and not commands: + commands.extend(have_cmd) return commands @staticmethod @@ -235,14 +238,16 @@ def _state_merged(**kwargs): candidate = want.get(item) if item == 'access' and candidate: for each in candidate: - if each.get('vlan') and str(each.get('vlan')) != obj_in_have.get('access_vlan')\ - or state == 'overridden': + if each.get('vlan') and str(each.get('vlan')) != obj_in_have.get('access_vlan'): for k,v in iteritems(L2_Interfaces.access_cmds): if k == 'access_vlan': v = v + ' {}'.format(each.get('vlan')) L2_Interfaces._add_command_to_interface(interface, v, commands) + L2_Interfaces._add_command_to_interface(interface, 'switchport mode access', commands) elif item == 'trunk' and candidate: - if obj_in_have.get('encapsulation') or candidate[0].get('encapsulation'): + for e in candidate: + encapsulation = e.get('encapsulation') + if obj_in_have.get('encapsulation') or encapsulation: L2_Interfaces._add_interface_switchport_trunk_cmd(state, candidate, obj_in_have, interface, commands) if commands and 'switchport mode trunk' not in commands: @@ -250,6 +255,7 @@ def _state_merged(**kwargs): else: msg = interface + " switchport trunk cannot be configured with negotiate trunking encapsulation" module.fail_json(msg=msg) + return commands @staticmethod From 2ff3b433674e1f3ee54a8c85064b22b7dd6f9918 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Sat, 25 May 2019 12:51:16 +0530 Subject: [PATCH 31/36] fix issue Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/l2_interfaces/l2_interfaces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py b/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py index b1226f2..3c7845e 100644 --- a/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py +++ b/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py @@ -32,10 +32,10 @@ def __init__(self, **kwargs): pass access_arg_spec = dict(vlan=dict(type=str, required=True)) - trunk_arg_spec = dict(allowed_vlan=dict(type=list, required=True), + trunk_arg_spec = dict(allowed_vlans=dict(type=list, required=True), encapsulation=dict(type=str), native_vlan=dict(type=str), - pruning_vlan=dict(type=list) + pruning_vlans=dict(type=list) ) config_spec = { From c28453b059bda9ac88fd8ed55ee2c0c004e046f1 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Sat, 25 May 2019 12:51:38 +0530 Subject: [PATCH 32/36] fix issue Signed-off-by: Sumit Jaiswal --- .../ios/config/l2_interfaces/l2_interfaces.py | 316 +++++++++--------- 1 file changed, 150 insertions(+), 166 deletions(-) diff --git a/module_utils/ios/config/l2_interfaces/l2_interfaces.py b/module_utils/ios/config/l2_interfaces/l2_interfaces.py index 0dc0150..a5ab666 100644 --- a/module_utils/ios/config/l2_interfaces/l2_interfaces.py +++ b/module_utils/ios/config/l2_interfaces/l2_interfaces.py @@ -11,14 +11,13 @@ """ -from ansible.module_utils.six import iteritems from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.ios.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs from ansible.module_utils.ios.config.base import ConfigBase from ansible.module_utils.ios.facts.facts import Facts from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list - +import q class L2_Interfaces(ConfigBase, L2_InterfacesArgs): """ @@ -30,10 +29,9 @@ class L2_Interfaces(ConfigBase, L2_InterfacesArgs): ] params = ('access', 'trunk') - trunk_params = ('encapsulation', 'pruning_vlan', 'native_vlan', 'allowed_vlan') access_cmds = {'access_vlan': 'switchport access vlan'} - trunk_cmds = {'encapsulation': 'switchport trunk encapsulation', 'pruning_vlan': 'switchport trunk pruning vlan', - 'native_vlan': 'switchport trunk native vlan', 'allowed_vlan': 'switchport trunk allowed vlan'} + trunk_cmds = {'encapsulation': 'switchport trunk encapsulation', 'pruning_vlans': 'switchport trunk pruning vlan', + 'native_vlan': 'switchport trunk native vlan', 'allowed_vlans': 'switchport trunk allowed vlan'} def get_interfaces_facts(self): """ Get the 'facts' (the current configuration) @@ -54,7 +52,6 @@ def execute_module(self): result = {'changed': False} commands = [] warnings = [] - existing_facts = self.get_interfaces_facts() commands.extend(self.set_config(existing_facts)) result['before'] = existing_facts @@ -78,11 +75,9 @@ def set_config(self, existing_facts): :returns: the commands necessary to migrate the current configuration to the deisred configuration """ - want = self._module.params['config'] - for w in want: - w.update({'name': normalize_interface(w['name'])}) - have = existing_facts#self.get_interfaces_facts() + want = self._module.params['config'] + have = existing_facts resp = self.set_state(want, have) return to_list(resp) @@ -96,25 +91,20 @@ def set_state(self, want, have): to the deisred configuration """ commands = [] - state = self._module.params['state'] + state = self._module.params['state'] if state == 'overridden': kwargs = {'want': want, 'have': have, 'module': self._module} - commands = L2_Interfaces._state_overridden(**kwargs) - else: - for w in want: - name = w['name'] - interface_type = get_interface_type(name) - obj_in_have = search_obj_in_list(name, have) - kwargs = {'want': w, 'have': obj_in_have, 'type': interface_type, 'module': self._module} - if state == 'deleted': - commands.extend(L2_Interfaces._state_deleted(**kwargs)) - - if state == 'merged': - commands.extend(L2_Interfaces._state_merged(**kwargs)) - - if state == 'replaced': - commands.extend(L2_Interfaces._state_replaced(**kwargs)) + 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 @@ -130,30 +120,19 @@ def _state_replaced(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - name = want['name'] - - if name: - interface = 'interface ' + name - if obj_in_have: - for item in L2_Interfaces.params: - candidate = want.get(item) - if item == 'trunk' and candidate: - for each in L2_Interfaces.trunk_params: - if each == 'allowed_vlan' or each == 'pruning_vlan': - if candidate[0].get(each): - proposed = (",".join(map(str, candidate[0].get(each)))).split(',') - if obj_in_have.get(each): - value = obj_in_have.get(each).split(',') - else: - value = '' - else: - proposed = str(candidate[0].get(each)) - value = obj_in_have.get(each) - if value and proposed != value: - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds.get(each), - commands) - commands.extend(L2_Interfaces._state_merged(**kwargs)) + 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(L2_Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} + commands.extend(L2_Interfaces.set_interface(**kwargs)) return commands @@ -168,52 +147,25 @@ def _state_overridden(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] + have = kwargs['have'] module = kwargs['module'] - # Get the user's input interface name to be configured - interface_want = [] - have_cmd = [] - want_cmd = [] - - for each in want: - interface_want.append(each.get('name')) - for have in obj_in_have: - name = have['name'] - obj_in_want = search_obj_in_list(name, want) - # To delete the L2 option already configured on other interface - if not obj_in_want: - interface_type = get_interface_type(name) - if interface_type.lower() == 'gigabitethernet': - for k, v in iteritems(have): - if have.get('mode') == 'access' and k != 'name' and k != 'mode': - interface = 'interface ' + name - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.access_cmds[k], - have_cmd) - elif have.get('mode') == 'trunk' and k != 'name' and k != 'mode': - interface = 'interface ' + name - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds[k], - have_cmd) - # To delete the L2 option already configured on input interface + + for each in have: + for interface in want: + if each['name'] == interface['name']: + break else: - cmd = [] - for k, v in iteritems(have): - interface = 'interface ' + name - if obj_in_want.get('access') and k != 'name' and k != 'mode': - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.access_cmds[k], cmd) - elif obj_in_want.get('trunk') and k != 'name' and k != 'mode': - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds[k], cmd) - want_cmd.extend(cmd) - for w in want: - name = w['name'] - interface = 'interface ' + name - have = search_obj_in_list(name, obj_in_have) - kwargs = {'want': w, 'have': have, 'module': module} - cmd = L2_Interfaces._state_merged(**kwargs) - if want_cmd and cmd and want_cmd[0] == interface: - commands.extend(want_cmd) - commands.extend(cmd) - if have_cmd and not commands: - commands.extend(have_cmd) + # 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(L2_Interfaces.clear_interface(**kwargs)) + continue + kwargs = {'want': interface, 'have': each} + commands.extend(L2_Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} + commands.extend(L2_Interfaces.set_interface(**kwargs)) + return commands @staticmethod @@ -227,34 +179,17 @@ def _state_merged(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - name = want['name'] + have = kwargs['have'] module = kwargs['module'] - state = module.params['state'] - if name: - interface = 'interface ' + name - if obj_in_have: - for item in L2_Interfaces.params: - candidate = want.get(item) - if item == 'access' and candidate: - for each in candidate: - if each.get('vlan') and str(each.get('vlan')) != obj_in_have.get('access_vlan'): - for k,v in iteritems(L2_Interfaces.access_cmds): - if k == 'access_vlan': - v = v + ' {}'.format(each.get('vlan')) - L2_Interfaces._add_command_to_interface(interface, v, commands) - L2_Interfaces._add_command_to_interface(interface, 'switchport mode access', commands) - elif item == 'trunk' and candidate: - for e in candidate: - encapsulation = e.get('encapsulation') - if obj_in_have.get('encapsulation') or encapsulation: - L2_Interfaces._add_interface_switchport_trunk_cmd(state, candidate, obj_in_have, interface, - commands) - if commands and 'switchport mode trunk' not in commands: - L2_Interfaces._add_command_to_interface(interface, 'switchport mode trunk', commands) - else: - msg = interface + " switchport trunk cannot be configured with negotiate trunking encapsulation" - module.fail_json(msg=msg) + + 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(L2_Interfaces.set_interface(**kwargs)) return commands @@ -270,23 +205,18 @@ def _state_deleted(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - interface_type = kwargs['type'] - if not obj_in_have or interface_type == 'unknown': - return commands + have = kwargs['have'] - interface = 'interface ' + want['name'] + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + else: + continue + interface = dict(name=interface['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(L2_Interfaces.clear_interface(**kwargs)) - if obj_in_have.get('mode') and obj_in_have['mode'] == 'access': - # delete switchport with access mode and its properties - L2_Interfaces._remove_command_from_interface(interface, 'switchport mode', commands) - if 'access_vlan' in obj_in_have: - L2_Interfaces._remove_command_from_interface(interface, 'switchport access vlan', commands) - elif obj_in_have.get('mode') and obj_in_have['mode'] == 'trunk': - # delete switchport with trunk mode and its properties - for item in L2_Interfaces.trunk_params: - if obj_in_have.get(item): - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds[item], commands) return commands @staticmethod @@ -305,33 +235,87 @@ def _add_command_to_interface(interface, cmd, commands): commands.append(cmd) @staticmethod - def _add_interface_switchport_trunk_cmd(state, candidate, obj_in_have, interface, commands): - for each in candidate: - for item in L2_Interfaces.trunk_params: - if item == 'encapsulation' and each.get(item) and each.get(item) != obj_in_have.get(item): - cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(each.get('encapsulation')) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - if item == 'native_vlan' and each.get(item) and str(each.get(item)) != obj_in_have.get(item): - cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(each.get(item)) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - if item == 'pruning_vlan' and each.get(item): - pruning_vlans = (",".join(map(str, each.get(item)))) - if pruning_vlans != obj_in_have.get(item): - cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(pruning_vlans) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - if item == 'allowed_vlan' and each.get(item): - allowed_vlan = (",".join(map(str, each.get(item)))) - existing_vlans = vlans_to_add = None - if obj_in_have.get(item): - existing_vlans = obj_in_have.get(item).split(',') - vlans_to_add = set(allowed_vlan.split(',')).difference(existing_vlans) - vlans_to_remove = set(existing_vlans).difference(allowed_vlan.split(',')) - if not existing_vlans and each.get(item): - cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(allowed_vlan) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - elif vlans_to_add and state == 'merged': - cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(allowed_vlan) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - elif vlans_to_remove and state == 'replaced': - cmd = L2_Interfaces.trunk_cmds[item] + ' {}'.format(allowed_vlan) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) + 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'] + + interface = 'interface ' + want['name'] + wants_access = want["access"] + if wants_access: + access_vlan = wants_access[0].get("vlan") + if access_vlan and access_vlan != have.get("access", {}).get("vlan") or\ + 'no switchport access vlan' in clear_cmds: + cmd = L2_Interfaces.access_cmds['access_vlan'] + ' {}'.format(access_vlan) + L2_Interfaces._add_command_to_interface(interface, 'switchport mode access', commands) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + + wants_trunk = want["trunk"] + if wants_trunk: + has_trunk = have.get("trunk", {}) + encapsulation = wants_trunk[0].get("encapsulation") + if not encapsulation and not has_trunk.get("encapsulation"): + module.fail_json(msg='Switchport Trunk mode cannot be configured with negotiate encapsulation!') + if encapsulation and (encapsulation != has_trunk.get("encapsulation") or\ + 'no switchport trunk encapsulation' in clear_cmds): + cmd = L2_Interfaces.trunk_cmds['encapsulation'] + ' {}'.format(encapsulation) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + + if commands and 'switchport mode trunk' not in commands or 'no switchport mode' in clear_cmds: + L2_Interfaces._add_command_to_interface(interface, 'switchport mode trunk', commands) + + native_vlan = wants_trunk[0].get("native_vlan") + if native_vlan and (native_vlan != has_trunk.get("native_vlan") or\ + 'no switchport trunk native vlan' in clear_cmds): + cmd = L2_Interfaces.trunk_cmds['native_vlan'] + ' {}'.format(native_vlan) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + + allowed_vlans = wants_trunk[0].get("allowed_vlans") + has_allowed = has_trunk.get("allowed_vlans") + if allowed_vlans: + allowed_vlans = (",".join(map(str, allowed_vlans))) + if allowed_vlans and (allowed_vlans != has_allowed or 'no switchport trunk allowed vlan' in clear_cmds): + cmd = L2_Interfaces.trunk_cmds['allowed_vlans'] + ' {}'.format(allowed_vlans) + L2_Interfaces._add_command_to_interface(interface, cmd, commands) + + pruning_vlans = wants_trunk[0].get("pruning_vlans") + has_pruning = has_trunk.get("pruning_vlans") + if pruning_vlans: + pruning_vlans = (",".join(map(str, pruning_vlans))) + + if pruning_vlans and (pruning_vlans != has_pruning or 'no switchport trunk pruning vlan' in clear_cmds): + cmd = L2_Interfaces.trunk_cmds['pruning_vlans'] + ' {}'.format(pruning_vlans) + L2_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 + commands = [] + want = kwargs['want'] + have = kwargs['have'] + interface = 'interface ' + want['name'] + + if "access" in have and not want.get('access'): + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.access_cmds['access_vlan'], commands) + + has_trunk = have.get("trunk") or {} + wants_trunk = want.get("trunk") or {} + if wants_trunk: + wants_trunk = wants_trunk[0] + if "encapsulation" in has_trunk and "encapsulation" not in wants_trunk: + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds['encapsulation'], commands) + if "allowed_vlans" in has_trunk and "allowed_vlans" not in wants_trunk: + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds['allowed_vlans'], commands) + if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk: + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds['native_vlan'], commands) + if "pruning_vlans" in has_trunk and "pruning_vlans" not in wants_trunk: + L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds['pruning_vlans'], commands) + + return commands From d0ce5a668c4b4f7a9e8ec9f864eccf5e030ed799 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Sat, 25 May 2019 12:51:47 +0530 Subject: [PATCH 33/36] fix issue Signed-off-by: Sumit Jaiswal --- .../ios/facts/l2_interfaces/l2_interfaces.py | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/module_utils/ios/facts/l2_interfaces/l2_interfaces.py b/module_utils/ios/facts/l2_interfaces/l2_interfaces.py index a9c91f9..c262c04 100644 --- a/module_utils/ios/facts/l2_interfaces/l2_interfaces.py +++ b/module_utils/ios/facts/l2_interfaces/l2_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 InterfacesFacts(FactsBase): """ The ios interfaces fact class @@ -62,11 +62,31 @@ def render_config(self, spec, conf): return {} # populate the facts from the configuration config['name'] = normalize_interface(intf) - config['encapsulation'] = self.parse_conf_arg(conf, 'switchport trunk encapsulation') - config['pruning_vlan'] = self.parse_conf_arg(conf, 'switchport trunk pruning vlan') - config['mode'] = self.parse_conf_arg(conf, 'switchport mode') - config['native_vlan'] = self.parse_conf_arg(conf, 'switchport trunk native vlan') - config['access_vlan'] = self.parse_conf_arg(conf, 'switchport access vlan') - config['allowed_vlan'] = self.parse_conf_arg(conf, 'switchport trunk allowed vlan') + + has_access = re.search(r"switchport access vlan (\d+)", conf) + if has_access: + config["access"] = {"vlan": int(has_access.group(1))} + + has_trunk = re.findall(r"switchport trunk (.+)", conf) + if has_trunk: + trunk = {} + for match in has_trunk: + has_encapsulation = re.match(r"encapsulation (\S+)", match) + if has_encapsulation: + trunk["encapsulation"] = has_encapsulation.group(1) + continue + has_native = re.match(r"native vlan (\d+)", match) + if has_native: + trunk["native_vlan"] = int(has_native.group(1)) + continue + has_allowed = re.match(r"allowed vlan (\S+)", match) + if has_allowed: + trunk["allowed_vlans"] = has_allowed.group(1) + continue + has_pruning = re.match(r"pruning vlan (\S+)", match) + if has_pruning: + trunk["pruning_vlans"] = has_pruning.group(1) + continue + config['trunk'] = trunk return self.generate_final_config(config) From 0b42fa867f102dabc8e6b8d3472e6288936e15cb Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Sat, 25 May 2019 12:54:01 +0530 Subject: [PATCH 34/36] fix issue Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/l2_interfaces/l2_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/ios/config/l2_interfaces/l2_interfaces.py b/module_utils/ios/config/l2_interfaces/l2_interfaces.py index a5ab666..9983cdf 100644 --- a/module_utils/ios/config/l2_interfaces/l2_interfaces.py +++ b/module_utils/ios/config/l2_interfaces/l2_interfaces.py @@ -17,7 +17,7 @@ from ansible.module_utils.ios.config.base import ConfigBase from ansible.module_utils.ios.facts.facts import Facts from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list -import q + class L2_Interfaces(ConfigBase, L2_InterfacesArgs): """ From 5b0523a9a47961f161b4a221ad0daaa55b8ff004 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Sat, 25 May 2019 12:55:08 +0530 Subject: [PATCH 35/36] fix issue Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/l2_interfaces/l2_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/ios/config/l2_interfaces/l2_interfaces.py b/module_utils/ios/config/l2_interfaces/l2_interfaces.py index 9983cdf..29cb199 100644 --- a/module_utils/ios/config/l2_interfaces/l2_interfaces.py +++ b/module_utils/ios/config/l2_interfaces/l2_interfaces.py @@ -21,7 +21,7 @@ class L2_Interfaces(ConfigBase, L2_InterfacesArgs): """ - The ios_interfaces class + The ios_l2_interfaces class """ gather_subset = [ From f8b16f703c11e85e48878fdc67d6a5045e18dff3 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Sat, 10 Aug 2019 15:25:26 +0530 Subject: [PATCH 36/36] fix old code --- ...s_l2_interface.py => ios_l2_interfaces.py} | 20 +- module_utils/ios/argspec/facts/facts.py | 23 -- .../argspec/l2_interfaces/l2_interfaces.py | 51 --- module_utils/ios/argspec/resource/resource.py | 29 -- .../ios/config/l2_interfaces/l2_interfaces.py | 321 ------------------ module_utils/ios/config/resource/resource.py | 0 module_utils/ios/facts/facts.py | 71 ---- .../ios/facts/l2_interfaces/__init__.py | 0 .../ios/facts/l2_interfaces/l2_interfaces.py | 92 ----- module_utils/ios/facts/resource/__init__.py | 0 module_utils/ios/facts/resource/resource.py | 63 ---- module_utils/ios/utils/__init__.py | 0 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 | 21 ++ .../ios/argspec/l2_interfaces/__init__.py | 0 .../argspec/l2_interfaces/l2_interfaces.py | 51 +++ .../ios/config}/__init__.py | 0 module_utils/{ => network}/ios/config/base.py | 0 .../ios/config/l2_interfaces}/__init__.py | 0 .../ios/config/l2_interfaces/l2_interfaces.py | 313 +++++++++++++++++ .../ios/facts}/__init__.py | 0 module_utils/{ => network}/ios/facts/base.py | 0 module_utils/network/ios/facts/facts.py | 61 ++++ .../ios/facts/l2_interfaces}/__init__.py | 0 .../ios/facts/l2_interfaces/l2_interfaces.py | 108 ++++++ .../facts => network/ios/utils}/__init__.py | 0 module_utils/{ => network}/ios/utils/utils.py | 78 +++++ 29 files changed, 643 insertions(+), 659 deletions(-) rename library/{ios_l2_interface.py => ios_l2_interfaces.py} (94%) delete mode 100644 module_utils/ios/argspec/facts/facts.py delete mode 100644 module_utils/ios/argspec/l2_interfaces/l2_interfaces.py delete mode 100644 module_utils/ios/argspec/resource/resource.py delete mode 100644 module_utils/ios/config/l2_interfaces/l2_interfaces.py delete mode 100644 module_utils/ios/config/resource/resource.py delete mode 100644 module_utils/ios/facts/facts.py delete mode 100644 module_utils/ios/facts/l2_interfaces/__init__.py delete mode 100644 module_utils/ios/facts/l2_interfaces/l2_interfaces.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 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%) create mode 100644 module_utils/network/ios/argspec/facts/facts.py rename module_utils/{ => network}/ios/argspec/l2_interfaces/__init__.py (100%) create mode 100644 module_utils/network/ios/argspec/l2_interfaces/l2_interfaces.py rename module_utils/{ios/argspec/resource => network/ios/config}/__init__.py (100%) rename module_utils/{ => network}/ios/config/base.py (100%) rename module_utils/{ios/config => network/ios/config/l2_interfaces}/__init__.py (100%) create mode 100644 module_utils/network/ios/config/l2_interfaces/l2_interfaces.py rename module_utils/{ios/config/l2_interfaces => network/ios/facts}/__init__.py (100%) rename module_utils/{ => network}/ios/facts/base.py (100%) create mode 100644 module_utils/network/ios/facts/facts.py rename module_utils/{ios/config/resource => network/ios/facts/l2_interfaces}/__init__.py (100%) create mode 100644 module_utils/network/ios/facts/l2_interfaces/l2_interfaces.py rename module_utils/{ios/facts => network/ios/utils}/__init__.py (100%) rename module_utils/{ => network}/ios/utils/utils.py (52%) diff --git a/library/ios_l2_interface.py b/library/ios_l2_interfaces.py similarity index 94% rename from library/ios_l2_interface.py rename to library/ios_l2_interfaces.py index eaf5ed2..26e7335 100644 --- a/library/ios_l2_interface.py +++ b/library/ios_l2_interfaces.py @@ -67,27 +67,28 @@ vlan: description: - Configure given VLAN in access port. It's used as the access VLAN ID. - type: str + type: int trunk: description: - Switchport mode trunk command to configure the interface as a Layer 2 trunk. Note The encapsulation is always set to dot1q. suboptions: - allowed_vlan: - description: - - List of allowed VLANs in a given trunk port. These are the only VLANs that will be - configured on the trunk. - type: list native_vlan: description: - Native VLAN to be configured in trunk port. It's used as the trunk native VLAN ID. type: str + allowed_vlans: + description: + - List of allowed VLANs in a given trunk port. These are the only VLANs that will be + configured on the trunk. If C(mode=trunk), these are the only VLANs that will be + configured on the trunk, i.e. "2-10,15". + type: list encapsulation: description: - Trunking encapsulation when interface is in trunking mode. choices: ['dot1q','isl','negotiate'] type: str - pruning_vlan: + pruning_vlans: description: - Pruning VLAN to be configured in trunk port. It's used as the trunk pruning VLAN ID. type: list @@ -313,7 +314,8 @@ from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.ios.config.l2_interfaces.l2_interfaces import L2_Interfaces +from ansible.module_utils.network.ios.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs +from ansible.module_utils.network.ios.config.l2_interfaces.l2_interfaces import L2_Interfaces def main(): @@ -322,7 +324,7 @@ def main(): :returns: the result form module invocation """ - module = AnsibleModule(argument_spec=L2_Interfaces.argument_spec, + module = AnsibleModule(argument_spec=L2_InterfacesArgs.argument_spec, supports_check_mode=True) result = L2_Interfaces(module).execute_module() diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/ios/argspec/facts/facts.py deleted file mode 100644 index d1dbaea..0000000 --- a/module_utils/ios/argspec/facts/facts.py +++ /dev/null @@ -1,23 +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 arg spec for the {{ network_os }} facts module. -""" - -class FactsArgs(object): #pylint: disable=R0903 - """ The arg spec for the {{ network_os }} facts module - """ - - def __init__(self, **kwargs): - pass - - choices = [ - 'all', - 'net_configuration_interfaces', - ] - - argument_spec = { - 'gather_subset': dict(default=['all'], choices=choices, type='list') - } diff --git a/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py b/module_utils/ios/argspec/l2_interfaces/l2_interfaces.py deleted file mode 100644 index 3c7845e..0000000 --- a/module_utils/ios/argspec/l2_interfaces/l2_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_interfaces module -""" - -class L2_InterfacesArgs(object): - - def __init__(self, **kwargs): - pass - - access_arg_spec = dict(vlan=dict(type=str, required=True)) - trunk_arg_spec = dict(allowed_vlans=dict(type=list, required=True), - encapsulation=dict(type=str), - native_vlan=dict(type=str), - pruning_vlans=dict(type=list) - ) - - config_spec = { - 'name': dict(type='str', required=True), - 'access': dict(type=list, element=dict, options=access_arg_spec), - 'trunk': dict(element=dict, type=list, options=trunk_arg_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 3b4e191..0000000 --- a/module_utils/ios/argspec/resource/resource.py +++ /dev/null @@ -1,29 +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 {{ network_os }}_{{ resource }} module -""" - - diff --git a/module_utils/ios/config/l2_interfaces/l2_interfaces.py b/module_utils/ios/config/l2_interfaces/l2_interfaces.py deleted file mode 100644 index 29cb199..0000000 --- a/module_utils/ios/config/l2_interfaces/l2_interfaces.py +++ /dev/null @@ -1,321 +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_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.ios.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs -from ansible.module_utils.ios.config.base import ConfigBase -from ansible.module_utils.ios.facts.facts import Facts -from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list - - -class L2_Interfaces(ConfigBase, L2_InterfacesArgs): - """ - The ios_l2_interfaces class - """ - - gather_subset = [ - 'net_configuration_interfaces', - ] - - params = ('access', 'trunk') - access_cmds = {'access_vlan': 'switchport access vlan'} - trunk_cmds = {'encapsulation': 'switchport trunk encapsulation', 'pruning_vlans': 'switchport trunk pruning vlan', - 'native_vlan': 'switchport trunk native vlan', 'allowed_vlans': 'switchport trunk allowed vlan'} - - def get_interfaces_facts(self): - """ Get the 'facts' (the current configuration) - :rtype: A dictionary - :returns: The current configuration as a dictionary - """ - facts = Facts().get_facts(self._module, self._connection, self.gather_subset) - interfaces_facts = facts['net_configuration'].get('interfaces') - if not interfaces_facts: - return [] - return interfaces_facts - - def execute_module(self): - """ Execute the module - :rtype: A dictionary - :returns: The result from moduel execution - """ - result = {'changed': False} - commands = [] - warnings = [] - existing_facts = self.get_interfaces_facts() - commands.extend(self.set_config(existing_facts)) - result['before'] = existing_facts - if commands: - if not self._module.check_mode: - self._connection.edit_config(commands) - result['changed'] = True - result['commands'] = commands - - interfaces_facts = self.get_interfaces_facts() - - if result['changed']: - result['after'] = interfaces_facts - result['warnings'] = warnings - return result - - def set_config(self, existing_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 deisred configuration - """ - - want = self._module.params['config'] - have = existing_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 deisred 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 - :param want: the desired configuration as a dictionary - :param have: the current configuration as a dictionary - :param interface_type: interface type - :rtype: A list - :returns: the commands necessary to migrate the current configuration - to the deisred 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(L2_Interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} - commands.extend(L2_Interfaces.set_interface(**kwargs)) - - return commands - - @staticmethod - def _state_overridden(**kwargs): - """ The command generator when state is overridden - :param want: the desired configuration as a dictionary - :param obj_in_have: the current configuration as a dictionary - :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(L2_Interfaces.clear_interface(**kwargs)) - continue - kwargs = {'want': interface, 'have': each} - commands.extend(L2_Interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} - commands.extend(L2_Interfaces.set_interface(**kwargs)) - - return commands - - @staticmethod - def _state_merged(**kwargs): - """ The command generator when state is merged - :param want: the additive configuration as a dictionary - :param obj_in_have: the current configuration as a dictionary - :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(L2_Interfaces.set_interface(**kwargs)) - - return commands - - @staticmethod - def _state_deleted(**kwargs): - """ The command generator when state is deleted - :param want: the objects from which the configuration should be removed - :param obj_in_have: the current configuration as a dictionary - :param interface_type: interface type - :rtype: A list - :returns: the commands necessary to remove the current configuration - of the provided objects - """ - commands = [] - want = kwargs['want'] - have = kwargs['have'] - - for interface in want: - for each in have: - if each['name'] == interface['name']: - break - else: - continue - interface = dict(name=interface['name']) - kwargs = {'want': interface, 'have': each} - commands.extend(L2_Interfaces.clear_interface(**kwargs)) - - return commands - - @staticmethod - def _remove_command_from_interface(interface, cmd, commands): - if interface not in commands: - commands.insert(0, interface) - if 'no switchport mode' not in commands: - commands.insert(1, 'no switchport mode') - 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'] - - interface = 'interface ' + want['name'] - wants_access = want["access"] - if wants_access: - access_vlan = wants_access[0].get("vlan") - if access_vlan and access_vlan != have.get("access", {}).get("vlan") or\ - 'no switchport access vlan' in clear_cmds: - cmd = L2_Interfaces.access_cmds['access_vlan'] + ' {}'.format(access_vlan) - L2_Interfaces._add_command_to_interface(interface, 'switchport mode access', commands) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - - wants_trunk = want["trunk"] - if wants_trunk: - has_trunk = have.get("trunk", {}) - encapsulation = wants_trunk[0].get("encapsulation") - if not encapsulation and not has_trunk.get("encapsulation"): - module.fail_json(msg='Switchport Trunk mode cannot be configured with negotiate encapsulation!') - if encapsulation and (encapsulation != has_trunk.get("encapsulation") or\ - 'no switchport trunk encapsulation' in clear_cmds): - cmd = L2_Interfaces.trunk_cmds['encapsulation'] + ' {}'.format(encapsulation) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - - if commands and 'switchport mode trunk' not in commands or 'no switchport mode' in clear_cmds: - L2_Interfaces._add_command_to_interface(interface, 'switchport mode trunk', commands) - - native_vlan = wants_trunk[0].get("native_vlan") - if native_vlan and (native_vlan != has_trunk.get("native_vlan") or\ - 'no switchport trunk native vlan' in clear_cmds): - cmd = L2_Interfaces.trunk_cmds['native_vlan'] + ' {}'.format(native_vlan) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - - allowed_vlans = wants_trunk[0].get("allowed_vlans") - has_allowed = has_trunk.get("allowed_vlans") - if allowed_vlans: - allowed_vlans = (",".join(map(str, allowed_vlans))) - if allowed_vlans and (allowed_vlans != has_allowed or 'no switchport trunk allowed vlan' in clear_cmds): - cmd = L2_Interfaces.trunk_cmds['allowed_vlans'] + ' {}'.format(allowed_vlans) - L2_Interfaces._add_command_to_interface(interface, cmd, commands) - - pruning_vlans = wants_trunk[0].get("pruning_vlans") - has_pruning = has_trunk.get("pruning_vlans") - if pruning_vlans: - pruning_vlans = (",".join(map(str, pruning_vlans))) - - if pruning_vlans and (pruning_vlans != has_pruning or 'no switchport trunk pruning vlan' in clear_cmds): - cmd = L2_Interfaces.trunk_cmds['pruning_vlans'] + ' {}'.format(pruning_vlans) - L2_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 - commands = [] - want = kwargs['want'] - have = kwargs['have'] - interface = 'interface ' + want['name'] - - if "access" in have and not want.get('access'): - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.access_cmds['access_vlan'], commands) - - has_trunk = have.get("trunk") or {} - wants_trunk = want.get("trunk") or {} - if wants_trunk: - wants_trunk = wants_trunk[0] - if "encapsulation" in has_trunk and "encapsulation" not in wants_trunk: - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds['encapsulation'], commands) - if "allowed_vlans" in has_trunk and "allowed_vlans" not in wants_trunk: - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds['allowed_vlans'], commands) - if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk: - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds['native_vlan'], commands) - if "pruning_vlans" in has_trunk and "pruning_vlans" not in wants_trunk: - L2_Interfaces._remove_command_from_interface(interface, L2_Interfaces.trunk_cmds['pruning_vlans'], 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/facts.py b/module_utils/ios/facts/facts.py deleted file mode 100644 index 05aa1fa..0000000 --- a/module_utils/ios/facts/facts.py +++ /dev/null @@ -1,71 +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 {{ network_os }} -this file validates each subset of facts and selectively -calls the appropriate facts gathering function -""" - -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.l2_interfaces.l2_interfaces import L2_InterfacesArgs -from ansible.module_utils.ios.facts.l2_interfaces.l2_interfaces import InterfacesFacts - -class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 - """ The fact class for {{ network_os }} - """ - - def get_facts(self, module, connection, gather_subset=['all']): - """ Collect the facts for {{ network_os }} - - :param module: The module instance - :param connection: The device connection - :param gather_subset: The facts subset to collect - :rtype: dict - :returns: the facts gathered - """ - valid_subsets = self.argument_spec['gather_subset'].get('choices', []) - if valid_subsets and 'all' in valid_subsets: - valid_subsets.remove('all') - - runable_subsets = set() - exclude_subsets = set() - if not gather_subset: - gather_subset = ['all'] - - for subset in gather_subset: - if subset == 'all': - runable_subsets.update(valid_subsets) - continue - if subset.startswith('!'): - subset = subset[1:] - if subset == 'all': - exclude_subsets.update(valid_subsets) - 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) - self.ansible_facts['gather_subset'] = list(runable_subsets) - - for attr in runable_subsets: - getattr(self, '_get_%s' % attr, {})(module, connection) - - return self.ansible_facts - - @staticmethod - def _get_net_configuration_interfaces(module, connection): - return InterfacesFacts(L2_InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) diff --git a/module_utils/ios/facts/l2_interfaces/__init__.py b/module_utils/ios/facts/l2_interfaces/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/ios/facts/l2_interfaces/l2_interfaces.py b/module_utils/ios/facts/l2_interfaces/l2_interfaces.py deleted file mode 100644 index c262c04..0000000 --- a/module_utils/ios/facts/l2_interfaces/l2_interfaces.py +++ /dev/null @@ -1,92 +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 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 -import q - -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) - - has_access = re.search(r"switchport access vlan (\d+)", conf) - if has_access: - config["access"] = {"vlan": int(has_access.group(1))} - - has_trunk = re.findall(r"switchport trunk (.+)", conf) - if has_trunk: - trunk = {} - for match in has_trunk: - has_encapsulation = re.match(r"encapsulation (\S+)", match) - if has_encapsulation: - trunk["encapsulation"] = has_encapsulation.group(1) - continue - has_native = re.match(r"native vlan (\d+)", match) - if has_native: - trunk["native_vlan"] = int(has_native.group(1)) - continue - has_allowed = re.match(r"allowed vlan (\S+)", match) - if has_allowed: - trunk["allowed_vlans"] = has_allowed.group(1) - continue - has_pruning = re.match(r"pruning vlan (\S+)", match) - if has_pruning: - trunk["pruning_vlans"] = has_pruning.group(1) - continue - config['trunk'] = trunk - - return self.generate_final_config(config) 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 59b462d..0000000 --- a/module_utils/ios/facts/resource/resource.py +++ /dev/null @@ -1,63 +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 {{ network_os }} {{ resource }} 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) 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/__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/network/ios/argspec/facts/facts.py b/module_utils/network/ios/argspec/facts/facts.py new file mode 100644 index 0000000..28344a7 --- /dev/null +++ b/module_utils/network/ios/argspec/facts/facts.py @@ -0,0 +1,21 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class FactsArgs(object): # pylint: disable=R0903 + """ The arg spec for the ios facts module + """ + + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'l2_interfaces', + '!l2_interfaces' + ] + + argument_spec = { + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(choices=choices, type='list'), + } \ No newline at end of file diff --git a/module_utils/ios/argspec/l2_interfaces/__init__.py b/module_utils/network/ios/argspec/l2_interfaces/__init__.py similarity index 100% rename from module_utils/ios/argspec/l2_interfaces/__init__.py rename to module_utils/network/ios/argspec/l2_interfaces/__init__.py diff --git a/module_utils/network/ios/argspec/l2_interfaces/l2_interfaces.py b/module_utils/network/ios/argspec/l2_interfaces/l2_interfaces.py new file mode 100644 index 0000000..5c21653 --- /dev/null +++ b/module_utils/network/ios/argspec/l2_interfaces/l2_interfaces.py @@ -0,0 +1,51 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the ios_interfaces module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class L2_InterfacesArgs(object): + + def __init__(self, **kwargs): + pass + + argument_spec = {'config': {'elements': 'dict', + 'options': {'name': {'type': 'str', 'required': True}, + 'access': {'type': 'dict', + 'options': {'vlan': {'type': 'int'}} + }, + 'trunk': {'type': 'dict', + 'options': {'allowed_vlans': {'type': 'list'}, + 'encapsulation': {'type': 'str'}, + 'native_vlan': {'type': 'int'}, + 'pruning_vlans': {'type': 'list'}} + }}, + '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/base.py b/module_utils/network/ios/config/base.py similarity index 100% rename from module_utils/ios/config/base.py rename to module_utils/network/ios/config/base.py diff --git a/module_utils/ios/config/__init__.py b/module_utils/network/ios/config/l2_interfaces/__init__.py similarity index 100% rename from module_utils/ios/config/__init__.py rename to module_utils/network/ios/config/l2_interfaces/__init__.py diff --git a/module_utils/network/ios/config/l2_interfaces/l2_interfaces.py b/module_utils/network/ios/config/l2_interfaces/l2_interfaces.py new file mode 100644 index 0000000..bddfa33 --- /dev/null +++ b/module_utils/network/ios/config/l2_interfaces/l2_interfaces.py @@ -0,0 +1,313 @@ +# -*- 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_l2_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.network.ios.facts.facts import Facts +from ansible.module_utils.network.ios.utils.utils import 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 + + +class L2_Interfaces(ConfigBase): + """ + The ios_l2_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'l2_interfaces', + ] + + access_cmds = {'access_vlan': 'switchport access vlan'} + trunk_cmds = {'encapsulation': 'switchport trunk encapsulation', 'pruning_vlans': 'switchport trunk pruning vlan', + 'native_vlan': 'switchport trunk native vlan', 'allowed_vlans': 'switchport trunk allowed vlan'} + + def get_interfaces_facts(self): + """ Get the 'facts' (the current configuration) + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + interfaces_facts = facts['ansible_network_resources'].get('l2_interfaces') + if not interfaces_facts: + return [] + + return interfaces_facts + + def execute_module(self): + """ Execute the module + :rtype: A dictionary + :returns: The result from moduel execution + """ + result = {'changed': False} + commands = [] + warnings = [] + existing_facts = self.get_interfaces_facts() + commands.extend(self.set_config(existing_facts)) + result['before'] = existing_facts + if commands: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + result['commands'] = commands + + interfaces_facts = self.get_interfaces_facts() + + if result['changed']: + result['after'] = interfaces_facts + result['warnings'] = warnings + return result + + def set_config(self, existing_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 deisred configuration + """ + + want = self._module.params['config'] + have = existing_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 deisred 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 + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :param interface_type: interface type + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the deisred configuration + """ + commands = [] + + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + else: + 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 + :param want: the desired configuration as a dictionary + :param obj_in_have: the current configuration as a dictionary + :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 + :param want: the additive configuration as a dictionary + :param obj_in_have: the current configuration as a dictionary + :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: + continue + commands.extend(self._set_config(interface, each, module)) + + return commands + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + :param want: the objects from which the configuration should be removed + :param obj_in_have: the current configuration as a dictionary + :param interface_type: interface type + :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 + 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 _check_for_correct_vlan_range(self, vlan, module): + # Function to check if the VLAN range passed is Valid + for each in vlan: + vlan_range = each.split('-') + if len(vlan_range) > 1: + if vlan_range[0] < vlan_range[1]: + return True + else: + module.fail_json(msg='Command rejected: Bad VLAN list - end of range not larger than the' + ' start of range!') + else: + return True + + def _set_config(self, want, have, module): + # Set the interface config based on the want and have config + commands = [] + interface = 'interface ' + want['name'] + + # Get the diff b/w want and have + want_dict = dict_diff(want) + have_dict = dict_diff(have) + want_trunk = dict(want_dict).get('trunk') + have_trunk = dict(have_dict).get('trunk') + if want_trunk and have_trunk: + diff = set(tuple(dict(want_dict).get('trunk'))) - set(tuple(dict(have_dict).get('trunk'))) + else: + diff = want_dict - have_dict + + if diff: + diff = dict(diff) + + if diff.get('access'): + cmd = 'switchport access vlan {0}'.format(diff.get('access')[0][1]) + add_command_to_config_list(interface, cmd, commands) + + if want_trunk: + if diff.get('trunk'): + diff = dict(diff.get('trunk')) + if diff.get('encapsulation'): + cmd = self.trunk_cmds['encapsulation'] + ' {0}'.format(diff.get('encapsulation')) + add_command_to_config_list(interface, cmd, commands) + if diff.get('native_vlan'): + cmd = self.trunk_cmds['native_vlan'] + ' {0}'.format(diff.get('native_vlan')) + add_command_to_config_list(interface, cmd, commands) + allowed_vlans = diff.get('allowed_vlans') + pruning_vlans = diff.get('pruning_vlans') + + if allowed_vlans and self._check_for_correct_vlan_range(allowed_vlans, module): + allowed_vlans = ','.join(allowed_vlans) + cmd = self.trunk_cmds['allowed_vlans'] + ' {0}'.format(allowed_vlans) + add_command_to_config_list(interface, cmd, commands) + if pruning_vlans and self._check_for_correct_vlan_range(pruning_vlans, module): + pruning_vlans = ','.join(pruning_vlans) + cmd = self.trunk_cmds['pruning_vlans'] + ' {0}'.format(pruning_vlans) + add_command_to_config_list(interface, cmd, commands) + + return commands + + def _clear_config(self, want, have): + # Delete the interface config based on the want and have config + commands = [] + if want.get('name'): + interface = 'interface ' + want['name'] + else: + interface = 'interface ' + have['name'] + + if have.get('access') and want.get('access') is None: + remove_command_from_config_list(interface, L2_Interfaces.access_cmds['access_vlan'], commands) + elif have.get('access') and want.get('access'): + if have.get('access').get('vlan') != want.get('access').get('vlan'): + remove_command_from_config_list(interface, L2_Interfaces.access_cmds['access_vlan'], commands) + + if have.get('trunk') and want.get('trunk') is None: + # Check when no config is passed + if have.get('trunk').get('encapsulation'): + remove_command_from_config_list(interface, self.trunk_cmds['encapsulation'], commands) + if have.get('trunk').get('native_vlan'): + remove_command_from_config_list(interface, self.trunk_cmds['native_vlan'], commands) + if have.get('trunk').get('allowed_vlans'): + remove_command_from_config_list(interface, self.trunk_cmds['allowed_vlans'], commands) + if have.get('trunk').get('pruning_vlans'): + remove_command_from_config_list(interface, self.trunk_cmds['pruning_vlans'], commands) + elif have.get('trunk') and want.get('trunk'): + # Check when config is passed, also used in replaced and override state + if have.get('trunk').get('encapsulation')\ + and have.get('trunk').get('encapsulation') != want.get('trunk').get('encapsulation'): + remove_command_from_config_list(interface, self.trunk_cmds['encapsulation'], commands) + if have.get('trunk').get('native_vlan') \ + and have.get('trunk').get('native_vlan') != want.get('trunk').get('native_vlan'): + remove_command_from_config_list(interface, self.trunk_cmds['native_vlan'], commands) + if have.get('trunk').get('allowed_vlans') \ + and have.get('trunk').get('allowed_vlans') != want.get('trunk').get('allowed_vlans'): + remove_command_from_config_list(interface, self.trunk_cmds['allowed_vlans'], commands) + if have.get('trunk').get('pruning_vlans') \ + and have.get('trunk').get('pruning_vlans') != want.get('trunk').get('pruning_vlans'): + remove_command_from_config_list(interface, self.trunk_cmds['pruning_vlans'], commands) + + + return commands diff --git a/module_utils/ios/config/l2_interfaces/__init__.py b/module_utils/network/ios/facts/__init__.py similarity index 100% rename from module_utils/ios/config/l2_interfaces/__init__.py rename to module_utils/network/ios/facts/__init__.py diff --git a/module_utils/ios/facts/base.py b/module_utils/network/ios/facts/base.py similarity index 100% rename from module_utils/ios/facts/base.py rename to module_utils/network/ios/facts/base.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..a8a9eae --- /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.l2_interfaces.l2_interfaces import L2_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, + l2_interfaces=L2_InterfacesFacts, +) + + +class Facts(FactsBase): + """ The fact class for ios + """ + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) + + def __init__(self, module): + super(Facts, self).__init__(module) + + def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None): + """ Collect the facts for ios + :param legacy_facts_type: List of legacy facts types + :param resource_facts_type: List of resource fact types + :param data: previously collected conf + :rtype: dict + :return: the facts gathered + """ + netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', []) + if self.VALID_RESOURCE_SUBSETS: + self.get_network_resources_facts(netres_choices, FACT_RESOURCE_SUBSETS, resource_facts_type, data) + + if self.VALID_LEGACY_GATHER_SUBSETS: + self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type) + + return self.ansible_facts, self._warnings diff --git a/module_utils/ios/config/resource/__init__.py b/module_utils/network/ios/facts/l2_interfaces/__init__.py similarity index 100% rename from module_utils/ios/config/resource/__init__.py rename to module_utils/network/ios/facts/l2_interfaces/__init__.py diff --git a/module_utils/network/ios/facts/l2_interfaces/l2_interfaces.py b/module_utils/network/ios/facts/l2_interfaces/l2_interfaces.py new file mode 100644 index 0000000..d175f31 --- /dev/null +++ b/module_utils/network/ios/facts/l2_interfaces/l2_interfaces.py @@ -0,0 +1,108 @@ +# +# -*- 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 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. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +import re +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.ios.utils.utils import get_interface_type, normalize_interface +from ansible.module_utils.network.ios.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs + + +class L2_InterfacesFacts(object): + """ The ios l2 interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = L2_InterfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for interfaces + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + + if not data: + data = connection.get('show running-config | 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['l2_interfaces'] = [] + params = utils.validate_config(self.argument_spec, {'config': objs}) + for cfg in params['config']: + facts['l2_interfaces'].append(utils.remove_empties(cfg)) + ansible_facts['ansible_network_resources'].update(facts) + + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys from spec for null values + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + match = re.search(r'^(\S+)', conf) + intf = match.group(1) + + if get_interface_type(intf) == 'unknown': + return {} + + if intf.lower().startswith('gi'): + # populate the facts from the configuration + config['name'] = normalize_interface(intf) + + has_access = utils.parse_conf_arg(conf, 'switchport access vlan') + if has_access: + config["access"] = {"vlan": int(has_access)} + + trunk = dict() + trunk["encapsulation"] = utils.parse_conf_arg(conf, 'encapsulation') + native_vlan = utils.parse_conf_arg(conf, 'native vlan') + if native_vlan: + trunk["native_vlan"] = int(native_vlan) + allowed_vlan = utils.parse_conf_arg(conf, 'allowed vlan') + if allowed_vlan: + trunk["allowed_vlans"] = allowed_vlan.split(',') + pruning_vlan = utils.parse_conf_arg(conf, 'pruning vlan') + if pruning_vlan: + trunk['pruning_vlans'] = pruning_vlan.split(',') + + config['trunk'] = trunk + + 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/ios/utils/utils.py b/module_utils/network/ios/utils/utils.py similarity index 52% rename from module_utils/ios/utils/utils.py rename to module_utils/network/ios/utils/utils.py index 040639d..1a2372b 100644 --- a/module_utils/ios/utils/utils.py +++ b/module_utils/network/ios/utils/utils.py @@ -5,6 +5,84 @@ # utils +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.six import iteritems + + +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 sample_dict.items(): + if v is not None: + if isinstance(v, list): + if isinstance(v[0], dict): + li = [] + for each in v: + for key, value in each.items(): + if isinstance(value, list): + each[key] = tuple(value) + li.extend(tuple(each.items())) + v = tuple(li) + else: + v = tuple(v) + elif isinstance(v, dict): + li = [] + for key, value in v.items(): + 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 want.items(): + if isinstance(v, dict): + 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 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 search_obj_in_list(name, lst): for o in lst: