Added nxos_lldp_global resource module (#60072)

* Added nxos_lldp_global module

Tests

Correction

remove q

* Added tests

* Corrections

Changes

Last few changes please

Final

Finall

Finalll

* Key error and punctuation changes

* .gitignore edit

* Facts file edit

* Changed facts collection and 'replaced' state

Changed facts collection and replaced state

Changed facts collection and replaced state

* Spacing and module error correction

* return facts as empty dict not list

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>
pull/61365/head
Adharsh Srivats R 5 years ago committed by Trishna Guha
parent 233a3088f4
commit ee05d6bde2

@ -10,6 +10,7 @@ The arg spec for the nxos facts module.
CHOICES = [
'all',
'lag_interfaces',
'lldp_global',
'telemetry',
'vlans',
'lacp',

@ -0,0 +1,109 @@
#
# -*- 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 nxos_lldp_global module
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class Lldp_globalArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_lldp_global module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'options': {
'holdtime': {
'type': 'int'
},
'port_id': {
'choices': [0, 1],
'type': 'int'
},
'reinit': {
'type': 'int'
},
'timer': {
'type': 'int'
},
'tlv_select': {
'options': {
'dcbxp': {
'type': 'bool'
},
'management_address': {
'options': {
'v4': {
'type': 'bool'
},
'v6': {
'type': 'bool'
}
},
'type': 'dict'
},
'port': {
'options': {
'description': {
'type': 'bool'
},
'vlan': {
'type': 'bool'
}
},
'type': 'dict'
},
'power_management': {
'type': 'bool'
},
'system': {
'options': {
'capabilities': {
'type': 'bool'
},
'description': {
'type': 'bool'
},
'name': {
'type': 'bool'
}
},
'type': 'dict'
}
},
'type': 'dict'
}
},
'type': 'dict'
},
'state': {
'choices': ['merged', 'replaced', 'deleted'],
'default': 'merged',
'type': 'str'
}
}

