1#!/usr/bin/python 2# 3# (c) 2015 Peter Sprygada, <psprygada@ansible.com> 4# Copyright (c) 2016 Dell Inc. 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11ANSIBLE_METADATA = {'metadata_version': '1.1', 12 'status': ['preview'], 13 'supported_by': 'community'} 14 15 16DOCUMENTATION = """ 17--- 18module: dellos6_facts 19version_added: "2.2" 20author: "Abirami N (@abirami-n)" 21short_description: Collect facts from remote devices running Dell EMC Networking OS6 22description: 23 - Collects a base set of device facts from a remote device that 24 is running OS6. This module prepends all of the 25 base network fact keys with C(ansible_net_<fact>). The facts 26 module will always collect a base set of facts from the device 27 and can enable or disable collection of additional facts. 28extends_documentation_fragment: dellos6 29options: 30 gather_subset: 31 description: 32 - When supplied, this argument will restrict the facts collected 33 to a given subset. Possible values for this argument include 34 all, hardware, config, and interfaces. Can specify a list of 35 values to include a larger subset. Values can also be used 36 with an initial C(M(!)) to specify that a specific subset should 37 not be collected. 38 default: [ '!config' ] 39""" 40 41EXAMPLES = """ 42# Collect all facts from the device 43- dellos6_facts: 44 gather_subset: all 45 46# Collect only the config and default facts 47- dellos6_facts: 48 gather_subset: 49 - config 50 51# Do not collect hardware facts 52- dellos6_facts: 53 gather_subset: 54 - "!interfaces" 55""" 56 57RETURN = """ 58ansible_net_gather_subset: 59 description: The list of fact subsets collected from the device. 60 returned: always. 61 type: list 62 63# default 64ansible_net_model: 65 description: The model name returned from the device. 66 returned: always. 67 type: str 68ansible_net_serialnum: 69 description: The serial number of the remote device. 70 returned: always. 71 type: str 72ansible_net_version: 73 description: The operating system version running on the remote device. 74 returned: always. 75 type: str 76ansible_net_hostname: 77 description: The configured hostname of the device. 78 returned: always. 79 type: str 80ansible_net_image: 81 description: The image file that the device is running. 82 returned: always 83 type: str 84 85# hardware 86ansible_net_memfree_mb: 87 description: The available free memory on the remote device in MB. 88 returned: When hardware is configured. 89 type: int 90ansible_net_memtotal_mb: 91 description: The total memory on the remote device in MB. 92 returned: When hardware is configured. 93 type: int 94 95# config 96ansible_net_config: 97 description: The current active config from the device. 98 returned: When config is configured. 99 type: str 100 101# interfaces 102ansible_net_interfaces: 103 description: A hash of all interfaces running on the system. 104 returned: When interfaces is configured. 105 type: dict 106ansible_net_neighbors: 107 description: The list of LLDP neighbors from the remote device. 108 returned: When interfaces is configured. 109 type: dict 110 111""" 112import re 113 114from ansible.module_utils.basic import AnsibleModule 115from ansible.module_utils.network.dellos6.dellos6 import run_commands 116from ansible.module_utils.network.dellos6.dellos6 import dellos6_argument_spec, check_args 117from ansible.module_utils.six import iteritems 118 119 120class FactsBase(object): 121 122 COMMANDS = list() 123 124 def __init__(self, module): 125 self.module = module 126 self.facts = dict() 127 self.responses = None 128 129 def populate(self): 130 self.responses = run_commands(self.module, self.COMMANDS, check_rc=False) 131 132 def run(self, cmd): 133 return run_commands(self.module, cmd, check_rc=False) 134 135 136class Default(FactsBase): 137 138 COMMANDS = [ 139 'show version', 140 'show running-config | include hostname' 141 ] 142 143 def populate(self): 144 super(Default, self).populate() 145 data = self.responses[0] 146 self.facts['version'] = self.parse_version(data) 147 self.facts['serialnum'] = self.parse_serialnum(data) 148 self.facts['model'] = self.parse_model(data) 149 self.facts['image'] = self.parse_image(data) 150 hdata = self.responses[1] 151 self.facts['hostname'] = self.parse_hostname(hdata) 152 153 def parse_version(self, data): 154 facts = dict() 155 match = re.search(r'HW Version(.+)\s(\d+)', data) 156 temp, temp_next = data.split('---- ----------- ----------- -------------- --------------') 157 for en in temp_next.splitlines(): 158 if en == '': 159 continue 160 match_image = re.search(r'^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', en) 161 version = match_image.group(4) 162 facts["Version"] = list() 163 fact = dict() 164 fact['HW Version'] = match.group(2) 165 fact['SW Version'] = match_image.group(4) 166 facts["Version"].append(fact) 167 return facts 168 169 def parse_hostname(self, data): 170 match = re.search(r'\S+\s(\S+)', data, re.M) 171 if match: 172 return match.group(1) 173 174 def parse_model(self, data): 175 match = re.search(r'System Model ID(.+)\s([A-Z0-9]*)\n', data, re.M) 176 if match: 177 return match.group(2) 178 179 def parse_image(self, data): 180 match = re.search(r'Image File(.+)\s([A-Z0-9a-z_.]*)\n', data) 181 if match: 182 return match.group(2) 183 184 def parse_serialnum(self, data): 185 match = re.search(r'Serial Number(.+)\s([A-Z0-9]*)\n', data) 186 if match: 187 return match.group(2) 188 189 190class Hardware(FactsBase): 191 192 COMMANDS = [ 193 'show memory cpu' 194 ] 195 196 def populate(self): 197 super(Hardware, self).populate() 198 data = self.responses[0] 199 match = re.findall(r'\s(\d+)\s', data) 200 if match: 201 self.facts['memtotal_mb'] = int(match[0]) // 1024 202 self.facts['memfree_mb'] = int(match[1]) // 1024 203 204 205class Config(FactsBase): 206 207 COMMANDS = ['show running-config'] 208 209 def populate(self): 210 super(Config, self).populate() 211 self.facts['config'] = self.responses[0] 212 213 214class Interfaces(FactsBase): 215 COMMANDS = [ 216 'show interfaces', 217 'show interfaces status', 218 'show interfaces transceiver properties', 219 'show ip int', 220 'show lldp', 221 'show lldp remote-device all', 222 'show version' 223 ] 224 225 def populate(self): 226 vlan_info = dict() 227 super(Interfaces, self).populate() 228 data = self.responses[0] 229 interfaces = self.parse_interfaces(data) 230 desc = self.responses[1] 231 properties = self.responses[2] 232 vlan = self.responses[3] 233 version_info = self.responses[6] 234 vlan_info = self.parse_vlan(vlan, version_info) 235 self.facts['interfaces'] = self.populate_interfaces(interfaces, desc, properties) 236 self.facts['interfaces'].update(vlan_info) 237 if 'LLDP is not enabled' not in self.responses[4]: 238 neighbors = self.responses[5] 239 self.facts['neighbors'] = self.parse_neighbors(neighbors) 240 241 def parse_vlan(self, vlan, version_info): 242 facts = dict() 243 if "N11" in version_info: 244 match = re.search(r'IP Address(.+)\s([0-9.]*)\n', vlan) 245 mask = re.search(r'Subnet Mask(.+)\s([0-9.]*)\n', vlan) 246 vlan_id_match = re.search(r'Management VLAN ID(.+)\s(\d+)', vlan) 247 vlan_id = "Vl" + vlan_id_match.group(2) 248 if vlan_id not in facts: 249 facts[vlan_id] = list() 250 fact = dict() 251 fact['address'] = match.group(2) 252 fact['masklen'] = mask.group(2) 253 facts[vlan_id].append(fact) 254 else: 255 vlan_info, vlan_info_next = vlan.split('---------- ----- --------------- --------------- -------') 256 for en in vlan_info_next.splitlines(): 257 if en == '': 258 continue 259 match = re.search(r'^(\S+)\s+(\S+)\s+(\S+)', en) 260 intf = match.group(1) 261 if intf not in facts: 262 facts[intf] = list() 263 fact = dict() 264 matc = re.search(r'^([\w+\s\d]*)\s+(\S+)\s+(\S+)', en) 265 fact['address'] = matc.group(2) 266 fact['masklen'] = matc.group(3) 267 facts[intf].append(fact) 268 return facts 269 270 def populate_interfaces(self, interfaces, desc, properties): 271 facts = dict() 272 for key, value in interfaces.items(): 273 intf = dict() 274 intf['description'] = self.parse_description(key, desc) 275 intf['macaddress'] = self.parse_macaddress(value) 276 intf['mtu'] = self.parse_mtu(value) 277 intf['bandwidth'] = self.parse_bandwidth(value) 278 intf['mediatype'] = self.parse_mediatype(key, properties) 279 intf['duplex'] = self.parse_duplex(value) 280 intf['lineprotocol'] = self.parse_lineprotocol(value) 281 intf['operstatus'] = self.parse_operstatus(value) 282 intf['type'] = self.parse_type(key, properties) 283 facts[key] = intf 284 return facts 285 286 def parse_neighbors(self, neighbors): 287 facts = dict() 288 neighbor, neighbor_next = neighbors.split('--------- ------- ------------------- ----------------- -----------------') 289 for en in neighbor_next.splitlines(): 290 if en == '': 291 continue 292 intf = self.parse_lldp_intf(en.split()[0]) 293 if intf not in facts: 294 facts[intf] = list() 295 fact = dict() 296 fact['port'] = self.parse_lldp_port(en.split()[3]) 297 if (len(en.split()) > 4): 298 fact['host'] = self.parse_lldp_host(en.split()[4]) 299 else: 300 fact['host'] = "Null" 301 facts[intf].append(fact) 302 303 return facts 304 305 def parse_interfaces(self, data): 306 parsed = dict() 307 for line in data.split('\n'): 308 if len(line) == 0: 309 continue 310 else: 311 match = re.match(r'Interface Name(.+)\s([A-Za-z0-9/]*)', line) 312 if match: 313 key = match.group(2) 314 parsed[key] = line 315 else: 316 parsed[key] += '\n%s' % line 317 return parsed 318 319 def parse_description(self, key, desc): 320 desc, desc_next = desc.split('--------- --------------- ------ ------- ---- ------ ----- -- -------------------') 321 if desc_next.find('Oob') > 0: 322 desc_val, desc_info = desc_next.split('Oob') 323 elif desc_next.find('Port') > 0: 324 desc_val, desc_info = desc_next.split('Port') 325 for en in desc_val.splitlines(): 326 if key in en: 327 match = re.search(r'^(\S+)\s+(\S+)', en) 328 if match.group(2) in ['Full', 'N/A']: 329 return "Null" 330 else: 331 return match.group(2) 332 333 def parse_macaddress(self, data): 334 match = re.search(r'Burned In MAC Address(.+)\s([A-Z0-9.]*)\n', data) 335 if match: 336 return match.group(2) 337 338 def parse_mtu(self, data): 339 match = re.search(r'MTU Size(.+)\s(\d+)\n', data) 340 if match: 341 return int(match.group(2)) 342 343 def parse_bandwidth(self, data): 344 match = re.search(r'Port Speed\s*[:\s\.]+\s(\d+)\n', data) 345 if match: 346 return int(match.group(1)) 347 348 def parse_duplex(self, data): 349 match = re.search(r'Port Mode\s([A-Za-z]*)(.+)\s([A-Za-z/]*)\n', data) 350 if match: 351 return match.group(3) 352 353 def parse_mediatype(self, key, properties): 354 mediatype, mediatype_next = properties.split('--------- ------- --------------------- --------------------- --------------') 355 flag = 1 356 for en in mediatype_next.splitlines(): 357 if key in en: 358 flag = 0 359 match = re.search(r'^(\S+)\s+(\S+)\s+(\S+)', en) 360 if match: 361 strval = match.group(3) 362 return strval 363 if flag == 1: 364 return "null" 365 366 def parse_type(self, key, properties): 367 type_val, type_val_next = properties.split('--------- ------- --------------------- --------------------- --------------') 368 flag = 1 369 for en in type_val_next.splitlines(): 370 if key in en: 371 flag = 0 372 match = re.search(r'^(\S+)\s+(\S+)\s+(\S+)', en) 373 if match: 374 strval = match.group(2) 375 return strval 376 if flag == 1: 377 return "null" 378 379 def parse_lineprotocol(self, data): 380 data = data.splitlines() 381 for d in data: 382 match = re.search(r'^Link Status\s*[:\s\.]+\s(\S+)', d) 383 if match: 384 return match.group(1) 385 386 def parse_operstatus(self, data): 387 data = data.splitlines() 388 for d in data: 389 match = re.search(r'^Link Status\s*[:\s\.]+\s(\S+)', d) 390 if match: 391 return match.group(1) 392 393 def parse_lldp_intf(self, data): 394 match = re.search(r'^([A-Za-z0-9/]*)', data) 395 if match: 396 return match.group(1) 397 398 def parse_lldp_host(self, data): 399 match = re.search(r'^([A-Za-z0-9-]*)', data) 400 if match: 401 return match.group(1) 402 403 def parse_lldp_port(self, data): 404 match = re.search(r'^([A-Za-z0-9/]*)', data) 405 if match: 406 return match.group(1) 407 408 409FACT_SUBSETS = dict( 410 default=Default, 411 hardware=Hardware, 412 interfaces=Interfaces, 413 config=Config, 414) 415 416VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) 417 418 419def main(): 420 """main entry point for module execution 421 """ 422 argument_spec = dict( 423 gather_subset=dict(default=['!config'], type='list') 424 ) 425 426 argument_spec.update(dellos6_argument_spec) 427 428 module = AnsibleModule(argument_spec=argument_spec, 429 supports_check_mode=True) 430 431 gather_subset = module.params['gather_subset'] 432 433 runable_subsets = set() 434 exclude_subsets = set() 435 436 for subset in gather_subset: 437 if subset == 'all': 438 runable_subsets.update(VALID_SUBSETS) 439 continue 440 441 if subset.startswith('!'): 442 subset = subset[1:] 443 if subset == 'all': 444 exclude_subsets.update(VALID_SUBSETS) 445 continue 446 exclude = True 447 else: 448 exclude = False 449 450 if subset not in VALID_SUBSETS: 451 module.fail_json(msg='Bad subset') 452 453 if exclude: 454 exclude_subsets.add(subset) 455 else: 456 runable_subsets.add(subset) 457 458 if not runable_subsets: 459 runable_subsets.update(VALID_SUBSETS) 460 461 runable_subsets.difference_update(exclude_subsets) 462 runable_subsets.add('default') 463 464 facts = dict() 465 facts['gather_subset'] = list(runable_subsets) 466 467 instances = list() 468 for key in runable_subsets: 469 instances.append(FACT_SUBSETS[key](module)) 470 471 for inst in instances: 472 inst.populate() 473 facts.update(inst.facts) 474 475 ansible_facts = dict() 476 for key, value in iteritems(facts): 477 key = 'ansible_net_%s' % key 478 ansible_facts[key] = value 479 480 warnings = list() 481 check_args(module, warnings) 482 483 module.exit_json(ansible_facts=ansible_facts, warnings=warnings) 484 485 486if __name__ == '__main__': 487 main() 488