### # Copyright 2020 Hewlett Packard Enterprise, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ### # -*- coding: utf-8 -*- """Error response handler for Redfish or LegacryRest responses. Extended information only available with registries available on system, otherwise will return generic error responses.""" import logging from redfish.ris.ris import SessionExpired from redfish.ris.rmc_helper import ( EmptyRaiseForEAFP, IdTokenError, IloLicenseError, IloResponseError, ScepenabledError, ValueChangedError, ) from redfish.ris.utils import ( get_errmsg_type, json_traversal, print_handler, warning_handler, ) # ---------Debug logger--------- LOGGER = logging.getLogger() # ---------End of debug logger--------- class ResponseHandler(object): """Class to handle error responses from the server. :param validation_mgr: ValidationManager instance to gather registries if needed. Available in an RmcApp class as an attribute. :type validation_mgr: ValidationManager :param msg_reg_type: Redfish (#MessageRegistry.) or LegacyRest (MessageRegistry.) message registry string. Available in Typesandpathdefines class. :type msg_reg_type: str """ def __init__(self, validaition_mgr, msg_type): self.validation_mgr = validaition_mgr self.msg_reg_type = msg_type def output_resp(self, response, dl_reg=False, verbosity=1, stderr_flag=False): """Prints or logs parsed MessageId response. Will raise an IloResponseError or return a list of message response data which includes the information returned from message_handler. :param response: message response of a call. :type response: :class:`redfish.rest.containers.RestResponse` :param dl_reg: Flag to download registry. If this is set to True a generic message response will be returned instead of gathered from registries. :type dl_reg: bool :param verbosity: Optional verbosity level. Only modifies what is output to log or screen. :type verbosity: int :returns: List of error response dictionaries. """ retdata = None if response.status > 299: message_text = "No error message returned or unable to parse error response." else: message_text = "The operation completed successfully." if response.status < 300 and (response._rest_request.method == "GET" or not response.read): # for rawget if verbosity == 0: verbosity = 1 print_handler( self.verbosity_levels( message=message_text, response_status=response.status, verbosity=verbosity, dl_reg=dl_reg ), stderr_flag, ) elif response.status == 401: raise SessionExpired() elif response.status == 403: results = response.dict["error"]["@Message.ExtendedInfo"] for result in results: if "License" in list(result.values())[0] or "license" in list(result.values())[0]: raise IloLicenseError("") raise IdTokenError() elif response.status == 412: warning_handler( "The property you are trying to change has been updated. " "Please check entry again before manipulating it.\n", override=True, ) raise ValueChangedError() else: retdata = self.message_handler( response_data=response, verbosity=verbosity, message_text=message_text, dl_reg=dl_reg, stderr_flag=stderr_flag, ) if response.status == 400: results = response.dict["error"]["@Message.ExtendedInfo"] for result in results: if "License" in list(result.values())[0] or "license" in list(result.values())[0]: raise IloLicenseError("") if "UnsupportedOperationACEEnabled" in list(result.values())[0]: raise ScepenabledError("") if response.status > 299: raise IloResponseError("") else: return retdata def message_handler(self, response_data, verbosity=0, message_text="No Response", dl_reg=False, stderr_flag=False): """Prints or logs parsed MessageId response based on verbosity level and returns the following message information in a list: * MessageArgs * MessageId * RestResponse status * Resolution * Full error message text :param response_data: message response of a call. :type response_data: :class:`redfish.rest.containers.RestResponse` :param verbosity: Optional verbosity level. Only modifies what is output to log or screen. :type verbosity: int :param message_text: Response message text. If not provided, message_handler will attempt to parse it from the RestResponse and registries. :type message_text: str :param dl_reg: Flag to download registry. If this is set to True a generic message response will be returned instead of gathered from registries. :type dl_reg: bool :returns: List of error response dictionaries. """ _tmp_message_id = _tmp_description = _tmp_resolution = message_text retlist = list() response_error_str = "" try: response_status = response_data.status except (AttributeError, ValueError): response_status = "???" try: response_data = response_data.dict except (AttributeError, ValueError): pass try: for inst in self.get_message_data(response_data, dl_reg): try: for _key in inst.keys(): if "messageid" in str(_key.lower()): _tmp_message_id = inst[_key] if "description" in str(_key.lower()): _tmp_description = inst[_key] if inst.get("Message") and inst.get("MessageArgs"): for i in range(inst["Message"].count("%")): inst["Message"] = inst["Message"].replace( "%" + str(i + 1), '"' + inst["MessageArgs"][i] + '"' ) message_text = inst.get("Message", " ") elif inst.get("Message"): message_text = inst.get("Message", " ") elif response_status not in [200, 201]: message_text = _tmp_message_id _tmp_resolution = inst.get("Resolution", " ") except (KeyError, ValueError, TypeError): pass finally: response_error_str += "[%s] %s\n" % (response_status, message_text) print_handler( self.verbosity_levels( message_text, _tmp_message_id, _tmp_description, _tmp_resolution, response_status, verbosity, dl_reg, ), stderr_flag, ) retlist.append(inst) except Exception: if not message_text: message_text = _tmp_message_id response_error_str += "[%s] %s\n" % (response_status, message_text) print_handler( self.verbosity_levels( message_text, _tmp_message_id, _tmp_description, _tmp_resolution, response_status, verbosity, dl_reg, ) ) retlist.append(inst) finally: return retlist def get_message_data(self, resp_data, dl_reg=False): """Obtain relevant keys from rest response. :param resp: response :type resp: :class:`redfish.rest.containers.RestResponse` :returns: list of error response dictionaries """ err_response_keys = ["MessageId", "Message", "MessageArgs", "Resolution"] try: if "messageid" in [_key.lower() for _key in resp_data.keys()]: data_extract = [resp_data] else: raise TypeError except (TypeError, KeyError): data_extract = json_traversal(resp_data, "messageid", ret_dict=True) if data_extract: try: if not dl_reg: for inst in data_extract: if [key.lower() for key in inst.keys()] not in [erk.lower() for erk in err_response_keys]: # Find the key that matches 'messageid' (case-insensitive) for key in inst.keys(): if key.lower() == "messageid": inst.update(self.get_error_messages(inst[key])) break finally: return data_extract else: return None def verbosity_levels( self, message, messageid=" ", description=" ", resolution=" ", response_status=None, verbosity=0, dl_reg=False, ): """Formatting based on verbosity level. :param message: Message from BMC response combined with the registry model/schema. :type message: str :param messageid: Error code as classified by the BMC's error code registry. :type messageid: str :param resolution: Message from BMC registry model/schema with the suggested resolution for the given error. :type resolution: str :param response_status: HTTP response status code. :type response_status: int :param verbosity: Option to set/control output message (stderr) verbosity. :type verbosity: int :returns: Message to be returned to caller. """ resp_str = "" if response_status: resp_str = "[" + str(response_status) + "] " if (verbosity == 1 or dl_reg) and message: return resp_str + message + "\n" elif verbosity > 1 and messageid and message and resolution: if not resp_str: resp_str = "None " return ( "\nHTTP Response Code: " + resp_str[:-1] + "\nMessageId: " + messageid + "\nDescription: " + description + "\nMessage: " + message + "\nResolution: " + resolution + "\n" ) else: if response_status == 400: return "" + message + "\n" else: return "" # unused? (removal pending) @staticmethod def _get_errmsg_type(results): """Return the registry type of a response. :param resuts: rest response. :type results: RestResponse. :returns: returns a Registry Id type string, None if not match is found, or no_id if the response is not an error message. """ return get_errmsg_type(results) def get_error_messages(self, regtype=None): """Returns registry error messages. Can specify a specific registry to return by Id. :param regtype: Id of registry type to add to list. :type regtype: str :returns: A list of error messages. """ LOGGER.info("Entering validation...") messages = None errmessages = {} reglist = [] # An error occurred during the shortcut method so let's go through each registry, # obtain the schema and narrow down the selected schema for the registry type provided try: _regtype = regtype.split(".")[0] for reg in self.validation_mgr.iterregmems(): # gen 10 / gen 9 rest if _regtype: if reg and "Id" in reg and reg["Id"] == _regtype: try: reglist.append(reg["Registry"]) except KeyError: reglist.append(reg["Schema"]) break else: continue if not reglist: # gen 9 redfish regval = [reg.get(arg, None) for arg in ["Registry", "Schema", "Id"]] regval = next( (val for val in regval if val and "biosattributeregistry" not in val), None, ) if not regval and reg: reg = reg["@odata.id"].split("/") reg = reg[len(reg) - 2] if "biosattributeregistry" not in reg.lower(): reglist.append(reg) elif regval: reglist.append(regval) for reg in reglist: reg = reg.replace("%23", "#") messages = self.validation_mgr.get_registry_model( getmsg=True, currtype=reg, searchtype=self.msg_reg_type ) if messages: errmessages.update(messages.get(next(iter(messages)))[regtype.split(".")[-1]]) if not reglist or not errmessages: raise Exception except Exception: raise EmptyRaiseForEAFP("Unable to find registry schema with provided registry " "type: %s" % regtype) else: return errmessages