From 10543c8a4cc0d458a38d68be45f15c43682c7ccd Mon Sep 17 00:00:00 2001 From: Nathan Swartz Date: Thu, 8 Aug 2019 10:36:23 -0500 Subject: [PATCH] Facts dev upstream (#57082) * Improved netapp_e_facts module. Added the following facts: - storage system segment size - cache block size capabilities - workload tags - storage array hosts - host groups - list of mapped volumes for each initiator * Remove proxy specific facts from netapp_e_facts module * Add unit tests for netapp_e_facts module. --- .../modules/storage/netapp/netapp_e_facts.py | 673 +++++++++++++----- test/sanity/ignore.txt | 2 - .../storage/netapp/test_netapp_e_facts.py | 454 ++++++++++++ 3 files changed, 963 insertions(+), 166 deletions(-) create mode 100644 test/units/modules/storage/netapp/test_netapp_e_facts.py diff --git a/lib/ansible/modules/storage/netapp/netapp_e_facts.py b/lib/ansible/modules/storage/netapp/netapp_e_facts.py index 7800ac79e65..cecc6a6517e 100644 --- a/lib/ansible/modules/storage/netapp/netapp_e_facts.py +++ b/lib/ansible/modules/storage/netapp/netapp_e_facts.py @@ -11,186 +11,531 @@ ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} - DOCUMENTATION = ''' module: netapp_e_facts -version_added: '2.2' short_description: NetApp E-Series retrieve facts about NetApp E-Series storage arrays -options: - api_username: - required: true - description: - - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API. - api_password: - required: true - description: - - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API. - api_url: - required: true - description: - - The url to the SANtricity WebServices Proxy or embedded REST API. - validate_certs: - required: false - default: true - description: - - Should https certificates be validated? - type: bool - ssid: - required: true - description: - - The ID of the array to manage. This value must be unique for each array. - description: - - Return various information about NetApp E-Series storage arrays (eg, configuration, disks) - -author: Kevin Hulquest (@hulquest) + - The netapp_e_facts module returns a collection of facts regarding NetApp E-Series storage arrays. + - When contacting a storage array directly the collection includes details about the array, controllers, management + interfaces, hostside interfaces, driveside interfaces, disks, storage pools, volumes, snapshots, and features. + - When contacting a web services proxy the collection will include basic information regarding the storage systems + that are under its management. +version_added: '2.2' +author: + - Kevin Hulquest (@hulquest) + - Nathan Swartz (@ndswartz) +extends_documentation_fragment: + - netapp.eseries ''' EXAMPLES = """ --- - - name: Get array facts - netapp_e_facts: - array_id: "{{ netapp_array_id }}" - api_url: "{{ netapp_api_url }}" - api_username: "{{ netapp_api_username }}" - api_password: "{{ netapp_api_password }}" - validate_certs: "{{ netapp_api_validate_certs }}" +- name: Get array facts + netapp_e_facts: + ssid: "{{ netapp_array_id }}" + api_url: "https://{{ netapp_e_api_host }}:8443/devmgr/v2" + api_username: "{{ netapp_api_username }}" + api_password: "{{ netapp_api_password }}" + validate_certs: "{{ netapp_api_validate_certs }}" +- name: Get array facts + netapp_e_facts: + ssid: 1 + api_url: https://192.168.1.100:8443/devmgr/v2 + api_username: myApiUser + api_password: myApiPass + validate_certs: true """ RETURN = """ -msg: - description: Gathered facts for . - returned: always - type: str + msg: + description: Success message + returned: on success + type: str + sample: + - Gathered facts for storage array. Array ID [1]. + - Gathered facts for web services proxy. + storage_array_facts: + description: provides details about the array, controllers, management interfaces, hostside interfaces, + driveside interfaces, disks, storage pools, volumes, snapshots, and features. + returned: on successful inquiry from from embedded web services rest api + type: complex + contains: + netapp_controllers: + description: storage array controller list that contains basic controller identification and status + type: complex + sample: + - [{"name": "A", "serial": "021632007299", "status": "optimal"}, + {"name": "B", "serial": "021632007300", "status": "failed"}] + netapp_disks: + description: drive list that contains identification, type, and status information for each drive + type: complex + sample: + - [{"available": false, + "firmware_version": "MS02", + "id": "01000000500003960C8B67880000000000000000", + "media_type": "ssd", + "product_id": "PX02SMU080 ", + "serial_number": "15R0A08LT2BA", + "status": "optimal", + "tray_ref": "0E00000000000000000000000000000000000000", + "usable_bytes": "799629205504" }] + netapp_driveside_interfaces: + description: drive side interface list that contains identification, type, and speed for each interface + type: complex + sample: + - [{ "controller": "A", "interface_speed": "12g", "interface_type": "sas" }] + - [{ "controller": "B", "interface_speed": "10g", "interface_type": "iscsi" }] + netapp_enabled_features: + description: specifies the enabled features on the storage array. + returned: on success + type: complex + sample: + - [ "flashReadCache", "performanceTier", "protectionInformation", "secureVolume" ] + netapp_host_groups: + description: specifies the host groups on the storage arrays. + returned: on success + type: complex + sample: + - [{ "id": "85000000600A098000A4B28D003610705C40B964", "name": "group1" }] + netapp_hosts: + description: specifies the hosts on the storage arrays. + returned: on success + type: complex + sample: + - [{ "id": "8203800000000000000000000000000000000000", + "name": "host1", + "group_id": "85000000600A098000A4B28D003610705C40B964", + "host_type_index": 28, + "ports": [{ "type": "fc", "address": "1000FF7CFFFFFF01", "label": "FC_1" }, + { "type": "fc", "address": "1000FF7CFFFFFF00", "label": "FC_2" }]}] + netapp_host_types: + description: lists the available host types on the storage array. + returned: on success + type: complex + sample: + - [{ "index": 0, "type": "FactoryDefault" }, + { "index": 1, "type": "W2KNETNCL"}, + { "index": 2, "type": "SOL" }, + { "index": 5, "type": "AVT_4M" }, + { "index": 6, "type": "LNX" }, + { "index": 7, "type": "LnxALUA" }, + { "index": 8, "type": "W2KNETCL" }, + { "index": 9, "type": "AIX MPIO" }, + { "index": 10, "type": "VmwTPGSALUA" }, + { "index": 15, "type": "HPXTPGS" }, + { "index": 17, "type": "SolTPGSALUA" }, + { "index": 18, "type": "SVC" }, + { "index": 22, "type": "MacTPGSALUA" }, + { "index": 23, "type": "WinTPGSALUA" }, + { "index": 24, "type": "LnxTPGSALUA" }, + { "index": 25, "type": "LnxTPGSALUA_PM" }, + { "index": 26, "type": "ONTAP_ALUA" }, + { "index": 27, "type": "LnxTPGSALUA_SF" }, + { "index": 28, "type": "LnxDHALUA" }, + { "index": 29, "type": "ATTOClusterAllOS" }] + netapp_hostside_interfaces: + description: host side interface list that contains identification, configuration, type, speed, and + status information for each interface + type: complex + sample: + - [{"iscsi": + [{ "controller": "A", + "current_interface_speed": "10g", + "ipv4_address": "10.10.10.1", + "ipv4_enabled": true, + "ipv4_gateway": "10.10.10.1", + "ipv4_subnet_mask": "255.255.255.0", + "ipv6_enabled": false, + "iqn": "iqn.1996-03.com.netapp:2806.600a098000a81b6d0000000059d60c76", + "link_status": "up", + "mtu": 9000, + "supported_interface_speeds": [ "10g" ] }]}] + netapp_management_interfaces: + description: management interface list that contains identification, configuration, and status for + each interface + type: complex + sample: + - [{"alias": "ict-2800-A", + "channel": 1, + "controller": "A", + "dns_config_method": "dhcp", + "dns_servers": [], + "ipv4_address": "10.1.1.1", + "ipv4_address_config_method": "static", + "ipv4_enabled": true, + "ipv4_gateway": "10.113.1.1", + "ipv4_subnet_mask": "255.255.255.0", + "ipv6_enabled": false, + "link_status": "up", + "mac_address": "00A098A81B5D", + "name": "wan0", + "ntp_config_method": "disabled", + "ntp_servers": [], + "remote_ssh_access": false }] + netapp_storage_array: + description: provides storage array identification, firmware version, and available capabilities + type: dict + sample: + - {"chassis_serial": "021540006043", + "firmware": "08.40.00.01", + "name": "ict-2800-11_40", + "wwn": "600A098000A81B5D0000000059D60C76", + "cacheBlockSizes": [4096, + 8192, + 16384, + 32768], + "supportedSegSizes": [8192, + 16384, + 32768, + 65536, + 131072, + 262144, + 524288]} + netapp_storage_pools: + description: storage pool list that contains identification and capacity information for each pool + type: complex + sample: + - [{"available_capacity": "3490353782784", + "id": "04000000600A098000A81B5D000002B45A953A61", + "name": "Raid6", + "total_capacity": "5399466745856", + "used_capacity": "1909112963072" }] + netapp_volumes: + description: storage volume list that contains identification and capacity information for each volume + type: complex + sample: + - [{"capacity": "5368709120", + "id": "02000000600A098000AAC0C3000002C45A952BAA", + "is_thin_provisioned": false, + "name": "5G", + "parent_storage_pool_id": "04000000600A098000A81B5D000002B45A953A61" }] + netapp_workload_tags: + description: workload tag list + type: complex + sample: + - [{"id": "87e19568-43fb-4d8d-99ea-2811daaa2b38", + "name": "ftp_server", + "workloadAttributes": [{"key": "use", + "value": "general"}]}] + netapp_volumes_by_initiators: + description: list of available volumes keyed by the mapped initiators. + type: complex + sample: + - {"192_168_1_1": [{"id": "02000000600A098000A4B9D1000015FD5C8F7F9E", + "meta_data": {"filetype": "xfs", "public": true}, + "name": "some_volume", + "workload_name": "test2_volumes", + "wwn": "600A098000A4B9D1000015FD5C8F7F9E"}]} + snapshot_images: + description: snapshot image list that contains identification, capacity, and status information for each + snapshot image + type: complex + sample: + - [{"active_cow": true, + "creation_method": "user", + "id": "34000000600A098000A81B5D00630A965B0535AC", + "pit_capacity": "5368709120", + "reposity_cap_utilization": "0", + "rollback_source": false, + "status": "optimal" }] """ -import json - -from ansible.module_utils.api import basic_auth_argument_spec -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native -from ansible.module_utils.urls import open_url -from ansible.module_utils.six.moves.urllib.error import HTTPError - - -def request(url, data=None, headers=None, method='GET', use_proxy=True, - force=False, last_mod_time=None, timeout=10, validate_certs=True, - url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False): - try: - r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy, - force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs, - url_username=url_username, url_password=url_password, http_agent=http_agent, - force_basic_auth=force_basic_auth) - except HTTPError as e: - r = e.fp - - try: - raw_data = r.read() - if raw_data: - data = json.loads(raw_data) - else: - data = None - except Exception: - if ignore_errors: - pass - else: - raise - - resp_code = r.getcode() - - if resp_code >= 400 and not ignore_errors: - raise Exception(resp_code, data) + +from re import match +from pprint import pformat +from ansible.module_utils.netapp import NetAppESeriesModule + + +class Facts(NetAppESeriesModule): + def __init__(self): + web_services_version = "02.00.0000.0000" + super(Facts, self).__init__(ansible_options={}, + web_services_version=web_services_version, + supports_check_mode=True) + + def get_controllers(self): + """Retrieve a mapping of controller references to their labels.""" + controllers = list() + try: + rc, controllers = self.request('storage-systems/%s/graph/xpath-filter?query=/controller/id' % self.ssid) + except Exception as err: + self.module.fail_json( + msg="Failed to retrieve controller list! Array Id [%s]. Error [%s]." + % (self.ssid, str(err))) + + controllers.sort() + + controllers_dict = {} + i = ord('A') + for controller in controllers: + label = chr(i) + controllers_dict[controller] = label + i += 1 + + return controllers_dict + + def get_array_facts(self): + """Extract particular facts from the storage array graph""" + facts = dict(facts_from_proxy=False, ssid=self.ssid) + controller_reference_label = self.get_controllers() + array_facts = None + + # Get the storage array graph + try: + rc, array_facts = self.request("storage-systems/%s/graph" % self.ssid) + except Exception as error: + self.module.fail_json(msg="Failed to obtain facts from storage array with id [%s]. Error [%s]" + % (self.ssid, str(error))) + + facts['netapp_storage_array'] = dict( + name=array_facts['sa']['saData']['storageArrayLabel'], + chassis_serial=array_facts['sa']['saData']['chassisSerialNumber'], + firmware=array_facts['sa']['saData']['fwVersion'], + wwn=array_facts['sa']['saData']['saId']['worldWideName'], + segment_sizes=array_facts['sa']['featureParameters']['supportedSegSizes'], + cache_block_sizes=array_facts['sa']['featureParameters']['cacheBlockSizes']) + + facts['netapp_controllers'] = [ + dict( + name=controller_reference_label[controller['controllerRef']], + serial=controller['serialNumber'].strip(), + status=controller['status'], + ) for controller in array_facts['controller']] + + facts['netapp_host_groups'] = [ + dict( + id=group['id'], + name=group['name'] + ) for group in array_facts['storagePoolBundle']['cluster']] + + facts['netapp_hosts'] = [ + dict( + group_id=host['clusterRef'], + hosts_reference=host['hostRef'], + id=host['id'], + name=host['name'], + host_type_index=host['hostTypeIndex'], + posts=host['hostSidePorts'] + ) for host in array_facts['storagePoolBundle']['host']] + + facts['netapp_host_types'] = [ + dict( + type=host_type['hostType'], + index=host_type['index'] + ) for host_type in array_facts['sa']['hostSpecificVals'] + if 'hostType' in host_type.keys() and host_type['hostType'] + # This conditional ignores zero-length strings which indicates that the associated host-specific NVSRAM region has been cleared. + ] + facts['snapshot_images'] = [ + dict( + id=snapshot['id'], + status=snapshot['status'], + pit_capacity=snapshot['pitCapacity'], + creation_method=snapshot['creationMethod'], + reposity_cap_utilization=snapshot['repositoryCapacityUtilization'], + active_cow=snapshot['activeCOW'], + rollback_source=snapshot['isRollbackSource'] + ) for snapshot in array_facts['highLevelVolBundle']['pit']] + + facts['netapp_disks'] = [ + dict( + id=disk['id'], + available=disk['available'], + media_type=disk['driveMediaType'], + status=disk['status'], + usable_bytes=disk['usableCapacity'], + tray_ref=disk['physicalLocation']['trayRef'], + product_id=disk['productID'], + firmware_version=disk['firmwareVersion'], + serial_number=disk['serialNumber'].lstrip() + ) for disk in array_facts['drive']] + + facts['netapp_management_interfaces'] = [ + dict(controller=controller_reference_label[controller['controllerRef']], + name=iface['ethernet']['interfaceName'], + alias=iface['ethernet']['alias'], + channel=iface['ethernet']['channel'], + mac_address=iface['ethernet']['macAddr'], + remote_ssh_access=iface['ethernet']['rloginEnabled'], + link_status=iface['ethernet']['linkStatus'], + ipv4_enabled=iface['ethernet']['ipv4Enabled'], + ipv4_address_config_method=iface['ethernet']['ipv4AddressConfigMethod'].lower().replace("config", ""), + ipv4_address=iface['ethernet']['ipv4Address'], + ipv4_subnet_mask=iface['ethernet']['ipv4SubnetMask'], + ipv4_gateway=iface['ethernet']['ipv4GatewayAddress'], + ipv6_enabled=iface['ethernet']['ipv6Enabled'], + dns_config_method=iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsAcquisitionType'], + dns_servers=(iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsServers'] + if iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsServers'] else []), + ntp_config_method=iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpAcquisitionType'], + ntp_servers=(iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpServers'] + if iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpServers'] else []) + ) for controller in array_facts['controller'] for iface in controller['netInterfaces']] + + facts['netapp_hostside_interfaces'] = [ + dict( + fc=[dict(controller=controller_reference_label[controller['controllerRef']], + channel=iface['fibre']['channel'], + link_status=iface['fibre']['linkStatus'], + current_interface_speed=strip_interface_speed(iface['fibre']['currentInterfaceSpeed']), + maximum_interface_speed=strip_interface_speed(iface['fibre']['maximumInterfaceSpeed'])) + for controller in array_facts['controller'] + for iface in controller['hostInterfaces'] + if iface['interfaceType'] == 'fc'], + ib=[dict(controller=controller_reference_label[controller['controllerRef']], + channel=iface['ib']['channel'], + link_status=iface['ib']['linkState'], + mtu=iface['ib']['maximumTransmissionUnit'], + current_interface_speed=strip_interface_speed(iface['ib']['currentSpeed']), + maximum_interface_speed=strip_interface_speed(iface['ib']['supportedSpeed'])) + for controller in array_facts['controller'] + for iface in controller['hostInterfaces'] + if iface['interfaceType'] == 'ib'], + iscsi=[dict(controller=controller_reference_label[controller['controllerRef']], + iqn=iface['iscsi']['iqn'], + link_status=iface['iscsi']['interfaceData']['ethernetData']['linkStatus'], + ipv4_enabled=iface['iscsi']['ipv4Enabled'], + ipv4_address=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4Address'], + ipv4_subnet_mask=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4SubnetMask'], + ipv4_gateway=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4GatewayAddress'], + ipv6_enabled=iface['iscsi']['ipv6Enabled'], + mtu=iface['iscsi']['interfaceData']['ethernetData']['maximumFramePayloadSize'], + current_interface_speed=strip_interface_speed(iface['iscsi']['interfaceData'] + ['ethernetData']['currentInterfaceSpeed']), + supported_interface_speeds=strip_interface_speed(iface['iscsi']['interfaceData'] + ['ethernetData'] + ['supportedInterfaceSpeeds'])) + for controller in array_facts['controller'] + for iface in controller['hostInterfaces'] + if iface['interfaceType'] == 'iscsi'], + sas=[dict(controller=controller_reference_label[controller['controllerRef']], + channel=iface['sas']['channel'], + current_interface_speed=strip_interface_speed(iface['sas']['currentInterfaceSpeed']), + maximum_interface_speed=strip_interface_speed(iface['sas']['maximumInterfaceSpeed']), + link_status=iface['sas']['iocPort']['state']) + for controller in array_facts['controller'] + for iface in controller['hostInterfaces'] + if iface['interfaceType'] == 'sas'])] + + facts['netapp_driveside_interfaces'] = [ + dict( + controller=controller_reference_label[controller['controllerRef']], + interface_type=interface['interfaceType'], + interface_speed=strip_interface_speed( + interface[interface['interfaceType']]['maximumInterfaceSpeed'] + if (interface['interfaceType'] == 'sata' or + interface['interfaceType'] == 'sas' or + interface['interfaceType'] == 'fibre') + else ( + interface[interface['interfaceType']]['currentSpeed'] + if interface['interfaceType'] == 'ib' + else ( + interface[interface['interfaceType']]['interfaceData']['maximumInterfaceSpeed'] + if interface['interfaceType'] == 'iscsi' else 'unknown' + ))), + ) + for controller in array_facts['controller'] + for interface in controller['driveInterfaces']] + + facts['netapp_storage_pools'] = [ + dict( + id=storage_pool['id'], + name=storage_pool['name'], + available_capacity=storage_pool['freeSpace'], + total_capacity=storage_pool['totalRaidedSpace'], + used_capacity=storage_pool['usedSpace'] + ) for storage_pool in array_facts['volumeGroup']] + + all_volumes = list(array_facts['volume']) + + facts['netapp_volumes'] = [ + dict( + id=v['id'], + name=v['name'], + parent_storage_pool_id=v['volumeGroupRef'], + capacity=v['capacity'], + is_thin_provisioned=v['thinProvisioned'], + workload=v['metadata'], + ) for v in all_volumes] + + workload_tags = None + try: + rc, workload_tags = self.request("storage-systems/%s/workloads" % self.ssid) + except Exception as error: + self.module.fail_json(msg="Failed to retrieve workload tags. Array [%s]." % self.ssid) + + facts['netapp_workload_tags'] = [ + dict( + id=workload_tag['id'], + name=workload_tag['name'], + attributes=workload_tag['workloadAttributes'] + ) for workload_tag in workload_tags] + + # Create a dictionary of volume lists keyed by host names + facts['netapp_volumes_by_initiators'] = dict() + for mapping in array_facts['storagePoolBundle']['lunMapping']: + for host in facts['netapp_hosts']: + if mapping['mapRef'] == host['hosts_reference'] or mapping['mapRef'] == host['group_id']: + if host['name'] not in facts['netapp_volumes_by_initiators'].keys(): + facts['netapp_volumes_by_initiators'].update({host['name']: []}) + + for volume in all_volumes: + if mapping['id'] in [volume_mapping['id'] for volume_mapping in volume['listOfMappings']]: + + # Determine workload name if there is one + workload_name = "" + metadata = dict() + for volume_tag in volume['metadata']: + if volume_tag['key'] == 'workloadId': + for workload_tag in facts['netapp_workload_tags']: + if volume_tag['value'] == workload_tag['id']: + workload_name = workload_tag['name'] + metadata = dict((entry['key'], entry['value']) + for entry in workload_tag['attributes'] + if entry['key'] != 'profileId') + + facts['netapp_volumes_by_initiators'][host['name']].append( + dict(name=volume['name'], + id=volume['id'], + wwn=volume['wwn'], + workload_name=workload_name, + meta_data=metadata)) + + features = [feature for feature in array_facts['sa']['capabilities']] + features.extend([feature['capability'] for feature in array_facts['sa']['premiumFeatures'] + if feature['isEnabled']]) + features = list(set(features)) # ensure unique + features.sort() + facts['netapp_enabled_features'] = features + + return facts + + def get_facts(self): + """Get the embedded or web services proxy information.""" + facts = self.get_array_facts() + + self.module.log("isEmbedded: %s" % self.is_embedded()) + self.module.log(pformat(facts)) + + self.module.exit_json(msg="Gathered facts for storage array. Array ID: [%s]." % self.ssid, + storage_array_facts=facts) + + +def strip_interface_speed(speed): + """Converts symbol interface speeds to a more common notation. Example: 'speed10gig' -> '10g'""" + if isinstance(speed, list): + result = [match(r"speed[0-9]{1,3}[gm]", sp) for sp in speed] + result = [sp.group().replace("speed", "") if result else "unknown" for sp in result if sp] + result = ["auto" if match(r"auto", sp) else sp for sp in result] else: - return resp_code, data + result = match(r"speed[0-9]{1,3}[gm]", speed) + result = result.group().replace("speed", "") if result else "unknown" + result = "auto" if match(r"auto", result.lower()) else result + return result def main(): - argument_spec = basic_auth_argument_spec() - argument_spec.update( - api_username=dict(type='str', required=True), - api_password=dict(type='str', required=True, no_log=True), - api_url=dict(type='str', required=True), - ssid=dict(required=True)) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True - ) - - p = module.params - - ssid = p['ssid'] - validate_certs = p['validate_certs'] - - api_usr = p['api_username'] - api_pwd = p['api_password'] - api_url = p['api_url'] - - facts = dict(ssid=ssid) - - # fetch the list of storage-pool objects and look for one with a matching name - try: - (rc, resp) = request(api_url + "/storage-systems/%s/graph" % ssid, - headers=dict(Accept="application/json"), - url_username=api_usr, url_password=api_pwd, validate_certs=validate_certs) - except Exception as e: - module.fail_json( - msg="Failed to obtain facts from storage array with id [%s]. Error [%s]" % (ssid, to_native(e))) - - facts['snapshot_images'] = [ - dict( - id=d['id'], - status=d['status'], - pit_capacity=d['pitCapacity'], - creation_method=d['creationMethod'], - reposity_cap_utilization=d['repositoryCapacityUtilization'], - active_cow=d['activeCOW'], - rollback_source=d['isRollbackSource'] - ) for d in resp['highLevelVolBundle']['pit']] - - facts['netapp_disks'] = [ - dict( - id=d['id'], - available=d['available'], - media_type=d['driveMediaType'], - status=d['status'], - usable_bytes=d['usableCapacity'], - tray_ref=d['physicalLocation']['trayRef'], - product_id=d['productID'], - firmware_version=d['firmwareVersion'], - serial_number=d['serialNumber'].lstrip() - ) for d in resp['drive']] - - facts['netapp_storage_pools'] = [ - dict( - id=sp['id'], - name=sp['name'], - available_capacity=sp['freeSpace'], - total_capacity=sp['totalRaidedSpace'], - used_capacity=sp['usedSpace'] - ) for sp in resp['volumeGroup']] - - all_volumes = list(resp['volume']) - # all_volumes.extend(resp['thinVolume']) - - # TODO: exclude thin-volume repo volumes (how to ID?) - facts['netapp_volumes'] = [ - dict( - id=v['id'], - name=v['name'], - parent_storage_pool_id=v['volumeGroupRef'], - capacity=v['capacity'], - is_thin_provisioned=v['thinProvisioned'] - ) for v in all_volumes] - - features = [f for f in resp['sa']['capabilities']] - features.extend([f['capability'] for f in resp['sa']['premiumFeatures'] if f['isEnabled']]) - features = list(set(features)) # ensure unique - features.sort() - facts['netapp_enabled_features'] = features - - # TODO: include other details about the storage pool (size, type, id, etc) - result = dict(ansible_facts=facts, changed=False) - module.exit_json(msg="Gathered facts for %s." % ssid, **result) + facts = Facts() + facts.get_facts() if __name__ == "__main__": diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 8f000c8f7f3..5a59e6b1bdd 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -5883,8 +5883,6 @@ lib/ansible/modules/storage/netapp/netapp_e_asup.py validate-modules:E337 lib/ansible/modules/storage/netapp/netapp_e_auditlog.py validate-modules:E337 lib/ansible/modules/storage/netapp/netapp_e_auth.py validate-modules:E337 lib/ansible/modules/storage/netapp/netapp_e_auth.py validate-modules:E338 -lib/ansible/modules/storage/netapp/netapp_e_facts.py validate-modules:E337 -lib/ansible/modules/storage/netapp/netapp_e_facts.py validate-modules:E338 lib/ansible/modules/storage/netapp/netapp_e_flashcache.py validate-modules:E322 lib/ansible/modules/storage/netapp/netapp_e_flashcache.py validate-modules:E326 lib/ansible/modules/storage/netapp/netapp_e_flashcache.py validate-modules:E337 diff --git a/test/units/modules/storage/netapp/test_netapp_e_facts.py b/test/units/modules/storage/netapp/test_netapp_e_facts.py new file mode 100644 index 00000000000..311f3de087c --- /dev/null +++ b/test/units/modules/storage/netapp/test_netapp_e_facts.py @@ -0,0 +1,454 @@ +# (c) 2018, NetApp Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible.modules.storage.netapp.netapp_e_facts import Facts +from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args + +from units.compat import mock + + +class FactsTest(ModuleTestCase): + REQUIRED_PARAMS = { + 'api_username': 'rw', + 'api_password': 'password', + 'api_url': 'http://localhost', + 'ssid': '1' + } + REQUEST_FUNC = 'ansible.modules.storage.netapp.netapp_e_facts.Facts.request' + GET_CONTROLLERS_FUNC = 'ansible.modules.storage.netapp.netapp_e_facts.Facts.get_controllers' + WORKLOAD_RESPONSE = [{"id": "4200000001000000000000000000000000000000", "name": "beegfs_metadata", + "workloadAttributes": [{"key": "profileId", "value": "ansible_workload_1"}]}, + {"id": "4200000002000000000000000000000000000000", "name": "other_workload_1", + "workloadAttributes": [{"key": "profileId", "value": "Other_1"}]}] + GRAPH_RESPONSE = { + "sa": {"saData": {"storageArrayLabel": "ictm0718s01c1", "saId": {"worldWideName": "600A098000A4B28D000000005CF10481"}, "fwVersion": "08.42.30.05", + "chassisSerialNumber": "021633035190"}, + "featureParameters": {"cacheBlockSizes": [4096, 8192, 16384, 32768], + "supportedSegSizes": [32768, 65536, 131072, 262144, 524288, 495616, 655360, 1982464]}, + "capabilities": ["autoCodeSync", "autoLunTransfer", "subLunsAllowed", "stagedDownload", "mixedDriveTypes", "bundleMigration", "raid6", + "performanceTier", "secureVolume", "protectionInformation", "ssdSupport", "driveSlotLimit", "flashReadCache", + "storagePoolsType2", "totalNumberOfArvmMirrorsPerArray", "totalNumberOfPitsPerArray", "totalNumberOfThinVolumesPerArray"], + "premiumFeatures": [], + "hostSpecificVals": [{"hostType": "FactoryDefault", "index": 0}, {"hostType": "W2KNETNCL", "index": 1}, {"hostPortType": "W2KNETCL", "index": 8}, + {"hostType": "LnxTPGSALUA_SF", "index": 27}, {"hostType": "LnxDHALUA", "index": 28}]}, "controller": [ + {"active": True, "quiesced": False, "status": "optimal", "controllerRef": "070000000000000000000001", + "physicalLocation": {"trayRef": "0E00000000000000000000000000000000000000", "slot": 1, + "locationParent": {"refType": "generic", "controllerRef": None, "symbolRef": "0000000000000000000000000000000000000000", + "typedReference": None}, "locationPosition": 1, "label": "A"}, "manufacturer": "NETAPP ", + "manufacturerDate": "1474675200", "appVersion": "08.42.30.05", "bootVersion": "08.42.30.05", "productID": "INF-01-00 ", + "productRevLevel": "0842", "serialNumber": "021619039162 ", "boardID": "2806", "cacheMemorySize": 3328, "processorMemorySize": 1278, + "hostInterfaces": [{"interfaceType": "iscsi", "fibre": None, "ib": None, + "iscsi": {"channel": 1, "channelPortRef": "1F00010001010000000000000000000000000000", "tcpListenPort": 3260, + "ipv4Enabled": True, "ipv4Data": {"ipv4Address": "0.0.0.0", "ipv4AddressConfigMethod": "configStatic", + "ipv4OutboundPacketPriority": {"isEnabled": False, "value": 1}, + "ipv4VlanId": {"isEnabled": False, "value": 1}, + "ipv4AddressData": {"configState": "configured", "ipv4Address": "10.10.11.110", + "ipv4SubnetMask": "255.255.255.0", + "ipv4GatewayAddress": "0.0.0.0"}}, + "interfaceData": {"type": "ethernet", "ethernetData": { + "partData": {"vendorName": "QLogic Corporation", "partNumber": "83xx", "revisionNumber": "5.5.31.511", + "serialNumber": "00a098a4b28f"}, "macAddress": "00A098A4B293", "fullDuplex": True, + "maximumFramePayloadSize": 9000, "currentInterfaceSpeed": "speed10gig", "maximumInterfaceSpeed": "speed10gig", + "linkStatus": "up", "supportedInterfaceSpeeds": ["speed1gig", "speed10gig"], "autoconfigSupport": False, + "copperCableDiagnosticsSupport": False}, "infinibandData": None}, + "interfaceRef": "2201020000000000000000000000000000000000", "ipv6Enabled": True, + "ipv6Data": {"ipv6LocalAddresses": [ + {"address": "FE8000000000000002A098FFFEA4B293", + "addressState": {"addressType": "typeInterface", "interfaceAddressState": "configured", + "routerAddressState": "__UNDEFINED"}}], "ipv6RoutableAddresses": [ + {"address": "00000000000000000000000000000000", + "addressState": {"addressType": "typeInterface", "interfaceAddressState": "unconfigured", + "routerAddressState": "__UNDEFINED"}}, + {"address": "00000000000000000000000000000000", + "addressState": {"addressType": "typeInterface", "interfaceAddressState": "unconfigured", + "routerAddressState": "__UNDEFINED"}}], + "ipv6PortRouterAddress": {"address": "00000000000000000000000000000000", + "addressState": {"addressType": "typeRouter", "interfaceAddressState": "__UNDEFINED", + "routerAddressState": "unknown"}}, + "ipv6AddressConfigMethod": "configStateless", "ipv6OutboundPacketPriority": {"isEnabled": False, "value": 1}, + "ipv6VlanId": {"isEnabled": False, "value": 1}, "ipv6HopLimit": 64, "ipv6NdReachableTime": 30000, + "ipv6NdRetransmitTime": 1000, "ipv6NdStaleTimeout": 30000, "ipv6DuplicateAddressDetectionAttempts": 1}, + "physicalLocation": {"trayRef": "0000000000000000000000000000000000000000", "slot": 0, + "locationParent": {"refType": "generic", "controllerRef": None, + "symbolRef": "0000000000000000000000000000000000000000", + "typedReference": None}, "locationPosition": 0, "label": ""}, + "protectionInformationCapable": True, "isIPv6Capable": True, "oneWayMaxRate": "1230000000", + "bidirectionalMaxRate": "2120000000", "iqn": "iqn.1992-08.com.netapp:2806.600a098000a4b28d000000005cf10481", + "controllerId": "070000000000000000000001", + "addressId": "iqn.1992-08.com.netapp:2806.600a098000a4b28d000000005cf10481", + "niceAddressId": "iqn.1992-08.com.netapp:2806.600a098000a4b28d000000005cf10481", + "interfaceId": "2201020000000000000000000000000000000000", "id": "2201020000000000000000000000000000000000"}, + "sas": None, "sata": None, "scsi": None}], + "driveInterfaces": [ + {"interfaceType": "sas", "fibre": None, "ib": None, "iscsi": None, + "sas": {"channel": 1, "currentInterfaceSpeed": "speed12gig", "maximumInterfaceSpeed": "speed12gig", "part": "LSISAS3008", + "revision": 172688896, "isDegraded": False, + "iocPort": { + "parent": {"type": "controller", "controller": "070000000000000000000001", "drive": None, "expander": None, "hostBoardRef": None}, + "attachedDevice": {"channel": 1, "channelType": "driveside", + "sasAttachedDeviceData": {"type": "expander", "alternateController": None, "drive": None, + "expander": "2000000000000000000000630001000000000000", + "remoteHostPortAddress": None, + "localController": None, "physicalLocation": None}}, "state": "optimal", + "miswireType": "None", "channelPortRef": "1F01000001010000000000000000000000000000", + "sasPhys": [{"phyIdentifier": 4, "isOperational": True}, {"phyIdentifier": 5, "isOperational": True}, + {"phyIdentifier": 6, "isOperational": True}, {"phyIdentifier": 7, "isOperational": True}], + "portTypeData": {"portType": "endDevice", "portIdentifier": "500A098A4B28D004", "routingType": "__UNDEFINED"}, + "portMode": "internal", + "domainNumber": 1, "attachedChannelPortRef": "0000000000000000000000000000000000000000", "discoveryStatus": 0}, + "interfaceRef": "2201000000000000000000000000000000000000", + "physicalLocation": {"trayRef": "0000000000000000000000000000000000000000", "slot": 0, + "locationParent": {"refType": "generic", "controllerRef": None, + "symbolRef": "0000000000000000000000000000000000000000", "typedReference": None}, + "locationPosition": 0, "label": ""}, "protectionInformationCapable": True, "oneWayMaxRate": "4400000000", + "bidirectionalMaxRate": "8400000000", "controllerId": None, "addressId": "500A098A4B28D004", "niceAddressId": "500A098A4B28D004", + "interfaceId": "2201000000000000000000000000000000000000", "basePortAddress": "500A098A4B28D00", + "id": "2201000000000000000000000000000000000000"}, "sata": None, "scsi": None}], + "netInterfaces": [{"interfaceType": "ethernet", + "ethernet": {"interfaceName": "wan0", "channel": 1, "speed": 1000, "ip": 175178176, "alias": "ictm0718s01c1-a", + "macAddr": "00A098A4B28D", "gatewayIp": 175177985, "subnetMask": -256, "bootpUsed": False, "rloginEnabled": True, + "reserved1": "0000000000000000", "setupError": False, "reserved2": "", + "interfaceRef": "2800070000000000000000000001000000000000", "linkStatus": "up", "ipv4Enabled": True, + "ipv4Address": "10.113.1.192", "ipv4SubnetMask": "255.255.255.0", "ipv4AddressConfigMethod": "configStatic", + "ipv6Enabled": False, "ipv6LocalAddress": {"address": "00000000000000000000000000000000", + "addressState": {"addressType": "typeInterface", + "interfaceAddressState": "configured", + "routerAddressState": "__UNDEFINED"}}, + "ipv6PortStaticRoutableAddress": {"address": "00000000000000000000000000000000", + "addressState": {"addressType": "typeInterface", + "interfaceAddressState": "__UNDEFINED", + "routerAddressState": "__UNDEFINED"}}, + "ipv6PortRoutableAddresses": [], "ipv6AddressConfigMethod": "configStatic", "fullDuplex": True, + "supportedSpeedSettings": ["speedAutoNegotiated", "speed10MbitHalfDuplex", "speed10MbitFullDuplex", + "speed100MbitHalfDuplex", "speed100MbitFullDuplex", "speed1000MbitFullDuplex"], + "configuredSpeedSetting": "speedAutoNegotiated", "currentSpeed": "speed1gig", + "physicalLocation": {"trayRef": "0E00000000000000000000000000000000000000", "slot": 0, + "locationParent": {"refType": "controller", "controllerRef": "070000000000000000000001", + "symbolRef": None, "typedReference": None}, "locationPosition": 1, + "label": "P1"}, "ipv4GatewayAddress": "10.113.1.1", + "controllerRef": "070000000000000000000001", "controllerSlot": 1, + "dnsProperties": { + "acquisitionProperties": {"dnsAcquisitionType": "stat", + "dnsServers": [ + {"addressType": "ipv4", "ipv4Address": "10.193.0.250", "ipv6Address": None}, + {"addressType": "ipv4", "ipv4Address": "10.192.0.250", "ipv6Address": None}]}, + "dhcpAcquiredDnsServers": []}, + "ntpProperties": { + "acquisitionProperties": {"ntpAcquisitionType": "stat", "ntpServers": [ + {"addrType": "ipvx", "domainName": None, + "ipvxAddress": {"addressType": "ipv4", "ipv4Address": "216.239.35.0", "ipv6Address": None}}, + {"addrType": "ipvx", "domainName": None, + "ipvxAddress": {"addressType": "ipv4", "ipv4Address": "216.239.35.4", "ipv6Address": None}}]}, + "dhcpAcquiredNtpServers": []}, + "id": "2800070000000000000000000001000000000000"}}], + "inventory": [], "reserved1": "000000000000000000000000", "reserved2": "", "hostBoardID": "None", "physicalCacheMemorySize": 4864, + "readyToRemove": False, "boardSubmodelID": "319", "submodelSupported": True, "oemPartNumber": "E2800A-8GB", "partNumber": "111-02829+C0 ", + "rtrAttributes": {"cruType": "dedicated", "parentCru": None, "rtrAttributeData": {"hasReadyToRemoveIndicator": False, "readyToRemove": False}}, + "bootTime": "1563988406", "modelName": "2806", + "networkSettings": {"ipv4DefaultRouterAddress": "10.113.1.1", + "ipv6DefaultRouterAddress": {"address": "00000000000000000000000000000000", + "addressState": {"addressType": "typeInterface", + "interfaceAddressState": "__UNDEFINED", "routerAddressState": "__UNDEFINED"}}, + "ipv6CandidateDefaultRouterAddresses": [], + "remoteAccessEnabled": True, + "dnsProperties": {"acquisitionProperties": {"dnsAcquisitionType": "stat", + "dnsServers": [ + {"addressType": "ipv4", "ipv4Address": "10.193.0.250", "ipv6Address": None}, + {"addressType": "ipv4", "ipv4Address": "10.192.0.250", "ipv6Address": None}]}, + "dhcpAcquiredDnsServers": []}, + "ntpProperties": { + "acquisitionProperties": { + "ntpAcquisitionType": "stat", "ntpServers": [ + {"addrType": "ipvx", "domainName": None, + "ipvxAddress": {"addressType": "ipv4", "ipv4Address": "216.239.35.0", "ipv6Address": None}}, + {"addrType": "ipvx", "domainName": None, + "ipvxAddress": {"addressType": "ipv4", "ipv4Address": "216.239.35.4", "ipv6Address": None}}]}, + "dhcpAcquiredNtpServers": []}}, + "repairPolicy": {"removalData": {"removalMethod": "__UNDEFINED", "rtrAttributes": None}, "replacementMethod": "__UNDEFINED"}, + "flashCacheMemorySize": 419430400, "ctrlIocDumpData": {"iocDumpNeedsRetrieved": False, "iocDumpTag": 0, "timeStamp": "0"}, + "locateInProgress": False, "hasTrayIdentityIndicator": False, "controllerErrorMode": "notInErrorMode", + "codeVersions": [{"codeModule": "raid", "versionString": "08.42.30.05"}, {"codeModule": "hypervisor", "versionString": "08.42.30.05"}, + {"codeModule": "management", "versionString": "11.42.0000.0026"}, {"codeModule": "iom", "versionString": "11.42.0G00.0001"}, + {"codeModule": "bundle", "versionString": "08.42.30.05"}, {"codeModule": "bundleDisplay", "versionString": "11.40.3R2"}], + "id": "070000000000000000000001"}], + "drive": [{"offline": False, "hotSpare": False, "invalidDriveData": False, "available": True, "pfa": False, + "driveRef": "0100000050000396AC882ED10000000000000000", "status": "optimal", "cause": "None", + "interfaceType": {"driveType": "sas", "fibre": None, + "sas": {"deviceName": "50000396AC882ED1", + "drivePortAddresses": [{"channel": 2, "portIdentifier": "50000396AC882ED3"}, + {"channel": 1, "portIdentifier": "50000396AC882ED2"}]}, + "scsi": None}, + "physicalLocation": {"trayRef": "0E00000000000000000000000000000000000000", "slot": 6, + "locationParent": {"refType": "genericTyped", "controllerRef": None, "symbolRef": None, + "typedReference": {"componentType": "tray", + "symbolRef": "0E00000000000000000000000000000000000000"}}, + "locationPosition": 6, "label": "5"}, "manufacturer": "TOSHIBA ", + "manufacturerDate": "1447200000", "productID": "PX04SVQ160 ", "serialNumber": "Y530A001T5MD", "softwareVersion": "MSB6", "blkSize": 512, + "usableCapacity": "1599784443904", "rawCapacity": "1600321314816", "worldWideName": "50000396AC882ED10000000000000000", + "currentVolumeGroupRef": "0000000000000000000000000000000000000000", "sparedForDriveRef": "0000000000000000000000000000000000000000", + "mirrorDrive": "0000000000000000000000000000000000000000", "nonRedundantAccess": False, "workingChannel": -1, "volumeGroupIndex": -1, + "currentSpeed": "speed12gig", "maxSpeed": "speed12gig", "uncertified": False, "hasDegradedChannel": False, "degradedChannels": [], + "phyDriveType": "sas", "spindleSpeed": 0, "rtrAttributes": {"cruType": "dedicated", "parentCru": None, + "rtrAttributeData": {"hasReadyToRemoveIndicator": False, + "readyToRemove": False}}, "reserved": "", + "phyDriveTypeData": {"phyDriveType": "sas", "sataDriveAttributes": None}, "pfaReason": "None", "bypassSource": [], + "repairPolicy": {"removalData": {"removalMethod": "self", "rtrAttributes": {"hasReadyToRemoveIndicator": False, "readyToRemove": False}}, + "replacementMethod": "self"}, "fdeCapable": True, "fdeEnabled": False, "fdeLocked": False, + "lockKeyID": "0000000000000000000000000000000000000000", + "ssdWearLife": {"averageEraseCountPercent": 18, "spareBlocksRemainingPercent": 91, "isWearLifeMonitoringSupported": True, + "percentEnduranceUsed": 18}, "driveMediaType": "ssd", "fpgaVersion": "", + "protectionInformationCapabilities": {"protectionInformationCapable": True, "protectionType": "type2Protection"}, + "protectionInformationCapable": False, "protectionType": "type0Protection", "interposerPresent": False, + "interposerRef": "0000000000000000000000000000000000000000", "currentCommandAgingTimeout": 6, "defaultCommandAgingTimeout": 6, + "driveTemperature": {"currentTemp": 25, "refTemp": 64}, "blkSizePhysical": 4096, "lowestAlignedLBA": "0", "removed": False, + "locateInProgress": False, "fipsCapable": False, "firmwareVersion": "MSB6", "lockKeyIDValue": None, + "id": "0100000050000396AC882ED10000000000000000"}, + {"offline": False, "hotSpare": False, "invalidDriveData": False, "available": True, "pfa": False, + "driveRef": "0100000050000396AC882EDD0000000000000000", "status": "optimal", "cause": "None", + "interfaceType": {"driveType": "sas", "fibre": None, + "sas": {"deviceName": "50000396AC882EDD", + "drivePortAddresses": [{"channel": 2, "portIdentifier": "50000396AC882EDF"}, + {"channel": 1, "portIdentifier": "50000396AC882EDE"}]}, + "scsi": None}, + "physicalLocation": {"trayRef": "0E00000000000000000000000000000000000000", "slot": 8, + "locationParent": {"refType": "genericTyped", "controllerRef": None, "symbolRef": None, + "typedReference": {"componentType": "tray", + "symbolRef": "0E00000000000000000000000000000000000000"}}, + "locationPosition": 8, "label": "7"}, "manufacturer": "TOSHIBA ", + "manufacturerDate": "1447200000", "productID": "PX04SVQ160 ", "serialNumber": "Y530A004T5MD", "softwareVersion": "MSB6", "blkSize": 512, + "usableCapacity": "1599784443904", "rawCapacity": "1600321314816", "worldWideName": "50000396AC882EDD0000000000000000", + "currentVolumeGroupRef": "0000000000000000000000000000000000000000", "sparedForDriveRef": "0000000000000000000000000000000000000000", + "mirrorDrive": "0000000000000000000000000000000000000000", "nonRedundantAccess": False, "workingChannel": -1, "volumeGroupIndex": -1, + "currentSpeed": "speed12gig", "maxSpeed": "speed12gig", "uncertified": False, "hasDegradedChannel": False, "degradedChannels": [], + "phyDriveType": "sas", "spindleSpeed": 0, "rtrAttributes": {"cruType": "dedicated", "parentCru": None, + "rtrAttributeData": {"hasReadyToRemoveIndicator": False, + "readyToRemove": False}}, "reserved": "", + "phyDriveTypeData": {"phyDriveType": "sas", "sataDriveAttributes": None}, "pfaReason": "None", "bypassSource": [], + "repairPolicy": {"removalData": {"removalMethod": "self", "rtrAttributes": {"hasReadyToRemoveIndicator": False, "readyToRemove": False}}, + "replacementMethod": "self"}, "fdeCapable": True, "fdeEnabled": False, "fdeLocked": False, + "lockKeyID": "0000000000000000000000000000000000000000", + "ssdWearLife": {"averageEraseCountPercent": 18, "spareBlocksRemainingPercent": 91, "isWearLifeMonitoringSupported": True, + "percentEnduranceUsed": 18}, "driveMediaType": "ssd", "fpgaVersion": "", + "protectionInformationCapabilities": {"protectionInformationCapable": True, "protectionType": "type2Protection"}, + "protectionInformationCapable": False, "protectionType": "type0Protection", "interposerPresent": False, + "interposerRef": "0000000000000000000000000000000000000000", "currentCommandAgingTimeout": 6, "defaultCommandAgingTimeout": 6, + "driveTemperature": {"currentTemp": 25, "refTemp": 64}, "blkSizePhysical": 4096, "lowestAlignedLBA": "0", "removed": False, + "locateInProgress": False, "fipsCapable": False, "firmwareVersion": "MSB6", "lockKeyIDValue": None, + "id": "0100000050000396AC882EDD0000000000000000"}], + "volumeGroup": [ + {"sequenceNum": 1, "offline": False, "raidLevel": "raid6", "worldWideName": "600A098000A4B9D10000380A5D4AAC3C", + "volumeGroupRef": "04000000600A098000A4B9D10000380A5D4AAC3C", "reserved1": "000000000000000000000000", "reserved2": "", + "trayLossProtection": False, "label": "beegfs_storage_vg", "state": "complete", "spindleSpeedMatch": True, "spindleSpeed": 10500, + "isInaccessible": False, "securityType": "capable", "drawerLossProtection": False, "protectionInformationCapable": False, + "protectionInformationCapabilities": {"protectionInformationCapable": True, "protectionType": "type2Protection"}, + "volumeGroupData": {"type": "unknown", "diskPoolData": None}, + "usage": "standard", "driveBlockFormat": "allNative", "reservedSpaceAllocated": False, "securityLevel": "fde", "usedSpace": "1099511627776", + "totalRaidedSpace": "9597654597632", + "extents": [{"sectorOffset": "268435456", "rawCapacity": "8498142969856", "raidLevel": "raid6", + "volumeGroupRef": "04000000600A098000A4B9D10000380A5D4AAC3C", "freeExtentRef": "03000000600A098000A4B9D10000380A5D4AAC3C", + "reserved1": "000000000000000000000000", "reserved2": ""}], + "largestFreeExtentSize": "8498142969856", "raidStatus": "optimal", "freeSpace": "8498142969856", "drivePhysicalType": "sas", + "driveMediaType": "hdd", "normalizedSpindleSpeed": "spindleSpeed10k", "diskPool": False, + "id": "04000000600A098000A4B9D10000380A5D4AAC3C", "name": "beegfs_storage_vg"}], "volume": [ + {"offline": False, "extremeProtection": False, "volumeHandle": 0, "raidLevel": "raid6", "sectorOffset": "0", + "worldWideName": "600A098000A4B28D00003E435D4AAC54", "label": "beegfs_storage_01_1", "blkSize": 512, "capacity": "1099511627776", + "reconPriority": 1, "segmentSize": 131072, "action": "None", + "cache": {"cwob": False, "enterpriseCacheDump": False, "mirrorActive": True, "mirrorEnable": True, "readCacheActive": False, + "readCacheEnable": False, "writeCacheActive": True, "writeCacheEnable": True, "cacheFlushModifier": "flush10Sec", + "readAheadMultiplier": 1}, "mediaScan": {"enable": True, "parityValidationEnable": True}, + "volumeRef": "02000000600A098000A4B28D00003E435D4AAC54", "status": "optimal", "volumeGroupRef": "04000000600A098000A4B9D10000380A5D4AAC3C", + "currentManager": "070000000000000000000001", "preferredManager": "070000000000000000000001", + "perms": {"mapToLUN": True, "snapShot": True, "format": True, "reconfigure": True, "mirrorPrimary": True, "mirrorSecondary": True, + "copySource": True, "copyTarget": True, "readable": True, "writable": True, "rollback": True, "mirrorSync": True, "newImage": True, + "allowDVE": True, "allowDSS": True, "concatVolumeMember": False, "flashReadCache": True, "asyncMirrorPrimary": True, + "asyncMirrorSecondary": True, "pitGroup": True, "cacheParametersChangeable": True, "allowThinManualExpansion": False, + "allowThinGrowthParametersChange": False}, + "mgmtClientAttribute": 0, "dssPreallocEnabled": False, "dssMaxSegmentSize": 0, "preReadRedundancyCheckEnabled": False, + "protectionInformationCapable": False, "protectionType": "type0Protection", "applicationTagOwned": True, + "repairedBlockCount": 0, "extendedUniqueIdentifier": "", "cacheMirroringValidateProtectionInformation": False, + "expectedProtectionInformationAppTag": 0, "volumeUse": "standardVolume", "volumeFull": False, "volumeCopyTarget": False, "volumeCopySource": False, + "pitBaseVolume": False, "asyncMirrorTarget": False, "asyncMirrorSource": False, "remoteMirrorSource": False, "remoteMirrorTarget": False, + "diskPool": False, "flashCached": False, "increasingBy": "0", "metadata": [], "dataAssurance": False, "objectType": "volume", + "listOfMappings": [ + {"lunMappingRef": "88000000A1010000000000000000000000000000", "lun": 1, "ssid": 0, "perms": 15, + "volumeRef": "02000000600A098000A4B28D00003E435D4AAC54", "type": "host", "mapRef": "84000000600A098000A4B28D00303D065D430118", + "id": "88000000A1010000000000000000000000000000"}], + "mapped": True, "currentControllerId": "070000000000000000000001", + "cacheSettings": {"cwob": False, "enterpriseCacheDump": False, "mirrorActive": True, "mirrorEnable": True, "readCacheActive": False, + "readCacheEnable": False, "writeCacheActive": True, "writeCacheEnable": True, "cacheFlushModifier": "flush10Sec", + "readAheadMultiplier": 1}, + "thinProvisioned": False, "preferredControllerId": "070000000000000000000001", "totalSizeInBytes": "1099511627776", "onlineVolumeCopy": False, + "wwn": "600A098000A4B28D00003E435D4AAC54", "name": "beegfs_storage_01_1", "id": "02000000600A098000A4B28D00003E435D4AAC54"}], + "storagePoolBundle": {"cluster": [], "host": [ + {"hostRef": "84000000600A098000A4B28D00303D005D430107", "clusterRef": "0000000000000000000000000000000000000000", "label": "test", + "isSAControlled": False, "confirmLUNMappingCreation": False, "hostTypeIndex": 28, "protectionInformationCapableAccessMethod": True, + "isLargeBlockFormatHost": False, "isLun0Restricted": False, "ports": [], + "initiators": [ + {"initiatorRef": "89000000600A098000A4B9D1003037005D4300F5", + "nodeName": {"ioInterfaceType": "iscsi", "iscsiNodeName": "iqn.iscsi_tests1", "remoteNodeWWN": None, "nvmeNodeName": None}, + "alias": {"ioInterfaceType": "iscsi", "iscsiAlias": ""}, "label": "iscsi_test1", + "configuredAuthMethods": {"authMethodData": [{"authMethod": "None", "chapSecret": None}]}, + "hostRef": "84000000600A098000A4B28D00303D005D430107", "initiatorInactive": False, "id": "89000000600A098000A4B9D1003037005D4300F5"}], + "hostSidePorts": [{"type": "iscsi", "address": "iqn.iscsi_tests1", "label": "iscsi_test1"}], + "id": "84000000600A098000A4B28D00303D005D430107", "name": "test"}, + {"hostRef": "84000000600A098000A4B9D1003037035D4300F8", "clusterRef": "0000000000000000000000000000000000000000", "label": "test2", + "isSAControlled": True, "confirmLUNMappingCreation": False, "hostTypeIndex": 28, "protectionInformationCapableAccessMethod": True, + "isLargeBlockFormatHost": False, "isLun0Restricted": False, "ports": [], + "initiators": [ + {"initiatorRef": "89000000600A098000A4B9D1003037075D4300F9", + "nodeName": {"ioInterfaceType": "iscsi", "iscsiNodeName": "iqn.iscsi_tests2", "remoteNodeWWN": None, "nvmeNodeName": None}, + "alias": {"ioInterfaceType": "iscsi", "iscsiAlias": ""}, "label": "iscsi_test2", + "configuredAuthMethods": {"authMethodData": [{"authMethod": "None", "chapSecret": None}]}, + "hostRef": "84000000600A098000A4B9D1003037035D4300F8", "initiatorInactive": False, "id": "89000000600A098000A4B9D1003037075D4300F9"}], + "hostSidePorts": [{"type": "iscsi", "address": "iqn.iscsi_tests2", "label": "iscsi_test2"}], + "id": "84000000600A098000A4B9D1003037035D4300F8", "name": "test2"}, + {"hostRef": "84000000600A098000A4B28D00303D065D430118", "clusterRef": "0000000000000000000000000000000000000000", "label": "beegfs_storage1", + "isSAControlled": False, "confirmLUNMappingCreation": False, "hostTypeIndex": 28, "protectionInformationCapableAccessMethod": True, + "isLargeBlockFormatHost": False, "isLun0Restricted": False, "ports": [], + "initiators": [ + {"initiatorRef": "89000000600A098000A4B28D00303CF55D4300E3", + "nodeName": {"ioInterfaceType": "iscsi", "iscsiNodeName": "iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818", "remoteNodeWWN": None, + "nvmeNodeName": None}, "alias": {"ioInterfaceType": "iscsi", "iscsiAlias": ""}, "label": "beegfs_storage1_iscsi_0", + "configuredAuthMethods": {"authMethodData": [{"authMethod": "None", "chapSecret": None}]}, + "hostRef": "84000000600A098000A4B28D00303D065D430118", "initiatorInactive": False, "id": "89000000600A098000A4B28D00303CF55D4300E3"}], + "hostSidePorts": [{"type": "iscsi", "address": "iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818", "label": "beegfs_storage1_iscsi_0"}], + "id": "84000000600A098000A4B28D00303D065D430118", "name": "beegfs_storage1"}, + {"hostRef": "84000000600A098000A4B9D10030370B5D430109", "clusterRef": "0000000000000000000000000000000000000000", "label": "beegfs_metadata1", + "isSAControlled": False, "confirmLUNMappingCreation": False, "hostTypeIndex": 28, "protectionInformationCapableAccessMethod": True, + "isLargeBlockFormatHost": False, "isLun0Restricted": False, "ports": [], + "initiators": [ + {"initiatorRef": "89000000600A098000A4B28D00303CFC5D4300F7", + "nodeName": {"ioInterfaceType": "iscsi", "iscsiNodeName": "iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8", "remoteNodeWWN": None, + "nvmeNodeName": None}, "alias": {"ioInterfaceType": "iscsi", "iscsiAlias": ""}, "label": "beegfs_metadata1_iscsi_0", + "configuredAuthMethods": {"authMethodData": [{"authMethod": "None", "chapSecret": None}]}, + "hostRef": "84000000600A098000A4B9D10030370B5D430109", "initiatorInactive": False, "id": "89000000600A098000A4B28D00303CFC5D4300F7"}], + "hostSidePorts": [{"type": "iscsi", "address": "iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8", "label": "beegfs_metadata1_iscsi_0"}], + "id": "84000000600A098000A4B9D10030370B5D430109", "name": "beegfs_metadata1"}], "lunMapping": [ + {"lunMappingRef": "8800000000000000000000000000000000000000", "lun": 7, "ssid": 16384, "perms": 15, + "volumeRef": "21000000600A098000A4B28D000027EC5CF10481", "type": "all", "mapRef": "0000000000000000000000000000000000000000", + "id": "8800000000000000000000000000000000000000"}, + {"lunMappingRef": "880000008B010000000000000000000000000000", "lun": 7, "ssid": 16384, "perms": 15, + "volumeRef": "21000000600A098000A4B28D000027EC5CF10481", "type": "host", "mapRef": "84000000600A098000A4B28D00303D065D430118", + "id": "880000008B010000000000000000000000000000"}, + {"lunMappingRef": "8800000090010000000000000000000000000000", "lun": 7, "ssid": 16384, "perms": 15, + "volumeRef": "21000000600A098000A4B28D000027EC5CF10481", "type": "host", "mapRef": "84000000600A098000A4B9D10030370B5D430109", + "id": "8800000090010000000000000000000000000000"}, + {"lunMappingRef": "8800000092010000000000000000000000000000", "lun": 7, "ssid": 16384, "perms": 15, + "volumeRef": "21000000600A098000A4B28D000027EC5CF10481", "type": "host", "mapRef": "84000000600A098000A4B28D00303D005D430107", + "id": "8800000092010000000000000000000000000000"}, {"lunMappingRef": "88000000A1010000000000000000000000000000", "lun": 1, "ssid": 0, "perms": 15, + "volumeRef": "02000000600A098000A4B28D00003E435D4AAC54", "type": "host", + "mapRef": "84000000600A098000A4B28D00303D065D430118", + "id": "88000000A1010000000000000000000000000000"}]}, "highLevelVolBundle": {"pit": []}} + + EXPECTED_GET_ARRAY_FACTS = {'facts_from_proxy': False, 'netapp_controllers': [{'name': 'A', 'serial': '021619039162', 'status': 'optimal'}], + 'netapp_disks': [ + {'available': True, 'firmware_version': 'MSB6', 'id': '0100000050000396AC882ED10000000000000000', 'media_type': 'ssd', + 'product_id': 'PX04SVQ160 ', 'serial_number': 'Y530A001T5MD', 'status': 'optimal', + 'tray_ref': '0E00000000000000000000000000000000000000', 'usable_bytes': '1599784443904'}, + {'available': True, 'firmware_version': 'MSB6', 'id': '0100000050000396AC882EDD0000000000000000', 'media_type': 'ssd', + 'product_id': 'PX04SVQ160 ', 'serial_number': 'Y530A004T5MD', 'status': 'optimal', + 'tray_ref': '0E00000000000000000000000000000000000000', 'usable_bytes': '1599784443904'}], + 'netapp_driveside_interfaces': [{'controller': 'A', 'interface_speed': '12g', 'interface_type': 'sas'}], + 'netapp_enabled_features': ['autoCodeSync', 'autoLunTransfer', 'bundleMigration', 'driveSlotLimit', 'flashReadCache', + 'mixedDriveTypes', 'performanceTier', 'protectionInformation', 'raid6', 'secureVolume', + 'ssdSupport', 'stagedDownload', 'storagePoolsType2', 'subLunsAllowed', + 'totalNumberOfArvmMirrorsPerArray', 'totalNumberOfPitsPerArray', + 'totalNumberOfThinVolumesPerArray'], 'netapp_host_groups': [], + 'netapp_host_types': [{'index': 0, 'type': 'FactoryDefault'}, {'index': 1, 'type': 'W2KNETNCL'}, + {'index': 27, 'type': 'LnxTPGSALUA_SF'}, {'index': 28, 'type': 'LnxDHALUA'}], + 'netapp_hosts': [ + {'group_id': '0000000000000000000000000000000000000000', 'host_type_index': 28, + 'hosts_reference': '84000000600A098000A4B28D00303D005D430107', + 'id': '84000000600A098000A4B28D00303D005D430107', 'name': 'test', + 'posts': [{'address': 'iqn.iscsi_tests1', 'label': 'iscsi_test1', 'type': 'iscsi'}]}, + {'group_id': '0000000000000000000000000000000000000000', 'host_type_index': 28, + 'hosts_reference': '84000000600A098000A4B9D1003037035D4300F8', + 'id': '84000000600A098000A4B9D1003037035D4300F8', 'name': 'test2', + 'posts': [{'address': 'iqn.iscsi_tests2', 'label': 'iscsi_test2', 'type': 'iscsi'}]}, + {'group_id': '0000000000000000000000000000000000000000', 'host_type_index': 28, + 'hosts_reference': '84000000600A098000A4B28D00303D065D430118', + 'id': '84000000600A098000A4B28D00303D065D430118', 'name': 'beegfs_storage1', + 'posts': [{'address': 'iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818', 'label': 'beegfs_storage1_iscsi_0', + 'type': 'iscsi'}]}, + {'group_id': '0000000000000000000000000000000000000000', 'host_type_index': 28, + 'hosts_reference': '84000000600A098000A4B9D10030370B5D430109', + 'id': '84000000600A098000A4B9D10030370B5D430109', 'name': 'beegfs_metadata1', + 'posts': [{'address': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8', 'label': 'beegfs_metadata1_iscsi_0', + 'type': 'iscsi'}]}], + 'netapp_hostside_interfaces': [{'fc': [], 'ib': [], + 'iscsi': [ + {'controller': 'A', 'current_interface_speed': '10g', 'ipv4_address': '10.10.11.110', + 'ipv4_enabled': True, + 'ipv4_gateway': '0.0.0.0', 'ipv4_subnet_mask': '255.255.255.0', 'ipv6_enabled': True, + 'iqn': 'iqn.1992-08.com.netapp:2806.600a098000a4b28d000000005cf10481', 'link_status': 'up', + 'mtu': 9000, + 'supported_interface_speeds': ['1g', '10g']}], 'sas': []}], + 'netapp_management_interfaces': [ + {'alias': 'ictm0718s01c1-a', 'channel': 1, 'controller': 'A', 'dns_config_method': 'stat', + 'dns_servers': [{'addressType': 'ipv4', 'ipv4Address': '10.193.0.250', 'ipv6Address': None}, + {'addressType': 'ipv4', 'ipv4Address': '10.192.0.250', 'ipv6Address': None}], + 'ipv4_address': '10.113.1.192', + 'ipv4_address_config_method': 'static', 'ipv4_enabled': True, 'ipv4_gateway': '10.113.1.1', + 'ipv4_subnet_mask': '255.255.255.0', 'ipv6_enabled': False, 'link_status': 'up', + 'mac_address': '00A098A4B28D', 'name': 'wan0', 'ntp_config_method': 'stat', + 'ntp_servers': [ + {'addrType': 'ipvx', 'domainName': None, + 'ipvxAddress': {'addressType': 'ipv4', 'ipv4Address': '216.239.35.0', 'ipv6Address': None}}, + {'addrType': 'ipvx', 'domainName': None, + 'ipvxAddress': {'addressType': 'ipv4', 'ipv4Address': '216.239.35.4', 'ipv6Address': None}}], + 'remote_ssh_access': True}], + 'netapp_storage_array': {'cache_block_sizes': [4096, 8192, 16384, 32768], 'chassis_serial': '021633035190', + 'firmware': '08.42.30.05', 'name': 'ictm0718s01c1', + 'segment_sizes': [32768, 65536, 131072, 262144, 524288, 495616, 655360, 1982464], + 'wwn': '600A098000A4B28D000000005CF10481'}, + 'netapp_storage_pools': [ + {'available_capacity': '8498142969856', 'id': '04000000600A098000A4B9D10000380A5D4AAC3C', 'name': 'beegfs_storage_vg', + 'total_capacity': '9597654597632', 'used_capacity': '1099511627776'}], + 'netapp_volumes': [ + {'capacity': '1099511627776', 'id': '02000000600A098000A4B28D00003E435D4AAC54', 'is_thin_provisioned': False, + 'name': 'beegfs_storage_01_1', 'parent_storage_pool_id': '04000000600A098000A4B9D10000380A5D4AAC3C', 'workload': []}], + 'netapp_volumes_by_initiators': {'beegfs_metadata1': [], + 'beegfs_storage1': [{ + 'id': '02000000600A098000A4B28D00003E435D4AAC54', + 'meta_data': {}, + 'name': 'beegfs_storage_01_1', + 'workload_name': '', + 'wwn': '600A098000A4B28D00003E435D4AAC54'}], + 'test': [], 'test2': []}, + 'netapp_workload_tags': [ + {'attributes': [{'key': 'profileId', 'value': 'ansible_workload_1'}], 'id': '4200000001000000000000000000000000000000', + 'name': 'beegfs_metadata'}, + {'attributes': [{'key': 'profileId', 'value': 'Other_1'}], 'id': '4200000002000000000000000000000000000000', + 'name': 'other_workload_1'}], 'snapshot_images': [], 'ssid': '1'} + + def _set_args(self, **kwargs): + module_args = self.REQUIRED_PARAMS.copy() + if kwargs is not None: + module_args.update(kwargs) + set_module_args(module_args) + + def test_get_controllers_pass(self): + """Verify get_controllers returns the expected results.""" + self._set_args() + facts = Facts() + with mock.patch(self.REQUEST_FUNC, return_value=(200, ["070000000000000000000002", "070000000000000000000001"])): + self.assertEqual(facts.get_controllers(), {"070000000000000000000001": "A", "070000000000000000000002": "B"}) + + def test_get_controllers_fail(self): + """Verify get_controllers throws the expected exceptions.""" + self._set_args() + facts = Facts() + with self.assertRaisesRegexp(AnsibleFailJson, "Failed to retrieve controller list!"): + with mock.patch(self.REQUEST_FUNC, return_value=Exception()): + facts.get_controllers() + + def test_get_array_facts_pass(self): + """Verify get_array_facts method returns expected results.""" + self._set_args() + facts = Facts() + with mock.patch(self.GET_CONTROLLERS_FUNC, return_value={"070000000000000000000001": "A", "070000000000000000000002": "B"}): + with mock.patch(self.REQUEST_FUNC, side_effect=[(200, self.GRAPH_RESPONSE), (200, self.WORKLOAD_RESPONSE)]): + self.assertEquals(facts.get_array_facts(), self.EXPECTED_GET_ARRAY_FACTS)