| #include "tlbmc/redfish/expand_query_aggregator.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstddef> |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <system_error> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/log/log.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/strings/substitute.h" |
| #include "boost/url/url.hpp" // NOLINT |
| #include "nlohmann/json_fwd.hpp" |
| #include "tlbmc/redfish/query_parameters.h" |
| #include "dbus_utility.hpp" // NOLINT |
| #include "async_resp.hpp" // NOLINT |
| #include "http_request.hpp" // NOLINT |
| |
| namespace milotic_tlbmc { |
| |
| using ::crow::Request; |
| using ::crow::Response; |
| |
| std::string ExpandQueryAggregator::CreateExpandQueryUrl( |
| absl::string_view url, [[maybe_unused]] const ExpandNode& expand_node) { |
| return absl::Substitute( |
| "$0?$$expand=$1($$levels=$2)", url, |
| ExpandTypeToUrlParam(query_parameters_.GetExpandType()), |
| std::to_string(query_parameters_.GetExpandLevel() - 1)); |
| } |
| |
| void ExpandQueryAggregator::StartQuery() { |
| std::vector<ExpandNode> urls_to_expand = GetUnexpandedUrls( |
| final_async_resp_->res.jsonValue, query_parameters_.GetExpandType()); |
| for (const ExpandNode& url_to_expand : urls_to_expand) { |
| boost::urls::url expand_url(url_to_expand.url); |
| boost::beast::http::request<boost::beast::http::string_body> request; |
| std::string request_url = |
| CreateExpandQueryUrl(expand_url.encoded_path(), url_to_expand); |
| request.target(request_url); |
| request.method(boost::beast::http::verb::get); |
| |
| std::error_code ec; |
| Request sub_req(std::move(request), ec); |
| auto sub_async_resp = |
| std::make_shared<bmcweb::AsyncResp>(final_async_resp_->strand_); |
| sub_async_resp->res.setCompleteRequestHandler( |
| // We must extend the lifetime of the final_response to the lifetime |
| // of the sub_async_resp. |
| [expand_aggregator = shared_from_this(), url_to_expand](Response& res) { |
| expand_aggregator->AddResponseToFinalResponse( |
| url_to_expand.json_pointer, res); |
| }); |
| LOG(INFO) << "Sub request: " << sub_req.target(); |
| smart_router_->Handle(sub_req, sub_async_resp); |
| } |
| } |
| |
| void ExpandQueryAggregator::AddResponseToFinalResponse( |
| const nlohmann::json::json_pointer& location, Response& sub_response) { |
| // Special handling for subresponse error |
| if (sub_response.resultInt() < 200 || sub_response.resultInt() >= 400) { |
| DLOG(INFO) << "Sub response error: " << sub_response.resultInt() << " " |
| << sub_response.jsonValue.dump(); |
| // Determine the final error code |
| final_async_resp_->res.result( |
| DetermineFinalErrorCode(sub_response.resultInt())); |
| |
| // Move Error Messages to final response |
| PropagateErrorMessage(sub_response.jsonValue); |
| |
| // Remove error from subresponse before moving |
| auto error = sub_response.jsonValue.find("error"); |
| if (error != sub_response.jsonValue.end()) { |
| sub_response.jsonValue.erase(error); |
| } |
| } |
| |
| final_async_resp_->res.jsonValue[location] = |
| std::move(sub_response.jsonValue); |
| } |
| |
| ErrorInformation ExpandQueryAggregator::GetErrorInformation( |
| const nlohmann::json& response_json) { |
| ErrorInformation error_information; |
| auto error = response_json.find("error"); |
| if (error != response_json.end()) { |
| auto message = error->find("message"); |
| if (message != error->end()) { |
| error_information.error_message = *message; |
| } |
| |
| auto code = error->find("code"); |
| if (code != error->end()) { |
| error_information.error_code = *code; |
| } |
| |
| auto extended_info = error->find("@Message.ExtendedInfo"); |
| if (extended_info != error->end() && extended_info->is_array()) { |
| error_information.extended_info = *extended_info; |
| } |
| } |
| return error_information; |
| } |
| |
| void ExpandQueryAggregator::PropagateErrorMessage( |
| nlohmann::json& sub_response_json) { |
| nlohmann::json& final_response_json_error = |
| final_async_resp_->res.jsonValue["error"]; |
| |
| ErrorInformation error_information = GetErrorInformation(sub_response_json); |
| |
| // If the final response doesn't have an error object, create and populate |
| if (!final_response_json_error.is_object()) { |
| final_response_json_error["code"] = error_information.error_code; |
| final_response_json_error["message"] = error_information.error_message; |
| } else { |
| // Else use generic error code and message |
| final_response_json_error["code"] = "Base.1.11.0.GeneralError"; |
| final_response_json_error["message"] = |
| "A general error has occurred. See Resolution for information on how " |
| "to resolve the error."; |
| } |
| |
| // Populate the Extended Info |
| nlohmann::json& final_extended_info = |
| final_response_json_error["@Message.ExtendedInfo"]; |
| |
| // Create Extended Info array if it doesn't exist |
| if (!final_extended_info.is_array()) { |
| final_extended_info = nlohmann::json::array(); |
| } |
| |
| // Populate subresponse's extended info into the final response's extended |
| // info |
| for (const auto& sub_response_extended_info : |
| error_information.extended_info) { |
| final_extended_info.push_back(sub_response_extended_info); |
| } |
| } |
| |
| // 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 |
| unsigned ExpandQueryAggregator::DetermineFinalErrorCode( |
| unsigned sub_response_code) { |
| unsigned cur_code = final_async_resp_->res.resultInt(); |
| // 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> ordered_codes = { |
| 500, 507, 503, 502, 501, 401, 412, 409, 406, 405, 404, 403, 400}; |
| size_t final_code_index = std::numeric_limits<size_t>::max(); |
| size_t sub_response_code_index = std::numeric_limits<size_t>::max(); |
| for (size_t i = 0; i < ordered_codes.size(); ++i) { |
| if (ordered_codes[i] == cur_code) { |
| final_code_index = i; |
| } |
| if (ordered_codes[i] == sub_response_code) { |
| sub_response_code_index = i; |
| } |
| } |
| if (final_code_index != std::numeric_limits<size_t>::max() && |
| sub_response_code_index != std::numeric_limits<size_t>::max()) { |
| return final_code_index <= sub_response_code_index ? cur_code |
| : sub_response_code; |
| } |
| if (sub_response_code == 500 || cur_code == 500) { |
| return 500; |
| } |
| if (sub_response_code > 500 || cur_code > 500) { |
| return std::max(cur_code, sub_response_code); |
| } |
| if (sub_response_code == 401) { |
| return sub_response_code; |
| } |
| return std::max(cur_code, sub_response_code); |
| } |
| |
| } // namespace milotic_tlbmc |