summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNan Zhou <nanzhoumails@gmail.com>2022-08-12 21:05:09 +0300
committerNan Zhou <nanzhoumails@gmail.com>2022-09-21 01:13:00 +0300
commit3590bd1dee5a0e56d4d282f0a1e3cf2c8804dfa1 (patch)
tree24dfa423506c14bbef29451d09b366fdfe7a5317
parent6e5a32691a861ef54f7b2e9abd90181a369085d6 (diff)
downloadbmcweb-3590bd1dee5a0e56d4d282f0a1e3cf2c8804dfa1.tar.xz
query: propogate errors for expand
The existing code doesn't propogate errors of subqueries correctly. This commit corrects the behavior, so that the final response gets all error message of subqueries and the "highest priority" HTTP code. DMTF doesn't specify how expand queries handle error codes, since using subqueries is an implementation choice that we made in this project. What we did here follows existing behavior of this project, and follows the error message section of the Redfish spec; [1] https://redfish.dmtf.org/schemas/DSP0266_1.15.1.html#error-responses As for now, this commit uses the worst HTTP code among all the error code. See query_param.hpp, function |propogateErrorCode| for detailed order of the errror codes. Tested: 1. this is difficult to test, but I hijacked the code so it returns errors in TaskServices, then I verified that "/redfish/v1?$expand=." correctly returns 500 and the gets the error message set. 2. unit test so that when there are multiple errors, the final response gets a generate error message. Signed-off-by: Nan Zhou <nanzhoumails@gmail.com> Change-Id: I0c1ebdd9015f389801db9150d687027485f1203c
-rw-r--r--http/http_response.hpp5
-rw-r--r--redfish-core/include/error_messages.hpp5
-rw-r--r--redfish-core/include/utils/query_param.hpp84
-rw-r--r--redfish-core/include/utils/query_param_test.cpp140
-rw-r--r--redfish-core/src/error_messages.cpp35
5 files changed, 268 insertions, 1 deletions
diff --git a/http/http_response.hpp b/http/http_response.hpp
index dd1f37aea2..ddc808cb29 100644
--- a/http/http_response.hpp
+++ b/http/http_response.hpp
@@ -92,6 +92,11 @@ struct Response
return *this;
}
+ void result(unsigned v)
+ {
+ stringResponse->result(v);
+ }
+
void result(boost::beast::http::status v)
{
stringResponse->result(v);
diff --git a/redfish-core/include/error_messages.hpp b/redfish-core/include/error_messages.hpp
index 50bcfa02bc..d686b5e9e0 100644
--- a/redfish-core/include/error_messages.hpp
+++ b/redfish-core/include/error_messages.hpp
@@ -38,6 +38,11 @@ constexpr const char* messageVersionPrefix = "Base.1.11.0.";
constexpr const char* messageAnnotation = "@Message.ExtendedInfo";
/**
+ * @brief Moves all error messages from the |source| JSON to |target|
+ */
+void moveErrorsToErrorJson(nlohmann::json& target, nlohmann::json& source);
+
+/**
* @brief Formats ResourceInUse message into JSON
* Message body: "The change to the requested resource failed because the
* resource is in use or in transition."
diff --git a/redfish-core/include/utils/query_param.hpp b/redfish-core/include/utils/query_param.hpp
index 45cb43bb48..fec5384b7e 100644
--- a/redfish-core/include/utils/query_param.hpp
+++ b/redfish-core/include/utils/query_param.hpp
@@ -659,6 +659,84 @@ inline std::optional<std::string> formatQueryForExpand(const Query& query)
return str;
}
+// Propogates the worst error code to the final response.
+// The order of error code is (from high to low)
+// 500 Internal Server Error
+// 511 Network Authentication Required
+// 510 Not Extended
+// 508 Loop Detected
+// 507 Insufficient Storage
+// 506 Variant Also Negotiates
+// 505 HTTP Version Not Supported
+// 504 Gateway Timeout
+// 503 Service Unavailable
+// 502 Bad Gateway
+// 501 Not Implemented
+// 401 Unauthorized
+// 451 - 409 Error codes (not listed explictly)
+// 408 Request Timeout
+// 407 Proxy Authentication Required
+// 406 Not Acceptable
+// 405 Method Not Allowed
+// 404 Not Found
+// 403 Forbidden
+// 402 Payment Required
+// 400 Bad Request
+inline unsigned propogateErrorCode(unsigned finalCode, unsigned subResponseCode)
+{
+ // We keep a explicit list for error codes that this project often uses
+ // Higer priority codes are in lower indexes
+ constexpr std::array<unsigned, 13> orderedCodes = {
+ 500, 507, 503, 502, 501, 401, 412, 409, 406, 405, 404, 403, 400};
+ size_t finalCodeIndex = std::numeric_limits<size_t>::max();
+ size_t subResponseCodeIndex = std::numeric_limits<size_t>::max();
+ for (size_t i = 0; i < orderedCodes.size(); ++i)
+ {
+ if (orderedCodes[i] == finalCode)
+ {
+ finalCodeIndex = i;
+ }
+ if (orderedCodes[i] == subResponseCode)
+ {
+ subResponseCodeIndex = i;
+ }
+ }
+ if (finalCodeIndex != std::numeric_limits<size_t>::max() &&
+ subResponseCodeIndex != std::numeric_limits<size_t>::max())
+ {
+ return finalCodeIndex <= subResponseCodeIndex ? finalCode
+ : subResponseCode;
+ }
+ if (subResponseCode == 500 || finalCode == 500)
+ {
+ return 500;
+ }
+ if (subResponseCode > 500 || finalCode > 500)
+ {
+ return std::max(finalCode, subResponseCode);
+ }
+ if (subResponseCode == 401)
+ {
+ return subResponseCode;
+ }
+ return std::max(finalCode, subResponseCode);
+}
+
+// Propogates all error messages into |finalResponse|
+inline void propogateError(crow::Response& finalResponse,
+ crow::Response& subResponse)
+{
+ // no errors
+ if (subResponse.resultInt() >= 200 && subResponse.resultInt() < 400)
+ {
+ return;
+ }
+ messages::moveErrorsToErrorJson(finalResponse.jsonValue,
+ subResponse.jsonValue);
+ finalResponse.result(
+ propogateErrorCode(finalResponse.resultInt(), subResponse.resultInt()));
+}
+
class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
{
public:
@@ -683,6 +761,12 @@ class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
void placeResult(const nlohmann::json::json_pointer& locationToPlace,
crow::Response& res)
{
+ BMCWEB_LOG_DEBUG << "placeResult for " << locationToPlace;
+ propogateError(finalRes->res, res);
+ if (!res.jsonValue.is_object() || res.jsonValue.empty())
+ {
+ return;
+ }
nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
finalObj = std::move(res.jsonValue);
}
diff --git a/redfish-core/include/utils/query_param_test.cpp b/redfish-core/include/utils/query_param_test.cpp
index 965b6adf9c..4b89b2e1a1 100644
--- a/redfish-core/include/utils/query_param_test.cpp
+++ b/redfish-core/include/utils/query_param_test.cpp
@@ -364,6 +364,146 @@ TEST(RecursiveSelect, ReservedPropertiesAreSelected)
EXPECT_EQ(root, expected);
}
+TEST(PropogateErrorCode, 500IsWorst)
+{
+ constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400,
+ 401, 500, 501};
+ for (auto code : codes)
+ {
+ EXPECT_EQ(propogateErrorCode(500, code), 500);
+ EXPECT_EQ(propogateErrorCode(code, 500), 500);
+ }
+}
+
+TEST(PropogateErrorCode, 5xxAreWorseThanOthers)
+{
+ constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400,
+ 401, 501, 502};
+ for (auto code : codes)
+ {
+ EXPECT_EQ(propogateErrorCode(code, 505), 505);
+ EXPECT_EQ(propogateErrorCode(505, code), 505);
+ }
+ EXPECT_EQ(propogateErrorCode(502, 501), 502);
+ EXPECT_EQ(propogateErrorCode(501, 502), 502);
+ EXPECT_EQ(propogateErrorCode(503, 502), 503);
+}
+
+TEST(PropogateErrorCode, 401IsWorseThanOthers)
+{
+ constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 401};
+ for (auto code : codes)
+ {
+ EXPECT_EQ(propogateErrorCode(code, 401), 401);
+ EXPECT_EQ(propogateErrorCode(401, code), 401);
+ }
+}
+
+TEST(PropogateErrorCode, 4xxIsWorseThanOthers)
+{
+ constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 402};
+ for (auto code : codes)
+ {
+ EXPECT_EQ(propogateErrorCode(code, 405), 405);
+ EXPECT_EQ(propogateErrorCode(405, code), 405);
+ }
+ EXPECT_EQ(propogateErrorCode(400, 402), 402);
+ EXPECT_EQ(propogateErrorCode(402, 403), 403);
+ EXPECT_EQ(propogateErrorCode(403, 402), 403);
+}
+
+TEST(PropogateError, IntermediateNoErrorMessageMakesNoChange)
+{
+ crow::Response intermediate;
+ intermediate.result(boost::beast::http::status::ok);
+
+ crow::Response finalRes;
+ finalRes.result(boost::beast::http::status::ok);
+ propogateError(finalRes, intermediate);
+ EXPECT_EQ(finalRes.result(), boost::beast::http::status::ok);
+ EXPECT_EQ(finalRes.jsonValue.find("error"), finalRes.jsonValue.end());
+}
+
+TEST(PropogateError, ErrorsArePropergatedWithErrorInRoot)
+{
+ nlohmann::json root = R"(
+{
+ "@odata.type": "#Message.v1_1_1.Message",
+ "Message": "The request failed due to an internal service error. The service is still operational.",
+ "MessageArgs": [],
+ "MessageId": "Base.1.13.0.InternalError",
+ "MessageSeverity": "Critical",
+ "Resolution": "Resubmit the request. If the problem persists, consider resetting the service."
+}
+)"_json;
+ crow::Response intermediate;
+ intermediate.result(boost::beast::http::status::internal_server_error);
+ intermediate.jsonValue = root;
+
+ crow::Response final;
+ final.result(boost::beast::http::status::ok);
+
+ propogateError(final, intermediate);
+
+ EXPECT_EQ(final.jsonValue["error"]["code"].get<std::string>(),
+ "Base.1.13.0.InternalError");
+ EXPECT_EQ(
+ final.jsonValue["error"]["message"].get<std::string>(),
+ "The request failed due to an internal service error. The service is still operational.");
+ EXPECT_EQ(intermediate.jsonValue, R"({})"_json);
+ EXPECT_EQ(final.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(PropogateError, ErrorsArePropergatedWithErrorCode)
+{
+ crow::Response intermediate;
+ intermediate.result(boost::beast::http::status::internal_server_error);
+
+ nlohmann::json error = R"(
+{
+ "error": {
+ "@Message.ExtendedInfo": [],
+ "code": "Base.1.13.0.InternalError",
+ "message": "The request failed due to an internal service error. The service is still operational."
+ }
+}
+)"_json;
+ nlohmann::json extendedInfo = R"(
+{
+ "@odata.type": "#Message.v1_1_1.Message",
+ "Message": "The request failed due to an internal service error. The service is still operational.",
+ "MessageArgs": [],
+ "MessageId": "Base.1.13.0.InternalError",
+ "MessageSeverity": "Critical",
+ "Resolution": "Resubmit the request. If the problem persists, consider resetting the service."
+}
+)"_json;
+
+ for (int i = 0; i < 10; ++i)
+ {
+ error["error"][messages::messageAnnotation].push_back(extendedInfo);
+ }
+ intermediate.jsonValue = error;
+ crow::Response final;
+ final.result(boost::beast::http::status::ok);
+
+ propogateError(final, intermediate);
+ EXPECT_EQ(final.jsonValue["error"][messages::messageAnnotation],
+ error["error"][messages::messageAnnotation]);
+ std::string errorCode = messages::messageVersionPrefix;
+ errorCode += "GeneralError";
+ std::string errorMessage =
+ "A general error has occurred. See Resolution for "
+ "information on how to resolve the error.";
+ EXPECT_EQ(final.jsonValue["error"]["code"].get<std::string>(), errorCode);
+ EXPECT_EQ(final.jsonValue["error"]["message"].get<std::string>(),
+ errorMessage);
+ EXPECT_EQ(intermediate.jsonValue, R"({})"_json);
+ EXPECT_EQ(final.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
TEST(QueryParams, ParseParametersOnly)
{
auto ret = boost::urls::parse_relative_ref("/redfish/v1?only");
diff --git a/redfish-core/src/error_messages.cpp b/redfish-core/src/error_messages.cpp
index 198e54425d..1bfce24504 100644
--- a/redfish-core/src/error_messages.cpp
+++ b/redfish-core/src/error_messages.cpp
@@ -74,7 +74,7 @@ static void addMessageToErrorJson(nlohmann::json& target,
"information on how to resolve the error.";
}
- // This check could technically be done in in the default construction
+ // This check could technically be done in the default construction
// branch above, but because we need the pointer to the extended info field
// anyway, it's more efficient to do it here.
auto& extendedInfo = error[messages::messageAnnotation];
@@ -86,6 +86,39 @@ static void addMessageToErrorJson(nlohmann::json& target,
extendedInfo.push_back(message);
}
+void moveErrorsToErrorJson(nlohmann::json& target, nlohmann::json& source)
+{
+ if (!source.is_object())
+ {
+ return;
+ }
+ auto errorIt = source.find("error");
+ if (errorIt == source.end())
+ {
+ // caller puts error message in root
+ messages::addMessageToErrorJson(target, source);
+ source.clear();
+ return;
+ }
+ auto extendedInfoIt = errorIt->find(messages::messageAnnotation);
+ if (extendedInfoIt == errorIt->end())
+ {
+ return;
+ }
+ const nlohmann::json::array_t* extendedInfo =
+ (*extendedInfoIt).get_ptr<const nlohmann::json::array_t*>();
+ if (extendedInfo == nullptr)
+ {
+ source.erase(errorIt);
+ return;
+ }
+ for (const nlohmann::json& message : *extendedInfo)
+ {
+ addMessageToErrorJson(target, message);
+ }
+ source.erase(errorIt);
+}
+
static void addMessageToJsonRoot(nlohmann::json& target,
const nlohmann::json& message)
{