Resource Module for exos_lldp_global (#60988)

* Model->Role->Module.
Yet to import modules/exos_facts.py

* exos_facts refactor

* future-import and metaclass

* Fix unit tests

* Fix openconfig-lldp word and send_request API from Httpapi class

idempotent, json.dumps in base class, send_requests from base exos class

* action file for exos_lldp_global

* Add intergration tests for exos_lldp_global

* Test data field of the request
pull/61485/head
Ujwal Komarla 5 years ago committed by Nathaniel Case
parent 841d204d0d
commit 5e200f2d0a

@ -0,0 +1,31 @@
#
# -*- 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 arg spec for the exos facts module.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class FactsArgs(object): # pylint: disable=R0903
""" The arg spec for the exos facts module
"""
def __init__(self, **kwargs):
pass
choices = [
'all',
'!all',
'lldp_global',
'!lldp_global'
]
argument_spec = {
'gather_subset': dict(default=['!config'], type='list'),
'gather_network_resources': dict(choices=choices,
type='list'),
}

@ -0,0 +1,57 @@
#
# -*- 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 exos_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 exos_lldp_global module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'options': {
'interval': {'default': 30, 'type': 'int'},
'tlv_select': {
'options': {
'management_address': {'type': 'bool'},
'port_description': {'type': 'bool'},
'system_capabilities': {'type': 'bool'},
'system_description': {
'default': True,
'type': 'bool'},
'system_name': {'default': True, 'type': 'bool'}},
'type': 'dict'}},
'type': 'dict'},
'state': {
'choices': ['merged', 'replaced', 'deleted'],
'default': 'merged',
'type': 'str'}} # pylint: disable=C0301

