New module eos_lacp_interfaces (#61349)

* Prepare for lacp_interfaces.

* Import module from builder

* Implement facts

* Implement config

* Add tests
pull/61359/merge
Nathaniel Case 5 years ago committed by GitHub
parent d987dca2b1
commit a7a053defc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,6 +19,8 @@ CHOICES = [
'!l3_interfaces',
'lacp',
'!lacp',
'lacp_interfaces',
'!lacp_interfaces',
'lag_interfaces',
'!lag_interfaces',
'lldp_global',

@ -0,0 +1,48 @@
#
# -*- 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_interfaces module
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class Lacp_interfacesArgs(object):
"""The arg spec for the eos_lacp_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {'elements': 'dict',
'options': {'name': {'type': 'str'},
'port_priority': {'type': 'int'},
'rate': {'choices': ['fast', 'normal'], 'type': 'str'}},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

@ -0,0 +1,215 @@
#
# -*- 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_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.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, dict_diff, param_list_to_dict
from ansible.module_utils.network.eos.facts.facts import Facts
class Lacp_interfaces(ConfigBase):
"""
The eos_lacp_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lacp_interfaces',
]
def __init__(self, module):
super(Lacp_interfaces, self).__init__(module)
def get_lacp_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)
lacp_interfaces_facts = facts['ansible_network_resources'].get('lacp_interfaces')
if not lacp_interfaces_facts:
return []
return lacp_interfaces_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_interfaces_facts = self.get_lacp_interfaces_facts()
commands.extend(self.set_config(existing_lacp_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
result['before'] = existing_lacp_interfaces_facts
if result['changed']:
result['after'] = changed_lacp_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_lacp_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_lacp_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 commands necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
want = param_list_to_dict(want)
have = param_list_to_dict(have)
if state == 'overridden':
commands = self._state_overridden(want, have)
elif 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 = []
for key, desired in want.items():
if key in have:
extant = have[key]
else:
extant = dict()
add_config = dict_diff(extant, desired)
del_config = dict_diff(desired, extant)
commands.extend(generate_commands(key, add_config, del_config))
return commands
@staticmethod
def _state_overridden(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
"""
commands = []
for key, extant in have.items():
if key in want:
desired = want[key]
else:
desired = dict()
add_config = dict_diff(extant, desired)
del_config = dict_diff(desired, extant)
commands.extend(generate_commands(key, add_config, del_config))
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 = []
for key, desired in want.items():
if key in have:
extant = have[key]
else:
extant = dict()
add_config = dict_diff(extant, desired)
commands.extend(generate_commands(key, add_config, {}))
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 = []
for key in want.keys():
desired = dict()
if key in have:
extant = have[key]
else:
extant = dict()
del_config = dict_diff(desired, extant)
commands.extend(generate_commands(key, {}, del_config))
return commands
def generate_commands(interface, to_set, to_remove):
commands = []
for key, value in to_set.items():
if value is None:
continue
commands.append("lacp {0} {1}".format(key.replace("_", "-"), value))
for key in to_remove.keys():
commands.append("no lacp {0}".format(key.replace("_", "-")))
if commands:
commands.insert(0, "interface {0}".format(interface))
return commands

@ -16,6 +16,7 @@ from ansible.module_utils.network.eos.facts.interfaces.interfaces import Interfa
from ansible.module_utils.network.eos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
from ansible.module_utils.network.eos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts
from ansible.module_utils.network.eos.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.eos.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts
from ansible.module_utils.network.eos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.eos.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.eos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
@ -34,6 +35,7 @@ FACT_RESOURCE_SUBSETS = dict(
l2_interfaces=L2_interfacesFacts,
l3_interfaces=L3_interfacesFacts,
lacp=LacpFacts,
lacp_interfaces=Lacp_interfacesFacts,
lag_interfaces=Lag_interfacesFacts,
lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts,

@ -0,0 +1,88 @@
#
# -*- 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_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
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.eos.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs
class Lacp_interfacesFacts(object):
""" The eos lacp_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Lacp_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 lacp_interfaces
: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 = 'interface'
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.append(obj)
ansible_facts['ansible_network_resources'].pop('lacp_interfaces', None)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
facts['lacp_interfaces'] = [utils.remove_empties(cfg) for cfg in 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['name'] = utils.parse_conf_arg(conf, 'interface')
config['port_priority'] = utils.parse_conf_arg(conf, 'port-priority')
config['rate'] = utils.parse_conf_arg(conf, 'rate')
return utils.remove_empties(config)

@ -50,8 +50,9 @@ options:
type: list
choices: [
'all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces',
'l3_interfaces', '!l3_interfaces', 'lacp', '!lacp', 'lag_interfaces', '!lag_interfaces',
'lldp_global', '!lldp_global', 'lldp_interfaces', '!lldp_interfaces', 'vlans', '!vlans',
'l3_interfaces', '!l3_interfaces', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces',
'lag_interfaces', '!lag_interfaces', 'lldp_global', '!lldp_global', 'lldp_interfaces', '!lldp_interfaces',
'vlans', '!vlans',
]
version_added: "2.9"
"""

@ -0,0 +1,250 @@
#!/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_interfaces
"""
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_interfaces
version_added: 2.9
short_description: Manage Link Aggregation Control Protocol (LACP) attributes of interfaces on Arista EOS devices.
description:
- This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces on Arista EOS devices.
author: Nathaniel Case (@Qalthos)
options:
config:
description: A dictionary of LACP interfaces options.
type: list
elements: dict
suboptions:
name:
description:
- Full name of the interface (i.e. Ethernet1).
type: str
port_priority:
description:
- LACP port priority for the interface. Range 1-65535.
type: int
rate:
description:
- Rate at which PDUs are sent by LACP.
At fast rate LACP is transmitted once every 1 second.
At normal rate LACP is transmitted every 30 seconds after the link is bundled.
type: str
choices: ['fast', 'normal']
state:
description:
- The state the configuration should be left in.
type: str
choices:
- merged
- replaced
- overridden
- deleted
default: merged
"""
EXAMPLES = """
# Using merged
#
#
# ------------
# Before state
# ------------
#
#
# veos#show run | section ^interface
# interface Ethernet1
# lacp port-priority 30
# interface Ethernet2
# lacp rate fast
- name: Merge provided configuration with device configuration
eos_lacp_interfaces:
config:
- name: Ethernet1
rate: fast
- name: Ethernet2
rate: normal
state: merged
#
# -----------
# After state
# -----------
#
# veos#show run | section ^interface
# interface Ethernet1
# lacp port-priority 30
# lacp rate fast
# interface Ethernet2
# Using replaced
#
#
# ------------
# Before state
# ------------
#
#
# veos#show run | section ^interface
# interface Ethernet1
# lacp port-priority 30
# interface Ethernet2
# lacp rate fast
- name: Replace existing LACP configuration of specified interfaces with provided configuration
eos_lacp_interfaces:
config:
- name: Ethernet1
rate: fast
state: replaced
#
# -----------
# After state
# -----------
#
# veos#show run | section ^interface
# interface Ethernet1
# lacp rate fast
# interface Ethernet2
# lacp rate fast
# Using overridden
#
#
# ------------
# Before state
# ------------
#
#
# veos#show run | section ^interface
# interface Ethernet1
# lacp port-priority 30
# interface Ethernet2
# lacp rate fast
- name: Override the LACP configuration of all the interfaces with provided configuration
eos_lacp_interfaces:
config:
- name: Ethernet1
rate: fast
state: overridden
#
# -----------
# After state
#
#
# veos#show run | section ^interface
# interface Ethernet1
# lacp rate fast
# interface Ethernet2
# Using deleted
#
#
# ------------
# Before state
# ------------
#
#
# veos#show run | section ^interface
# interface Ethernet1
# lacp port-priority 30
# interface Ethernet2
# lacp rate fast
- name: Delete LACP attributes of given interfaces (or all interfaces if none specified).
eos_lacp_interfaces:
state: deleted
#
# -----------
# After state
# -----------
#
# veos#show run | section ^interface
# interface Ethernet1
# interface Ethernet2
"""
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.
commands:
description: The set of commands pushed to the remote device.
returned: always
type: list
sample: ['interface Ethernet1', 'lacp rate fast']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.eos.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs
from ansible.module_utils.network.eos.config.lacp_interfaces.lacp_interfaces import Lacp_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Lacp_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Lacp_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

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

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

@ -0,0 +1,39 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
other_config:
- name: Ethernet2
rate: fast
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- name: Returns vlans to default parameters
eos_lacp_interfaces:
config: "{{ config }}"
state: deleted
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp_interfaces|symmetric_difference(result.after) == []"
- set_fact:
expected_config: "{{ other_config }}"
- assert:
that:
- "expected_config|symmetric_difference(ansible_facts.network_resources.lacp_interfaces) == []"

@ -0,0 +1,43 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
rate: fast
- name: Ethernet2
rate: normal
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- name: Merge provided configuration with device configuration
eos_lacp_interfaces:
config: "{{ config }}"
state: merged
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp_interfaces|symmetric_difference(result.after) == []"
- set_fact:
expected_config:
- name: Ethernet1
port_priority: 30
rate: fast
- assert:
that:
- "expected_config|symmetric_difference(ansible_facts.network_resources.lacp_interfaces) == []"

@ -0,0 +1,37 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
rate: fast
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- name: Overrides device configuration of all vlans with provided configuration
eos_lacp_interfaces:
config: "{{ config }}"
state: overridden
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp_interfaces|symmetric_difference(result.after) == []"
- set_fact:
expected_config: "{{ config }}"
- assert:
that:
- "expected_config|symmetric_difference(ansible_facts.network_resources.lacp_interfaces) == []"

@ -0,0 +1,40 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
rate: fast
other_config:
- name: Ethernet2
rate: fast
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- name: Replaces device configuration of listed vlans with provided configuration
eos_lacp_interfaces:
config: "{{ config }}"
state: replaced
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.lacp_interfaces|symmetric_difference(result.after) == []"
- set_fact:
expected_config: "{{ config }} + {{ other_config }}"
- assert:
that:
- "expected_config|symmetric_difference(ansible_facts.network_resources.lacp_interfaces) == []"

@ -0,0 +1,26 @@
---
- name: Reset initial config
cli_config:
config: |
interface Ethernet1
lacp port-priority 30
lacp rate normal
interface Ethernet2
no lacp port-priority
lacp rate fast
become: yes
- eos_facts:
gather_network_resources: lacp_interfaces
become: yes
- set_fact:
expected_config:
- name: Ethernet1
port_priority: 30
- name: Ethernet2
rate: fast
- assert:
that:
- "expected_config|symmetric_difference(ansible_facts.network_resources.lacp_interfaces) == []"
Loading…
Cancel
Save