def get_volume(self, volume_name): self.debug('fetching volumes') # fetch the list of volume objects and look for one with a matching name (we'll need to merge volumes and thin-volumes) try: (rc, volumes) = request(self.api_url + "/storage-systems/%s/volumes" % (self.ssid), headers=dict(Accept="application/json"), url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs) except Exception as err: self.module.fail_json( msg="Failed to obtain list of standard/thick volumes. Array Id [%s]. Error[%s]." % (self.ssid, to_native(err))) try: self.debug('fetching thin-volumes') (rc, thinvols) = request(self.api_url + "/storage-systems/%s/thin-volumes" % (self.ssid), headers=dict(Accept="application/json"), url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs) except Exception as err: self.module.fail_json( msg="Failed to obtain list of thin volumes. Array Id [%s]. Error[%s]." % (self.ssid, to_native(err))) volumes.extend(thinvols) self.debug("searching for volume '%s'", volume_name) volume_detail = next(ifilter(lambda a: a['name'] == volume_name, volumes), None) if volume_detail: self.debug('found') else: self.debug('not found') return volume_detail
def get_host_and_group_map(module, ssid, api_url, user, pwd, validate_certs): mapping = dict(host=dict(), group=dict()) hostgroups = 'storage-systems/%s/host-groups' % ssid groups_url = api_url + hostgroups try: hg_rc, hg_data = request(groups_url, headers=HEADERS, url_username=user, url_password=pwd, validate_certs=validate_certs) except Exception as err: module.fail_json(msg="Failed to get host groups. Id [%s]. Error [%s]" % (ssid, to_native(err))) for group in hg_data: mapping['group'][group['name']] = group['id'] hosts = 'storage-systems/%s/hosts' % ssid hosts_url = api_url + hosts try: h_rc, h_data = request(hosts_url, headers=HEADERS, url_username=user, url_password=pwd, validate_certs=validate_certs) except Exception as err: module.fail_json(msg="Failed to get hosts. Id [%s]. Error [%s]" % (ssid, to_native(err))) for host in h_data: mapping['host'][host['name']] = host['id'] return mapping
def update(self): """Execute the changes the require changes on the storage array.""" target_match, lun_reference, lun = self.get_lun_mapping() update = (self.state and not target_match) or (not self.state and target_match) if update and not self.check_mode: try: if self.state: body = dict() target = None if not self.target else self.mapping_info[ "target_by_name"][self.target] if target: body.update(dict(targetId=target)) if self.lun is not None: body.update(dict(lun=self.lun)) if lun_reference: rc, response = request( self.url + "storage-systems/%s/volume-mappings/%s/move" % (self.ssid, lun_reference), method="POST", data=json.dumps(body), headers=HEADERS, **self.creds) else: body.update( dict(mappableObjectId=self. mapping_info["volume_by_name"][self.volume])) rc, response = request( self.url + "storage-systems/%s/volume-mappings" % self.ssid, method="POST", data=json.dumps(body), headers=HEADERS, **self.creds) else: # Remove existing lun mapping for volume and target rc, response = request( self.url + "storage-systems/%s/volume-mappings/%s" % (self.ssid, lun_reference), method="DELETE", headers=HEADERS, **self.creds) except Exception as error: self.module.fail_json( msg= "Failed to update storage array lun mapping. Id [%s]. Error [%s]" % (self.ssid, to_native(error))) self.module.exit_json(msg="Lun mapping is complete.", changed=update)
def create_storage_pool(self): self.debug("creating storage pool...") sp_add_req = dict(raidLevel=self.raid_level, diskDriveIds=self.disk_ids, name=self.name) if self.erase_secured_drives: sp_add_req['eraseSecuredDrives'] = self.erase_secured_drives try: (rc, resp) = request(self.api_url + "/storage-systems/%s/storage-pools" % (self.ssid), data=json.dumps(sp_add_req), headers=self.post_headers, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except: err = get_exception() pool_id = self.pool_detail['id'] self.module.exit_json( msg= "Failed to create storage pool. Pool id [%s]. Array id [%s]. Error[%s]." % (pool_id, self.ssid, str(err))) self.pool_detail = self.get_storage_pool(self.name) if self.secure_pool: secure_pool_data = dict(securePool=True) try: (retc, r) = request(self.api_url + "/storage-systems/%s/storage-pools/%s" % (self.ssid, self.pool_detail['id']), data=json.dumps(secure_pool_data), headers=self.post_headers, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120, ignore_errors=True) except: err = get_exception() pool_id = self.pool_detail['id'] self.module.exit_json( msg= "Failed to update storage pool. Pool id [%s]. Array id [%s]. Error[%s]." % (pool_id, self.ssid, str(err)))
def delete_log_messages(self): """Delete all audit-log messages.""" self._logger.info("Deleting audit-log messages...") try: if self.proxy_used: rc, result = request(self.url + "audit-log?clearAll=True", timeout=300, method="DELETE", headers=self.HEADERS, **self.creds) else: rc, result = request(self.url + "storage-systems/%s/audit-log?clearAll=True" % self.ssid, timeout=300, method="DELETE", headers=self.HEADERS, **self.creds) except Exception as err: self.module.fail_json(msg="Failed to delete audit-log messages! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
def find_valid_copy_pair_targets_and_sources(params): get_status = 'storage-systems/%s/volumes' % params['ssid'] url = params['api_url'] + get_status (response_code, response_data) = request(url, ignore_errors=True, method='GET', url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS, validate_certs=params['validate_certs']) if response_code == 200: source_capacity = None candidates = [] for volume in response_data: if volume['id'] == params['search_volume_id']: source_capacity = volume['capacity'] else: candidates.append(volume) potential_sources = [] potential_targets = [] for volume in candidates: if volume['capacity'] > source_capacity: if volume['volumeCopyTarget'] is False: if volume['volumeCopySource'] is False: potential_targets.append(volume['id']) else: if volume['volumeCopyTarget'] is False: if volume['volumeCopySource'] is False: potential_sources.append(volume['id']) return potential_targets, potential_sources else: raise Exception("Response [%s]" % response_code)
def send_test_email(self): """Send a test email to verify that the provided configuration is valid and functional.""" if not self.check_mode: try: (rc, result) = request( self.url + 'storage-systems/%s/device-alerts/alert-email-test' % self.ssid, timeout=300, method='POST', headers=HEADERS, **self.creds) if result['response'] != 'emailSentOK': self.module.fail_json( msg= "The test email failed with status=[%s]! Array Id [%s]." % (result['response'], self.ssid)) # This is going to catch cases like a connection failure except Exception as err: self.module.fail_json( msg= "We failed to send the test email! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
def reassign_ports(self, apply=True): post_body = dict( portsToUpdate=dict() ) for port in self.ports: if self.port_on_diff_host(port): host_port = self.get_port(port['label'], port['port']) post_body['portsToUpdate'].update(dict( portRef=host_port['id'], hostRef=self.host_obj['id'], label=port['label'] # Doesn't yet address port identifier or chap secret )) self._logger.info("reassign_ports: %s", pformat(post_body)) if apply: try: (rc, self.host_obj) = request( self.url + 'storage-systems/%s/hosts/%s' % (self.ssid, self.host_obj['id']), url_username=self.user, url_password=self.pwd, headers=HEADERS, validate_certs=self.certs, method='POST', data=json.dumps(post_body)) except Exception as err: self.module.fail_json( msg="Failed to reassign host port. Host Id [%s]. Array Id [%s]. Error [%s]." % ( self.host_obj['id'], self.ssid, to_native(err))) return post_body
def hostports_available(self): used_ids = list() try: (rc, self.available_ports) = request(self.url + 'storage-systems/%s/unassociated-host-ports' % self.ssid, url_password=self.pwd, url_username=self.user, validate_certs=self.certs, headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to get unassociated host ports. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) if len(self.available_ports) > 0 and len(self.ports) <= len(self.available_ports): for port in self.ports: for free_port in self.available_ports: # Desired Type matches but also make sure we haven't already used the ID if not free_port['id'] in used_ids: # update the port arg to have an id attribute used_ids.append(free_port['id']) break if len(used_ids) != len(self.ports) and not self.force_port: self.module.fail_json( msg="There are not enough free host ports with the specified port types to proceed") else: return True else: self.module.fail_json(msg="There are no host ports available OR there are not enough unassigned host ports")
def reassign_ports(self, apply=True): if not self.post_body: self.post_body = dict( portsToUpdate=dict() ) for port in self.ports: if self.port_on_diff_host(port): self.post_body['portsToUpdate'].update(dict( portRef=self.other_host['hostPortRef'], hostRef=self.host_obj['id'], # Doesn't yet address port identifier or chap secret )) if apply: try: (rc, self.host_obj) = request( self.url + 'storage-systems/%s/hosts/%s' % (self.ssid, self.host_obj['id']), url_username=self.user, url_password=self.pwd, headers=HEADERS, validate_certs=self.certs, method='POST', data=json.dumps(self.post_body)) except: err = get_exception() self.module.fail_json( msg="Failed to reassign host port. Host Id [%s]. Array Id [%s]. Error [%s]." % ( self.host_obj['id'], self.ssid, str(err)))
def create_host(self): post_body = dict( name=self.name, host_type=dict(index=self.host_type_index), groupId=self.group_id, ports=self.ports ) if self.ports: # Check that all supplied port args are valid if self.hostports_available: post_body.update(ports=self.ports) elif not self.force_port: self.module.fail_json( msg="You supplied ports that are already in use. Supply force_port to True if you wish to reassign the ports") if not self.host_exists: try: (rc, create_resp) = request(self.url + "storage-systems/%s/hosts" % self.ssid, method='POST', url_username=self.user, url_password=self.pwd, validate_certs=self.certs, data=json.dumps(post_body), headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to create host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) else: self.module.exit_json(changed=False, msg="Host already exists. Id [%s]. Host [%s]." % (self.ssid, self.name)) self.host_obj = create_resp if self.ports and self.force_port: self.reassign_ports() self.module.exit_json(changed=True, **self.host_obj)
def create_volume(self, pool_id, name, size_unit, size, segment_size_kb, data_assurance_enabled): volume_add_req = dict( name=name, poolId=pool_id, sizeUnit=size_unit, size=size, segSize=segment_size_kb, dataAssuranceEnabled=data_assurance_enabled, ) self.debug("creating volume '%s'", name) try: (rc, resp) = request(self.api_url + "/storage-systems/%s/volumes" % (self.ssid), data=json.dumps(volume_add_req), headers=HEADERS, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception as err: self.module.fail_json( msg= "Failed to create volume. Volume [%s]. Array Id [%s]. Error[%s]." % (self.name, self.ssid, to_native(err)))
def create_host(self): post_body = dict( name=self.name, hostType=dict(index=self.host_type_index), groupId=self.group_id, ports=self.ports ) if self.ports: # Check that all supplied port args are valid if self.hostports_available: post_body.update(ports=self.ports) elif not self.force_port: self.module.fail_json( msg="You supplied ports that are already in use. Supply force_port to True if you wish to reassign the ports") if not self.host_exists: try: (rc, create_resp) = request(self.url + "storage-systems/%s/hosts" % self.ssid, method='POST', url_username=self.user, url_password=self.pwd, validate_certs=self.certs, data=json.dumps(post_body), headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to create host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) else: self.module.exit_json(changed=False, msg="Host already exists. Id [%s]. Host [%s]." % (self.ssid, self.name)) self.host_obj = create_resp if self.ports and self.force_port: self.reassign_ports() self.module.exit_json(changed=True, **self.host_obj)
def create_thin_volume(self, pool_id, name, size_unit, size, thin_volume_repo_size, thin_volume_max_repo_size, data_assurance_enabled): thin_volume_add_req = dict( name=name, poolId=pool_id, sizeUnit=size_unit, virtualSize=size, repositorySize=thin_volume_repo_size, maximumRepositorySize=thin_volume_max_repo_size, dataAssuranceEnabled=data_assurance_enabled, ) self.debug("creating thin-volume '%s'", name) try: (rc, resp) = request(self.api_url + "/storage-systems/%s/thin-volumes" % (self.ssid), data=json.dumps(thin_volume_add_req), headers=HEADERS, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception: err = get_exception() self.module.fail_json( msg="Failed to create thin volume. Volume [%s]. Array Id [%s]. Error[%s]." % (self.name, self.ssid, str(err)))
def hostports_available(self): used_ids = list() try: (rc, self.available_ports) = request(self.url + 'storage-systems/%s/unassociated-host-ports' % self.ssid, url_password=self.pwd, url_username=self.user, validate_certs=self.certs, headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to get unassociated host ports. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) if len(self.available_ports) > 0 and len(self.ports) <= len(self.available_ports): for port in self.ports: for free_port in self.available_ports: # Desired Type matches but also make sure we haven't already used the ID if not free_port['id'] in used_ids: # update the port arg to have an id attribute used_ids.append(free_port['id']) break if len(used_ids) != len(self.ports) and not self.force_port: self.module.fail_json( msg="There are not enough free host ports with the specified port types to proceed") else: return True else: self.module.fail_json(msg="There are no host ports available OR there are not enough unassigned host ports")
def clear_configuration(self): configuration = self.get_full_configuration() updated = False msg = self.NO_CHANGE_MSG if configuration['ldapDomains']: updated = True msg = "The LDAP configuration for all domains was cleared." if not self.check_mode: try: (rc, result) = request(self.url + self.base_path, method='DELETE', ignore_errors=True, **self.creds) # Older versions of NetApp E-Series restAPI does not possess an API to remove all existing configs if rc == 405: for config in configuration['ldapDomains']: self.clear_single_configuration(config['id']) except Exception as err: self.module.fail_json( msg= "Failed to clear LDAP configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return msg, updated
def create_mapping(module, ssid, lun_map, vol_name, api_url, user, pwd, validate_certs): mappings = 'storage-systems/%s/volume-mappings' % ssid url = api_url + mappings if lun_map is not None: post_body = json.dumps(dict( mappableObjectId=lun_map['volumeRef'], targetId=lun_map['mapRef'], lun=lun_map['lun'] )) else: post_body = json.dumps(dict( mappableObjectId=lun_map['volumeRef'], targetId=lun_map['mapRef'], )) rc, data = request(url, data=post_body, method='POST', url_username=user, url_password=pwd, headers=HEADERS, ignore_errors=True, validate_certs=validate_certs) if rc == 422 and lun_map['lun'] is not None: data = move_lun(module, ssid, lun_map, vol_name, api_url, user, pwd, validate_certs) # module.fail_json(msg="The volume you specified '%s' is already " # "part of a different LUN mapping. If you " # "want to move it to a different host or " # "hostgroup, then please use the " # "netapp_e_move_lun module" % vol_name) return data
def host_exists(self): """Determine if the requested host exists As a side effect, set the full list of defined hosts in 'all_hosts', and the target host in 'host_obj'. """ all_hosts = list() try: (rc, all_hosts) = request(self.url + 'storage-systems/%s/hosts' % self.ssid, url_password=self.pwd, url_username=self.user, validate_certs=self.certs, headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to determine host existence. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) self.all_hosts = all_hosts # Augment the host objects for host in all_hosts: # Augment hostSidePorts with their ID (this is an omission in the API) host_side_ports = host['hostSidePorts'] initiators = dict((port['label'], port['id']) for port in host['initiators']) ports = dict((port['label'], port['id']) for port in host['ports']) ports.update(initiators) for port in host_side_ports: if port['label'] in ports: port['id'] = ports[port['label']] try: # Try to grab the host object self.host_obj = list(filter(lambda host: host['label'] == self.name, all_hosts))[0] return True except IndexError: # Host with the name passed in does not exist return False
def get_configuration(self): """Retrieve the existing audit-log configurations. :returns: dictionary containing current audit-log configuration """ try: if self.proxy_used: rc, data = request(self.url + "audit-log/config", timeout=300, headers=self.HEADERS, **self.creds) else: rc, data = request(self.url + "storage-systems/%s/audit-log/config" % self.ssid, timeout=300, headers=self.HEADERS, **self.creds) return data except Exception as err: self.module.fail_json(msg="Failed to retrieve the audit-log configuration! " "Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
def create_mapping(module, ssid, lun_map, vol_name, api_url, user, pwd, validate_certs): mappings = 'storage-systems/%s/volume-mappings' % ssid url = api_url + mappings if lun_map is not None: post_body = json.dumps( dict(mappableObjectId=lun_map['volumeRef'], targetId=lun_map['mapRef'], lun=lun_map['lun'])) else: post_body = json.dumps( dict( mappableObjectId=lun_map['volumeRef'], targetId=lun_map['mapRef'], )) rc, data = request(url, data=post_body, method='POST', url_username=user, url_password=pwd, headers=HEADERS, ignore_errors=True, validate_certs=validate_certs) if rc == 422 and lun_map['lun'] is not None: data = move_lun(module, ssid, lun_map, vol_name, api_url, user, pwd, validate_certs) # module.fail_json(msg="The volume you specified '%s' is already " # "part of a different LUN mapping. If you " # "want to move it to a different host or " # "hostgroup, then please use the " # "netapp_e_move_lun module" % vol_name) return data
def has_match(module, ssid, api_url, api_pwd, api_usr, body): compare_keys = ['syncIntervalMinutes', 'syncWarnThresholdMinutes', 'recoveryWarnThresholdMinutes', 'repoUtilizationWarnThreshold'] desired_state = dict((x, (body.get(x))) for x in compare_keys) label_exists = False matches_spec = False current_state = None async_id = None api_data = None desired_name = body.get('name') endpoint = 'storage-systems/%s/async-mirrors' % ssid url = api_url + endpoint try: rc, data = request(url, url_username=api_usr, url_password=api_pwd, headers=HEADERS) except Exception as e: module.exit_json(msg="Error finding a match. Message: %s" % to_native(e), exception=traceback.format_exc()) for async_group in data: if async_group['label'] == desired_name: label_exists = True api_data = async_group async_id = async_group['groupRef'] current_state = dict( syncIntervalMinutes=async_group['syncIntervalMinutes'], syncWarnThresholdMinutes=async_group['syncCompletionTimeAlertThresholdMinutes'], recoveryWarnThresholdMinutes=async_group['recoveryPointAgeAlertThresholdMinutes'], repoUtilizationWarnThreshold=async_group['repositoryUtilizationWarnThreshold'], ) if current_state == desired_state: matches_spec = True return label_exists, matches_spec, api_data, async_id
def controllers(self): """Retrieve a mapping of controller labels to their references { 'A': '070000000000000000000001', 'B': '070000000000000000000002', } :return: the controllers defined on the system """ try: (rc, controllers) = request( self.url + 'storage-systems/%s/controllers' % self.ssid, headers=HEADERS, **self.creds) except Exception as err: controllers = list() self.module.fail_json( msg= "Failed to retrieve the controller settings. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) controllers.sort(key=lambda c: c['physicalLocation']['slot']) controllers_dict = dict() i = ord('A') for controller in controllers: label = chr(i) settings = dict( controllerSlot=controller['physicalLocation']['slot'], controllerRef=controller['controllerRef'], ssh=controller['networkSettings']['remoteAccessEnabled']) controllers_dict[label] = settings i += 1 return controllers_dict
def group_id(self): if self.group: try: (rc, all_groups) = request( self.url + 'storage-systems/%s/host-groups' % self.ssid, url_password=self.pwd, url_username=self.user, validate_certs=self.certs, headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to get host groups. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) try: group_obj = list( filter(lambda group: group['name'] == self.group, all_groups))[0] return group_obj['id'] except IndexError: self.module.fail_json(msg="No group with the name: %s exists" % self.group) else: # Return the value equivalent of no group return "0000000000000000000000000000000000000000"
def update_host(self): if self.ports: if self.hostports_available: if self.force_port_update is True: self.reassign_ports(apply=False) # Make sure that only ports that arent being reassigned are passed into the ports attr self.ports = [ port for port in self.ports if not self.port_on_diff_host(port) ] self.post_body['ports'] = self.ports if self.group: self.post_body['groupId'] = self.group_id self.post_body['hostType'] = dict(index=self.host_type_index) try: (rc, self.host_obj) = request(self.url + 'storage-systems/%s/hosts/%s' % (self.ssid, self.host_obj['id']), url_username=self.user, url_password=self.pwd, headers=HEADERS, validate_certs=self.certs, method='POST', data=json.dumps(self.post_body)) except Exception as err: self.module.fail_json( msg="Failed to update host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) self.module.exit_json(changed=True, **self.host_obj)
def get_expansion_candidate_drives(self): # sanity checks; don't call this if we can't/don't need to expand if not self.needs_expansion: self.module.fail_json(msg="can't get expansion candidates when pool doesn't need expansion") self.debug("fetching expansion candidate drives...") try: (rc, resp) = request( self.api_url + "/storage-systems/%s/storage-pools/%s/expand" % (self.ssid, self.pool_detail['id']), method='GET', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception as err: pool_id = self.pool_detail['id'] self.module.exit_json( msg="Failed to fetch candidate drives for storage pool. Pool id [%s]. Array id [%s]. Error[%s]." % ( pool_id, self.ssid, to_native(err))) current_drive_count = len(self.sp_drives) current_capacity_bytes = int(self.pool_detail['totalRaidedSpace']) # TODO: is this the right attribute to use? if self.criteria_min_usable_capacity: requested_capacity_bytes = self.criteria_min_usable_capacity * self._size_unit_map[self.criteria_size_unit] else: requested_capacity_bytes = current_capacity_bytes if self.criteria_drive_count: minimum_disks_to_add = max((self.criteria_drive_count - current_drive_count), 1) else: minimum_disks_to_add = 1 minimum_bytes_to_add = max(requested_capacity_bytes - current_capacity_bytes, 0) # FUTURE: allow more control over expansion candidate selection? # loop over candidate disk sets and add until we've met both criteria added_drive_count = 0 added_capacity_bytes = 0 drives_to_add = set() for s in resp: # don't trust the API not to give us duplicate drives across candidate sets, especially in multi-drive sets candidate_drives = s['drives'] if len(drives_to_add.intersection(candidate_drives)) != 0: # duplicate, skip continue drives_to_add.update(candidate_drives) added_drive_count += len(candidate_drives) added_capacity_bytes += int(s['usableCapacity']) if added_drive_count >= minimum_disks_to_add and added_capacity_bytes >= minimum_bytes_to_add: break if (added_drive_count < minimum_disks_to_add) or (added_capacity_bytes < minimum_bytes_to_add): self.module.fail_json( msg="unable to find at least %s drives to add that would add at least %s bytes of capacity" % ( minimum_disks_to_add, minimum_bytes_to_add)) return list(drives_to_add)
def update_configuration(self): # Define a new domain based on the user input domain = self.make_configuration() # This is the current list of configurations current = self.get_configuration(self.identifier) update = current != domain msg = "No changes were necessary for [%s]." % self.identifier self._logger.info("Is updated: %s", update) if update and not self.check_mode: msg = "The configuration changes were made for [%s]." % self.identifier try: if current is None: api = self.base_path + 'addDomain' else: api = self.base_path + '%s' % (domain['id']) (rc, result) = request(self.url + api, method='POST', data=json.dumps(domain), **self.creds) except Exception as err: self._logger.exception( "Failed to modify the LDAP configuration.") self.module.fail_json( msg= "Failed to modify LDAP configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return msg, update
def has_match(module, ssid, api_url, api_pwd, api_usr, body): compare_keys = ['syncIntervalMinutes', 'syncWarnThresholdMinutes', 'recoveryWarnThresholdMinutes', 'repoUtilizationWarnThreshold'] desired_state = dict((x, (body.get(x))) for x in compare_keys) label_exists = False matches_spec = False current_state = None async_id = None api_data = None desired_name = body.get('name') endpoint = 'storage-systems/%s/async-mirrors' % ssid url = api_url + endpoint try: rc, data = request(url, url_username=api_usr, url_password=api_pwd, headers=HEADERS) except Exception: error = get_exception() module.exit_json(exception="Error finding a match. Message: %s" % str(error)) for async_group in data: if async_group['label'] == desired_name: label_exists = True api_data = async_group async_id = async_group['groupRef'] current_state = dict( syncIntervalMinutes=async_group['syncIntervalMinutes'], syncWarnThresholdMinutes=async_group['syncCompletionTimeAlertThresholdMinutes'], recoveryWarnThresholdMinutes=async_group['recoveryPointAgeAlertThresholdMinutes'], repoUtilizationWarnThreshold=async_group['repositoryUtilizationWarnThreshold'], ) if current_state == desired_state: matches_spec = True return label_exists, matches_spec, api_data, async_id
def get_controllers(self): """Retrieve a mapping of controller labels to their references { 'A': '070000000000000000000001', 'B': '070000000000000000000002', } :return: the controllers defined on the system """ controllers = list() try: (rc, controllers) = request(self.url + 'storage-systems/%s/graph/xpath-filter?query=/controller/id' % self.ssid, headers=HEADERS, **self.creds) except Exception as err: self.module.fail_json( msg="Failed to retrieve controller list! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) controllers.sort() controllers_dict = {} i = ord('A') for controller in controllers: label = chr(i) controllers_dict[label] = controller i += 1 return controllers_dict
def update_async(module, ssid, api_url, pwd, user, body, new_name, async_id): endpoint = 'storage-systems/%s/async-mirrors/%s' % (ssid, async_id) url = api_url + endpoint compare_keys = [ 'syncIntervalMinutes', 'syncWarnThresholdMinutes', 'recoveryWarnThresholdMinutes', 'repoUtilizationWarnThreshold' ] desired_state = dict((x, (body.get(x))) for x in compare_keys) if new_name: desired_state['new_name'] = new_name post_data = json.dumps(desired_state) try: rc, data = request(url, data=post_data, method='POST', headers=HEADERS, url_username=user, url_password=pwd) except Exception as e: module.exit_json( msg="Exception while updating async mirror group. Message: %s" % to_native(e), exception=traceback.format_exc()) return data
def remove_mapping(module, ssid, lun_mapping, api_url, user, pwd, validate_certs): lun_id = get_lun_id(module, ssid, lun_mapping, api_url, user, pwd) lun_del = "storage-systems/%s/volume-mappings/%s" % (ssid, lun_id) url = api_url + lun_del rc, data = request(url, method='DELETE', url_username=user, url_password=pwd, headers=HEADERS, validate_certs=validate_certs) return data
def get_storage_pool(self, storage_pool_name): # global ifilter self.debug("fetching storage pools") # map the storage pool name to its id try: (rc, resp) = request(self.api_url + "/storage-systems/%s/storage-pools" % (self.ssid), headers=dict(Accept="application/json"), url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs) except Exception: err = get_exception() rc = err.args[0] if rc == 404 and self.state == 'absent': self.module.exit_json( msg="Storage pool [%s] did not exist." % (self.name)) else: err = get_exception() self.module.exit_json( msg="Failed to get storage pools. Array id [%s]. Error[%s]. State[%s]. RC[%s]." % (self.ssid, str(err), self.state, rc)) self.debug("searching for storage pool '%s'", storage_pool_name) pool_detail = next(select(lambda a: a['name'] == storage_pool_name, resp), None) if pool_detail: found = 'found' else: found = 'not found' self.debug(found) return pool_detail
def update_host(self): self._logger.info("Beginning the update for host=%s.", self.name) if self.ports: # Remove ports that need reassigning from their current host. self.assigned_host_ports(apply_unassigning=True) self.post_body["portsToUpdate"] = self.portsForUpdate self.post_body["ports"] = self.newPorts self._logger.info("Requested ports: %s", pformat(self.ports)) else: self._logger.info("No host ports were defined.") if self.group: self.post_body['groupId'] = self.group_id() self.post_body['hostType'] = dict(index=self.host_type_index) api = self.url + 'storage-systems/%s/hosts/%s' % (self.ssid, self.host_obj['id']) self._logger.info("POST => url=%s, body=%s.", api, pformat(self.post_body)) if not self.check_mode: try: (rc, self.host_obj) = request(api, url_username=self.user, url_password=self.pwd, headers=HEADERS, validate_certs=self.certs, method='POST', data=json.dumps(self.post_body)) except Exception as err: self.module.fail_json( msg="Failed to update host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) payload = self.build_success_payload(self.host_obj) self.module.exit_json(changed=True, **payload)
def create_thin_volume(self, pool_id, name, size_unit, size, thin_volume_repo_size, thin_volume_max_repo_size, data_assurance_enabled): thin_volume_add_req = dict( name=name, poolId=pool_id, sizeUnit=size_unit, virtualSize=size, repositorySize=thin_volume_repo_size, maximumRepositorySize=thin_volume_max_repo_size, dataAssuranceEnabled=data_assurance_enabled, ) self.debug("creating thin-volume '%s'" % name) try: (rc, resp) = request(self.api_url + "/storage-systems/%s/thin-volumes" % (self.ssid), data=json.dumps(thin_volume_add_req), headers=HEADERS, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception: err = get_exception() self.module.fail_json( msg= "Failed to create thin volume. Volume [%s]. Array Id [%s]. Error[%s]." % (self.name, self.ssid, str(err)))
def update_volume_properties(self): update_volume_req = dict() # conditionally add values so we ignore unspecified props if self.volume_ssdcache_setting_changed: update_volume_req['flashCache'] = self.ssd_cache_enabled self.debug("updating volume properties...") try: (rc, resp) = request(self.api_url + "/storage-systems/%s/%s/%s/" % (self.ssid, self.volume_resource_name, self.volume_detail['id']), data=json.dumps(update_volume_req), headers=HEADERS, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception: err = get_exception() self.module.fail_json( msg= "Failed to update volume properties. Volume [%s]. Array Id [%s]. Error[%s]." % (self.name, self.ssid, str(err)))
def create_host(self): self._logger.info("Creating host definition.") # Remove ports that need reassigning from their current host. self.assigned_host_ports(apply_unassigning=True) # needs_reassignment = False post_body = dict( name=self.name, hostType=dict(index=self.host_type_index), groupId=self.group_id(), ) if self.ports: post_body.update(ports=self.ports) api = self.url + "storage-systems/%s/hosts" % self.ssid self._logger.info('POST => url=%s, body=%s', api, pformat(post_body)) if not self.check_mode: if not self.host_exists(): try: (rc, self.host_obj) = request(api, method='POST', url_username=self.user, url_password=self.pwd, validate_certs=self.certs, data=json.dumps(post_body), headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to create host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) else: payload = self.build_success_payload(self.host_obj) self.module.exit_json(changed=False, msg="Host already exists. Id [%s]. Host [%s]." % (self.ssid, self.name), **payload) payload = self.build_success_payload(self.host_obj) self.module.exit_json(changed=True, msg='Host created.', **payload)
def get_storage_pool(self, storage_pool_name): self.debug("fetching storage pools") # map the storage pool name to its id try: (rc, resp) = request(self.api_url + "/storage-systems/%s/storage-pools" % (self.ssid), headers=dict(Accept="application/json"), url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs) except Exception: err = get_exception() self.module.fail_json( msg= "Failed to obtain list of storage pools. Array Id [%s]. Error[%s]." % (self.ssid, str(err))) self.debug("searching for storage pool '%s'" % storage_pool_name) pool_detail = next( ifilter(lambda a: a['name'] == storage_pool_name, resp), None) if pool_detail: self.debug('found') else: self.debug('not found') return pool_detail
def sp_drives(self, exclude_hotspares=True): if not self._sp_drives_cached: self.debug("fetching drive list...") try: (rc, resp) = request( self.api_url + "/storage-systems/%s/drives" % (self.ssid), method='GET', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs) except Exception as err: pool_id = self.pool_detail['id'] self.module.exit_json( msg= "Failed to fetch disk drives. Pool id [%s]. Array id [%s]. Error[%s]." % (pool_id, self.ssid, to_native(err))) sp_id = self.pool_detail['id'] if exclude_hotspares: self._sp_drives_cached = [ d for d in resp if d['currentVolumeGroupRef'] == sp_id and not d['hotSpare'] ] else: self._sp_drives_cached = [ d for d in resp if d['currentVolumeGroupRef'] == sp_id ] return self._sp_drives_cached
def host_exists(self): """Determine if the requested host exists As a side effect, set the full list of defined hosts in 'all_hosts', and the target host in 'host_obj'. """ match = False all_hosts = list() try: (rc, all_hosts) = request(self.url + 'storage-systems/%s/hosts' % self.ssid, url_password=self.pwd, url_username=self.user, validate_certs=self.certs, headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to determine host existence. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) # Augment the host objects for host in all_hosts: for port in host['hostSidePorts']: port['type'] = port['type'].lower() port['address'] = port['address'].lower() port['label'] = port['label'].lower() # Augment hostSidePorts with their ID (this is an omission in the API) ports = dict((port['label'], port['id']) for port in host['ports']) ports.update((port['label'], port['id']) for port in host['initiators']) for host_side_port in host['hostSidePorts']: if host_side_port['label'] in ports: host_side_port['id'] = ports[host_side_port['label']] if host['label'] == self.name: self.host_obj = host match = True self.all_hosts = all_hosts return match
def reduce_drives(self, drive_list): if all(drive in drive_list for drive in self.sp_drives): # all the drives passed in are present in the system pass else: self.module.fail_json( msg= "One of the drives you wish to remove does not currently exist in the storage pool you specified" ) try: (rc, resp) = request(self.api_url + "/storage-systems/%s/storage-pools/%s/reduction" % (self.ssid, self.pool_detail['id']), data=json.dumps(drive_list), headers=self.post_headers, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception as err: pool_id = self.pool_detail['id'] self.module.exit_json( msg= "Failed to remove drives from storage pool. Pool id [%s]. Array id [%s]. Error[%s]." % (pool_id, self.ssid, to_native(err)))
def find_valid_copy_pair_targets_and_sources(params): get_status = 'storage-systems/%s/volumes' % params['ssid'] url = params['api_url'] + get_status (response_code, response_data) = request(url, ignore_errors=True, method='GET', url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS, validate_certs=params['validate_certs']) if response_code == 200: source_capacity = None candidates = [] for volume in response_data: if volume['id'] == params['search_volume_id']: source_capacity = volume['capacity'] else: candidates.append(volume) potential_sources = [] potential_targets = [] for volume in candidates: if volume['capacity'] > source_capacity: if volume['volumeCopyTarget'] is False: if volume['volumeCopySource'] is False: potential_targets.append(volume['id']) else: if volume['volumeCopyTarget'] is False: if volume['volumeCopySource'] is False: potential_sources.append(volume['id']) return potential_targets, potential_sources else: raise Exception("Response [%s]" % response_code)
def move_lun(module, ssid, lun_map, vol_name, api_url, user, pwd, validate_certs): lun_id = get_lun_id(module, ssid, lun_map, api_url, user, pwd, validate_certs) move_lun = "storage-systems/%s/volume-mappings/%s/move" % (ssid, lun_id) url = api_url + move_lun post_body = json.dumps(dict(targetId=lun_map['mapRef'], lun=lun_map['lun'])) rc, data = request(url, data=post_body, method='POST', url_username=user, url_password=pwd, headers=HEADERS, validate_certs=validate_certs) return data
def get_volumes(module, ssid, api_url, user, pwd, mappable, validate_certs): volumes = 'storage-systems/%s/%s' % (ssid, mappable) url = api_url + volumes try: rc, data = request(url, url_username=user, url_password=pwd, validate_certs=validate_certs) except Exception as err: module.fail_json( msg="Failed to mappable objects. Type[%s. Id [%s]. Error [%s]." % (mappable, ssid, to_native(err))) return data
def get_hostgroups(module, ssid, api_url, user, pwd, validate_certs): groups = "storage-systems/%s/host-groups" % ssid url = api_url + groups try: rc, data = request(url, headers=HEADERS, url_username=user, url_password=pwd, validate_certs=validate_certs) return data except Exception: module.fail_json(msg="There was an issue with connecting, please check that your" "endpoint is properly defined and your credentials are correct")
def create_storage_pool(self): self.debug("creating storage pool...") sp_add_req = dict( raidLevel=self.raid_level, diskDriveIds=self.disk_ids, name=self.name ) if self.erase_secured_drives: sp_add_req['eraseSecuredDrives'] = self.erase_secured_drives try: (rc, resp) = request(self.api_url + "/storage-systems/%s/storage-pools" % (self.ssid), data=json.dumps(sp_add_req), headers=self.post_headers, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except: err = get_exception() pool_id = self.pool_detail['id'] self.module.exit_json( msg="Failed to create storage pool. Pool id [%s]. Array id [%s]. Error[%s]." % (pool_id, self.ssid, str(err))) self.pool_detail = self.get_storage_pool(self.name) if self.secure_pool: secure_pool_data = dict(securePool=True) try: (retc, r) = request( self.api_url + "/storage-systems/%s/storage-pools/%s" % (self.ssid, self.pool_detail['id']), data=json.dumps(secure_pool_data), headers=self.post_headers, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120, ignore_errors=True) except: err = get_exception() pool_id = self.pool_detail['id'] self.module.exit_json( msg="Failed to update storage pool. Pool id [%s]. Array id [%s]. Error[%s]." % (pool_id, self.ssid, str(err)))
def expand_storage_pool(self): drives_to_add = self.get_expansion_candidate_drives() self.debug("adding %s drives to storage pool...", len(drives_to_add)) sp_expand_req = dict( drives=drives_to_add ) try: request( self.api_url + "/storage-systems/%s/storage-pools/%s/expand" % (self.ssid, self.pool_detail['id']), data=json.dumps(sp_expand_req), headers=self.post_headers, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception as err: pool_id = self.pool_detail['id'] self.module.exit_json( msg="Failed to add drives to storage pool. Pool id [%s]. Array id [%s]. Error[%s]." % (pool_id, self.ssid, to_native(err)))
def remove_host(self): try: (rc, resp) = request(self.url + "storage-systems/%s/hosts/%s" % (self.ssid, self.host_obj['id']), method='DELETE', url_username=self.user, url_password=self.pwd, validate_certs=self.certs) except Exception as err: self.module.fail_json( msg="Failed to remote host. Host[%s]. Array Id [%s]. Error [%s]." % (self.host_obj['id'], self.ssid, to_native(err)))
def get_candidate_disks(self): self.debug("getting candidate disks...") # driveCapacityMin is broken on /drives POST. Per NetApp request we built our own # switch back to commented code below if it gets fixed # drives_req = dict( # driveCount = self.criteria_drive_count, # sizeUnit = 'mb', # raidLevel = self.raid_level # ) # # if self.criteria_drive_type: # drives_req['driveType'] = self.criteria_drive_type # if self.criteria_disk_min_aggregate_size_mb: # drives_req['targetUsableCapacity'] = self.criteria_disk_min_aggregate_size_mb # # # TODO: this arg appears to be ignored, uncomment if it isn't # #if self.criteria_disk_min_size_gb: # # drives_req['driveCapacityMin'] = self.criteria_disk_min_size_gb * 1024 # (rc,drives_resp) = request(self.api_url + "/storage-systems/%s/drives" % (self.ssid), data=json.dumps(drives_req), headers=self.post_headers, # method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs) # # if rc == 204: # self.module.fail_json(msg='Cannot find disks to match requested criteria for storage pool') # disk_ids = [d['id'] for d in drives_resp] try: (rc, drives_resp) = request(self.api_url + "/storage-systems/%s/drives" % (self.ssid), method='GET', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs) except: err = get_exception() self.module.exit_json( msg="Failed to fetch disk drives. Array id [%s]. Error[%s]." % (self.ssid, str(err))) try: candidate_set = self.filter_drives(drives_resp, exact_drive_count=self.criteria_drive_count, drive_type=self.criteria_drive_type, min_drive_size=self.criteria_drive_min_size, raid_level=self.raid_level, size_unit=self.criteria_size_unit, min_total_capacity=self.criteria_min_usable_capacity, interface_type=self.criteria_drive_interface_type, fde_required=self.criteria_drive_require_fde ) except: err = get_exception() self.module.fail_json( msg="Failed to allocate adequate drive count. Id [%s]. Error [%s]." % (self.ssid, str(err))) disk_ids = [d['id'] for d in candidate_set] return disk_ids
def remove_amg(module, ssid, api_url, pwd, user, async_id): endpoint = 'storage-systems/%s/async-mirrors/%s' % (ssid, async_id) url = api_url + endpoint try: rc, data = request(url, method='DELETE', url_username=user, url_password=pwd, headers=HEADERS) except Exception as e: module.exit_json(msg="Exception while removing async mirror group. Message: %s" % to_native(e), exception=traceback.format_exc()) return
def create_async(module, ssid, api_url, api_pwd, api_usr, body): endpoint = 'storage-systems/%s/async-mirrors' % ssid url = api_url + endpoint post_data = json.dumps(body) try: rc, data = request(url, data=post_data, method='POST', url_username=api_usr, url_password=api_pwd, headers=HEADERS) except Exception as e: module.exit_json(msg="Exception while creating aysnc mirror group. Message: %s" % to_native(e), exception=traceback.format_exc()) return data
def find_volume_copy_pair_id_by_volume_copy_pair_id(params): get_status = 'storage-systems/%s/volume-copy-jobs/%s?retainRepositories=false' % ( params['ssid'], params['volume_copy_pair_id']) url = params['api_url'] + get_status (rc, resp) = request(url, ignore_errors=True, method='DELETE', url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS, validate_certs=params['validate_certs']) if rc != 200: return False, (rc, resp) else: return True, (rc, resp)
def update_reserve_drive_count(self, qty): data = dict(reservedDriveCount=qty) try: (rc, resp) = request( self.api_url + "/storage-systems/%s/storage-pools/%s" % (self.ssid, self.pool_detail['id']), data=json.dumps(data), headers=self.post_headers, method='POST', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception as err: pool_id = self.pool_detail['id'] self.module.exit_json( msg="Failed to update reserve drive count. Pool id [%s]. Array id [%s]. Error[%s]." % (pool_id, self.ssid, to_native(err)))
def get_lun_mappings(ssid, api_url, user, pwd, validate_certs, get_all=None): mappings = 'storage-systems/%s/volume-mappings' % ssid url = api_url + mappings rc, data = request(url, url_username=user, url_password=pwd, validate_certs=validate_certs) if not get_all: remove_keys = ('ssid', 'perms', 'lunMappingRef', 'type', 'id') for key in remove_keys: for mapping in data: del mapping[key] return data
def valid_host_type(self): try: (rc, host_types) = request(self.url + 'storage-systems/%s/host-types' % self.ssid, url_password=self.pwd, url_username=self.user, validate_certs=self.certs, headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to get host types. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) try: match = filter(lambda host_type: host_type['index'] == self.host_type_index, host_types)[0] return True except IndexError: self.module.fail_json(msg="There is no host type with index %s" % self.host_type_index)
def delete_volume(self): # delete the volume self.debug("deleting volume '%s'", self.volume_detail['name']) try: (rc, resp) = request( self.api_url + "/storage-systems/%s/%s/%s" % (self.ssid, self.volume_resource_name, self.volume_detail['id']), method='DELETE', url_username=self.api_usr, url_password=self.api_pwd, validate_certs=self.validate_certs, timeout=120) except Exception as err: self.module.fail_json( msg="Failed to delete volume. Volume [%s]. Array Id [%s]. Error[%s]." % (self.name, self.ssid, to_native(err)))
def start_stop_copy(params): get_status = 'storage-systems/%s/volume-copy-jobs-control/%s?control=%s' % ( params['ssid'], params['volume_copy_pair_id'], params['start_stop_copy']) url = params['api_url'] + get_status (response_code, response_data) = request(url, ignore_errors=True, method='POST', url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS, validate_certs=params['validate_certs']) if response_code == 200: return True, response_data[0]['percentComplete'] else: return False, response_data
def find_volume_copy_pair_id_from_source_volume_id_and_destination_volume_id(params): get_status = 'storage-systems/%s/volume-copy-jobs' % params['ssid'] url = params['api_url'] + get_status (rc, resp) = request(url, method='GET', url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS, validate_certs=params['validate_certs']) volume_copy_pair_id = None for potential_copy_pair in resp: if potential_copy_pair['sourceVolume'] == params['source_volume_id']: if potential_copy_pair['sourceVolume'] == params['source_volume_id']: volume_copy_pair_id = potential_copy_pair['id'] return volume_copy_pair_id
def host_exists(self): try: (rc, all_hosts) = request(self.url + 'storage-systems/%s/hosts' % self.ssid, url_password=self.pwd, url_username=self.user, validate_certs=self.certs, headers=HEADERS) except Exception as err: self.module.fail_json( msg="Failed to determine host existence. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) self.all_hosts = all_hosts try: # Try to grab the host object self.host_obj = filter(lambda host: host['label'] == self.name, all_hosts)[0] return True except IndexError: # Host with the name passed in does not exist return False
def create_copy_pair(params): get_status = 'storage-systems/%s/volume-copy-jobs' % params['ssid'] url = params['api_url'] + get_status rData = { "sourceId": params['source_volume_id'], "targetId": params['destination_volume_id'] } (rc, resp) = request(url, data=json.dumps(rData), ignore_errors=True, method='POST', url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS, validate_certs=params['validate_certs']) if rc != 200: return False, (rc, resp) else: return True, (rc, resp)