From ba60ac04fc48c06aa0f7f3ee9ac6aa7da916f7bb Mon Sep 17 00:00:00 2001 From: Ganesh Nalawade Date: Fri, 7 Jul 2017 13:25:14 +0530 Subject: [PATCH] Implementation of junos_static_route module (#26501) * Implementation of junos_static_route module * junos implementation of net_static_route * integration test for junos_static_route * integration test for junos net_static_route * Minor change * Doc change * Fix CI issue --- .../network/junos/junos_static_route.py | 218 ++++++++++++++++++ test/integration/junos.yaml | 9 +- .../junos_static_route/defaults/main.yaml | 2 + .../junos_static_route/tasks/main.yaml | 2 + .../junos_static_route/tasks/netconf.yaml | 16 ++ .../tests/netconf/basic.yaml | 142 ++++++++++++ .../targets/net_static_route/tasks/main.yaml | 1 + .../net_static_route/tasks/netconf.yaml | 16 ++ .../net_static_route/tests/junos/basic.yaml | 71 ++++++ .../net_static_route/tests/netconf/basic.yaml | 3 + 10 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 lib/ansible/modules/network/junos/junos_static_route.py create mode 100644 test/integration/targets/junos_static_route/defaults/main.yaml create mode 100644 test/integration/targets/junos_static_route/tasks/main.yaml create mode 100644 test/integration/targets/junos_static_route/tasks/netconf.yaml create mode 100644 test/integration/targets/junos_static_route/tests/netconf/basic.yaml create mode 100644 test/integration/targets/net_static_route/tasks/netconf.yaml create mode 100644 test/integration/targets/net_static_route/tests/junos/basic.yaml create mode 100644 test/integration/targets/net_static_route/tests/netconf/basic.yaml diff --git a/lib/ansible/modules/network/junos/junos_static_route.py b/lib/ansible/modules/network/junos/junos_static_route.py new file mode 100644 index 00000000000..31507dc3d7d --- /dev/null +++ b/lib/ansible/modules/network/junos/junos_static_route.py @@ -0,0 +1,218 @@ +#!/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_static_route +version_added: "2.4" +author: "Ganesh Nalawade (@ganeshrn)" +short_description: Manage static IP routes on Juniper JUNOS network devices +description: + - This module provides declarative management of static + IP routes on Juniper JUNOS network devices. +options: + address: + description: + - Network address with prefix of the static route. + required: true + aliases: ['prefix'] + next_hop: + description: + - Next hop IP of the static route. + required: true + qualified_next_hop: + description: + - Qualified next hop IP of the static route. Qualified next hops allow + to associate preference with a particular next-hop address. + preference: + description: + - Global admin preference of the static route. + aliases: ['admin_distance'] + qualified_preference: + description: + - Assign preference for qualified next hop. + collection: + description: List of static route definitions + purge: + description: + - Purge static routes not defined in the collections parameter. + default: no + state: + description: + - State of the static route 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: configure static route + junos_static_route: + address: 192.168.2.0/24 + next_hop: 10.0.0.1 + qualified_next_hop: 10.0.0.2 + qualified_preference: 3 + state: present + +- name: delete static route + junos_static_route: + address: 192.168.2.0/24 + state: absent + +- name: deactivate static route configuration + junos_static_route: + address: 192.168.2.0/24 + next_hop: 10.0.0.1 + qualified_next_hop: 10.0.0.2 + qualified_preference: 3 + state: present + active: False + +- name: activate static route configuration + junos_static_route: + address: 192.168.2.0/24 + next_hop: 10.0.0.1 + qualified_next_hop: 10.0.0.2 + qualified_preference: 3 + state: present + active: True +""" + +RETURN = """ +diff: + description: Configuration difference before and after applying change. + returned: when configuration is changed. + type: string + sample: > + [edit routing-options static] + route 2.2.2.0/24 { ... } + + route 4.4.4.0/24 { + next-hop 3.3.3.3; + qualified-next-hop 5.5.5.5 { + + preference 30; + } + + preference 10; + + } +""" +import collections + +from ansible.module_utils.junos import junos_argument_spec, check_args +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.junos import load_config, map_params_to_obj, map_obj_to_ele + +try: + from lxml.etree import tostring +except ImportError: + from xml.etree.ElementTree import tostring + +USE_PERSISTENT_CONNECTION = True + + +def validate_param_values(module, obj): + for key in obj: + # validate the param value (if validator func exists) + validator = globals().get('validate_%s' % key) + if callable(validator): + validator(module.params.get(key), module) + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + address=dict(required=True, type='str', aliases=['prefix']), + next_hop=dict(type='str'), + preference=dict(type='int', aliases=['admin_distance']), + qualified_next_hop=dict(type='str'), + qualified_preference=dict(type='int'), + collection=dict(type='list'), + purge=dict(type='bool'), + state=dict(default='present', choices=['present', 'absent']), + active=dict(default=True, type='bool') + ) + + argument_spec.update(junos_argument_spec) + required_one_of = [['collection', 'address']] + mutually_exclusive = [['collection', 'address']] + + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + if module.params['state'] == 'present': + if not module.params['address'] and module.params['next_hop']: + module.fail_json(msg="parameters are required together: ['address', 'next_hop']") + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + top = 'routing-options/static/route' + + param_to_xpath_map = collections.OrderedDict() + param_to_xpath_map.update([ + ('address', {'xpath': 'name', 'is_key': True}), + ('next_hop', 'next-hop'), + ('preference', 'preference/metric-value'), + ('qualified_next_hop', {'xpath': 'name', 'top': 'qualified-next-hop'}), + ('qualified_preference', {'xpath': 'preference', 'top': 'qualified-next-hop'}) + ]) + + validate_param_values(module, param_to_xpath_map) + + want = list() + want.append(map_params_to_obj(module, param_to_xpath_map)) + ele = map_obj_to_ele(module, want, top) + + kwargs = {'commit': not module.check_mode} + kwargs['action'] = 'replace' + + diff = load_config(module, tostring(ele), warnings, **kwargs) + + if diff: + result.update({ + 'changed': True, + 'diff': diff, + }) + + module.exit_json(**result) + +if __name__ == "__main__": + main() diff --git a/test/integration/junos.yaml b/test/integration/junos.yaml index 3ae5d4d9bfc..de6e0ad92d4 100644 --- a/test/integration/junos.yaml +++ b/test/integration/junos.yaml @@ -99,8 +99,15 @@ rescue: - set_fact: test_failed=true + - block: + - include_role: + name: junos_static_route + when: "limit_to in ['*', 'junos_static_route']" + rescue: + - set_fact: test_failed=true + ########### - name: Has any previous test failed? fail: msg: "One or more tests failed, check log for details" - when: test_failed \ No newline at end of file + when: test_failed diff --git a/test/integration/targets/junos_static_route/defaults/main.yaml b/test/integration/targets/junos_static_route/defaults/main.yaml new file mode 100644 index 00000000000..5f709c5aac1 --- /dev/null +++ b/test/integration/targets/junos_static_route/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/test/integration/targets/junos_static_route/tasks/main.yaml b/test/integration/targets/junos_static_route/tasks/main.yaml new file mode 100644 index 00000000000..cc27f174fd8 --- /dev/null +++ b/test/integration/targets/junos_static_route/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: netconf.yaml, tags: ['netconf'] } diff --git a/test/integration/targets/junos_static_route/tasks/netconf.yaml b/test/integration/targets/junos_static_route/tasks/netconf.yaml new file mode 100644 index 00000000000..1286b354228 --- /dev/null +++ b/test/integration/targets/junos_static_route/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_static_route/tests/netconf/basic.yaml b/test/integration/targets/junos_static_route/tests/netconf/basic.yaml new file mode 100644 index 00000000000..593308acbe9 --- /dev/null +++ b/test/integration/targets/junos_static_route/tests/netconf/basic.yaml @@ -0,0 +1,142 @@ +--- +- debug: msg="START junos_static_route netconf/basic.yaml" + +- name: setup - remove static route + junos_static_route: + address: 1.1.1.0/24 + state: absent + provider: "{{ netconf }}" + +- name: Confgiure static route + junos_static_route: + address: 1.1.1.0/24 + next_hop: 3.3.3.3 + preference: 10 + qualified_next_hop: 5.5.5.5 + qualified_preference: 30 + 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.0/24' in config.xml" + - "'3.3.3.3' in config.xml" + - "'' in config.xml" + - "'5.5.5.5' in config.xml" + - "'30' in config.xml" + - "'10' in config.xml" + +- name: Confgiure static route (idempotent) + junos_static_route: + address: 1.1.1.0/24 + next_hop: 3.3.3.3 + preference: 10 + qualified_next_hop: 5.5.5.5 + qualified_preference: 30 + state: present + provider: "{{ netconf }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: Deactivate static route + junos_static_route: + address: 1.1.1.0/24 + next_hop: 3.3.3.3 + preference: 10 + qualified_next_hop: 5.5.5.5 + qualified_preference: 30 + 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: route 1.1.1.0/24' in result.diff" + +- name: Activate static route + junos_static_route: + address: 1.1.1.0/24 + next_hop: 3.3.3.3 + preference: 10 + qualified_next_hop: 5.5.5.5 + qualified_preference: 30 + 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.0/24' in config.xml" + - "'3.3.3.3' in config.xml" + - "'' in config.xml" + - "'5.5.5.5' in config.xml" + - "'30' in config.xml" + - "'10' in config.xml" + - "'inactive: route 1.1.1.0/24' not in result.diff" + +- name: Delete static route + junos_static_route: + address: 1.1.1.0/24 + next_hop: 3.3.3.3 + preference: 10 + qualified_next_hop: 5.5.5.5 + qualified_preference: 30 + state: absent + 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.0/24' not in config.xml" + +- name: Delete static route (idempotent) + junos_static_route: + address: 1.1.1.0/24 + next_hop: 3.3.3.3 + preference: 10 + qualified_next_hop: 5.5.5.5 + qualified_preference: 30 + state: absent + active: True + provider: "{{ netconf }}" + register: result + +- assert: + that: + - "result.changed == false" diff --git a/test/integration/targets/net_static_route/tasks/main.yaml b/test/integration/targets/net_static_route/tasks/main.yaml index 415c99d8b12..af08869c922 100644 --- a/test/integration/targets/net_static_route/tasks/main.yaml +++ b/test/integration/targets/net_static_route/tasks/main.yaml @@ -1,2 +1,3 @@ --- - { include: cli.yaml, tags: ['cli'] } +- { include: netconf.yaml, tags: ['netconf'] } diff --git a/test/integration/targets/net_static_route/tasks/netconf.yaml b/test/integration/targets/net_static_route/tasks/netconf.yaml new file mode 100644 index 00000000000..1286b354228 --- /dev/null +++ b/test/integration/targets/net_static_route/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_static_route/tests/junos/basic.yaml b/test/integration/targets/net_static_route/tests/junos/basic.yaml new file mode 100644 index 00000000000..bbf6d27212c --- /dev/null +++ b/test/integration/targets/net_static_route/tests/junos/basic.yaml @@ -0,0 +1,71 @@ +--- +- debug: msg="START net_static_route junos/basic.yaml" + +- name: setup - remove static route + net_static_route: + address: 1.1.1.0/24 + state: absent + provider: "{{ netconf }}" + +- name: Confgiure static route + net_static_route: + prefix: 1.1.1.0/24 + next_hop: 3.3.3.3 + admin_distance: 10 + 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.0/24' in config.xml" + - "'3.3.3.3' in config.xml" + +- name: Confgiure static route (idempotent) + net_static_route: + prefix: 1.1.1.0/24 + next_hop: 3.3.3.3 + admin_distance: 10 + state: present + provider: "{{ netconf }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: Delete static route + net_static_route: + prefix: 1.1.1.0/24 + 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.0/24' not in config.xml" + +- name: Delete static route (idempotent) + net_static_route: + prefix: 1.1.1.0/24 + state: absent + provider: "{{ netconf }}" + register: result + +- assert: + that: + - "result.changed == false" diff --git a/test/integration/targets/net_static_route/tests/netconf/basic.yaml b/test/integration/targets/net_static_route/tests/netconf/basic.yaml new file mode 100644 index 00000000000..5ff7cf5af8e --- /dev/null +++ b/test/integration/targets/net_static_route/tests/netconf/basic.yaml @@ -0,0 +1,3 @@ +--- +- include: "{{ role_path }}/tests/junos/basic.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'junos'