From fdf48ed0b478225e17ccb5951c3b22b93d3d96d7 Mon Sep 17 00:00:00 2001 From: Ganesh Nalawade Date: Mon, 12 Aug 2019 11:24:34 +0530 Subject: [PATCH] Add [junos_lacp_interfaces] resource module (#59708) * Add [junos_lacp_interfaces] resource module * Add new resource module junos_lacp_interfaces. * Targets model https://github.com/ansible-network/resource_module_models/pull/36 * Fix sanity test failure --- .../network/junos/argspec/facts/facts.py | 1 + .../junos/argspec/lacp_interfaces/__init__.py | 0 .../lacp_interfaces/lacp_interfaces.py | 53 ++ .../junos/config/lacp_interfaces/__init__.py | 0 .../config/lacp_interfaces/lacp_interfaces.py | 253 +++++++++ .../module_utils/network/junos/facts/facts.py | 2 + .../junos/facts/lacp_interfaces/__init__.py | 0 .../facts/lacp_interfaces/lacp_interfaces.py | 112 ++++ .../modules/network/junos/junos_facts.py | 2 +- .../network/junos/junos_lacp_interfaces.py | 515 ++++++++++++++++++ .../junos_lacp_interfaces/defaults/main.yaml | 3 + .../junos_lacp_interfaces/meta/main.yaml | 2 + .../junos_lacp_interfaces/tasks/main.yaml | 2 + .../junos_lacp_interfaces/tasks/netconf.yaml | 17 + .../tests/netconf/_base_config.yaml | 16 + .../tests/netconf/_remove_config.yaml | 17 + .../tests/netconf/deleted.yaml | 97 ++++ .../tests/netconf/merged.yaml | 56 ++ .../tests/netconf/overridden.yml | 76 +++ .../tests/netconf/replaced.yaml | 100 ++++ .../tests/netconf/rtt.yaml | 90 +++ 21 files changed, 1413 insertions(+), 1 deletion(-) create mode 100644 lib/ansible/module_utils/network/junos/argspec/lacp_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/junos/argspec/lacp_interfaces/lacp_interfaces.py create mode 100644 lib/ansible/module_utils/network/junos/config/lacp_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/junos/config/lacp_interfaces/lacp_interfaces.py create mode 100644 lib/ansible/module_utils/network/junos/facts/lacp_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/junos/facts/lacp_interfaces/lacp_interfaces.py create mode 100644 lib/ansible/modules/network/junos/junos_lacp_interfaces.py create mode 100644 test/integration/targets/junos_lacp_interfaces/defaults/main.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/meta/main.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/tasks/main.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/tasks/netconf.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/tests/netconf/_base_config.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/tests/netconf/_remove_config.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/tests/netconf/deleted.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/tests/netconf/merged.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/tests/netconf/overridden.yml create mode 100644 test/integration/targets/junos_lacp_interfaces/tests/netconf/replaced.yaml create mode 100644 test/integration/targets/junos_lacp_interfaces/tests/netconf/rtt.yaml diff --git a/lib/ansible/module_utils/network/junos/argspec/facts/facts.py b/lib/ansible/module_utils/network/junos/argspec/facts/facts.py index b4591228b9f..9d4a469ea1c 100644 --- a/lib/ansible/module_utils/network/junos/argspec/facts/facts.py +++ b/lib/ansible/module_utils/network/junos/argspec/facts/facts.py @@ -9,6 +9,7 @@ The arg spec for the junos facts module. CHOICES = [ 'all', 'interfaces', + 'lacp_interfaces', 'lag_interfaces', 'l2_interfaces', 'l3_interfaces', diff --git a/lib/ansible/module_utils/network/junos/argspec/lacp_interfaces/__init__.py b/lib/ansible/module_utils/network/junos/argspec/lacp_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/junos/argspec/lacp_interfaces/lacp_interfaces.py b/lib/ansible/module_utils/network/junos/argspec/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000000..94d6e666bdb --- /dev/null +++ b/lib/ansible/module_utils/network/junos/argspec/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,53 @@ +# +# -*- 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 junos_lacp_interfaces module +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Lacp_interfacesArgs(object): + """The arg spec for the junos_lacp_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = {'config': {'elements': 'dict', + 'options': {'force_up': {'type': 'bool'}, + 'name': {'type': 'str'}, + 'period': {'choices': ['fast', 'slow']}, + 'port_priority': {'type': 'int'}, + 'sync_reset': {'choices': ['disable', 'enable'], + 'type': 'str'}, + 'system': {'options': {'mac': {'type': 'dict', + 'options': {'address': {'type': 'str'}}}, + 'priority': {'type': 'int'}}, + 'type': 'dict'}}, + 'type': 'list'}, + 'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} diff --git a/lib/ansible/module_utils/network/junos/config/lacp_interfaces/__init__.py b/lib/ansible/module_utils/network/junos/config/lacp_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/junos/config/lacp_interfaces/lacp_interfaces.py b/lib/ansible/module_utils/network/junos/config/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000000..8ac50a6c884 --- /dev/null +++ b/lib/ansible/module_utils/network/junos/config/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,253 @@ +# +# -*- 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 junos_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, get_xml_conf_arg +from ansible.module_utils.network.junos.facts.facts import Facts +from ansible.module_utils.network.junos.junos import locked_config, load_config, commit_configuration, discard_changes, tostring +from ansible.module_utils.network.common.netconf import build_root_xml_node, build_child_xml_node, build_subtree + + +class Lacp_interfaces(ConfigBase): + """ + The junos_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 execute_module(self): + """ Execute the module + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + + existing_lacp_interfaces_facts = self.get_lacp_interfaces_facts() + config_xmls = self.set_config(existing_lacp_interfaces_facts) + + with locked_config(self._module): + for config_xml in to_list(config_xmls): + diff = load_config(self._module, config_xml, []) + + commit = not self._module.check_mode + if diff: + if commit: + commit_configuration(self._module) + else: + discard_changes(self._module) + result['changed'] = True + + if self._module._diff: + result['diff'] = {'prepared': diff} + + result['xml'] = config_xmls + + changed_lacp_interfaces_facts = self.get_lacp_interfaces_facts() + + result['before'] = existing_lacp_interfaces_facts + if result['changed']: + result['after'] = changed_lacp_interfaces_facts + + 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 + """ + root = build_root_xml_node('interfaces') + state = self._module.params['state'] + if state == 'overridden': + config_xmls = self._state_overridden(want, have) + elif state == 'deleted': + config_xmls = self._state_deleted(want, have) + elif state == 'merged': + config_xmls = self._state_merged(want, have) + elif state == 'replaced': + config_xmls = self._state_replaced(want, have) + + for xml in config_xmls: + root.append(xml) + + return tostring(root) + + def _state_replaced(self, want, have): + """ The xml configuration generator when state is replaced + :rtype: A list + :returns: the xml configuration necessary to migrate the current configuration + to the desired configuration + """ + intf_xml = [] + intf_xml.extend(self._state_deleted(want, have)) + intf_xml.extend(self._state_merged(want, have)) + + return intf_xml + + def _state_overridden(self, want, have): + """ The xml configuration generator when state is overridden + :rtype: A list + :returns: the xml configuration necessary to migrate the current configuration + to the desired configuration + """ + interface_xmls_obj = [] + # replace interface config with data in want + interface_xmls_obj.extend(self._state_replaced(want, have)) + + # delete interface config if interface in have not present in want + delete_obj = [] + for have_obj in have: + for want_obj in want: + if have_obj['name'] == want_obj['name']: + break + else: + delete_obj.append(have_obj) + + if delete_obj: + interface_xmls_obj.extend(self._state_deleted(delete_obj, have)) + return interface_xmls_obj + + def _state_merged(self, want, have): + """ The xml configuration generator when state is merged + :rtype: A list + :returns: the xml configuration necessary to merge the provided into + the current configuration + """ + intf_xml = [] + + for config in want: + lacp_intf_name = config['name'] + lacp_intf_root = build_root_xml_node('interface') + + build_child_xml_node(lacp_intf_root, 'name', lacp_intf_name) + if lacp_intf_name.startswith('ae'): + element = build_subtree(lacp_intf_root, 'aggregated-ether-options/lacp') + if config['period']: + build_child_xml_node(element, 'periodic', config['period']) + if config['sync_reset']: + build_child_xml_node(element, 'sync-reset', config['sync_reset']) + + system = config['system'] + if system: + mac = system.get('mac') + if mac: + if mac.get('address'): + build_child_xml_node(element, 'system-id', mac['address']) + if system.get('priority'): + build_child_xml_node(element, 'system-priority', system['priority']) + intf_xml.append(lacp_intf_root) + elif config['port_priority'] or config['force_up'] is not None: + element = build_subtree(lacp_intf_root, 'ether-options/ieee-802.3ad/lacp') + build_child_xml_node(element, 'port-priority', config['port_priority']) + if config['force_up'] is False: + build_child_xml_node(element, 'force-up', None, {'delete': 'delete'}) + else: + build_child_xml_node(element, 'force-up') + intf_xml.append(lacp_intf_root) + + return intf_xml + + def _state_deleted(self, want, have): + """ The xml configuration generator when state is deleted + :rtype: A list + :returns: the xml configuration necessary to remove the current configuration + of the provided objects + """ + intf_xml = [] + intf_obj = want + + if not intf_obj: + # delete lag interfaces attribute for all the interface + intf_obj = have + + for config in intf_obj: + lacp_intf_name = config['name'] + lacp_intf_root = build_root_xml_node('interface') + build_child_xml_node(lacp_intf_root, 'name', lacp_intf_name) + if lacp_intf_name.startswith('ae'): + element = build_subtree(lacp_intf_root, 'aggregated-ether-options/lacp') + build_child_xml_node(element, 'periodic', None, {'delete': 'delete'}) + build_child_xml_node(element, 'sync-reset', None, {'delete': 'delete'}) + build_child_xml_node(element, 'system-id', None, {'delete': 'delete'}) + build_child_xml_node(element, 'system-priority', None, {'delete': 'delete'}) + else: + element = build_subtree(lacp_intf_root, 'ether-options/ieee-802.3ad/lacp') + build_child_xml_node(element, 'port-priority', None, {'delete': 'delete'}) + build_child_xml_node(element, 'force-up', None, {'delete': 'delete'}) + + intf_xml.append(lacp_intf_root) + + return intf_xml diff --git a/lib/ansible/module_utils/network/junos/facts/facts.py b/lib/ansible/module_utils/network/junos/facts/facts.py index 89985002737..4a755f8bf3f 100644 --- a/lib/ansible/module_utils/network/junos/facts/facts.py +++ b/lib/ansible/module_utils/network/junos/facts/facts.py @@ -13,6 +13,7 @@ from ansible.module_utils.network.junos.argspec.facts.facts import FactsArgs from ansible.module_utils.network.common.facts.facts import FactsBase from ansible.module_utils.network.junos.facts.legacy.base import Default, Hardware, Config, Interfaces, OFacts, HAS_PYEZ from ansible.module_utils.network.junos.facts.interfaces.interfaces import InterfacesFacts +from ansible.module_utils.network.junos.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts from ansible.module_utils.network.junos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts from ansible.module_utils.network.junos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts from ansible.module_utils.network.junos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts @@ -27,6 +28,7 @@ FACT_LEGACY_SUBSETS = dict( ) FACT_RESOURCE_SUBSETS = dict( interfaces=InterfacesFacts, + lacp_interfaces=Lacp_interfacesFacts, lag_interfaces=Lag_interfacesFacts, l2_interfaces=L2_interfacesFacts, l3_interfaces=L3_interfacesFacts, diff --git a/lib/ansible/module_utils/network/junos/facts/lacp_interfaces/__init__.py b/lib/ansible/module_utils/network/junos/facts/lacp_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/junos/facts/lacp_interfaces/lacp_interfaces.py b/lib/ansible/module_utils/network/junos/facts/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000000..8b9afcec7d1 --- /dev/null +++ b/lib/ansible/module_utils/network/junos/facts/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,112 @@ +# +# -*- 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 junos 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 + +from copy import deepcopy + +from ansible.module_utils._text import to_bytes +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.junos.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs +from ansible.module_utils.six import string_types +try: + from lxml import etree + HAS_LXML = True +except ImportError: + HAS_LXML = False + + +class Lacp_interfacesFacts(object): + """ The junos 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 interfaces + :param connection: the device connection + :param data: previously collected configuration as lxml ElementTree root instance + or valid xml sting + :rtype: dictionary + :returns: facts + """ + if not HAS_LXML: + self._module.fail_json(msg='lxml is not installed.') + + if not data: + config_filter = """ + + + + """ + data = connection.get_configuration(filter=config_filter) + + if isinstance(data, string_types): + data = etree.fromstring(to_bytes(data, errors='surrogate_then_replace')) + + self._resources = data.xpath('configuration/interfaces/interface') + + objs = [] + for resource in self._resources: + if resource is not None: + obj = self.render_config(self.generated_spec, resource) + if obj: + objs.append(obj) + 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 ElementTree instance of configuration object + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + config['name'] = utils.get_xml_conf_arg(conf, 'name') + config['period'] = utils.get_xml_conf_arg(conf, 'aggregated-ether-options/lacp/periodic') + config['sync_reset'] = utils.get_xml_conf_arg(conf, 'aggregated-ether-options/lacp/sync-reset') + force_up = utils.get_xml_conf_arg(conf, 'ether-options/ieee-802.3ad/lacp/force-up', data='tag') + if force_up: + config['force_up'] = True + config['port_priority'] = utils.get_xml_conf_arg(conf, 'ether-options/ieee-802.3ad/lacp/port-priority') + config['system']['priority'] = utils.get_xml_conf_arg(conf, 'aggregated-ether-options/lacp/system-priority') + address = utils.get_xml_conf_arg(conf, 'aggregated-ether-options/lacp/system-id') + if address: + config['system'].update({'mac': {'address': address}}) + + lacp_intf_cfg = utils.remove_empties(config) + # if lacp config is not present for interface return empty dict + if len(lacp_intf_cfg) == 1: + return {} + else: + return lacp_intf_cfg diff --git a/lib/ansible/modules/network/junos/junos_facts.py b/lib/ansible/modules/network/junos/junos_facts.py index 474c8ee6883..1a75de86f33 100644 --- a/lib/ansible/modules/network/junos/junos_facts.py +++ b/lib/ansible/modules/network/junos/junos_facts.py @@ -63,7 +63,7 @@ options: to a given subset. Possible values for this argument include all and the resources like interfaces, vlans etc. Can specify a list of values to include a larger subset. - choices: ['all', 'interfaces', 'lag_interfaces', 'l2_interfaces', 'l3_interfaces', 'lldp_interfaces', 'vlans'] + choices: ['all', 'interfaces', 'lacp_interfaces', 'lag_interfaces', 'l2_interfaces', 'l3_interfaces', 'lldp_interfaces', 'vlans'] required: false version_added: "2.9" requirements: diff --git a/lib/ansible/modules/network/junos/junos_lacp_interfaces.py b/lib/ansible/modules/network/junos/junos_lacp_interfaces.py new file mode 100644 index 00000000000..0b93a799d0e --- /dev/null +++ b/lib/ansible/modules/network/junos/junos_lacp_interfaces.py @@ -0,0 +1,515 @@ +#!/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 junos_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: junos_lacp_interfaces +version_added: 2.9 +short_description: Manage Link Aggregation Control Protocol (LACP) attributes of interfaces on Juniper JUNOS devices. +description: + - This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces on Juniper JUNOS devices. +author: Ganesh Nalawade (@ganeshrn) +options: + config: + description: The list of dictionaries of LACP interfaces options. + type: list + elements: dict + suboptions: + name: + description: + - Name Identifier of the interface or link aggregation group. + type: str + period: + description: + - Timer interval for periodic transmission of LACP packets. If the value is + set to C(fast) the packets are received every second and if the value is + C(slow) the packets are received every 30 seconds. This value is applicable + for aggregate interface only. + type: str + choices: ['fast', 'slow'] + sync_reset: + description: + - The argument notifies minimum-link failure out of sync to peer. If the value + is C(disable) it disables minimum-link failure handling at LACP level and if + value is C(enable) it enables minimum-link failure handling at LACP level. + This value is applicable for aggregate interface only. + type: str + choices: ['disable', 'enable'] + force_up: + description: + - This is a boolean argument to control if the port should be up in absence + of received link Aggregation Control Protocol Data Unit (LACPDUS). + This value is applicable for member interfaces only. + type: bool + port_priority: + description: + - Priority of the member port. This value is applicable for member interfaces only. + - Refer to vendor documentation for valid values. + type: int + system: + description: + - This dict object contains configurable options related to LACP + system parameters for the link aggregation group. + This value is applicable for aggregate interface only. + 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: dict + suboptions: + address: + description: + - The system ID to use in LACP negotiations. + 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: +# ------------- +# user@junos01# show interfaces +# ge-0/0/2 { +# ether-options { +# 802.3ad ae4; +# } +# } +# ge-0/0/3 { +# ether-options { +# 802.3ad ae0; +# } +# } +# ae0 { +# description "lag interface merged"; +# aggregated-ether-options { +# lacp { +# passive; +# } +# } +# } +# ae4 { +# description "test aggregate interface"; +# aggregated-ether-options { +# lacp { +# passive; +# link-protection; +# } +# } +# } + +- name: Merge provided configuration with device configuration + junos_lacp_interfaces: + config: + - name: ae0 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:02 + - name: ge-0/0/3 + port_priority: 100 + force_up: True + state: merged + +# After state: +# ------------- +# user@junos01# show interfaces +# ge-0/0/2 { +# ether-options { +# 802.3ad ae4; +# } +# } +# ge-0/0/3 { +# ether-options { +# 802.3ad { +# lacp { +# force-up; +# port-priority 100; +# } +# ae0; +# } +# } +# } +# ae0 { +# description "lag interface merged"; +# aggregated-ether-options { +# lacp { +# passive; +# periodic fast; +# sync-reset enable; +# system-priority 100; +# system-id 00:00:00:00:00:02; +# } +# } +# } +# ae4 { +# description "test aggregate interface"; +# aggregated-ether-options { +# lacp { +# passive; +# link-protection; +# } +# } +# } + +# Using replaced +# Before state: +# ------------- +# user@junos01# show interfaces +# ge-0/0/2 { +# ether-options { +# 802.3ad ae4; +# } +# } +# ge-0/0/3 { +# ether-options { +# 802.3ad { +# lacp { +# force-up; +# port-priority 100; +# } +# ae0; +# } +# } +# } +# ae0 { +# description "lag interface merged"; +# aggregated-ether-options { +# lacp { +# passive; +# periodic fast; +# sync-reset enable; +# system-priority 100; +# system-id 00:00:00:00:00:02; +# } +# } +# } +# ae4 { +# description "test aggregate interface"; +# aggregated-ether-options { +# lacp { +# passive; +# link-protection; +# } +# } +# } + +- name: Replace device LACP interfaces configuration with provided configuration + junos_lacp_interfaces: + config: + - name: ae0 + period: slow + state: replaced + +# After state: +# ------------- +# user@junos01# show interfaces +# ge-0/0/2 { +# ether-options { +# 802.3ad ae4; +# } +# } +# ge-0/0/3 { +# ether-options { +# 802.3ad { +# lacp { +# force-up; +# port-priority 100; +# } +# ae0; +# } +# } +# } +# ae0 { +# description "lag interface merged"; +# aggregated-ether-options { +# lacp { +# passive; +# periodic slow; +# } +# } +# } +# ae4 { +# description "test aggregate interface"; +# aggregated-ether-options { +# lacp { +# passive; +# link-protection; +# } +# } +# } + +# Using overridden +# Before state: +# ------------- +# user@junos01# show interfaces +# ge-0/0/2 { +# ether-options { +# 802.3ad ae4; +# } +# } +# ge-0/0/3 { +# ether-options { +# 802.3ad { +# lacp { +# force-up; +# port-priority 100; +# } +# ae0; +# } +# } +# } +# ae0 { +# description "lag interface merged"; +# aggregated-ether-options { +# lacp { +# passive; +# periodic slow; +# } +# } +# } +# ae4 { +# description "test aggregate interface"; +# aggregated-ether-options { +# lacp { +# passive; +# link-protection; +# } +# } +# } + +- name: Overrides all device LACP interfaces configuration with provided configuration + junos_lacp_interfaces: + config: + - name: ae0 + system: + priority: 300 + mac: + address: 00:00:00:00:00:03 + - name: ge-0/0/2 + port_priority: 200 + force_up: False + state: overridden + +# After state: +# ------------- +# user@junos01# show interfaces +# ge-0/0/2 { +# ether-options { +# 802.3ad { +# lacp { +# port-priority 200; +# } +# ae4; +# } +# } +# } +# ge-0/0/3 { +# ether-options { +# 802.3ad { +# lacp { +# force-up; +# port-priority 100; +# } +# ae0; +# } +# } +# } +# ae0 { +# description "lag interface merged"; +# aggregated-ether-options { +# lacp { +# passive; +# system-priority 300; +# system-id 00:00:00:00:00:03; +# } +# } +# } +# ae4 { +# description "test aggregate interface"; +# aggregated-ether-options { +# lacp { +# passive; +# link-protection; +# } +# } +# } + +# Using deleted +# Before state: +# ------------- +# user@junos01# show interfaces +# ge-0/0/2 { +# ether-options { +# 802.3ad { +# lacp { +# port-priority 200; +# } +# ae4; +# } +# } +# } +# ge-0/0/3 { +# ether-options { +# 802.3ad { +# lacp { +# force-up; +# port-priority 100; +# } +# ae0; +# } +# } +# } +# ae0 { +# description "lag interface merged"; +# aggregated-ether-options { +# lacp { +# passive; +# system-priority 300; +# system-id 00:00:00:00:00:03; +# } +# } +# } +# ae4 { +# description "test aggregate interface"; +# aggregated-ether-options { +# lacp { +# passive; +# link-protection; +# } +# } +# } + +- name: "Delete LACP interfaces attributes of given interfaces (Note: This won't delete the interface itself)" + junos_lacp_interfaces: + config: + - name: ae0 + - name: ge-0/0/3 + - name: ge-0/0/2 + state: deleted + +# After state: +# ------------- +# user@junos01# show interfaces +# ge-0/0/2 { +# ether-options { +# 802.3ad ae4; +# } +# } +# ge-0/0/3 { +# ether-options { +# 802.3ad ae0; +# } +# } +# ae0 { +# description "lag interface merged"; +# aggregated-ether-options { +# lacp { +# passive; +# } +# } +# } +# ae4 { +# description "test aggregate interface"; +# aggregated-ether-options { +# lacp { +# passive; +# link-protection; +# } +# } +# } +""" + +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: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.junos.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs +from ansible.module_utils.network.junos.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/junos_lacp_interfaces/defaults/main.yaml b/test/integration/targets/junos_lacp_interfaces/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/junos_lacp_interfaces/meta/main.yaml b/test/integration/targets/junos_lacp_interfaces/meta/main.yaml new file mode 100644 index 00000000000..874017d64b1 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/meta/main.yaml @@ -0,0 +1,2 @@ +dependencies: +# - prepare_junos_tests diff --git a/test/integration/targets/junos_lacp_interfaces/tasks/main.yaml b/test/integration/targets/junos_lacp_interfaces/tasks/main.yaml new file mode 100644 index 00000000000..cc27f174fd8 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: netconf.yaml, tags: ['netconf'] } diff --git a/test/integration/targets/junos_lacp_interfaces/tasks/netconf.yaml b/test/integration/targets/junos_lacp_interfaces/tasks/netconf.yaml new file mode 100644 index 00000000000..73b91adfaa2 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tasks/netconf.yaml @@ -0,0 +1,17 @@ +--- +- name: collect all netconf test cases + find: + paths: "{{ role_path }}/tests/netconf" + patterns: "{{ testcase }}.yaml" + use_regex: true + connection: local + register: test_cases + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case (connection=netconf) + include: "{{ test_case_to_run }} ansible_connection=netconf" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/junos_lacp_interfaces/tests/netconf/_base_config.yaml b/test/integration/targets/junos_lacp_interfaces/tests/netconf/_base_config.yaml new file mode 100644 index 00000000000..75f9612a298 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tests/netconf/_base_config.yaml @@ -0,0 +1,16 @@ +--- +- debug: + msg: "Start junos_lacp_interfaces base config ansible_connection={{ ansible_connection }}" + +- name: Configure base lag interface + junos_config: + lines: + - set interfaces ae1 description "Configured by Ansible" + - set interfaces ae2 description "Configured by Ansible" + - set interfaces ge-0/0/1 ether-options 802.3ad ae1 + - set interfaces ge-0/0/2 ether-options 802.3ad ae1 + - set interfaces ge-0/0/3 ether-options 802.3ad ae2 + - set interfaces ge-0/0/4 ether-options 802.3ad ae2 + +- debug: + msg: "End junos_lacp_interfaces base config ansible_connection={{ ansible_connection }}" diff --git a/test/integration/targets/junos_lacp_interfaces/tests/netconf/_remove_config.yaml b/test/integration/targets/junos_lacp_interfaces/tests/netconf/_remove_config.yaml new file mode 100644 index 00000000000..ef784f1b0ed --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tests/netconf/_remove_config.yaml @@ -0,0 +1,17 @@ +--- +- debug: + msg: "Start junos_nterfaces deleted remove interface config ansible_connection={{ ansible_connection }}" + +- name: "Setup - remove interface config" + junos_config: + lines: + - delete interfaces ae1 + - delete interfaces ae2 + - delete interfaces ge-0/0/1 + - delete interfaces ge-0/0/2 + - delete interfaces ge-0/0/3 + - delete interfaces ge-0/0/4 + - delete interfaces lo0 + +- debug: + msg: "End junos_nterfaces deleted remove interface config ansible_connection={{ ansible_connection }}" diff --git a/test/integration/targets/junos_lacp_interfaces/tests/netconf/deleted.yaml b/test/integration/targets/junos_lacp_interfaces/tests/netconf/deleted.yaml new file mode 100644 index 00000000000..449ed3aba53 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tests/netconf/deleted.yaml @@ -0,0 +1,97 @@ +--- +- debug: + msg: "START junos_lacp_interfaces deleted integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml +- include_tasks: _base_config.yaml + +- set_fact: + expected_deleted_output: + - name: ae2 + period: slow + sync_reset: disable + system: + priority: 200 + mac: + address: 00:00:00:00:00:04 + - name: ge-0/0/3 + port_priority: 300 + +- block: + - name: Configure initial state for interface + junos_lacp_interfaces: &initial + config: + - name: ae1 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:02 + - name: ge-0/0/1 + port_priority: 100 + force_up: True + - name: ae2 + period: slow + sync_reset: disable + system: + priority: 200 + mac: + address: 00:00:00:00:00:04 + - name: ge-0/0/3 + port_priority: 300 + force_up: False + state: merged + register: result + + - name: Delete the provided interface configuration from running configuration + junos_lacp_interfaces: &deleted + config: + - name: ae1 + - name: ge-0/0/1 + state: deleted + register: result + + - name: Assert the configuration is reflected on host + assert: + that: + - "{{ expected_deleted_output | symmetric_difference(result['after']) |length == 0 }}" + + - name: Delete the provided lacp interface configuration from running configuration (IDEMPOTENT) + junos_lacp_interfaces: *deleted + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + - name: Configure initial state for interface + junos_lacp_interfaces: *initial + register: result + + - name: Delete the all lacp interface configuration from running configuration + junos_lacp_interfaces: + state: deleted + register: result + + - name: Assert the configuration is reflected on host + assert: + that: + - "{{ result['after'] == [] }}" + + - name: Delete the all lacp interfaces configuration from running configuration (IDEMPOTENT) + junos_lacp_interfaces: + state: deleted + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + always: + - include_tasks: _remove_config.yaml + +- debug: + msg: "END junos_lacp_interfaces deleted integration tests on connection={{ ansible_connection }}" diff --git a/test/integration/targets/junos_lacp_interfaces/tests/netconf/merged.yaml b/test/integration/targets/junos_lacp_interfaces/tests/netconf/merged.yaml new file mode 100644 index 00000000000..0019dffe1d9 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tests/netconf/merged.yaml @@ -0,0 +1,56 @@ +--- +- debug: + msg: "START junos_lacp_interfaces merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml +- include_tasks: _base_config.yaml + +- set_fact: + expected_merged_output: + - name: ae1 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:02 + - name: ge-0/0/1 + port_priority: 100 + force_up: True + +- block: + - name: Merge the provided configuration with the exisiting running configuration + junos_lacp_interfaces: &merged + config: + - name: ae1 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:02 + - name: ge-0/0/1 + port_priority: 100 + force_up: True + state: merged + register: result + + - name: Assert the configuration is reflected on host + assert: + that: + - "{{ expected_merged_output | symmetric_difference(result['after']) |length == 0 }}" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + junos_lacp_interfaces: *merged + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + always: + - include_tasks: _remove_config.yaml + +- debug: + msg: "END junos_lacp_interfaces merged integration tests on connection={{ ansible_connection }}" diff --git a/test/integration/targets/junos_lacp_interfaces/tests/netconf/overridden.yml b/test/integration/targets/junos_lacp_interfaces/tests/netconf/overridden.yml new file mode 100644 index 00000000000..d2d16c7205c --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tests/netconf/overridden.yml @@ -0,0 +1,76 @@ +--- +- debug: + msg: "START junos_lacp_interfaces overide integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml +- include_tasks: _base_config.yaml + +- set_fact: + expected_overridden_output: + - name: ae1 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:01 + +- block: + - name: Configure initial state for lacp interface + junos_lacp_interfaces: + config: + - name: ae1 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:02 + - name: ge-0/0/1 + port_priority: 100 + force_up: True + - name: ae2 + period: slow + sync_reset: disable + system: + priority: 200 + mac: + address: 00:00:00:00:00:04 + - name: ge-0/0/3 + port_priority: 300 + force_up: False + state: merged + register: result + + - name: Override the provided configuration with the exisiting running configuration + junos_lacp_interfaces: &overridden + config: + - name: ae1 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:01 + state: overridden + register: result + + - name: Assert the configuration is reflected on host + assert: + that: + - "{{ expected_overridden_output | symmetric_difference(result['after']) |length == 0 }}" + + - name: Override the provided configuration with the existing running configuration (IDEMPOTENT) + junos_lacp_interfaces: *overridden + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + always: + - include_tasks: _remove_config.yaml + +- debug: + msg: "END junos_lacp_interfaces override integration tests on connection={{ ansible_connection }}" diff --git a/test/integration/targets/junos_lacp_interfaces/tests/netconf/replaced.yaml b/test/integration/targets/junos_lacp_interfaces/tests/netconf/replaced.yaml new file mode 100644 index 00000000000..27e5f23df30 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tests/netconf/replaced.yaml @@ -0,0 +1,100 @@ +--- +- debug: + msg: "START junos_lacp_interfaces replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml +- include_tasks: _base_config.yaml + +- set_fact: + expected_replaced_output: + - name: ae1 + period: slow + sync_reset: disable + system: + priority: 10 + mac: + address: 00:00:00:00:00:03 + - name: ae2 + period: fast + system: + priority: 300 + - name: ge-0/0/1 + force_up: true + port_priority: 100 + - name: ge-0/0/2 + port_priority: 250 + - name: ge-0/0/3 + port_priority: 300 + - name: ge-0/0/4 + port_priority: 400 + force_up: true + +- block: + - name: Configure initial state for lacp interface + junos_lacp_interfaces: + config: + - name: ae1 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:02 + - name: ge-0/0/1 + port_priority: 100 + force_up: True + - name: ae2 + period: slow + sync_reset: disable + system: + priority: 200 + mac: + address: 00:00:00:00:00:04 + - name: ge-0/0/3 + port_priority: 300 + force_up: False + state: merged + register: result + + - name: Replace the provided configuration with the exisiting running configuration + junos_lacp_interfaces: &replaced + config: + - name: ae1 + period: slow + sync_reset: disable + system: + priority: 10 + mac: + address: 00:00:00:00:00:03 + - name: ge-0/0/2 + port_priority: 250 + force_up: False + - name: ae2 + period: fast + system: + priority: 300 + - name: ge-0/0/4 + port_priority: 400 + force_up: True + state: replaced + register: result + + - name: Assert the configuration is reflected on host + assert: + that: + - "{{ expected_replaced_output | symmetric_difference(result['after']) |length == 0 }}" + + - name: Replace the provided configuration with the existing running configuration (IDEMPOTENT) + junos_lacp_interfaces: *replaced + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + always: + - include_tasks: _remove_config.yaml + +- debug: + msg: "END junos_lacp_interfaces replaced integration tests on connection={{ ansible_connection }}" diff --git a/test/integration/targets/junos_lacp_interfaces/tests/netconf/rtt.yaml b/test/integration/targets/junos_lacp_interfaces/tests/netconf/rtt.yaml new file mode 100644 index 00000000000..a46fbe83b68 --- /dev/null +++ b/test/integration/targets/junos_lacp_interfaces/tests/netconf/rtt.yaml @@ -0,0 +1,90 @@ +--- +- debug: + msg: "START junos_lacp_interfaces round trip integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml +- include_tasks: _base_config.yaml + +- set_fact: + expected_revert_output: + - name: ae1 + period: slow + sync_reset: disable + system: + priority: 10 + mac: + address: 00:00:00:00:00:03 + - name: ae2 + period: fast + system: + priority: 300 + - name: ge-0/0/2 + port_priority: 250 + force_up: True + - name: ge-0/0/4 + port_priority: 400 + force_up: True + +- block: + - name: Apply the provided configuration (base config) + junos_lacp_interfaces: + config: + - name: ae1 + period: slow + sync_reset: disable + system: + priority: 10 + mac: + address: 00:00:00:00:00:03 + - name: ge-0/0/2 + port_priority: 250 + force_up: False + - name: ae2 + period: fast + system: + priority: 300 + - name: ge-0/0/4 + port_priority: 400 + force_up: True + state: merged + register: base_config + + - name: Gather interfaces facts + junos_facts: + gather_subset: + - default + gather_network_resources: + - lacp_interfaces + + - name: Apply the provided configuration (config to be reverted) + junos_lacp_interfaces: + config: + - name: ae1 + period: fast + sync_reset: enable + system: + priority: 100 + mac: + address: 00:00:00:00:00:01 + state: merged + register: result + + - name: Assert that changes were applied + assert: + that: "result['changed'] == true" + + - name: Revert back to base config using facts round trip + junos_lacp_interfaces: + config: "{{ ansible_facts['network_resources']['lacp_interfaces'] }}" + state: overridden + register: revert + + - name: Assert that config was reverted + assert: + that: "{{ expected_revert_output | symmetric_difference(revert['after']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml + +- debug: + msg: "END junos_lacp_interfaces round trip integration tests on connection={{ ansible_connection }}"