1# -*- coding: utf-8 -*- 2# Copyright 2019 Red Hat 3# GNU General Public License v3.0+ 4# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6""" 7The exos legacy fact class 8It is in this file the configuration is collected from the device 9for a given resource, parsed, and the facts tree is populated 10based on the configuration. 11""" 12 13from __future__ import absolute_import, division, print_function 14__metaclass__ = type 15 16 17import re 18import json 19 20from ansible.module_utils.network.exos.exos import run_commands 21from ansible.module_utils.basic import AnsibleModule 22from ansible.module_utils.six import iteritems 23 24 25class FactsBase(object): 26 27 COMMANDS = list() 28 29 def __init__(self, module): 30 self.module = module 31 self.facts = dict() 32 self.warnings = list() 33 self.responses = None 34 35 def populate(self): 36 self.responses = run_commands(self.module, self.COMMANDS) 37 38 def run(self, cmd): 39 return run_commands(self.module, cmd) 40 41 42class Default(FactsBase): 43 44 COMMANDS = [ 45 'show version', 46 'show switch' 47 ] 48 49 def populate(self): 50 super(Default, self).populate() 51 data = self.responses[0] 52 if data: 53 self.facts['version'] = self.parse_version(data) 54 self.facts['serialnum'] = self.parse_serialnum(data) 55 56 data = self.responses[1] 57 if data: 58 self.facts['model'] = self.parse_model(data) 59 self.facts['hostname'] = self.parse_hostname(data) 60 61 def parse_version(self, data): 62 match = re.search(r'Image\s+: ExtremeXOS version (\S+)', data) 63 if match: 64 return match.group(1) 65 66 def parse_model(self, data): 67 match = re.search(r'System Type:\s+(.*$)', data, re.M) 68 if match: 69 return match.group(1) 70 71 def parse_hostname(self, data): 72 match = re.search(r'SysName:\s+(\S+)', data, re.M) 73 if match: 74 return match.group(1) 75 76 def parse_serialnum(self, data): 77 match = re.search(r'Switch\s+: \S+ (\S+)', data, re.M) 78 if match: 79 return match.group(1) 80 # For stack, return serial number of the first switch in the stack. 81 match = re.search(r'Slot-\d+\s+: \S+ (\S+)', data, re.M) 82 if match: 83 return match.group(1) 84 # Handle unique formatting for VM 85 match = re.search(r'Switch\s+: PN:\S+\s+SN:(\S+)', data, re.M) 86 if match: 87 return match.group(1) 88 89 90class Hardware(FactsBase): 91 92 COMMANDS = [ 93 'show memory' 94 ] 95 96 def populate(self): 97 super(Hardware, self).populate() 98 data = self.responses[0] 99 if data: 100 self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0)) 101 self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0)) 102 103 def parse_memtotal(self, data): 104 match = re.search(r' Total DRAM \(KB\): (\d+)', data, re.M) 105 if match: 106 return match.group(1) 107 # Handle unique formatting for VM 108 match = re.search(r' Total \s+\(KB\): (\d+)', data, re.M) 109 if match: 110 return match.group(1) 111 112 def parse_memfree(self, data): 113 match = re.search(r' Free\s+\(KB\): (\d+)', data, re.M) 114 if match: 115 return match.group(1) 116 117 118class Config(FactsBase): 119 120 COMMANDS = ['show configuration detail'] 121 122 def populate(self): 123 super(Config, self).populate() 124 data = self.responses[0] 125 if data: 126 self.facts['config'] = data 127 128 129class Interfaces(FactsBase): 130 131 COMMANDS = [ 132 'show switch', 133 {'command': 'show port config', 'output': 'json'}, 134 {'command': 'show port description', 'output': 'json'}, 135 {'command': 'show vlan detail', 'output': 'json'}, 136 {'command': 'show lldp neighbors', 'output': 'json'} 137 ] 138 139 def populate(self): 140 super(Interfaces, self).populate() 141 142 self.facts['all_ipv4_addresses'] = list() 143 self.facts['all_ipv6_addresses'] = list() 144 145 data = self.responses[0] 146 if data: 147 sysmac = self.parse_sysmac(data) 148 149 data = self.responses[1] 150 if data: 151 self.facts['interfaces'] = self.populate_interfaces(data, sysmac) 152 153 data = self.responses[2] 154 if data: 155 self.populate_interface_descriptions(data) 156 157 data = self.responses[3] 158 if data: 159 self.populate_vlan_interfaces(data, sysmac) 160 161 data = self.responses[4] 162 if data: 163 self.facts['neighbors'] = self.parse_neighbors(data) 164 165 def parse_sysmac(self, data): 166 match = re.search(r'System MAC:\s+(\S+)', data, re.M) 167 if match: 168 return match.group(1) 169 170 def populate_interfaces(self, interfaces, sysmac): 171 facts = dict() 172 for elem in interfaces: 173 intf = dict() 174 175 if 'show_ports_config' not in elem: 176 continue 177 178 key = str(elem['show_ports_config']['port']) 179 180 if elem['show_ports_config']['linkState'] == 2: 181 # Link state is "not present", don't include 182 continue 183 184 intf['type'] = 'Ethernet' 185 intf['macaddress'] = sysmac 186 intf['bandwidth_configured'] = str(elem['show_ports_config']['speedCfg']) 187 intf['bandwidth'] = str(elem['show_ports_config']['speedActual']) 188 intf['duplex_configured'] = elem['show_ports_config']['duplexCfg'] 189 intf['duplex'] = elem['show_ports_config']['duplexActual'] 190 if elem['show_ports_config']['linkState'] == 1: 191 intf['lineprotocol'] = 'up' 192 else: 193 intf['lineprotocol'] = 'down' 194 if elem['show_ports_config']['portState'] == 1: 195 intf['operstatus'] = 'up' 196 else: 197 intf['operstatus'] = 'admin down' 198 199 facts[key] = intf 200 return facts 201 202 def populate_interface_descriptions(self, data): 203 for elem in data: 204 if 'show_ports_description' not in elem: 205 continue 206 key = str(elem['show_ports_description']['port']) 207 208 if 'descriptionString' in elem['show_ports_description']: 209 desc = elem['show_ports_description']['descriptionString'] 210 self.facts['interfaces'][key]['description'] = desc 211 212 def populate_vlan_interfaces(self, data, sysmac): 213 for elem in data: 214 if 'vlanProc' in elem: 215 key = elem['vlanProc']['name1'] 216 if key not in self.facts['interfaces']: 217 intf = dict() 218 intf['type'] = 'VLAN' 219 intf['macaddress'] = sysmac 220 self.facts['interfaces'][key] = intf 221 222 if elem['vlanProc']['ipAddress'] != '0.0.0.0': 223 self.facts['interfaces'][key]['ipv4'] = list() 224 addr = elem['vlanProc']['ipAddress'] 225 subnet = elem['vlanProc']['maskForDisplay'] 226 ipv4 = dict(address=addr, subnet=subnet) 227 self.add_ip_address(addr, 'ipv4') 228 self.facts['interfaces'][key]['ipv4'].append(ipv4) 229 230 if 'rtifIpv6Address' in elem: 231 key = elem['rtifIpv6Address']['rtifName'] 232 if key not in self.facts['interfaces']: 233 intf = dict() 234 intf['type'] = 'VLAN' 235 intf['macaddress'] = sysmac 236 self.facts['interfaces'][key] = intf 237 self.facts['interfaces'][key]['ipv6'] = list() 238 addr, subnet = elem['rtifIpv6Address']['ipv6_address_mask'].split('/') 239 ipv6 = dict(address=addr, subnet=subnet) 240 self.add_ip_address(addr, 'ipv6') 241 self.facts['interfaces'][key]['ipv6'].append(ipv6) 242 243 def add_ip_address(self, address, family): 244 if family == 'ipv4': 245 if address not in self.facts['all_ipv4_addresses']: 246 self.facts['all_ipv4_addresses'].append(address) 247 else: 248 if address not in self.facts['all_ipv6_addresses']: 249 self.facts['all_ipv6_addresses'].append(address) 250 251 def parse_neighbors(self, data): 252 facts = dict() 253 for elem in data: 254 if 'lldpPortNbrInfoShort' not in elem: 255 continue 256 intf = str(elem['lldpPortNbrInfoShort']['port']) 257 if intf not in facts: 258 facts[intf] = list() 259 fact = dict() 260 fact['host'] = elem['lldpPortNbrInfoShort']['nbrSysName'] 261 fact['port'] = str(elem['lldpPortNbrInfoShort']['nbrPortID']) 262 facts[intf].append(fact) 263 return facts 264