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 0000000000..31507dc3d7
--- /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 3ae5d4d9bf..de6e0ad92d 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 0000000000..5f709c5aac
--- /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 0000000000..cc27f174fd
--- /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 0000000000..1286b35422
--- /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 0000000000..593308acbe
--- /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 415c99d8b1..af08869c92 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 0000000000..1286b35422
--- /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 0000000000..bbf6d27212
--- /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 0000000000..5ff7cf5af8
--- /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'