diff --git a/lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py b/lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py index 600360db357..93959291def 100644 --- a/lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py +++ b/lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py @@ -20,7 +20,10 @@ class FactsArgs(object): # pylint: disable=R0903 choices = [ 'all', - 'lacp' + 'lacp', + '!lacp', + 'lacp_interfaces', + '!lacp_interfaces' ] argument_spec = { diff --git a/lib/ansible/module_utils/network/iosxr/argspec/lacp_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/argspec/lacp_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/argspec/lacp_interfaces/lacp_interfaces.py b/lib/ansible/module_utils/network/iosxr/argspec/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000000..f8a0070a5dc --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/argspec/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,79 @@ +# +# -*- 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 iosxr_lacp_interfaces module +""" + + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Lacp_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the iosxr_lacp_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'churn_logging': { + 'choices': ['actor', 'partner', 'both'], + 'type': 'str' + }, + 'collector_max_delay': { + 'type': 'int' + }, + 'name': { + 'type': 'str' + }, + 'period': { + 'type': 'int' + }, + 'switchover_suppress_flaps': { + 'type': 'int' + }, + 'system': { + 'options': { + 'mac': { + 'type': 'str' + }, + 'priority': { + 'type': 'int' + } + }, + 'type': 'dict' + } + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/lib/ansible/module_utils/network/iosxr/config/lacp_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/config/lacp_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/config/lacp_interfaces/lacp_interfaces.py b/lib/ansible/module_utils/network/iosxr/config/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000000..8967d62f582 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/config/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,261 @@ +# +# -*- 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 iosxr_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 __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.network.iosxr.facts.facts import Facts +from ansible.module_utils.network.common.utils import dict_diff, remove_empties +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.iosxr. \ + utils.utils import search_obj_in_list, dict_delete, pad_commands, flatten_dict + + +class Lacp_interfaces(ConfigBase): + """ + The iosxr_lacp_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'lacp_interfaces', + ] + + def __init__(self, module): + super(Lacp_interfaces, self).__init__(module) + + 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) + lacp_interfaces_facts = facts['ansible_network_resources'].get('lacp_interfaces') + if not lacp_interfaces_facts: + return [] + return lacp_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_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_lacp_interfaces_facts = self.get_lacp_interfaces_facts() + + result['before'] = existing_lacp_interfaces_facts + if result['changed']: + result['after'] = changed_lacp_interfaces_facts + + result['warnings'] = warnings + return result + + 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) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_lacp_interfaces_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + state = self._module.params['state'] + + if state == 'overridden': + commands.extend( + Lacp_interfaces._state_overridden( + want, have + ) + ) + + elif state == 'deleted': + if not want: + for intf in have: + commands.extend( + Lacp_interfaces._state_deleted( + {'name': intf['name']}, + intf + ) + ) + else: + for item in want: + obj_in_have = search_obj_in_list(item['name'], have) + commands.extend( + Lacp_interfaces._state_deleted( + item, obj_in_have + ) + ) + + else: + for item in want: + name = item['name'] + obj_in_have = search_obj_in_list(name, have) + + if state == 'merged': + commands.extend( + Lacp_interfaces._state_merged( + item, obj_in_have + ) + ) + + elif state == 'replaced': + commands.extend( + Lacp_interfaces._state_replaced( + item, obj_in_have + ) + ) + + return commands + + @staticmethod + def _state_replaced(want, have): + """ The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + replaced_commands = [] + merged_commands = [] + + if have: + replaced_commands = Lacp_interfaces._state_deleted(want, have) + + merged_commands = Lacp_interfaces._state_merged(want, have) + + if merged_commands and replaced_commands: + del merged_commands[0] + + commands.extend(replaced_commands) + commands.extend(merged_commands) + + return commands + + @staticmethod + def _state_overridden(want, have): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + for intf in have: + intf_in_want = search_obj_in_list(intf['name'], want) + if not intf_in_want: + commands.extend(Lacp_interfaces._state_deleted({'name': intf['name']}, intf)) + + for intf in want: + intf_in_have = search_obj_in_list(intf['name'], have) + commands.extend(Lacp_interfaces._state_replaced(intf, intf_in_have)) + + return commands + + @staticmethod + def _state_merged(want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + + if not have: + have = {'name': want['name']} + + for key, value in iteritems(flatten_dict(remove_empties(dict_diff(have, want)))): + commands.append(Lacp_interfaces._compute_commands(key, value)) + + if commands: + pad_commands(commands, want['name']) + + return commands + + @staticmethod + def _state_deleted(want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + + for key, value in iteritems(flatten_dict(dict_delete(have, remove_empties(want)))): + commands.append(Lacp_interfaces._compute_commands(key, value, remove=True)) + + if commands: + pad_commands(commands, have['name']) + + return commands + + @staticmethod + def _compute_commands(key, value, remove=False): + if key == "churn_logging": + cmd = "lacp churn logging {0}".format(value) + + elif key == "collector_max_delay": + cmd = "lacp collector-max-delay {0}".format(value) + + elif key == "period": + cmd = "lacp period {0}".format(value) + + elif key == "switchover_suppress_flaps": + cmd = "lacp switchover suppress-flaps {0}".format(value) + + elif key == 'mac': + cmd = "lacp system mac {0}".format(value) + + elif key == 'priority': + cmd = "lacp system priority {0}".format(value) + + if remove: + cmd = "no " + cmd + + return cmd diff --git a/lib/ansible/module_utils/network/iosxr/facts/facts.py b/lib/ansible/module_utils/network/iosxr/facts/facts.py index fad4f617fb8..7ace062e355 100644 --- a/lib/ansible/module_utils/network/iosxr/facts/facts.py +++ b/lib/ansible/module_utils/network/iosxr/facts/facts.py @@ -16,6 +16,7 @@ __metaclass__ = type from ansible.module_utils.network.iosxr.argspec.facts.facts import FactsArgs from ansible.module_utils.network.common.facts.facts import FactsBase from ansible.module_utils.network.iosxr.facts.lacp.lacp import LacpFacts +from ansible.module_utils.network.iosxr.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts from ansible.module_utils.network.iosxr.facts.legacy.\ base import Default, Hardware, Interfaces, Config @@ -28,6 +29,7 @@ FACT_LEGACY_SUBSETS = dict( ) FACT_RESOURCE_SUBSETS = dict( lacp=LacpFacts, + lacp_interfaces=Lacp_interfacesFacts ) diff --git a/lib/ansible/module_utils/network/iosxr/facts/lacp_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/facts/lacp_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/facts/lacp_interfaces/lacp_interfaces.py b/lib/ansible/module_utils/network/iosxr/facts/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000000..d7744c6ad23 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/facts/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,104 @@ +# +# -*- 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 iosxr 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. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import re +from copy import deepcopy + +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.iosxr.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs +from ansible.module_utils.six import iteritems + + +class Lacp_interfacesFacts(object): + """ The iosxr 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 not data: + data = connection.get_config(flags='interface') + interfaces = data.split('interface ') + + objs = [] + for interface in interfaces: + obj = self.render_config(self.generated_spec, interface) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('lacp_interfaces', None) + facts = {} + if objs: + facts['lacp_interfaces'] = [] + params = utils.validate_config(self.argument_spec, {'config': objs}) + for cfg in params['config']: + facts['lacp_interfaces'].append(utils.remove_empties(cfg)) + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + + match = re.search(r'(GigabitEthernet|Bundle-Ether|TenGigE|FortyGigE|HundredGigE)(\S+)', conf, re.M) + if match: + config['name'] = match.group(1) + match.group(2) + + temp = { + 'churn_logging': 'lacp churn logging', + 'switchover_suppress_flaps': 'lacp switchover suppress-flaps', + 'collector_max_delay': 'lacp collector-max-delay', + 'period': 'lacp period' + } + + for key, value in iteritems(temp): + config[key] = utils.parse_conf_arg( + conf, value) + + for key in config['system'].keys(): + config['system'][key] = utils.parse_conf_arg( + conf, 'lacp system {0}'.format(key)) + + return utils.remove_empties(config) diff --git a/lib/ansible/module_utils/network/iosxr/utils/utils.py b/lib/ansible/module_utils/network/iosxr/utils/utils.py index 5f132784f9d..89a85d3a7a9 100644 --- a/lib/ansible/module_utils/network/iosxr/utils/utils.py +++ b/lib/ansible/module_utils/network/iosxr/utils/utils.py @@ -30,3 +30,34 @@ def flatten_dict(x): result[key] = value return result + + +def dict_delete(base, comparable): + """ + + This function generates a dict containing key, value pairs for keys + that are present in the `base` dict but not present in the `comparable` + dict. + + :param base: dict object to base the diff on + :param comparable: dict object to compare against base + + :returns: new dict object with key, value pairs that needs to be deleted. + + """ + to_delete = dict() + + for key in base: + if isinstance(base[key], dict): + sub_diff = dict_delete(base[key], comparable.get(key, {})) + if sub_diff: + to_delete[key] = sub_diff + else: + if key not in comparable: + to_delete[key] = base[key] + + return to_delete + + +def pad_commands(commands, interface): + commands.insert(0, 'interface {0}'.format(interface)) diff --git a/lib/ansible/modules/network/iosxr/iosxr_facts.py b/lib/ansible/modules/network/iosxr/iosxr_facts.py index 190e7cb1fd0..7a701f82a3d 100644 --- a/lib/ansible/modules/network/iosxr/iosxr_facts.py +++ b/lib/ansible/modules/network/iosxr/iosxr_facts.py @@ -51,7 +51,7 @@ options: can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. required: false - choices: ['all', 'lacp'] + choices: ['all', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces'] version_added: "2.9" """ @@ -71,7 +71,7 @@ EXAMPLES = """ gather_subset: - "!hardware" -# Collect only the lag_interfaces facts +# Collect only the lacp facts - iosxr_facts: gather_subset: - "!all" @@ -79,12 +79,12 @@ EXAMPLES = """ gather_network_resources: - lacp -# Do not collect lag_interfaces facts +# Do not collect lacp_interfaces facts - iosxr_facts: gather_network_resources: - - "!lacp" + - "!lacp_interfaces" -# Collect lag_interfaces and minimal default facts +# Collect lacp and minimal default facts - iosxr_facts: gather_subset: min gather_network_resources: lacp diff --git a/lib/ansible/modules/network/iosxr/iosxr_lacp_interfaces.py b/lib/ansible/modules/network/iosxr/iosxr_lacp_interfaces.py new file mode 100644 index 00000000000..3e47bbc2b83 --- /dev/null +++ b/lib/ansible/modules/network/iosxr/iosxr_lacp_interfaces.py @@ -0,0 +1,533 @@ +#!/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 iosxr_lacp_interfaces +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} + +DOCUMENTATION = """ +--- +module: iosxr_lacp_interfaces +version_added: 2.9 +short_description: Manage Link Aggregation Control Protocol (LACP) attributes of interfaces on IOS-XR devices. +description: + - This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces on IOS-XR devices. +author: Nilashish Chakraborty (@nilashishc) +options: + config: + description: A dictionary of LACP interfaces options. + type: list + suboptions: + name: + description: + - Name/Identifier of the interface or Ether-Bundle. + type: str + churn_logging: + description: + - Specifies the parameter for logging of LACP churn events. + - Valid only for ether-bundles. + - Mode 'actor' logs actor churn events only. + - Mode 'partner' logs partner churn events only. + - Mode 'both' logs actor and partner churn events only. + type: str + choices: ['actor', 'partner', 'both'] + collector_max_delay: + description: + - Specifies the collector max delay to be signaled to the LACP partner. + - Valid only for ether-bundles. + - Refer to vendor documentation for valid values. + type: int + period: + description: + - Specifies the rate at which packets are sent or received. + - For ether-bundles, this specifies the period to be used + by its member links. + - Refer to vendor documentation for valid values. + type: int + switchover_suppress_flaps: + description: + - Specifies the time for which to suppress flaps during + a LACP switchover. + - Valid only for ether-bundles. + - Refer to vendor documentation for valid values. + type: int + system: + description: + - This dict object contains configurable options related to LACP + system parameters for ether-bundles. + type: dict + suboptions: + priority: + description: + - Specifies the system priority to use in LACP negotiations for + the bundle. + - Refer to vendor documentation for valid values. + type: int + mac: + description: + - Specifies the system ID to use in LACP negotiations for + the bundle, encoded as a MAC address. + type: str + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +""" +EXAMPLES = """ +# Using merged +# +# +# ------------ +# Before state +# ------------ +# +# +# +# RP/0/0/CPU0:an-iosxr#sh running-config interface +# Sun Jul 21 18:01:35.079 UTC +# interface Bundle-Ether10 +# ! +# interface Bundle-Ether11 +# ! +# interface Bundle-Ether12 +# ! +# interface Loopback888 +# description test for ansible +# shutdown +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/1 +# description 'GigabitEthernet - 1' +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# +# + + - name: Merge provided configuration with device configuration + iosxr_lacp_interfaces: + config: + - name: Bundle-Ether10 + churn_logging: actor + collector_max_delay: 100 + switchover_suppress_flaps: 500 + + - name: Bundle-Ether11 + system: + mac: 00c2.4c00.bd15 + + - name: GigabitEthernet0/0/0/1 + period: 200 + state: merged + +# +# +# ----------- +# After state +# ----------- +# +# +# RP/0/0/CPU0:an-iosxr#sh run int +# Sun Jul 21 18:24:52.413 UTC +# interface Bundle-Ether10 +# lacp churn logging actor +# lacp switchover suppress-flaps 500 +# lacp collector-max-delay 100 +# ! +# interface Bundle-Ether11 +# lacp system mac 00c2.4c00.bd15 +# ! +# interface Bundle-Ether12 +# ! +# interface Loopback888 +# description test for ansible +# shutdown +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/1 +# description 'GigabitEthernet - 1" +# lacp period 200 +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# + + +# Using replaced +# +# +# ------------ +# Before state +# ------------ +# +# +# RP/0/0/CPU0:an-iosxr#sh run int +# Sun Jul 21 18:24:52.413 UTC +# interface Bundle-Ether10 +# lacp churn logging actor +# lacp switchover suppress-flaps 500 +# lacp collector-max-delay 100 +# ! +# interface Bundle-Ether11 +# lacp system mac 00c2.4c00.bd15 +# ! +# interface Bundle-Ether12 +# ! +# interface Loopback888 +# description test for ansible +# shutdown +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/1 +# description 'GigabitEthernet - 1" +# lacp period 200 +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# + + - name: Replace LACP configuration of listed interfaces with provided configuration + iosxr_lacp_interfaces: + config: + - name: Bundle-Ether10 + churn_logging: partner + + - name: GigabitEthernet0/0/0/2 + period: 300 + state: replaced + +# +# +# ----------- +# After state +# ----------- +# +# +# RP/0/0/CPU0:an-iosxr#sh run int +# Sun Jul 21 18:50:21.929 UTC +# interface Bundle-Ether10 +# lacp churn logging partner +# ! +# interface Bundle-Ether11 +# lacp system mac 00c2.4c00.bd15 +# ! +# interface Bundle-Ether12 +# ! +# interface Loopback888 +# description test for ansible +# shutdown +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/1 +# description 'GigabitEthernet - 1" +# lacp period 200 +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# lacp period 300 +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# +# + + +# Using overridden +# +# +# ------------ +# Before state +# ------------ +# +# +# RP/0/0/CPU0:an-iosxr#sh run int +# Sun Jul 21 18:24:52.413 UTC +# interface Bundle-Ether10 +# lacp churn logging actor +# lacp switchover suppress-flaps 500 +# lacp collector-max-delay 100 +# ! +# interface Bundle-Ether11 +# lacp system mac 00c2.4c00.bd15 +# ! +# interface Bundle-Ether12 +# ! +# interface Loopback888 +# description test for ansible +# shutdown +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/1 +# description 'GigabitEthernet - 1" +# lacp period 200 +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# lacp period 200 +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# +# + + - name: Overridde all interface LACP configuration with provided configuration + iosxr_lacp_interfaces: + config: + - name: Bundle-Ether12 + churn_logging: both + collector_max_delay: 100 + switchover_suppress_flaps: 500 + + - name: GigabitEthernet0/0/0/1 + period: 300 + state: overridden + +# +# +# ----------- +# After state +# ----------- +# +# +# RP/0/0/CPU0:an-iosxr(config-if)#do sh run int +# Sun Jul 21 19:32:36.115 UTC +# interface Bundle-Ether10 +# ! +# interface Bundle-Ether11 +# ! +# interface Bundle-Ether12 +# lacp churn logging both +# lacp switchover suppress-flaps 500 +# lacp collector-max-delay 100 +# ! +# interface Loopback888 +# description test for ansible +# shutdown +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/1 +# description 'GigabitEthernet - 1" +# lacp period 300 +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# + + +# Using deleted +# +# +# ------------ +# Before state +# ------------ +# +# +# RP/0/0/CPU0:an-iosxr#sh run int +# Sun Jul 21 18:24:52.413 UTC +# interface Bundle-Ether10 +# lacp churn logging actor +# lacp switchover suppress-flaps 500 +# lacp collector-max-delay 100 +# ! +# interface Bundle-Ether11 +# lacp non-revertive +# ! +# interface Bundle-Ether12 +# ! +# interface Loopback888 +# description test for ansible +# shutdown +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/1 +# description 'GigabitEthernet - 1" +# lacp period 200 +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# lacp period 300 +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# + + - name: Deleted LACP configurations of provided interfaces (Note - This won't delete the interface itself) + iosxr_lacp_interfaces: + config: + - name: Bundle-Ether10 + - name: Bundle-Ether11 + - name: GigabitEthernet0/0/0/1 + - name: GigabitEthernet0/0/0/2 + state: deleted + +# +# +# ----------- +# After state +# ----------- +# +# +# RP/0/0/CPU0:an-iosxr#sh run int +# Sun Jul 21 19:51:03.499 UTC +# interface Bundle-Ether10 +# ! +# interface Bundle-Ether11 +# ! +# interface Bundle-Ether12 +# ! +# interface Loopback888 +# description test for ansible +# shutdown +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/1 +# description 'GigabitEthernet - 1" +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + 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 + type: list + 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: ['interface Bundle-Ether10', 'lacp churn logging partner', 'lacp period 150'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.iosxr.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs +from ansible.module_utils.network.iosxr.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/test/integration/targets/iosxr_interface/tests/cli/basic.yaml b/test/integration/targets/iosxr_interface/tests/cli/basic.yaml index 05ca4f2980a..403893d81fd 100644 --- a/test/integration/targets/iosxr_interface/tests/cli/basic.yaml +++ b/test/integration/targets/iosxr_interface/tests/cli/basic.yaml @@ -231,6 +231,8 @@ - name: Delete interface aggregate iosxr_interface: aggregate: + - name: GigabitEthernet0/0/0/2 + - name: GigabitEthernet0/0/0/3 - name: GigabitEthernet0/0/0/4 - name: GigabitEthernet0/0/0/5 state: absent @@ -246,6 +248,8 @@ - name: Delete interface aggregate (idempotent) iosxr_interface: aggregate: + - name: GigabitEthernet0/0/0/2 + - name: GigabitEthernet0/0/0/3 - name: GigabitEthernet0/0/0/4 - name: GigabitEthernet0/0/0/5 state: absent diff --git a/test/integration/targets/iosxr_lacp_interfaces/defaults/main.yaml b/test/integration/targets/iosxr_lacp_interfaces/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/iosxr_lacp_interfaces/tasks/cli.yaml b/test/integration/targets/iosxr_lacp_interfaces/tasks/cli.yaml new file mode 100644 index 00000000000..337e34133b0 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tasks/cli.yaml @@ -0,0 +1,20 @@ +--- +- name: Collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + delegate_to: localhost + +- name: Run test case (connection=network_cli) + include: "{{ test_case_to_run }}" + vars: + ansible_connection: network_cli + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/iosxr_lacp_interfaces/tasks/main.yaml b/test/integration/targets/iosxr_lacp_interfaces/tasks/main.yaml new file mode 100644 index 00000000000..415c99d8b12 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/iosxr_lacp_interfaces/tests/cli/_populate.yaml b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/_populate.yaml new file mode 100644 index 00000000000..630828d6d17 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/_populate.yaml @@ -0,0 +1,26 @@ +--- +- name: Setup Bundle-Ether10 + iosxr_config: + lines: + - lacp churn logging actor + - lacp switchover suppress-flaps 500 + - lacp collector-max-delay 100 + parents: interface Bundle-Ether10 + +- name: Setup Bundle-Ether11 + iosxr_config: + lines: + - lacp system mac 00c2.4c00.bd15 + parents: interface Bundle-Ether11 + +- name: Setup GigE0 + iosxr_config: + lines: + - lacp period 100 + parents: interface GigabitEthernet0/0/0/0 + +- name: Setup GigE1 + iosxr_config: + lines: + - lacp period 200 + parents: interface GigabitEthernet0/0/0/1 diff --git a/test/integration/targets/iosxr_lacp_interfaces/tests/cli/_remove_config.yaml b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/_remove_config.yaml new file mode 100644 index 00000000000..bf6e6afca3c --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/_remove_config.yaml @@ -0,0 +1,32 @@ +--- +- name: Remove Bundles + cli_config: + config: "{{ lines }}" + vars: + lines: | + no interface Bundle-Ether10 + no interface Bundle-Ether11 + no interface Bundle-Ether12 + +- name: Remove LACP interface config + iosxr_config: + lines: + - no lacp period + - shutdown + parents: "interface GigabitEthernet{{ item }}" + loop: + - 0/0/0/0 + - 0/0/0/1 + +# To make sure our assertions are not affected by +# spill overs from previous tests +- name: Remove unwanted interfaces from config + iosxr_config: + lines: + - "no interface GigabitEthernet{{ item }}" + loop: + - 0/0/0/2 + - 0/0/0/3 + - 0/0/0/4 + - 0/0/0/5 + ignore_errors: yes diff --git a/test/integration/targets/iosxr_lacp_interfaces/tests/cli/deleted.yaml b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/deleted.yaml new file mode 100644 index 00000000000..a2aa5e1dbc4 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/deleted.yaml @@ -0,0 +1,46 @@ +--- +- debug: + msg: "Start iosxr_lacp_interfaces deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Delete LACP attributes of all interfaces + iosxr_lacp_interfaces: &deleted + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Delete LACP attributes of all interfaces (IDEMPOTENT) + iosxr_lacp_interfaces: *deleted + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result.changed == false" + - "result.commands|length == 0" + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp_interfaces/tests/cli/merged.yaml b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/merged.yaml new file mode 100644 index 00000000000..be5134091b2 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/merged.yaml @@ -0,0 +1,54 @@ +--- +- debug: + msg: "START iosxr_lacp_interfaces merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge the provided configuration with the exisiting running configuration + iosxr_lacp_interfaces: &merged + config: + - name: Bundle-Ether10 + churn_logging: actor + collector_max_delay: 100 + switchover_suppress_flaps: 500 + + - name: Bundle-Ether11 + system: + mac: 00c2.4c00.bd15 + + - name: GigabitEthernet0/0/0/1 + period: 100 + state: merged + register: result + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + iosxr_lacp_interfaces: *merged + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + - "result.commands|length == 0" + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}" + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp_interfaces/tests/cli/overridden.yaml b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/overridden.yaml new file mode 100644 index 00000000000..0ad9ac47a41 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/overridden.yaml @@ -0,0 +1,54 @@ +--- +- debug: + msg: "START iosxr_lacp_interfaces overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Overridde all interface LACP configuration with provided configuration + iosxr_lacp_interfaces: &overridden + config: + - name: Bundle-Ether12 + churn_logging: both + collector_max_delay: 100 + switchover_suppress_flaps: 500 + + - name: GigabitEthernet0/0/0/1 + period: 300 + state: overridden + register: result + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Overridde all interface LACP configuration with provided configuration (IDEMPOTENT) + iosxr_lacp_interfaces: *overridden + register: result + + - name: Assert that task was idempotent + assert: + that: + - "result['changed'] == false" + - "result.commands|length == 0" + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp_interfaces/tests/cli/replaced.yaml b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/replaced.yaml new file mode 100644 index 00000000000..0dcb8505e0a --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/replaced.yaml @@ -0,0 +1,52 @@ +--- +- debug: + msg: "START iosxr_lacp_interfaces replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Replace device configurations of listed interfaces with provided configurations + iosxr_lacp_interfaces: &replaced + config: + - name: Bundle-Ether10 + churn_logging: partner + + - name: GigabitEthernet0/0/0/1 + period: 300 + state: replaced + register: result + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Replace device configurations of listed interfaces with provided configurarions (IDEMPOTENT) + iosxr_lacp_interfaces: *replaced + register: result + + - name: Assert that task was idempotent + assert: + that: + - "result['changed'] == false" + - "result.commands|length == 0" + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp_interfaces/tests/cli/rtt.yaml b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/rtt.yaml new file mode 100644 index 00000000000..a73965c5457 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/tests/cli/rtt.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: "START isoxr_lacp_interfaces round trip integration tests on connection={{ ansible_connection }}" + +- block: + - include_tasks: _remove_config.yaml + + - name: Apply the provided configuration (base config) + iosxr_lacp_interfaces: + config: + - name: Bundle-Ether10 + churn_logging: actor + collector_max_delay: 200 + + - name: Bundle-Ether11 + period: 100 + + - name: GigabitEthernet0/0/0/0 + period: 200 + state: merged + register: base_config + + - name: Gather interfaces facts + iosxr_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: + - lacp_interfaces + + - name: Apply the provided configuration (config to be reverted) + iosxr_lacp_interfaces: + config: + - name: Bundle-Ether10 + churn_logging: partner + + - name: Bundle-Ether11 + period: 200 + + - name: GigabitEthernet0/0/0/0 + period: 300 + state: overridden + register: result + + - name: Assert that changes were applied + assert: + that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Revert back to base config using facts round trip + iosxr_lacp_interfaces: + config: "{{ ansible_facts['network_resources']['lacp_interfaces'] }}" + state: overridden + register: revert + + - name: Assert that config was reverted + assert: + that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp_interfaces/vars/main.yaml b/test/integration/targets/iosxr_lacp_interfaces/vars/main.yaml new file mode 100644 index 00000000000..ffe921bd547 --- /dev/null +++ b/test/integration/targets/iosxr_lacp_interfaces/vars/main.yaml @@ -0,0 +1,137 @@ +--- +merged: + before: + - name: GigabitEthernet0/0/0/0 + + - name: GigabitEthernet0/0/0/1 + + commands: + - "interface Bundle-Ether10" + - "lacp churn logging actor" + - "lacp switchover suppress-flaps 500" + - "lacp collector-max-delay 100" + - "interface Bundle-Ether11" + - "lacp system mac 00c2.4c00.bd15" + - "interface GigabitEthernet0/0/0/1" + - "lacp period 100" + + after: + - name: Bundle-Ether10 + churn_logging: actor + switchover_suppress_flaps: 500 + collector_max_delay: 100 + + - name: Bundle-Ether11 + system: + mac: 00c2.4c00.bd15 + + - name: GigabitEthernet0/0/0/0 + + - name: GigabitEthernet0/0/0/1 + period: 100 + +populate: + - name: Bundle-Ether10 + churn_logging: actor + switchover_suppress_flaps: 500 + collector_max_delay: 100 + + - name: Bundle-Ether11 + system: + mac: 00c2.4c00.bd15 + + - name: GigabitEthernet0/0/0/0 + period: 100 + + - name: GigabitEthernet0/0/0/1 + period: 200 + +replaced: + commands: + - "interface Bundle-Ether10" + - "no lacp switchover suppress-flaps 500" + - "no lacp collector-max-delay 100" + - "lacp churn logging partner" + - "interface GigabitEthernet0/0/0/1" + - "lacp period 300" + + after: + - name: Bundle-Ether10 + churn_logging: partner + + - name: Bundle-Ether11 + system: + mac: 00c2.4c00.bd15 + + - name: GigabitEthernet0/0/0/0 + period: 100 + + - name: GigabitEthernet0/0/0/1 + period: 300 + +overridden: + commands: + - "interface Bundle-Ether10" + - "no lacp switchover suppress-flaps 500" + - "no lacp collector-max-delay 100" + - "no lacp churn logging actor" + - "interface Bundle-Ether11" + - "no lacp system mac 00c2.4c00.bd15" + - "interface GigabitEthernet0/0/0/0" + - "no lacp period 100" + - "interface Bundle-Ether12" + - "lacp churn logging both" + - "lacp collector-max-delay 100" + - "lacp switchover suppress-flaps 500" + - "interface GigabitEthernet0/0/0/1" + - "lacp period 300" + + after: + - name: Bundle-Ether10 + + - name: Bundle-Ether11 + + - name: Bundle-Ether12 + churn_logging: both + collector_max_delay: 100 + switchover_suppress_flaps: 500 + + - name: GigabitEthernet0/0/0/0 + + - name: GigabitEthernet0/0/0/1 + period: 300 + +deleted: + commands: + - "interface Bundle-Ether10" + - "no lacp switchover suppress-flaps 500" + - "no lacp collector-max-delay 100" + - "no lacp churn logging actor" + - "interface Bundle-Ether11" + - "no lacp system mac 00c2.4c00.bd15" + - "interface GigabitEthernet0/0/0/0" + - "no lacp period 100" + - "interface GigabitEthernet0/0/0/1" + - "no lacp period 200" + + after: + - name: Bundle-Ether10 + + - name: Bundle-Ether11 + + - name: GigabitEthernet0/0/0/0 + + - name: GigabitEthernet0/0/0/1 + +round_trip: + after: + - name: Bundle-Ether10 + churn_logging: partner + + - name: Bundle-Ether11 + period: 200 + + - name: GigabitEthernet0/0/0/0 + period: 300 + + - name: GigabitEthernet0/0/0/1