Add [junos_lacp] model (#59705)

* Add [junos_lacp] model

*  Add new resource module junos_lacp.
*  Targets model https://github.com/ansible-network/resource_module_models/pull/28

* Fix CI issues

* Fix CI failures
This commit is contained in:
Ganesh Nalawade 2019-08-12 13:54:44 +05:30 committed by GitHub
parent 84d89190c7
commit 8cbfa75038
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 735 additions and 2 deletions

View file

@ -9,6 +9,7 @@ The arg spec for the junos facts module.
CHOICES = [ CHOICES = [
'all', 'all',
'interfaces', 'interfaces',
'lacp',
'lacp_interfaces', 'lacp_interfaces',
'lag_interfaces', 'lag_interfaces',
'l2_interfaces', 'l2_interfaces',

View file

@ -0,0 +1,47 @@
#
# -*- 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_lacp module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class LacpArgs(object):
"""The arg spec for the junos_lacp module
"""
def __init__(self, **kwargs):
pass
argument_spec = {'config': {'type': 'dict',
'options': {'link_protection': {'choices': ['revertive',
'non-revertive'],
'type': 'str'},
'system_priority': {'type': 'int'}},
},
'state': {'choices': ['merged', 'replaced', 'deleted'],
'default': 'merged',
'type': 'str'}}

View file

@ -0,0 +1,185 @@
#
# -*- 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_lacp 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.netconf import build_root_xml_node, build_child_xml_node, build_subtree
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
class Lacp(ConfigBase):
"""
The junos_lacp class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lacp',
]
def __init__(self, module):
super(Lacp, self).__init__(module)
def get_lacp_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)
lacp_facts = facts['ansible_network_resources'].get('lacp')
if not lacp_facts:
return {}
return lacp_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
existing_lacp_facts = self.get_lacp_facts()
config_xmls = self.set_config(existing_lacp_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_lacp_facts = self.get_lacp_facts()
result['before'] = existing_lacp_facts
if result['changed']:
result['after'] = changed_lacp_facts
return result
def set_config(self, existing_lacp_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_lacp_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('chassis')
ethernet_ele = build_subtree(root, 'aggregated-devices/ethernet')
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:
ethernet_ele.append(xml)
return tostring(root)
def _state_replaced(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
"""
lacp_xml = []
lacp_xml.extend(self._state_deleted(want, have))
lacp_xml.extend(self._state_merged(want, have))
return lacp_xml
def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
lacp_xml = []
lacp_xml.extend(self._state_deleted(want, have))
lacp_xml.extend(self._state_merged(want, have))
return lacp_xml
def _state_merged(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
"""
lacp_xml = []
lacp_root = build_root_xml_node('lacp')
build_child_xml_node(lacp_root, 'system-priority', want.get('system_priority'))
if want.get('link_protection') == 'non-revertive':
build_subtree(lacp_root, 'link-protection/non-revertive')
elif want.get('link_protection') == 'revertive':
link_root = build_child_xml_node(lacp_root, 'link-protection')
build_child_xml_node(link_root, 'non-revertive', None, {'delete': 'delete'})
lacp_xml.append(lacp_root)
return lacp_xml
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
lacp_xml = []
lacp_root = build_root_xml_node('lacp')
build_child_xml_node(lacp_root, 'system-priority', None, {'delete': 'delete'})
element = build_child_xml_node(lacp_root, 'link-protection', None, {'delete': 'delete'})
build_child_xml_node(element, 'non-revertive', None, {'delete': 'delete'})
lacp_xml.append(lacp_root)
return lacp_xml

View file

@ -13,6 +13,7 @@ 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.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.legacy.base import Default, Hardware, Config, Interfaces, OFacts, HAS_PYEZ
from ansible.module_utils.network.junos.facts.interfaces.interfaces import InterfacesFacts from ansible.module_utils.network.junos.facts.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.junos.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.junos.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts from ansible.module_utils.network.junos.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts
from ansible.module_utils.network.junos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts from ansible.module_utils.network.junos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.junos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts from ansible.module_utils.network.junos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts
@ -28,6 +29,7 @@ FACT_LEGACY_SUBSETS = dict(
) )
FACT_RESOURCE_SUBSETS = dict( FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts, interfaces=InterfacesFacts,
lacp=LacpFacts,
lacp_interfaces=Lacp_interfacesFacts, lacp_interfaces=Lacp_interfacesFacts,
lag_interfaces=Lag_interfacesFacts, lag_interfaces=Lag_interfacesFacts,
l2_interfaces=L2_interfacesFacts, l2_interfaces=L2_interfacesFacts,

View file

@ -0,0 +1,95 @@
#
# -*- 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 lacp 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.lacp.lacp import LacpArgs
from ansible.module_utils.six import string_types
try:
from lxml import etree
HAS_LXML = True
except ImportError:
HAS_LXML = False
class LacpFacts(object):
""" The junos lacp fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = LacpArgs.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>
<chassis>
<aggregated-devices>
<ethernet>
<lacp>
</lacp>
</ethernet>
</aggregated-devices>
</chassis>
</configuration>
"""
data = connection.get_configuration(filter=config_filter)
if isinstance(data, string_types):
data = etree.fromstring(to_bytes(data, errors='surrogate_then_replace'))
facts = {}
config = deepcopy(self.generated_spec)
resources = data.xpath('configuration/chassis/aggregated-devices/ethernet/lacp')
if resources:
lacp_root = resources[0]
config['system_priority'] = utils.get_xml_conf_arg(lacp_root, 'system-priority')
if utils.get_xml_conf_arg(lacp_root, 'link-protection/non-revertive', data='tag'):
config['link_protection'] = "non-revertive"
elif utils.get_xml_conf_arg(lacp_root, 'link-protection'):
config['link_protection'] = "revertive"
params = utils.validate_config(self.argument_spec, {'config': utils.remove_empties(config)})
facts['lacp'] = {}
facts['lacp'].update(utils.remove_empties(params['config']))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts

View file

@ -63,7 +63,7 @@ options:
to a given subset. Possible values for this argument include to a given subset. Possible values for this argument include
all and the resources like interfaces, vlans etc. all and the resources like interfaces, vlans etc.
Can specify a list of values to include a larger subset. Can specify a list of values to include a larger subset.
choices: ['all', 'interfaces', 'lacp_interfaces', 'lag_interfaces', 'l2_interfaces', 'l3_interfaces', 'lldp_interfaces', 'vlans'] choices: ['all', 'interfaces', 'lacp', 'lacp_interfaces', 'lag_interfaces', 'l2_interfaces', 'l3_interfaces', 'lldp_interfaces', 'vlans']
required: false required: false
version_added: "2.9" version_added: "2.9"
requirements: requirements:

View file

@ -0,0 +1,189 @@
#!/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_lacp
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: junos_lacp
version_added: 2.9
short_description: Manage Global Link Aggregation Control Protocol (LACP) on Juniper Junos devices
description: This module provides declarative management of global LACP on Juniper Junos network devices.
author: Ganesh Nalawade (@ganeshrn)
options:
config:
description: A dictionary of LACP global options
type: dict
suboptions:
system_priority:
description:
- LACP priority for the system.
type: int
link_protection:
description:
- Enable LACP link-protection for the system. If the value is set to C(non-revertive)
it will not revert links when a better priority link comes up. By default the link will
be reverted.
type: str
choices: ['revertive', 'non-revertive']
state:
description:
- The state the configuration should be left in
type: str
choices:
- merged
- replaced
- deleted
default: merged
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.1R1.
- 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 chassis aggregated-devices ethernet lacp
# system-priority 63;
# link-protection {
# non-revertive;
# }
- name: Delete global LACP attributes
junos_lacp:
state: deleted
# After state:
# ------------
# user@junos01# show chassis aggregated-devices ethernet lacp
#
# Using merged
# Before state:
# -------------
# user@junos01# show chassis aggregated-devices ethernet lacp
#
- name: Merge global LACP attributes
junos_lacp:
config:
system_priority: 63
link_protection: revertive
state: merged
# After state:
# ------------
# user@junos01# show chassis aggregated-devices ethernet lacp
# system-priority 63;
# link-protection {
# non-revertive;
# }
# Using replaced
# Before state:
# -------------
# user@junos01# show chassis aggregated-devices ethernet lacp
# system-priority 63;
# link-protection {
# non-revertive;
# }
- name: Replace global LACP attributes
junos_lacp:
config:
system_priority: 30
link_protection: non-revertive
state: replaced
# After state:
# ------------
# user@junos01# show chassis aggregated-devices ethernet lacp
# system-priority 30;
# link-protection;
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: dict
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: dict
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.lacp.lacp import LacpArgs
from ansible.module_utils.network.junos.config.lacp.lacp import Lacp
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=LacpArgs.argument_spec,
supports_check_mode=True)
result = Lacp(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

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,11 @@
---
- debug:
msg: "Start junos_lacp deleted remove lacp config ansible_connection={{ ansible_connection }}"
- name: "Setup - remove lacp config"
junos_config:
lines:
- delete chassis aggregated-devices ethernet lacp
- debug:
msg: "End junos_lacp deleted remove lacp config ansible_connection={{ ansible_connection }}"

View file

@ -0,0 +1,43 @@
---
- debug:
msg: "START junos_lacp deleted lacp tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_deleted_output: []
- block:
- name: Merge global LACP attributes
junos_lacp:
config:
system_priority: 63
link_protection: revertive
state: merged
register: result
- name: Delete global lacp attributes
junos_lacp: &deleted
config:
state: deleted
register: result
- name: Assert the configuration is reflected on host
assert:
that:
- "{{ result['after'] == {} }}"
- name: Delete the provided interface configuration from running configuration (IDEMPOTENT)
junos_lacp: *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_lacp deleted lacp integration tests on connection={{ ansible_connection }}"

View file

@ -0,0 +1,39 @@
---
- debug:
msg: "START junos_lacp merged integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_merged_output:
- system_priority: 63
link_protection: revertive
- block:
- name: Merge the provided configuration with the exisiting running configuration
junos_lacp: &merged
config:
system_priority: 63
link_protection: revertive
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_lacp: *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_lacp merged integration tests on connection={{ ansible_connection }}"

View file

@ -0,0 +1,44 @@
---
- debug:
msg: "START junos_lacp replaced integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_replaced_output:
- system_priority: 73
- block:
- name: Set initial config
junos_lacp:
config:
system_priority: 73
link_protection: revertive
state: replaced
- name: Replace the provided configuration with the exisiting running configuration
junos_lacp: &replaced
config:
system_priority: 73
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_lacp: *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_lacp replaced integration tests on connection={{ ansible_connection }}"

View file

@ -0,0 +1,53 @@
---
- debug:
msg: "START junos_lacp RTT integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- set_fact:
expected_revert_output:
- system_priority: 63
link_protection: non-revertive
- block:
- name: Apply the provided configuration (base config)
junos_lacp:
config:
system_priority: 63
link_protection: non-revertive
state: merged
- name: Gather interfaces facts
junos_facts:
gather_subset:
- default
gather_network_resources:
- lacp
- name: Apply the provided configuration (config to be reverted)
junos_lacp: &replaced
config:
system_priority: 73
link_protection: revertive
state: replaced
register: result
- name: Assert that changes were applied
assert:
that: "result['changed'] == true"
- name: Revert back to base config using facts round trip
junos_lacp:
config: "{{ ansible_facts['network_resources']['lacp'] }}"
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
- debug:
msg: "END junos_lacp RTT integration tests on connection={{ ansible_connection }}"

View file

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