Add junos_interfaces resource module (#58871)

* Add junos_interfaces resource module [junos_interfaces]

*  Add new resource module junos_interfaces.
*  Targets model https://github.com/ansible-network/resource_module_models/pull/8
*  Depcreate junos_interface module

* Fix CI failures

* Fix integration test failures

* Fix CI failures

* Fix review comments
pull/59006/head
Ganesh Nalawade 5 years ago committed by GitHub
parent 6327a6114b
commit f09bd91ad0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -31,9 +31,9 @@ from ansible.module_utils._text import to_text, to_bytes
from ansible.module_utils.connection import Connection, ConnectionError
try:
from ncclient.xml_ import NCElement
from ncclient.xml_ import NCElement, new_ele, sub_ele
HAS_NCCLIENT = True
except ImportError:
except (ImportError, AttributeError):
HAS_NCCLIENT = False
try:
@ -139,3 +139,24 @@ def remove_namespaces(data):
raise ImportError("ncclient is required but does not appear to be installed. "
"It can be installed using `pip install ncclient`")
return NCElement(data, transform_reply()).data_xml
def build_root_xml_node(tag):
return new_ele(tag)
def build_child_xml_node(parent, tag, text=None, attrib=None):
element = sub_ele(parent, tag)
if text:
element.text = to_text(text)
if attrib:
element.attrib.update(attrib)
return element
def build_subtree(parent, path):
element = parent
for field in path.split('/'):
sub_element = build_child_xml_node(element, field)
element = sub_element
return element

@ -8,6 +8,7 @@ The arg spec for the junos facts module.
"""
CHOICES = [
'all',
'interfaces'
]

@ -0,0 +1,56 @@
#
# -*- 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_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class InterfacesArgs(object):
"""The arg spec for the junos_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {'config': {'elements': 'dict',
'options': {'description': {'type': 'str'},
'duplex': {'choices': ['automatic',
'full-duplex',
'half-duplex'],
'type': 'str'},
'enable': {'default': True, 'type': 'bool'},
'hold_time': {'options': {'down': {'type': 'int'},
'up': {'type': 'int'}},
'required_together': [['down', 'up']],
'type': 'dict'},
'mtu': {'type': 'int'},
'name': {'required': True, 'type': 'str'},
'speed': {'type': 'str'}},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

@ -0,0 +1,238 @@
#
# -*- 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_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.utils import to_list
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.junos.junos import locked_config, load_config, commit_configuration, discard_changes, tostring
from ansible.module_utils.network.junos.facts.facts import Facts
from ansible.module_utils.network.common.netconf import build_root_xml_node, build_child_xml_node
class Interfaces(ConfigBase):
"""
The junos_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'interfaces',
]
def __init__(self, module):
super(Interfaces, self).__init__(module)
def get_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)
interfaces_facts = facts['ansible_network_resources'].get('interfaces')
if not interfaces_facts:
return []
return interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
existing_interfaces_facts = self.get_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, [])
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_interfaces_facts()
result['before'] = existing_interfaces_facts
if result['changed']:
result['after'] = changed_interfaces_facts
return result
def set_config(self, existing_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_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 _state_replaced(self, want, have):
""" The xml configuration generator when state is replaced
:rtype: A list
:returns: the xml configuration 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 configuration generator when state is overridden
:rtype: A list
:returns: the xml configuration necessary to migrate the current configuration
to the desired configuration
"""
interface_xmls_obj = []
# replace interface config with data in want
interface_xmls_obj.extend(self._state_replaced(want, have))
# delete interface config if interface in have not present in want
delete_obj = []
for have_obj in have:
for want_obj in want:
if have_obj['name'] == want_obj['name']:
break
else:
delete_obj.append(have_obj)
if delete_obj:
interface_xmls_obj.extend(self._state_deleted(delete_obj, have))
return interface_xmls_obj
def _state_merged(self, want, have):
""" The xml configuration generator when state is merged
:rtype: A list
:returns: the xml configuration necessary to merge the provided into
the current configuration
"""
intf_xml = []
for config in want:
intf = build_root_xml_node('interface')
build_child_xml_node(intf, 'name', config['name'])
intf_fields = ['description', 'speed']
if not config['name'].startswith('fxp'):
intf_fields.append('mtu')
for field in intf_fields:
if config.get(field):
build_child_xml_node(intf, field, config[field])
if config.get('duplex'):
build_child_xml_node(intf, 'link-mode', config['duplex'])
if config.get('enable') is False:
build_child_xml_node(intf, 'disable')
holdtime = config.get('hold_time')
if holdtime:
holdtime_ele = build_child_xml_node(intf, 'hold-time')
for holdtime_field in ['up', 'down']:
build_child_xml_node(holdtime_ele, holdtime_field, holdtime.get(holdtime_field, ''))
intf_xml.append(intf)
return intf_xml
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 = []
intf_obj = want
if not intf_obj:
# delete base interfaces attribute from all the existing interface
intf_obj = have
for config in intf_obj:
intf = build_root_xml_node('interface')
build_child_xml_node(intf, 'name', config['name'])
intf_fields = ['description']
if not config['name'].startswith('lo'):
intf_fields.append('speed')
if not any([config['name'].startswith('fxp'), config['name'].startswith('lo')]):
intf_fields.append('mtu')
for field in intf_fields:
build_child_xml_node(intf, field, None, {'delete': 'delete'})
if not config['name'].startswith('lo'):
build_child_xml_node(intf, 'link-mode', None, {'delete': 'delete'})
build_child_xml_node(intf, 'disable', None, {'delete': 'delete'})
holdtime_ele = build_child_xml_node(intf, 'hold-time')
for holdtime_field in ['up', 'down']:
build_child_xml_node(holdtime_ele, holdtime_field, None, {'delete': 'delete'})
intf_xml.append(intf)
return intf_xml

@ -12,6 +12,7 @@ calls the appropriate facts gathering function
from ansible.module_utils.network.junos.argspec.facts.facts import FactsArgs
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
FACT_LEGACY_SUBSETS = dict(
default=Default,
@ -20,6 +21,7 @@ FACT_LEGACY_SUBSETS = dict(
interfaces=Interfaces,
)
FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts,
)

@ -0,0 +1,110 @@
#
# -*- 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 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._text import to_bytes
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.junos.argspec.interfaces.interfaces import InterfacesArgs
from ansible.module_utils.six import string_types
try:
from lxml import etree
HAS_LXML = True
except ImportError:
HAS_LXML = False
class InterfacesFacts(object):
""" The junos interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = 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 interfaces
:param connection: the device connection
:param data: previously collected configuration as lxml ElementTree root instance
or valid xml sting
:rtype: dictionary
:returns: facts
"""
if not HAS_LXML:
self._module.fail_json(msg='lxml is not installed.')
if not data:
config_filter = """
<configuration>
<interfaces/>
</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/interfaces/interface')
objs = []
for resource in resources:
if resource is not None:
obj = self.render_config(self.generated_spec, resource)
if obj:
objs.append(obj)
facts = {}
if objs:
facts['interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['interfaces'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
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 ElementTree instance of configuration object
:rtype: dictionary
:returns: The generated config
"""
config = deepcopy(spec)
config['name'] = utils.get_xml_conf_arg(conf, 'name')
config['description'] = utils.get_xml_conf_arg(conf, 'description')
mtu = utils.get_xml_conf_arg(conf, 'mtu')
config['mtu'] = int(mtu) if mtu else None
config['speed'] = utils.get_xml_conf_arg(conf, 'speed')
config['duplex'] = utils.get_xml_conf_arg(conf, 'link-mode')
config['hold_time']['down'] = utils.get_xml_conf_arg(conf, 'hold-time/down')
config['hold_time']['up'] = utils.get_xml_conf_arg(conf, 'hold-time/up')
disable = utils.get_xml_conf_arg(conf, 'disable', data='tag')
if disable:
config['enable'] = False
else:
config['enable'] = True
return utils.remove_empties(config)

@ -5,45 +5,3 @@
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# utils
from ansible.module_utils._text import to_text
try:
from ncclient.xml_ import to_ele, to_xml, new_ele, sub_ele
HAS_NCCLIENT = True
except (ImportError, AttributeError):
HAS_NCCLIENT = False
def build_root_xml_node(tag):
return new_ele(tag)
def build_child_xml_node(parent, tag, text=None, attrib=None):
element = sub_ele(parent, tag)
if text:
element.text = to_text(text)
if attrib:
element.attrib.update(attrib)
return element
def build_subtree(parent, path):
element = parent
for field in path.split('/'):
sub_element = build_child_xml_node(element, field)
element = sub_element
return element
def _handle_field_replace(root, field, have, want, tag=None):
tag = field if not tag else tag
want_value = want.get(field) if want else None
have_value = have.get(field) if have else None
if have_value:
if want_value:
if want_value != have_value:
build_child_xml_node(root, tag, want_value)
else:
build_child_xml_node(root, tag, None, {'delete': 'delete'})
elif want_value:
build_child_xml_node(root, tag, want_value)

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

@ -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']
choices: ['all', 'interfaces']
required: false
version_added: "2.9"
requirements:

@ -0,0 +1,327 @@
#!/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_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_interfaces
version_added: 2.9
short_description: Manages interface attributes of Juniper Junos OS network devices.
description: This module manages the interfaces on Juniper Junos OS network devices.
author: Ganesh Nalawade (@ganeshrn)
options:
config:
description: The provided configuration
type: list
suboptions:
name:
description:
- Full name of interface, e.g. ge-0/0/0.
type: str
required: True
description:
description:
- Interface description.
type: str
duplex:
description:
- Interface link status. Applicable for Ethernet interfaces only, either in half duplex,
full duplex or in automatic state which negotiates the duplex automatically.
type: str
choices: ['automatic', 'full-duplex', 'half-duplex']
enable:
default: True
description:
- Administrative state of the interface.
- Set the value to C(true) to administratively enable the interface or C(false) to disable it.
type: bool
hold_time:
description:
- The hold time for given interface name.
type: dict
suboptions:
down:
description:
- The link down hold time in milliseconds.
type: int
up:
description:
- The link up hold time in milliseconds.
type: int
mtu:
description:
- MTU for a specific interface.
- Applicable for Ethernet interfaces only.
type: int
speed:
description:
- Interface link speed. Applicable for Ethernet interfaces only.
type: int
state:
choices:
- merged
- replaced
- overridden
- deleted
default: merged
description:
- The state the configuration should be left in
type: str
requirements:
- ncclient (>=v0.6.4)
notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 18.4R1.
- This module works with connection C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
"""
EXAMPLES = """
# Using deleted
# Before state:
# -------------
# user@junos01# show interfaces
# ge-0/0/1 {
# description "Configured by Ansible-1";
# speed 1g;
# mtu 1800
# }
# ge-0/0/2 {
# description "Configured by Ansible-2";
# ether-options {
# auto-negotiation;
# }
# }
- name: "Delete given options for the interface (Note: This won't delete the interface itself if any other values are configured for interface)"
junos_interfaces:
config:
- name: ge-0/0/1
description: 'Configured by Ansible-1'
speed: 1g
mtu: 1800
- name: ge-0/0/2
description: 'Configured by Ansible -2'
state: deleted
# After state:
# ------------
# user@junos01# show interfaces
# ge-0/0/2 {
# ether-options {
# auto-negotiation;
# }
# }
# Using merged
# Before state:
# -------------
# user@junos01# show interfaces
# ge-0/0/1 {
# description "test interface";
# speed 1g;
# }
- name: "Merge provided configuration with device configuration (default operation is merge)"
junos_interfaces:
config:
- name: ge-0/0/1
description: 'Configured by Ansible-1'
enable: True
mtu: 1800
- name: ge-0/0/2
description: 'Configured by Ansible-2'
enable: False
state: merged
# After state:
# ------------
# user@junos01# show interfaces
# ge-0/0/1 {
# description "Configured by Ansible-1";
# speed 1g;
# mtu 1800
# }
# ge-0/0/2 {
# disable;
# description "Configured by Ansible-2";
# }
# Using overriden
# Before state:
# -------------
# user@junos01# show interfaces
# ge-0/0/1 {
# description "Configured by Ansible-1";
# speed 1g;
# mtu 1800
# }
# ge-0/0/2 {
# disable;
# description "Configured by Ansible-2";
# ether-options {
# auto-negotiation;
# }
# }
# ge-0/0/11 {
# description "Configured by Ansible-11";
# }
- name: "Override device configuration of all interfaces with provided configuration"
junos_interfaces:
config:
- name: ge-0/0/2
description: 'Configured by Ansible-2'
enable: False
mtu: 2800
- name: ge-0/0/3
description: 'Configured by Ansible-3'
state: overriden
# After state:
# ------------
# user@junos01# show interfaces
# ge-0/0/2 {
# disable;
# description "Configured by Ansible-2";
# mtu 2800
# }
# ge-0/0/3 {
# description "Configured by Ansible-3";
# }
# Using replaced
# Before state:
# -------------
# user@junos01# show interfaces
# ge-0/0/1 {
# description "Configured by Ansible-1";
# speed 1g;
# mtu 1800
# }
# ge-0/0/2 {
# disable;
# mtu 1800;
# speed 1g;
# description "Configured by Ansible-2";
# ether-options {
# auto-negotiation;
# }
# }
# ge-0/0/11 {
# description "Configured by Ansible-11";
# }
- name: "Replaces device configuration of listed interfaces with provided configuration"
junos_interfaces:
config:
- name: ge-0/0/2
description: 'Configured by Ansible-2'
enable: False
mtu: 2800
- name: ge-0/0/3
description: 'Configured by Ansible-3'
state: replaced
# After state:
# ------------
# user@junos01# show interfaces
# ge-0/0/1 {
# description "Configured by Ansible-1";
# speed 1g;
# mtu 1800
# }
# ge-0/0/2 {
# disable;
# description "Configured by Ansible-2";
# mtu 2800
# }
# ge-0/0/3 {
# description "Configured by Ansible-3";
# }
# ge-0/0/11 {
# description "Configured by Ansible-11";
# }
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: list
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: list
sample: >
The configuration returned will always be in the same format
of the parameters above.
xml:
description: The set of xml rpc payload pushed to the remote device.
returned: always
type: list
sample: ['xml 1', 'xml 2', 'xml 3']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.junos.argspec.interfaces.interfaces import InterfacesArgs
from ansible.module_utils.network.junos.config.interfaces.interfaces import Interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec,
supports_check_mode=True)
result = Interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

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

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

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

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

@ -0,0 +1,13 @@
---
- debug:
msg: "Start junos_nterfaces deleted remove interface config ansible_connection={{ ansible_connection }}"
- name: Remove interface config
junos_config:
lines:
- delete interfaces ge-0/0/1
- delete interfaces ge-0/0/2
- delete interfaces ge-0/0/3
- debug:
msg: "End junos_nterfaces deleted remove interface config ansible_connection={{ ansible_connection }}"

@ -0,0 +1,87 @@
---
- debug:
msg: "START junos_interfaces deleted integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_deleted_output:
- name: fxp0
enable: true
- name: lo0
enable: true
- block:
- name: Configure initial state for interface
junos_interfaces: &initial
config:
- name: ge-0/0/1
description: "Configured by Ansible - Interface 1"
mtu: 1024
speed: 100m
enable: False
duplex: full-duplex
hold_time:
up: 2000
down: 2200
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2"
mtu: 2048
speed: 10m
hold_time:
up: 3000
down: 3200
state: merged
register: result
- name: Delete the provided interface configuration from running configuration
junos_interfaces: &deleted
config:
- name: ge-0/0/1
- name: ge-0/0/2
state: deleted
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ expected_deleted_output | symmetric_difference(result['after']) |length == 0 }}"
- name: Delete the provided interface configuration from running configuration (IDEMPOTENT)
junos_interfaces: *deleted
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
- name: Configure initial state for interface
junos_interfaces: *initial
register: result
- name: Delete the all interface configuration from running configuration
junos_interfaces:
state: deleted
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ expected_deleted_output | symmetric_difference(result['after']) |length == 0 }}"
- name: Delete the all interface configuration from running configuration (IDEMPOTENT)
junos_interfaces:
state: 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_interfaces deleted integration tests on connection={{ ansible_connection }}"

@ -0,0 +1,72 @@
---
- debug:
msg: "START junos_interfaces merged integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_merged_output:
- name: ge-0/0/1
description: "Configured by Ansible - Interface 1"
mtu: 1024
speed: 100m
enable: false
duplex: full-duplex
hold_time:
up: 2000
down: 2200
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2"
mtu: 2048
speed: 10m
enable: true
hold_time:
up: 3000
down: 3200
- name: fxp0
enable: true
- name: lo0
enable: true
- block:
- name: Merge the provided configuration with the exisiting running configuration
junos_interfaces: &merged
config:
- name: ge-0/0/1
description: "Configured by Ansible - Interface 1"
mtu: 1024
speed: 100m
enable: False
duplex: full-duplex
hold_time:
up: 2000
down: 2200
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2"
mtu: 2048
speed: 10m
hold_time:
up: 3000
down: 3200
state: merged
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ expected_merged_output | symmetric_difference(result['after']) |length == 0 }}"
- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
junos_interfaces: *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_interfaces merged integration tests on connection={{ ansible_connection }}"

@ -0,0 +1,66 @@
---
- debug:
msg: "START junos_interfaces overridden integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_overridden_output:
- name: ge-0/0/1
description: "Overridden by Ansible - Interface 1"
enable: true
- name: fxp0
enable: true
- name: lo0
enable: true
- block:
- name: Configure initial state for interface
junos_interfaces:
config:
- name: ge-0/0/1
description: "Configured by Ansible - Interface 1"
mtu: 1024
speed: 100m
enable: False
duplex: full-duplex
hold_time:
up: 2000
down: 2200
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2"
mtu: 2048
speed: 10m
hold_time:
up: 3000
down: 3200
state: merged
register: result
- name: Override the provided configuration with the exisiting running configuration
junos_interfaces: &overridden
config:
- name: ge-0/0/1
description: "Overridden by Ansible - Interface 1"
state: overridden
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ expected_overridden_output | symmetric_difference(result['after']) |length == 0 }}"
- name: Override the provided configuration with the existing running configuration (IDEMPOTENT)
junos_interfaces: *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_interfaces overridden integration tests on connection={{ ansible_connection }}"

@ -0,0 +1,79 @@
---
- debug:
msg: "START junos_interfaces replaced integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_replaced_output:
- name: ge-0/0/1
description: "Replaced by Ansible - Interface 1"
mtu: 2048
speed: 10m
enable: true
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2"
mtu: 2048
speed: 10m
enable: true
hold_time:
up: 3000
down: 3200
- name: fxp0
enable: true
- name: lo0
enable: true
- block:
- name: Configure initial state for interface
junos_interfaces:
config:
- name: ge-0/0/1
description: "Configured by Ansible - Interface 1"
mtu: 1024
speed: 100m
enable: False
duplex: full-duplex
hold_time:
up: 2000
down: 2200
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2"
mtu: 2048
speed: 10m
hold_time:
up: 3000
down: 3200
state: merged
register: result
- name: Replace the provided configuration with the exisiting running configuration
junos_interfaces: &replaced
config:
- name: ge-0/0/1
description: "Replaced by Ansible - Interface 1"
mtu: 2048
speed: 10m
enable: True
state: replaced
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ expected_replaced_output | symmetric_difference(result['after']) |length == 0 }}"
- name: Replace the provided configuration with the existing running configuration (IDEMPOTENT)
junos_interfaces: *replaced
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_interfaces replaced integration tests on connection={{ ansible_connection }}"

@ -0,0 +1,100 @@
---
- debug:
msg: "START junos_interfaces round trip integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_revert_output:
- name: ge-0/0/1
description: "Configured by Ansible - Interface 1"
mtu: 1024
speed: 100m
enable: false
duplex: full-duplex
hold_time:
up: 2000
down: 2200
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2"
mtu: 2048
speed: 10m
enable: true
hold_time:
up: 3000
down: 3200
- name: fxp0
enable: true
- name: lo0
enable: true
- block:
- name: Apply the provided configuration (base config)
junos_interfaces:
config:
- name: ge-0/0/1
description: "Configured by Ansible - Interface 1"
mtu: 1024
speed: 100m
enable: False
duplex: full-duplex
hold_time:
up: 2000
down: 2200
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2"
mtu: 2048
speed: 10m
enable: True
hold_time:
up: 3000
down: 3200
state: merged
register: base_config
- name: Gather interfaces facts
junos_facts:
gather_subset:
- default
gather_network_resources:
- interfaces
- name: Apply the provided configuration (config to be reverted)
junos_interfaces:
config:
- name: ge-0/0/1
description: "Configured by Ansible - Interface 1 modified"
mtu: 3048
speed: 10m
enable: True
duplex: half-duplex
hold_time:
up: 3000
down: 2200
- name: ge-0/0/2
description: "Configured by Ansible - Interface 2 modified"
mtu: 4048
speed: 100m
enable: False
hold_time:
up: 4000
down: 5200
state: merged
register: result
- name: Assert that changes were applied
assert:
that: "result['changed'] == true"
- name: Revert back to base config using facts round trip
junos_interfaces:
config: "{{ ansible_facts['network_resources']['interfaces'] }}"
state: replaced
register: revert
- name: Assert that config was reverted
assert:
that: "{{ expected_revert_output | symmetric_difference(revert['after']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -2530,9 +2530,9 @@ lib/ansible/modules/network/junos/junos_config.py E337
lib/ansible/modules/network/junos/junos_config.py E338
lib/ansible/modules/network/junos/junos_facts.py E337
lib/ansible/modules/network/junos/junos_facts.py E338
lib/ansible/modules/network/junos/junos_interface.py E324
lib/ansible/modules/network/junos/junos_interface.py E337
lib/ansible/modules/network/junos/junos_interface.py E338
lib/ansible/modules/network/junos/_junos_interface.py E324
lib/ansible/modules/network/junos/_junos_interface.py E337
lib/ansible/modules/network/junos/_junos_interface.py E338
lib/ansible/modules/network/junos/junos_l2_interface.py E337
lib/ansible/modules/network/junos/junos_l2_interface.py E338
lib/ansible/modules/network/junos/junos_l3_interface.py E337

Loading…
Cancel
Save