Files

366 lines
14 KiB
Python

###
# 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