@ -0,0 +1,199 @@
#
# -*- 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 exos_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
from ansible.module_utils.network.exos.facts.facts import Facts
from ansible.module_utils.network.exos.exos import send_requests
import json
from copy import deepcopy
class Lldp_global(ConfigBase):
"""
The exos_lldp_global class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lldp_global',
]
LLDP_DEFAULT_INTERVAL = 30
LLDP_DEFAULT_TLV = {
'system_name': True,
'system_description': True,
'system_capabilities': False,
'port_description': False,
'management_address': False
}
LLDP_REQUEST = {
"data": {"openconfig-lldp:config": {}},
"method": "PUT",
"path": "/rest/restconf/data/openconfig-lldp:lldp/config"
}
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()
requests = list()
existing_lldp_global_facts = self.get_lldp_global_facts()
requests.extend(self.set_config(existing_lldp_global_facts))
if requests:
if not self._module.check_mode:
send_requests(self._module, requests)
result['changed'] = True
result['requests'] = requests
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 requests necessary to migrate the current configuration
to the desired 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 requests necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
if state == 'deleted':
requests = self._state_deleted(want, have)
elif state == 'merged':
requests = self._state_merged(want, have)
elif state == 'replaced':
requests = self._state_replaced(want, have)
return requests
def _state_replaced(self, want, have):
""" The request generator when state is replaced
:rtype: A list
:returns: the requests necessary to migrate the current configuration
to the desired configuration
"""
requests = []
requests.extend(self._state_deleted(want, have))
requests.extend(self._state_merged(want, have))
return requests
def _state_merged(self, want, have):
""" The request generator when state is merged
:rtype: A list
:returns: the requests necessary to merge the provided into
the current configuration
"""
requests = []
request = deepcopy(self.LLDP_REQUEST)
self._update_lldp_config_body_if_diff(want, have, request)
if len(request["data"]["openconfig-lldp:config"]):
request["data"] = json.dumps(request["data"])
requests.append(request)
return requests
def _state_deleted(self, want, have):
""" The request generator when state is deleted
:rtype: A list
:returns: the requests necessary to remove the current configuration
of the provided objects
"""
requests = []
request = deepcopy(self.LLDP_REQUEST)
if want:
self._update_lldp_config_body_if_diff(want, have, request)
else:
if self.LLDP_DEFAULT_INTERVAL != have['interval']:
request["data"]["openconfig-lldp:config"].update(
{"hello-timer": self.LLDP_DEFAULT_INTERVAL})
if have['tlv_select'] != self.LLDP_DEFAULT_TLV:
request["data"]["openconfig-lldp:config"].update(
{"suppress-tlv-advertisement": [key.upper() for key, value in self.LLDP_DEFAULT_TLV.items() if not value]})
request["data"]["openconfig-lldp:config"]["suppress-tlv-advertisement"].sort()
if len(request["data"]["openconfig-lldp:config"]):
request["data"] = json.dumps(request["data"])
requests.append(request)
return requests
def _update_lldp_config_body_if_diff(self, want, have, request):
if want.get('interval'):
if want['interval'] != have['interval']:
request["data"]["openconfig-lldp:config"].update(
{"hello-timer": want['interval']})
if want.get('tlv_select'):
# Create list of TLVs to be suppressed which aren't already
want_suppress = [key.upper() for key, value in want["tlv_select"].items() if have["tlv_select"][key] != value and value is False]
if want_suppress:
# Add previously suppressed TLVs to the list as we are doing a PUT op
want_suppress.extend([key.upper() for key, value in have["tlv_select"].items() if value is False])
request["data"]["openconfig-lldp:config"].update(
{"suppress-tlv-advertisement": want_suppress})
request["data"]["openconfig-lldp:config"]["suppress-tlv-advertisement"].sort()

@ -209,7 +209,7 @@ def to_request(module, requests):
transform = ComplexList(dict(
path=dict(key=True),
method=dict(),
data=dict(),
data=dict(type='dict'),
), module)
return transform(to_list(requests))

@ -0,0 +1,56 @@
#
# -*- 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 facts class for exos
this file validates each subset of facts and selectively
calls the appropriate facts gathering function
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.network.exos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.exos.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.exos.facts.legacy.base import Default, Hardware, Interfaces, Config
FACT_LEGACY_SUBSETS = dict(
default=Default,
hardware=Hardware,
interfaces=Interfaces,
config=Config)
FACT_RESOURCE_SUBSETS = dict(
lldp_global=Lldp_globalFacts,
)
class Facts(FactsBase):
""" The fact class for exos
"""
VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys())
def __init__(self, module):
super(Facts, self).__init__(module)
def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
""" Collect the facts for exos
:param legacy_facts_type: List of legacy facts types
:param resource_facts_type: List of resource fact types
:param data: previously collected conf
:rtype: dict
:return: the facts gathered
"""
netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', [])
if self.VALID_RESOURCE_SUBSETS:
self.get_network_resources_facts(netres_choices, FACT_RESOURCE_SUBSETS, resource_facts_type, data)
if self.VALID_LEGACY_GATHER_SUBSETS:
self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type)
return self.ansible_facts, self._warnings

@ -0,0 +1,263 @@
# -*- 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 exos legacy 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
import json
from ansible.module_utils.network.exos.exos import run_commands
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
class FactsBase(object):
COMMANDS = list()
def __init__(self, module):
self.module = module
self.facts = dict()
self.warnings = list()
self.responses = None
def populate(self):
self.responses = run_commands(self.module, self.COMMANDS)
def run(self, cmd):
return run_commands(self.module, cmd)
class Default(FactsBase):
COMMANDS = [
'show version',
'show switch'
]
def populate(self):
super(Default, self).populate()
data = self.responses[0]
if data:
self.facts['version'] = self.parse_version(data)
self.facts['serialnum'] = self.parse_serialnum(data)
data = self.responses[1]
if data:
self.facts['model'] = self.parse_model(data)
self.facts['hostname'] = self.parse_hostname(data)
def parse_version(self, data):
match = re.search(r'Image\s+: ExtremeXOS version (\S+)', data)
if match:
return match.group(1)
def parse_model(self, data):
match = re.search(r'System Type:\s+(.*$)', data, re.M)
if match:
return match.group(1)
def parse_hostname(self, data):
match = re.search(r'SysName:\s+(\S+)', data, re.M)
if match:
return match.group(1)
def parse_serialnum(self, data):
match = re.search(r'Switch\s+: \S+ (\S+)', data, re.M)
if match:
return match.group(1)
# For stack, return serial number of the first switch in the stack.
match = re.search(r'Slot-\d+\s+: \S+ (\S+)', data, re.M)
if match:
return match.group(1)
# Handle unique formatting for VM
match = re.search(r'Switch\s+: PN:\S+\s+SN:(\S+)', data, re.M)
if match:
return match.group(1)
class Hardware(FactsBase):
COMMANDS = [
'show memory'
]
def populate(self):
super(Hardware, self).populate()
data = self.responses[0]
if data:
self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0))
self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0))
def parse_memtotal(self, data):
match = re.search(r' Total DRAM \(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
# Handle unique formatting for VM
match = re.search(r' Total \s+\(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
def parse_memfree(self, data):
match = re.search(r' Free\s+\(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
class Config(FactsBase):
COMMANDS = ['show configuration detail']
def populate(self):
super(Config, self).populate()
data = self.responses[0]
if data:
self.facts['config'] = data
class Interfaces(FactsBase):
COMMANDS = [
'show switch',
{'command': 'show port config', 'output': 'json'},
{'command': 'show port description', 'output': 'json'},
{'command': 'show vlan detail', 'output': 'json'},
{'command': 'show lldp neighbors', 'output': 'json'}
]
def populate(self):
super(Interfaces, self).populate()
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
data = self.responses[0]
if data:
sysmac = self.parse_sysmac(data)
data = self.responses[1]
if data:
self.facts['interfaces'] = self.populate_interfaces(data, sysmac)
data = self.responses[2]
if data:
self.populate_interface_descriptions(data)
data = self.responses[3]
if data:
self.populate_vlan_interfaces(data, sysmac)
data = self.responses[4]
if data:
self.facts['neighbors'] = self.parse_neighbors(data)
def parse_sysmac(self, data):
match = re.search(r'System MAC:\s+(\S+)', data, re.M)
if match:
return match.group(1)
def populate_interfaces(self, interfaces, sysmac):
facts = dict()
for elem in interfaces:
intf = dict()
if 'show_ports_config' not in elem:
continue
key = str(elem['show_ports_config']['port'])
if elem['show_ports_config']['linkState'] == 2:
# Link state is "not present", don't include
continue
intf['type'] = 'Ethernet'
intf['macaddress'] = sysmac
intf['bandwidth_configured'] = str(elem['show_ports_config']['speedCfg'])
intf['bandwidth'] = str(elem['show_ports_config']['speedActual'])
intf['duplex_configured'] = elem['show_ports_config']['duplexCfg']
intf['duplex'] = elem['show_ports_config']['duplexActual']
if elem['show_ports_config']['linkState'] == 1:
intf['lineprotocol'] = 'up'
else:
intf['lineprotocol'] = 'down'
if elem['show_ports_config']['portState'] == 1:
intf['operstatus'] = 'up'
else:
intf['operstatus'] = 'admin down'
facts[key] = intf
return facts
def populate_interface_descriptions(self, data):
for elem in data:
if 'show_ports_description' not in elem:
continue
key = str(elem['show_ports_description']['port'])
if 'descriptionString' in elem['show_ports_description']:
desc = elem['show_ports_description']['descriptionString']
self.facts['interfaces'][key]['description'] = desc
def populate_vlan_interfaces(self, data, sysmac):
for elem in data:
if 'vlanProc' in elem:
key = elem['vlanProc']['name1']
if key not in self.facts['interfaces']:
intf = dict()
intf['type'] = 'VLAN'
intf['macaddress'] = sysmac
self.facts['interfaces'][key] = intf
if elem['vlanProc']['ipAddress'] != '0.0.0.0':
self.facts['interfaces'][key]['ipv4'] = list()
addr = elem['vlanProc']['ipAddress']
subnet = elem['vlanProc']['maskForDisplay']
ipv4 = dict(address=addr, subnet=subnet)
self.add_ip_address(addr, 'ipv4')
self.facts['interfaces'][key]['ipv4'].append(ipv4)
if 'rtifIpv6Address' in elem:
key = elem['rtifIpv6Address']['rtifName']
if key not in self.facts['interfaces']:
intf = dict()
intf['type'] = 'VLAN'
intf['macaddress'] = sysmac
self.facts['interfaces'][key] = intf
self.facts['interfaces'][key]['ipv6'] = list()
addr, subnet = elem['rtifIpv6Address']['ipv6_address_mask'].split('/')
ipv6 = dict(address=addr, subnet=subnet)
self.add_ip_address(addr, 'ipv6')
self.facts['interfaces'][key]['ipv6'].append(ipv6)
def add_ip_address(self, address, family):
if family == 'ipv4':
if address not in self.facts['all_ipv4_addresses']:
self.facts['all_ipv4_addresses'].append(address)
else:
if address not in self.facts['all_ipv6_addresses']:
self.facts['all_ipv6_addresses'].append(address)
def parse_neighbors(self, data):
facts = dict()
for elem in data:
if 'lldpPortNbrInfoShort' not in elem:
continue
intf = str(elem['lldpPortNbrInfoShort']['port'])
if intf not in facts:
facts[intf] = list()
fact = dict()
fact['host'] = elem['lldpPortNbrInfoShort']['nbrSysName']
fact['port'] = str(elem['lldpPortNbrInfoShort']['nbrPortID'])
facts[intf].append(fact)
return facts

@ -0,0 +1,97 @@
#
# -*- 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 exos 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.exos.argspec.lldp_global.lldp_global \
import Lldp_globalArgs
from ansible.module_utils.network.exos.exos import send_requests
class Lldp_globalFacts(object):
""" The exos lldp_global fact class
"""
TLV_SELECT_OPTIONS = [
"SYSTEM_NAME",
"SYSTEM_DESCRIPTION",
"SYSTEM_CAPABILITIES",
"MANAGEMENT_ADDRESS",
"PORT_DESCRIPTION"]
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:
request = {
"path": "/rest/restconf/data/openconfig-lldp:lldp/config/",
"method": "GET",
}
data = send_requests(self._module, request)
obj = {}
if data:
lldp_obj = self.render_config(self.generated_spec, data[0])
if lldp_obj:
obj = lldp_obj
ansible_facts['ansible_network_resources'].pop('lldp_global', None)
facts = {}
params = utils.validate_config(self.argument_spec, {'config': obj})
facts['lldp_global'] = 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['interval'] = conf["openconfig-lldp:config"]["hello-timer"]
for item in self.TLV_SELECT_OPTIONS:
config["tlv_select"][item.lower()] = (
False if (item in conf["openconfig-lldp:config"]["suppress-tlv-advertisement"])
else True)
return utils.remove_empties(config)

@ -0,0 +1,6 @@
# -*- 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)
# utils

@ -30,7 +30,9 @@ DOCUMENTATION = """
---
module: exos_facts
version_added: "2.7"
author: "Lance Richardson (@hlrichardson)"
author:
- "Lance Richardson (@hlrichardson)"
- "Ujwal Koamrla (@ujwalkomarla)"
short_description: Collect facts from devices running Extreme EXOS
description:
- Collects a base set of device facts from a remote device that
@ -50,21 +52,49 @@ options:
with an initial C(M(!)) to specify that a specific subset should
not be collected.
required: false
type: list
default: ['!config']
gather_network_resources:
description:
- When supplied, this argument will restrict the facts collected
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', '!all', 'lldp_global', '!lldp_global']
type: list
version_added: "2.9"
"""
EXAMPLES = """
- name: collect all facts from the device
- name: Gather all legacy facts
exos_facts:
gather_subset: all
- name: collect only the config and default facts
- name: Gather only the config and default facts
exos_facts:
gather_subset: config
- name: do not collect hardware facts
- name: do not gather hardware facts
exos_facts:
gather_subset: "!hardware"
- name: Gather legacy and resource facts
exos_facts:
gather_subset: all
gather_network_resources: all
- name: Gather only the lldp global resource facts and no legacy facts
exos_facts:
gather_subset:
- '!all'
- '!min'
gather_network_resource:
- lldp_global
- name: Gather lldp global resource and minimal legacy facts
exos_facts:
gather_subset: min
gather_network_resource: lldp_global
"""
RETURN = """
@ -73,6 +103,11 @@ ansible_net_gather_subset:
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
@ -125,323 +160,27 @@ ansible_net_neighbors:
returned: when interfaces is configured
type: dict
"""
import re
import json
from ansible.module_utils.network.exos.exos import run_commands
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
class FactsBase(object):
COMMANDS = list()
def __init__(self, module):
self.module = module
self.facts = dict()
self.responses = None
def populate(self):
self.responses = run_commands(self.module, self.COMMANDS)
def run(self, cmd):
return run_commands(self.module, cmd)
class Default(FactsBase):
COMMANDS = [
'show version',
'show switch'
]
def populate(self):
super(Default, self).populate()
data = self.responses[0]
if data:
self.facts['version'] = self.parse_version(data)
self.facts['serialnum'] = self.parse_serialnum(data)
data = self.responses[1]
if data:
self.facts['model'] = self.parse_model(data)
self.facts['hostname'] = self.parse_hostname(data)
def parse_version(self, data):
match = re.search(r'Image\s+: ExtremeXOS version (\S+)', data)
if match:
return match.group(1)
def parse_model(self, data):
match = re.search(r'System Type:\s+(.*$)', data, re.M)
if match:
return match.group(1)
def parse_hostname(self, data):
match = re.search(r'SysName:\s+(\S+)', data, re.M)
if match:
return match.group(1)
def parse_serialnum(self, data):
match = re.search(r'Switch\s+: \S+ (\S+)', data, re.M)
if match:
return match.group(1)
# For stack, return serial number of the first switch in the stack.
match = re.search(r'Slot-\d+\s+: \S+ (\S+)', data, re.M)
if match:
return match.group(1)
# Handle unique formatting for VM
match = re.search(r'Switch\s+: PN:\S+\s+SN:(\S+)', data, re.M)
if match:
return match.group(1)
class Hardware(FactsBase):
COMMANDS = [
'show memory'
]
def populate(self):
super(Hardware, self).populate()
data = self.responses[0]
if data:
self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0))
self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0))
def parse_memtotal(self, data):
match = re.search(r' Total DRAM \(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
# Handle unique formatting for VM
match = re.search(r' Total \s+\(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
def parse_memfree(self, data):
match = re.search(r' Free\s+\(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
class Config(FactsBase):
COMMANDS = ['show configuration detail']
def populate(self):
super(Config, self).populate()
data = self.responses[0]
if data:
self.facts['config'] = data
class Interfaces(FactsBase):
COMMANDS = [
'show switch',
{'command': 'show port config', 'output': 'json'},
{'command': 'show port description', 'output': 'json'},
{'command': 'show vlan detail', 'output': 'json'},
{'command': 'show lldp neighbors', 'output': 'json'}
]
def populate(self):
super(Interfaces, self).populate()
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
data = self.responses[0]
if data:
sysmac = self.parse_sysmac(data)
data = self.responses[1]
if data:
self.facts['interfaces'] = self.populate_interfaces(data, sysmac)
data = self.responses[2]
if data:
self.populate_interface_descriptions(data)
data = self.responses[3]
if data:
self.populate_vlan_interfaces(data, sysmac)
data = self.responses[4]
if data:
self.facts['neighbors'] = self.parse_neighbors(data)
def parse_sysmac(self, data):
match = re.search(r'System MAC:\s+(\S+)', data, re.M)
if match:
return match.group(1)
def populate_interfaces(self, interfaces, sysmac):
facts = dict()
for elem in interfaces:
intf = dict()
if 'show_ports_config' not in elem:
continue
key = str(elem['show_ports_config']['port'])
if elem['show_ports_config']['linkState'] == 2:
# Link state is "not present", don't include
continue
intf['type'] = 'Ethernet'
intf['macaddress'] = sysmac
intf['bandwidth_configured'] = str(elem['show_ports_config']['speedCfg'])
intf['bandwidth'] = str(elem['show_ports_config']['speedActual'])
intf['duplex_configured'] = elem['show_ports_config']['duplexCfg']
intf['duplex'] = elem['show_ports_config']['duplexActual']
if elem['show_ports_config']['linkState'] == 1:
intf['lineprotocol'] = 'up'
else:
intf['lineprotocol'] = 'down'
if elem['show_ports_config']['portState'] == 1:
intf['operstatus'] = 'up'
else:
intf['operstatus'] = 'admin down'
facts[key] = intf
return facts
def populate_interface_descriptions(self, data):
for elem in data:
if 'show_ports_description' not in elem:
continue
key = str(elem['show_ports_description']['port'])
if 'descriptionString' in elem['show_ports_description']:
desc = elem['show_ports_description']['descriptionString']
self.facts['interfaces'][key]['description'] = desc
def populate_vlan_interfaces(self, data, sysmac):
for elem in data:
if 'vlanProc' in elem:
key = elem['vlanProc']['name1']
if key not in self.facts['interfaces']:
intf = dict()
intf['type'] = 'VLAN'
intf['macaddress'] = sysmac
self.facts['interfaces'][key] = intf
if elem['vlanProc']['ipAddress'] != '0.0.0.0':
self.facts['interfaces'][key]['ipv4'] = list()
addr = elem['vlanProc']['ipAddress']
subnet = elem['vlanProc']['maskForDisplay']
ipv4 = dict(address=addr, subnet=subnet)
self.add_ip_address(addr, 'ipv4')
self.facts['interfaces'][key]['ipv4'].append(ipv4)
if 'rtifIpv6Address' in elem:
key = elem['rtifIpv6Address']['rtifName']
if key not in self.facts['interfaces']:
intf = dict()
intf['type'] = 'VLAN'
intf['macaddress'] = sysmac
self.facts['interfaces'][key] = intf
self.facts['interfaces'][key]['ipv6'] = list()
addr, subnet = elem['rtifIpv6Address']['ipv6_address_mask'].split('/')
ipv6 = dict(address=addr, subnet=subnet)
self.add_ip_address(addr, 'ipv6')
self.facts['interfaces'][key]['ipv6'].append(ipv6)
def add_ip_address(self, address, family):
if family == 'ipv4':
if address not in self.facts['all_ipv4_addresses']:
self.facts['all_ipv4_addresses'].append(address)
else:
if address not in self.facts['all_ipv6_addresses']:
self.facts['all_ipv6_addresses'].append(address)
def parse_neighbors(self, data):
facts = dict()
for elem in data:
if 'lldpPortNbrInfoShort' not in elem:
continue
intf = str(elem['lldpPortNbrInfoShort']['port'])
if intf not in facts:
facts[intf] = list()
fact = dict()
fact['host'] = elem['lldpPortNbrInfoShort']['nbrSysName']
fact['port'] = str(elem['lldpPortNbrInfoShort']['nbrPortID'])
facts[intf].append(fact)
return facts
FACT_SUBSETS = dict(
default=Default,
hardware=Hardware,
interfaces=Interfaces,
config=Config)
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
from ansible.module_utils.network.exos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.exos.facts.facts import Facts
def main():
"""main entry point for module execution
"""Main entry point for AnsibleModule
"""
argument_spec = dict(
gather_subset=dict(default=["!config"], type='list')
)
argument_spec = FactsArgs.argument_spec
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
gather_subset = module.params['gather_subset']
runable_subsets = set()
exclude_subsets = set()
for subset in gather_subset:
if subset == 'all':
runable_subsets.update(VALID_SUBSETS)
continue
if subset.startswith('!'):
subset = subset[1:]
if subset == 'all':
exclude_subsets.update(VALID_SUBSETS)
continue
exclude = True
else:
exclude = False
if subset not in VALID_SUBSETS:
module.fail_json(msg='Bad subset')
if exclude:
exclude_subsets.add(subset)
else:
runable_subsets.add(subset)
if not runable_subsets:
runable_subsets.update(VALID_SUBSETS)
runable_subsets.difference_update(exclude_subsets)
runable_subsets.add('default')
facts = dict()
facts['gather_subset'] = list(runable_subsets)
instances = list()
for key in runable_subsets:
instances.append(FACT_SUBSETS[key](module))
for inst in instances:
inst.populate()
facts.update(inst.facts)
warnings = ['default value for `gather_subset` '
'will be changed to `min` from `!config` v2.11 onwards']
ansible_facts = dict()
for key, value in iteritems(facts):
key = 'ansible_net_%s' % key
ansible_facts[key] = value
result = Facts(module).get_facts()
warnings = list()
ansible_facts, additional_warnings = result
warnings.extend(additional_warnings)
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)