@ -0,0 +1,240 @@
#
# -*- 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 nxos_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 to_list, remove_empties, dict_diff, dict_merge
from ansible.module_utils.network.nxos.facts.facts import Facts
class Lldp_global(ConfigBase):
"""
The nxos_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}
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'] = dict(existing_lldp_global_facts)
if result['changed']:
result['after'] = dict(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']
have = existing_lldp_global_facts
resp = self.set_state(remove_empties(want), have)
return 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(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
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
merge_dict = dict_diff(have, want)
# merge_dict will contain new and unique values in want
delete_dict = self.find_delete_params(have, want)
self._module.params['state'] = 'deleted'
commands.extend(self._state_deleted(delete_dict)) # delete
self._module.params['state'] = 'merged'
commands.extend(self.set_commands(merge_dict)) # merge
self._module.params['state'] = 'replaced'
return commands
def delete_nested_dict(self, have, want):
"""
Returns tlv_select nested dict that needs to be defaulted
"""
outer_dict = {}
for key, val in have.items():
inner_dict = {}
if not isinstance(val, dict):
if key not in want.keys():
inner_dict.update({key: val})
return inner_dict
else:
if key in want.keys():
outer_dict.update(
{key: self.delete_nested_dict(val, want[key])})
else:
outer_dict.update({key: val})
return outer_dict
def find_delete_params(self, have, want):
"""
Returns parameters that are present in have and not in want, that need to be defaulted
"""
delete_dict = {}
for key, val in have.items():
if key not in want.keys():
delete_dict.update({key: val})
else:
if key == 'tlv_select':
delete_dict.update({key: self.delete_nested_dict(
have['tlv_select'], want['tlv_select'])})
return delete_dict
def _state_merged(self, 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 = []
diff = dict_diff(have, want)
commands.extend(self.set_commands(diff))
return commands
def _state_deleted(self, 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 = []
if have:
for key, val in have.items():
if 'tlv_select' in key:
commands.extend(self.process_nested_dict(val))
else:
if key == 'port_id':
key = 'portid-subtype'
commands.append('no lldp ' + key + ' ' + str(val))
return commands
def set_commands(self, diff):
commands = []
for key, val in diff.items():
commands.extend(self.add_commands(key, val))
return commands
def add_commands(self, key, val):
command = []
if 'port_id' in key:
command.append('lldp portid-subtype ' + str(val))
elif 'tlv_select' in key:
command.extend(self.process_nested_dict(val))
else:
if val:
command.append('lldp ' + key + ' ' + str(val))
return command
def process_nested_dict(self, val):
nested_commands = []
for k, v in val.items():
if isinstance(v, dict):
for k1, v1 in v.items():
com1 = 'lldp tlv-select '
com2 = ''
if 'system' in k:
com2 = 'system-' + k1
elif 'management_address' in k:
com2 = 'management-address ' + k1
elif 'port' in k:
com2 = 'port-' + k1
com1 += com2
com1 = self.negate_command(com1, v1)
nested_commands.append(com1)
else:
com1 = 'lldp tlv-select '
if 'power_management' in k:
com1 += 'power-management'
else:
com1 += k
com1 = self.negate_command(com1, v)
nested_commands.append(com1)
return nested_commands
def negate_command(self, command, val):
# for merged, replaced vals need to be checked to add 'no'
if self._module.params['state'] == 'merged':
if not val:
command = 'no ' + command
return command

@ -20,6 +20,7 @@ from ansible.module_utils.network.nxos.facts.lag_interfaces.lag_interfaces impor
from ansible.module_utils.network.nxos.facts.telemetry.telemetry import TelemetryFacts
from ansible.module_utils.network.nxos.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.nxos.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts
from ansible.module_utils.network.nxos.facts.lldp_global.lldp_global import Lldp_globalFacts
FACT_LEGACY_SUBSETS = dict(
@ -32,6 +33,7 @@ FACT_LEGACY_SUBSETS = dict(
)
FACT_RESOURCE_SUBSETS = dict(
lag_interfaces=Lag_interfacesFacts,
lldp_global=Lldp_globalFacts,
telemetry=TelemetryFacts,
vlans=VlansFacts,
lacp=LacpFacts,

@ -0,0 +1,105 @@
#
# -*- 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 nxos 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.nxos.argspec.lldp_global.lldp_global import Lldp_globalArgs
class Lldp_globalFacts(object):
""" The nxos 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 | include lldp')
objs = {}
objs = self.render_config(self.generated_spec, data)
ansible_facts['ansible_network_resources'].pop('lldp_global', None)
facts = {}
if objs:
params = utils.validate_config(
self.argument_spec, {'config': objs})
facts['lldp_global'] = params['config']
facts = utils.remove_empties(facts)
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)
conf = re.split('\n', conf)
for command in conf:
param = re.search(
r'(.*)lldp (\w+(-?)\w+)',
command) # get the word after 'lldp'
if param:
# get the nested-dict/value for that param
key2 = re.search(r'%s(.*)' % param.group(2), command)
key2 = key2.group(1).strip()
key1 = param.group(2).replace('-', '_')
if key1 == 'portid_subtype':
key1 = 'port_id'
config[key1] = key2
elif key1 == 'tlv_select':
key2 = key2.split()
key2[0] = key2[0].replace('-', '_')
if len(key2) == 1:
if 'port' in key2[0] or 'system' in key2[0]: # nested dicts
key2 = key2[0].split('_')
# config[tlv_select][system][name]=False
config[key1][key2[0]][key2[1]] = False
else:
# config[tlv_select][dcbxp]=False
config[key1][key2[0]] = False
else:
# config[tlv_select][management_address][v6]=False
config[key1][key2[0]][key2[1]] = False
else:
config[key1] = key2 # config[reinit]=4
return utils.remove_empties(config)

