diff --git a/lib/ansible/module_utils/junos.py b/lib/ansible/module_utils/junos.py index c0bf146f00e..af93f276ce4 100644 --- a/lib/ansible/module_utils/junos.py +++ b/lib/ansible/module_utils/junos.py @@ -311,7 +311,7 @@ def map_obj_to_ele(module, want, top, value_map=None): if value_map and fxpath in value_map: value = value_map[fxpath].get(value) - if value or tag_only or leaf_only: + if (value is not None) or tag_only or leaf_only: ele = node if field_top: # eg: top = 'system/syslog/file' @@ -336,7 +336,7 @@ def map_obj_to_ele(module, want, top, value_map=None): ele = ele_list[0] tags = fxpath.split('/') - if value: + if value is not None: value = to_text(value, errors='surrogate_then_replace') for item in tags: diff --git a/lib/ansible/modules/network/junos/junos_l3_interface.py b/lib/ansible/modules/network/junos/junos_l3_interface.py new file mode 100644 index 00000000000..b5912015cb6 --- /dev/null +++ b/lib/ansible/modules/network/junos/junos_l3_interface.py @@ -0,0 +1,171 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Ansible by Red Hat, inc +# +# This file is part of Ansible by Red Hat +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'core'} + + +DOCUMENTATION = """ +--- +module: junos_l3_interface +version_added: "2.4" +author: "Ganesh Nalawade (@ganeshrn)" +short_description: Manage L3 interfaces on Juniper JUNOS network devices +description: + - This module provides declarative management of L3 interfaces + on Juniper JUNOS network devices. +options: + name: + description: + - Name of the L3 interface. + ipv4: + description: + - IPv4 of the L3 interface. + ipv6: + description: + - IPv6 of the L3 interface. + unit: + description: + - Logical interface number. + default: 0 + aggregate: + description: List of L3 interfaces definitions + purge: + description: + - Purge L3 interfaces not defined in the aggregate parameter. + default: no + state: + description: + - State of the L3 interface configuration. + default: present + choices: ['present', 'absent'] + active: + description: + - Specifies whether or not the configuration is active or deactivated + default: True + choices: [True, False] +requirements: + - ncclient (>=v0.5.2) +notes: + - This module requires the netconf system service be enabled on + the remote device being managed +""" + +EXAMPLES = """ +- name: Set ge-0/0/1 IPv4 address + junos_l3_interface: + name: ge-0/0/1 + ipv4: 192.168.0.1 + +- name: Remove ge-0/0/1 IPv4 address + junos_l3_interface: + name: ge-0/0/1 + state: absent +""" + +RETURN = """ +diff: + description: Configuration difference before and after applying change. + returned: when configuration is changed and diff option is enabled. + type: string + sample: > + [edit interfaces ge-0/0/1 unit 0 family inet] + + address 1.1.1.1/32; + [edit interfaces ge-0/0/1 unit 0 family inet6] + + address fd5d:12c9:2201:1::1/128; +""" +import collections + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.junos import junos_argument_spec, check_args +from ansible.module_utils.junos import load_config, map_params_to_obj, map_obj_to_ele +from ansible.module_utils.junos import commit_configuration, discard_changes, locked_config + +try: + from lxml.etree import tostring +except ImportError: + from xml.etree.ElementTree import tostring + +USE_PERSISTENT_CONNECTION = True + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + name=dict(required=True), + ipv4=dict(), + ipv6=dict(), + unit=dict(default=0, type='int'), + aggregate=dict(), + purge=dict(default=False, type='bool'), + state=dict(default='present', choices=['present', 'absent']), + active=dict(default=True, type='bool') + ) + + argument_spec.update(junos_argument_spec) + + required_one_of = [['ipv4', 'ipv6']] + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=required_one_of) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + top = 'interfaces/interface' + + param_to_xpath_map = collections.OrderedDict() + param_to_xpath_map.update([ + ('name', {'xpath': 'name', 'parent_attrib': False, 'is_key': True}), + ('unit', {'xpath': 'name', 'top': 'unit', 'parent_attrib': False, 'is_key': True}), + ('ipv4', {'xpath': 'inet/address/name', 'top': 'unit/family', 'is_key': True}), + ('ipv6', {'xpath': 'inet6/address/name', 'top': 'unit/family', 'is_key': True}) + ]) + + want = map_params_to_obj(module, param_to_xpath_map) + ele = map_obj_to_ele(module, want, top) + + with locked_config(module): + diff = load_config(module, tostring(ele), warnings, action='replace') + + commit = not module.check_mode + if diff: + if commit: + commit_configuration(module) + else: + discard_changes(module) + result['changed'] = True + + if module._diff: + result['diff'] = {'prepared': diff} + + module.exit_json(**result) + +if __name__ == "__main__": + main() diff --git a/test/integration/junos.yaml b/test/integration/junos.yaml index 78b04468f1b..2a8bf329cc2 100644 --- a/test/integration/junos.yaml +++ b/test/integration/junos.yaml @@ -113,6 +113,13 @@ rescue: - set_fact: test_failed=true + - block: + - include_role: + name: junos_l3_interface + when: "limit_to in ['*', 'junos_l3_interface']" + rescue: + - set_fact: test_failed=true + ########### - name: Has any previous test failed? fail: diff --git a/test/integration/platform_agnostic.yaml b/test/integration/platform_agnostic.yaml index bbb5cbb751f..61b8421c8fa 100644 --- a/test/integration/platform_agnostic.yaml +++ b/test/integration/platform_agnostic.yaml @@ -99,6 +99,13 @@ rescue: - set_fact: test_failed=true + - block: + - include_role: + name: net_l3_interface + when: "limit_to in ['*', 'net_l3_interface']" + rescue: + - set_fact: test_failed=true + ########### - name: Has any previous test failed? fail: diff --git a/test/integration/targets/junos_l3_interface/defaults/main.yaml b/test/integration/targets/junos_l3_interface/defaults/main.yaml new file mode 100644 index 00000000000..5f709c5aac1 --- /dev/null +++ b/test/integration/targets/junos_l3_interface/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/test/integration/targets/junos_l3_interface/tasks/main.yaml b/test/integration/targets/junos_l3_interface/tasks/main.yaml new file mode 100644 index 00000000000..cc27f174fd8 --- /dev/null +++ b/test/integration/targets/junos_l3_interface/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: netconf.yaml, tags: ['netconf'] } diff --git a/test/integration/targets/junos_l3_interface/tasks/netconf.yaml b/test/integration/targets/junos_l3_interface/tasks/netconf.yaml new file mode 100644 index 00000000000..1286b354228 --- /dev/null +++ b/test/integration/targets/junos_l3_interface/tasks/netconf.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all netconf test cases + find: + paths: "{{ role_path }}/tests/netconf" + patterns: "{{ testcase }}.yaml" + register: test_cases + delegate_to: localhost + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/junos_l3_interface/tests/netconf/basic.yaml b/test/integration/targets/junos_l3_interface/tests/netconf/basic.yaml new file mode 100644 index 00000000000..f3d31c42fef --- /dev/null +++ b/test/integration/targets/junos_l3_interface/tests/netconf/basic.yaml @@ -0,0 +1,129 @@ +--- +- debug: msg="START junos_l3_interface netconf/basic.yaml" + +- name: setup - remove interface address + junos_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: absent + provider: "{{ netconf }}" + +- name: Configure interface address + junos_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: present + provider: "{{ netconf }}" + register: result + +- name: Get running configuration + junos_rpc: + rpc: get-configuration + provider: "{{ netconf }}" + register: config + +- assert: + that: + - "result.changed == true" + - "'1.1.1.1/32' in config.xml" + - "'fd5d:12c9:2201:1::1/128' in config.xml" + - "'+ address 1.1.1.1/32;' in result.diff.prepared" + - "'+ address fd5d:12c9:2201:1::1/128;' in result.diff.prepared" + +- name: Configure interface address (idempotent) + junos_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: present + provider: "{{ netconf }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: Deactivate interface address + junos_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: present + active: False + provider: "{{ netconf }}" + register: result + +- name: Get running configuration + junos_rpc: + rpc: get-configuration + provider: "{{ netconf }}" + register: config + +- assert: + that: + - "result.changed == true" + - "'
' in config.xml" + - "'! inactive: address 1.1.1.1/32' in result.diff.prepared" + - "'! inactive: address fd5d:12c9:2201:1::1/128' in result.diff.prepared" + +- name: Activate interface address + junos_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: present + active: True + provider: "{{ netconf }}" + register: result + +- name: Get running configuration + junos_rpc: + rpc: get-configuration + provider: "{{ netconf }}" + register: config + +- assert: + that: + - "result.changed == true" + - "'1.1.1.1/32' in config.xml" + - "'fd5d:12c9:2201:1::1/128' in config.xml" + - "'! active: address 1.1.1.1/32' in result.diff.prepared" + - "'! active: address fd5d:12c9:2201:1::1/128' in result.diff.prepared" + +- name: Delete interface address + junos_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: absent + provider: "{{ netconf }}" + register: result + +- name: Get running configuration + junos_rpc: + rpc: get-configuration + provider: "{{ netconf }}" + register: config + +- assert: + that: + - "result.changed == true" + - "'1.1.1.1/32' not in config.xml" + - "'fd5d:12c9:2201:1::1/128' not in config.xml" + - "'- address 1.1.1.1/32;' in result.diff.prepared" + - "'- address fd5d:12c9:2201:1::1/128;' in result.diff.prepared" + +- name: Delete interface address (idempotent) + junos_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: absent + provider: "{{ netconf }}" + register: result + +- assert: + that: + - "result.changed == false" diff --git a/test/integration/targets/net_l3_interface/aliases.txt b/test/integration/targets/net_l3_interface/aliases.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/integration/targets/net_l3_interface/defaults/main.yaml b/test/integration/targets/net_l3_interface/defaults/main.yaml new file mode 100644 index 00000000000..5f709c5aac1 --- /dev/null +++ b/test/integration/targets/net_l3_interface/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/test/integration/targets/net_l3_interface/tasks/cli.yaml b/test/integration/targets/net_l3_interface/tasks/cli.yaml new file mode 100644 index 00000000000..46d86dd6988 --- /dev/null +++ b/test/integration/targets/net_l3_interface/tasks/cli.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + register: test_cases + delegate_to: localhost + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/net_l3_interface/tasks/main.yaml b/test/integration/targets/net_l3_interface/tasks/main.yaml new file mode 100644 index 00000000000..af08869c922 --- /dev/null +++ b/test/integration/targets/net_l3_interface/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- { include: cli.yaml, tags: ['cli'] } +- { include: netconf.yaml, tags: ['netconf'] } diff --git a/test/integration/targets/net_l3_interface/tasks/netconf.yaml b/test/integration/targets/net_l3_interface/tasks/netconf.yaml new file mode 100644 index 00000000000..1286b354228 --- /dev/null +++ b/test/integration/targets/net_l3_interface/tasks/netconf.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all netconf test cases + find: + paths: "{{ role_path }}/tests/netconf" + patterns: "{{ testcase }}.yaml" + register: test_cases + delegate_to: localhost + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/net_l3_interface/tests/cli/basic.yaml b/test/integration/targets/net_l3_interface/tests/cli/basic.yaml new file mode 100644 index 00000000000..b1bec77f7ca --- /dev/null +++ b/test/integration/targets/net_l3_interface/tests/cli/basic.yaml @@ -0,0 +1,4 @@ +--- + +- include: "{{ role_path }}/tests/eos/basic.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'eos' diff --git a/test/integration/targets/net_l3_interface/tests/eos/basic.yaml b/test/integration/targets/net_l3_interface/tests/eos/basic.yaml new file mode 100644 index 00000000000..87457c3575d --- /dev/null +++ b/test/integration/targets/net_l3_interface/tests/eos/basic.yaml @@ -0,0 +1,2 @@ +--- +- debug: msg="START net_l3_interface eos/basic.yaml" \ No newline at end of file diff --git a/test/integration/targets/net_l3_interface/tests/junos/basic.yaml b/test/integration/targets/net_l3_interface/tests/junos/basic.yaml new file mode 100644 index 00000000000..0017721cc09 --- /dev/null +++ b/test/integration/targets/net_l3_interface/tests/junos/basic.yaml @@ -0,0 +1,82 @@ +--- +- debug: msg="START net_l3_interface junos/basic.yaml" + +- name: setup - remove interface address + net_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: absent + provider: "{{ netconf }}" + +- name: Configure interface address + net_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: present + provider: "{{ netconf }}" + register: result + +- name: Get running configuration + junos_rpc: + rpc: get-configuration + provider: "{{ netconf }}" + register: config + +- assert: + that: + - "result.changed == true" + - "'1.1.1.1/32' in config.xml" + - "'fd5d:12c9:2201:1::1/128' in config.xml" + - "'+ address 1.1.1.1/32;' in result.diff.prepared" + - "'+ address fd5d:12c9:2201:1::1/128;' in result.diff.prepared" + +- name: Configure interface address (idempotent) + net_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: present + provider: "{{ netconf }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: Delete interface address + net_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: absent + provider: "{{ netconf }}" + register: result + +- name: Get running configuration + junos_rpc: + rpc: get-configuration + provider: "{{ netconf }}" + register: config + +- assert: + that: + - "result.changed == true" + - "'1.1.1.1/32' not in config.xml" + - "'fd5d:12c9:2201:1::1/128' not in config.xml" + - "'- address 1.1.1.1/32;' in result.diff.prepared" + - "'- address fd5d:12c9:2201:1::1/128;' in result.diff.prepared" + +- name: Delete interface address (idempotent) + net_l3_interface: + name: ge-0/0/1 + ipv4: 1.1.1.1 + ipv6: fd5d:12c9:2201:1::1 + state: absent + provider: "{{ netconf }}" + register: result + +- assert: + that: + - "result.changed == false" diff --git a/test/integration/targets/net_l3_interface/tests/netconf/basic.yaml b/test/integration/targets/net_l3_interface/tests/netconf/basic.yaml new file mode 100644 index 00000000000..5ff7cf5af8e --- /dev/null +++ b/test/integration/targets/net_l3_interface/tests/netconf/basic.yaml @@ -0,0 +1,3 @@ +--- +- include: "{{ role_path }}/tests/junos/basic.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'junos'