From a095f663b36b3533ed14524fdc0b0df491659b24 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 5 Jul 2019 15:57:43 +0530 Subject: [PATCH 01/14] ios lag interface Signed-off-by: Sumit Jaiswal --- library/__init__.py | 0 library/ios_facts.py | 112 ++++++ library/ios_lag_interfaces.py | 372 ++++++++++++++++++ module_utils/__init__.py | 0 module_utils/network/__ini__.py | 0 module_utils/network/ios/__init__.py | 0 module_utils/network/ios/argspec/__init__.py | 0 .../network/ios/argspec/facts/__init__.py | 0 .../network/ios/argspec/facts/facts.py | 29 ++ .../ios/argspec/lag_interfaces/__init__.py | 0 .../argspec/lag_interfaces/lag_interfaces.py | 50 +++ .../network/ios/argspec/resource/__init__.py | 0 .../network/ios/argspec/resource/resource.py | 25 ++ module_utils/network/ios/config/__init__.py | 0 module_utils/network/ios/config/base.py | 26 ++ .../ios/config/lag_interfaces/__init__.py | 0 .../config/lag_interfaces/lag_interfaces.py | 306 ++++++++++++++ .../network/ios/config/resource/__init__.py | 0 module_utils/network/ios/facts/__init__.py | 0 module_utils/network/ios/facts/facts.py | 49 +++ .../ios/facts/lag_interfaces/__init__.py | 0 .../facts/lag_interfaces/lag_interfaces.py | 115 ++++++ .../network/ios/facts/resource/__init__.py | 0 module_utils/network/ios/utils/__init__.py | 0 module_utils/network/ios/utils/utils.py | 100 +++++ 25 files changed, 1184 insertions(+) create mode 100644 library/__init__.py create mode 100644 library/ios_facts.py create mode 100644 library/ios_lag_interfaces.py create mode 100644 module_utils/__init__.py create mode 100644 module_utils/network/__ini__.py create mode 100644 module_utils/network/ios/__init__.py create mode 100644 module_utils/network/ios/argspec/__init__.py create mode 100644 module_utils/network/ios/argspec/facts/__init__.py create mode 100644 module_utils/network/ios/argspec/facts/facts.py create mode 100644 module_utils/network/ios/argspec/lag_interfaces/__init__.py create mode 100644 module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py create mode 100644 module_utils/network/ios/argspec/resource/__init__.py create mode 100644 module_utils/network/ios/argspec/resource/resource.py create mode 100644 module_utils/network/ios/config/__init__.py create mode 100644 module_utils/network/ios/config/base.py create mode 100644 module_utils/network/ios/config/lag_interfaces/__init__.py create mode 100644 module_utils/network/ios/config/lag_interfaces/lag_interfaces.py create mode 100644 module_utils/network/ios/config/resource/__init__.py create mode 100644 module_utils/network/ios/facts/__init__.py create mode 100644 module_utils/network/ios/facts/facts.py create mode 100644 module_utils/network/ios/facts/lag_interfaces/__init__.py create mode 100644 module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py create mode 100644 module_utils/network/ios/facts/resource/__init__.py create mode 100644 module_utils/network/ios/utils/__init__.py create mode 100644 module_utils/network/ios/utils/utils.py diff --git a/library/__init__.py b/library/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/library/ios_facts.py b/library/ios_facts.py new file mode 100644 index 0000000..b87568a --- /dev/null +++ b/library/ios_facts.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The module file for ios_facts +""" + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network'} + +NETWORK_OS = "ios" +RESOURCE = "facts" +COPYRIGHT = "Copyright 2019 Red Hat" + +DOCUMENTATION = """ +--- +module: ios_facts +version_added: 2.9 +short_description: Get facts about Cisco ios devices. +description: + - Collects facts from network devices running the ios operating + system. This module places the facts gathered in the fact tree keyed by the + respective resource name. The facts module will always collect a + base set of facts from the device and can enable or disable + collection of additional facts. +author: [u'Sumit Jaiswal (@justjais)'] +notes: + - Tested against iosv Version 6.1.3 on VIRL +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, min, hardware, config, legacy, and interfaces. Can specify a + list of values to include a larger subset. Values can also be used + with an initial C(M(!)) to specify that a specific subset should + not be collected. + required: false + default: 'all' + version_added: "2.2" + gather_network_resources: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all and the resources like interfaces, vlans etc. + Can specify a list of values to include a larger subset. Values + can also be used with an initial C(M(!)) to specify that a + specific subset should not be collected. + required: false + version_added: "2.9" +""" + +EXAMPLES = """ +# Gather all facts +- ios_facts: + gather_subset: all + gather_network_resources: all +# Collect only the ios facts +- ios_facts: + gather_subset: + - !all + - !min + gather_network_resources: + - ios +# Do not collect ios facts +- ios_facts: + gather_network_resources: + - "!ios" +# Collect ios and minimal default facts +- ios_facts: + gather_subset: min + gather_network_resources: ios +""" + +RETURN = """ +See the respective resource module parameters for the tree. +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.ios.facts.facts import Facts + + +def main(): + """ + Main entry point for module execution + :returns: ansible_facts + """ + module = AnsibleModule(argument_spec=Facts.argument_spec, + supports_check_mode=True) + warnings = ['default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards'] + + connection = Connection(module._socket_path) + gather_subset = module.params['gather_subset'] + gather_network_resources = module.params['gather_network_resources'] + result = Facts().get_facts(module, connection, gather_subset, gather_network_resources) + + try: + ansible_facts, warning = result + warnings.extend(warning) + except (TypeError, KeyError): + ansible_facts = result + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/library/ios_lag_interfaces.py b/library/ios_lag_interfaces.py new file mode 100644 index 0000000..53363b4 --- /dev/null +++ b/library/ios_lag_interfaces.py @@ -0,0 +1,372 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################## +# WARNING +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +# +############################################## + +""" +The module file for ios_l3_interfaces +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +GENERATOR_VERSION = '1.0' + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network'} + +NETWORK_OS = "ios" +RESOURCE = "l3_interfaces" +COPYRIGHT = "Copyright 2019 Red Hat" + +DOCUMENTATION = """ +--- + module: ios_lag_interfaces + version_added: 2.9 + short_description: Manage Link Aggregation on Cisco IOS devices. + description: This module manages properties of Link Aggregation Group on Cisco IOS devices. + author: Sumit Jaiswal (@justjais) + notes: + - Tested against Cisco IOSv Version 15.2 on VIRL + options: + config: + description: A list of link aggregation group configurations. + type: list + elements: dict + suboptions: + name: + description: + - ID of Ethernet Channel of interfaces. Note, Port-channel group number must be in + between 1-48. + type: str + required: True + members: + description: + - Interface options for the link aggregation group. + suboptions: + member: + description: + - Interface member of the link aggregation group. + type: str + flowcontrol: + description: + - To configure the flow control on a given interface, use the flow control interface + (Ethernet, Port Channel) configuration mode command. Flow control is a feature that + allows the receiving device to send a signal to the sending device that it is congested. + This tells the sending device to temporarily stop transmitting to help ease the congestion. + type: str + choices: + - on + - off + mode: + description: + - Etherchannel Mode of the interface for link aggregation. + type: str + choices: + - auto + - on + - desirable + - active + - passive + link: + description: + - Assign a link identifier used for load-balancing. Channel group load-balancing link + identifier with range between 1-4. Note, parameter only supported on Cisco IOS XE + platform. + type: int + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +""" + +EXAMPLES = """ +--- +# Using merged +# +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# interface GigabitEthernet0/1 +# shutdown +# interface GigabitEthernet0/2 +# shutdown +# interface GigabitEthernet0/3 +# shutdown +# interface GigabitEthernet0/4 +# shutdown + +- name: Merge provided configuration with device configuration + ios_lag_interfaces: + config: + - name: 10 + members: + member: GigabitEthernet0/1 + mode: auto + flowcontrol: on + member: GigabitEthernet0/2 + mode: auto + - name: 20 + members: + member: GigabitEthernet0/3 + mode: on + - name: 30 + members: + member: GigabitEthernet0/4 + mode: active + flowcontrol: on + operation: merged + +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# flowcontrol receive on +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/2 +# shutdown +# channel-group 10 mode auto +# interface GigabitEthernet0/3 +# shutdown +# channel-group 20 mode on +# interface GigabitEthernet0/4 +# shutdown +# flowcontrol receive on +# channel-group 30 mode active + +# Using overridden +# +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# flowcontrol receive on +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/2 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/3 +# shutdown +# channel-group 20 mode on +# interface GigabitEthernet0/4 +# shutdown +# channel-group 30 mode active + +- name: Override device configuration of all interfaces with provided configuration + ios_lag_interfaces: + config: + - name: 20 + members: + member: GigabitEthernet0/2 + mode: auto + flowcontrol: on + member: GigabitEthernet0/3 + mode: auto + operation: overridden + +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# interface GigabitEthernet0/2 +# shutdown +# flowcontrol receive on +# channel-group 20 mode auto +# interface GigabitEthernet0/3 +# shutdown +# channel-group 20 mode auto +# interface GigabitEthernet0/4 +# shutdown + +# Using replaced +# +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# flowcontrol receive on +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/2 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/3 +# shutdown +# channel-group 20 mode on +# interface GigabitEthernet0/4 +# shutdown +# channel-group 30 mode active + +- name: Replaces device configuration of listed interfaces with provided configuration + ios_lag_interfaces: + config: + - name: 40 + members: + member: GigabitEthernet0/3 + mode: auto + flowcontrol: on + operation: replaced + +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# flowcontrol receive on +# interface Port-channel20 +# interface Port-channel30 +# interface Port-channel40 +# flowcontrol receive off +# interface GigabitEthernet0/1 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/2 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/3 +# shutdown +# flowcontrol receive on +# channel-group 40 mode on +# interface GigabitEthernet0/4 +# shutdown +# channel-group 30 mode active + +# Using Deleted +# +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# flowcontrol receive on +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/2 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/3 +# shutdown +# channel-group 20 mode on +# interface GigabitEthernet0/4 +# shutdown +# channel-group 30 mode active + +- name: Delete LAG attributes of given interfaces (Note: This won't delete the interface itself) + ios_lag_interfaces: + config: + - name: 10 + - name: 20 + - name: 30 + operation: deleted + +# After state: +# ------------- +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# interface GigabitEthernet0/2 +# shutdown +# interface GigabitEthernet0/3 +# shutdown +# interface GigabitEthernet0/4 +# shutdown +""" + +RETURN = """ +before: + description: The configuration prior to the model invocation + returned: always + sample: The configuration returned will alwys be in the same format of the paramters above. +after: + description: The resulting configuration model invocation + returned: when changed + sample: The configuration returned will alwys be in the same format of the paramters above. +commands: + description: The set of commands pushed to the remote device + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.ios.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs +from ansible.module_utils.network.ios.config.lag_interfaces.lag_interfaces import Lag_interfaces + + +def main(): + """ + Main entry point for module execution + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Lag_interfacesArgs.argument_spec, + supports_check_mode=True) + result = Lag_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/module_utils/__init__.py b/module_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/__ini__.py b/module_utils/network/__ini__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/__init__.py b/module_utils/network/ios/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/argspec/__init__.py b/module_utils/network/ios/argspec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/argspec/facts/__init__.py b/module_utils/network/ios/argspec/facts/__init__.py new file mode 100644 index 0000000..e69de29 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..abe32d7 --- /dev/null +++ b/module_utils/network/ios/argspec/facts/facts.py @@ -0,0 +1,29 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The arg spec for the ios facts module. +""" + + +class FactsArgs(object): + """ The arg spec for the ios facts module + """ + + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'interfaces', + ] + + argument_spec = { + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(default=['all'], + choices=choices, + type='list'), + } + diff --git a/module_utils/network/ios/argspec/lag_interfaces/__init__.py b/module_utils/network/ios/argspec/lag_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py new file mode 100644 index 0000000..18e3698 --- /dev/null +++ b/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py @@ -0,0 +1,50 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the ios_lag_interfaces module +""" + + +class Lag_interfacesArgs(object): + + def __init__(self, **kwargs): + pass + + argument_spec = {'config': {'elements': 'dict', + 'options': {'name': {'required': True, 'type': 'str'}, + 'members': {'elements': 'dict', + 'options': { + 'member': {'type': 'str'}, + 'flowcontrol': {'choices': ['on', 'off']}, + 'mode': {'choices': ['auto', 'on', 'desirable', + 'active', 'passive'], + 'type': 'str', 'required': True}, + 'link': {'type': 'int'} + }, + 'type': 'list'}}, + 'type': 'list'}, + 'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} diff --git a/module_utils/network/ios/argspec/resource/__init__.py b/module_utils/network/ios/argspec/resource/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/argspec/resource/resource.py b/module_utils/network/ios/argspec/resource/resource.py new file mode 100644 index 0000000..22c796d --- /dev/null +++ b/module_utils/network/ios/argspec/resource/resource.py @@ -0,0 +1,25 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# 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_lag_interfaces module +""" + diff --git a/module_utils/network/ios/config/__init__.py b/module_utils/network/ios/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/config/base.py b/module_utils/network/ios/config/base.py new file mode 100644 index 0000000..8090821 --- /dev/null +++ b/module_utils/network/ios/config/base.py @@ -0,0 +1,26 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The base class for all ios resource modules +""" + +from ansible.module_utils.connection import Connection + + +class ConfigBase(object): + """ The base class for all ios resource modules + """ + _connection = None + + def __init__(self, module): + self._module = module + self._connection = self._get_connection() + + def _get_connection(self): + if self._connection: + return self._connection + self._connection = Connection(self._module._socket_path) + return self._connection + diff --git a/module_utils/network/ios/config/lag_interfaces/__init__.py b/module_utils/network/ios/config/lag_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py new file mode 100644 index 0000000..c3583cc --- /dev/null +++ b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py @@ -0,0 +1,306 @@ +# +# -*- 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_lag_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.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.network.ios.facts.facts import Facts + + +class Lag_interfaces(ConfigBase): + """ + The ios_lag_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'interfaces', + ] + + def __init__(self, module): + super(Lag_interfaces, self).__init__(module) + + def get_interfaces_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + interfaces_facts = facts['ansible_network_resources'].get('lag_interfaces') + if not interfaces_facts: + return [] + return interfaces_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + commands = list() + warnings = list() + + existing_interfaces_facts = self.get_interfaces_facts() + commands.extend(self.set_config(existing_interfaces_facts)) + if commands: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + result['commands'] = commands + + changed_interfaces_facts = self.get_interfaces_facts() + + result['before'] = existing_interfaces_facts + if result['changed']: + result['after'] = changed_interfaces_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_interfaces_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_interfaces_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + + state = self._module.params['state'] + if state == 'overridden': + kwargs = {'want': want, 'have': have} + commands = self._state_overridden(**kwargs) + elif state == 'deleted': + kwargs = {'want': want, 'have': have} + commands = self._state_deleted(**kwargs) + elif state == 'merged': + kwargs = {'want': want, 'have': have} + commands = self._state_merged(**kwargs) + elif state == 'replaced': + kwargs = {'want': want, 'have': have} + commands = self._state_replaced(**kwargs) + return commands + + @staticmethod + def _state_replaced(**kwargs): + """ The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + + for interface in want: + for each_interface in interface.get('members'): + for each in have: + if each['members']['member'] == each_interface['member']: + break + else: + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each} + commands.extend(Lag_interfaces.set_interface(**kwargs)) + # Remove the duplicate interface call + commands = Lag_interfaces._remove_duplicate_interface(commands) + + return commands + + @staticmethod + def _state_overridden(**kwargs): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + + for interface in want: + for each_interface in interface.get('members'): + for each in have: + if each['members']['member'] == each_interface.get('member'): + break + else: + # We didn't find a matching desired state, which means we can + # pretend we recieved an empty desired state. + kwargs = {'want': interface, 'have': each} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands} + commands.extend(Lag_interfaces.set_interface(**kwargs)) + # Remove the duplicate interface call + commands = Lag_interfaces._remove_duplicate_interface(commands) + + return commands + + @staticmethod + def _state_merged(**kwargs): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + + for interface in want: + for each_interface in interface.get('members'): + for each in have: + if each['members']['member'] == each_interface['member']: + break + else: + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Lag_interfaces.set_interface(**kwargs)) + + return commands + + @staticmethod + def _state_deleted(**kwargs): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + + for interface in want: + for each in have: + port_channel = 'port-channel{}'.format(interface.get('name')) + if interface.get('name') == each.get('name'): + kwargs = {'want': interface, 'have': each} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) + elif port_channel == each.get('members').get('member'): + kwargs = {'want': interface, 'have': each} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) + else: + continue + + 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) + if cmd not in commands: + commands.append(cmd) + + @staticmethod + def _remove_duplicate_interface(commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if 'interface' in each: + interface = each + if interface not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + + + @staticmethod + def set_interface(**kwargs): + # Set the interface config based on the want and have config + commands = [] + want = kwargs['want'] + have = kwargs['have'] + interface = 'interface ' + have['members']['member'] + member_diff = None + + want_members = set(tuple({k: v for k, v in iteritems(member) if v is not None}.items()) + for member in want.get("members") or []) + have_member = set(tuple({k:v for k, v in iteritems(have['members']) if v is not None}.items())) + + # Get the diff between want and have members + for each_member in want_members: + if dict(each_member)['member'] == dict(have_member)['member']: + member_diff = dict(set(each_member) - have_member) + break + mode = dict(member_diff).get('mode') + link = dict(member_diff).get('link') + flowcontrol = dict(member_diff).get('flowcontrol') + + # Compare the value and set the commands + if want.get('name') != have.get('name'): + cmd = 'channel-group {} mode {}'.format(want['name'], want['members'][0]['mode']) + Lag_interfaces._add_command_to_interface(interface, cmd, commands) + if mode: + cmd = 'channel-group {} mode {}'.format(want['name'], mode) + Lag_interfaces._add_command_to_interface(interface, cmd, commands) + elif link: + cmd = 'channel-group {} mode {}'.format(want['name'], link) + Lag_interfaces._add_command_to_interface(interface, cmd, commands) + if flowcontrol and not have.get('members').get('flowcontrol'): + cmd = 'flowcontrol receive {}'.format(flowcontrol) + Lag_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 ' + have['members']['member'] + + if have.get('members').get('flowcontrol'): + cmd = 'flowcontrol receive' + Lag_interfaces._remove_command_from_interface(interface, cmd, commands) + if have.get('name') and want.get('name'): + Lag_interfaces._remove_command_from_interface(interface, 'channel-group', commands) + + return commands diff --git a/module_utils/network/ios/config/resource/__init__.py b/module_utils/network/ios/config/resource/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/facts/__init__.py b/module_utils/network/ios/facts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/facts/facts.py b/module_utils/network/ios/facts/facts.py new file mode 100644 index 0000000..d860a9f --- /dev/null +++ b/module_utils/network/ios/facts/facts.py @@ -0,0 +1,49 @@ +# +# -*- 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 lag_interfaces +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +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.lag_interfaces.lag_interfaces import Lag_interfacesFacts + + +FACT_LEGACY_SUBSETS = {} +FACT_RESOURCE_SUBSETS = dict( + interfaces=Lag_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/network/ios/facts/lag_interfaces/__init__.py b/module_utils/network/ios/facts/lag_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py new file mode 100644 index 0000000..c910f9b --- /dev/null +++ b/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py @@ -0,0 +1,115 @@ +# +# -*- 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 lag_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.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.lag_interfaces.lag_interfaces import Lag_interfacesArgs + + +class Lag_interfacesFacts(object): + """ The ios_lag_interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Lag_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 + """ + if connection: # just for linting purposes, remove + pass + + 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['lag_interfaces'] = [] + #params = utils.validate_config(self.argument_spec, {'config': objs}) + + params = {'config': objs} + + for cfg in params['config']: + facts['lag_interfaces'].append(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 {} + members = {} + channel_group = utils.parse_conf_arg(conf, 'channel-group') + + if channel_group: + channel_group = channel_group.split(' ') + config['name'] = channel_group[0] + #config['mode'] = channel_group[2] + if 'mode' in channel_group: + mode = channel_group[2] + members.update({'mode': mode}) + if 'link' in channel_group: + link = channel_group[2] + members.update({'link': link}) + flowcontrol = utils.parse_conf_arg(conf, 'flowcontrol receive') + if flowcontrol: + members.update({'flowcontrol': flowcontrol}) + # else: + # members.update({'flowcontrol': 'off'}) + + member = normalize_interface(intf) + + members.update({'member': member}) + config['members'] = members + + return utils.remove_empties(config) + diff --git a/module_utils/network/ios/facts/resource/__init__.py b/module_utils/network/ios/facts/resource/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/utils/__init__.py b/module_utils/network/ios/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/utils/utils.py b/module_utils/network/ios/utils/utils.py new file mode 100644 index 0000000..4ed570d --- /dev/null +++ b/module_utils/network/ios/utils/utils.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# 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 521fdae95a176eb1572f18e1dfbbf3444b4e6610 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 8 Jul 2019 14:31:01 +0530 Subject: [PATCH 02/14] fix bug Signed-off-by: Sumit Jaiswal --- .../config/lag_interfaces/lag_interfaces.py | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py index c3583cc..c315962 100644 --- a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py @@ -102,7 +102,7 @@ def set_state(self, want, have): kwargs = {'want': want, 'have': have} commands = self._state_overridden(**kwargs) elif state == 'deleted': - kwargs = {'want': want, 'have': have} + kwargs = {'want': want, 'have': have, 'state': state} commands = self._state_deleted(**kwargs) elif state == 'merged': kwargs = {'want': want, 'have': have} @@ -207,15 +207,16 @@ def _state_deleted(**kwargs): commands = [] want = kwargs['want'] have = kwargs['have'] + state = kwargs['state'] for interface in want: for each in have: port_channel = 'port-channel{}'.format(interface.get('name')) if interface.get('name') == each.get('name'): - kwargs = {'want': interface, 'have': each} + kwargs = {'want': interface, 'have': each, 'state': state} commands.extend(Lag_interfaces.clear_interface(**kwargs)) elif port_channel == each.get('members').get('member'): - kwargs = {'want': interface, 'have': each} + kwargs = {'want': interface, 'have': each, 'state': state} commands.extend(Lag_interfaces.clear_interface(**kwargs)) else: continue @@ -263,7 +264,6 @@ def set_interface(**kwargs): want_members = set(tuple({k: v for k, v in iteritems(member) if v is not None}.items()) for member in want.get("members") or []) have_member = set(tuple({k:v for k, v in iteritems(have['members']) if v is not None}.items())) - # Get the diff between want and have members for each_member in want_members: if dict(each_member)['member'] == dict(have_member)['member']: @@ -274,18 +274,17 @@ def set_interface(**kwargs): flowcontrol = dict(member_diff).get('flowcontrol') # Compare the value and set the commands - if want.get('name') != have.get('name'): - cmd = 'channel-group {} mode {}'.format(want['name'], want['members'][0]['mode']) - Lag_interfaces._add_command_to_interface(interface, cmd, commands) if mode: cmd = 'channel-group {} mode {}'.format(want['name'], mode) Lag_interfaces._add_command_to_interface(interface, cmd, commands) elif link: cmd = 'channel-group {} mode {}'.format(want['name'], link) Lag_interfaces._add_command_to_interface(interface, cmd, commands) - if flowcontrol and not have.get('members').get('flowcontrol'): - cmd = 'flowcontrol receive {}'.format(flowcontrol) - Lag_interfaces._add_command_to_interface(interface, cmd, commands) + if flowcontrol: + if have.get('members').get('flowcontrol') == 'on' and flowcontrol == 'off': + Lag_interfaces._add_command_to_interface(interface, 'flowcontrol receive off', commands) + elif not have.get('members').get('flowcontrol') and flowcontrol == 'on': + Lag_interfaces._add_command_to_interface(interface, 'flowcontrol receive on', commands) return commands @@ -295,12 +294,22 @@ def clear_interface(**kwargs): commands = [] want = kwargs['want'] have = kwargs['have'] + state = kwargs['state'] if kwargs.get('state') else '' interface = 'interface ' + have['members']['member'] - if have.get('members').get('flowcontrol'): - cmd = 'flowcontrol receive' - Lag_interfaces._remove_command_from_interface(interface, cmd, commands) - if have.get('name') and want.get('name'): + if want.get('members'): + for each in want.get('members'): + if have['members']['member'] == each['member']: + if have.get('members').get('flowcontrol') and \ + have.get('members').get('flowcontrol') != each.get('flowcontrol'): + Lag_interfaces._remove_command_from_interface(interface, 'flowcontrol receive', commands) + break + elif have['members'].get('flowcontrol'): + Lag_interfaces._remove_command_from_interface(interface, 'flowcontrol receive', commands) + else: + if have.get('members').get('flowcontrol'): + Lag_interfaces._remove_command_from_interface(interface, 'flowcontrol receive', commands) + if have.get('name') and (have.get('name') != want.get('name') or state == 'deleted'): Lag_interfaces._remove_command_from_interface(interface, 'channel-group', commands) return commands From ea21dd2edca64c46575ce304c5beef7aa3678be2 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Jul 2019 15:25:41 +0530 Subject: [PATCH 03/14] support all delete method Signed-off-by: Sumit Jaiswal --- library/ios_lag_interfaces.py | 55 +++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/library/ios_lag_interfaces.py b/library/ios_lag_interfaces.py index 53363b4..8b568a9 100644 --- a/library/ios_lag_interfaces.py +++ b/library/ios_lag_interfaces.py @@ -144,7 +144,7 @@ member: GigabitEthernet0/4 mode: active flowcontrol: on - operation: merged + state: merged # After state: # ------------ @@ -204,7 +204,7 @@ flowcontrol: on member: GigabitEthernet0/3 mode: auto - operation: overridden + state: overridden # After state: # ------------ @@ -258,7 +258,7 @@ member: GigabitEthernet0/3 mode: auto flowcontrol: on - operation: replaced + state: replaced # After state: # ------------ @@ -316,8 +316,53 @@ config: - name: 10 - name: 20 - - name: 30 - operation: deleted + state: deleted + +# After state: +# ------------- +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# interface GigabitEthernet0/2 +# shutdown +# interface GigabitEthernet0/3 +# shutdown +# interface GigabitEthernet0/4 +# shutdown +# channel-group 30 mode active + +# Using Deleted +# +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# flowcontrol receive on +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/2 +# shutdown +# flowcontrol receive on +# channel-group 10 mode auto +# interface GigabitEthernet0/3 +# shutdown +# channel-group 20 mode on +# interface GigabitEthernet0/4 +# shutdown +# channel-group 30 mode active + +- name: Delete LAG attributes of all configured interfaces (Note: This won't delete the interface itself) + ios_lag_interfaces: + state: deleted # After state: # ------------- From 27dd060016b78201a90a8668cd54e1e80a2720e3 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Jul 2019 15:25:55 +0530 Subject: [PATCH 04/14] support all delete method Signed-off-by: Sumit Jaiswal --- .../config/lag_interfaces/lag_interfaces.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py index c315962..03303cb 100644 --- a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py @@ -209,17 +209,22 @@ def _state_deleted(**kwargs): have = kwargs['have'] state = kwargs['state'] - for interface in want: + if want: + for interface in want: + for each in have: + port_channel = 'port-channel{}'.format(interface.get('name')) + if interface.get('name') == each.get('name'): + kwargs = {'want': interface, 'have': each, 'state': state} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) + elif port_channel == each.get('members').get('member'): + kwargs = {'want': interface, 'have': each, 'state': state} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) + else: + continue + else: for each in have: - port_channel = 'port-channel{}'.format(interface.get('name')) - if interface.get('name') == each.get('name'): - kwargs = {'want': interface, 'have': each, 'state': state} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) - elif port_channel == each.get('members').get('member'): - kwargs = {'want': interface, 'have': each, 'state': state} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) - else: - continue + kwargs = {'want': {}, 'have': each, 'state': state} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) return commands From 3fecee6a44aadfa18f908d9287ec75004b0b929d Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Jul 2019 15:28:43 +0530 Subject: [PATCH 05/14] support all delete method Signed-off-by: Sumit Jaiswal --- library/ios_lag_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ios_lag_interfaces.py b/library/ios_lag_interfaces.py index 8b568a9..ae6ad78 100644 --- a/library/ios_lag_interfaces.py +++ b/library/ios_lag_interfaces.py @@ -360,7 +360,7 @@ # shutdown # channel-group 30 mode active -- name: Delete LAG attributes of all configured interfaces (Note: This won't delete the interface itself) +- name: Delete LAG attributes for all configured interfaces (Note: This won't delete the interface itself) ios_lag_interfaces: state: deleted From 39365d5ebe4fae47aad58306b3a5d6ad4e4e78a6 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 15 Jul 2019 13:50:56 +0530 Subject: [PATCH 06/14] name to id Signed-off-by: Sumit Jaiswal --- library/ios_lag_interfaces.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/ios_lag_interfaces.py b/library/ios_lag_interfaces.py index ae6ad78..6039de5 100644 --- a/library/ios_lag_interfaces.py +++ b/library/ios_lag_interfaces.py @@ -55,11 +55,11 @@ type: list elements: dict suboptions: - name: + id: description: - ID of Ethernet Channel of interfaces. Note, Port-channel group number must be in between 1-48. - type: str + type: int required: True members: description: @@ -128,18 +128,18 @@ - name: Merge provided configuration with device configuration ios_lag_interfaces: config: - - name: 10 + - id: 10 members: member: GigabitEthernet0/1 mode: auto flowcontrol: on member: GigabitEthernet0/2 mode: auto - - name: 20 + - id: 20 members: member: GigabitEthernet0/3 mode: on - - name: 30 + - id: 30 members: member: GigabitEthernet0/4 mode: active @@ -197,7 +197,7 @@ - name: Override device configuration of all interfaces with provided configuration ios_lag_interfaces: config: - - name: 20 + - id: 20 members: member: GigabitEthernet0/2 mode: auto @@ -253,7 +253,7 @@ - name: Replaces device configuration of listed interfaces with provided configuration ios_lag_interfaces: config: - - name: 40 + - id: 40 members: member: GigabitEthernet0/3 mode: auto @@ -314,8 +314,8 @@ - name: Delete LAG attributes of given interfaces (Note: This won't delete the interface itself) ios_lag_interfaces: config: - - name: 10 - - name: 20 + - id: 10 + - id: 20 state: deleted # After state: @@ -360,7 +360,7 @@ # shutdown # channel-group 30 mode active -- name: Delete LAG attributes for all configured interfaces (Note: This won't delete the interface itself) +- name: Delete all configured LAG attributes for interfaces (Note: This won't delete the interface itself) ios_lag_interfaces: state: deleted From 4f5691958316976477c4a3dba0eee99ee5c0edd5 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 15 Jul 2019 13:51:07 +0530 Subject: [PATCH 07/14] name to id Signed-off-by: Sumit Jaiswal --- .../network/ios/argspec/lag_interfaces/lag_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py index 18e3698..767ddca 100644 --- a/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py @@ -33,7 +33,7 @@ def __init__(self, **kwargs): pass argument_spec = {'config': {'elements': 'dict', - 'options': {'name': {'required': True, 'type': 'str'}, + 'options': {'id': {'required': True, 'type': 'int'}, 'members': {'elements': 'dict', 'options': { 'member': {'type': 'str'}, From fded10dc9ff9a9b06b41e729b38115accd99ce69 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 15 Jul 2019 13:51:33 +0530 Subject: [PATCH 08/14] name to id Signed-off-by: Sumit Jaiswal --- .../ios/config/lag_interfaces/lag_interfaces.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py index 03303cb..81dc441 100644 --- a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py @@ -212,8 +212,8 @@ def _state_deleted(**kwargs): if want: for interface in want: for each in have: - port_channel = 'port-channel{}'.format(interface.get('name')) - if interface.get('name') == each.get('name'): + port_channel = 'port-channel{}'.format(interface.get('id')) + if interface.get('id') == each.get('id'): kwargs = {'want': interface, 'have': each, 'state': state} commands.extend(Lag_interfaces.clear_interface(**kwargs)) elif port_channel == each.get('members').get('member'): @@ -280,10 +280,10 @@ def set_interface(**kwargs): # Compare the value and set the commands if mode: - cmd = 'channel-group {} mode {}'.format(want['name'], mode) + cmd = 'channel-group {} mode {}'.format(want['id'], mode) Lag_interfaces._add_command_to_interface(interface, cmd, commands) elif link: - cmd = 'channel-group {} mode {}'.format(want['name'], link) + cmd = 'channel-group {} link {}'.format(want['id'], link) Lag_interfaces._add_command_to_interface(interface, cmd, commands) if flowcontrol: if have.get('members').get('flowcontrol') == 'on' and flowcontrol == 'off': @@ -314,7 +314,7 @@ def clear_interface(**kwargs): else: if have.get('members').get('flowcontrol'): Lag_interfaces._remove_command_from_interface(interface, 'flowcontrol receive', commands) - if have.get('name') and (have.get('name') != want.get('name') or state == 'deleted'): + if have.get('id') and (have.get('id') != want.get('id') or state == 'deleted'): Lag_interfaces._remove_command_from_interface(interface, 'channel-group', commands) return commands From bafe220248aeea306a6136dfce8a99e8092e781c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 15 Jul 2019 13:51:43 +0530 Subject: [PATCH 09/14] name to id Signed-off-by: Sumit Jaiswal --- .../network/ios/facts/lag_interfaces/lag_interfaces.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py index c910f9b..9f26f0c 100644 --- a/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py @@ -92,8 +92,7 @@ def render_config(self, spec, conf): if channel_group: channel_group = channel_group.split(' ') - config['name'] = channel_group[0] - #config['mode'] = channel_group[2] + config['id'] = int(channel_group[0]) if 'mode' in channel_group: mode = channel_group[2] members.update({'mode': mode}) @@ -103,8 +102,6 @@ def render_config(self, spec, conf): flowcontrol = utils.parse_conf_arg(conf, 'flowcontrol receive') if flowcontrol: members.update({'flowcontrol': flowcontrol}) - # else: - # members.update({'flowcontrol': 'off'}) member = normalize_interface(intf) From 997a4637aa8e1dc6cf30deed841eeec04484fb0d Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 18 Jul 2019 16:16:53 +0530 Subject: [PATCH 10/14] updated review comments Signed-off-by: Sumit Jaiswal --- library/ios_lag_interfaces.py | 51 ++++++----------------------------- 1 file changed, 8 insertions(+), 43 deletions(-) diff --git a/library/ios_lag_interfaces.py b/library/ios_lag_interfaces.py index 6039de5..b945439 100644 --- a/library/ios_lag_interfaces.py +++ b/library/ios_lag_interfaces.py @@ -55,7 +55,7 @@ type: list elements: dict suboptions: - id: + name: description: - ID of Ethernet Channel of interfaces. Note, Port-channel group number must be in between 1-48. @@ -69,16 +69,6 @@ description: - Interface member of the link aggregation group. type: str - flowcontrol: - description: - - To configure the flow control on a given interface, use the flow control interface - (Ethernet, Port Channel) configuration mode command. Flow control is a feature that - allows the receiving device to send a signal to the sending device that it is congested. - This tells the sending device to temporarily stop transmitting to help ease the congestion. - type: str - choices: - - on - - off mode: description: - Etherchannel Mode of the interface for link aggregation. @@ -128,22 +118,20 @@ - name: Merge provided configuration with device configuration ios_lag_interfaces: config: - - id: 10 + - name: 10 members: member: GigabitEthernet0/1 mode: auto - flowcontrol: on member: GigabitEthernet0/2 mode: auto - - id: 20 + - name: 20 members: member: GigabitEthernet0/3 mode: on - - id: 30 + - name: 30 members: member: GigabitEthernet0/4 mode: active - flowcontrol: on state: merged # After state: @@ -151,12 +139,10 @@ # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/2 # shutdown @@ -166,7 +152,6 @@ # channel-group 20 mode on # interface GigabitEthernet0/4 # shutdown -# flowcontrol receive on # channel-group 30 mode active # Using overridden @@ -176,16 +161,13 @@ # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/2 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/3 # shutdown @@ -197,11 +179,10 @@ - name: Override device configuration of all interfaces with provided configuration ios_lag_interfaces: config: - - id: 20 + - name: 20 members: member: GigabitEthernet0/2 mode: auto - flowcontrol: on member: GigabitEthernet0/3 mode: auto state: overridden @@ -217,7 +198,6 @@ # shutdown # interface GigabitEthernet0/2 # shutdown -# flowcontrol receive on # channel-group 20 mode auto # interface GigabitEthernet0/3 # shutdown @@ -232,16 +212,13 @@ # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/2 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/3 # shutdown @@ -253,11 +230,10 @@ - name: Replaces device configuration of listed interfaces with provided configuration ios_lag_interfaces: config: - - id: 40 + - name: 40 members: member: GigabitEthernet0/3 mode: auto - flowcontrol: on state: replaced # After state: @@ -265,22 +241,17 @@ # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface Port-channel40 -# flowcontrol receive off # interface GigabitEthernet0/1 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/2 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/3 # shutdown -# flowcontrol receive on # channel-group 40 mode on # interface GigabitEthernet0/4 # shutdown @@ -293,16 +264,13 @@ # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/2 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/3 # shutdown @@ -314,8 +282,8 @@ - name: Delete LAG attributes of given interfaces (Note: This won't delete the interface itself) ios_lag_interfaces: config: - - id: 10 - - id: 20 + - name: 10 + - name: 20 state: deleted # After state: @@ -342,16 +310,13 @@ # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/2 # shutdown -# flowcontrol receive on # channel-group 10 mode auto # interface GigabitEthernet0/3 # shutdown From 16736e97a3884479dcad27b635325666ee899db4 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 18 Jul 2019 16:17:04 +0530 Subject: [PATCH 11/14] updated review comments Signed-off-by: Sumit Jaiswal --- .../network/ios/argspec/lag_interfaces/lag_interfaces.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py index 767ddca..95cccee 100644 --- a/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py @@ -33,11 +33,10 @@ def __init__(self, **kwargs): pass argument_spec = {'config': {'elements': 'dict', - 'options': {'id': {'required': True, 'type': 'int'}, + 'options': {'name': {'required': True, 'type': 'str'}, 'members': {'elements': 'dict', 'options': { 'member': {'type': 'str'}, - 'flowcontrol': {'choices': ['on', 'off']}, 'mode': {'choices': ['auto', 'on', 'desirable', 'active', 'passive'], 'type': 'str', 'required': True}, From 6a870946db31e77b3e3818663a5996b06e17a6c4 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 18 Jul 2019 16:17:15 +0530 Subject: [PATCH 12/14] updated review comments Signed-off-by: Sumit Jaiswal --- .../config/lag_interfaces/lag_interfaces.py | 141 +++++++++++------- 1 file changed, 89 insertions(+), 52 deletions(-) diff --git a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py index 81dc441..17e2efb 100644 --- a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py @@ -11,6 +11,7 @@ created """ +import re from ansible.module_utils.six import iteritems from ansible.module_utils.network.common.cfg.base import ConfigBase @@ -98,17 +99,18 @@ def set_state(self, want, have): """ state = self._module.params['state'] + module = self._module if state == 'overridden': - kwargs = {'want': want, 'have': have} + kwargs = {'want': want, 'have': have, 'module': module} commands = self._state_overridden(**kwargs) elif state == 'deleted': kwargs = {'want': want, 'have': have, 'state': state} commands = self._state_deleted(**kwargs) elif state == 'merged': - kwargs = {'want': want, 'have': have} + kwargs = {'want': want, 'have': have, 'module': module} commands = self._state_merged(**kwargs) elif state == 'replaced': - kwargs = {'want': want, 'have': have} + kwargs = {'want': want, 'have': have, 'module': module} commands = self._state_replaced(**kwargs) return commands @@ -123,17 +125,19 @@ def _state_replaced(**kwargs): commands = [] want = kwargs['want'] have = kwargs['have'] + module = kwargs['module'] for interface in want: for each_interface in interface.get('members'): for each in have: - if each['members']['member'] == each_interface['member']: - break + if each['members']: + if each['members'][0]['member'] == each_interface['member']: + break else: continue kwargs = {'want': interface, 'have': each} commands.extend(Lag_interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each} + kwargs = {'want': interface, 'have': each, 'module': module} commands.extend(Lag_interfaces.set_interface(**kwargs)) # Remove the duplicate interface call commands = Lag_interfaces._remove_duplicate_interface(commands) @@ -151,12 +155,18 @@ def _state_overridden(**kwargs): commands = [] want = kwargs['want'] have = kwargs['have'] + module = kwargs['module'] for interface in want: for each_interface in interface.get('members'): for each in have: - if each['members']['member'] == each_interface.get('member'): - break + if each['members']: + if each['members'][0]['member'] == each_interface['member']: + break + else: + kwargs = {'want': interface, 'have': each} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) + continue else: # We didn't find a matching desired state, which means we can # pretend we recieved an empty desired state. @@ -165,7 +175,7 @@ def _state_overridden(**kwargs): continue kwargs = {'want': interface, 'have': each} commands.extend(Lag_interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each, 'commands': commands} + kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} commands.extend(Lag_interfaces.set_interface(**kwargs)) # Remove the duplicate interface call commands = Lag_interfaces._remove_duplicate_interface(commands) @@ -183,15 +193,20 @@ def _state_merged(**kwargs): commands = [] want = kwargs['want'] have = kwargs['have'] + module = kwargs['module'] for interface in want: for each_interface in interface.get('members'): for each in have: - if each['members']['member'] == each_interface['member']: - break + if each['members']: + if each['members'][0]['member'] == each_interface['member']: + break + else: + if each.get('name') == each_interface['member']: + break else: continue - kwargs = {'want': interface, 'have': each} + kwargs = {'want': interface, 'have': each, 'module': module} commands.extend(Lag_interfaces.set_interface(**kwargs)) return commands @@ -212,15 +227,12 @@ def _state_deleted(**kwargs): if want: for interface in want: for each in have: - port_channel = 'port-channel{}'.format(interface.get('id')) - if interface.get('id') == each.get('id'): - kwargs = {'want': interface, 'have': each, 'state': state} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) - elif port_channel == each.get('members').get('member'): - kwargs = {'want': interface, 'have': each, 'state': state} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) - else: - continue + if each.get('name') == interface['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each, 'state': state} + commands.extend(Lag_interfaces.clear_interface(**kwargs)) else: for each in have: kwargs = {'want': {}, 'have': each, 'state': state} @@ -261,35 +273,71 @@ def _remove_duplicate_interface(commands): def set_interface(**kwargs): # Set the interface config based on the want and have config commands = [] + channel_name_diff = False want = kwargs['want'] have = kwargs['have'] - interface = 'interface ' + have['members']['member'] - member_diff = None + module = kwargs['module'] + if have['members']: + interface = 'interface ' + have['members'][0]['member'] + else: + interface = 'interface ' + have['name'] + member_diff = {} + want_each_member = {} + + # To check if channel-group differs in want n have + if have.get('name') != want.get('name'): + channel_name_diff = True + # Create Set for want and have field for diff comparison want_members = set(tuple({k: v for k, v in iteritems(member) if v is not None}.items()) for member in want.get("members") or []) - have_member = set(tuple({k:v for k, v in iteritems(have['members']) if v is not None}.items())) + if have['members']: + have_member = set(tuple({k:v for k, v in iteritems(have['members'][0]) if v is not None}.items())) + else: + have_member = set(tuple({k:v for k, v in iteritems(have) if v}.items())) + # Get the diff between want and have members for each_member in want_members: - if dict(each_member)['member'] == dict(have_member)['member']: - member_diff = dict(set(each_member) - have_member) - break + if dict(each_member)['member'] == dict(have_member).get('member'): + want_each_member = each_member + if have_member: + member_diff = dict(set(each_member) - have_member) + break + else: + member_diff = dict(set(each_member)) + break + elif dict(each_member)['member'] == dict(have_member).get('name'): + want_each_member = each_member + # To get the diff channel mode n link config from computed diff mode = dict(member_diff).get('mode') link = dict(member_diff).get('link') - flowcontrol = dict(member_diff).get('flowcontrol') - + # To get the channel-group ID from the interface Port-Channel + channel_name = re.search('(\d+)', want.get('name')) + if channel_name: + channel_id = channel_name.group() + else: + module.fail_json(msg="Lag Interface Name is not correct!") # Compare the value and set the commands if mode: - cmd = 'channel-group {} mode {}'.format(want['id'], mode) + cmd = 'channel-group {} mode {}'.format(channel_id, mode) Lag_interfaces._add_command_to_interface(interface, cmd, commands) elif link: - cmd = 'channel-group {} link {}'.format(want['id'], link) + cmd = 'channel-group {} link {}'.format(channel_id, link) Lag_interfaces._add_command_to_interface(interface, cmd, commands) - if flowcontrol: - if have.get('members').get('flowcontrol') == 'on' and flowcontrol == 'off': - Lag_interfaces._add_command_to_interface(interface, 'flowcontrol receive off', commands) - elif not have.get('members').get('flowcontrol') and flowcontrol == 'on': - Lag_interfaces._add_command_to_interface(interface, 'flowcontrol receive on', commands) + elif channel_name_diff and not member_diff: + # In Merge case if want n have channel ID differs and channel mode config is kept same + if want_each_member: + final_dict = dict(want_each_member) + else: + final_dict = dict(have_member) + mode = final_dict.get('mode') + link = final_dict.get('link') + if mode: + cmd = 'channel-group {} mode {}'.format(channel_id, mode) + Lag_interfaces._add_command_to_interface(interface, cmd, commands) + elif link: + cmd = 'channel-group {} link {}'.format(channel_id, link) + Lag_interfaces._add_command_to_interface(interface, cmd, commands) return commands @@ -300,21 +348,10 @@ def clear_interface(**kwargs): want = kwargs['want'] have = kwargs['have'] state = kwargs['state'] if kwargs.get('state') else '' - interface = 'interface ' + have['members']['member'] - - if want.get('members'): - for each in want.get('members'): - if have['members']['member'] == each['member']: - if have.get('members').get('flowcontrol') and \ - have.get('members').get('flowcontrol') != each.get('flowcontrol'): - Lag_interfaces._remove_command_from_interface(interface, 'flowcontrol receive', commands) - break - elif have['members'].get('flowcontrol'): - Lag_interfaces._remove_command_from_interface(interface, 'flowcontrol receive', commands) - else: - if have.get('members').get('flowcontrol'): - Lag_interfaces._remove_command_from_interface(interface, 'flowcontrol receive', commands) - if have.get('id') and (have.get('id') != want.get('id') or state == 'deleted'): - Lag_interfaces._remove_command_from_interface(interface, 'channel-group', commands) + + if have['members']: + interface = 'interface ' + have['members'][0]['member'] + if have.get('name') and (have.get('name') != want.get('name') or state == 'deleted'): + Lag_interfaces._remove_command_from_interface(interface, 'channel-group', commands) return commands From e46c326f6aaa29cb4c2cb5870e6d275d8bd7ad43 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 18 Jul 2019 16:17:26 +0530 Subject: [PATCH 13/14] updated review comments Signed-off-by: Sumit Jaiswal --- .../facts/lag_interfaces/lag_interfaces.py | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py index 9f26f0c..81aa89e 100644 --- a/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py @@ -56,14 +56,14 @@ def populate_facts(self, connection, ansible_facts, data=None): if conf: obj = self.render_config(self.generated_spec, conf) if obj: + if not obj.get('members'): + obj.update({'members': []}) objs.append(obj) facts = {} if objs: facts['lag_interfaces'] = [] - #params = utils.validate_config(self.argument_spec, {'config': objs}) - - params = {'config': objs} + params = utils.validate_config(self.argument_spec, {'config': objs}) for cfg in params['config']: facts['lag_interfaces'].append(cfg) @@ -87,26 +87,23 @@ def render_config(self, spec, conf): if get_interface_type(intf) == 'unknown': return {} - members = {} + member_config = {} channel_group = utils.parse_conf_arg(conf, 'channel-group') - - if channel_group: - channel_group = channel_group.split(' ') - config['id'] = int(channel_group[0]) - if 'mode' in channel_group: - mode = channel_group[2] - members.update({'mode': mode}) - if 'link' in channel_group: - link = channel_group[2] - members.update({'link': link}) - flowcontrol = utils.parse_conf_arg(conf, 'flowcontrol receive') - if flowcontrol: - members.update({'flowcontrol': flowcontrol}) - - member = normalize_interface(intf) - - members.update({'member': member}) - config['members'] = members + if intf.startswith('Gi'): + config['name'] = intf + config['members'] = [] + if channel_group: + channel_group = channel_group.split(' ') + id = channel_group[0] + config['name'] = 'Port-channel{}'.format(str(id)) + if 'mode' in channel_group: + mode = channel_group[2] + member_config.update({'mode': mode}) + if 'link' in channel_group: + link = channel_group[2] + member_config.update({'link': link}) + if member_config.get('mode') or member_config.get('link'): + member_config['member'] = normalize_interface(intf) + config['members'].append(member_config) return utils.remove_empties(config) - From f9d94ff73f7dc789df7e4156c6f7444221d74470 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 16 Aug 2019 15:21:02 +0530 Subject: [PATCH 14/14] fix vlans --- library/ios_lag_interfaces.py | 110 +++---- .../network/ios/argspec/facts/facts.py | 6 +- .../argspec/lag_interfaces/lag_interfaces.py | 3 + .../network/ios/argspec/resource/__init__.py | 0 .../network/ios/argspec/resource/resource.py | 25 -- .../config/lag_interfaces/lag_interfaces.py | 283 +++++++----------- .../network/ios/config/resource/__init__.py | 0 module_utils/network/ios/facts/facts.py | 2 +- .../facts/lag_interfaces/lag_interfaces.py | 11 +- .../network/ios/facts/resource/__init__.py | 0 module_utils/network/ios/utils/utils.py | 120 +++++++- 11 files changed, 302 insertions(+), 258 deletions(-) delete mode 100644 module_utils/network/ios/argspec/resource/__init__.py delete mode 100644 module_utils/network/ios/argspec/resource/resource.py delete mode 100644 module_utils/network/ios/config/resource/__init__.py delete mode 100644 module_utils/network/ios/facts/resource/__init__.py diff --git a/library/ios_lag_interfaces.py b/library/ios_lag_interfaces.py index b945439..2edf1a9 100644 --- a/library/ios_lag_interfaces.py +++ b/library/ios_lag_interfaces.py @@ -42,59 +42,61 @@ DOCUMENTATION = """ --- - module: ios_lag_interfaces - version_added: 2.9 - short_description: Manage Link Aggregation on Cisco IOS devices. - description: This module manages properties of Link Aggregation Group on Cisco IOS devices. - author: Sumit Jaiswal (@justjais) - notes: +module: ios_lag_interfaces +version_added: 2.9 +short_description: Manage Link Aggregation on Cisco IOS devices. +description: This module manages properties of Link Aggregation Group on Cisco IOS devices. +author: Sumit Jaiswal (@justjais) +notes: - Tested against Cisco IOSv Version 15.2 on VIRL - options: - config: - description: A list of link aggregation group configurations. - type: list - elements: dict + - This module works with connection C(network_cli). + See L(IOS Platform Options,../network/user_guide/platform_ios.html). +options: +config: + description: A list of link aggregation group configurations. + type: list + elements: dict + suboptions: + name: + description: + - ID of Ethernet Channel of interfaces. + - Refer to vendor documentation for valid port values. + type: int + required: True + members: + description: + - Interface options for the link aggregation group. suboptions: - name: + member: description: - - ID of Ethernet Channel of interfaces. Note, Port-channel group number must be in - between 1-48. - type: int - required: True - members: + - Interface member of the link aggregation group. + type: str + mode: description: - - Interface options for the link aggregation group. - suboptions: - member: - description: - - Interface member of the link aggregation group. - type: str - mode: - description: - - Etherchannel Mode of the interface for link aggregation. - type: str - choices: - - auto - - on - - desirable - - active - - passive - link: - description: - - Assign a link identifier used for load-balancing. Channel group load-balancing link - identifier with range between 1-4. Note, parameter only supported on Cisco IOS XE - platform. - type: int - state: - description: - - The state the configuration should be left in - type: str - choices: - - merged - - replaced - - overridden - - deleted - default: merged + - Etherchannel Mode of the interface for link aggregation. + type: str + choices: + - auto + - on + - desirable + - active + - passive + link: + description: + - Assign a link identifier used for load-balancing. + - Refer to vendor documentation for valid values. + - NOTE, parameter only supported on Cisco IOS XE platform. + type: int + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged """ EXAMPLES = """ @@ -279,7 +281,7 @@ # shutdown # channel-group 30 mode active -- name: Delete LAG attributes of given interfaces (Note: This won't delete the interface itself) +- name: "Delete LAG attributes of given interfaces (Note: This won't delete the interface itself)" ios_lag_interfaces: config: - name: 10 @@ -303,7 +305,9 @@ # shutdown # channel-group 30 mode active -# Using Deleted +# Using Deleted without any config passed +#"(NOTE: This will delete all of configured LLDP module attributes)" + # # Before state: # ------------- @@ -325,7 +329,7 @@ # shutdown # channel-group 30 mode active -- name: Delete all configured LAG attributes for interfaces (Note: This won't delete the interface itself) +- name: "Delete all configured LAG attributes for interfaces (Note: This won't delete the interface itself)" ios_lag_interfaces: state: deleted @@ -350,10 +354,12 @@ before: description: The configuration prior to the model invocation returned: always + type: list sample: The configuration returned will alwys be in the same format of the paramters above. after: description: The resulting configuration model invocation returned: when changed + type: list sample: The configuration returned will alwys be in the same format of the paramters above. commands: description: The set of commands pushed to the remote device diff --git a/module_utils/network/ios/argspec/facts/facts.py b/module_utils/network/ios/argspec/facts/facts.py index abe32d7..8f8d793 100644 --- a/module_utils/network/ios/argspec/facts/facts.py +++ b/module_utils/network/ios/argspec/facts/facts.py @@ -7,6 +7,9 @@ The arg spec for the ios facts module. """ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + class FactsArgs(object): """ The arg spec for the ios facts module @@ -17,7 +20,8 @@ def __init__(self, **kwargs): choices = [ 'all', - 'interfaces', + 'lag_interfaces', + '!lag_interfaces' ] argument_spec = { diff --git a/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py index 95cccee..3e30fff 100644 --- a/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/argspec/lag_interfaces/lag_interfaces.py @@ -26,6 +26,9 @@ The arg spec for the ios_lag_interfaces module """ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + class Lag_interfacesArgs(object): diff --git a/module_utils/network/ios/argspec/resource/__init__.py b/module_utils/network/ios/argspec/resource/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/network/ios/argspec/resource/resource.py b/module_utils/network/ios/argspec/resource/resource.py deleted file mode 100644 index 22c796d..0000000 --- a/module_utils/network/ios/argspec/resource/resource.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -############################################# -# 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_lag_interfaces module -""" - diff --git a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py index 17e2efb..31b227f 100644 --- a/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/config/lag_interfaces/lag_interfaces.py @@ -11,13 +11,18 @@ created """ -import re -from ansible.module_utils.six import iteritems +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re +from ansible.module_utils.network.common import utils 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_to_set +from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface +import q class Lag_interfaces(ConfigBase): """ @@ -30,7 +35,7 @@ class Lag_interfaces(ConfigBase): ] gather_network_resources = [ - 'interfaces', + 'lag_interfaces', ] def __init__(self, module): @@ -101,21 +106,16 @@ def set_state(self, want, have): state = self._module.params['state'] module = self._module if state == 'overridden': - kwargs = {'want': want, 'have': have, 'module': module} - commands = self._state_overridden(**kwargs) + commands = self._state_overridden(want, have, module) elif state == 'deleted': - kwargs = {'want': want, 'have': have, 'state': state} - commands = self._state_deleted(**kwargs) + commands = self._state_deleted(want, have) elif state == 'merged': - kwargs = {'want': want, 'have': have, 'module': module} - commands = self._state_merged(**kwargs) + commands = self._state_merged(want, have, module) elif state == 'replaced': - kwargs = {'want': want, 'have': have, 'module': module} - commands = self._state_replaced(**kwargs) + commands = self._state_replaced(want, have, module) return commands - @staticmethod - def _state_replaced(**kwargs): + def _state_replaced(self, want, have, module): """ The command generator when state is replaced :rtype: A list @@ -123,29 +123,35 @@ def _state_replaced(**kwargs): to the desired configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] - module = kwargs['module'] - + q(want, have) for interface in want: for each_interface in interface.get('members'): for each in have: - if each['members']: - if each['members'][0]['member'] == each_interface['member']: - break - else: - continue - kwargs = {'want': interface, 'have': each} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each, 'module': module} - commands.extend(Lag_interfaces.set_interface(**kwargs)) - # Remove the duplicate interface call - commands = Lag_interfaces._remove_duplicate_interface(commands) + if each.get('members'): + for every in each.get('members'): + match = False + if every['member'] == each_interface['member']: + match = True + break + else: + continue + if match: + 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)) + elif each.get('name') == each_interface['member']: + 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)) + break + # Remove the duplicate interface call + commands = remove_duplicate_interface(commands) + q(commands) + commands = [] return commands - @staticmethod - def _state_overridden(**kwargs): + def _state_overridden(self, want, have, module): """ The command generator when state is overridden :rtype: A list @@ -153,37 +159,34 @@ def _state_overridden(**kwargs): to the desired configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] - module = kwargs['module'] for interface in want: for each_interface in interface.get('members'): for each in have: - if each['members']: - if each['members'][0]['member'] == each_interface['member']: - break - else: - kwargs = {'want': interface, 'have': each} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) - continue - else: - # We didn't find a matching desired state, which means we can - # pretend we recieved an empty desired state. - kwargs = {'want': interface, 'have': each} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) - continue - kwargs = {'want': interface, 'have': each} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module} - commands.extend(Lag_interfaces.set_interface(**kwargs)) + if each.get('members'): + for every in each.get('members'): + match = False + if every['member'] == each_interface['member']: + match = True + break + else: + commands.extend(self._clear_config(interface, each)) + continue + if match: + 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)) + elif each.get('name') == each_interface['member']: + 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)) + break # Remove the duplicate interface call - commands = Lag_interfaces._remove_duplicate_interface(commands) + commands = remove_duplicate_interface(commands) return commands - @staticmethod - def _state_merged(**kwargs): + def _state_merged(self, want, have, module): """ The command generator when state is merged :rtype: A list @@ -191,28 +194,23 @@ def _state_merged(**kwargs): the current configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] - module = kwargs['module'] for interface in want: for each_interface in interface.get('members'): for each in have: - if each['members']: - if each['members'][0]['member'] == each_interface['member']: - break - else: - if each.get('name') == each_interface['member']: - break + if each.get('members'): + for every in each.get('members'): + if every['member'] == each_interface['member']: + break + elif each.get('name') == each_interface['member']: + break else: continue - kwargs = {'want': interface, 'have': each, 'module': module} - commands.extend(Lag_interfaces.set_interface(**kwargs)) + commands.extend(self._set_config(interface, each, module)) return commands - @staticmethod - def _state_deleted(**kwargs): + def _state_deleted(self, want, have): """ The command generator when state is deleted :rtype: A list @@ -220,9 +218,6 @@ def _state_deleted(**kwargs): of the provided objects """ commands = [] - want = kwargs['want'] - have = kwargs['have'] - state = kwargs['state'] if want: for interface in want: @@ -231,127 +226,75 @@ def _state_deleted(**kwargs): break else: continue - kwargs = {'want': interface, 'have': each, 'state': state} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) + commands.extend(self._clear_config(interface, each)) else: for each in have: - kwargs = {'want': {}, 'have': each, 'state': state} - commands.extend(Lag_interfaces.clear_interface(**kwargs)) + commands.extend(self._clear_config(dict(), each)) return commands - @staticmethod - def _remove_command_from_interface(interface, cmd, commands): + def remove_command_from_config_list(self, interface, cmd, commands): + # To delete the passed config if interface not in commands: - commands.insert(0, interface) + commands.append(interface) commands.append('no %s' % cmd) return commands - @staticmethod - def _add_command_to_interface(interface, cmd, commands): + def add_command_to_config_list(self, interface, cmd, commands): + # To set the passed config if interface not in commands: - commands.insert(0, interface) - if cmd not in commands: - commands.append(cmd) - - @staticmethod - def _remove_duplicate_interface(commands): - # Remove duplicate interface from commands - set_cmd = [] - for each in commands: - if 'interface' in each: - interface = each - if interface not in set_cmd: - set_cmd.append(each) - else: - set_cmd.append(each) - - return set_cmd - - - @staticmethod - def set_interface(**kwargs): + commands.append(interface) + commands.append(cmd) + return commands + + def _set_config(self, want, have, module): # Set the interface config based on the want and have config commands = [] - channel_name_diff = False - want = kwargs['want'] - have = kwargs['have'] - module = kwargs['module'] - if have['members']: - interface = 'interface ' + have['members'][0]['member'] - else: - interface = 'interface ' + have['name'] - member_diff = {} - want_each_member = {} - - # To check if channel-group differs in want n have - if have.get('name') != want.get('name'): - channel_name_diff = True - - # Create Set for want and have field for diff comparison - want_members = set(tuple({k: v for k, v in iteritems(member) if v is not None}.items()) - for member in want.get("members") or []) - if have['members']: - have_member = set(tuple({k:v for k, v in iteritems(have['members'][0]) if v is not None}.items())) - else: - have_member = set(tuple({k:v for k, v in iteritems(have) if v}.items())) - - # Get the diff between want and have members - for each_member in want_members: - if dict(each_member)['member'] == dict(have_member).get('member'): - want_each_member = each_member - if have_member: - member_diff = dict(set(each_member) - have_member) - break - else: - member_diff = dict(set(each_member)) - break - elif dict(each_member)['member'] == dict(have_member).get('name'): - want_each_member = each_member - # To get the diff channel mode n link config from computed diff - mode = dict(member_diff).get('mode') - link = dict(member_diff).get('link') - # To get the channel-group ID from the interface Port-Channel + + # To remove keys with None values from want dict + want = utils.remove_empties(want) + + # Get the diff b/w want and have + want_dict = dict_to_set(want) + have_dict = dict_to_set(have) + diff = want_dict - have_dict + q(want_dict, have_dict, diff) + # To get the channel-id from lag port-channel name + lag_config = dict(diff).get('members') channel_name = re.search('(\d+)', want.get('name')) if channel_name: channel_id = channel_name.group() else: module.fail_json(msg="Lag Interface Name is not correct!") - # Compare the value and set the commands - if mode: - cmd = 'channel-group {} mode {}'.format(channel_id, mode) - Lag_interfaces._add_command_to_interface(interface, cmd, commands) - elif link: - cmd = 'channel-group {} link {}'.format(channel_id, link) - Lag_interfaces._add_command_to_interface(interface, cmd, commands) - elif channel_name_diff and not member_diff: - # In Merge case if want n have channel ID differs and channel mode config is kept same - if want_each_member: - final_dict = dict(want_each_member) - else: - final_dict = dict(have_member) - mode = final_dict.get('mode') - link = final_dict.get('link') - if mode: - cmd = 'channel-group {} mode {}'.format(channel_id, mode) - Lag_interfaces._add_command_to_interface(interface, cmd, commands) - elif link: - cmd = 'channel-group {} link {}'.format(channel_id, link) - Lag_interfaces._add_command_to_interface(interface, cmd, commands) + if lag_config: + for each in lag_config: + each = dict(each) + each_interface = 'interface {0}'.format(each.get('member')) + if have.get('name') == want['members'][0]['member'] or have.get('name').lower().startswith('po'): + if each.get('mode'): + cmd = 'channel-group {0} mode {1}'.format(channel_id, each.get('mode')) + self.add_command_to_config_list(each_interface, cmd, commands) + elif each.get('link'): + cmd = 'channel-group {0} link {1}'.format(channel_id, each.get('link')) + self.add_command_to_config_list(each_interface, cmd, commands) return commands - @staticmethod - def clear_interface(**kwargs): + def _clear_config(self, want, have): # Delete the interface config based on the want and have config commands = [] - want = kwargs['want'] - have = kwargs['have'] - state = kwargs['state'] if kwargs.get('state') else '' - - if have['members']: - interface = 'interface ' + have['members'][0]['member'] - if have.get('name') and (have.get('name') != want.get('name') or state == 'deleted'): - Lag_interfaces._remove_command_from_interface(interface, 'channel-group', commands) + q(want, have) + + if have.get('members'): + for each in have['members']: + interface = 'interface ' + each['member'] + if want.get('members'): + for every in want.get('members'): + if each.get('member') and each.get('member') != every['member']: + q(interface) + self.remove_command_from_config_list(interface, 'channel-group', commands) + else: + if each.get('member') and want.get('members') is None: + self.remove_command_from_config_list(interface, 'channel-group', commands) return commands diff --git a/module_utils/network/ios/config/resource/__init__.py b/module_utils/network/ios/config/resource/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/network/ios/facts/facts.py b/module_utils/network/ios/facts/facts.py index d860a9f..d2525dd 100644 --- a/module_utils/network/ios/facts/facts.py +++ b/module_utils/network/ios/facts/facts.py @@ -16,7 +16,7 @@ FACT_LEGACY_SUBSETS = {} FACT_RESOURCE_SUBSETS = dict( - interfaces=Lag_interfacesFacts, + lag_interfaces=Lag_interfacesFacts, ) diff --git a/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py b/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py index 81aa89e..982e5f2 100644 --- a/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py +++ b/module_utils/network/ios/facts/lag_interfaces/lag_interfaces.py @@ -15,7 +15,7 @@ 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.lag_interfaces.lag_interfaces import Lag_interfacesArgs - +import q class Lag_interfacesFacts(object): """ The ios_lag_interfaces fact class @@ -59,6 +59,13 @@ def populate_facts(self, connection, ansible_facts, data=None): if not obj.get('members'): obj.update({'members': []}) objs.append(obj) + + # for appending members configured with same channel-group + for each in range(len(objs)): + if each < (len(objs) - 1): + if objs[each]['name'] == objs[each + 1]['name']: + objs[each]['members'].append(objs[each + 1]['members'][0]) + del objs[each + 1] facts = {} if objs: @@ -66,7 +73,7 @@ def populate_facts(self, connection, ansible_facts, data=None): params = utils.validate_config(self.argument_spec, {'config': objs}) for cfg in params['config']: - facts['lag_interfaces'].append(cfg) + facts['lag_interfaces'].append(utils.remove_empties(cfg)) ansible_facts['ansible_network_resources'].update(facts) return ansible_facts diff --git a/module_utils/network/ios/facts/resource/__init__.py b/module_utils/network/ios/facts/resource/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/network/ios/utils/utils.py b/module_utils/network/ios/utils/utils.py index 4ed570d..ce3fb3b 100644 --- a/module_utils/network/ios/utils/utils.py +++ b/module_utils/network/ios/utils/utils.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +# # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ @@ -6,11 +6,118 @@ # utils -def search_obj_in_list(name, lst): - for o in lst: - if o['name'] == name: - return o - return None +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.common.utils import is_masklen, to_netmask + + +def remove_command_from_config_list(interface, cmd, commands): + # To delete the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append('no %s' % cmd) + return commands + + +def add_command_to_config_list(interface, cmd, commands): + # To set the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append(cmd) + + +def dict_to_set(sample_dict): + # Generate a set with passed dictionary for comparison + test_dict = {} + for k, v in iteritems(sample_dict): + if v is not None: + if isinstance(v, list) and v: + if isinstance(v[0], dict): + li = [] + for each in v: + for key, value in iteritems(each): + if isinstance(value, list): + each[key] = tuple(value) + li.append(tuple(each.items())) + v = tuple(li) + else: + v = tuple(v) + elif isinstance(v, dict): + li = [] + for key, value in iteritems(v): + if isinstance(value, list): + v[key] = tuple(value) + li.extend(tuple(v.items())) + v = tuple(li) + elif isinstance(v, list): + v = tuple(v) + test_dict.update({k: v}) + return_set = set(tuple(test_dict.items())) + return return_set + + +def filter_dict_having_none_value(want, have): + # Generate dict with have dict value which is None in want dict + test_dict = dict() + test_key_dict = dict() + test_dict['name'] = want.get('name') + for k, v in iteritems(want): + if isinstance(v, dict): + for key, value in iteritems(v): + if value is None: + dict_val = have.get(k).get(key) + test_key_dict.update({key: dict_val}) + test_dict.update({k: test_key_dict}) + if v is None: + val = have.get(k) + test_dict.update({k: val}) + return test_dict + + +def remove_duplicate_interface(commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if 'interface' in each: + if each not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + + +def validate_ipv4(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format {0}'.format(value)) + + if not is_masklen(address[1]): + module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-32'.format(address[1])) + + +def validate_ipv6(value, module): + if value: + address = value.split('/') + if len(address) != 2: + module.fail_json(msg='address format is /, got invalid format {0}'.format(value)) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-128'.format(address[1])) + + +def validate_n_expand_ipv4(module, want): + # Check if input IPV4 is valid IP and expand IPV4 with its subnet mask + ip_addr_want = want.get('address') + validate_ipv4(ip_addr_want, module) + ip = ip_addr_want.split('/') + if len(ip) == 2: + ip_addr_want = '{0} {1}'.format(ip[0], to_netmask(ip[1])) + + return ip_addr_want def normalize_interface(name): @@ -97,4 +204,3 @@ def get_interface_type(interface): return 'HundredGigE' else: return 'unknown' -