1#!/usr/bin/python 2 3# (c) 2016, NetApp, Inc 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import absolute_import, division, print_function 7__metaclass__ = type 8 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14DOCUMENTATION = ''' 15module: netapp_e_facts 16short_description: NetApp E-Series retrieve facts about NetApp E-Series storage arrays 17description: 18 - The netapp_e_facts module returns a collection of facts regarding NetApp E-Series storage arrays. 19 - When contacting a storage array directly the collection includes details about the array, controllers, management 20 interfaces, hostside interfaces, driveside interfaces, disks, storage pools, volumes, snapshots, and features. 21 - When contacting a web services proxy the collection will include basic information regarding the storage systems 22 that are under its management. 23version_added: '2.2' 24author: 25 - Kevin Hulquest (@hulquest) 26 - Nathan Swartz (@ndswartz) 27extends_documentation_fragment: 28 - netapp.eseries 29''' 30 31EXAMPLES = """ 32--- 33- name: Get array facts 34 netapp_e_facts: 35 ssid: "{{ netapp_array_id }}" 36 api_url: "https://{{ netapp_e_api_host }}:8443/devmgr/v2" 37 api_username: "{{ netapp_api_username }}" 38 api_password: "{{ netapp_api_password }}" 39 validate_certs: "{{ netapp_api_validate_certs }}" 40- name: Get array facts 41 netapp_e_facts: 42 ssid: 1 43 api_url: https://192.168.1.100:8443/devmgr/v2 44 api_username: myApiUser 45 api_password: myApiPass 46 validate_certs: true 47""" 48 49RETURN = """ 50 msg: 51 description: Success message 52 returned: on success 53 type: str 54 sample: 55 - Gathered facts for storage array. Array ID [1]. 56 - Gathered facts for web services proxy. 57 storage_array_facts: 58 description: provides details about the array, controllers, management interfaces, hostside interfaces, 59 driveside interfaces, disks, storage pools, volumes, snapshots, and features. 60 returned: on successful inquiry from from embedded web services rest api 61 type: complex 62 contains: 63 netapp_controllers: 64 description: storage array controller list that contains basic controller identification and status 65 type: complex 66 sample: 67 - [{"name": "A", "serial": "021632007299", "status": "optimal"}, 68 {"name": "B", "serial": "021632007300", "status": "failed"}] 69 netapp_disks: 70 description: drive list that contains identification, type, and status information for each drive 71 type: complex 72 sample: 73 - [{"available": false, 74 "firmware_version": "MS02", 75 "id": "01000000500003960C8B67880000000000000000", 76 "media_type": "ssd", 77 "product_id": "PX02SMU080 ", 78 "serial_number": "15R0A08LT2BA", 79 "status": "optimal", 80 "tray_ref": "0E00000000000000000000000000000000000000", 81 "usable_bytes": "799629205504" }] 82 netapp_driveside_interfaces: 83 description: drive side interface list that contains identification, type, and speed for each interface 84 type: complex 85 sample: 86 - [{ "controller": "A", "interface_speed": "12g", "interface_type": "sas" }] 87 - [{ "controller": "B", "interface_speed": "10g", "interface_type": "iscsi" }] 88 netapp_enabled_features: 89 description: specifies the enabled features on the storage array. 90 returned: on success 91 type: complex 92 sample: 93 - [ "flashReadCache", "performanceTier", "protectionInformation", "secureVolume" ] 94 netapp_host_groups: 95 description: specifies the host groups on the storage arrays. 96 returned: on success 97 type: complex 98 sample: 99 - [{ "id": "85000000600A098000A4B28D003610705C40B964", "name": "group1" }] 100 netapp_hosts: 101 description: specifies the hosts on the storage arrays. 102 returned: on success 103 type: complex 104 sample: 105 - [{ "id": "8203800000000000000000000000000000000000", 106 "name": "host1", 107 "group_id": "85000000600A098000A4B28D003610705C40B964", 108 "host_type_index": 28, 109 "ports": [{ "type": "fc", "address": "1000FF7CFFFFFF01", "label": "FC_1" }, 110 { "type": "fc", "address": "1000FF7CFFFFFF00", "label": "FC_2" }]}] 111 netapp_host_types: 112 description: lists the available host types on the storage array. 113 returned: on success 114 type: complex 115 sample: 116 - [{ "index": 0, "type": "FactoryDefault" }, 117 { "index": 1, "type": "W2KNETNCL"}, 118 { "index": 2, "type": "SOL" }, 119 { "index": 5, "type": "AVT_4M" }, 120 { "index": 6, "type": "LNX" }, 121 { "index": 7, "type": "LnxALUA" }, 122 { "index": 8, "type": "W2KNETCL" }, 123 { "index": 9, "type": "AIX MPIO" }, 124 { "index": 10, "type": "VmwTPGSALUA" }, 125 { "index": 15, "type": "HPXTPGS" }, 126 { "index": 17, "type": "SolTPGSALUA" }, 127 { "index": 18, "type": "SVC" }, 128 { "index": 22, "type": "MacTPGSALUA" }, 129 { "index": 23, "type": "WinTPGSALUA" }, 130 { "index": 24, "type": "LnxTPGSALUA" }, 131 { "index": 25, "type": "LnxTPGSALUA_PM" }, 132 { "index": 26, "type": "ONTAP_ALUA" }, 133 { "index": 27, "type": "LnxTPGSALUA_SF" }, 134 { "index": 28, "type": "LnxDHALUA" }, 135 { "index": 29, "type": "ATTOClusterAllOS" }] 136 netapp_hostside_interfaces: 137 description: host side interface list that contains identification, configuration, type, speed, and 138 status information for each interface 139 type: complex 140 sample: 141 - [{"iscsi": 142 [{ "controller": "A", 143 "current_interface_speed": "10g", 144 "ipv4_address": "10.10.10.1", 145 "ipv4_enabled": true, 146 "ipv4_gateway": "10.10.10.1", 147 "ipv4_subnet_mask": "255.255.255.0", 148 "ipv6_enabled": false, 149 "iqn": "iqn.1996-03.com.netapp:2806.600a098000a81b6d0000000059d60c76", 150 "link_status": "up", 151 "mtu": 9000, 152 "supported_interface_speeds": [ "10g" ] }]}] 153 netapp_management_interfaces: 154 description: management interface list that contains identification, configuration, and status for 155 each interface 156 type: complex 157 sample: 158 - [{"alias": "ict-2800-A", 159 "channel": 1, 160 "controller": "A", 161 "dns_config_method": "dhcp", 162 "dns_servers": [], 163 "ipv4_address": "10.1.1.1", 164 "ipv4_address_config_method": "static", 165 "ipv4_enabled": true, 166 "ipv4_gateway": "10.113.1.1", 167 "ipv4_subnet_mask": "255.255.255.0", 168 "ipv6_enabled": false, 169 "link_status": "up", 170 "mac_address": "00A098A81B5D", 171 "name": "wan0", 172 "ntp_config_method": "disabled", 173 "ntp_servers": [], 174 "remote_ssh_access": false }] 175 netapp_storage_array: 176 description: provides storage array identification, firmware version, and available capabilities 177 type: dict 178 sample: 179 - {"chassis_serial": "021540006043", 180 "firmware": "08.40.00.01", 181 "name": "ict-2800-11_40", 182 "wwn": "600A098000A81B5D0000000059D60C76", 183 "cacheBlockSizes": [4096, 184 8192, 185 16384, 186 32768], 187 "supportedSegSizes": [8192, 188 16384, 189 32768, 190 65536, 191 131072, 192 262144, 193 524288]} 194 netapp_storage_pools: 195 description: storage pool list that contains identification and capacity information for each pool 196 type: complex 197 sample: 198 - [{"available_capacity": "3490353782784", 199 "id": "04000000600A098000A81B5D000002B45A953A61", 200 "name": "Raid6", 201 "total_capacity": "5399466745856", 202 "used_capacity": "1909112963072" }] 203 netapp_volumes: 204 description: storage volume list that contains identification and capacity information for each volume 205 type: complex 206 sample: 207 - [{"capacity": "5368709120", 208 "id": "02000000600A098000AAC0C3000002C45A952BAA", 209 "is_thin_provisioned": false, 210 "name": "5G", 211 "parent_storage_pool_id": "04000000600A098000A81B5D000002B45A953A61" }] 212 netapp_workload_tags: 213 description: workload tag list 214 type: complex 215 sample: 216 - [{"id": "87e19568-43fb-4d8d-99ea-2811daaa2b38", 217 "name": "ftp_server", 218 "workloadAttributes": [{"key": "use", 219 "value": "general"}]}] 220 netapp_volumes_by_initiators: 221 description: list of available volumes keyed by the mapped initiators. 222 type: complex 223 sample: 224 - {"192_168_1_1": [{"id": "02000000600A098000A4B9D1000015FD5C8F7F9E", 225 "meta_data": {"filetype": "xfs", "public": true}, 226 "name": "some_volume", 227 "workload_name": "test2_volumes", 228 "wwn": "600A098000A4B9D1000015FD5C8F7F9E"}]} 229 snapshot_images: 230 description: snapshot image list that contains identification, capacity, and status information for each 231 snapshot image 232 type: complex 233 sample: 234 - [{"active_cow": true, 235 "creation_method": "user", 236 "id": "34000000600A098000A81B5D00630A965B0535AC", 237 "pit_capacity": "5368709120", 238 "reposity_cap_utilization": "0", 239 "rollback_source": false, 240 "status": "optimal" }] 241""" 242 243from re import match 244from pprint import pformat 245from ansible.module_utils.netapp import NetAppESeriesModule 246 247 248class Facts(NetAppESeriesModule): 249 def __init__(self): 250 web_services_version = "02.00.0000.0000" 251 super(Facts, self).__init__(ansible_options={}, 252 web_services_version=web_services_version, 253 supports_check_mode=True) 254 255 def get_controllers(self): 256 """Retrieve a mapping of controller references to their labels.""" 257 controllers = list() 258 try: 259 rc, controllers = self.request('storage-systems/%s/graph/xpath-filter?query=/controller/id' % self.ssid) 260 except Exception as err: 261 self.module.fail_json( 262 msg="Failed to retrieve controller list! Array Id [%s]. Error [%s]." 263 % (self.ssid, str(err))) 264 265 controllers.sort() 266 267 controllers_dict = {} 268 i = ord('A') 269 for controller in controllers: 270 label = chr(i) 271 controllers_dict[controller] = label 272 i += 1 273 274 return controllers_dict 275 276 def get_array_facts(self): 277 """Extract particular facts from the storage array graph""" 278 facts = dict(facts_from_proxy=False, ssid=self.ssid) 279 controller_reference_label = self.get_controllers() 280 array_facts = None 281 282 # Get the storage array graph 283 try: 284 rc, array_facts = self.request("storage-systems/%s/graph" % self.ssid) 285 except Exception as error: 286 self.module.fail_json(msg="Failed to obtain facts from storage array with id [%s]. Error [%s]" 287 % (self.ssid, str(error))) 288 289 facts['netapp_storage_array'] = dict( 290 name=array_facts['sa']['saData']['storageArrayLabel'], 291 chassis_serial=array_facts['sa']['saData']['chassisSerialNumber'], 292 firmware=array_facts['sa']['saData']['fwVersion'], 293 wwn=array_facts['sa']['saData']['saId']['worldWideName'], 294 segment_sizes=array_facts['sa']['featureParameters']['supportedSegSizes'], 295 cache_block_sizes=array_facts['sa']['featureParameters']['cacheBlockSizes']) 296 297 facts['netapp_controllers'] = [ 298 dict( 299 name=controller_reference_label[controller['controllerRef']], 300 serial=controller['serialNumber'].strip(), 301 status=controller['status'], 302 ) for controller in array_facts['controller']] 303 304 facts['netapp_host_groups'] = [ 305 dict( 306 id=group['id'], 307 name=group['name'] 308 ) for group in array_facts['storagePoolBundle']['cluster']] 309 310 facts['netapp_hosts'] = [ 311 dict( 312 group_id=host['clusterRef'], 313 hosts_reference=host['hostRef'], 314 id=host['id'], 315 name=host['name'], 316 host_type_index=host['hostTypeIndex'], 317 posts=host['hostSidePorts'] 318 ) for host in array_facts['storagePoolBundle']['host']] 319 320 facts['netapp_host_types'] = [ 321 dict( 322 type=host_type['hostType'], 323 index=host_type['index'] 324 ) for host_type in array_facts['sa']['hostSpecificVals'] 325 if 'hostType' in host_type.keys() and host_type['hostType'] 326 # This conditional ignores zero-length strings which indicates that the associated host-specific NVSRAM region has been cleared. 327 ] 328 facts['snapshot_images'] = [ 329 dict( 330 id=snapshot['id'], 331 status=snapshot['status'], 332 pit_capacity=snapshot['pitCapacity'], 333 creation_method=snapshot['creationMethod'], 334 reposity_cap_utilization=snapshot['repositoryCapacityUtilization'], 335 active_cow=snapshot['activeCOW'], 336 rollback_source=snapshot['isRollbackSource'] 337 ) for snapshot in array_facts['highLevelVolBundle']['pit']] 338 339 facts['netapp_disks'] = [ 340 dict( 341 id=disk['id'], 342 available=disk['available'], 343 media_type=disk['driveMediaType'], 344 status=disk['status'], 345 usable_bytes=disk['usableCapacity'], 346 tray_ref=disk['physicalLocation']['trayRef'], 347 product_id=disk['productID'], 348 firmware_version=disk['firmwareVersion'], 349 serial_number=disk['serialNumber'].lstrip() 350 ) for disk in array_facts['drive']] 351 352 facts['netapp_management_interfaces'] = [ 353 dict(controller=controller_reference_label[controller['controllerRef']], 354 name=iface['ethernet']['interfaceName'], 355 alias=iface['ethernet']['alias'], 356 channel=iface['ethernet']['channel'], 357 mac_address=iface['ethernet']['macAddr'], 358 remote_ssh_access=iface['ethernet']['rloginEnabled'], 359 link_status=iface['ethernet']['linkStatus'], 360 ipv4_enabled=iface['ethernet']['ipv4Enabled'], 361 ipv4_address_config_method=iface['ethernet']['ipv4AddressConfigMethod'].lower().replace("config", ""), 362 ipv4_address=iface['ethernet']['ipv4Address'], 363 ipv4_subnet_mask=iface['ethernet']['ipv4SubnetMask'], 364 ipv4_gateway=iface['ethernet']['ipv4GatewayAddress'], 365 ipv6_enabled=iface['ethernet']['ipv6Enabled'], 366 dns_config_method=iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsAcquisitionType'], 367 dns_servers=(iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsServers'] 368 if iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsServers'] else []), 369 ntp_config_method=iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpAcquisitionType'], 370 ntp_servers=(iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpServers'] 371 if iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpServers'] else []) 372 ) for controller in array_facts['controller'] for iface in controller['netInterfaces']] 373 374 facts['netapp_hostside_interfaces'] = [ 375 dict( 376 fc=[dict(controller=controller_reference_label[controller['controllerRef']], 377 channel=iface['fibre']['channel'], 378 link_status=iface['fibre']['linkStatus'], 379 current_interface_speed=strip_interface_speed(iface['fibre']['currentInterfaceSpeed']), 380 maximum_interface_speed=strip_interface_speed(iface['fibre']['maximumInterfaceSpeed'])) 381 for controller in array_facts['controller'] 382 for iface in controller['hostInterfaces'] 383 if iface['interfaceType'] == 'fc'], 384 ib=[dict(controller=controller_reference_label[controller['controllerRef']], 385 channel=iface['ib']['channel'], 386 link_status=iface['ib']['linkState'], 387 mtu=iface['ib']['maximumTransmissionUnit'], 388 current_interface_speed=strip_interface_speed(iface['ib']['currentSpeed']), 389 maximum_interface_speed=strip_interface_speed(iface['ib']['supportedSpeed'])) 390 for controller in array_facts['controller'] 391 for iface in controller['hostInterfaces'] 392 if iface['interfaceType'] == 'ib'], 393 iscsi=[dict(controller=controller_reference_label[controller['controllerRef']], 394 iqn=iface['iscsi']['iqn'], 395 link_status=iface['iscsi']['interfaceData']['ethernetData']['linkStatus'], 396 ipv4_enabled=iface['iscsi']['ipv4Enabled'], 397 ipv4_address=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4Address'], 398 ipv4_subnet_mask=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4SubnetMask'], 399 ipv4_gateway=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4GatewayAddress'], 400 ipv6_enabled=iface['iscsi']['ipv6Enabled'], 401 mtu=iface['iscsi']['interfaceData']['ethernetData']['maximumFramePayloadSize'], 402 current_interface_speed=strip_interface_speed(iface['iscsi']['interfaceData'] 403 ['ethernetData']['currentInterfaceSpeed']), 404 supported_interface_speeds=strip_interface_speed(iface['iscsi']['interfaceData'] 405 ['ethernetData'] 406 ['supportedInterfaceSpeeds'])) 407 for controller in array_facts['controller'] 408 for iface in controller['hostInterfaces'] 409 if iface['interfaceType'] == 'iscsi'], 410 sas=[dict(controller=controller_reference_label[controller['controllerRef']], 411 channel=iface['sas']['channel'], 412 current_interface_speed=strip_interface_speed(iface['sas']['currentInterfaceSpeed']), 413 maximum_interface_speed=strip_interface_speed(iface['sas']['maximumInterfaceSpeed']), 414 link_status=iface['sas']['iocPort']['state']) 415 for controller in array_facts['controller'] 416 for iface in controller['hostInterfaces'] 417 if iface['interfaceType'] == 'sas'])] 418 419 facts['netapp_driveside_interfaces'] = [ 420 dict( 421 controller=controller_reference_label[controller['controllerRef']], 422 interface_type=interface['interfaceType'], 423 interface_speed=strip_interface_speed( 424 interface[interface['interfaceType']]['maximumInterfaceSpeed'] 425 if (interface['interfaceType'] == 'sata' or 426 interface['interfaceType'] == 'sas' or 427 interface['interfaceType'] == 'fibre') 428 else ( 429 interface[interface['interfaceType']]['currentSpeed'] 430 if interface['interfaceType'] == 'ib' 431 else ( 432 interface[interface['interfaceType']]['interfaceData']['maximumInterfaceSpeed'] 433 if interface['interfaceType'] == 'iscsi' else 'unknown' 434 ))), 435 ) 436 for controller in array_facts['controller'] 437 for interface in controller['driveInterfaces']] 438 439 facts['netapp_storage_pools'] = [ 440 dict( 441 id=storage_pool['id'], 442 name=storage_pool['name'], 443 available_capacity=storage_pool['freeSpace'], 444 total_capacity=storage_pool['totalRaidedSpace'], 445 used_capacity=storage_pool['usedSpace'] 446 ) for storage_pool in array_facts['volumeGroup']] 447 448 all_volumes = list(array_facts['volume']) 449 450 facts['netapp_volumes'] = [ 451 dict( 452 id=v['id'], 453 name=v['name'], 454 parent_storage_pool_id=v['volumeGroupRef'], 455 capacity=v['capacity'], 456 is_thin_provisioned=v['thinProvisioned'], 457 workload=v['metadata'], 458 ) for v in all_volumes] 459 460 workload_tags = None 461 try: 462 rc, workload_tags = self.request("storage-systems/%s/workloads" % self.ssid) 463 except Exception as error: 464 self.module.fail_json(msg="Failed to retrieve workload tags. Array [%s]." % self.ssid) 465 466 facts['netapp_workload_tags'] = [ 467 dict( 468 id=workload_tag['id'], 469 name=workload_tag['name'], 470 attributes=workload_tag['workloadAttributes'] 471 ) for workload_tag in workload_tags] 472 473 # Create a dictionary of volume lists keyed by host names 474 facts['netapp_volumes_by_initiators'] = dict() 475 for mapping in array_facts['storagePoolBundle']['lunMapping']: 476 for host in facts['netapp_hosts']: 477 if mapping['mapRef'] == host['hosts_reference'] or mapping['mapRef'] == host['group_id']: 478 if host['name'] not in facts['netapp_volumes_by_initiators'].keys(): 479 facts['netapp_volumes_by_initiators'].update({host['name']: []}) 480 481 for volume in all_volumes: 482 if mapping['id'] in [volume_mapping['id'] for volume_mapping in volume['listOfMappings']]: 483 484 # Determine workload name if there is one 485 workload_name = "" 486 metadata = dict() 487 for volume_tag in volume['metadata']: 488 if volume_tag['key'] == 'workloadId': 489 for workload_tag in facts['netapp_workload_tags']: 490 if volume_tag['value'] == workload_tag['id']: 491 workload_name = workload_tag['name'] 492 metadata = dict((entry['key'], entry['value']) 493 for entry in workload_tag['attributes'] 494 if entry['key'] != 'profileId') 495 496 facts['netapp_volumes_by_initiators'][host['name']].append( 497 dict(name=volume['name'], 498 id=volume['id'], 499 wwn=volume['wwn'], 500 workload_name=workload_name, 501 meta_data=metadata)) 502 503 features = [feature for feature in array_facts['sa']['capabilities']] 504 features.extend([feature['capability'] for feature in array_facts['sa']['premiumFeatures'] 505 if feature['isEnabled']]) 506 features = list(set(features)) # ensure unique 507 features.sort() 508 facts['netapp_enabled_features'] = features 509 510 return facts 511 512 def get_facts(self): 513 """Get the embedded or web services proxy information.""" 514 facts = self.get_array_facts() 515 516 self.module.log("isEmbedded: %s" % self.is_embedded()) 517 self.module.log(pformat(facts)) 518 519 self.module.exit_json(msg="Gathered facts for storage array. Array ID: [%s]." % self.ssid, 520 storage_array_facts=facts) 521 522 523def strip_interface_speed(speed): 524 """Converts symbol interface speeds to a more common notation. Example: 'speed10gig' -> '10g'""" 525 if isinstance(speed, list): 526 result = [match(r"speed[0-9]{1,3}[gm]", sp) for sp in speed] 527 result = [sp.group().replace("speed", "") if result else "unknown" for sp in result if sp] 528 result = ["auto" if match(r"auto", sp) else sp for sp in result] 529 else: 530 result = match(r"speed[0-9]{1,3}[gm]", speed) 531 result = result.group().replace("speed", "") if result else "unknown" 532 result = "auto" if match(r"auto", result.lower()) else result 533 return result 534 535 536def main(): 537 facts = Facts() 538 facts.get_facts() 539 540 541if __name__ == "__main__": 542 main() 543