Add junos_static_routes module (#65239)

This commit adds a new network resource module for static routes on
junos devices.

Signed-off-by: Daniel Mellado <dmellado@redhat.com>
This commit is contained in:
Daniel Mellado 2019-12-04 12:59:56 +01:00 committed by GitHub
parent 520a505caf
commit 9404384985
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1030 additions and 8 deletions

View file

@ -0,0 +1,73 @@
#
# -*- 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_static_routes module
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class Static_routesArgs(object): # pylint: disable=R0903
"""The arg spec for the junos_static_routes module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'address_families': {
'elements': 'dict',
'options': {
'afi': {
'choices': ['ipv4', 'ipv6'],
'required': True,
'type': 'str'},
'routes': {'elements': 'dict',
'options': {
'dest': {
'type': 'str'},
'metric': {
'type': 'int'},
'next_hop': {
'elements': 'dict',
'options': {
'forward_router_address': {
'type': 'str'}},
'type': 'list'}},
'type': 'list'}},
'type': 'list'},
'vrf': {
'type': 'str'}},
'type': 'list'},
'state': {
'choices': ['merged',
'replaced',
'overridden',
'deleted'],
'default': 'merged',
'type': 'str'}} # pylint: disable=C0301

View file

@ -0,0 +1,235 @@
#
# -*- 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_static_routes 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 Static_routes(ConfigBase):
"""
The junos_static_routes class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'static_routes',
]
def __init__(self, module):
super(Static_routes, self).__init__(module)
def get_static_routes_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)
static_routes_facts = facts['ansible_network_resources'].get('static_routes')
if not static_routes_facts:
return []
return static_routes_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
existing_static_routes_facts = self.get_static_routes_facts()
config_xmls = self.set_config(existing_static_routes_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_static_routes_facts = self.get_static_routes_facts()
result['before'] = existing_static_routes_facts
if result['changed']:
result['after'] = changed_static_routes_facts
result['warnings'] = warnings
return result
def set_config(self, existing_static_routes_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_static_routes_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
"""
state = self._module.params['state']
root = build_root_xml_node('configuration')
routing_options = build_child_xml_node(root, 'routing-options')
routing_instances = build_child_xml_node(root, 'routing-instances')
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:
if xml['root_type'] == 'routing-options':
routing_options.append(xml['static_route_xml'])
elif xml['root_type'] == 'routing-instances':
routing_instances.append(xml['static_route_xml'])
return [tostring(xml) for xml in root.getchildren()]
def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the xml necessary to migrate the current configuration
to the desired configuration
"""
static_route_xml = []
static_route_xml.extend(self._state_deleted(want, have))
static_route_xml.extend(self._state_merged(want, have))
return static_route_xml
def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the xml necessary to migrate the current configuration
to the desired configuration
"""
static_route_xml = []
static_route_xml.extend(self._state_deleted(have, have))
static_route_xml.extend(self._state_merged(want, have))
return static_route_xml
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the xml necessary to migrate the current configuration
to the desired configuration
"""
if not want:
want = have
static_route_xml = self._state_merged(want, have, delete={'delete': 'delete'})
return static_route_xml
def _state_merged(self, want, have, delete=None):
""" The command generator when state is merged
:rtype: A list
:returns: the xml necessary to migrate the current configuration
to the desired configuration
"""
static_route_xml = []
root_type = ""
vrf_name = None
for config in want:
if config.get('vrf'):
vrf_name = config['vrf']
if vrf_name:
root_type = 'routing-instances'
instance = build_root_xml_node('instance')
build_child_xml_node(instance, 'name', vrf_name)
routing_options = build_child_xml_node(instance, 'routing-options')
else:
root_type = 'routing-options'
for afi in config['address_families']:
protocol = afi['afi']
if protocol == 'ipv6':
if vrf_name:
rib_route_root = build_child_xml_node(routing_options, 'rib')
build_child_xml_node(rib_route_root, 'name', vrf_name + '.inet6.0')
else:
rib_route_root = build_root_xml_node('rib')
build_child_xml_node(rib_route_root, 'name', 'inet6.0')
static_route_root = build_child_xml_node(rib_route_root, 'static')
elif protocol == 'ipv4':
if vrf_name:
static_route_root = build_child_xml_node(routing_options, 'static')
else:
static_route_root = build_root_xml_node('static')
if afi.get('routes'):
for route in afi['routes']:
route_node = build_child_xml_node(static_route_root, 'route')
if delete:
route_node.attrib.update(delete)
if route.get('dest'):
build_child_xml_node(route_node, 'name', route['dest'])
if not delete:
if route.get('metric'):
build_child_xml_node(route_node, 'metric', route['metric'])
if route.get('next_hop'):
for hop in route['next_hop']:
build_child_xml_node(route_node, 'next-hop', hop['forward_router_address'])
elif delete:
if vrf_name:
instance.attrib.update(delete)
static_route_root.attrib.update(delete)
if vrf_name:
static_route_xml.append({'root_type': root_type, 'static_route_xml': instance})
else:
if protocol == 'ipv6':
static_route_xml.append({'root_type': root_type, 'static_route_xml': rib_route_root})
else:
static_route_xml.append({'root_type': root_type, 'static_route_xml': static_route_root})
return static_route_xml

View file

@ -21,6 +21,7 @@ from ansible.module_utils.network.junos.facts.lldp_global.lldp_global import Lld
from ansible.module_utils.network.junos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible.module_utils.network.junos.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.junos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
from ansible.module_utils.network.junos.facts.static_routes.static_routes import Static_routesFacts
FACT_LEGACY_SUBSETS = dict(
default=Default,
@ -38,6 +39,7 @@ FACT_RESOURCE_SUBSETS = dict(
lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts,
vlans=VlansFacts,
static_routes=Static_routesFacts
)

View file

@ -0,0 +1,152 @@
#
# -*- 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 static_routes 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.static_routes.static_routes import Static_routesArgs
from ansible.module_utils.six import string_types
try:
from lxml import etree
HAS_LXML = True
except ImportError:
HAS_LXML = False
try:
import xmltodict
HAS_XMLTODICT = True
except ImportError:
HAS_XMLTODICT = False
class Static_routesFacts(object):
""" The junos static_routes fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Static_routesArgs.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 static_routes
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not HAS_LXML:
self._module.fail_json(msg='lxml is not installed.')
if not HAS_XMLTODICT:
self._module.fail_json(msg='xmltodict is not installed.')
if not data:
config_filter = """
<configuration>
<routing-instances/>
<routing-options/>
</configuration>
"""
data = connection.get_configuration(filter=config_filter)
if isinstance(data, string_types):
data = etree.fromstring(to_bytes(data,
errors='surrogate_then_replace'))
resources = data.xpath('configuration/routing-options')
vrf_resources = data.xpath('configuration/routing-instances')
resources.extend(vrf_resources)
objs = []
for resource in resources:
if resource is not None:
xml = self._get_xml_dict(resource)
obj = self.render_config(self.generated_spec, xml)
if obj:
objs.append(obj)
facts = {}
if objs:
facts['static_routes'] = []
params = utils.validate_config(self.argument_spec,
{'config': objs})
for cfg in params['config']:
facts['static_routes'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def _get_xml_dict(self, xml_root):
xml_dict = xmltodict.parse(etree.tostring(xml_root), dict_constructor=dict)
return xml_dict
def _create_route_dict(self, afi, route_path):
routes_dict = {'afi': afi,
'routes': []}
if isinstance(route_path, dict):
route_path = [route_path]
for route in route_path:
route_dict = {}
route_dict['dest'] = route['name']
if route.get('metric'):
route_dict['metric'] = route['metric']['metric-value']
route_dict['next_hop'] = []
route_dict['next_hop'].append({'forward_router_address': route['next-hop']})
routes_dict['routes'].append(route_dict)
return routes_dict
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)
routes = []
config['address_families'] = []
if conf.get('routing-options'):
if conf['routing-options'].get('rib'):
if conf['routing-options'].get('rib').get('name') == 'inet6.0':
if conf['routing-options'].get('rib').get('static'):
route_path = conf['routing-options']['rib']['static'].get('route')
routes.append(self._create_route_dict('ipv6', route_path))
if conf['routing-options'].get('static'):
route_path = conf['routing-options']['static'].get('route')
routes.append(self._create_route_dict('ipv4', route_path))
if conf.get('routing-instances'):
config['vrf'] = conf['routing-instances']['instance']['name']
if conf['routing-instances'].get('instance').get('routing-options').get('rib').get('name') == config['vrf'] + '.inet6.0':
if conf['routing-instances']['instance']['routing-options']['rib'].get('static'):
route_path = conf['routing-instances']['instance']['routing-options']['rib']['static'].get('route')
routes.append(self._create_route_dict('ipv6', route_path))
if conf['routing-instances'].get('instance').get('routing-options').get('static'):
route_path = conf['routing-instances']['instance']['routing-options']['static'].get('route')
routes.append(self._create_route_dict('ipv4', route_path))
config['address_families'].extend(routes)
return utils.remove_empties(config)

View file

@ -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 static IP routes on Juniper JUNOS network devices
description:
- This module provides declarative management of static
IP routes on Juniper JUNOS network devices.
deprecated:
removed_in: "2.13"
why: Updated modules released with more functionality
alternative: Use M(junos_static_routes) instead.
options:
address:
description:

View file

@ -0,0 +1,282 @@
#!/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_static_routes
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: junos_static_routes
version_added: '2.10'
short_description: Manage static routes on Juniper JUNOS devices
description: This module provides declarative management of static routes on Juniper JUNOS devices
author: Daniel Mellado (@dmellado)
requirements:
- ncclient (>=v0.6.4)
- xmltodict (>=0.12)
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 static routes options
type: list
elements: dict
suboptions:
vrf:
description:
- Virtual Routing and Forwarding (VRF) name
type: str
address_families:
description:
- Address family to use for the static routes
elements: dict
type: list
suboptions:
afi:
description:
- afi to use for the static routes
type: str
required: true
choices:
- ipv4
- ipv6
routes:
description:
- Static route configuration
elements: dict
type: list
suboptions:
dest:
description:
- Static route destination including prefix
type: str
next_hop:
elements: dict
type: list
description:
- Next hop to destination
suboptions:
forward_router_address:
description:
- List of next hops
type: str
metric:
description:
- Metric value for the static route
type: int
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 routing-options
# static {
# route 192.168.47.0/24 next-hop 172.16.1.2;
# route 192.168.16.0/24 next-hop 172.16.1.2;
# route 10.200.16.75/24 next-hop 10.200.16.2;
# }
- name: Delete provided configuration (default operation is merge)
junos_static_routes:
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 10.200.16.75/24
next_hop:
- forward_router_address: 10.200.16.2
state: deleted
# After state:
# ------------
#
# admin# show routing-options
# static {
# route 192.168.47.0/24 next-hop 172.16.1.2;
# route 192.168.16.0/24 next-hop 172.16.1.2;
# }
# Using merged
# Before state
# ------------
#
# admin# show routing-options
# static {
# route 192.168.47.0/24 next-hop 172.16.1.2;
# route 192.168.16.0/24 next-hop 172.16.1.2;
# }
- name: Merge provided configuration with device configuration (default operation is merge)
junos_static_routes:
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 10.200.16.75/24
next_hop:
- forward_router_address: 10.200.16.2
state: merged
# After state:
# ------------
#
# admin# show routing-options
# static {
# route 192.168.47.0/24 next-hop 172.16.1.2;
# route 192.168.16.0/24 next-hop 172.16.1.2;
# route 10.200.16.75/24 next-hop 10.200.16.2;
# }
# Using overridden
# Before state
# ------------
#
# admin# show routing-options
# static {
# route 192.168.47.0/24 next-hop 172.16.1.2;
# route 192.168.16.0/24 next-hop 172.16.0.1;
# }
- name: Override provided configuration with device configuration (default operation is merge)
junos_static_routes:
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 10.200.16.75/24
next_hop:
- forward_router_address: 10.200.16.2
state: overridden
# After state:
# ------------
#
# admin# show routing-options
# static {
# route 10.200.16.75/24 next-hop 10.200.16.2;
# }
# Using replaced
# Before state
# ------------
#
# admin# show routing-options
# static {
# route 192.168.47.0/24 next-hop 172.16.1.2;
# route 192.168.16.0/24 next-hop 172.16.1.2;
# }
- name: Replace provided configuration with device configuration (default operation is merge)
junos_static_routes:
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.168.47.0/24
next_hop:
- forward_router_address: 10.200.16.2
state: replaced
# After state:
# ------------
#
# admin# show routing-options
# static {
# route 192.168.47.0/24 next-hop 10.200.16.2;
# route 192.168.16.0/24 next-hop 172.16.1.2;
# }
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: str
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: str
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.static_routes.static_routes import Static_routesArgs
from ansible.module_utils.network.junos.config.static_routes.static_routes import Static_routes
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Static_routesArgs.argument_spec,
supports_check_mode=True)
result = Static_routes(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -3,3 +3,4 @@ scp # for Cisco ios
selectors2 # for ncclient
ncclient # for Junos
jxmlease # for Junos
xmltodict # for Junos

View file

@ -0,0 +1,3 @@
---
testcase: "[^_].*"
test_items: []

View file

@ -0,0 +1,2 @@
dependencies:
# - prepare_junos_tests

View file

@ -0,0 +1,2 @@
---
- { include: netconf.yaml, tags: ['netconf'] }

View file

@ -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

View file

@ -0,0 +1,21 @@
---
- debug:
msg: "Start junos_static_routes base config ansible_connection={{ ansible_connection }}"
- name: Configure base static_routes
junos_static_routes:
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.168.0.0/24
next_hop:
- forward_router_address: 192.168.0.1
- afi: 'ipv6'
routes:
- dest: 2001:db8::5/128
next_hop:
- forward_router_address: 2001:db8:0:1:2a0:a502:0:19da
- debug:
msg: "End junos_static_routes base config ansible_connection={{ ansible_connection }}"

View file

@ -0,0 +1,14 @@
---
- debug:
msg: "Start junos_static_routes teardown ansible_connection={{ ansible_connection }}"
- name: Remove static route config
junos_static_routes:
config:
- address_families:
- afi: 'ipv4'
- afi: 'ipv6'
state: deleted
- debug:
msg: "End junos_static_routes teardown ansible_connection={{ ansible_connection }}"

View file

@ -0,0 +1,37 @@
---
- debug:
msg: "START junos_static_routes deleted integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _base_config.yaml
- block:
- name: Delete the provided configuration with the exisiting running configuration
junos_static_routes: &deleted
config:
- address_families:
- afi: 'ipv4'
- afi: 'ipv6'
state: deleted
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- not result.after
debugger: on_failed
- name: Delete the provided configuration with the existing running configuration (IDEMPOTENT)
junos_static_routes: *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_static_routes deleted integration tests on connection={{ ansible_connection }}"

View file

@ -0,0 +1,72 @@
---
- debug:
msg: "START junos_static_routes merged integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_merged_output:
- address_families:
- afi: 'ipv6'
routes:
- dest: 2001:db8::5/128
next_hop:
- forward_router_address: 2001:db8:0:1:2a0:a502:0:19da
- dest: ::/0
next_hop:
- forward_router_address: 2001:db8:0:1:2a0:a502:0:19da
- afi: 'ipv4'
routes:
- dest: 192.168.0.0/24
next_hop:
- forward_router_address: 192.168.0.1
- dest: 192.168.1.0/24
metric: 2
next_hop:
- forward_router_address: 192.168.1.1
- block:
- name: Merge the provided configuration with the exisiting running configuration
junos_static_routes: &merged
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.168.0.0/24
next_hop:
- forward_router_address: 192.168.0.1
- dest: 192.168.1.0/24
next_hop:
- forward_router_address: 192.168.1.1
metric: 2
- afi: 'ipv6'
routes:
- dest: 2001:db8::5/128
next_hop:
- forward_router_address: 2001:db8:0:1:2a0:a502:0:19da
- dest: ::/0
next_hop:
- forward_router_address: 2001:db8:0:1:2a0:a502:0:19da
state: merged
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ expected_merged_output | symmetric_difference(result['after']) |length == 0 }}"
debugger: on_failed
- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
junos_static_routes: *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_static_routes merged integration tests on connection={{ ansible_connection }}"

View file

@ -0,0 +1,51 @@
---
- debug:
msg: "START junos_static_routes overridden integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _base_config.yaml
- set_fact:
expected_overridden_output:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.168.20.0/24
next_hop:
- forward_router_address: 192.168.20.1
metric: 10
- block:
- name: Override the provided configuration with the exisiting running configuration
junos_static_routes: &overridden
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.168.20.0/24
next_hop:
- forward_router_address: 192.168.20.1
metric: 10
state: overridden
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ expected_overridden_output | symmetric_difference(result['after']) |length == 0 }}"
debugger: on_failed
- name: Override the provided configuration with the existing running configuration (IDEMPOTENT)
junos_static_routes: *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_static_routes overridden integration tests on connection={{ ansible_connection }}"

View file

@ -0,0 +1,54 @@
---
- debug:
msg: "START junos_static_routes overridden integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _base_config.yaml
- set_fact:
expected_overridden_output:
- address_families:
- afi: 'ipv6'
routes:
- dest: 2001:db8::5/128
next_hop:
- forward_router_address: 2001:db8:0:1:2a0:a502:0:19da
- afi: 'ipv4'
routes:
- dest: 192.168.0.0/24
next_hop:
- forward_router_address: 192.168.20.1
- block:
- name: Replace the provided configuration with the exisiting running configuration
junos_static_routes: &overridden
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.168.0.0/24
next_hop:
- forward_router_address: 192.168.20.1
state: replaced
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ expected_overridden_output | symmetric_difference(result['after']) |length == 0 }}"
debugger: on_failed
- name: Override the provided configuration with the existing running configuration (IDEMPOTENT)
junos_static_routes: *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_static_routes overridden integration tests on connection={{ ansible_connection }}"

View file

@ -3994,13 +3994,13 @@ lib/ansible/modules/network/junos/junos_scp.py validate-modules:doc-missing-type
lib/ansible/modules/network/junos/junos_scp.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/junos/junos_scp.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/network/junos/junos_scp.py validate-modules:undocumented-parameter
lib/ansible/modules/network/junos/junos_static_route.py validate-modules:doc-choices-do-not-match-spec
lib/ansible/modules/network/junos/junos_static_route.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/network/junos/junos_static_route.py validate-modules:doc-missing-type
lib/ansible/modules/network/junos/junos_static_route.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/junos/junos_static_route.py validate-modules:missing-suboption-docs
lib/ansible/modules/network/junos/junos_static_route.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/network/junos/junos_static_route.py validate-modules:undocumented-parameter
lib/ansible/modules/network/junos/_junos_static_route.py validate-modules:doc-choices-do-not-match-spec
lib/ansible/modules/network/junos/_junos_static_route.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/network/junos/_junos_static_route.py validate-modules:doc-missing-type
lib/ansible/modules/network/junos/_junos_static_route.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/junos/_junos_static_route.py validate-modules:missing-suboption-docs
lib/ansible/modules/network/junos/_junos_static_route.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/network/junos/_junos_static_route.py validate-modules:undocumented-parameter
lib/ansible/modules/network/junos/junos_system.py validate-modules:doc-choices-do-not-match-spec
lib/ansible/modules/network/junos/junos_system.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/network/junos/junos_system.py validate-modules:doc-missing-type