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