| #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 |