New module eos_lldp_global (#60975)

* New module eos_lldp_global

* Implement facts

* Implement config, add tests

* Assorted fixes
This commit is contained in:
Nathaniel Case 2019-08-26 11:34:41 -04:00 committed by GitHub
parent 0b284c1431
commit a567a3fae0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 723 additions and 1 deletions

View file

@ -21,6 +21,8 @@ CHOICES = [
'!lacp',
'lag_interfaces',
'!lag_interfaces',
'lldp_global',
'!lldp_global',
'vlans',
'!vlans',
]

View file

@ -0,0 +1,58 @@
#
# -*- 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_lldp_global module
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class Lldp_globalArgs(object):
"""The arg spec for the eos_lldp_global module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'options': {
'holdtime': {'type': 'int'},
'reinit': {'type': 'int'},
'timer': {'type': 'int'},
'tlv_select': {
'options': {
'link_aggregation': {'type': 'bool'},
'management_address': {'type': 'bool'},
'max_frame_size': {'type': 'bool'},
'port_description': {'type': 'bool'},
'system_capabilities': {'type': 'bool'},
'system_description': {'type': 'bool'},
'system_name': {'type': 'bool'}},
'type': 'dict'}},
'type': 'dict'},
'state': {'choices': ['merged', 'replaced', 'deleted'], 'default': 'merged', 'type': 'str'}
}

View file

@ -0,0 +1,166 @@
#
# -*- 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_lldp_global 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 dict_diff, to_list
from ansible.module_utils.network.eos.facts.facts import Facts
class Lldp_global(ConfigBase):
"""
The eos_lldp_global class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lldp_global',
]
def __init__(self, module):
super(Lldp_global, self).__init__(module)
def get_lldp_global_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)
lldp_global_facts = facts['ansible_network_resources'].get('lldp_global')
if not lldp_global_facts:
return {}
return lldp_global_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_lldp_global_facts = self.get_lldp_global_facts()
commands.extend(self.set_config(existing_lldp_global_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lldp_global_facts = self.get_lldp_global_facts()
result['before'] = existing_lldp_global_facts
if result['changed']:
result['after'] = changed_lldp_global_facts
result['warnings'] = warnings
return result
def set_config(self, existing_lldp_global_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_lldp_global_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 = state_deleted(want, have)
elif state == 'merged':
commands = state_merged(want, have)
elif state == 'replaced':
commands = state_replaced(want, have)
return commands
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 = set()
# merged and deleted are likely to emit duplicate tlv-select commands
commands.update(state_merged(want, have))
commands.update(state_deleted(want, have))
return list(commands)
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)
tlv_options = to_set.pop("tlv_select", {})
for key, value in to_set.items():
commands.append("lldp {0} {1}".format(key, value))
for key, value in tlv_options.items():
device_option = key.replace("_", "-")
if value is True:
commands.append("lldp tlv-select {0}".format(device_option))
elif value is False:
commands.append("no lldp tlv-select {0}".format(device_option))
return commands
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_remove = dict_diff(want, have)
tlv_options = to_remove.pop("tlv_select", {})
for key in to_remove:
commands.append("no lldp {0}".format(key))
for key, value in tlv_options.items():
device_option = key.replace("_", "-")
if value is False:
commands.append("lldp tlv-select {0}".format(device_option))
elif value is True:
commands.append("no lldp tlv-select {0}".format(device_option))
return commands

View file

@ -17,6 +17,7 @@ from ansible.module_utils.network.eos.facts.l2_interfaces.l2_interfaces import L
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.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.vlans.vlans import VlansFacts
from ansible.module_utils.network.eos.facts.legacy.base import Default, Hardware, Config, Interfaces
@ -33,6 +34,7 @@ FACT_RESOURCE_SUBSETS = dict(
l3_interfaces=L3_interfacesFacts,
lacp=LacpFacts,
lag_interfaces=Lag_interfacesFacts,
lldp_global=Lldp_globalFacts,
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 lldp_global 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.lldp_global.lldp_global import Lldp_globalArgs
class Lldp_globalFacts(object):
""" The eos lldp_global fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Lldp_globalArgs.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 lldp_global
: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 lldp')
obj = {}
if data:
obj.update(self.render_config(self.generated_spec, data))
ansible_facts['ansible_network_resources'].pop('lldp_global', None)
facts = {}
if obj:
params = utils.validate_config(self.argument_spec, {'config': obj})
facts['lldp_global'] = utils.remove_empties(params['config'])
else:
facts['lldp_global'] = {}
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['holdtime'] = utils.parse_conf_arg(conf, 'holdtime')
config['reinit'] = utils.parse_conf_arg(conf, 'reinit')
config['timer'] = utils.parse_conf_arg(conf, 'timer')
for match in re.findall(r'^(no)? lldp tlv-select (\S+)', conf, re.MULTILINE):
tlv_option = match[1].replace("-", "_")
config['tlv_select'][tlv_option] = bool(match[0] != "no")
return utils.remove_empties(config)

View file

@ -51,7 +51,7 @@ options:
choices: [
'all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces',
'l3_interfaces', '!l3_interfaces', 'lacp', '!lacp', 'lag_interfaces', '!lag_interfaces',
'vlans', '!vlans',
'lldp_global', '!lldp_global', 'vlans', '!vlans',
]
version_added: "2.9"
"""

View file

@ -0,0 +1,242 @@
#!/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_lldp_global
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: eos_lldp_global
version_added: 2.9
short_description: Manage Global Link Layer Discovery Protocol (LLDP) settings on Arista EOS devices.
description:
- This module manages Global Link Layer Discovery Protocol (LLDP) settings on Arista EOS devices.
author: Nathaniel Case (@Qalthos)
options:
config:
description: The provided global LLDP configuration.
type: dict
suboptions:
holdtime:
description:
- Specifies the holdtime (in sec) to be sent in packets.
type: int
reinit:
description:
- Specifies the delay (in sec) for LLDP initialization on any interface.
type: int
timer:
description:
- Specifies the rate at which LLDP packets are sent (in sec).
type: int
tlv_select:
description:
- Specifies the LLDP TLVs to enable or disable.
type: dict
suboptions:
link_aggregation:
description:
- Enable or disable link aggregation TLV.
type: bool
management_address:
description:
- Enable or disable management address TLV.
type: bool
max_frame_size:
description:
- Enable or disable maximum frame size TLV.
type: bool
port_description:
description:
- Enable or disable port description TLV.
type: bool
system_capabilities:
description:
- Enable or disable system capabilities TLV.
type: bool
system_description:
description:
- Enable or disable system description TLV.
type: bool
system_name:
description:
- Enable or disable system name TLV.
type: bool
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 run | section lldp
# lldp timer 3000
# lldp holdtime 100
# lldp reinit 5
# no lldp tlv-select management-address
# no lldp tlv-select system-description
- name: Merge provided LLDP configuration with the existing configuration
eos_lldp_global:
config:
holdtime: 100
tlv_select:
management_address: False
port_description: False
system_description: True
state: merged
# -----------
# After state
# -----------
#
# veos# show run | section lldp
# lldp timer 3000
# lldp holdtime 100
# lldp reinit 5
# no lldp tlv-select management-address
# no lldp tlv-select port-description
# Using replaced
#
# ------------
# Before State
# ------------
#
# veos# show run | section lldp
# lldp timer 3000
# lldp holdtime 100
# lldp reinit 5
# no lldp tlv-select management-address
# no lldp tlv-select system-description
- name: Replace existing LLDP device configuration with provided configuration
eos_lldp_global:
config:
holdtime: 100
tlv_select:
management_address: False
port_description: False
system_description: True
state: replaced
# -----------
# After state
# -----------
#
# veos# show run | section lldp
# lldp holdtime 100
# no lldp tlv-select management-address
# no lldp tlv-select port-description
# Using deleted
#
# ------------
# Before State
# ------------
#
# veos# show run | section lldp
# lldp timer 3000
# lldp holdtime 100
# lldp reinit 5
# no lldp tlv-select management-address
# no lldp tlv-select system-description
- name: Delete existing LLDP configurations from the device
eos_lldp_global:
state: deleted
# -----------
# After state
# -----------
#
# veos# show run | section ^lldp
"""
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: ['lldp holdtime 100', 'no lldp timer', 'lldp tlv-select system-description']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.eos.argspec.lldp_global.lldp_global import Lldp_globalArgs
from ansible.module_utils.network.eos.config.lldp_global.lldp_global import Lldp_global
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec,
supports_check_mode=True)
result = Lldp_global(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: lldp_global
become: yes
- name: Returns LLDP configuration to default parameters
eos_lldp_global:
state: deleted
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.before"
- eos_facts:
gather_network_resources: lldp_global
become: yes
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.after"
- assert:
that:
- "ansible_facts.network_resources.lldp_global == {}"

View file

@ -0,0 +1,46 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
holdtime: 100
tlv_select:
management_address: False
port_description: False
system_description: True
- eos_facts:
gather_network_resources: lldp_global
become: yes
- name: Merge provided LLDP configuration with device configuration
eos_lldp_global:
config: "{{ config }}"
state: merged
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.before"
- eos_facts:
gather_network_resources: lldp_global
become: yes
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.after"
- set_fact:
expected_config:
timer: 3000
holdtime: 100
reinit: 5
tlv_select:
management_address: False
port_description: False
- assert:
that:
- "ansible_facts.network_resources.lldp_global == expected_config"

View file

@ -0,0 +1,44 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
holdtime: 100
tlv_select:
management_address: False
port_description: False
system_description: True
- eos_facts:
gather_network_resources: lldp_global
become: yes
- name: Replaces device configuration with provided LLDP configuration
eos_lldp_global:
config: "{{ config }}"
state: replaced
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.before"
- eos_facts:
gather_network_resources: lldp_global
become: yes
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.after"
- set_fact:
expected_config:
holdtime: 100
tlv_select:
management_address: False
port_description: False
- assert:
that:
- "ansible_facts.network_resources.lldp_global == expected_config"

View file

@ -0,0 +1,28 @@
---
- name: Reset initial config
cli_config:
config: |
lldp timer 3000
lldp holdtime 100
lldp reinit 5
no lldp tlv-select management-address
no lldp tlv-select system-description
lldp tlv-select port-description
become: yes
- eos_facts:
gather_network_resources: lldp_global
become: yes
- set_fact:
expected_config:
timer: 3000
holdtime: 100
reinit: 5
tlv_select:
management_address: False
system_description: False
- assert:
that:
- "expected_config == ansible_facts.network_resources.lldp_global"