blob: 53aa36ac104bb53f28648765966f18c75fd97aeb [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 "fru.pb.h"
#include "resource.pb.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;
}
std::vector<std::string> GetAllFanIdsForChassis(
const std::unique_ptr<Store>& store, absl::string_view chassis_id) {
std::vector<std::string> fan_ids;
if (absl::StatusOr<std::vector<std::pair<std::string, std::string>>>
fan_info = store->GetFanInfoByConfigKey(chassis_id);
fan_info.ok()) {
for (const auto& [fan_id, _] : *fan_info) {
fan_ids.push_back(fan_id);
}
}
return fan_ids;
}
absl::StatusOr<ResourceType> GetResourceType(
const std::unique_ptr<Store>& store, absl::string_view config_key) {
ECCLESIA_ASSIGN_OR_RETURN(absl::string_view fru_key,
store->GetFruKeyByConfigKey(config_key));
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::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;
absl::flat_hash_map<std::string, std::vector<std::string>>
chassis_id_to_fan_ids;
if (absl::StatusOr<std::vector<std::string>> configs =
store_->GetAllConfigKeys();
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);
chassis_id_to_fan_ids[config] =
GetAllFanIdsForChassis(store_, 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 collection corresponding to the configs in the store.
if (route == "/redfish/v1/Chassis/<str>/Sensors/") {
for (const std::string& chassis_id : chassis_ids) {
if (store_->IsConfigKeyOwningAllSensors(chassis_id)) {
owned_urls.insert(
std::make_pair(absl::StrCat("/redfish/v1/Chassis/", chassis_id,
"/Sensors"),
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_->GetAllSensorKeysByConfigKey(chassis_id)) {
owned_urls.insert(
std::make_pair(absl::StrCat("/redfish/v1/Chassis/", chassis_id,
"/Sensors/", sensor_key),
methods_fields));
}
}
continue;
}
if (route == "/redfish/v1/Chassis/<str>/PowerSubsystem/") {
for (const std::string& chassis_id : chassis_ids) {
owned_urls.insert(
std::make_pair(absl::StrCat("/redfish/v1/Chassis/", chassis_id,
"/PowerSubsystem"),
methods_fields));
}
continue;
}
if (route == "/redfish/v1/Chassis/<str>/ThermalSubsystem/") {
for (const std::string& chassis_id : chassis_ids) {
owned_urls.insert(
std::make_pair(absl::StrCat("/redfish/v1/Chassis/", chassis_id,
"/ThermalSubsystem"),
methods_fields));
}
continue;
}
if (route == "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") {
for (const std::string& chassis_id : chassis_ids) {
owned_urls.insert(
std::make_pair(absl::StrCat("/redfish/v1/Chassis/", chassis_id,
"/ThermalSubsystem/Fans"),
methods_fields));
}
continue;
}
if (route == "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") {
for (const auto& [chassis_id, fan_ids] : chassis_id_to_fan_ids) {
for (const auto& fan_id : fan_ids) {
owned_urls.insert(
std::make_pair(absl::StrCat("/redfish/v1/Chassis/", chassis_id,
"/ThermalSubsystem/Fans/", fan_id),
methods_fields));
}
}
continue;
}
if (route == "/redfish/v1/Chassis/<str>/PowerSubsystem/") {
for (const std::string& chassis_id : chassis_ids) {
owned_urls.insert(std::make_pair(
absl::StrCat("/redfish/v1/Chassis/", chassis_id, "/PowerSubsystem"),
methods_fields));
}
continue;
}
if (absl::StartsWith(
route,
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/")) {
owned_urls.insert(
std::make_pair(std::string(route.substr(0, route.size() - 1)),
MethodFieldsFromSingleHttpVerb(HttpVerb::kGet)));
continue;
}
}
return owned_urls;
}
absl::flat_hash_set<std::string> RedfishApp::GetOwnedSubtrees() const {
absl::flat_hash_set<std::string> owned_subtrees;
// We own the whole UpdateService and TaskService if install module is
// enabled.
if (GetTlbmcConfig().install_module().enabled()) {
owned_subtrees.insert(
{"/redfish/v1/UpdateService", "/redfish/v1/TaskService"});
}
// We own the whole CertificateService if trust bundle install module is
// enabled.
if (GetTlbmcConfig().trust_bundle_install_module().enabled()) {
owned_subtrees.insert({"/redfish/v1/CertificateService"});
}
return owned_subtrees;
}
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> owned_urls_map = GetOwnedUrls();
std::vector<std::pair<std::string, size_t>> owned_urls = {
owned_urls_map.begin(), owned_urls_map.end()};
std::sort(owned_urls.begin(), owned_urls.end());
LOG(INFO) << "tlBMC Owned URLs: "
<< absl::StrJoin(owned_urls, ",", map_formatter);
absl::flat_hash_set<std::string> owned_subtrees = GetOwnedSubtrees();
std::vector<std::string> sorted_subtrees(owned_subtrees.begin(),
owned_subtrees.end());
std::sort(sorted_subtrees.begin(), sorted_subtrees.end());
LOG(INFO) << "tlBMC Owned Subtrees: " << absl::StrJoin(sorted_subtrees, ",");
}
} // namespace milotic_tlbmc