@ -57,7 +57,7 @@ options:
to a given subset. Possible values for this argument include
all and the resources like interfaces, vlans etc.
Can specify a list of values to include a larger subset.
choices: ['all', 'lag_interfaces', 'telemetry', 'vlans', 'lacp', 'lacp_interfaces', 'interfaces', 'l3_interfaces', 'l2_interfaces']
choices: ['all', 'lag_interfaces', 'telemetry', 'vlans', 'lacp', 'lacp_interfaces', 'interfaces', 'l3_interfaces', 'l2_interfaces', 'lldp_global']
required: false
version_added: "2.9"
"""
@ -66,22 +66,18 @@ EXAMPLES = """
- name: Gather all legacy facts
nxos_facts:
gather_subset: all
- name: Gather only the config and default facts
nxos_facts:
gather_subset:
- config
- name: Do not gather hardware facts
nxos_facts:
gather_subset:
- "!hardware"
- name: Gather legacy and resource facts
nxos_facts:
gather_subset: all
gather_network_resources: all
- name: Gather only the interfaces resource facts and no legacy facts
nxos_facts:
gather_subset:
@ -89,7 +85,6 @@ EXAMPLES = """
- '!min'
gather_network_resources:
- interfaces
- name: Gather interfaces resource and minimal legacy facts
nxos_facts:
gather_subset: min
@ -101,12 +96,10 @@ ansible_net_gather_subset:
description: The list of fact subsets collected from the device
returned: always
type: list
ansible_net_gather_network_resources:
description: The list of fact for network resource subsets collected from the device
returned: when the resource is configured
type: list
# default
ansible_net_model:
description: The model name returned from the device
@ -140,7 +133,6 @@ ansible_net_python_version:
description: The Python version Ansible controller is using
returned: always
type: str
# hardware
ansible_net_filesystems:
description: All file system names available on the device
@ -154,13 +146,11 @@ ansible_net_memtotal_mb:
description: The total memory on the remote device in Mb
returned: when hardware is configured
type: int
# config
ansible_net_config:
description: The current active config from the device
returned: when config is configured
type: str
# interfaces
ansible_net_all_ipv4_addresses:
description: All IPv4 addresses configured on the device
@ -180,7 +170,6 @@ ansible_net_neighbors:
CDP and LLDP neighbor data is present on one port, CDP is preferred.
returned: when interfaces is configured
type: dict
# legacy (pre Ansible 2.2)
fan_info:
description: A hash of facts about fans in the remote device

@ -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 nxos_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: nxos_lldp_global
version_added: 2.9
short_description: Configure and manage Link Layer Discovery Protocol(LLDP) attributes on NX-OS platforms.
description: This module configures and manages the Link Layer Discovery Protocol(LLDP) attributes on NX-OS platforms.
author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
notes:
- Tested against NxOS 7.3.(0)D1(1) on VIRL
- The LLDP feature needs to be enabled before using this module
options:
config:
description:
- A list of link layer discovery configurations
type: dict
suboptions:
holdtime:
description:
- Amount of time the receiving device should hold the information (in seconds)
type: int
port_id:
description:
- This attribute defines if the interface names should be advertised in the long(0) or short(1) form.
type: int
choices: [0, 1]
reinit:
description:
- Amount of time to delay the intialization of LLDP on any interface (in seconds)
type: int
timer:
description:
- Frequency at which LLDP updates need to be transmitted (in seconds)
type: int
tlv_select:
description:
- This attribute can be used to specify the TLVs that need to be sent and received in the LLDP packets. By default, all TLVs are advertised
type: dict
suboptions:
dcbxp:
description:
- Used to specify the Data Center Bridging Exchange Protocol TLV
type: bool
management_address:
description:
- Used to specify the management address in TLV messages
type: dict
suboptions:
v4:
description: Management address with TLV v4
type: bool
v6:
description: Management address with TLV v6
type: bool
port:
description:
- Used to manage port based attributes in TLV messages
type: dict
suboptions:
description:
description:
- Used to specify the port description TLV
type: bool
vlan:
description:
- Used to specify the port VLAN ID TLV
type: bool
power_management:
description:
- Used to specify IEEE 802.3 DTE Power via MDI TLV
type: bool
system:
description:
- Used to manage system based attributes in TLV messages
type: dict
suboptions:
capabilities:
description:
- Used to specify the system capabilities TLV
type: bool
description:
description:
- Used to specify the system description TLV
type: bool
name:
description:
- Used to specify the 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:
# -------------
#
# user(config)# show running-config | include lldp
# feature lldp
- name: Merge provided configuration with device configuration
nxos_lldp_global:
config:
timer: 35
holdtime: 100
state: merged
# After state:
# ------------
#
# user(config)# show running-config | include lldp
# feature lldp
# lldp timer 35
# lldp holdtime 100
# Using replaced
# Before state:
# -------------
#
# user(config)# show running-config | include lldp
# feature lldp
# lldp holdtime 100
# lldp reinit 5
# lldp timer 35
- name: Replace device configuration of specific LLDP attributes with provided configuration
nxos_lldp_global:
config:
timer: 40
tlv_select:
system:
description: true
name: false
management_address:
v4: true
state: replaced
# After state:
# ------------
#
# user(config)# show running-config | include lldp
# feature lldp
# lldp timer 40
# no lldp tlv-select system-name
# Using deleted
# Before state:
# -------------
#
# user(config)# show running-config | include lldp
# feature lldp
# lldp holdtime 5
# lldp reinit 3
- name: Delete LLDP configuration (this will by default remove all lldp configuration)
nxos_lldp_global:
state: deleted
# After state:
# ------------
#
# user(config)# show running-config | include lldp
# feature 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 125', 'lldp reinit 4', 'no lldp tlv-select system-name']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.lldp_global.lldp_global import Lldp_globalArgs
from ansible.module_utils.network.nxos.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,2 @@
dependencies:
- prepare_nxos_tests

@ -0,0 +1,20 @@
---
- name: collect cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yml"
connection: local
register: test_cases
- set_fact:
test_cases:
files: "{{ test_cases.files }}"
- 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 connection={{ 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,63 @@
---
- debug:
msg: "Start nxos_lldp_global deleted integration tests connection = {{ ansible_connection }}"
- name: Enable LLDP feature
nxos_feature:
feature: lldp
state: enabled
- block:
- name: Setup
cli_config:
config: |
lldp holdtime 125
lldp timer 32
no lldp tlv-select dcbxp
lldp tlv-select system-name
no lldp tlv-select system-description
lldp tlv-select power-management
- name: Gather lldp_global facts
nxos_facts: &facts
gather_subset:
- '!all'
- '!min'
gather_network_resources: lldp_global
- name: Deleted
nxos_lldp_global: &deleted
state: deleted
register: result
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.before"
- "'no lldp holdtime 125' in result.commands"
- "'no lldp timer 32' in result.commands"
- "'lldp tlv-select dcbxp' in result.commands"
- "'lldp tlv-select system-description' in result.commands"
- "result.changed == true "
- "result.after | length == 0"
- name: Gather lldp_global post facts
nxos_facts: *facts
- assert:
that:
- "ansible_facts.network_resources == {} "
- name: Idempotence - deleted
nxos_lldp_global: *deleted
register: result
- assert:
that:
- "result.changed == false"
- "result.commands|length == 0"
always:
- name: teardown
nxos_feature:
feature: lldp
state: disabled

@ -0,0 +1,55 @@
---
- debug:
msg: "Start nxos_lldp_global merged integration tests connection={{ansible_connection}}"
- name: Enable lldp
nxos_feature:
feature: lldp
- block:
- name: Merged
nxos_lldp_global: &merged
config:
reinit: 5
timer: 40
port_id: 1
tlv_select:
power_management: false
state: merged
register: result
- assert:
that:
- "result.before|length == 0"
- "result.changed == true"
- "'lldp reinit 5' in result.commands"
- "'lldp timer 40' in result.commands"
- "'lldp portid-subtype 1' in result.commands"
- "'no lldp tlv-select power-management' in result.commands"
- "result.commands | length == 4"
- name: Gather lldp_global facts
nxos_facts:
gather_subset:
- '!all'
- '!min'
gather_network_resources: lldp_global
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.after"
- name: Idempotence - Merged
nxos_lldp_global: *merged
register: result
- assert:
that:
- "result.changed == false"
- "result.commands | length == 0"
always:
- name: teardown
nxos_feature:
feature: lldp
state: disabled

@ -0,0 +1,70 @@
---
- debug:
msg: "Start nxos_lldp_global replaced integration tests connection = {{ansible_connection}}"
- name: Enable lldp feature
nxos_feature:
feature: lldp
state: enabled
- block:
- name: Setup
cli_config:
config: |
lldp holdtime 125
lldp portid-subtype 1
lldp tlv-select system-name
no lldp tlv-select port-vlan
no lldp tlv-select power-management
- name: Replaced
nxos_lldp_global: &replaced
config:
holdtime: 125
timer: 35
tlv_select:
system:
name: false
description: false
port:
vlan: false
dcbxp: false
state: replaced
register: result
- assert:
that:
- "result.changed == true"
- "'lldp timer 35' in result.commands"
- "'lldp tlv-select power-management' in result.commands"
- "'no lldp portid-subtype 1' in result.commands"
- "'no lldp tlv-select system-name' in result.commands"
- "'no lldp tlv-select system-description' in result.commands"
- "'no lldp tlv-select dcbxp' in result.commands"
- "result.commands|length == 6"
- name: Gather lldp_global post facts
nxos_facts:
gather_subset:
- '!all'
- '!min'
gather_network_resources: lldp_global
- assert:
that:
- "ansible_facts.network_resources.lldp_global == result.after"
- name: Idempotence - Replaced
nxos_lldp_global: *replaced
register: result
- assert:
that:
- "result.changed == false"
- "result.commands|length == 0"
always:
- name: teardown
nxos_feature:
feature: lldp
state: disabled
Loading…
Cancel
Save