def _assert_rest_result(self, result, err_str): if result['error']['code'] != 0: msg = (_('%(err)s\nresult: %(res)s.') % { 'err': err_str, 'res': result }) LOG.error(msg) raise exception.StorageBackendException(msg)
def _get_performance_switch(self): url = "/performance_statistic_switch" result = self.call(url, method='GET', log_filter_flag=True) msg = _('Get performance_statistic_switch failed.') self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) return result['data']
def login(self): """Login Huawei storage array.""" device_id = None for item_url in self.san_address: url = item_url + "xx/sessions" data = { "username": self.rest_username, "password": cryptor.decode(self.rest_password), "scope": "0" } self.init_http_head() result = self.do_call(url, data, 'POST', calltimeout=consts.LOGIN_SOCKET_TIMEOUT, log_filter_flag=True) if (result['error']['code'] != 0) or ("data" not in result): LOG.error("Login error. URL: %(url)s\n" "Reason: %(reason)s.", { "url": item_url, "reason": result }) continue LOG.debug('Login success: %(url)s', {'url': item_url}) device_id = result['data']['deviceid'] self.device_id = device_id self.url = item_url + device_id self.session.headers['iBaseToken'] = result['data']['iBaseToken'] if (result['data']['accountstate'] in (consts.PWD_EXPIRED, consts.PWD_RESET)): self.logout() msg = _("Password has expired or has been reset, " "please change the password.") LOG.error(msg) raise exception.StorageBackendException(msg) break if device_id is None: msg = _("Failed to login with all rest URLs.") LOG.error(msg) raise exception.StorageBackendException(msg) return device_id
def get_all_controllers(self): url = "/controller" result = self.call(url, method='GET', log_filter_flag=True) msg = _('Get controller error.') self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) return result['data']
def parse_alert(self, context, alert): """Parse alert data and fill the alert model.""" # Check for mandatory alert attributes LOG.info("Get alert from storage: %s", alert) for attr in self._mandatory_alert_attributes: if not alert.get(attr): msg = "Mandatory information %s missing in alert message. " \ % attr raise exception.InvalidInput(msg) try: alert_model = dict() # These information are sourced from device registration info alert_model['alert_id'] = alert['hwIsmReportingAlarmAlarmID'] alert_model['alert_name'] = alert['hwIsmReportingAlarmFaultTitle'] alert_model['severity'] = self.SEVERITY_MAP.get( alert['hwIsmReportingAlarmFaultLevel'], constants.Severity.NOT_SPECIFIED) alert_model['category'] = self.CATEGORY_MAP.get( alert['hwIsmReportingAlarmFaultCategory'], constants.Category.NOT_SPECIFIED) alert_model['type'] = self.TYPE_MAP.get( alert['hwIsmReportingAlarmFaultType'], constants.EventType.NOT_SPECIFIED) alert_model['sequence_number'] \ = alert['hwIsmReportingAlarmSerialNo'] occur_time = datetime.strptime( alert['hwIsmReportingAlarmFaultTime'], self.TIME_PATTERN) alert_model['occur_time'] = int(occur_time.timestamp() * 1000) description = alert['hwIsmReportingAlarmAdditionInfo'] if self._is_hex(description): description = bytes.fromhex(description[2:]).decode('ascii') alert_model['description'] = description recovery_advice = alert['hwIsmReportingAlarmRestoreAdvice'] if self._is_hex(recovery_advice): recovery_advice = bytes.fromhex( recovery_advice[2:]).decode('ascii') alert_model['recovery_advice'] = recovery_advice alert_model['resource_type'] = constants.DEFAULT_RESOURCE_TYPE alert_model['location'] = 'Node code=' \ + alert['hwIsmReportingAlarmNodeCode'] if alert.get('hwIsmReportingAlarmLocationInfo'): alert_model['location'] \ = alert_model['location'] + ',' + alert[ 'hwIsmReportingAlarmLocationInfo'] return alert_model except Exception as e: LOG.error(e) msg = (_("Failed to build alert model as some attributes missing " "in alert message.")) raise exception.InvalidResults(msg)
def _set_performance_switch(self, value): url = "/performance_statistic_switch" data = {"CMO_PERFORMANCE_SWITCH": value} result = self.call(url, data, method='PUT', log_filter_flag=True) msg = _('Set performance_statistic_switch failed.') self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) return result['data']
def _cb_fun(self, state_reference, context_engine_id, context_name, var_binds, cb_ctx): """Callback function to process the incoming trap.""" exec_context = self.snmp_engine.observer.getExecutionContext( 'rfc3412.receiveMessage:request') LOG.info('#Notification from %s \n#ContextEngineId: "%s" ' '\n#ContextName: ''"%s" \n#SNMPVER "%s" \n#SecurityName "%s" ' % ( '@'.join( [str(x) for x in exec_context['transportAddress']]), context_engine_id.prettyPrint(), context_name.prettyPrint(), exec_context['securityModel'], exec_context['securityName'])) try: # transportAddress contains both ip and port, extract ip address source_ip = exec_context['transportAddress'][0] alert_source = self._get_alert_source_by_host(source_ip) # In case of non v3 version, community string is used to map the # trap. Pysnmp library helps to filter traps whose community string # are not configured. But if a given community name x is configured # for storage1, if the trap is received with x from storage 2, # library will allow the trap. So for non v3 version, we need to # verify that community name is configured at alert source db for # the storage which is sending traps. # context_name contains the incoming community string value if exec_context['securityModel'] != constants.SNMP_V3_INT \ and alert_source['community_string'] != str(context_name): msg = (_("Community string not matching with alert source %s, " "dropping it.") % source_ip) raise exception.InvalidResults(msg) var_binds = [rfc1902.ObjectType( rfc1902.ObjectIdentity(x[0]), x[1]).resolveWithMib( self.mib_view_controller) for x in var_binds] alert = {} for var_bind in var_binds: oid, value = self._extract_oid_value(var_bind) alert[oid] = value # Fill additional info to alert info alert['transport_address'] = source_ip alert['storage_id'] = alert_source['storage_id'] # Handover to alert processor for model translation and export alert_processor.AlertProcessor().process_alert_info(alert) except (exception.AlertSourceNotFound, exception.StorageNotFound, exception.InvalidResults) as e: # Log and end the trap processing error flow LOG.error(e) except Exception as e: # Unexpected exception occurred LOG.error(e)
def check_params_exist(keys, params): """Validates if keys exist in params. :param keys: List of keys to check :param params: Parameters received from REST API """ if any(set(keys) - set(params)): msg = _("Must specify all mandatory parameters: %s") % keys raise exception.InvalidInput(msg)
def get_storage(self): url = "/system/" result = self.call(url, method='GET', log_filter_flag=True) msg = _('Get storage error.') self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) return result['data']
def do_call(self, url, data, method, calltimeout=consts.SOCKET_TIMEOUT, log_filter_flag=False): """Send requests to Huawei storage server. Send HTTPS call, get response in JSON. Convert response into Python Object and return it. """ if self.url: url = self.url + url kwargs = {'timeout': calltimeout} if data: kwargs['data'] = json.dumps(data) if method in ('POST', 'PUT', 'GET', 'DELETE'): func = getattr(self.session, method.lower()) else: msg = _("Request method %s is invalid.") % method LOG.error(msg) raise exception.StorageBackendException(msg) try: res = func(url, **kwargs) except requests.exceptions.SSLError as e: LOG.error('SSLError exception from server: %(url)s.' ' Error: %(err)s', {'url': url, 'err': e}) err_str = six.text_type(e) if 'certificate verify failed' in err_str: raise exception.SSLCertificateFailed() else: raise exception.SSLHandshakeFailed() except Exception as err: LOG.exception('Bad response from server: %(url)s.' ' Error: %(err)s', {'url': url, 'err': err}) return {"error": {"code": consts.ERROR_CONNECT_TO_SERVER, "description": "Connect to server error."}} try: res.raise_for_status() except requests.HTTPError as exc: return {"error": {"code": exc.response.status_code, "description": six.text_type(exc)}} res_json = res.json() if not log_filter_flag: LOG.info('\n\n\n\nRequest URL: %(url)s\n\n' 'Call Method: %(method)s\n\n' 'Request Data: %(data)s\n\n' 'Response Data:%(res)s\n\n', {'url': url, 'method': method, 'data': data, 'res': res_json}) return res_json
def get_lock(self, name): """Return a Tooz back end lock. :param str name: The lock name that is used to identify it across all nodes. """ # NOTE(gouthamr): Tooz expects lock name as a byte string lock_name = (self.prefix + name).encode('ascii') if self.started: return self.coordinator.get_lock(lock_name) else: raise exception.LockCreationFailed(_('Coordinator uninitialized.'))
def dispatch(self, ctxt, data): if not isinstance(data, (list, tuple)): data = [data] for exporter in self.exporters: try: exporter.dispatch(ctxt, data) except exception.DelfinException as e: err_msg = _("Failed to export data (%s).") % e.msg LOG.exception(err_msg) except Exception as e: err_msg = six.text_type(e) LOG.exception(err_msg)
def sync(self): """ :return: """ LOG.info('Syncing storage host group for storage id:{0}'.format( self.storage_id)) try: # Collect the storage host group list from driver and database. # Build relation between host grp and host to be handled here. storage_hg_obj = self.driver_api \ .list_storage_host_groups(self.context, self.storage_id) storage_host_groups = storage_hg_obj['storage_host_groups'] storage_host_rels = storage_hg_obj['storage_host_grp_host_rels'] if storage_host_groups: db.storage_host_grp_host_rels_delete_by_storage( self.context, self.storage_id) db.storage_host_grp_host_rels_create(self.context, storage_host_rels) LOG.info('Building host group relations successful for ' 'storage id:{0}'.format(self.storage_id)) db_storage_host_groups = db.storage_host_groups_get_all( self.context, filters={"storage_id": self.storage_id}) add_list, update_list, delete_id_list = self._classify_resources( storage_host_groups, db_storage_host_groups, 'native_storage_host_group_id') LOG.debug('###StorageHostGroupTask for {0}:add={1},delete={2},' 'update={3}'.format(self.storage_id, len(add_list), len(delete_id_list), len(update_list))) if delete_id_list: db.storage_host_groups_delete(self.context, delete_id_list) if update_list: db.storage_host_groups_update(self.context, update_list) if add_list: db.storage_host_groups_create(self.context, add_list) except AttributeError as e: LOG.error(e) except NotImplementedError: # Ignore this exception because driver may not support it. pass except Exception as e: msg = _( 'Failed to sync storage host groups entry in DB: {0}'.format( e)) LOG.error(msg) else: LOG.info("Syncing storage host groups successful!!!")
def __init__(self, ext_mgr=None): if ext_mgr is None: if self.ExtensionManager: # pylint: disable=not-callable ext_mgr = self.ExtensionManager() else: raise Exception(_("Must specify an ExtensionManager class")) mapper = ProjectMapper() self.resources = {} self._setup_routes(mapper) self._setup_ext_routes(mapper, ext_mgr) self._setup_extensions(ext_mgr) super(APIRouter, self).__init__(mapper)
def login(self): """Login Hpe3par storage array.""" access_session = self.REST_AUTH_TOKEN if access_session is None: if self.san_address: url = RestClient.REST_AUTH_URL data = {"user": self.rest_username, "password": self.rest_password } self.init_http_head() res = self.do_call(url, data, 'POST', calltimeout=consts.SOCKET_TIMEOUT) if res is not None: # check login status 201 if res.status_code == consts.LOGIN_SUCCESS_STATUS_CODES: result = res.json() access_session = result.get('key') self.REST_AUTH_TOKEN = access_session # set taken in the header, # key is X-HP3PAR-WSAPI-SessionKey self.session.headers[ RestClient.REST_AUTH_KEY] = access_session else: LOG.error("Login error. URL: %(url)s\n" "Reason: %(reason)s.", {"url": url, "reason": res.text}) if 'invalid username or password' in res.text: raise exception.InvalidUsernameOrPassword() else: raise exception.Invalid() else: LOG.error('login res is None') raise exception.InvalidResults('res is None') else: LOG.error('login Parameter error') else: LOG.error( "No login required!self.access_session have value=={}".format( access_session)) if access_session is None: msg = _("Failed to login with all rest URLs.") LOG.error(msg) raise exception.BadRequest(reason=msg) return access_session
def do_call(self, url, data, method, calltimeout=SOCKET_TIMEOUT): if 'http' not in url: if self.san_address: url = '%s%s' % (self.san_address, url) kwargs = {'timeout': calltimeout} if data: kwargs['data'] = json.dumps(data) if method in ('POST', 'PUT', 'GET', 'DELETE'): func = getattr(self.session, method.lower()) else: msg = _("Request method %s is invalid.") % method LOG.error(msg) raise exception.StorageBackendException(msg) res = None try: res = func(url, **kwargs) except requests.exceptions.ConnectTimeout as ct: LOG.error('Connect Timeout err: {}'.format(ct)) raise exception.InvalidIpOrPort() except requests.exceptions.ReadTimeout as rt: LOG.error('Read timed out err: {}'.format(rt)) raise exception.StorageBackendException(six.text_type(rt)) except requests.exceptions.SSLError as e: LOG.error('SSLError for %s %s' % (method, url)) err_str = six.text_type(e) if 'certificate verify failed' in err_str: raise exception.SSLCertificateFailed() else: raise exception.SSLHandshakeFailed() except Exception as err: LOG.exception( 'Bad response from server: %(url)s.' ' Error: %(err)s', { 'url': url, 'err': err }) if 'WSAETIMEDOUT' in str(err): raise exception.ConnectTimeout() elif 'Failed to establish a new connection' in str(err): LOG.error('Failed to establish: {}'.format(err)) raise exception.InvalidIpOrPort() elif 'Read timed out' in str(err): raise exception.StorageBackendException(six.text_type(err)) else: raise exception.BadResponse() return res
def check_status_code_success(operation, status_code, message): """Check if a status code indicates success. :param operation: the operation :param status_code: the status code :param message: the server response :raises: StorageBackendException """ if status_code not in [STATUS_200, STATUS_201, STATUS_202, STATUS_204]: exception_message = ( _("Error %(operation)s. The status code received is %(sc)s " "and the message is %(message)s.") % { 'operation': operation, 'sc': status_code, 'message': message }) raise exception.StorageBackendException(message=exception_message)
def _set_performance_strategy(self, hist_enable=1, hist_duration=60, auto_stop=0, duration=5, max_duration=0): url = "/performance_statistic_strategy" data = { "CMO_STATISTIC_ARCHIVE_SWITCH": hist_enable, "CMO_STATISTIC_ARCHIVE_TIME": hist_duration, "CMO_STATISTIC_AUTO_STOP": auto_stop, "CMO_STATISTIC_INTERVAL": duration, "CMO_STATISTIC_MAX_TIME": max_duration } result = self.call(url, data, method='PUT', log_filter_flag=True) msg = _('Set performance_statistic_strategy failed.') self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) return result['data']
def sync(self): """ :return: """ LOG.info('Syncing storage device for storage id:{0}'.format( self.storage_id)) try: storage = self.driver_api.get_storage(self.context, self.storage_id) db.storage_update(self.context, self.storage_id, storage) except Exception as e: msg = _('Failed to update storage entry in DB: {0}'.format(e)) LOG.error(msg) raise else: LOG.info("Syncing storage successful!!!")
def get_volume(self, array, version, device_id): """Get a VMax volume from array. :param array: the array serial number :param device_id: the volume device id :returns: volume dict :raises: StorageBackendException """ volume_dict = self.get_resource( array, SLOPROVISIONING, 'volume', resource_name=device_id, version=version) if not volume_dict: exception_message = (_("Volume %(deviceID)s not found.") % {'deviceID': device_id}) LOG.error(exception_message) raise exception.StorageBackendException( message=exception_message) return volume_dict
def _get_alert_source_by_host(source_ip): """Gets alert source for given source ip address.""" filters = {'host': source_ip} ctxt = context.RequestContext() # Using the known filter and db exceptions are handled by api alert_source = db.alert_source_get_all(ctxt, filters=filters) if not alert_source: raise exception.AlertSourceNotFoundWithHost(source_ip) # This is to make sure unique host is configured each alert source if len(alert_source) > 1: msg = (_("Failed to get unique alert source with host %s.") % source_ip) raise exception.InvalidResults(msg) return alert_source[0]
def get_bool_from_api_params(key, params, default=False, strict=True): """Parse bool value from request params. HTTPBadRequest will be directly raised either of the cases below: 1. invalid bool string was found by key(with strict on). 2. key not found while default value is invalid(with strict on). """ param = params.get(key, default) try: param = strutils.bool_from_string(param, strict=strict, default=default) except ValueError: msg = _('Invalid value %(param)s for %(param_string)s. ' 'Expecting a boolean.') % {'param': param, 'param_string': key} raise exception.InvalidInput(msg) return param
def perform_history_collection(self, start_time, end_time, last_run_time): # Trigger one historic collection to make sure we do not # miss any Data points due to reschedule LOG.debug('Triggering one historic collection for task %s', self.task_id) try: telemetry = PerformanceCollectionTask() ret = telemetry.collect(self.ctx, self.storage_id, self.args, start_time, end_time) LOG.debug('Historic collection performed for task %s with ' 'result %s' % (self.task_id, ret)) db.task_update(self.ctx, self.task_id, {'last_run_time': last_run_time}) except Exception as e: msg = _("Failed to collect performance metrics during history " "collection for storage id:{0}, reason:{1}".format( self.storage_id, six.text_type(e))) LOG.error(msg)
def check_string_length(value, name, min_length=0, max_length=None, allow_all_spaces=True): """Check the length of specified string. :param value: the value of the string :param name: the name of the string :param min_length: the min_length of the string :param max_length: the max_length of the string """ try: strutils.check_string_length(value, name=name, min_length=min_length, max_length=max_length) except(ValueError, TypeError) as exc: raise exception.InvalidInput(exc) if not allow_all_spaces and value.isspace(): msg = _('%(name)s cannot be all spaces.') % name raise exception.InvalidInput(msg)
def validate(self, *args, **kwargs): try: self.validator.validate(*args, **kwargs) except jsonschema.ValidationError as ex: if isinstance(ex.cause, exception.InvalidName): raise ex.cause elif len(ex.path) > 0: detail = _("Invalid input for field/attribute %(path)s." " %(message)s") % { 'path': ex.path.pop(), 'message': ex.message } else: detail = ex.message raise exception.InvalidInput(detail) except TypeError as ex: # NOTE: If passing non string value to patternProperties parameter, # TypeError happens. Here is for catching the TypeError. detail = six.text_type(ex) raise exception.InvalidInput(detail)
def _unregister_perf_collection(self, storage_id): schedule = config.Scheduler.getInstance() # The path of scheduler config file config_file = CONF.scheduler.config_path try: # Load the scheduler configuration file data = config.load_json_file(config_file) for storage in data.get("storages"): config_storage_id = storage.get('id') if config_storage_id == storage_id: for resource in storage.keys(): # Skip storage id attribute and # check for all metric collection jobs if resource == 'id': continue job_id = storage_id + resource if schedule.get_job(job_id): schedule.remove_job(job_id) # Remove the entry for storage being deleted and # update schedular config file data['storages'].remove(storage) with open(config_file, "w") as jsonFile: json.dump(data, jsonFile) jsonFile.close() break except TypeError: LOG.error("Failed to unregister performance collection. Error " "occurred during parsing of config file") except json.decoder.JSONDecodeError: msg = ("Failed to unregister performance collection. Not able to " "open the config file: {0} ".format(config_file)) LOG.error(msg) except Exception as e: msg = _('Failed to unregister performance collection. Reason: {0}'. format(six.text_type(e))) LOG.error(msg)
def sync(self): """ :return: """ LOG.info('Syncing storage host initiator for storage id:{0}'.format( self.storage_id)) try: # Collect the storage host initiator list from driver and database storage_host_initiators = self.driver_api \ .list_storage_host_initiators(self.context, self.storage_id) db_storage_host_initiators = db.storage_host_initiators_get_all( self.context, filters={"storage_id": self.storage_id}) add_list, update_list, delete_id_list = self._classify_resources( storage_host_initiators, db_storage_host_initiators, 'native_storage_host_initiator_id') LOG.debug('###StorageHostInitiatorTask for {0}:add={1},delete={2},' 'update={3}'.format(self.storage_id, len(add_list), len(delete_id_list), len(update_list))) if delete_id_list: db.storage_host_initiators_delete(self.context, delete_id_list) if update_list: db.storage_host_initiators_update(self.context, update_list) if add_list: db.storage_host_initiators_create(self.context, add_list) except AttributeError as e: LOG.error(e) except NotImplementedError: # Ignore this exception because driver may not support it. pass except Exception as e: msg = _('Failed to sync storage host initiators entry in DB: {0}'. format(e)) LOG.error(msg) else: LOG.info("Syncing storage host initiators successful!!!")
def parse_alert(alert): try: alert_model = dict() alert_model['alert_id'] = alert.get(consts.OID_MESSAGECODE) alert_model['alert_name'] = alert.get(consts.OID_DETAILS) alert_model['severity'] = consts.TRAP_LEVEL_MAP.get( alert.get(consts.OID_SEVERITY), constants.Severity.INFORMATIONAL) alert_model['category'] = constants.Category.FAULT alert_model['type'] = constants.EventType.EQUIPMENT_ALARM alert_model['occur_time'] = int(time.time() * units.k) alert_model['description'] = alert.get(consts.OID_DETAILS) alert_model['resource_type'] = constants.DEFAULT_RESOURCE_TYPE alert_model['match_key'] = hashlib.md5( alert.get(consts.OID_DETAILS, '').encode()).hexdigest() return alert_model except Exception as e: LOG.error(e) msg = (_("Failed to build alert model as some attributes missing " "in alert message.")) raise exception.InvalidResults(msg)
def collect(self): """ :return: """ LOG.info( 'Collecting array performance metrics for storage id:{0}'.format( self.storage_id)) try: # collect the performance metrics from driver and push to # prometheus exporter api array_metrics = self.driver_api.collect_array_metrics( self.context, self.storage_id, self.interval, self.is_historic) self.perf_exporter.dispatch(self.context, array_metrics) except Exception as e: msg = _('Failed to collect array performance metrics from ' 'driver: {0}'.format(e)) LOG.error(msg) else: LOG.info("Array performance metrics collection done!!!")
def create(self, req, body): """Register a new storage device.""" ctxt = req.environ['delfin.context'] access_info_dict = body if self._storage_exist(ctxt, access_info_dict): raise exception.StorageAlreadyExists() storage = self.driver_api.discover_storage(ctxt, access_info_dict) # Registration success, sync resource collection for this storage try: self.sync(req, storage['id']) except Exception as e: # Unexpected error occurred, while syncing resources. msg = _('Failed to sync resources for storage: %(storage)s. ' 'Error: %(err)s') % { 'storage': storage['id'], 'err': e } LOG.error(msg) return storage_view.build_storage(storage)