Resource module for ios_lldp_global (#60506)

* ios_lldp_global model

* final fix for lldp_global

* fix shippable n reviews

* fix reviews
pull/61012/head
Sumit Jaiswal 5 years ago committed by GitHub
parent 4615139194
commit 35d457a14f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -33,6 +33,8 @@ class FactsArgs(object):
'!lacp',
'lacp_interfaces',
'!lacp_interfaces',
'lldp_global',
'!lldp_global',
]
argument_spec = {

@ -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 ios_lldp_global module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Lldp_globalArgs(object):
def __init__(self, **kwargs):
pass
argument_spec = {'config': {'options': {'holdtime': {'type': 'int'},
'reinit': {'type': 'int'},
'enable': {'type': 'bool'},
'timer': {'type': 'int'},
'tlv_select': {
'options': {
'four_wire_power_management': {'type': 'bool'},
'mac_phy_cfg': {'type': 'bool'},
'management_address': {'type': 'bool'},
'port_description': {'type': 'bool'},
'port_vlan': {'type': 'bool'},
'power_management': {'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'}}

@ -0,0 +1,236 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The ios_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 its desired end-state is
created
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.ios.facts.facts import Facts
from ansible.module_utils.network.ios.utils.utils import dict_to_set
from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value
class Lldp_global(ConfigBase):
"""
The ios_lldp_global class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lldp_global',
]
tlv_select_params = {'four_wire_power_management': '4-wire-power-management', 'mac_phy_cfg': 'mac-phy-cfg',
'management_address': 'management-address', 'port_description': 'port-description',
'port_vlan': 'port-vlan', 'power_management': 'power-management',
'system_capabilities': 'system-capabilities', 'system_description': 'system-description',
'system_name': 'system-name'}
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 moduel execution
"""
result = {'changed': False}
commands = list()
warnings = 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 deisred configuration
"""
want = self._module.params['config']
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 deisred configuration
"""
commands = []
state = self._module.params['state']
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
def _state_replaced(self, want, have):
""" The command generator when state is replaced
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:param interface_type: interface type
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the deisred configuration
"""
commands = []
have_dict = filter_dict_having_none_value(want, have)
commands.extend(self._clear_config(have_dict))
commands.extend(self._set_config(want, have))
return commands
def _state_merged(self, want, have):
""" The command generator when state is merged
:param want: the additive configuration as a dictionary
:param obj_in_have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
commands.extend(self._set_config(want, have))
return commands
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:param want: the objects from which the configuration should be removed
:param obj_in_have: the current configuration as a dictionary
:param interface_type: interface type
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
commands.extend(self._clear_config(have))
return commands
def _remove_command_from_config_list(self, cmd, commands):
if cmd not in commands:
commands.append('no %s' % cmd)
def add_command_to_config_list(self, cmd, commands):
if cmd not in commands:
commands.append(cmd)
def _set_config(self, want, have):
# Set the interface config based on the want and have config
commands = []
# Get the diff b/w want and have
want_dict = dict_to_set(want)
have_dict = dict_to_set(have)
diff = want_dict - have_dict
if diff:
diff = dict(diff)
holdtime = diff.get('holdtime')
enable = diff.get('enable')
timer = diff.get('timer')
reinit = diff.get('reinit')
tlv_select = diff.get('tlv_select')
if holdtime:
cmd = 'lldp holdtime {0}'.format(holdtime)
self.add_command_to_config_list(cmd, commands)
if enable:
cmd = 'lldp run'
self.add_command_to_config_list(cmd, commands)
if timer:
cmd = 'lldp timer {0}'.format(timer)
self.add_command_to_config_list(cmd, commands)
if reinit:
cmd = 'lldp reinit {0}'.format(reinit)
self.add_command_to_config_list(cmd, commands)
if tlv_select:
tlv_selec_dict = dict(tlv_select)
for k, v in iteritems(self.tlv_select_params):
if k in tlv_selec_dict and tlv_selec_dict[k]:
cmd = 'lldp tlv-select {0}'.format(v)
self.add_command_to_config_list(cmd, commands)
return commands
def _clear_config(self, have):
# Delete the interface config based on the want and have config
commands = []
if have.get('holdtime'):
cmd = 'lldp holdtime'
self._remove_command_from_config_list(cmd, commands)
if have.get('enable'):
cmd = 'lldp run'
self._remove_command_from_config_list(cmd, commands)
if have.get('timer'):
cmd = 'lldp timer'
self._remove_command_from_config_list(cmd, commands)
if have.get('reinit'):
cmd = 'lldp reinit'
self._remove_command_from_config_list(cmd, commands)
return commands

@ -21,6 +21,7 @@ from ansible.module_utils.network.ios.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.ios.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.ios.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.ios.facts.lacp_interfaces.lacp_interfaces import Lacp_InterfacesFacts
from ansible.module_utils.network.ios.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.ios.facts.legacy.base import Default, Hardware, Interfaces, Config
@ -38,6 +39,7 @@ FACT_RESOURCE_SUBSETS = dict(
lag_interfaces=Lag_interfacesFacts,
lacp=LacpFacts,
lacp_interfaces=Lacp_InterfacesFacts,
lldp_global=Lldp_globalFacts
)

@ -0,0 +1,90 @@
#
# -*- 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 ios 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
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.ios.argspec.lldp_global.lldp_global import Lldp_globalArgs
class Lldp_globalFacts(object):
""" The ios 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
"""
objs = dict()
if not data:
data = connection.get('show running-config | section ^lldp')
# operate on a collection of resource x
config = data.split('\n')
for conf in config:
if conf:
obj = self.render_config(self.generated_spec, conf)
if obj:
objs.update(obj)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': utils.remove_empties(objs)})
facts['lldp_global'] = 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)
holdtime = utils.parse_conf_arg(conf, 'lldp holdtime')
timer = utils.parse_conf_arg(conf, 'lldp timer')
reinit = utils.parse_conf_arg(conf, 'lldp reinit')
if holdtime:
config['holdtime'] = int(holdtime)
if 'lldp run' in conf:
config['enable'] = True
if timer:
config['timer'] = int(timer)
if reinit:
config['reinit'] = int(reinit)
return utils.remove_empties(config)

@ -31,28 +31,31 @@ def add_command_to_config_list(interface, cmd, commands):
def dict_to_set(sample_dict):
# Generate a set with passed dictionary for comparison
test_dict = {}
for k, v in iteritems(sample_dict):
if v is not None:
if isinstance(v, list):
if isinstance(v[0], dict):
if isinstance(sample_dict, dict):
for k, v in iteritems(sample_dict):
if v is not None:
if isinstance(v, list):
if isinstance(v[0], dict):
li = []
for each in v:
for key, value in iteritems(each):
if isinstance(value, list):
each[key] = tuple(value)
li.append(tuple(iteritems(each)))
v = tuple(li)
else:
v = tuple(v)
elif isinstance(v, dict):
li = []
for each in v:
for key, value in iteritems(each):
if isinstance(value, list):
each[key] = tuple(value)
li.append(tuple(iteritems(each)))
for key, value in iteritems(v):
if isinstance(value, list):
v[key] = tuple(value)
li.extend(tuple(iteritems(v)))
v = tuple(li)
else:
v = tuple(v)
elif isinstance(v, dict):
li = []
for key, value in iteritems(v):
if isinstance(value, list):
v[key] = tuple(value)
li.extend(tuple(iteritems(v)))
v = tuple(li)
test_dict.update({k: v})
return_set = set(tuple(iteritems(test_dict)))
test_dict.update({k: v})
return_set = set(tuple(iteritems(test_dict)))
else:
return_set = set(sample_dict)
return return_set
@ -60,7 +63,9 @@ def filter_dict_having_none_value(want, have):
# Generate dict with have dict value which is None in want dict
test_dict = dict()
test_key_dict = dict()
test_dict['name'] = want.get('name')
name = want.get('name')
if name:
test_dict['name'] = name
diff_ip = False
want_ip = ''
for k, v in iteritems(want):

@ -55,7 +55,8 @@ options:
all and the resources like interfaces, vlans etc.
Can specify a list of values to include a larger subset.
choices: ['all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces', 'vlans', '!vlans',
'lag_interfaces', '!lag_interfaces', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces']
'lag_interfaces', '!lag_interfaces', 'lacp', '!lacp', 'lacp_interfaces', '!lacp_interfaces', 'lldp_global',
'!lldp_global']
version_added: "2.9"
"""

@ -0,0 +1,252 @@
#!/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 ios_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: ios_lldp_global
version_added: 2.9
short_description: Configure and manage Link Layer Discovery Protocol(LLDP) attributes on IOS platforms.
description: This module configures and manages the Link Layer Discovery Protocol(LLDP) attributes on IOS platforms.
author: Sumit Jaiswal (@justjais)
notes:
- Tested against Cisco IOSv Version 15.2 on VIRL
- This module works with connection C(network_cli),
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
options:
config:
description: A dictionary of LLDP options
type: dict
suboptions:
holdtime:
description:
- LLDP holdtime (in sec) to be sent in packets.
- Refer to vendor documentation for valid values.
type: int
reinit:
description:
- Specify the delay (in secs) for LLDP to initialize.
- Refer to vendor documentation for valid values.
- NOTE, if LLDP reinit is configured with a starting
value, idempotency won't be maintained as the Cisco
device doesn't record the starting reinit configured
value. As such, Ansible cannot verify if the respective
starting reinit value is already configured or not from
the device side. If you try to apply starting reinit
value in every play run, Ansible will show changed as True.
For any other reinit value, idempotency will be maintained
since any other reinit value is recorded in the Cisco device.
type: int
enable:
description:
- Enable LLDP
type: bool
timer:
description:
- Specify the rate at which LLDP packets are sent (in sec).
- Refer to vendor documentation for valid values.
type: int
tlv_select:
description:
- Selection of LLDP TLVs i.e. type-length-value to send
- NOTE, if tlv-select is configured idempotency won't be maintained
as Cisco device doesn't record configured tlv-select options. As
such, Ansible cannot verify if the respective tlv-select options is
already configured or not from the device side. If you try to apply
tlv-select option in every play run, Ansible will show changed as True.
type: dict
suboptions:
four_wire_power_management:
description:
- Cisco 4-wire Power via MDI TLV
type: bool
mac_phy_cfg:
description:
- IEEE 802.3 MAC/Phy Configuration/status TLV
type: bool
management_address:
description:
- Management Address TLV
type: bool
port_description:
description:
- Port Description TLV
type: bool
port_vlan:
description:
- Port VLAN ID TLV
type: bool
power_management:
description:
- IEEE 802.3 DTE Power via MDI TLV
type: bool
system_capabilities:
description:
- System Capabilities TLV
type: bool
system_description:
description:
- System Description TLV
type: bool
system_name:
description:
- 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:
# -------------
# vios#sh running-config | section ^lldp
# vios1#
- name: Merge provided configuration with device configuration
ios_lldp_global:
config:
holdtime: 10
run: True
reinit: 3
timer: 10
state: merged
# After state:
# ------------
# vios#sh running-config | section ^lldp
# lldp timer 10
# lldp holdtime 10
# lldp reinit 3
# lldp run
# Using replaced
# Before state:
# -------------
# vios#sh running-config | section ^lldp
# lldp timer 10
# lldp holdtime 10
# lldp reinit 3
# lldp run
- name: Replaces LLDP device configuration with provided configuration
ios_lldp_global:
config:
holdtime: 20
reinit: 5
state: replaced
# After state:
# -------------
# vios#sh running-config | section ^lldp
# lldp holdtime 20
# lldp reinit 5
# Using Deleted without any config passed
#"(NOTE: This will delete all of configured LLDP module attributes)"
# Before state:
# -------------
# vios#sh running-config | section ^lldp
# lldp timer 10
# lldp holdtime 10
# lldp reinit 3
# lldp run
- name: "Delete LLDP attributes (Note: This won't delete the interface itself)"
ios_lldp_global:
state: deleted
# After state:
# -------------
# vios#sh running-config | section ^lldp
# vios1#
"""
RETURN = """
before:
description: The configuration prior to the model invocation
returned: always
type: dict
sample: The configuration returned will alwys be in the same format of the paramters above.
after:
description: The resulting configuration model invocation
returned: when changed
type: dict
sample: The configuration returned will alwys be in the same format of the paramters above.
commands:
description: The set of commands pushed to the remote device
returned: always
type: list
sample: ['lldp holdtime 10', 'lldp run', 'lldp timer 10']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.ios.argspec.lldp_global.lldp_global import Lldp_globalArgs
from ansible.module_utils.network.ios.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()

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

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

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

@ -0,0 +1,10 @@
---
- name: Populate Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
lldp holdtime 10
lldp run
lldp timer 100
lldp reinit 3

@ -0,0 +1,10 @@
---
- name: Remove Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
no lldp holdtime
no lldp run
no lldp timer
no lldp reinit

@ -0,0 +1,40 @@
---
- debug:
msg: "Start Deleted integration state for ios_lldp_global ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Delete configured Global LLDP
ios_lldp_global: &deleted
state: deleted
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ deleted['before'] == result['before'] }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ deleted['before'] == result['before'] }}"
- name: Delete configured Global LLDP (IDEMPOTENT)
ios_lldp_global: *deleted
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,43 @@
---
- debug:
msg: "START Merged ios_lldp_global state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Merge provided configuration Global LLDP
ios_lldp_global: &merged
config:
holdtime: 10
enable: True
reinit: 3
timer: 20
state: merged
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ merged['before'] == result['before'] }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ merged['before'] == result['before'] }}"
- name: Merge provided configuration Global LLDP (IDEMPOTENT)
ios_lldp_global: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,43 @@
---
- debug:
msg: "START Replaced ios_lldp_global state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Replaces Global LLDP configuration with provided LLDP configuration
ios_lldp_global: &replaced
config:
holdtime: 20
reinit: 5
state: replaced
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ replaced['before'] == result['before'] }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ replaced['before'] == result['before'] }}"
- name: Replaces Global LLDP configuration with provided LLDP configuration (IDEMPOTENT)
ios_lldp_global: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,47 @@
---
merged:
before: {}
commands:
- "lldp holdtime 10"
- "lldp run"
- "lldp timer 20"
- "lldp reinit 3"
after:
timer: 20
holdtime: 10
reinit: 3
enable: True
replaced:
before:
timer: 100
holdtime: 10
reinit: 3
enable: True
commands:
- "no lldp run"
- "no lldp timer"
- "lldp holdtime 20"
- "lldp reinit 5"
after:
holdtime: 10
reinit: 3
deleted:
before:
timer: 100
holdtime: 10
reinit: 3
enable: True
commands:
- "no lldp holdtime"
- "no lldp run"
- "no lldp timer"
- "no lldp reinit"
after: {}
Loading…
Cancel
Save