blob: f5245f154a76ebec085bc97527f9f188ce0de842 [file] [log] [blame]
#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