@ -0,0 +1,430 @@
#!/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 exos_lldp_global
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = """
---
module: exos_lldp_global
version_added: 2.9
short_description: Configure and manage Link Layer Discovery Protocol(LLDP) attribures on EXOS platforms.
description: This module configures and manages the Link Layer Discovery Protocol(LLDP) attributes on Extreme Networks EXOS platforms.
author: Ujwal Komarla (@ujwalkomarla)
notes:
- Tested against Extreme Networks EXOS version 30.2.1.8 on x460g2.
- This module works with connection C(httpapi).
See L(EXOS Platform Options,../network/user_guide/platform_exos.html)
options:
config:
description: A dictionary of LLDP options
type: dict
suboptions:
interval:
description:
- Frequency at which LLDP advertisements are sent (in seconds). By default - 30 seconds.
type: int
default: 30
tlv_select:
description:
- This attribute can be used to specify the TLVs that need to be sent in the LLDP packets. By default, only system name and system description is sent
type: dict
suboptions:
management_address:
description:
- Used to specify the management address in TLV messages
type: bool
port_description:
description:
- Used to specify the port description TLV
type: bool
system_capabilities:
description:
- Used to specify the system capabilities TLV
type: bool
system_description:
description:
- Used to specify the system description TLV
type: bool
default: true
system_name:
description:
- Used to specify the system name TLV
type: bool
default: true
state:
description:
- The state the configuration should be left in.
type: str
choices:
- merged
- replaced
- deleted
default: merged
"""
EXAMPLES = """
# Using merged
# Before state:
# -------------
# path: /rest/restconf/data/openconfig_lldp:lldp/config
# method: GET
# data:
# {
# "openconfig_lldp:config": {
# "enabled": true,
# "hello-timer": 30,
# "suppress-tlv-advertisement": [
# "PORT_DESCRIPTION",
# "SYSTEM_CAPABILITIES",
# "MANAGEMENT_ADDRESS"
# ],
# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8"
# "system-name": "X460G2-24t-10G4"
# }
# }
- name: Merge provided LLDP configuration with device configuration
exos_lldp_global:
config:
interval: 10000
tlv_select:
system_capabilities: true
state: merged
# Module Execution Results:
# -------------------------
#
# "before": [
# {
# "interval": 30,
# "tlv_select": {
# "system_name": true,
# "system_description": true
# "port_description": false,
# "management_address": false,
# "system_capabilities": false
# }
# }
# ]
#
# "requests": [
# {
# "data": {
# "openconfig_lldp:config": {
# "hello-timer": 10000,
# "suppress-tlv-advertisement": [
# "PORT_DESCRIPTION",
# "MANAGEMENT_ADDRESS"
# ]
# }
# },
# "method": "PATCH",
# "path": "/rest/restconf/data/openconfig_lldp:lldp/config"
# }
# ]
#
# "after": [
# {
# "interval": 10000,
# "tlv_select": {
# "system_name": true,
# "system_description": true,
# "port_description": false,
# "management_address": false,
# "system_capabilities": true
# }
# }
# ]
# After state:
# -------------
# path: /rest/restconf/data/openconfig_lldp:lldp/config
# method: GET
# data:
# {
# "openconfig_lldp:config": {
# "enabled": true,
# "hello-timer": 10000,
# "suppress-tlv-advertisement": [
# "PORT_DESCRIPTION",
# "MANAGEMENT_ADDRESS"
# ],
# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8"
# "system-name": "X460G2-24t-10G4"
# }
# }
# Using replaced
# Before state:
# -------------
# path: /rest/restconf/data/openconfig_lldp:lldp/config
# method: GET
# data:
# {
# "openconfig_lldp:config": {
# "enabled": true,
# "hello-timer": 30,
# "suppress-tlv-advertisement": [
# "PORT_DESCRIPTION",
# "SYSTEM_CAPABILITIES",
# "MANAGEMENT_ADDRESS"
# ],
# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8"
# "system-name": "X460G2-24t-10G4"
# }
# }
- name: Replace device configuration with provided LLDP configuration
exos_lldp_global:
config:
interval: 10000
tlv_select:
system_capabilities: true
state: replaced
# Module Execution Results:
# -------------------------
#
# "before": [
# {
# "interval": 30,
# "tlv_select": {
# "system_name": true,
# "system_description": true
# "port_description": false,
# "management_address": false,
# "system_capabilities": false
# }
# }
# ]
#
# "requests": [
# {
# "data": {
# "openconfig_lldp:config": {
# "hello-timer": 10000,
# "suppress-tlv-advertisement": [
# "SYSTEM_NAME",
# "SYSTEM_DESCRIPTION",
# "PORT_DESCRIPTION",
# "MANAGEMENT_ADDRESS"
# ]
# }
# },
# "method": "PATCH",
# "path": "/rest/restconf/data/openconfig_lldp:lldp/config"
# }
# ]
#
# "after": [
# {
# "interval": 10000,
# "tlv_select": {
# "system_name": false,
# "system_description": false,
# "port_description": false,
# "management_address": false,
# "system_capabilities": true
# }
# }
# ]
# After state:
# -------------
# path: /rest/restconf/data/openconfig_lldp:lldp/config
# method: GET
# data:
# {
# "openconfig_lldp:config": {
# "enabled": true,
# "hello-timer": 10000,
# "suppress-tlv-advertisement": [
# "SYSTEM_NAME",
# "SYSTEM_DESCRIPTION",
# "PORT_DESCRIPTION",
# "MANAGEMENT_ADDRESS"
# ],
# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8"
# "system-name": "X460G2-24t-10G4"
# }
# }
# Using deleted
# Before state:
# -------------
# path: /rest/restconf/data/openconfig_lldp:lldp/config
# method: GET
# data:
# {
# "openconfig_lldp:config": {
# "enabled": true,
# "hello-timer": 10000,
# "suppress-tlv-advertisement": [
# "SYSTEM_CAPABILITIES",
# "MANAGEMENT_ADDRESS"
# ],
# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8"
# "system-name": "X460G2-24t-10G4"
# }
# }
- name: Delete attributes of given LLDP service (This won't delete the LLDP service itself)
exos_lldp_global:
config:
state: deleted
# Module Execution Results:
# -------------------------
#
# "before": [
# {
# "interval": 10000,
# "tlv_select": {
# "system_name": true,
# "system_description": true,
# "port_description": true,
# "management_address": false,
# "system_capabilities": false
# }
# }
# ]
#
# "requests": [
# {
# "data": {
# "openconfig_lldp:config": {
# "hello-timer": 30,
# "suppress-tlv-advertisement": [
# "SYSTEM_CAPABILITIES",
# "PORT_DESCRIPTION",
# "MANAGEMENT_ADDRESS"
# ]
# }
# },
# "method": "PATCH",
# "path": "/rest/restconf/data/openconfig_lldp:lldp/config"
# }
# ]
#
# "after": [
# {
# "interval": 30,
# "tlv_select": {
# "system_name": true,
# "system_description": true,
# "port_description": false,
# "management_address": false,
# "system_capabilities": false
# }
# }
# ]
# After state:
# -------------
# path: /rest/restconf/data/openconfig_lldp:lldp/config
# method: GET
# data:
# {
# "openconfig_lldp:config": {
# "enabled": true,
# "hello-timer": 30,
# "suppress-tlv-advertisement": [
# "SYSTEM_CAPABILITIES",
# "PORT_DESCRIPTION",
# "MANAGEMENT_ADDRESS"
# ],
# "system-description": "ExtremeXOS (X460G2-24t-10G4) version 30.2.1.8"
# "system-name": "X460G2-24t-10G4"
# }
# }
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
sample: >
The configuration returned will always be in the same format
of the parameters above.
type: list
after:
description: The resulting configuration model invocation.
returned: when changed
sample: >
The configuration returned will always be in the same format
of the parameters above.
type: list
requests:
description: The set of requests pushed to the remote device.
returned: always
type: list
sample: [{"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}, {"data": "...", "method": "...", "path": "..."}]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.exos.argspec.lldp_global.lldp_global import Lldp_globalArgs
from ansible.module_utils.network.exos.config.lldp_global.lldp_global import Lldp_global
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
required_if = [('state', 'merged', ('config',)),
('state', 'replaced', ('config',))]
module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec, required_if=required_if,
supports_check_mode=True)
result = Lldp_global(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -0,0 +1,33 @@
#
# Copyright 2015 Peter Sprygada <psprygada@ansible.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
class ActionModule(ActionNetworkModule):
def run(self, tmp=None, task_vars=None):
del tmp # tmp no longer has any effect
if self._play_context.connection != 'httpapi':
return {'failed': True, 'msg': "Connection type %s is not valid for this module" % self._play_context.connection}
return super(ActionModule, self).run(task_vars=task_vars)

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

@ -0,0 +1,19 @@
---
- name: Collect all httpapi test cases
find:
paths: "{{ role_path }}/tests/httpapi"
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 }}"
- name: Run test case (connection=httpapi)
include: "{{ test_case_to_run }}"
vars:
ansible_connection: httpapi
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

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

@ -0,0 +1,7 @@
---
- name: Populate Config
exos_config:
lines:
- configure lldp transmit-interval 50
- configure lldp ports all no-advertise all-tlvs
- configure lldp ports all advertise system-name system-description system-capabilities

@ -0,0 +1,7 @@
---
- name: Reset Config
exos_config:
lines:
- configure lldp transmit-interval 30
- configure lldp ports all no-advertise all-tlvs
- configure lldp ports all advertise system-name system-description

@ -0,0 +1,48 @@
---
- debug:
msg: "Start exos_lldp_global deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _reset_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Delete attributes of LLDP service
exos_lldp_global: &deleted
config:
state: deleted
register: result
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ populate == result['before']}}"
- name: Assert that correct set of requests were generated
assert:
that:
- "{{ deleted['requests'][0]['method'] == result['requests'][0]['method'] }}"
- "{{ deleted['requests'][0]['path'] == result['requests'][0]['path'] }}"
- "{{ deleted['requests'][0]['data'] == result['requests'][0]['data'] }}"
- name: Assert that the after dicts were correctly generated
assert:
that:
- "{{ deleted['after'] == result['after']}}"
- name: Delete attributes of all configured interfaces (IDEMPOTENT)
exos_lldp_global: *deleted
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ deleted['after'] == result['before'] }}"
always:
- include_tasks: _reset_config.yaml

@ -0,0 +1,49 @@
---
- debug:
msg: "START exos_lldp_global merged integration tests on connection={{ ansible_connection }}"
- include_tasks: _reset_config.yaml
- block:
- name: Merge the provided configuration with the exisiting running configuration
exos_lldp_global: &merged
config:
interval: 10
tlv_select:
system_description: false
system_capabilities: false
state: merged
register: result
- name: Assert that before dicts were correctly generated
assert:
that: "{{ merged['before'] == result['before'] }}"
- name: Assert that correct set of requests were generated
assert:
that:
- "{{ merged['requests'][0]['method'] == result['requests'][0]['method'] }}"
- "{{ merged['requests'][0]['path'] == result['requests'][0]['path'] }}"
- "{{ merged['requests'][0]['data'] == result['requests'][0]['data'] }}"
- name: Assert that after dicts was correctly generated
assert:
that:
- "{{ merged['after'] == result['after'] }}"
- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
exos_lldp_global: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- " result['changed'] == false"
- name: Assert that before dicts were correctly generated
assert:
that:
- "{{ merged['after'] == result['before'] }}"
always:
- include_tasks: _reset_config.yaml

@ -0,0 +1,53 @@
---
- debug:
msg: "START exos_lldp_global replaced integration tests on connection={{ ansible_connection }}"
- include_tasks: _reset_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Replace device configurations of LLDP service with provided configurations
exos_lldp_global: &replaced
config:
interval: 20
tlv_select:
system_name: false
system_description: true
system_capabilities: false
state: replaced
register: result
- name: Assert that correct set of results were generated
assert:
that:
- "{{ replaced['requests'][0]['method'] == result['requests'][0]['method'] }}"
- "{{ replaced['requests'][0]['path'] == result['requests'][0]['path'] }}"
- "{{ replaced['requests'][0]['data'] == result['requests'][0]['data'] }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ populate == result['before'] }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ replaced['after'] == result['after'] }}"
- name: Replace device configurations of LLDP service with provided configurations (IDEMPOTENT)
exos_lldp_global: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
- name: Assert that before dict is correctly generated
assert:
that:
- "{{ replaced['after'] == result['before'] }}"
always:
- include_tasks: _reset_config.yaml

@ -0,0 +1,85 @@
---
merged:
before:
interval: 30
tlv_select:
system_name: true
system_description: true
system_capabilities: false
port_description: false
management_address: false
requests:
# 'suppress-tlv-advertisement' list is sorted to compare test
- data: '{"openconfig-lldp:config": {"suppress-tlv-advertisement": ["MANAGEMENT_ADDRESS", "PORT_DESCRIPTION", "SYSTEM_CAPABILITIES", "SYSTEM_DESCRIPTION"], "hello-timer": 10}}'
method: PUT
path: /rest/restconf/data/openconfig-lldp:lldp/config
after:
interval: 10
tlv_select:
system_name: true
system_description: false
system_capabilities: false
port_description: false
management_address: false
populate:
interval: 50
tlv_select:
system_name: true
system_description: true
system_capabilities: true
port_description: false
management_address: false
replaced:
requests:
# 'suppress-tlv-advertisement' list is sorted to compare test
- data: '{"openconfig-lldp:config": {"suppress-tlv-advertisement": ["MANAGEMENT_ADDRESS", "PORT_DESCRIPTION", "SYSTEM_CAPABILITIES", "SYSTEM_NAME"], "hello-timer": 20}}'
method: PUT
path: /rest/restconf/data/openconfig-lldp:lldp/config
after:
interval: 20
tlv_select:
system_name: false
system_description: true
system_capabilities: false
port_description: false
management_address: false
deleted:
before:
interval: 50
tlv_select:
system_name: true
system_description: true
system_capabilities: true
port_description: false
management_address: false
requests:
# 'suppress-tlv-advertisement' list is sorted to compare test
- data: '{"openconfig-lldp:config": {"suppress-tlv-advertisement": ["MANAGEMENT_ADDRESS", "PORT_DESCRIPTION", "SYSTEM_CAPABILITIES"], "hello-timer": 30}}'
method: PUT
path: /rest/restconf/data/openconfig-lldp:lldp/config
after:
interval: 30
tlv_select:
system_name: true
system_description: true
system_capabilities: false
port_description: false
management_address: false
round_trip:
interval: 30
tlv_select:
system_name: true
system_description: true
system_capabilities: false
port_description: false
management_address: false

@ -3636,7 +3636,6 @@ lib/ansible/modules/network/exos/exos_command.py validate-modules:E337
lib/ansible/modules/network/exos/exos_command.py validate-modules:E338
lib/ansible/modules/network/exos/exos_config.py validate-modules:E337
lib/ansible/modules/network/exos/exos_config.py validate-modules:E338
lib/ansible/modules/network/exos/exos_facts.py validate-modules:E337
lib/ansible/modules/network/f5/_bigip_asm_policy.py validate-modules:E337
lib/ansible/modules/network/f5/_bigip_asm_policy.py validate-modules:E338
lib/ansible/modules/network/f5/_bigip_facts.py validate-modules:E337

@ -36,9 +36,12 @@ class TestExosFactsModule(TestExosModule):
def setUp(self):
super(TestExosFactsModule, self).setUp()
self.mock_run_commands = patch('ansible.modules.network.exos.exos_facts.run_commands')
self.mock_run_commands = patch('ansible.module_utils.network.exos.facts.legacy.base.run_commands')
self.run_commands = self.mock_run_commands.start()
self.mock_get_resource_connection = patch('ansible.module_utils.network.common.facts.facts.get_resource_connection')
self.get_resource_connection = self.mock_get_resource_connection.start()
def tearDown(self):
super(TestExosFactsModule, self).tearDown()
self.mock_run_commands.stop()

Loading…
Cancel
Save