network/exos: add exos_facts module (#43210)
Add exos_facts module. Known limitations at this time include: - Interface MTU is not reported. - Only primary interface IP is reported. Add basic unit tests for the exos_facts module. An EXOS CLI prompt can be prefixed with '! ' (shutting down), '* ' (running configuration does not match saved configuration), and can include various status tokens within parentheses after these prefixes. Update prompt regex to accept valid CLI prompts.
This commit is contained in:
parent
e24c036057
commit
bd4d68c785
10 changed files with 697 additions and 1 deletions
450
lib/ansible/modules/network/exos/exos_facts.py
Normal file
450
lib/ansible/modules/network/exos/exos_facts.py
Normal file
|
@ -0,0 +1,450 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# (c) 2018 Extreme Networks Inc.
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: exos_facts
|
||||
version_added: "2.7"
|
||||
author: "Lance Richardson (@hlrichardson)"
|
||||
short_description: Collect facts from devices running Extreme EXOS
|
||||
description:
|
||||
- Collects a base set of device facts from a remote device that
|
||||
is running EXOS. This module prepends all of the base network
|
||||
fact keys with C(ansible_net_<fact>). The facts module will
|
||||
always collect a base set of facts from the device and can
|
||||
enable or disable collection of additional facts.
|
||||
notes:
|
||||
- Tested against EXOS 22.5.1.7
|
||||
options:
|
||||
gather_subset:
|
||||
description:
|
||||
- When supplied, this argument will restrict the facts collected
|
||||
to a given subset. Possible values for this argument include
|
||||
all, hardware, config, and interfaces. Can specify a list of
|
||||
values to include a larger subset. Values can also be used
|
||||
with an initial C(M(!)) to specify that a specific subset should
|
||||
not be collected.
|
||||
required: false
|
||||
default: ['!config']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: collect all facts from the device
|
||||
exos_facts:
|
||||
gather_subset: all
|
||||
|
||||
- name: collect only the config and default facts
|
||||
exos_facts:
|
||||
gather_subset: config
|
||||
|
||||
- name: do not collect hardware facts
|
||||
exos_facts:
|
||||
gather_subset: "!hardware"
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
ansible_net_gather_subset:
|
||||
description: The list of fact subsets collected from the device
|
||||
returned: always
|
||||
type: list
|
||||
|
||||
# default
|
||||
ansible_net_model:
|
||||
description: The model name returned from the device
|
||||
returned: always
|
||||
type: str
|
||||
ansible_net_serialnum:
|
||||
description: The serial number of the remote device
|
||||
returned: always
|
||||
type: str
|
||||
ansible_net_version:
|
||||
description: The operating system version running on the remote device
|
||||
returned: always
|
||||
type: str
|
||||
ansible_net_hostname:
|
||||
description: The configured hostname of the device
|
||||
returned: always
|
||||
type: string
|
||||
|
||||
# hardware
|
||||
ansible_net_memfree_mb:
|
||||
description: The available free memory on the remote device in Mb
|
||||
returned: when hardware is configured
|
||||
type: int
|
||||
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
|
||||
returned: when interfaces is configured
|
||||
type: list
|
||||
ansible_net_all_ipv6_addresses:
|
||||
description: All Primary IPv6 addresses configured on the device
|
||||
returned: when interfaces is configured
|
||||
type: list
|
||||
ansible_net_interfaces:
|
||||
description: A hash of all interfaces running on the system
|
||||
returned: when interfaces is configured
|
||||
type: dict
|
||||
ansible_net_neighbors:
|
||||
description: The list of LLDP neighbors from the remote device
|
||||
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',
|
||||
'run script cli2json.py show port config',
|
||||
'run script cli2json.py show port description',
|
||||
'run script cli2json.py show vlan detail',
|
||||
'run script cli2json.py show lldp neighbors'
|
||||
]
|
||||
|
||||
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 = json.loads(self.responses[1])
|
||||
if data:
|
||||
self.facts['interfaces'] = self.populate_interfaces(data, sysmac)
|
||||
|
||||
data = json.loads(self.responses[2])
|
||||
if data:
|
||||
self.populate_interface_descriptions(data)
|
||||
|
||||
data = json.loads(self.responses[3])
|
||||
if data:
|
||||
self.populate_vlan_interfaces(data, sysmac)
|
||||
|
||||
data = json.loads(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):
|
||||
facts = dict()
|
||||
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())
|
||||
|
||||
|
||||
def main():
|
||||
"""main entry point for module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
gather_subset=dict(default=["!config"], type='list')
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
ansible_facts = dict()
|
||||
for key, value in iteritems(facts):
|
||||
key = 'ansible_net_%s' % key
|
||||
ansible_facts[key] = value
|
||||
|
||||
warnings = list()
|
||||
|
||||
module.exit_json(ansible_facts=ansible_facts)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -28,7 +28,7 @@ from ansible.plugins.terminal import TerminalBase
|
|||
class TerminalModule(TerminalBase):
|
||||
|
||||
terminal_stdout_re = [
|
||||
re.compile(br"[\r\n][\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3} (?:[>#]) ?$")
|
||||
re.compile(br"[\r\n](?:! )?(?:\* )?(?:\(.*\) )?(?:Slot-\d+ )?\S+\.\d+ (?:[>#]) ?$")
|
||||
]
|
||||
|
||||
terminal_stderr_re = [
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
[{"CLIoutput": "\n Neighbor Neighbor Neighbor\nPort Chassis ID Port ID TTL Age System Name\n===============================================================================\n1 00:02:02:02:02:02 1 120 26 EXOS-VM\n2 00:02:02:02:02:02 2 120 25 EXOS-VM\n3 00:02:02:02:02:02 3 120 25 EXOS-VM\n===============================================================================\nNOTE: The Chassis ID and/or Port ID might be truncated to fit the screen.\n\n"}, {"lldpPortNbrInfoShort": {"age": 26, "lastUpdate": 8412, "nbrChassisID": "00:02:02:02:02:02", "nbrChassisIdType": 4, "nbrIndex": 1, "nbrPortDescr": "Not-Advertised", "nbrPortID": 1, "nbrPortIdType": 5, "nbrSysDescr": "ExtremeXOS (EXOS-VM) version 30.1.0.27 xos_30.1 by lrichardson on Mon Apr 30 13:38:10 EDT 2018", "nbrSysName": "EXOS-VM", "nbrsOnThisPort": 1, "port": 1, "ttl": 120}, "status": "MORE"}, {"lldpPortNbrInfoShort": {"age": 25, "lastUpdate": 8412, "nbrChassisID": "00:02:02:02:02:02", "nbrChassisIdType": 4, "nbrIndex": 1, "nbrPortDescr": "Not-Advertised", "nbrPortID": 2, "nbrPortIdType": 5, "nbrSysDescr": "ExtremeXOS (EXOS-VM) version 30.1.0.27 xos_30.1 by lrichardson on Mon Apr 30 13:38:10 EDT 2018", "nbrSysName": "EXOS-VM", "nbrsOnThisPort": 1, "port": 2, "ttl": 120}, "status": "MORE"}, {"lldpPortNbrInfoShort": {"age": 25, "lastUpdate": 8417, "nbrChassisID": "00:02:02:02:02:02", "nbrChassisIdType": 4, "nbrIndex": 1, "nbrPortDescr": "Not-Advertised", "nbrPortID": 3, "nbrPortIdType": 5, "nbrSysDescr": "ExtremeXOS (EXOS-VM) version 30.1.0.27 xos_30.1 by lrichardson on Mon Apr 30 13:38:10 EDT 2018", "nbrSysName": "EXOS-VM", "nbrsOnThisPort": 1, "port": 3, "ttl": 120}, "status": "MORE"}, {"status": "SUCCESS"}]
|
|
@ -0,0 +1 @@
|
|||
[{"CLIoutput": "Port Configuration\nPort Virtual Port Link Auto Speed Duplex Flow Load Media\n router State State Neg Cfg Actual Cfg Actual Cntrl Master Pri Red\n================================================================================\n1 VR-Default E R OFF 25000 FULL NONE \n2 VR-Default E R OFF 25000 FULL NONE \n3 VR-Default E R OFF 25000 FULL NONE \n4 VR-Default E R OFF 25000 FULL NONE \n================================================================================\n> indicates Port Display Name truncated past 8 characters\nLink State: A-Active, R-Ready, NP-Port Not Present, L-Loopback\nPort State: D-Disabled, E-Enabled, L-License Disabled\nMedia: !-Unsupported, $-Unlicensed\nMedia Red: * - use \"show port info detail\" for redundant media type\nFlow Cntrl: Shows link partner's abilities. NONE if Auto Neg is OFF\n"}, {"show_ports_config": {"duplexActual": null, "duplexCfg": "FULL", "flowControl": null, "isAutoNegOn": 0, "licenseDisable": 0, "linkState": 0, "port": 1, "portList": "1-4", "portState": 1, "primaryMedia": " NONE", "speedActual": null, "speedCfg": 25000, "vrName": "VR-Default"}, "status": "MORE"}, {"show_ports_config": {"duplexActual": null, "duplexCfg": "FULL", "flowControl": null, "isAutoNegOn": 0, "licenseDisable": 0, "linkState": 0, "port": 2, "portList": "1-4", "portState": 1, "primaryMedia": " NONE", "speedActual": null, "speedCfg": 25000, "vrName": "VR-Default"}, "status": "MORE"}, {"show_ports_config": {"duplexActual": null, "duplexCfg": "FULL", "flowControl": null, "isAutoNegOn": 0, "licenseDisable": 0, "linkState": 0, "port": 3, "portList": "1-4", "portState": 1, "primaryMedia": " NONE", "speedActual": null, "speedCfg": 25000, "vrName": "VR-Default"}, "status": "MORE"}, {"show_ports_config": {"duplexActual": null, "duplexCfg": "FULL", "flowControl": null, "isAutoNegOn": 0, "licenseDisable": 0, "linkState": 0, "port": 4, "portList": "1-4", "portState": 1, "primaryMedia": " NONE", "speedActual": null, "speedCfg": 25000, "vrName": "VR-Default"}, "status": "SUCCESS"}]
|
|
@ -0,0 +1,2 @@
|
|||
[{"CLIoutput": "Port Display String Description String \n===== ==================== ==================================================\n1 Firewall\n2 \n3 Database Server\n4 \n===== ==================== ==================================================\n"}, {"show_ports_description": {"descriptionString": "Firewall", "port": 1, "portList": "1-4"}, "status": "MORE"}, {"show_ports_description": {"port": 2, "portList": "1-4"}, "status": "MORE"}, {"show_ports_description": {"descriptionString": "Database Server", "port": 3, "portList": "1-4"}, "status": "MORE"}, {"show_ports_description": {"port": 4, "portList": "1-4"}, "status": "SUCCESS"}]
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
95
test/units/modules/network/exos/fixtures/show_memory
Normal file
95
test/units/modules/network/exos/fixtures/show_memory
Normal file
|
@ -0,0 +1,95 @@
|
|||
|
||||
System Memory Information
|
||||
-------------------------
|
||||
Total DRAM (KB): 8388608
|
||||
System (KB): 357088
|
||||
User (KB): 558460
|
||||
Free (KB): 7473060
|
||||
|
||||
Memory Utilization Statistics
|
||||
-----------------------------
|
||||
|
||||
Process Name Memory (KB)
|
||||
-----------------------------
|
||||
aaa 2212
|
||||
acl 1637
|
||||
bfd 1158
|
||||
bgp 10031
|
||||
brm 822
|
||||
cfgmgr 2466
|
||||
cli 16169
|
||||
devmgr 884
|
||||
dirser 463
|
||||
dosprotect 570
|
||||
dot1ag 1370
|
||||
eaps 1359
|
||||
edp 1260
|
||||
elrp 1250
|
||||
elsm 917
|
||||
ems 3196
|
||||
epm 1646
|
||||
erps 1282
|
||||
esrp 1101
|
||||
ethoam 858
|
||||
etmon 7865
|
||||
exacl 0
|
||||
exdhcpsnoop 0
|
||||
exdos 0
|
||||
exfib 0
|
||||
exnasnoop 0
|
||||
exosmc 0
|
||||
exosq 0
|
||||
expolicy 0
|
||||
exsflow 0
|
||||
exsnoop 0
|
||||
exsshd 1522
|
||||
exvlan 0
|
||||
fdb 1990
|
||||
hal 141451
|
||||
hclag 899
|
||||
idMgr 3448
|
||||
ipSecurity 1042
|
||||
ipfix 956
|
||||
isis 1403
|
||||
ismb 0
|
||||
lacp 1306
|
||||
lldp 1724
|
||||
mcmgr 2183
|
||||
mpls 0
|
||||
mrp 1482
|
||||
msdp 915
|
||||
netLogin 1641
|
||||
netTools 4336
|
||||
nettx 0
|
||||
nodealias 1847
|
||||
nodemgr 501
|
||||
ntp 812
|
||||
openflow 0
|
||||
ospf 1455
|
||||
ospfv3 5130
|
||||
otm 1095
|
||||
ovsdb 8206
|
||||
pim 2100
|
||||
polMgr 479
|
||||
policy 45998
|
||||
pwmib 458
|
||||
rip 1000
|
||||
ripng 739
|
||||
rtmgr 2679
|
||||
snmpMaster 2798
|
||||
snmpSubagent 5728
|
||||
stp 2020
|
||||
techSupport 681
|
||||
telnetd 890
|
||||
tftpd 336
|
||||
throw 5262
|
||||
thttpd 8944
|
||||
twamp 471
|
||||
upm 859
|
||||
vlan 3215
|
||||
vmt 1599
|
||||
vpex 1771
|
||||
vrrp 1185
|
||||
vsm 1486
|
||||
xmlc 1013
|
||||
xmld 3468
|
33
test/units/modules/network/exos/fixtures/show_switch
Normal file
33
test/units/modules/network/exos/fixtures/show_switch
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
SysName: X870-32c
|
||||
SysLocation:
|
||||
SysContact: support@extremenetworks.com, +1 888 257 3000
|
||||
System MAC: 00:04:96:9A:B4:F7
|
||||
System Type: X870-32c
|
||||
|
||||
SysHealth check: Enabled (Normal)
|
||||
Recovery Mode: All
|
||||
System Watchdog: Enabled
|
||||
|
||||
Current Time: Wed Jul 18 12:44:49 2018
|
||||
Timezone: [Auto DST Disabled] GMT Offset: 0 minutes, name is UTC.
|
||||
Boot Time: Tue Jul 17 12:49:58 2018
|
||||
Boot Count: 4970
|
||||
Next Reboot: None scheduled
|
||||
System UpTime: 23 hours 54 minutes 50 seconds
|
||||
|
||||
Current State: OPERATIONAL
|
||||
Image Selected: secondary
|
||||
Image Booted: secondary
|
||||
Primary ver: 30.1.0.37
|
||||
Secondary ver: 22.5.1.7
|
||||
|
||||
Config Selected: primary.cfg
|
||||
Config Booted: primary.cfg
|
||||
Config Automatic: NONE (Disabled)
|
||||
|
||||
primary.cfg Created by ExtremeXOS version 22.6.0.11
|
||||
983139 bytes saved on Wed Jun 6 16:59:49 2018
|
||||
|
||||
LAA MAC: Locally Administered MAC Address Disabled
|
||||
|
111
test/units/modules/network/exos/test_exos_facts.py
Normal file
111
test/units/modules/network/exos/test_exos_facts.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
#
|
||||
# (c) 2018 Extreme Networks Inc.
|
||||
#
|
||||
# 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
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from ansible.compat.tests.mock import patch
|
||||
from ansible.modules.network.exos import exos_facts
|
||||
from units.modules.utils import set_module_args
|
||||
from .exos_module import TestExosModule, load_fixture
|
||||
|
||||
|
||||
class TestExosFactsModule(TestExosModule):
|
||||
|
||||
module = exos_facts
|
||||
|
||||
def setUp(self):
|
||||
super(TestExosFactsModule, self).setUp()
|
||||
|
||||
self.mock_run_commands = patch('ansible.modules.network.exos.exos_facts.run_commands')
|
||||
self.run_commands = self.mock_run_commands.start()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestExosFactsModule, self).tearDown()
|
||||
self.mock_run_commands.stop()
|
||||
|
||||
def load_fixtures(self, commands=None):
|
||||
|
||||
def load_from_file(*args, **kwargs):
|
||||
module, commands = args
|
||||
output = list()
|
||||
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
|
||||
|
||||
for command in commands:
|
||||
filename = str(command).replace(' ', '_')
|
||||
filename = os.path.join(fixture_path, filename)
|
||||
with open(filename) as f:
|
||||
data = f.read()
|
||||
output.append(data)
|
||||
return output
|
||||
|
||||
self.run_commands.side_effect = load_from_file
|
||||
|
||||
def test_exos_facts_default(self):
|
||||
set_module_args(dict(gather_subset='default'))
|
||||
result = self.execute_module()
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_model'], 'X870-32c'
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_serialnum'], '1604G-00175'
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_version'], '22.5.1.7'
|
||||
)
|
||||
|
||||
def test_exos_facts_hardware(self):
|
||||
set_module_args(dict(gather_subset='hardware'))
|
||||
result = self.execute_module()
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_memfree_mb'], 7298
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_memtotal_mb'], 8192
|
||||
)
|
||||
|
||||
def test_exos_facts_interfaces(self):
|
||||
set_module_args(dict(gather_subset='interfaces'))
|
||||
result = self.execute_module()
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_interfaces']['1']['bandwidth_configured'], '25000'
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_interfaces']['3']['description'], 'Database Server'
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_interfaces']['3']['type'], 'Ethernet'
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_interfaces']['vlan1']['ipv4'][0]['address'], '10.0.1.1'
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_interfaces']['vlan3']['ipv6'][0]['address'], 'fe80::202:b3ff:fe1e:8329'
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_all_ipv4_addresses'], ['10.0.1.1', '192.168.1.1']
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_all_ipv6_addresses'], ['fe80::202:b3ff:fe1e:8329']
|
||||
)
|
||||
self.assertEqual(
|
||||
result['ansible_facts']['ansible_net_interfaces']['vlan3']['type'], 'VLAN'
|
||||
)
|
Loading…
Reference in a new issue