New module eos_lacp (#60698)

* Initial builder import

* Add tests

* Implement facts & config
This commit is contained in:
Nathaniel Case 2019-08-20 16:11:31 -04:00 committed by GitHub
parent 543401f83b
commit 764a81fce7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 619 additions and 1 deletions

View file

@ -15,6 +15,8 @@ CHOICES = [
'!interfaces',
'l2_interfaces',
'!l2_interfaces',
'lacp',
'!lacp',
'lag_interfaces',
'!lag_interfaces',
'vlans',

View file

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

View file

@ -0,0 +1,161 @@
# -*- 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 eos_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.utils import to_list, dict_diff
from ansible.module_utils.network.eos.facts.facts import Facts
class Lacp(ConfigBase):
"""
The eos_lacp class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lacp',
]
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}
warnings = list()
commands = list()
existing_lacp_facts = self.get_lacp_facts()
commands.extend(self.set_config(existing_lacp_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lacp_facts = self.get_lacp_facts()
result['before'] = existing_lacp_facts
if result['changed']:
result['after'] = changed_lacp_facts
result['warnings'] = warnings
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'] or {}
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 commands necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
if state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged':
commands = self._state_merged(want, have)
elif state == 'replaced':
commands = self._state_replaced(want, have)
return commands
@staticmethod
def _state_replaced(want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
to_set = dict_diff(have, want)
if 'system' in to_set:
system = to_set['system']
if 'priority' in system:
commands.append('lacp system-priority {0}'.format(system['priority']))
to_del = dict_diff(want, have)
if 'system' in to_del:
system = to_del['system']
system_set = to_set.get('system', {})
if 'priority' in system and 'priority' not in system_set:
commands.append('no lacp system-priority')
return commands
@staticmethod
def _state_merged(want, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
to_set = dict_diff(have, want)
if 'system' in to_set:
system = to_set['system']
if 'priority' in system:
commands.append('lacp system-priority {0}'.format(system['priority']))
return commands
@staticmethod
def _state_deleted(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
"""
commands = []
to_del = dict_diff(want, have)
if 'system' in to_del:
system = to_del['system']
if 'priority' in system:
commands.append('no lacp system-priority')
return commands

View file

@ -14,6 +14,7 @@ from ansible.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.eos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.eos.facts.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.eos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
from ansible.module_utils.network.eos.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.eos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.eos.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.eos.facts.legacy.base import Default, Hardware, Config, Interfaces
@ -28,6 +29,7 @@ FACT_LEGACY_SUBSETS = dict(
FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts,
l2_interfaces=L2_interfacesFacts,
lacp=LacpFacts,
lag_interfaces=Lag_interfacesFacts,
vlans=VlansFacts,
)

View file

@ -0,0 +1,86 @@
# -*- 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 eos 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
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.eos.argspec.lacp.lacp import LacpArgs
class LacpFacts(object):
""" The eos 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 lacp
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = connection.get('show running-config | section ^lacp')
# split the config into instances of the resource
resource_delim = 'lacp'
find_pattern = r'(?:^|\n)%s.*?(?=(?:^|\n)%s|$)' % (resource_delim,
resource_delim)
resources = [p.strip() for p in re.findall(find_pattern, data, re.DOTALL)]
objs = {}
for resource in resources:
if resource:
obj = self.render_config(self.generated_spec, resource)
if obj:
objs.update(obj)
ansible_facts['ansible_network_resources'].pop('lacp', None)
facts = {'lacp': {}}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
facts['lacp'] = utils.remove_empties(params['config'])
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 configuration
:rtype: dictionary
:returns: The generated config
"""
config = deepcopy(spec)
config['system']['priority'] = utils.parse_conf_arg(conf, 'system-priority')
return utils.remove_empties(config)

View file

@ -48,7 +48,10 @@ options:
specific subset should not be collected.
required: false
type: list
choices: ['all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces', 'lag_interfaces', '!lag_interfaces', 'vlans', '!vlans']
choices: [
'all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces',
'lacp', '!lacp', 'lag_interfaces', '!lag_interfaces', 'vlans', '!vlans',
]
version_added: "2.9"
"""

View file

@ -0,0 +1,178 @@
#!/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 eos_lacp
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_lacp
version_added: 2.9
short_description: Manage Global Link Aggregation Control Protocol (LACP) on Arista EOS devices.
description:
- This module manages Global Link Aggregation Control Protocol (LACP) on Arista EOS devices.
author: Nathaniel Case (@Qalthos)
notes:
- Tested against Arista EOS 4.20.10M
- This module works with connection C(network_cli). See the
L(EOS Platform Options,../network/user_guide/platform_eos.html).
options:
config:
description: LACP global options.
type: dict
suboptions:
system:
description: LACP system options.
type: dict
suboptions:
priority:
description:
- The system priority to use in LACP negotiations.
- Lower value is higher priority.
- Refer to vendor documentation for valid values.
type: int
state:
description:
- The state the configuration should be left in.
type: str
choices:
- merged
- replaced
- deleted
default: merged
"""
EXAMPLES = """
# Using merged
# Before state:
# -------------
# veos# show running-config | include lacp
# lacp system-priority 10
- name: Merge provided global LACP attributes with device attributes
eos_lacp:
config:
system:
priority: 20
state: merged
# After state:
# ------------
# veos# show running-config | include lacp
# lacp system-priority 20
#
# Using replaced
# Before state:
# -------------
# veos# show running-config | include lacp
# lacp system-priority 10
- name: Replace device global LACP attributes with provided attributes
eos_lacp:
config:
system:
priority: 20
state: replaced
# After state:
# ------------
# veos# show running-config | include lacp
# lacp system-priority 20
#
# Using deleted
# Before state:
# -------------
# veos# show running-config | include lacp
# lacp system-priority 10
- name: Delete global LACP attributes
eos_lacp:
state: deleted
# After state:
# ------------
# veos# show running-config | include lacp
#
"""
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.
commands:
description: The set of commands pushed to the remote device.
returned: always
type: list
sample: ['lacp system-priority 10']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.eos.argspec.lacp.lacp import LacpArgs
from ansible.module_utils.network.eos.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,2 @@
---
testcase: "*"

View file

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

View file

@ -0,0 +1,16 @@
---
- name: collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
delegate_to: localhost
register: test_cases
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=network_cli)
include: "{{ test_case_to_run }} ansible_connection=network_cli"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

View file

@ -0,0 +1,28 @@
---
- include_tasks: reset_config.yml
- eos_facts:
gather_network_resources: lacp
become: yes
- name: Returns lacp to default parameters
eos_lacp:
state: deleted
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp == result.before"
- eos_facts:
gather_network_resources: lacp
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp == result.after"
- assert:
that:
- "ansible_facts.network_resources.lacp == {}"

View file

@ -0,0 +1,34 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
system:
priority: 20
- eos_facts:
gather_network_resources: lacp
become: yes
- name: Merge provided lacp configuration with device configuration
eos_lacp:
config: "{{ config }}"
state: merged
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp == result.before"
- eos_facts:
gather_network_resources: lacp
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp == result.after"
- assert:
that:
- "config == ansible_facts.network_resources.lacp"

View file

@ -0,0 +1,34 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
system:
priority: 20
- eos_facts:
gather_network_resources: lacp
become: yes
- name: Replaces device lacp configuration with provided configuration
eos_lacp:
config: "{{ config }}"
state: replaced
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp == result.before"
- eos_facts:
gather_network_resources: lacp
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp == result.after"
- assert:
that:
- "config == ansible_facts.network_resources.lacp"

View file

@ -0,0 +1,19 @@
---
- name: Reset initial config
cli_config:
config: |
lacp system-priority 10
become: yes
- eos_facts:
gather_network_resources: lacp
become: yes
- set_fact:
expected_config:
system:
priority: 10
- assert:
that:
- "expected_config == ansible_facts.network_resources.lacp"