blob: 39d59ab6238255e7af9d15a50b8257841430a6a6 [file] [log] [blame]
#include "tlbmc/redfish/app.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <memory>
#include <string>
#include <string_view>
#include <thread> // NOLINT
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "g3/macros.h"
#include "http_request.hpp"
#include "async_resp.hpp"
#include "tlbmc/central_config/config.h"
#include "tlbmc/pacemaker/pacemaker.h"
#include "tlbmc/redfish/request.h"
#include "tlbmc/redfish/response.h"
#include "tlbmc/redfish/verb.h"
#include "tlbmc/store/store.h"
#include "redfish-core/include/error_messages.hpp"
#include "router_interface.h"
namespace milotic_tlbmc {
using ::crow::RouterInterface;
RedfishApp::RedfishApp(std::unique_ptr<milotic_tlbmc::Store> store)
: store_(std::move(store)),
thread_pool_(std::thread::hardware_concurrency()) {}
void RedfishApp::HandleInternal(const RedfishRequest& req,
RedfishResponse& resp) const {
if (store_ == nullptr) {
resp.SetToAbslStatus(absl::InternalError("Store is nullptr"));
resp.End();
return;
}
router_.Handle(req, resp);
}
RedfishApp::RedfishApp(std::unique_ptr<milotic_tlbmc::Pacemaker> pacemaker)
: thread_pool_(std::thread::hardware_concurrency()),
pacemaker_(std::move(pacemaker)) {}
void RedfishApp::Handle(RedfishRequest&& req, RedfishResponse&& resp) const {
boost::asio::post(thread_pool_, [this, req(std::move(req)),
resp(std::move(resp))]() mutable {
HandleInternal(req, resp);
});
}
void RedfishApp::HandleFromCrowRequest(
const ::crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& async_resp) const {
absl::StatusOr<RedfishRequest> redfish_req =
RedfishRequest::Create(req.request());
if (!redfish_req.ok()) {
LOG(WARNING) << "Failed to create redfish request: "
<< redfish_req.status();
redfish::messages::internalError(async_resp->res);
return;
}
redfish_req->SetWithTrustBundle(req.with_trust_bundle);
redfish_req->SetFromGrpc(req.fromGrpc);
redfish_req->SetPeerAuthenticated(req.peer_authenticated);
redfish_req->SetPeerPrivileges(req.peer_privileges);
RedfishResponse redfish_resp;
redfish_resp.SetAsyncResp(async_resp);
HandleInternal(*redfish_req, redfish_resp);
}
void RedfishApp::Validate() { router_.Validate(); }
absl::flat_hash_map<std::string, size_t> RedfishApp::GetRegisteredRoutes()
const {
// Routes returned from `router_` is a vector of pairs of URL and its single
// bit set method bitmask.
std::vector<std::pair<std::string_view, size_t>> routes =
router_.GetRegisteredRoutes();
absl::flat_hash_map<std::string, size_t> routes_map;
for (const auto& [route, methods] : routes) {
auto& methods_fields = routes_map[route];
// We will merge the methods fields for the same URL.
methods_fields = MergeMethodFields(methods_fields, methods);
}
return routes_map;
}
absl::StatusOr<ResourceType> GetResourceType(
const std::unique_ptr<Store>& store, absl::string_view config_name) {
ECCLESIA_ASSIGN_OR_RETURN(absl::string_view fru_key,
store->GetFruKeyByConfigName(config_name));
ECCLESIA_ASSIGN_OR_RETURN(const Fru* fru, store->GetFru(fru_key));
return fru->attributes().resource_type();
}
absl::flat_hash_map<std::string, size_t> RedfishApp::GetSupportedUrls() const {
absl::flat_hash_map<std::string, size_t> routes = GetRegisteredRoutes();
absl::flat_hash_map<std::string, size_t> supported_urls;
constexpr std::array<absl::string_view, 4> kSupportedUrlCandidates = {
"/redfish/", "/redfish/v1/", "/redfish/v1/Cables/",
"/redfish/v1/Chassis/"};
for (const auto& url : kSupportedUrlCandidates) {
if (auto it = routes.find(url); it != routes.end()) {
// Get rid of trailing slash for supported urls.
supported_urls.insert(
std::make_pair(
std::string(url.substr(0, url.size() - 1)), it->second));
}
}
// Individual entities
std::vector<std::string> cable_ids;
std::vector<std::string> chassis_ids;
if (absl::StatusOr<std::vector<std::string>> configs =
store_->GetAllConfigNames();
configs.ok()) {
for (const std::string& config : *configs) {
absl::StatusOr<ResourceType> resource_type =
GetResourceType(store_, config);
if (!resource_type.ok()) {
continue;
}
switch (*resource_type) {
case RESOURCE_TYPE_BOARD:
chassis_ids.push_back(config);
break;
case RESOURCE_TYPE_CABLE:
cable_ids.push_back(config);
break;
default:
break;
}
}
}
if (auto it = routes.find("/redfish/v1/Cables/<str>/"); it != routes.end()) {
for (const std::string& cable_id : cable_ids) {
supported_urls.insert(std::make_pair(
absl::StrCat("/redfish/v1/Cables/", cable_id), it->second));
}
}
if (auto it = routes.find("/redfish/v1/Chassis/<str>/"); it != routes.end()) {
for (const std::string& chassis_id : chassis_ids) {
supported_urls.insert(std::make_pair(
absl::StrCat("/redfish/v1/Chassis/", chassis_id), it->second));
}
}
// Sensor Collections
if (auto it = routes.find("/redfish/v1/Chassis/<str>/Sensors/");
it != routes.end()) {
for (const std::string& chassis_id : chassis_ids) {
supported_urls.insert(std::make_pair(
absl::StrCat("/redfish/v1/Chassis/", chassis_id, "/Sensors"),
it->second));
}
}
// Individual sensors
if (auto it = routes.find("/redfish/v1/Chassis/<str>/Sensors/<str>/");
it != routes.end()) {
for (const std::string& chassis_id : chassis_ids) {
for (const auto& sensor_key :
store_->GetAllSensorKeysByConfigName(chassis_id)) {
supported_urls.insert(
std::make_pair(absl::StrCat("/redfish/v1/Chassis/", chassis_id,
"/Sensors/", sensor_key),
it->second));
}
}
}
constexpr std::array<std::string_view, 10> kTlbmcUrls = {
"/redfish/tlbmc/",
"/redfish/tlbmc/AllChassis/",
"/redfish/tlbmc/AllSensors/",
"/redfish/tlbmc/AllSensorsHistory/",
"/redfish/tlbmc/Debug/",
"/redfish/tlbmc/Debug/App/",
"/redfish/tlbmc/Debug/Store/",
"/redfish/tlbmc/Debug/Scheduler/",
"/redfish/tlbmc/Debug/HftService/",
"/redfish/tlbmc/Metrics/",
};
for (const auto& url : kTlbmcUrls) {
if (routes.contains(url)) {
// Get rid of trailing slash.
supported_urls.insert(
std::make_pair(std::string(url.substr(0, url.size() - 1)),
MethodFieldsFromSingleHttpVerb(HttpVerb::kGet)));
}
}
return supported_urls;
}
absl::flat_hash_map<std::string, size_t> RedfishApp::GetOwnedUrls() const {
absl::flat_hash_map<std::string, size_t> routes = GetRegisteredRoutes();
absl::flat_hash_map<std::string, size_t> owned_urls;
// Individual entities
std::vector<std::string> cable_ids;
std::vector<std::string> chassis_ids;
if (absl::StatusOr<std::vector<std::string>> configs =
store_->GetAllConfigNames();
configs.ok()) {
for (const std::string& config : *configs) {
absl::StatusOr<ResourceType> resource_type =
GetResourceType(store_, config);
if (!resource_type.ok()) {
continue;
}
switch (*resource_type) {
case RESOURCE_TYPE_BOARD:
chassis_ids.push_back(config);
break;
case RESOURCE_TYPE_CABLE:
cable_ids.push_back(config);
break;
default:
break;
}
}
}
for (const auto& [route, methods_fields] : routes) {
// The tlbmc redfish subtree /redfish/tlbmc/ is owned by tlbmc.
if (absl::StartsWith(route, "/redfish/tlbmc/")) {
// Rules ALWAYS have a trailing slash, but we don't want that for the
// owned urls.
// https://source.corp.google.com/piper///depot/google3/third_party/milotic/external/cc/tlbmc/redfish/routing.h;rcl=731047512;l=271
owned_urls.insert(std::make_pair(
std::string(route.substr(0, route.size() - 1)), methods_fields));
continue;
}
// Own the cables corresponding to the configs in the store.
if (route == "/redfish/v1/Cables/<str>/") {
for (const std::string& cable_id : cable_ids) {
owned_urls.insert(std::make_pair(
absl::StrCat("/redfish/v1/Cables/", cable_id), methods_fields));
}
continue;
}
// Own the chassis corresponding to the configs in the store.
if (route == "/redfish/v1/Chassis/<str>/") {
for (const std::string& chassis_id : chassis_ids) {
owned_urls.insert(std::make_pair(
absl::StrCat("/redfish/v1/Chassis/", chassis_id), methods_fields));
}
continue;
}
// Own the sensors corresponding to the configs in the store.
if (route == "/redfish/v1/Chassis/<str>/Sensors/<str>/") {
for (const std::string& chassis_id : chassis_ids) {
for (const auto& sensor_key :
store_->GetAllSensorKeysByConfigName(chassis_id)) {
owned_urls.insert(
std::make_pair(absl::StrCat("/redfish/v1/Chassis/", chassis_id,
"/Sensors/", sensor_key),
methods_fields));
}
}
continue;
}
}
return owned_urls;
}
absl::flat_hash_set<std::string> RedfishApp::GetOwnedSubtrees() const {
// We own the whole UpdateService and TaskService if install module is
// enabled.
if (GetTlbmcConfig().install_module().enabled()) {
return {"/redfish/v1/UpdateService", "/redfish/v1/TaskService"};
}
return {};
}
void RedfishApp::SetSmartRouter(RouterInterface* smart_router) {
router_.SetSmartRouter(smart_router);
store_->SetSmartRouter(smart_router);
}
void RedfishApp::DebugPrint() const {
auto map_formatter = [](std::string* out,
const std::pair<std::string, size_t>& pair) {
absl::StrAppend(out, pair.first);
absl::StrAppend(out, ":", MethodFieldsToString(pair.second));
};
// Sort so the output is deterministic and testable.
absl::flat_hash_map<std::string, size_t> supported_urls_map =
GetSupportedUrls();
std::vector<std::pair<std::string, size_t>> supported_urls = {
supported_urls_map.begin(), supported_urls_map.end()};
absl::flat_hash_map<std::string, size_t> owned_urls_map = GetOwnedUrls();
std::vector<std::pair<std::string, size_t>> owned_urls = {
owned_urls_map.begin(), owned_urls_map.end()};
std::sort(supported_urls.begin(), supported_urls.end());
std::sort(owned_urls.begin(), owned_urls.end());
LOG(INFO) << "tlBMC Supported URLs: "
<< absl::StrJoin(supported_urls, ",", map_formatter);
LOG(INFO) << "tlBMC Owned URLs: "
<< absl::StrJoin(owned_urls, ",", map_formatter);
}
} // namespace milotic_tlbmc