def target(self): """Provide information on the iSCSI Target configuration Sample: { 'alias': 'myCustomName', 'ping': True, 'unnamed_discovery': True, 'chap': False, 'iqn': 'iqn.1992-08.com.netapp:2800.000a132000b006d2000000005a0e8f45', } """ target = dict() try: (rc, data) = request( self.url + 'storage-systems/%s/graph/xpath-filter?query=/storagePoolBundle/target' % self.ssid, headers=HEADERS, **self.creds) # This likely isn't an iSCSI-enabled system if not data: self.module.fail_json( msg= "This storage-system doesn't appear to have iSCSI interfaces. Array Id [%s]." % (self.ssid)) data = data[0] chap = any([ auth for auth in data['configuredAuthMethods']['authMethodData'] if auth['authMethod'] == 'chap' ]) target.update( dict(alias=data['alias']['iscsiAlias'], iqn=data['nodeName']['iscsiNodeName'], chap=chap)) (rc, data) = request( self.url + 'storage-systems/%s/graph/xpath-filter?query=/sa/iscsiEntityData' % self.ssid, headers=HEADERS, **self.creds) data = data[0] target.update( dict( ping=data['icmpPingResponseEnabled'], unnamed_discovery=data['unnamedDiscoverySessionsEnabled'])) except Exception as err: self.module.fail_json( msg= "Failed to retrieve the iSCSI target information. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return target
def apply_target_changes(self): update = False target = self.target body = dict() if self.ping != target['ping']: update = True body['icmpPingResponseEnabled'] = self.ping if self.unnamed_discovery != target['unnamed_discovery']: update = True body['unnamedDiscoverySessionsEnabled'] = self.unnamed_discovery self._logger.info(pformat(body)) if update and not self.check_mode: try: request(self.url + 'storage-systems/%s/iscsi/entity' % self.ssid, method='POST', data=json.dumps(body), timeout=60, headers=HEADERS, **self.creds) except Exception as err: self.module.fail_json( msg= "Failed to update the iSCSI target settings. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return update
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 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 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 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 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 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 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 update_configuration(self, update=None, body=None, attempt_recovery=True): """Update audit-log configuration.""" if update is None or body is None: update, body = self.build_configuration() if update and not self.check_mode: try: if self.proxy_used: rc, result = request(self.url + "storage-systems/audit-log/config", timeout=300, data=json.dumps(body), method='POST', headers=self.HEADERS, ignore_errors=True, **self.creds) else: rc, result = request( self.url + "storage-systems/%s/audit-log/config" % self.ssid, timeout=300, data=json.dumps(body), method='POST', headers=self.HEADERS, ignore_errors=True, **self.creds) if rc == 422: if self.force and attempt_recovery: self.delete_log_messages() update = self.update_configuration(update, body, False) else: self.module.fail_json( msg= "Failed to update audit-log configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(rc, result))) except Exception as error: self.module.fail_json( msg= "Failed to update audit-log configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(error))) return update
def update_configuration(self): config = self.get_configuration() update = False body = dict() if self.asup: body = dict(asupEnabled=True) if not config['asupEnabled']: update = True if (config['onDemandEnabled'] and config['remoteDiagsEnabled']) != self.active: update = True body.update( dict(onDemandEnabled=self.active, remoteDiagsEnabled=self.active)) self.days.sort() config['schedule']['daysOfWeek'].sort() body['schedule'] = dict(daysOfWeek=self.days, dailyMinTime=self.start, dailyMaxTime=self.end, weeklyMinTime=self.start, weeklyMaxTime=self.end) if self.days != config['schedule']['daysOfWeek']: update = True if self.start != config['schedule'][ 'dailyMinTime'] or self.start != config['schedule'][ 'weeklyMinTime']: update = True elif self.end != config['schedule'][ 'dailyMaxTime'] or self.end != config['schedule'][ 'weeklyMaxTime']: update = True elif config['asupEnabled']: body = dict(asupEnabled=False) update = True self._logger.info(pformat(body)) if update and not self.check_mode: try: (rc, result) = request(self.url + 'device-asup', method='POST', data=json.dumps(body), headers=HEADERS, **self.creds) # This is going to catch cases like a connection failure except Exception as err: self.module.fail_json( msg= "We failed to set the storage-system name! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return update
def update_configuration(self): config = self.get_configuration() update = False body = dict() if self.alerts: body = dict(alertingEnabled=True) if not config['alertingEnabled']: update = True body.update(emailServerAddress=self.server) if config['emailServerAddress'] != self.server: update = True body.update(additionalContactInformation=self.contact, sendAdditionalContactInformation=True) if self.contact and ( self.contact != config['additionalContactInformation'] or not config['sendAdditionalContactInformation']): update = True body.update(emailSenderAddress=self.sender) if config['emailSenderAddress'] != self.sender: update = True self.recipients.sort() if config['recipientEmailAddresses']: config['recipientEmailAddresses'].sort() body.update(recipientEmailAddresses=self.recipients) if config['recipientEmailAddresses'] != self.recipients: update = True elif config['alertingEnabled']: body = dict(alertingEnabled=False) update = True self._logger.debug(pformat(body)) if update and not self.check_mode: try: (rc, result) = request( self.url + 'storage-systems/%s/device-alerts' % self.ssid, method='POST', data=json.dumps(body), headers=HEADERS, **self.creds) # This is going to catch cases like a connection failure except Exception as err: self.module.fail_json( msg= "We failed to set the storage-system name! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return update
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 get_full_configuration(self): try: (rc, result) = request(self.url + self.base_path, **self.creds) return result except Exception as err: self._logger.exception( "Failed to retrieve the LDAP configuration.") self.module.fail_json( msg= "Failed to retrieve LDAP configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
def make_configuration_request(self, body): # make http request(s) if not self.check_mode: try: if self.syslog: if "id" in body: (rc, result) = request( self.url + "storage-systems/{0}/syslog/{1}".format( self.ssid, body["id"]), method='POST', data=json.dumps(body), headers=HEADERS, **self.creds) else: (rc, result) = request( self.url + "storage-systems/{0}/syslog".format(self.ssid), method='POST', data=json.dumps(body), headers=HEADERS, **self.creds) body.update(result) # send syslog test message if self.test: self.test_configuration(body) elif "id" in body: (rc, result) = request( self.url + "storage-systems/{0}/syslog/{1}".format( self.ssid, body["id"]), method='DELETE', headers=HEADERS, **self.creds) # This is going to catch cases like a connection failure except Exception as err: self.module.fail_json( msg= "We failed to modify syslog configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
def get_configuration(self): """Retrieve existing syslog configuration.""" try: (rc, result) = request( self.url + "storage-systems/{0}/syslog".format(self.ssid), headers=HEADERS, **self.creds) return result except Exception as err: self.module.fail_json( msg= "Failed to retrieve syslog configuration! Array Id [%s]. Error [%s]." % (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 remove host. Host[%s]. Array Id [%s]. Error [%s]." % (self.host_obj['id'], self.ssid, to_native(err)))
def update_api_address_interface_match(self, body): """Change network interface address which matches the api_address""" try: try: (rc, data) = request( self.url + 'storage-systems/%s/configuration/ethernet-interfaces' % self.ssid, use_proxy=False, force=True, ignore_errors=True, method='POST', data=json.dumps(body), headers=HEADERS, timeout=10, **self.creds) except Exception: url_parts = list(urlparse.urlparse(self.url)) domain = url_parts[1].split(":") domain[0] = self.address url_parts[1] = ":".join(domain) expected_url = urlparse.urlunparse(url_parts) self._logger.info(pformat(expected_url)) (rc, data) = request( expected_url + 'storage-systems/%s/configuration/ethernet-interfaces' % self.ssid, headers=HEADERS, timeout=300, **self.creds) return except Exception as err: self._logger.info(type(err)) self.module.fail_json( msg= "Connection failure: we failed to modify the network settings! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
def get_name(self): try: (rc, result) = request(self.url + 'storage-systems/%s' % self.ssid, headers=HEADERS, **self.creds) if result['status'] in ['offline', 'neverContacted']: self.module.fail_json( msg="This storage-system is offline! Array Id [%s]." % (self.ssid)) return result['name'] except Exception as err: self.module.fail_json( msg="Connection failure! Array Id [%s]. Error [%s]." % (self.ssid, to_native(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 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 get_configuration(self): try: (rc, result) = request( self.url + 'storage-systems/%s/device-alerts' % self.ssid, headers=HEADERS, **self.creds) self._logger.info("Current config: %s", pformat(result)) return result except Exception as err: self.module.fail_json( msg= "Failed to retrieve the alerts configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
def apply_iscsi_settings(self): """Update the iSCSI target alias and CHAP settings""" update = False target = self.target body = dict() if self.name is not None and self.name != target['alias']: update = True body['alias'] = self.name # If the CHAP secret was provided, we trigger an update. if self.chap_secret: update = True body.update( dict(enableChapAuthentication=True, chapSecret=self.chap_secret)) # If no secret was provided, then we disable chap elif target['chap']: update = True body.update(dict(enableChapAuthentication=False)) if update and not self.check_mode: try: request(self.url + 'storage-systems/%s/iscsi/target-settings' % self.ssid, method='POST', data=json.dumps(body), headers=HEADERS, **self.creds) except Exception as err: self.module.fail_json( msg= "Failed to update the iSCSI target settings. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return update
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 get_configuration(self): try: (rc, result) = request(self.url + 'device-asup', headers=HEADERS, **self.creds) if not (result['asupCapable'] and result['onDemandCapable']): self.module.fail_json( msg="ASUP is not supported on this device. Array Id [%s]." % (self.ssid)) return result except Exception as err: self.module.fail_json( msg= "Failed to retrieve ASUP configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
def test_configuration(self, body): """Send test syslog message to the storage array. Allows fix number of retries to occur before failure is issued to give the storage array time to create new syslog server record. """ try: (rc, result) = request( self.url + "storage-systems/{0}/syslog/{1}/test".format( self.ssid, body["id"]), method='POST', headers=HEADERS, **self.creds) except Exception as err: self.module.fail_json( msg= "We failed to send test message! Array Id [{0}]. Error [{1}].". format(self.ssid, to_native(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 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 check_health(self): """It's possible, due to a previous operation, for the API to report a 424 (offline) status for the storage-system. Therefore, we run a manual check with retries to attempt to contact the system before we continue. """ try: (rc, data) = request(self.url + 'storage-systems/%s/controllers' % self.ssid, headers=HEADERS, ignore_errors=True, **self.creds) # We've probably recently changed the interface settings and it's still coming back up: retry. if rc == 424: if self.retries < self.MAX_RETRIES: self.retries += 1 self._logger.info("We hit a 424, retrying in 5s.") time.sleep(5) self.check_health() else: self.module.fail_json( msg= "We failed to pull storage-system information. Array Id [%s] Message [%s]." % (self.ssid, data)) elif rc >= 300: self.module.fail_json( msg= "We failed to pull storage-system information. Array Id [%s] Message [%s]." % (self.ssid, data)) # This is going to catch cases like a connection failure except Exception as err: if self.retries < self.MAX_RETRIES: self._logger.info( "We hit a connection failure, retrying in 5s.") self.retries += 1 time.sleep(5) self.check_health() else: self.module.fail_json( msg= "Connection failure: we failed to modify the network settings! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))