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 0651082bb24..d569286701c 100644 --- a/lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py +++ b/lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py @@ -32,6 +32,8 @@ class FactsArgs(object): # pylint: disable=R0903 '!interfaces', 'l2_interfaces', '!l2_interfaces', + 'lag_interfaces', + '!lag_interfaces' ] argument_spec = { diff --git a/lib/ansible/module_utils/network/iosxr/argspec/lag_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/argspec/lag_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/argspec/lag_interfaces/lag_interfaces.py b/lib/ansible/module_utils/network/iosxr/argspec/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000000..d5799ee764b --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/argspec/lag_interfaces/lag_interfaces.py @@ -0,0 +1,87 @@ +# +# -*- 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_lag_interfaces module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Lag_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the iosxr_lag_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'links': { + 'options': { + 'max_active': { + 'type': 'int' + }, + 'min_active': { + 'type': 'int' + } + }, + 'type': 'dict' + }, + 'load_balancing_hash': { + 'choices': ['dst-ip', 'src-ip'], + 'type': 'str' + }, + 'members': { + 'elements': 'dict', + 'options': { + 'member': { + 'type': 'str' + }, + 'mode': { + 'choices': ['on', 'active', 'passive', 'inherit'], + 'type': 'str' + } + }, + 'type': 'list' + }, + 'mode': { + 'choices': ['on', 'active', 'passive'], + 'type': 'str' + }, + 'name': { + 'required': True, + 'type': 'str' + } + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str' + } + } diff --git a/lib/ansible/module_utils/network/iosxr/config/lag_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/config/lag_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/config/lag_interfaces/lag_interfaces.py b/lib/ansible/module_utils/network/iosxr/config/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000000..27f763fd261 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/config/lag_interfaces/lag_interfaces.py @@ -0,0 +1,383 @@ +# +# -*- 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_lag_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.iosxr.facts.facts import Facts +from ansible.module_utils.network.common.utils \ + import ( + to_list, + dict_diff, + remove_empties, + search_obj_in_list, + param_list_to_dict + ) +from ansible.module_utils.network.iosxr.utils.utils \ + import ( + diff_list_of_dicts, + pad_commands, + flatten_dict, + dict_delete, + normalize_interface + ) + + +class Lag_interfaces(ConfigBase): + """ + The iosxr_lag_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'lag_interfaces', + ] + + def __init__(self, module): + super(Lag_interfaces, self).__init__(module) + + def get_lag_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) + lag_interfaces_facts = facts['ansible_network_resources'].get( + 'lag_interfaces') + if not lag_interfaces_facts: + return [] + return lag_interfaces_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + commands = list() + + existing_lag_interfaces_facts = self.get_lag_interfaces_facts() + commands.extend(self.set_config(existing_lag_interfaces_facts)) + if commands: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + result['commands'] = commands + + changed_lag_interfaces_facts = self.get_lag_interfaces_facts() + + result['before'] = existing_lag_interfaces_facts + if result['changed']: + result['after'] = changed_lag_interfaces_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_lag_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'] + if want: + for item in want: + item['name'] = normalize_interface(item['name']) + if 'members' in want and want['members']: + for item in want['members']: + item.update({ + 'member': normalize_interface(item['member']), + 'mode': item['mode'] + }) + have = existing_lag_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'] + commands = [] + + if state == 'overridden': + commands.extend(self._state_overridden(want, have)) + + elif state == 'deleted': + commands.extend(self._state_deleted(want, have)) + + else: + # Instead of passing entire want and have + # list of dictionaries to the respective + # _state_* methods we are passing the want + # and have dictionaries per interface + for item in want: + name = item['name'] + obj_in_have = search_obj_in_list(name, have) + + if state == 'merged': + commands.extend(self._state_merged(item, obj_in_have)) + + elif state == 'replaced': + commands.extend(self._state_replaced(item, obj_in_have)) + + return commands + + def _state_replaced(self, 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 = [] + if have: + commands.extend(self._render_bundle_del_commands(want, have)) + commands.extend(self._render_bundle_updates(want, have)) + + if commands or have == {}: + pad_commands(commands, want['name']) + + if have: + commands.extend(self._render_interface_del_commands(want, have)) + commands.extend(self._render_interface_updates(want, have)) + + return commands + + def _state_overridden(self, 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 have_intf in have: + intf_in_want = search_obj_in_list(have_intf['name'], want) + if not intf_in_want: + commands.extend(self._purge_attribs(have_intf)) + + for intf in want: + intf_in_have = search_obj_in_list(intf['name'], have) + commands.extend(self._state_replaced(intf, intf_in_have)) + + return commands + + def _state_merged(self, 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 = [] + commands.extend(self._render_bundle_updates(want, have)) + + if commands or have == {}: + pad_commands(commands, want['name']) + + commands.extend(self._render_interface_updates(want, have)) + + return commands + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + + if not want: + for item in have: + commands.extend(self._purge_attribs(intf=item)) + else: + for item in want: + name = item['name'] + obj_in_have = search_obj_in_list(name, have) + if not obj_in_have: + self._module.fail_json( + msg=('interface {0} does not exist'.format(name))) + commands.extend(self._purge_attribs(intf=obj_in_have)) + + return commands + + def _render_bundle_updates(self, want, have): + """ The command generator for updates to bundles + :rtype: A list + :returns: the commands necessary to update bundles + """ + commands = [] + if not have: + have = {'name': want['name']} + + want_copy = deepcopy(want) + have_copy = deepcopy(have) + + want_copy.pop('members', []) + have_copy.pop('members', []) + + bundle_updates = dict_diff(have_copy, want_copy) + + if bundle_updates: + for key, value in iteritems( + flatten_dict(remove_empties(bundle_updates))): + commands.append(self._compute_commands(key=key, value=value)) + + return commands + + def _render_interface_updates(self, want, have): + """ The command generator for updates to member + interfaces + :rtype: A list + :returns: the commands necessary to update member + interfaces + """ + commands = [] + + if not have: + have = {'name': want['name']} + + member_diff = diff_list_of_dicts(want['members'], + have.get('members', [])) + + for diff in member_diff: + diff_cmd = [] + bundle_cmd = 'bundle id {0}'.format( + want['name'].split('Bundle-Ether')[1]) + if diff.get('mode'): + bundle_cmd += ' mode {0}'.format(diff.get('mode')) + diff_cmd.append(bundle_cmd) + pad_commands(diff_cmd, diff['member']) + commands.extend(diff_cmd) + + return commands + + def _render_bundle_del_commands(self, want, have): + """ The command generator for delete commands + w.r.t bundles + :rtype: A list + :returns: the commands necessary to update member + interfaces + """ + commands = [] + if not want: + want = {'name': have['name']} + + want_copy = deepcopy(want) + have_copy = deepcopy(have) + want_copy.pop('members', []) + have_copy.pop('members', []) + + to_delete = dict_delete(have_copy, remove_empties(want_copy)) + if to_delete: + for key, value in iteritems(flatten_dict( + remove_empties(to_delete))): + commands.append( + self._compute_commands(key=key, value=value, remove=True)) + + return commands + + def _render_interface_del_commands(self, want, have): + """ The command generator for delete commands + w.r.t member interfaces + :rtype: A list + :returns: the commands necessary to update member + interfaces + """ + commands = [] + if not want: + want = {} + have_members = have.get('members') + + if have_members: + have_members = param_list_to_dict(deepcopy(have_members), unique_key='member') + want_members = param_list_to_dict(deepcopy(want).get('members', []), unique_key='member') + + for key in have_members: + if key not in want_members: + member_cmd = ['no bundle id'] + pad_commands(member_cmd, key) + commands.extend(member_cmd) + + return commands + + def _purge_attribs(self, intf): + """ The command generator for purging attributes + :rtype: A list + :returns: the commands necessary to purge attributes + """ + commands = [] + have_copy = deepcopy(intf) + members = have_copy.pop('members', []) + + to_delete = dict_delete(have_copy, remove_empties({'name': have_copy['name']})) + if to_delete: + for key, value in iteritems(flatten_dict(remove_empties(to_delete))): + commands.append(self._compute_commands(key=key, value=value, remove=True)) + + if commands: + pad_commands(commands, intf['name']) + + if members: + members = param_list_to_dict(deepcopy(members), unique_key='member') + for key in members: + member_cmd = ['no bundle id'] + pad_commands(member_cmd, key) + commands.extend(member_cmd) + + return commands + + def _compute_commands(self, key, value, remove=False): + """ The method generates LAG commands based on the + key, value passed. When remove is set to True, + the command is negated. + :rtype: str + :returns: a command based on the `key`, `value` pair + passed and the value of `remove` + """ + if key == "mode": + cmd = "lacp mode {0}".format(value) + + elif key == "load_balancing_hash": + cmd = "bundle load-balancing hash {0}".format(value) + + elif key == "max_active": + cmd = "bundle maximum-active links {0}".format(value) + + elif key == "min_active": + cmd = "bundle minimum-active links {0}".format(value) + + if remove: + cmd = "no {0}".format(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 af55b9b293c..8a740d3aefd 100644 --- a/lib/ansible/module_utils/network/iosxr/facts/facts.py +++ b/lib/ansible/module_utils/network/iosxr/facts/facts.py @@ -20,6 +20,7 @@ from ansible.module_utils.network.iosxr.facts.lacp_interfaces.lacp_interfaces im from ansible.module_utils.network.iosxr.facts.lldp_global.lldp_global import Lldp_globalFacts from ansible.module_utils.network.iosxr.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts from ansible.module_utils.network.iosxr.facts.interfaces.interfaces import InterfacesFacts +from ansible.module_utils.network.iosxr.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts from ansible.module_utils.network.iosxr.facts.legacy.base import Default, Hardware, Interfaces, Config from ansible.module_utils.network.iosxr.facts.l2_interfaces.l2_interfaces import L2_InterfacesFacts from ansible.module_utils.network.iosxr.facts.legacy.\ @@ -39,6 +40,7 @@ FACT_RESOURCE_SUBSETS = dict( lldp_interfaces=Lldp_interfacesFacts, interfaces=InterfacesFacts, l2_interfaces=L2_InterfacesFacts, + lag_interfaces=Lag_interfacesFacts ) diff --git a/lib/ansible/module_utils/network/iosxr/facts/lag_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/facts/lag_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/facts/lag_interfaces/lag_interfaces.py b/lib/ansible/module_utils/network/iosxr/facts/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000000..212232cfea6 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/facts/lag_interfaces/lag_interfaces.py @@ -0,0 +1,128 @@ +# +# -*- 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 lag_interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +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.lag_interfaces.lag_interfaces import Lag_interfacesArgs + + +class Lag_interfacesFacts(object): + """ The iosxr lag_interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Lag_interfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for lag_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: + if interface.startswith("Bundle-Ether"): + obj = self.render_config(self.generated_spec, interface, interfaces) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('lag_interfaces', None) + facts = {} + + facts['lag_interfaces'] = [] + params = utils.validate_config(self.argument_spec, {'config': objs}) + for cfg in params['config']: + facts['lag_interfaces'].append(utils.remove_empties(cfg)) + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf, data): + """ + 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'(Bundle-Ether)(\d+)', conf, re.M) + if match: + config['name'] = match.group(1) + match.group(2) + config['load_balancing_hash'] = utils.parse_conf_arg( + conf, 'bundle load-balancing hash') + config['mode'] = utils.parse_conf_arg(conf, 'lacp mode') + config['links']['max_active'] = utils.parse_conf_arg( + conf, 'bundle maximum-active links') + config['links']['min_active'] = utils.parse_conf_arg( + conf, 'bundle minimum-active links') + config['members'] = self.parse_members(match.group(2), data) + + return utils.remove_empties(config) + + def parse_members(self, bundle_id, interfaces): + """ + Renders a list of member interfaces for every bundle + present in running-config. + + :param bundle_id: The Bundle-Ether ID fetched from running-config + :param interfaces: Data of all interfaces present in running-config + :rtype: list + :returns: A list of member interfaces + """ + def _parse_interface(name): + if name.startswith('preconfigure'): + return name.split()[1] + else: + return name.split()[0] + + members = [] + for interface in interfaces: + if not interface.startswith('Bu'): + match = re.search(r'bundle id (\d+) mode (\S+)', interface, re.M) + if match: + if bundle_id == match.group(1): + members.append( + { + 'member': _parse_interface(interface), + 'mode': match.group(2) + } + ) + + return members diff --git a/lib/ansible/module_utils/network/iosxr/utils/utils.py b/lib/ansible/module_utils/network/iosxr/utils/utils.py index 458b419cd73..9affd069bed 100644 --- a/lib/ansible/module_utils/network/iosxr/utils/utils.py +++ b/lib/ansible/module_utils/network/iosxr/utils/utils.py @@ -126,6 +126,27 @@ def pad_commands(commands, interface): commands.insert(0, 'interface {0}'.format(interface)) +def diff_list_of_dicts(w, h): + """ + Returns a list containing diff between + two list of dictionaries + """ + if not w: + w = [] + if not h: + h = [] + + diff = [] + set_w = set(tuple(d.items()) for d in w) + set_h = set(tuple(d.items()) for d in h) + difference = set_w.difference(set_h) + + for element in difference: + diff.append(dict((x, y) for x, y in element)) + + return diff + + def normalize_interface(name): """Return the normalized interface name """ @@ -145,20 +166,20 @@ def normalize_interface(name): if_type = 'FastEthernet' elif name.lower().startswith('fo'): if_type = 'FortyGigE' - 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('te'): + if_type = 'TenGigE' elif name.lower().startswith('twe'): if_type = 'TwentyFiveGigE' elif name.lower().startswith('hu'): if_type = 'HundredGigE' + elif name.lower().startswith('vl'): + if_type = 'Vlan' + elif name.lower().startswith('lo'): + if_type = 'Loopback' + elif name.lower().startswith('be'): + if_type = 'Bundle-Ether' + elif name.lower().startswith('bp'): + if_type = 'Bundle-POS' else: if_type = None @@ -188,12 +209,10 @@ def get_interface_type(interface): return 'FortyGigE' 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' + return 'Loopback' + elif interface.upper().startswith('BE'): + return 'Bundle-Ether' elif interface.upper().startswith('NV'): return 'nve' elif interface.upper().startswith('TWE'): diff --git a/lib/ansible/modules/network/iosxr/iosxr_facts.py b/lib/ansible/modules/network/iosxr/iosxr_facts.py index 119e7caf146..e487d0fa9a5 100644 --- a/lib/ansible/modules/network/iosxr/iosxr_facts.py +++ b/lib/ansible/modules/network/iosxr/iosxr_facts.py @@ -55,7 +55,8 @@ options: specific subset should not be collected. required: false choices: ['all', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces', 'lldp_global', '!lldp_global', - 'lldp_interfaces', '!lldp_interfaces', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces'] + 'lldp_interfaces', '!lldp_interfaces', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces', + 'lag_interfaces', '!lag_interfaces'] version_added: "2.9" """ diff --git a/lib/ansible/modules/network/iosxr/iosxr_lag_interfaces.py b/lib/ansible/modules/network/iosxr/iosxr_lag_interfaces.py new file mode 100644 index 00000000000..846d2ef751e --- /dev/null +++ b/lib/ansible/modules/network/iosxr/iosxr_lag_interfaces.py @@ -0,0 +1,639 @@ +#!/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_lag_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_lag_interfaces +version_added: 2.9 +short_description: Manages attributes of LAG/Ether-Bundle interfaces on IOS-XR devices. +description: + - This module manages the attributes of LAG/Ether-Bundle interfaces on IOS-XR devices. +notes: + - Tested against IOS-XR 6.1.3. + - This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html). +author: Nilashish Chakraborty (@NilashishC) +options: + config: + description: A provided Link Aggregation Group (LAG) configuration. + type: list + elements: dict + suboptions: + name: + description: + - Name/Identifier of the LAG/Ether-Bundle to configure. + type: str + required: True + members: + description: + - List of member interfaces for the LAG/Ether-Bundle. + type: list + elements: dict + suboptions: + member: + description: + - Name of the member interface. + type: str + mode: + description: + - Specifies the mode of the operation for the member interface. + - Mode 'active' runs LACP in active mode. + - Mode 'on' does not run LACP over the port. + - Mode 'passive' runs LACP in passive mode over the port. + - Mode 'inherit' runs LACP as configured in the bundle. + choices: ['on', 'active', 'passive', 'inherit'] + type: str + mode: + description: + - LAG mode. + - Mode 'active' runs LACP in active mode over the port. + - Mode 'on' does not run LACP over the port. + - Mode 'passive' runs LACP in passive mode over the port. + choices: ['on', 'active', 'passive'] + type: str + links: + description: + - This dict contains configurable options related to LAG/Ether-Bundle links. + type: dict + suboptions: + max_active: + description: + - Specifies the limit on the number of links that can be active in the LAG/Ether-Bundle. + - Refer to vendor documentation for valid values. + type: int + min_active: + description: + - Specifies the minimum number of active links needed to bring up the LAG/Ether-Bundle. + - Refer to vendor documentation for valid values. + type: int + load_balancing_hash: + description: + - Specifies the hash function used for traffic forwarded over the LAG/Ether-Bundle. + - Option 'dst-ip' uses the destination IP as the hash function. + - Option 'src-ip' uses the source IP as the hash function. + type: str + choices: ['dst-ip', 'src-ip'] + 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:iosxr01#show run int +# Sun Jul 7 19:42:59.416 UTC +# 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_lag_interfaces: + config: + - name: Bundle-Ether10 + members: + - member: GigabitEthernet0/0/0/1 + mode: inherit + - member: GigabitEthernet0/0/0/3 + mode: inherit + mode: active + links: + max_active: 5 + min_active: 2 + load_balancing_hash: src-ip + + - name: Bundle-Ether12 + members: + - member: GigabitEthernet0/0/0/2 + mode: passive + - member: GigabitEthernet0/0/0/4 + mode: passive + load_balancing_hash: dst-ip + state: merged +# +# +# ----------- +# After state +# ----------- +# +# RP/0/0/CPU0:iosxr01#show run int +# Sun Jul 7 20:51:17.685 UTC +# interface Bundle-Ether10 +# lacp mode active +# bundle load-balancing hash src-ip +# bundle maximum-active links 5 +# bundle minimum-active links 2 +# ! +# interface Bundle-Ether12 +# bundle load-balancing hash dst-ip +# ! +# 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" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# bundle id 12 mode passive +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# bundle id 12 mode passive +# ! +# + + +# Using replaced +# +# +# ------------- +# Before state +# ------------- +# +# +# RP/0/0/CPU0:iosxr01#sho run int +# Sun Jul 7 20:58:06.527 UTC +# interface Bundle-Ether10 +# lacp mode active +# bundle load-balancing hash src-ip +# bundle maximum-active links 5 +# bundle minimum-active links 2 +# ! +# interface Bundle-Ether12 +# bundle load-balancing hash dst-ip +# ! +# 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" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# bundle id 12 mode passive +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# bundle id 12 mode passive +# ! +# +# +- name: Replace device configuration of listed Bundles with provided configurations + iosxr_lag_interfaces: + config: + - name: Bundle-Ether12 + members: + - name: GigabitEthernet0/0/0/2 + mode: passive + + - name: Bundle-Ether11 + members: + - name: GigabitEthernet0/0/0/4 + load_balancing_hash: src-ip + state: replaced +# +# +# ----------- +# After state +# ----------- +# +# +# RP/0/0/CPU0:iosxr01#sh run int +# Sun Jul 7 21:22:27.397 UTC +# interface Bundle-Ether10 +# lacp mode active +# bundle load-balancing hash src-ip +# bundle maximum-active links 5 +# bundle minimum-active links 2 +# ! +# interface Bundle-Ether11 +# bundle load-balancing hash src-ip +# ! +# interface Bundle-Ether12 +# lacp mode passive +# ! +# 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" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# bundle id 12 mode on +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# bundle id 11 mode on +# ! +# +# + + +# Using overridden +# +# +# ------------ +# Before state +# ------------ +# +# +# RP/0/0/CPU0:iosxr01#sh run int +# Sun Jul 7 21:22:27.397 UTC +# interface Bundle-Ether10 +# lacp mode active +# bundle load-balancing hash src-ip +# bundle maximum-active links 5 +# bundle minimum-active links 2 +# ! +# interface Bundle-Ether11 +# bundle load-balancing hash src-ip +# ! +# interface Bundle-Ether12 +# lacp mode passive +# ! +# 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" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# bundle id 12 mode on +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# bundle id 11 mode on +# ! +# +# + +- name: Overrides all device configuration with provided configuration + iosxr_lag_interfaces: + config: + - name: Bundle-Ether10 + members: + - member: GigabitEthernet0/0/0/1 + mode: inherit + - member: GigabitEthernet0/0/0/2 + mode: inherit + mode: active + load_balancing_hash: dst-ip + state: overridden +# +# +# ------------ +# After state +# ------------ +# +# +# RP/0/0/CPU0:iosxr01#sh run int +# Sun Jul 7 21:43:04.802 UTC +# interface Bundle-Ether10 +# lacp mode active +# bundle load-balancing hash dst-ip +# ! +# 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" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# ! +# +# + + +# Using deleted +# +# +# ------------ +# Before state +# ------------ +# +# RP/0/0/CPU0:iosxr01#sh run int +# Sun Jul 7 21:22:27.397 UTC +# interface Bundle-Ether10 +# lacp mode active +# bundle load-balancing hash src-ip +# bundle maximum-active links 5 +# bundle minimum-active links 2 +# ! +# interface Bundle-Ether11 +# bundle load-balancing hash src-ip +# ! +# interface Bundle-Ether12 +# lacp mode passive +# ! +# 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" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/2 +# description "GigabitEthernet - 2" +# bundle id 12 mode on +# !n +# interface GigabitEthernet0/0/0/3 +# description "GigabitEthernet - 3" +# bundle id 10 mode inherit +# ! +# interface GigabitEthernet0/0/0/4 +# description "GigabitEthernet - 4" +# bundle id 11 mode on +# ! +# +# + +- name: Delete attributes of given bundles and removes member interfaces from them (Note - This won't delete the bundles themselves) + iosxr_lag_interfaces: + config: + - name: Bundle-Ether10 + - name: Bundle-Ether11 + - name: Bundle-Ether12 + state: deleted + +# +# +# ------------ +# After state +# ------------ +# +# RP/0/0/CPU0:iosxr01#sh run int +# Sun Jul 7 21:49:50.004 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" +# ! +# +# + +# Using deleted (without config) +# +# +# ------------ +# Before state +# ------------ +# +# RP/0/0/CPU0:an-iosxr#sh run int +# Sun Aug 18 19:49:51.908 UTC +# interface Bundle-Ether10 +# lacp mode active +# bundle load-balancing hash src-ip +# bundle maximum-active links 10 +# bundle minimum-active links 2 +# ! +# interface Bundle-Ether11 +# bundle load-balancing hash dst-ip +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 192.0.2.11 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ! +# interface GigabitEthernet0/0/0/1 +# bundle id 10 mode inherit +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# bundle id 10 mode passive +# shutdown +# ! +# interface GigabitEthernet0/0/0/3 +# bundle id 11 mode passive +# shutdown +# ! +# interface GigabitEthernet0/0/0/4 +# bundle id 11 mode passive +# shutdown +# ! +# + +- name: Delete attributes of all bundles and removes member interfaces from them (Note - This won't delete the bundles themselves) + iosxr_lag_interfaces: + state: deleted + +# +# +# ------------ +# After state +# ------------ +# +# +# RP/0/0/CPU0:an-iosxr#sh run int +# Sun Aug 18 19:54:22.389 UTC +# interface Bundle-Ether10 +# ! +# interface Bundle-Ether11 +# ! +# interface MgmtEth0/0/CPU0/0 +# ipv4 address 10.8.38.69 255.255.255.0 +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# shutdown +# ! +# interface GigabitEthernet0/0/0/3 +# shutdown +# ! +# interface GigabitEthernet0/0/0/4 +# shutdown +# ! + +""" +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', 'bundle minimum-active links 2', 'bundle load-balancing hash src-ip'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.iosxr.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs +from ansible.module_utils.network.iosxr.config.lag_interfaces.lag_interfaces import Lag_interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [('state', 'merged', ('config',)), + ('state', 'replaced', ('config',)), + ('state', 'overridden', ('config',))] + module = AnsibleModule(argument_spec=Lag_interfacesArgs.argument_spec, required_if=required_if, + supports_check_mode=True) + + result = Lag_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/plugins/cliconf/iosxr.py b/lib/ansible/plugins/cliconf/iosxr.py index 566ffd8637d..aa20ad0e4ee 100644 --- a/lib/ansible/plugins/cliconf/iosxr.py +++ b/lib/ansible/plugins/cliconf/iosxr.py @@ -190,7 +190,7 @@ class Cliconf(CliconfBase): elif label: cmd_obj['command'] = 'commit label {0}'.format(label) else: - cmd_obj['command'] = 'commit' + cmd_obj['command'] = 'commit show-error' self.send_command(**cmd_obj) diff --git a/test/integration/targets/iosxr_lag_interfaces/defaults/main.yaml b/test/integration/targets/iosxr_lag_interfaces/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/iosxr_lag_interfaces/tasks/cli.yaml b/test/integration/targets/iosxr_lag_interfaces/tasks/cli.yaml new file mode 100644 index 00000000000..337e34133b0 --- /dev/null +++ b/test/integration/targets/iosxr_lag_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_lag_interfaces/tasks/main.yaml b/test/integration/targets/iosxr_lag_interfaces/tasks/main.yaml new file mode 100644 index 00000000000..415c99d8b12 --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/iosxr_lag_interfaces/tests/cli/_populate_config.yaml b/test/integration/targets/iosxr_lag_interfaces/tests/cli/_populate_config.yaml new file mode 100644 index 00000000000..f3b53a853a6 --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/tests/cli/_populate_config.yaml @@ -0,0 +1,26 @@ +--- +- name: Setup + iosxr_lag_interfaces: + config: + - name: Bundle-Ether10 + mode: active + members: + - member: GigabitEthernet0/0/0/0 + mode: inherit + + - member: GigabitEthernet0/0/0/1 + mode: passive + load_balancing_hash: src-ip + links: + max_active: 10 + min_active: 2 + + - name: Bundle-Ether11 + load_balancing_hash: dst-ip + members: + - member: GigabitEthernet0/0/0/8 + mode: passive + + - member: GigabitEthernet0/0/0/9 + mode: passive + state: merged diff --git a/test/integration/targets/iosxr_lag_interfaces/tests/cli/_remove_config.yaml b/test/integration/targets/iosxr_lag_interfaces/tests/cli/_remove_config.yaml new file mode 100644 index 00000000000..3b102740a8a --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/tests/cli/_remove_config.yaml @@ -0,0 +1,34 @@ +--- +- name: Remove Bundles + cli_config: + config: "{{ lines }}" + vars: + lines: | + no interface Bundle-Ether10 + no interface Bundle-Ether11 + no interface Bundle-Ether12 + ignore_errors: yes + +- name: Remove LAG interface config + iosxr_config: + lines: + - no bundle id + - shutdown + parents: "interface GigabitEthernet{{ item }}" + loop: + - 0/0/0/0 + - 0/0/0/1 + - 0/0/0/2 + - 0/0/0/8 + - 0/0/0/9 + ignore_errors: yes + +- name: Remove unwanted interfaces from config + iosxr_config: + lines: + - "no interface GigabitEthernet{{ item }}" + loop: + - 0/0/0/2 + - 0/0/0/8 + - 0/0/0/9 + ignore_errors: yes diff --git a/test/integration/targets/iosxr_lag_interfaces/tests/cli/deleted.yaml b/test/integration/targets/iosxr_lag_interfaces/tests/cli/deleted.yaml new file mode 100644 index 00000000000..762a7d9f365 --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/tests/cli/deleted.yaml @@ -0,0 +1,46 @@ +--- +- debug: + msg: "Start iosxr_lag_interfaces deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Delete LAG interfaces configuration + iosxr_lag_interfaces: &deleted + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ merged['after'] | 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_lag_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_lag_interfaces/tests/cli/merged.yaml b/test/integration/targets/iosxr_lag_interfaces/tests/cli/merged.yaml new file mode 100644 index 00000000000..5f5e57d5ca6 --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/tests/cli/merged.yaml @@ -0,0 +1,65 @@ +--- +- debug: + msg: "START iosxr_lag_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_lag_interfaces: &merged + config: + - name: Bundle-Ether10 + mode: active + members: + - member: GigabitEthernet0/0/0/0 + mode: inherit + + - member: GigabitEthernet0/0/0/1 + mode: passive + load_balancing_hash: src-ip + links: + max_active: 10 + min_active: 2 + + - name: Bundle-Ether11 + load_balancing_hash: dst-ip + members: + - member: GigabitEthernet0/0/0/8 + mode: passive + + - member: GigabitEthernet0/0/0/9 + mode: passive + 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_lag_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_lag_interfaces/tests/cli/overridden.yaml b/test/integration/targets/iosxr_lag_interfaces/tests/cli/overridden.yaml new file mode 100644 index 00000000000..76bccca85ff --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/tests/cli/overridden.yaml @@ -0,0 +1,59 @@ +--- +- debug: + msg: "START iosxr_lag_interfaces overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Overridde all LAG interface configuration with provided configuration + iosxr_lag_interfaces: &overridden + config: + - name: Bundle-Ether11 + mode: active + members: + - member: GigabitEthernet0/0/0/0 + mode: active + + - member: GigabitEthernet0/0/0/1 + mode: active + load_balancing_hash: src-ip + links: + max_active: 10 + min_active: 5 + 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: + - "{{ merged['after'] | 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 LAG interface configuration with provided configuration (IDEMPOTENT) + iosxr_lag_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_lag_interfaces/tests/cli/replaced.yaml b/test/integration/targets/iosxr_lag_interfaces/tests/cli/replaced.yaml new file mode 100644 index 00000000000..fd7b1e29ea3 --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/tests/cli/replaced.yaml @@ -0,0 +1,56 @@ +--- +- debug: + msg: "START iosxr_lag_interfaces replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Replace device configurations of listed interfaces with provided configurations + iosxr_lag_interfaces: &replaced + config: + - name: Bundle-Ether10 + mode: passive + members: + - member: GigabitEthernet0/0/0/0 + mode: passive + load_balancing_hash: dst-ip + + - name: Bundle-Ether12 + mode: active + 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: + - "{{ merged['after'] | 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_lag_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_lag_interfaces/tests/cli/rtt.yaml b/test/integration/targets/iosxr_lag_interfaces/tests/cli/rtt.yaml new file mode 100644 index 00000000000..cd4c308d076 --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/tests/cli/rtt.yaml @@ -0,0 +1,62 @@ +--- +- debug: + msg: "START isoxr_lag_interfaces round trip integration tests on connection={{ ansible_connection }}" + +- block: + - include_tasks: _remove_config.yaml + + - name: Apply the provided configuration (base config) + iosxr_lag_interfaces: + config: + - name: Bundle-Ether10 + members: + - member: GigabitEthernet0/0/0/1 + mode: passive + links: + max_active: 10 + min_active: 2 + + - name: Bundle-Ether11 + mode: passive + state: merged + register: base_config + + - name: Gather interfaces facts + iosxr_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: + - lag_interfaces + + - name: Apply the provided configuration (config to be reverted) + iosxr_lag_interfaces: + config: + - name: Bundle-Ether10 + members: + - member: GigabitEthernet0/0/0/9 + mode: active + - member: GigabitEthernet0/0/0/8 + mode: passive + + - name: Bundle-Ether11 + mode: active + 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_lag_interfaces: + config: "{{ ansible_facts['network_resources']['lag_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_lag_interfaces/vars/main.yaml b/test/integration/targets/iosxr_lag_interfaces/vars/main.yaml new file mode 100644 index 00000000000..44ad52f9b12 --- /dev/null +++ b/test/integration/targets/iosxr_lag_interfaces/vars/main.yaml @@ -0,0 +1,149 @@ +--- +merged: + before: [] + + commands: + - "interface Bundle-Ether10" + - "bundle load-balancing hash src-ip" + - "bundle minimum-active links 2" + - "bundle maximum-active links 10" + - "lacp mode active" + - "interface GigabitEthernet0/0/0/1" + - "bundle id 10 mode passive" + - "interface GigabitEthernet0/0/0/0" + - "bundle id 10 mode inherit" + - "interface Bundle-Ether11" + - "bundle load-balancing hash dst-ip" + - "interface GigabitEthernet0/0/0/8" + - "bundle id 11 mode passive" + - "interface GigabitEthernet0/0/0/9" + - "bundle id 11 mode passive" + + after: + - name: Bundle-Ether10 + links: + max_active: 10 + min_active: 2 + load_balancing_hash: src-ip + members: + - member: GigabitEthernet0/0/0/0 + mode: inherit + - member: GigabitEthernet0/0/0/1 + mode: passive + mode: active + + - name: Bundle-Ether11 + load_balancing_hash: dst-ip + members: + - member: GigabitEthernet0/0/0/8 + mode: passive + - member: GigabitEthernet0/0/0/9 + mode: passive + +replaced: + commands: + - interface Bundle-Ether10 + - no bundle maximum-active links 10 + - no bundle minimum-active links 2 + - bundle load-balancing hash dst-ip + - lacp mode passive + - interface GigabitEthernet0/0/0/1 + - no bundle id + - interface GigabitEthernet0/0/0/0 + - bundle id 10 mode passive + - interface Bundle-Ether12 + - lacp mode active + + after: + - load_balancing_hash: dst-ip + members: + - member: GigabitEthernet0/0/0/0 + mode: passive + mode: passive + name: Bundle-Ether10 + + - load_balancing_hash: dst-ip + members: + - member: GigabitEthernet0/0/0/8 + mode: passive + - member: GigabitEthernet0/0/0/9 + mode: passive + name: Bundle-Ether11 + + - mode: active + name: Bundle-Ether12 + +overridden: + commands: + - interface Bundle-Ether10 + - no bundle maximum-active links 10 + - no bundle minimum-active links 2 + - no bundle load-balancing hash src-ip + - no lacp mode active + - interface GigabitEthernet0/0/0/0 + - no bundle id + - interface GigabitEthernet0/0/0/1 + - no bundle id + - interface Bundle-Ether11 + - bundle load-balancing hash src-ip + - bundle minimum-active links 5 + - bundle maximum-active links 10 + - lacp mode active + - interface GigabitEthernet0/0/0/8 + - no bundle id + - interface GigabitEthernet0/0/0/9 + - no bundle id + - interface GigabitEthernet0/0/0/0 + - bundle id 11 mode active + - interface GigabitEthernet0/0/0/1 + - bundle id 11 mode active + + after: + - name: Bundle-Ether10 + + - links: + max_active: 10 + min_active: 5 + load_balancing_hash: src-ip + members: + - member: GigabitEthernet0/0/0/0 + mode: active + - member: GigabitEthernet0/0/0/1 + mode: active + mode: active + name: Bundle-Ether11 + +deleted: + commands: + - interface Bundle-Ether10 + - no bundle maximum-active links 10 + - no bundle minimum-active links 2 + - no bundle load-balancing hash src-ip + - no lacp mode active + - interface GigabitEthernet0/0/0/0 + - no bundle id + - interface GigabitEthernet0/0/0/1 + - no bundle id + - interface Bundle-Ether11 + - no bundle load-balancing hash dst-ip + - interface GigabitEthernet0/0/0/8 + - no bundle id + - interface GigabitEthernet0/0/0/9 + - no bundle id + + after: + - name: Bundle-Ether10 + - name: Bundle-Ether11 + +round_trip: + after: + - members: + - member: GigabitEthernet0/0/0/8 + mode: passive + - member: GigabitEthernet0/0/0/9 + mode: active + name: Bundle-Ether10 + + - mode: active + name: Bundle-Ether11 + \ No newline at end of file