From 58a53fe0ebc80e2c663010729e634a8ad1e16495 Mon Sep 17 00:00:00 2001 From: Daniel Mellado Area Date: Wed, 31 Jul 2019 16:29:32 +0200 Subject: [PATCH] Add junos_l3_interfaces (#59026) --- .../network/junos/argspec/facts/facts.py | 3 +- .../junos/argspec/l3_interfaces/__init__.py | 0 .../argspec/l3_interfaces/l3_interfaces.py | 38 ++ .../junos/config/l3_interfaces/__init__.py | 0 .../config/l3_interfaces/l3_interfaces.py | 234 ++++++++++ .../module_utils/network/junos/facts/facts.py | 2 + .../junos/facts/l3_interfaces/__init__.py | 0 .../facts/l3_interfaces/l3_interfaces.py | 115 +++++ ...l3_interface.py => _junos_l3_interface.py} | 6 +- .../modules/network/junos/junos_facts.py | 2 +- .../network/junos/junos_l3_interfaces.py | 407 ++++++++++++++++++ .../junos_l3_interfaces/defaults/main.yaml | 3 + .../junos_l3_interfaces/meta/main.yaml | 2 + .../junos_l3_interfaces/tasks/main.yaml | 2 + .../junos_l3_interfaces/tasks/netconf.yaml | 16 + .../tests/netconf/junos_l3_interfaces.yml | 99 +++++ test/sanity/ignore.txt | 12 +- 17 files changed, 932 insertions(+), 9 deletions(-) create mode 100644 lib/ansible/module_utils/network/junos/argspec/l3_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/junos/argspec/l3_interfaces/l3_interfaces.py create mode 100644 lib/ansible/module_utils/network/junos/config/l3_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/junos/config/l3_interfaces/l3_interfaces.py create mode 100644 lib/ansible/module_utils/network/junos/facts/l3_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/junos/facts/l3_interfaces/l3_interfaces.py rename lib/ansible/modules/network/junos/{junos_l3_interface.py => _junos_l3_interface.py} (97%) create mode 100644 lib/ansible/modules/network/junos/junos_l3_interfaces.py create mode 100644 test/integration/targets/junos_l3_interfaces/defaults/main.yaml create mode 100644 test/integration/targets/junos_l3_interfaces/meta/main.yaml create mode 100644 test/integration/targets/junos_l3_interfaces/tasks/main.yaml create mode 100644 test/integration/targets/junos_l3_interfaces/tasks/netconf.yaml create mode 100644 test/integration/targets/junos_l3_interfaces/tests/netconf/junos_l3_interfaces.yml 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 2e8b302c812..499acb1a54c 100644 --- a/lib/ansible/module_utils/network/junos/argspec/facts/facts.py +++ b/lib/ansible/module_utils/network/junos/argspec/facts/facts.py @@ -9,7 +9,8 @@ The arg spec for the junos facts module. CHOICES = [ 'all', 'interfaces', - 'lag_interfaces' + 'lag_interfaces', + 'l3_interfaces' ] diff --git a/lib/ansible/module_utils/network/junos/argspec/l3_interfaces/__init__.py b/lib/ansible/module_utils/network/junos/argspec/l3_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/junos/argspec/l3_interfaces/l3_interfaces.py b/lib/ansible/module_utils/network/junos/argspec/l3_interfaces/l3_interfaces.py new file mode 100644 index 00000000000..e0a52d28d67 --- /dev/null +++ b/lib/ansible/module_utils/network/junos/argspec/l3_interfaces/l3_interfaces.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The arg spec for the junos_l3_interfaces module +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class L3_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the junos_l3_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = {'config': {'elements': 'dict', 'options': + { + 'ipv4': {'elements': 'dict', + 'options': + {'address': {'type': 'str'}}, + 'type': 'list'}, + 'ipv6': {'elements': 'dict', + 'options': + {'address': {'type': 'str'}}, + 'type': 'list'}, + 'name': {'required': True, 'type': 'str'}, + 'description': {'type': 'str'}, + 'unit': {'type': 'int', 'default': 0} + }, + 'type': 'list'}, + 'state': {'choices': + ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} # pylint: disable=C0301 diff --git a/lib/ansible/module_utils/network/junos/config/l3_interfaces/__init__.py b/lib/ansible/module_utils/network/junos/config/l3_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/junos/config/l3_interfaces/l3_interfaces.py b/lib/ansible/module_utils/network/junos/config/l3_interfaces/l3_interfaces.py new file mode 100644 index 00000000000..2b740def000 --- /dev/null +++ b/lib/ansible/module_utils/network/junos/config/l3_interfaces/l3_interfaces.py @@ -0,0 +1,234 @@ +# +# -*- 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_l3_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.network.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) + + +class L3_interfaces(ConfigBase): + """ + The junos_l3_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'l3_interfaces', + ] + + def __init__(self, module): + super(L3_interfaces, self).__init__(module) + + def get_l3_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) + l3_interfaces_facts = facts['ansible_network_resources'].get( + 'l3_interfaces') + if not l3_interfaces_facts: + return [] + return l3_interfaces_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + + existing_interfaces_facts = self.get_l3_interfaces_facts() + + config_xmls = self.set_config(existing_interfaces_facts) + with locked_config(self._module): + for config_xml in to_list(config_xmls): + diff = load_config(self._module, config_xml, warnings) + + 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_interfaces_facts = self.get_l3_interfaces_facts() + + result['before'] = existing_interfaces_facts + if result['changed']: + result['after'] = changed_interfaces_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_l3_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_l3_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 list xml configuration 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 _get_common_xml_node(self, name): + root_node = build_root_xml_node('interface') + build_child_xml_node(root_node, 'name', name) + intf_unit_node = build_child_xml_node(root_node, 'unit') + return root_node, intf_unit_node + + def _state_replaced(self, want, have): + """ The xml generator when state is replaced + + :rtype: A list + :returns: the xml 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 generator when state is overridden + + :rtype: A list + :returns: the xml necessary to migrate the current configuration + to the desired configuration + """ + intf_xml = [] + intf_xml.extend(self._state_deleted(have, have)) + intf_xml.extend(self._state_merged(want, have)) + return intf_xml + + def _state_merged(self, want, have): + """ The xml generator when state is merged + + :rtype: A list + :returns: the xml necessary to merge the provided into + the current configuration + """ + intf_xml = [] + for config in want: + root_node, unit_node = self._get_common_xml_node(config['name']) + build_child_xml_node(unit_node, 'name', + str(config['unit'])) + if config.get('ipv4'): + self.build_ipaddr_et(config, unit_node) + if config.get('ipv6'): + self.build_ipaddr_et(config, unit_node, protocol='ipv6') + intf_xml.append(root_node) + return intf_xml + + def build_ipaddr_et(self, config, unit_node, protocol='ipv4', + delete=False): + family = build_child_xml_node(unit_node, 'family') + inet = 'inet' + if protocol == 'ipv6': + inet = 'inet6' + ip_protocol = build_child_xml_node(family, inet) + for ip_addr in config[protocol]: + if ip_addr['address'] == 'dhcp' and protocol == 'ipv4': + build_child_xml_node(ip_protocol, 'dhcp') + else: + ip_addresses = build_child_xml_node( + ip_protocol, 'address') + build_child_xml_node( + ip_addresses, 'name', ip_addr['address']) + + 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 = [] + existing_l3_intfs = [l3_intf['name'] for l3_intf in have] + + if not want: + want = have + + for config in want: + if config['name'] not in existing_l3_intfs: + continue + else: + root_node, unit_node = self._get_common_xml_node( + config['name']) + build_child_xml_node(unit_node, 'name', + str(config['unit'])) + family = build_child_xml_node(unit_node, 'family') + ipv4 = build_child_xml_node(family, 'inet') + intf = next( + (intf for intf in have if intf['name'] == config['name']), + None) + if 'ipv4' in intf: + if 'dhcp' in [x['address'] for x in intf.get('ipv4') if intf.get('ipv4') is not None]: + build_child_xml_node(ipv4, 'dhcp', None, {'delete': 'delete'}) + else: + build_child_xml_node( + ipv4, 'address', None, {'delete': 'delete'}) + ipv6 = build_child_xml_node(family, 'inet6') + build_child_xml_node(ipv6, 'address', None, {'delete': 'delete'}) + intf_xml.append(root_node) + 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 90d282744fe..8f1635c6b27 100644 --- a/lib/ansible/module_utils/network/junos/facts/facts.py +++ b/lib/ansible/module_utils/network/junos/facts/facts.py @@ -14,6 +14,7 @@ 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.lag_interfaces.lag_interfaces import Lag_interfacesFacts +from ansible.module_utils.network.junos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts FACT_LEGACY_SUBSETS = dict( default=Default, @@ -24,6 +25,7 @@ FACT_LEGACY_SUBSETS = dict( FACT_RESOURCE_SUBSETS = dict( interfaces=InterfacesFacts, lag_interfaces=Lag_interfacesFacts, + l3_interfaces=L3_interfacesFacts, ) diff --git a/lib/ansible/module_utils/network/junos/facts/l3_interfaces/__init__.py b/lib/ansible/module_utils/network/junos/facts/l3_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/junos/facts/l3_interfaces/l3_interfaces.py b/lib/ansible/module_utils/network/junos/facts/l3_interfaces/l3_interfaces.py new file mode 100644 index 00000000000..85c3626a187 --- /dev/null +++ b/lib/ansible/module_utils/network/junos/facts/l3_interfaces/l3_interfaces.py @@ -0,0 +1,115 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The junos l3_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.network.common import utils +from ansible.module_utils.network.junos.argspec.l3_interfaces.l3_interfaces import L3_interfacesArgs + + +class L3_interfacesFacts(object): + """ The junos l3_interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = L3_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 l3_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_configuration() + resources = data.xpath('//configuration/interfaces/node()') + + objs = [] + if resources: + objs = self.parse_l3_if_resources(resources) + + config = [] + if objs: + for l3_if_obj in objs: + config.append(self.render_config( + self.generated_spec, l3_if_obj)) + + facts = {} + facts['l3_interfaces'] = config + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def parse_l3_if_resources(self, l3_if_resources): + l3_ifaces = [] + for iface in l3_if_resources: + interface = {} + interface['name'] = iface.find('name').text + if iface.find('description') is not None: + interface['description'] = iface.find('description').text + interface['unit'] = iface.find('unit/name').text + family = iface.find('unit/family/') + if family is not None and family.tag != 'ethernet-switching': + ipv4 = iface.findall('unit/family/inet/address') + dhcp = iface.findall('unit/family/inet/dhcp') + ipv6 = iface.findall('unit/family/inet6/address') + if dhcp: + interface['ipv4'] = [{'address': 'dhcp'}] + if ipv4: + interface['ipv4'] = self.get_ip_addresses(ipv4) + elif not dhcp: + interface['ipv4'] = None + if ipv6: + interface['ipv6'] = self.get_ip_addresses(ipv6) + l3_ifaces.append(interface) + return l3_ifaces + + def get_ip_addresses(self, ip_addr): + address_list = [] + for ip in ip_addr: + for addr in ip: + address_list.append({'address': addr.text}) + return address_list + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + config['name'] = conf['name'] + config['description'] = conf.get('description') + config['unit'] = conf.get('unit', 0) + config['ipv4'] = conf.get('ipv4') + config['ipv6'] = conf.get('ipv6') + + return utils.remove_empties(config) diff --git a/lib/ansible/modules/network/junos/junos_l3_interface.py b/lib/ansible/modules/network/junos/_junos_l3_interface.py similarity index 97% rename from lib/ansible/modules/network/junos/junos_l3_interface.py rename to lib/ansible/modules/network/junos/_junos_l3_interface.py index 9c580ac37f5..63beccc0ec0 100644 --- a/lib/ansible/modules/network/junos/junos_l3_interface.py +++ b/lib/ansible/modules/network/junos/_junos_l3_interface.py @@ -9,7 +9,7 @@ __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], + 'status': ['deprecated'], 'supported_by': 'network'} @@ -22,6 +22,10 @@ short_description: Manage L3 interfaces on Juniper JUNOS network devices description: - This module provides declarative management of L3 interfaces on Juniper JUNOS network devices. +deprecated: + removed_in: "2.13" + why: Updated modules released with more functionality + alternative: Use M(junos_l3_interfaces) instead. options: name: description: diff --git a/lib/ansible/modules/network/junos/junos_facts.py b/lib/ansible/modules/network/junos/junos_facts.py index 0ca895a5c2f..c145ce1a06a 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'] + choices: ['all', 'interfaces', 'lag_interfaces', 'l3_interfaces'] required: false version_added: "2.9" requirements: diff --git a/lib/ansible/modules/network/junos/junos_l3_interfaces.py b/lib/ansible/modules/network/junos/junos_l3_interfaces.py new file mode 100644 index 00000000000..3c1f3e8bf58 --- /dev/null +++ b/lib/ansible/modules/network/junos/junos_l3_interfaces.py @@ -0,0 +1,407 @@ +#!/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_l3_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_l3_interfaces +version_added: 2.9 +short_description: Manage Layer 3 interface on Juniper JUNOS devices +description: This module provides declarative management of a Layer 3 interface on Juniper JUNOS devices +author: Daniel Mellado (@dmellado) +requirements: + - ncclient (>=v0.6.4) +notes: + - This module requires the netconf system service be enabled on the device being managed. + - This module works with connection C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html). + - Tested against JunOS v18.4R1 +options: + config: + description: A dictionary of Layer 3 interface options + type: list + elements: dict + suboptions: + name: + description: + - Full name of interface, e.g. ge-0/0/1 + type: str + required: True + description: + description: + - Description about the interface, like an alias + type: str + unit: + description: + - Logical interface number. Value of C(unit) should be of type integer + default: 0 + type: int + ipv4: + description: + - IPv4 addresses to be set for the Layer 3 logical interface mentioned in I(name) option. + The address format is /. The mask is number in range 0-32 + for example, 192.0.2.1/24, or C(dhcp) to query DHCP for an IP address + type: list + elements: dict + suboptions: + address: + description: + - IPv4 address to be set for the specific interface + type: str + ipv6: + description: + - IPv6 addresses to be set for the Layer 3 logical interface mentioned in I(name) option. + The address format is /, the mask is number in range 0-128 + for example, 2001:db8:2201:1::1/64 or C(auto-config) to use SLAAC + type: list + elements: dict + suboptions: + address: + description: + - IPv6 address to be set for the specific interface + type: str + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +""" +EXAMPLES = """ +# Using deleted + +# Before state: +# ------------- +# +# admin# show interfaces +# ge-0/0/1 { +# description "L3 interface"; +# unit 0 { +# family inet { +# address 10.200.16.10/24; +# } +# } +# } +# ge-0/0/2 { +# description "non L3 interface"; +# unit 0 { +# family ethernet-switching { +# interface-mode access; +# vlan { +# members 2; +# } +# } +# } +# } + +- name: Delete JUNOS L3 logical interface + junos_l3_interfaces: + config: + - name: ge-0/0/1 + - name: ge-0/0/2 + state: deleted + +# After state: +# ------------ +# +# admin# show interfaces +# ge-0/0/1 { +# description "deleted L3 interface"; +# } +# ge-0/0/2 { +# description "non L3 interface"; +# unit 0 { +# family ethernet-switching { +# interface-mode access; +# vlan { +# members 2; +# } +# } +# } +# } + + +# Using merged + +# Before state +# ------------ +# +# admin# show interfaces +# ge-0/0/1 { +# description "L3 interface"; +# unit 0 { +# family inet { +# address 10.200.16.10/24; +# } +# } +# } +# ge-0/0/2 { +# description "non configured interface"; +# unit 0; +# } + +- name: Merge provided configuration with device configuration (default operation is merge) + junos_l3_interfaces: + config: + - name: ge-0/0/1 + ipv4: + - address: 192.168.1.10/24 + ipv6: + - address: 8d8d:8d01::1/64 + - name: ge-0/0/2 + ipv4: + - address: dhcp + state: merged + +# After state: +# ------------ +# +# admin# show interfaces +# ge-0/0/1 { +# description "L3 interface"; +# unit 0 { +# family inet { +# address 10.200.16.10/24; +# address 192.168.1.10/24; +# } +# family inet6 { +# address 8d8d:8d01::1/64; +# } +# } +# } +# ge-0/0/2 { +# description "L3 interface with dhcp"; +# unit 0 { +# family inet { +# dhcp; +# } +# } +# } + + +# Using overridden + +# Before state +# ------------ +# +# admin# show interfaces +# ge-0/0/1 { +# description "L3 interface"; +# unit 0 { +# family inet { +# address 10.200.16.10/24; +# } +# } +# } +# ge-0/0/2 { +# description "L3 interface with dhcp"; +# unit 0 { +# family inet { +# dhcp; +# } +# } +# } +# ge-0/0/3 { +# description "another L3 interface"; +# unit 0 { +# family inet { +# address 192.168.1.10/24; +# } +# } +# } + +- name: Override provided configuration with device configuration + junos_l3_interfaces: + config: + - name: ge-0/0/1 + ipv4: + - address: 192.168.1.10/24 + ipv6: + - address: 8d8d:8d01::1/64 + - name: ge-0/0/2 + ipv6: + - address: 2001:db8:3000::/64 + state: overridden + +# After state: +# ------------ +# +# admin# show interfaces +# ge-0/0/1 { +# description "L3 interface"; +# unit 0 { +# family inet { +# address 192.168.1.10/24; +# } +# family inet6 { +# address 8d8d:8d01::1/64; +# } +# } +# } +# ge-0/0/2 { +# description "L3 interface whith ipv6"; +# unit 0 { +# family inet6 { +# address 2001:db8:3000::/64; +# } +# } +# } +# ge-0/0/3 { +# description "overridden L3 interface"; +# unit 0; +# } + + +# Using replaced + +# Before state +# ------------ +# +# admin# show interfaces +# ge-0/0/1 { +# description "L3 interface"; +# unit 0 { +# family inet { +# address 10.200.16.10/24; +# } +# } +# } +# ge-0/0/2 { +# description "non configured interface"; +# unit 0; +# } +# ge-0/0/3 { +# description "another L3 interface"; +# unit 0 { +# family inet { +# address 192.168.1.10/24; +# } +# } +# } + +- name: Replace provided configuration with device configuration + junos_l3_interfaces: + config: + - name: ge-0/0/1 + ipv4: + - address: 192.168.1.10/24 + ipv6: + - address: 8d8d:8d01::1/64 + - name: ge-0/0/2 + ipv4: + - address: dhcp + state: replaced + +# After state: +# ------------ +# +# admin# show interfaces +# ge-0/0/1 { +# description "L3 interface"; +# unit 0 { +# family inet { +# address 192.168.1.10/24; +# } +# family inet6 { +# address 8d8d:8d01::1/64; +# } +# } +# } +# ge-0/0/2 { +# description "L3 interface with dhcp"; +# unit 0 { +# family inet { +# dhcp; +# } +# } +# } +# ge-0/0/3 { +# description "another L3 interface"; +# unit 0 { +# family inet { +# address 192.168.1.10/24; +# } +# } +# } + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +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.l3_interfaces.l3_interfaces import L3_interfacesArgs +from ansible.module_utils.network.junos.config.l3_interfaces.l3_interfaces import L3_interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=L3_interfacesArgs.argument_spec, + supports_check_mode=True) + + result = L3_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/junos_l3_interfaces/defaults/main.yaml b/test/integration/targets/junos_l3_interfaces/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/junos_l3_interfaces/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/junos_l3_interfaces/meta/main.yaml b/test/integration/targets/junos_l3_interfaces/meta/main.yaml new file mode 100644 index 00000000000..191a0f2ea1d --- /dev/null +++ b/test/integration/targets/junos_l3_interfaces/meta/main.yaml @@ -0,0 +1,2 @@ +dependencies: + - prepare_junos_tests diff --git a/test/integration/targets/junos_l3_interfaces/tasks/main.yaml b/test/integration/targets/junos_l3_interfaces/tasks/main.yaml new file mode 100644 index 00000000000..cc27f174fd8 --- /dev/null +++ b/test/integration/targets/junos_l3_interfaces/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: netconf.yaml, tags: ['netconf'] } diff --git a/test/integration/targets/junos_l3_interfaces/tasks/netconf.yaml b/test/integration/targets/junos_l3_interfaces/tasks/netconf.yaml new file mode 100644 index 00000000000..3bb73b248e6 --- /dev/null +++ b/test/integration/targets/junos_l3_interfaces/tasks/netconf.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all netconf test cases + find: + paths: "{{ role_path }}/tests/netconf" + patterns: "{{ testcase }}.yaml" + 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_l3_interfaces/tests/netconf/junos_l3_interfaces.yml b/test/integration/targets/junos_l3_interfaces/tests/netconf/junos_l3_interfaces.yml new file mode 100644 index 00000000000..7a717cf5517 --- /dev/null +++ b/test/integration/targets/junos_l3_interfaces/tests/netconf/junos_l3_interfaces.yml @@ -0,0 +1,99 @@ +--- +- name: bootstrap interfaces + junos_l3_interfaces: + config: + - name: ge-1/0/0 + ipv4: + - address: 192.168.100.1/24 + - address: 10.200.16.20/24 + - name: ge-2/0/0 + ipv4: + - address: 192.168.100.2/24 + - address: 10.200.16.21/24 + - name: ge-3/0/0 + ipv4: + - address: 192.168.100.3/24 + - address: 10.200.16.22/24 + state: replaced + register: result + +- assert: + that: + - result is changed + - "'192.168.100.1/24' in result.xml[0]" + - "'10.200.16.20/24' in result.xml[0]" + - "result.after[0].name == 'ge-1/0/0'" + - "result.after[0].ipv4[0]['address'] == '192.168.100.1/24'" + - "result.after[0].ipv4[1]['address'] == '10.200.16.20/24'" + +- name: bootstrap interfaces (idempotent) + junos_l3_interfaces: + config: + - name: ge-1/0/0 + ipv4: + - address: 192.168.100.1/24 + - address: 10.200.16.20/24 + - name: ge-2/0/0 + ipv4: + - address: 192.168.100.2/24 + - address: 10.200.16.21/24 + - name: ge-3/0/0 + ipv4: + - address: 192.168.100.3/24 + - address: 10.200.16.22/24 + state: replaced + register: result + +- assert: + that: + - result is not changed + +- name: Add another interface ip + junos_l3_interfaces: + config: + - name: ge-1/0/0 + ipv4: + - address: 100.64.0.1/10 + - address: 100.64.0.2/10 + state: merged + register: result + +- assert: + that: + - result is changed + - "'100.64.0.1/10' in result.xml[0]" + - "'100.64.0.2/10' in result.xml[0]" + - "result.after[0].name == 'ge-1/0/0'" + - "result.after[0].ipv4[0]['address'] == '192.168.100.1/24'" + - "result.after[0].ipv4[1]['address'] == '10.200.16.20/24'" + - "result.after[0].ipv4[2]['address'] == '100.64.0.1/10'" + - "result.after[0].ipv4[3]['address'] == '100.64.0.2/10'" + +- name: Delete ge-2/0/0 interface config + junos_l3_interfaces: + config: + - name: ge-2/0/0 + state: deleted + register: result + +- assert: + that: + - result is changed + - "'ge-2/0/00' in result.xml[0]" + +- name: Override all config + junos_l3_interfaces: + config: + - name: ge-1/0/0 + ipv4: + - address: dhcp + - name: fxp0 + ipv4: + - address: dhcp + state: overridden + register: result + +- assert: + that: + - result is changed + - "'fxp00' in result.xml[0]" diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 62e643e2c8d..e5b08324cb5 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -4898,12 +4898,12 @@ lib/ansible/modules/network/junos/junos_l2_interface.py validate-modules:E326 lib/ansible/modules/network/junos/junos_l2_interface.py validate-modules:E337 lib/ansible/modules/network/junos/junos_l2_interface.py validate-modules:E338 lib/ansible/modules/network/junos/junos_l2_interface.py validate-modules:E340 -lib/ansible/modules/network/junos/junos_l3_interface.py validate-modules:E322 -lib/ansible/modules/network/junos/junos_l3_interface.py validate-modules:E324 -lib/ansible/modules/network/junos/junos_l3_interface.py validate-modules:E326 -lib/ansible/modules/network/junos/junos_l3_interface.py validate-modules:E337 -lib/ansible/modules/network/junos/junos_l3_interface.py validate-modules:E338 -lib/ansible/modules/network/junos/junos_l3_interface.py validate-modules:E340 +lib/ansible/modules/network/junos/_junos_l3_interface.py validate-modules:E322 +lib/ansible/modules/network/junos/_junos_l3_interface.py validate-modules:E324 +lib/ansible/modules/network/junos/_junos_l3_interface.py validate-modules:E326 +lib/ansible/modules/network/junos/_junos_l3_interface.py validate-modules:E337 +lib/ansible/modules/network/junos/_junos_l3_interface.py validate-modules:E338 +lib/ansible/modules/network/junos/_junos_l3_interface.py validate-modules:E340 lib/ansible/modules/network/junos/junos_lag_interfaces.py validate-modules:E338 lib/ansible/modules/network/junos/junos_lldp.py validate-modules:E322 lib/ansible/modules/network/junos/junos_lldp.py validate-modules:E324