From c436f32eed54a9b62a67205a905fa066f650a954 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Sat, 6 Jul 2019 14:28:45 +0530 Subject: [PATCH 1/5] ios lacp interface Signed-off-by: Sumit Jaiswal --- library/__init__.py | 0 library/ios_facts.py | 108 +++++++ library/ios_lacp_interfaces.py | 296 ++++++++++++++++++ module_utils/__init__.py | 0 module_utils/network/__init__.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 | 28 ++ .../ios/argspec/lacp_interfaces/__init__.py | 0 .../lacp_interfaces/lacp_interfaces.py | 43 +++ module_utils/network/ios/config/__init__.py | 0 .../ios/config/lacp_interfaces/__init__.py | 0 .../config/lacp_interfaces/lacp_interfaces.py | 279 +++++++++++++++++ module_utils/network/ios/facts/__init__.py | 0 module_utils/network/ios/facts/facts.py | 49 +++ .../ios/facts/lacp_interfaces/__init__.py | 0 .../facts/lacp_interfaces/lacp_interfaces.py | 95 ++++++ module_utils/network/ios/utils/__init__.py | 0 module_utils/network/ios/utils/utils.py | 100 ++++++ 20 files changed, 998 insertions(+) create mode 100644 library/__init__.py create mode 100644 library/ios_facts.py create mode 100644 library/ios_lacp_interfaces.py create mode 100644 module_utils/__init__.py create mode 100644 module_utils/network/__init__.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/lacp_interfaces/__init__.py create mode 100644 module_utils/network/ios/argspec/lacp_interfaces/lacp_interfaces.py create mode 100644 module_utils/network/ios/config/__init__.py create mode 100644 module_utils/network/ios/config/lacp_interfaces/__init__.py create mode 100644 module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.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/lacp_interfaces/__init__.py create mode 100644 module_utils/network/ios/facts/lacp_interfaces/lacp_interfaces.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..5e63930 --- /dev/null +++ b/library/ios_facts.py @@ -0,0 +1,108 @@ +#!/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 module file for myos_facts +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': [u'preview'], + 'supported_by': ''} + + +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 Cisco IOSv Version 15.2 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 lacp_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 lacp_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.network.ios.argspec.facts.facts import FactsArgs +from ansible.module_utils.network.ios.facts.facts import Facts + + +def main(): + """ + Main entry point for module execution + + :returns: ansible_facts + """ + module = AnsibleModule(argument_spec=FactsArgs.argument_spec, + supports_check_mode=True) + warnings = ['default value for `gather_subset` ' + 'will be changed to `min` from `!config` v2.11 onwards'] + + result = Facts(module).get_facts() + + ansible_facts, additional_warnings = result + warnings.extend(additional_warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/library/ios_lacp_interfaces.py b/library/ios_lacp_interfaces.py new file mode 100644 index 0000000..2e2b8a1 --- /dev/null +++ b/library/ios_lacp_interfaces.py @@ -0,0 +1,296 @@ +#!/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 module file for ios_lacp_interfaces +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} + +NETWORK_OS = "ios" +RESOURCE = "lacp_interfaces" +COPYRIGHT = "Copyright 2019 Red Hat" + +DOCUMENTATION = """ +--- +module: ios_lacp_interfaces +version_added: 2.9 +short_description: Manage Link Aggregation Control Protocol (LACP) on Cisco IOS devices interface. +description: This module provides declarative management of LACP on Cisco IOS network devices lacp_interfaces. +author: Sumit Jaiswal (@justjais) +notes: + - Tested against Cisco IOSv Version 15.2 on VIRL +options: + config: + description: A dictionary of LACP lacp_interfaces option + type: list + elements: dict + suboptions: + name: + description: + - Name of the Interface for configuring LACP. + type: str + required: True + port_priority: + description: + - LACP priority on this interface. Range 0-65535. + type: int + required: True + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +""" +EXAMPLES = """ +# 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 +# lacp port-priority 10 +# interface GigabitEthernet0/2 +# shutdown +# lacp port-priority 20 +# interface GigabitEthernet0/3 +# shutdown +# lacp port-priority 30 + +- name: Delete LAG attributes of given lacp_interfaces (Note: This won't delete the interface itself) + ios_lacp_interfaces: + config: + - name: GigabitEthernet0/1 + - name: GigabitEthernet0/2 + - name: GigabitEthernet0/3 + 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 + +# Using merged +# +# Before 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 + +- name: Merge provided configuration with device configuration + ios_lacp_interfaces: + config: + - name: GigabitEthernet0/1 + port_priority: 10 + - name: GigabitEthernet0/2 + port_priority: 20 + - name: GigabitEthernet0/3 + port_priority: 30 + 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 +# lacp port-priority 10 +# interface GigabitEthernet0/2 +# shutdown +# lacp port-priority 20 +# interface GigabitEthernet0/3 +# shutdown +# lacp port-priority 30 + +# 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 +# lacp port-priority 10 +# interface GigabitEthernet0/2 +# shutdown +# lacp port-priority 20 +# interface GigabitEthernet0/3 +# shutdown +# lacp port-priority 30 + +- name: Override device configuration of all lacp_interfaces with provided configuration + ios_lacp_interfaces: + config: + - name: GigabitEthernet0/1 + port_priority: 20 + operation: overridden + +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# flowcontrol receive on +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# lacp port-priority 20 +# interface GigabitEthernet0/2 +# shutdown +# interface GigabitEthernet0/3 +# 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 +# lacp port-priority 10 +# interface GigabitEthernet0/2 +# shutdown +# lacp port-priority 20 +# interface GigabitEthernet0/3 +# shutdown +# lacp port-priority 30 + +- name: Replaces device configuration of listed lacp_interfaces with provided configuration + ios_lacp_interfaces: + config: + - name: GigabitEthernet0/3 + port_priority: 40 + operation: replaced + +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface Port-channel10 +# flowcontrol receive on +# interface Port-channel20 +# interface Port-channel30 +# interface GigabitEthernet0/1 +# shutdown +# lacp port-priority 10 +# interface GigabitEthernet0/2 +# shutdown +# lacp port-priority 20 +# interface GigabitEthernet0/3 +# shutdown +# lacp port-priority 40 + +""" + +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters 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.lacp_interfaces.lacp_interfaces import Lacp_InterfacesArgs +from ansible.module_utils.network.ios.config.lacp_interfaces.lacp_interfaces import Lacp_Interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Lacp_InterfacesArgs.argument_spec, + supports_check_mode=True) + + result = Lacp_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/__init__.py b/module_utils/network/__init__.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..d62ec1f --- /dev/null +++ b/module_utils/network/ios/argspec/facts/facts.py @@ -0,0 +1,28 @@ +#!/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/lacp_interfaces/__init__.py b/module_utils/network/ios/argspec/lacp_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/argspec/lacp_interfaces/lacp_interfaces.py b/module_utils/network/ios/argspec/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 0000000..3abf137 --- /dev/null +++ b/module_utils/network/ios/argspec/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,43 @@ +#!/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_lacp_interfaces module +""" + + +class Lacp_InterfacesArgs(object): + """The arg spec for the ios_lacp_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = {'config': {'elements': 'dict', + 'options': {'name': {'required': True, 'type': 'str'}, + 'port_priority': {'type': 'str'}}, + 'type': 'list'}, + 'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} 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/lacp_interfaces/__init__.py b/module_utils/network/ios/config/lacp_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py b/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 0000000..93b279d --- /dev/null +++ b/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,279 @@ +# +# -*- 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_lacp_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 Lacp_Interfaces(ConfigBase): + """ + The ios_lacp_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'interfaces', + ] + + def __init__(self, module): + super(Lacp_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('lacp_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 in have: + if each['name'] == interface['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each, } + commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each} + commands.extend(Lacp_Interfaces.set_interface(**kwargs)) + # Remove the duplicate interface call + commands = Lacp_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 each in have: + for interface in want: + if each['name'] == interface['name']: + break + else: + # We didn't find a matching desired state, which means we can + # pretend we recieved an empty desired state. + interface = dict(name=each['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each} + commands.extend(Lacp_Interfaces.set_interface(**kwargs)) + # Remove the duplicate interface call + commands = Lacp_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 in have: + if interface['name'] == each['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Lacp_Interfaces.set_interface(**kwargs)) + + return commands + + @staticmethod + def _state_deleted(**kwargs): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + want = kwargs['want'] + have = kwargs['have'] + + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + else: + continue + interface = dict(name=interface['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) + + return commands + + @staticmethod + def _remove_command_from_interface(interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + commands.append('no %s' % cmd) + return commands + + @staticmethod + def _add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + 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['name'] + + want_dict = set(tuple({k: v for k, v in iteritems(want) if v is not None}.items())) + have_dict = set(tuple({k: v for k, v in iteritems(have) if v is not None}.items())) + diff = want_dict - have_dict + + if diff: + cmd = 'lacp port-priority {}'.format(dict(diff).get('port_priority')) + Lacp_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['name'] + + if have.get('port_priority'): + cmd = 'lacp port-priority' + Lacp_Interfaces._remove_command_from_interface(interface, cmd, commands) + + return commands 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..266b321 --- /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 +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.lacp_interfaces.lacp_interfaces import Lacp_InterfacesFacts + + +FACT_LEGACY_SUBSETS = {} +FACT_RESOURCE_SUBSETS = dict( + interfaces=Lacp_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 myos + + :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/lacp_interfaces/__init__.py b/module_utils/network/ios/facts/lacp_interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/network/ios/facts/lacp_interfaces/lacp_interfaces.py b/module_utils/network/ios/facts/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 0000000..57caf60 --- /dev/null +++ b/module_utils/network/ios/facts/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,95 @@ +# +# -*- 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_lacp_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.lacp_interfaces.lacp_interfaces import Lacp_InterfacesArgs + + +class Lacp_InterfacesFacts(object): + """ The ios_lacp_interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + + self._module = module + self.argument_spec = Lacp_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 lacp_interfaces + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if connection: + 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['lacp_interfaces'] = [] + # params = utils.validate_config(self.argument_spec, {'config': objs}) + + params = {'config': objs} + + for cfg in params['config']: + facts['lacp_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 {} + + config['name'] = normalize_interface(intf) + port_priority = utils.parse_conf_arg(conf, 'lacp port-priority') + config['port_priority'] = port_priority + + return utils.remove_empties(config) 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..d0fcd06 --- /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' \ No newline at end of file From 617fed915a11c5b15b0ae7931dcc92caf51f5d71 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 8 Jul 2019 14:43:03 +0530 Subject: [PATCH 2/5] fix bug Signed-off-by: Sumit Jaiswal --- .../network/ios/config/lacp_interfaces/lacp_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py b/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py index 93b279d..35b9a28 100644 --- a/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py +++ b/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py @@ -272,7 +272,7 @@ def clear_interface(**kwargs): have = kwargs['have'] interface = 'interface ' + have['name'] - if have.get('port_priority'): + if have.get('port_priority') and have.get('port_priority') != want.get('port_priority'): cmd = 'lacp port-priority' Lacp_Interfaces._remove_command_from_interface(interface, cmd, commands) From 1cc6e2d8f5ccf98e79b0b79b85dfc2fc4646bd0e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Jul 2019 17:37:35 +0530 Subject: [PATCH 3/5] delete w/o key method Signed-off-by: Sumit Jaiswal --- library/ios_lacp_interfaces.py | 52 +++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/library/ios_lacp_interfaces.py b/library/ios_lacp_interfaces.py index 2e2b8a1..0113721 100644 --- a/library/ios_lacp_interfaces.py +++ b/library/ios_lacp_interfaces.py @@ -96,13 +96,51 @@ # shutdown # lacp port-priority 30 -- name: Delete LAG attributes of given lacp_interfaces (Note: This won't delete the interface itself) +- name: Delete LACP attributes of given interfaces (Note: This won't delete the interface itself) ios_lacp_interfaces: config: - name: GigabitEthernet0/1 - - name: GigabitEthernet0/2 - - name: GigabitEthernet0/3 - 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 +# lacp port-priority 20 +# interface GigabitEthernet0/3 +# shutdown +# lacp port-priority 30 + +# 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 +# lacp port-priority 10 +# interface GigabitEthernet0/2 +# shutdown +# lacp port-priority 20 +# interface GigabitEthernet0/3 +# shutdown +# lacp port-priority 30 + +- name: Delete LACP attributes for all configured interfaces (Note: This won't delete the interface itself) + ios_lacp_interfaces: + state: deleted # After state: # ------------- @@ -143,7 +181,7 @@ port_priority: 20 - name: GigabitEthernet0/3 port_priority: 30 - operation: merged + state: merged # After state: # ------------ @@ -188,7 +226,7 @@ config: - name: GigabitEthernet0/1 port_priority: 20 - operation: overridden + state: overridden # After state: # ------------ @@ -231,7 +269,7 @@ config: - name: GigabitEthernet0/3 port_priority: 40 - operation: replaced + state: replaced # After state: # ------------ From 422eba5e03f35faefd5168002e84aca7e3144a9d Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Jul 2019 17:37:54 +0530 Subject: [PATCH 4/5] delete w/o key method Signed-off-by: Sumit Jaiswal --- .../config/lacp_interfaces/lacp_interfaces.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py b/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py index 35b9a28..85a0a81 100644 --- a/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py +++ b/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py @@ -206,16 +206,22 @@ def _state_deleted(**kwargs): want = kwargs['want'] have = kwargs['have'] - for interface in want: + if want: + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + else: + continue + interface = dict(name=interface['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) + else: for each in have: - if each['name'] == interface['name']: - break - else: - continue - interface = dict(name=interface['name']) - kwargs = {'want': interface, 'have': each} - commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) - + kwargs = {'want': {}, 'have': each} + commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) + q(commands) + commands=[] return commands @staticmethod From e1eb2b0aba8424b6fa194e5da6e0f6bcf11f8c1d Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 19 Aug 2019 22:36:07 +0530 Subject: [PATCH 5/5] fix lacp interfaces --- library/ios_lacp_interfaces.py | 116 +++++++----- .../network/ios/argspec/facts/facts.py | 2 + .../lacp_interfaces/lacp_interfaces.py | 4 +- .../config/lacp_interfaces/lacp_interfaces.py | 166 +++++++----------- module_utils/network/ios/facts/facts.py | 2 +- .../facts/lacp_interfaces/lacp_interfaces.py | 12 +- module_utils/network/ios/utils/utils.py | 140 ++++++++++++++- 7 files changed, 282 insertions(+), 160 deletions(-) diff --git a/library/ios_lacp_interfaces.py b/library/ios_lacp_interfaces.py index 0113721..a8422ba 100644 --- a/library/ios_lacp_interfaces.py +++ b/library/ios_lacp_interfaces.py @@ -35,9 +35,6 @@ 'supported_by': 'network' } -NETWORK_OS = "ios" -RESOURCE = "lacp_interfaces" -COPYRIGHT = "Copyright 2019 Red Hat" DOCUMENTATION = """ --- @@ -47,7 +44,9 @@ description: This module provides declarative management of LACP on Cisco IOS network devices lacp_interfaces. author: Sumit Jaiswal (@justjais) notes: - - Tested against Cisco IOSv Version 15.2 on VIRL + - Tested against Cisco IOSv Version 15.2 on VIRL. + - This module works with connection C(network_cli), + See L(IOS Platform Options,../network/user_guide/platform_ios.html). options: config: description: A dictionary of LACP lacp_interfaces option @@ -61,9 +60,18 @@ required: True port_priority: description: - - LACP priority on this interface. Range 0-65535. + - LACP priority on this interface. + - Refer to vendor documentation for valid port values. + type: int + fast_switchover: + description: + - LACP fast switchover supported on this port channel. + type: bool + max_bundle: + description: + - LACP maximum number of ports to bundle in this port channel. + - Refer to vendor documentation for valid port values. type: int - required: True state: description: - The state the configuration should be left in @@ -76,41 +84,48 @@ default: merged """ EXAMPLES = """ -# Using Deleted +# Using merged # # Before state: # ------------- # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown -# lacp port-priority 10 # interface GigabitEthernet0/2 # shutdown -# lacp port-priority 20 # interface GigabitEthernet0/3 # shutdown -# lacp port-priority 30 -- name: Delete LACP attributes of given interfaces (Note: This won't delete the interface itself) +- name: Merge provided configuration with device configuration ios_lacp_interfaces: config: - name: GigabitEthernet0/1 - state: deleted + port_priority: 10 + - name: GigabitEthernet0/2 + port_priority: 20 + - name: GigabitEthernet0/3 + port_priority: 30 + - name: Port-channel10 + fast_switchover: True + max_bundle: 5 + state: merged # After state: -# ------------- +# ------------ # # vios#show running-config | section ^interface # interface Port-channel10 +# lacp fast-switchover +# lacp max-bundle 5 # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown +# lacp port-priority 10 # interface GigabitEthernet0/2 # shutdown # lacp port-priority 20 @@ -118,14 +133,14 @@ # shutdown # lacp port-priority 30 -# Using Deleted +# Using overridden # # Before state: # ------------- # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on +# lacp fast-switchover # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 @@ -138,57 +153,68 @@ # shutdown # lacp port-priority 30 -- name: Delete LACP attributes for all configured interfaces (Note: This won't delete the interface itself) +- name: Override device configuration of all lacp_interfaces with provided configuration ios_lacp_interfaces: - state: deleted + config: + - name: GigabitEthernet0/1 + port_priority: 20 + - name: Port-channel10 + max_bundle: 2 + state: overridden # After state: -# ------------- +# ------------ # # vios#show running-config | section ^interface # interface Port-channel10 +# lacp max-bundle 2 # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown +# lacp port-priority 20 # interface GigabitEthernet0/2 # shutdown # interface GigabitEthernet0/3 # shutdown -# Using merged +# Using replaced # # Before state: # ------------- # # vios#show running-config | section ^interface # interface Port-channel10 +# lacp max-bundle 5 # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown +# lacp port-priority 10 # interface GigabitEthernet0/2 # shutdown +# lacp port-priority 20 # interface GigabitEthernet0/3 # shutdown +# lacp port-priority 30 -- name: Merge provided configuration with device configuration +- name: Replaces device configuration of listed lacp_interfaces with provided configuration ios_lacp_interfaces: config: - - name: GigabitEthernet0/1 - port_priority: 10 - - name: GigabitEthernet0/2 - port_priority: 20 - name: GigabitEthernet0/3 - port_priority: 30 - state: merged + port_priority: 40 + - name: Port-channel10 + fast_switchover: True + max_bundle: 2 + state: replaced # After state: # ------------ # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on +# lacp fast-switchover +# lacp max-bundle 2 # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 @@ -199,9 +225,9 @@ # lacp port-priority 20 # interface GigabitEthernet0/3 # shutdown -# lacp port-priority 30 +# lacp port-priority 40 -# Using overridden +# Using Deleted # # Before state: # ------------- @@ -221,38 +247,39 @@ # shutdown # lacp port-priority 30 -- name: Override device configuration of all lacp_interfaces with provided configuration +- name: "Delete LACP attributes of given interfaces (Note: This won't delete the interface itself)" ios_lacp_interfaces: config: - name: GigabitEthernet0/1 - port_priority: 20 - state: overridden + state: deleted # After state: -# ------------ +# ------------- # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown -# lacp port-priority 20 # interface GigabitEthernet0/2 # shutdown +# lacp port-priority 20 # interface GigabitEthernet0/3 # shutdown +# lacp port-priority 30 -# Using replaced +# Using Deleted without any config passed +# "(NOTE: This will delete all of configured LLDP module attributes)" # # Before state: # ------------- # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on +# lacp fast-switchover # interface Port-channel20 +# lacp max-bundle 2 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown @@ -264,30 +291,23 @@ # shutdown # lacp port-priority 30 -- name: Replaces device configuration of listed lacp_interfaces with provided configuration +- name: "Delete LACP attributes for all configured interfaces (Note: This won't delete the interface itself)" ios_lacp_interfaces: - config: - - name: GigabitEthernet0/3 - port_priority: 40 - state: replaced + state: deleted # After state: -# ------------ +# ------------- # # vios#show running-config | section ^interface # interface Port-channel10 -# flowcontrol receive on # interface Port-channel20 # interface Port-channel30 # interface GigabitEthernet0/1 # shutdown -# lacp port-priority 10 # interface GigabitEthernet0/2 # shutdown -# lacp port-priority 20 # interface GigabitEthernet0/3 # shutdown -# lacp port-priority 40 """ diff --git a/module_utils/network/ios/argspec/facts/facts.py b/module_utils/network/ios/argspec/facts/facts.py index d62ec1f..dffb705 100644 --- a/module_utils/network/ios/argspec/facts/facts.py +++ b/module_utils/network/ios/argspec/facts/facts.py @@ -18,6 +18,8 @@ def __init__(self, **kwargs): choices = [ 'all', 'interfaces', + 'lacp_interfaces', + '!lacp_interfaces', ] argument_spec = { diff --git a/module_utils/network/ios/argspec/lacp_interfaces/lacp_interfaces.py b/module_utils/network/ios/argspec/lacp_interfaces/lacp_interfaces.py index 3abf137..51b2109 100644 --- a/module_utils/network/ios/argspec/lacp_interfaces/lacp_interfaces.py +++ b/module_utils/network/ios/argspec/lacp_interfaces/lacp_interfaces.py @@ -36,7 +36,9 @@ def __init__(self, **kwargs): argument_spec = {'config': {'elements': 'dict', 'options': {'name': {'required': True, 'type': 'str'}, - 'port_priority': {'type': 'str'}}, + 'port_priority': {'type': 'str'}, + 'fast_switchover': {'type': 'bool'}, + 'max_bundle': {'type': 'int'}}, 'type': 'list'}, 'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], 'default': 'merged', diff --git a/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py b/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py index 85a0a81..d0f1fee 100644 --- a/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py +++ b/module_utils/network/ios/config/lacp_interfaces/lacp_interfaces.py @@ -11,11 +11,16 @@ created """ -from ansible.module_utils.six import iteritems +from __future__ import absolute_import, division, print_function +__metaclass__ = type + from ansible.module_utils.network.common.cfg.base import ConfigBase from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.network.ios.facts.facts import Facts +from ansible.module_utils.network.ios.utils.utils import dict_to_set +from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list +from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface class Lacp_Interfaces(ConfigBase): @@ -29,24 +34,24 @@ class Lacp_Interfaces(ConfigBase): ] gather_network_resources = [ - 'interfaces', + 'lacp_interfaces', ] def __init__(self, module): super(Lacp_Interfaces, self).__init__(module) - def get_interfaces_facts(self): + def get_lacp_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('lacp_interfaces') + lacp_interfaces_facts = facts['ansible_network_resources'].get('lacp_interfaces') - if not interfaces_facts: + if not lacp_interfaces_facts: return [] - return interfaces_facts + return lacp_interfaces_facts def execute_module(self): """ Execute the module @@ -58,24 +63,25 @@ def execute_module(self): commands = list() warnings = list() - existing_interfaces_facts = self.get_interfaces_facts() - commands.extend(self.set_config(existing_interfaces_facts)) + existing_lacp_interfaces_facts = self.get_lacp_interfaces_facts() + commands.extend(self.set_config(existing_lacp_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() + changed_lacp_interfaces_facts = self.get_lacp_interfaces_facts() - result['before'] = existing_interfaces_facts + result['before'] = existing_lacp_interfaces_facts if result['changed']: - result['after'] = changed_interfaces_facts + result['after'] = changed_lacp_interfaces_facts result['warnings'] = warnings + return result - def set_config(self, existing_interfaces_facts): + def set_config(self, existing_lacp_interfaces_facts): """ Collect the configuration from the args passed to the module, collect the current configuration (as a dict from facts) @@ -84,8 +90,9 @@ def set_config(self, existing_interfaces_facts): to the desired configuration """ want = self._module.params['config'] - have = existing_interfaces_facts + have = existing_lacp_interfaces_facts resp = self.set_state(want, have) + return to_list(resp) def set_state(self, want, have): @@ -99,21 +106,17 @@ def set_state(self, want, have): """ state = self._module.params['state'] if state == 'overridden': - kwargs = {'want': want, 'have': have} - commands = self._state_overridden(**kwargs) + commands = self._state_overridden(want, have) elif state == 'deleted': - kwargs = {'want': want, 'have': have} - commands = self._state_deleted(**kwargs) + commands = self._state_deleted(want, have) elif state == 'merged': - kwargs = {'want': want, 'have': have} - commands = self._state_merged(**kwargs) + commands = self._state_merged(want, have) elif state == 'replaced': - kwargs = {'want': want, 'have': have} - commands = self._state_replaced(**kwargs) + commands = self._state_replaced(want, have) + return commands - @staticmethod - def _state_replaced(**kwargs): + def _state_replaced(self, want, have): """ The command generator when state is replaced :rtype: A list @@ -121,8 +124,6 @@ def _state_replaced(**kwargs): to the desired configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for interface in want: for each in have: @@ -130,17 +131,15 @@ def _state_replaced(**kwargs): break else: continue - kwargs = {'want': interface, 'have': each, } - commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each} - commands.extend(Lacp_Interfaces.set_interface(**kwargs)) + have_dict = filter_dict_having_none_value(interface, each) + commands.extend(self._clear_config(dict(), have_dict)) + commands.extend(self._set_config(interface, each)) # Remove the duplicate interface call - commands = Lacp_Interfaces._remove_duplicate_interface(commands) + commands = remove_duplicate_interface(commands) return commands - @staticmethod - def _state_overridden(**kwargs): + def _state_overridden(self, want, have): """ The command generator when state is overridden :rtype: A list @@ -148,8 +147,6 @@ def _state_overridden(**kwargs): to the desired configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for each in have: for interface in want: @@ -159,20 +156,17 @@ def _state_overridden(**kwargs): # We didn't find a matching desired state, which means we can # pretend we recieved an empty desired state. interface = dict(name=each['name']) - kwargs = {'want': interface, 'have': each} - commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) + commands.extend(self._clear_config(interface, each)) continue - kwargs = {'want': interface, 'have': each} - commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) - kwargs = {'want': interface, 'have': each} - commands.extend(Lacp_Interfaces.set_interface(**kwargs)) + have_dict = filter_dict_having_none_value(interface, each) + commands.extend(self._clear_config(dict(), have_dict)) + commands.extend(self._set_config(interface, each)) # Remove the duplicate interface call - commands = Lacp_Interfaces._remove_duplicate_interface(commands) + commands = remove_duplicate_interface(commands) return commands - @staticmethod - def _state_merged(**kwargs): + def _state_merged(self, want, have): """ The command generator when state is merged :rtype: A list @@ -180,8 +174,6 @@ def _state_merged(**kwargs): the current configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for interface in want: for each in have: @@ -189,13 +181,11 @@ def _state_merged(**kwargs): break else: continue - kwargs = {'want': interface, 'have': each} - commands.extend(Lacp_Interfaces.set_interface(**kwargs)) + commands.extend(self._set_config(interface, each)) return commands - @staticmethod - def _state_deleted(**kwargs): + def _state_deleted(self, want, have): """ The command generator when state is deleted :rtype: A list @@ -203,8 +193,6 @@ def _state_deleted(**kwargs): of the provided objects """ commands = [] - want = kwargs['want'] - have = kwargs['have'] if want: for interface in want: @@ -214,72 +202,54 @@ def _state_deleted(**kwargs): else: continue interface = dict(name=interface['name']) - kwargs = {'want': interface, 'have': each} - commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) + commands.extend(self._clear_config(interface, each)) else: for each in have: - kwargs = {'want': {}, 'have': each} - commands.extend(Lacp_Interfaces.clear_interface(**kwargs)) - q(commands) - commands=[] - return commands + commands.extend(self._clear_config(dict(), each)) - @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): + def _set_config(self, want, have): # Set the interface config based on the want and have config commands = [] - want = kwargs['want'] - have = kwargs['have'] interface = 'interface ' + have['name'] - want_dict = set(tuple({k: v for k, v in iteritems(want) if v is not None}.items())) - have_dict = set(tuple({k: v for k, v in iteritems(have) if v is not None}.items())) + want_dict = dict_to_set(want) + have_dict = dict_to_set(have) diff = want_dict - have_dict if diff: - cmd = 'lacp port-priority {}'.format(dict(diff).get('port_priority')) - Lacp_Interfaces._add_command_to_interface(interface, cmd, commands) + port_priotity = dict(diff).get('port_priority') + max_bundle = dict(diff).get('max_bundle') + fast_switchover = dict(diff).get('fast_switchover') + if port_priotity: + cmd = 'lacp port-priority {0}'.format(port_priotity) + add_command_to_config_list(interface, cmd, commands) + if max_bundle: + cmd = 'lacp max-bundle {0}'.format(max_bundle) + add_command_to_config_list(interface, cmd, commands) + if fast_switchover: + cmd = 'lacp fast-switchover' + add_command_to_config_list(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'] - interface = 'interface ' + have['name'] + if want.get('name'): + interface = 'interface ' + want['name'] + else: + interface = 'interface ' + have['name'] if have.get('port_priority') and have.get('port_priority') != want.get('port_priority'): cmd = 'lacp port-priority' - Lacp_Interfaces._remove_command_from_interface(interface, cmd, commands) + remove_command_from_config_list(interface, cmd, commands) + if have.get('max_bundle') and have.get('max_bundle') != want.get('max_bundle'): + cmd = 'lacp max-bundle' + remove_command_from_config_list(interface, cmd, commands) + if have.get('fast_switchover'): + cmd = 'lacp fast-switchover' + remove_command_from_config_list(interface, cmd, commands) return commands diff --git a/module_utils/network/ios/facts/facts.py b/module_utils/network/ios/facts/facts.py index 266b321..0300174 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=Lacp_InterfacesFacts, + lacp_interfaces=Lacp_InterfacesFacts, ) diff --git a/module_utils/network/ios/facts/lacp_interfaces/lacp_interfaces.py b/module_utils/network/ios/facts/lacp_interfaces/lacp_interfaces.py index 57caf60..39bba41 100644 --- a/module_utils/network/ios/facts/lacp_interfaces/lacp_interfaces.py +++ b/module_utils/network/ios/facts/lacp_interfaces/lacp_interfaces.py @@ -62,12 +62,9 @@ def populate_facts(self, connection, ansible_facts, data=None): if objs: facts['lacp_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['lacp_interfaces'].append(cfg) + facts['lacp_interfaces'].append(utils.remove_empties(cfg)) ansible_facts['ansible_network_resources'].update(facts) return ansible_facts @@ -90,6 +87,11 @@ def render_config(self, spec, conf): config['name'] = normalize_interface(intf) port_priority = utils.parse_conf_arg(conf, 'lacp port-priority') + max_bundle = utils.parse_conf_arg(conf, 'lacp max-bundle') config['port_priority'] = port_priority + if 'lacp fast-switchover' in conf: + config['fast_switchover'] = True + if max_bundle: + config['max_bundle'] = int(max_bundle) return utils.remove_empties(config) diff --git a/module_utils/network/ios/utils/utils.py b/module_utils/network/ios/utils/utils.py index d0fcd06..aacb852 100644 --- a/module_utils/network/ios/utils/utils.py +++ b/module_utils/network/ios/utils/utils.py @@ -7,11 +7,137 @@ # 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): + if isinstance(v[0], dict): + li = [] + for each in v: + for key, value in iteritems(each): + if isinstance(value, list): + each[key] = tuple(value) + li.append(tuple(each.items())) + v = tuple(li) + else: + v = tuple(v) + elif isinstance(v, dict): + li = [] + for key, value in iteritems(v): + if isinstance(value, list): + v[key] = tuple(value) + li.extend(tuple(v.items())) + v = tuple(li) + test_dict.update({k: v}) + return_set = set(tuple(test_dict.items())) + return return_set + + +def filter_dict_having_none_value(want, have): + # Generate dict with have dict value which is None in want dict + test_dict = dict() + test_key_dict = dict() + test_dict['name'] = want.get('name') + diff_ip = False + want_ip = '' + for k, v in want.items(): + if isinstance(v, dict): + for key, value in v.items(): + if value is None: + dict_val = have.get(k).get(key) + test_key_dict.update({key: dict_val}) + test_dict.update({k: test_key_dict}) + if isinstance(v, list): + for key, value in v[0].items(): + if value is None: + dict_val = have.get(k).get(key) + test_key_dict.update({key: dict_val}) + # below conditions checks are added to check if + # secondary IP is configured, if yes then delete + # the already configured IP if want and have IP + # is different else if it's same no need to delete + if isinstance(value, str): + want_ip = value.split('/') + have_ip = have.get('ipv4') + if len(want_ip) > 1 and have_ip: + have_ip = have_ip[0]['address'].split(' ')[0] + if have_ip != want_ip[0]: + diff_ip = True + if key == 'secondary' and value is True and diff_ip is True: + test_key_dict.update({key: value}) + test_dict.update({k: test_key_dict}) + if v is None: + val = have.get(k) + test_dict.update({k: val}) + 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): @@ -44,7 +170,7 @@ def _get_number(name): elif name.lower().startswith('lo'): if_type = 'loopback' elif name.lower().startswith('po'): - if_type = 'port-channel' + if_type = 'Port-channel' elif name.lower().startswith('nv'): if_type = 'nve' elif name.lower().startswith('twe'): @@ -89,7 +215,7 @@ def get_interface_type(interface): elif interface.upper().startswith('LO'): return 'loopback' elif interface.upper().startswith('PO'): - return 'port-channel' + return 'Port-channel' elif interface.upper().startswith('NV'): return 'nve' elif interface.upper().startswith('TWE'):