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 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 test_systems_found(self, systems_found, serial, label, addresses): """Verify and build api urls.""" api_urls = [] for address in addresses: for port in self.ports: if port == "8080": url = "http://%s:%s/devmgr/" % (address, port) else: url = "https://%s:%s/devmgr/" % (address, port) try: rc, response = request(url + "utils/about", validate_certs=False, timeout=self.SEARCH_TIMEOUT) api_urls.append(url + "v2/") break except Exception as error: pass systems_found.update({ serial: { "api_urls": api_urls, "label": label, "addresses": addresses, "proxy_required": False } })
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 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 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 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 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 check_ip_address(self, systems_found, address): """Determine where an E-Series storage system is available at a specific ip address.""" for port in self.ports: if port == "8080": url = "http://%s:%s/devmgr/v2/storage-systems/1/" % (address, port) else: url = "https://%s:%s/devmgr/v2/storage-systems/1/" % (address, port) try: rc, sa_data = request(url + "symbol/getSAData", validate_certs=False, force_basic_auth=False, ignore_errors=True) if rc == 401: # Unauthorized self.module.warn( "Fail over and discover any storage system without a set admin password. This will discover systems without a set password" " such as newly deployed storage systems. Address [%s]." % address) # Fail over and discover any storage system without a set admin password. This will cover newly deployed systems. rc, graph = request(url + "graph", validate_certs=False, url_username="******", url_password="", timeout=self.SEARCH_TIMEOUT) sa_data = graph["sa"]["saData"] if sa_data["chassisSerialNumber"] in systems_found: systems_found[ sa_data["chassisSerialNumber"]]["api_urls"].append(url) else: systems_found.update({ sa_data["chassisSerialNumber"]: { "api_urls": [url], "label": sa_data["storageArrayLabel"], "addresses": [], "proxy_ssid": "", "proxy_required": False } }) break except Exception as error: pass
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 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 verify_proxy_service(self): """Verify proxy url points to a web services proxy.""" try: rc, about = request(self.proxy_about_url, validate_certs=self.proxy_validate_certs) if not about["runningAsProxy"]: self.module.fail_json( msg="Web Services is not running as a proxy!") except Exception as error: self.module.fail_json( msg="Proxy is not available! Check proxy_url. Error [%s]." % to_native(error))
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_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 update(self): self.controllers = self.get_controllers() if self.controller not in self.controllers: self.module.fail_json( msg= "The provided controller name is invalid. Valid controllers: %s." % ", ".join(self.controllers.keys())) iface_before = self.fetch_target_interface() update_required, body = self.make_update_body(iface_before) if update_required and not self.check_mode: try: url = ( self.url + 'storage-systems/%s/symbol/setIscsiInterfaceProperties' % self.ssid) (rc, result) = request(url, method='POST', data=json.dumps(body), headers=HEADERS, timeout=300, ignore_errors=True, **self.creds) # We could potentially retry this a few times, but it's probably a rare enough case (unless a playbook # is cancelled mid-flight), that it isn't worth the complexity. if rc == 422 and result['retcode'] in ['busy', '3']: self.module.fail_json( msg= "The interface is currently busy (probably processing a previously requested modification" " request). This operation cannot currently be completed. Array Id [%s]. Error [%s]." % (self.ssid, result)) # Handle authentication issues, etc. elif rc != 200: self.module.fail_json( msg= "Failed to modify the interface! Array Id [%s]. Error [%s]." % (self.ssid, to_native(result))) self._logger.debug("Update request completed successfully.") # This is going to catch cases like a connection failure except Exception as err: self.module.fail_json( msg= "Connection failure: we failed to modify the interface! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) iface_after = self.fetch_target_interface() self.module.exit_json(msg="The interface settings have been updated.", changed=update_required, enabled=iface_after['ipv4Enabled'])
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_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 get_configuration(self, identifier): try: (rc, result) = request(self.url + self.base_path + '%s' % (identifier), ignore_errors=True, **self.creds) if rc == 200: return result elif rc == 404: return None else: self.module.fail_json(msg="Failed to retrieve LDAP configuration! Array Id [%s]. Error [%s]." % (self.ssid, 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 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 clear_single_configuration(self, identifier=None): if identifier is None: identifier = self.identifier configuration = self.get_configuration(identifier) updated = False msg = self.NO_CHANGE_MSG if configuration: updated = True msg = "The LDAP domain configuration for [%s] was cleared." % identifier if not self.check_mode: try: (rc, result) = request(self.url + self.base_path + '%s' % identifier, method='DELETE', **self.creds) except Exception as err: self.module.fail_json(msg="Failed to remove LDAP configuration! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return msg, updated
def is_embedded(self): """Determine whether or not we're using the embedded or proxy implemenation of Web Services""" if self.embedded is None: url = self.url try: parts = urlparse.urlparse(url) parts = parts._replace(path='/devmgr/utils/') url = urlparse.urlunparse(parts) (rc, result) = request(url + 'about', **self.creds) self.embedded = not result['runningAsProxy'] except Exception as err: self._logger.exception("Failed to retrieve the About information.") self.module.fail_json(msg="Failed to determine the Web Services implementation type!" " Array Id [%s]. Error [%s]." % (self.ssid, to_native(err))) return self.embedded
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 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)))
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 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