| #include "tlbmc/redfish/routes/managers.h" |
| |
| #include <endian.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <filesystem> // NOLINT |
| #include <fstream> |
| #include <functional> |
| #include <ios> |
| #include <optional> |
| #include <string> |
| #include <system_error> // NOLINT |
| #include <utility> |
| #include <vector> |
| |
| #include "offline_data.pb.h" |
| #include "absl/functional/bind_front.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/time.h" |
| #include "g3/macros.h" |
| #include <nlohmann/json.hpp> |
| #include "json_utils.h" |
| #include "tlbmc/central_config/config.h" |
| #include "topology_config.pb.h" |
| #include "tlbmc/redfish/app.h" |
| #include "tlbmc/redfish/data/stable_id.h" |
| #include "stable_id.pb.h" |
| #include "tlbmc/redfish/request.h" |
| #include "tlbmc/redfish/response.h" |
| #include "tlbmc/redfish/routes/action_managers/file_manager.h" |
| #include "tlbmc/redfish/url.h" |
| #include "fru.pb.h" |
| #include "tlbmc/thermal/controller/fan_pid_controller.h" |
| #include "tlbmc/thermal/controller/pid_controller.h" |
| #include "tlbmc/thermal/zone_manager.h" |
| #include "google/protobuf/text_format.h" |
| |
| namespace milotic_tlbmc::managers { |
| |
| namespace { |
| // This function will process the thermal coefficient given the json and the key |
| // and remove the key value pair from the json. |
| std::optional<double> ProcessThermalCoefficient(nlohmann::json& json, |
| absl::string_view key) { |
| std::optional<double> value = |
| milotic::authz::GetValueAsDoubleFromFloatOrInteger(json, key); |
| // If the value is present, we will process the value. If the value is bad, |
| // then it wont be processed and be sent back to the client. |
| if (value.has_value()) { |
| json.erase(key); |
| } |
| return value; |
| } |
| |
| } // namespace |
| |
| void HandleGetManagerFanModeChangeActionInfo(const RedfishApp& app, |
| const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody("/@odata.id", |
| CreateUrl({"redfish", "v1", "Managers", "bmc", |
| "FanMode.Change.ActionInfo"})); |
| resp.SetKeyInJsonBody("/@odata.type", "#ActionInfo.v1_1_2.ActionInfo"); |
| resp.SetKeyInJsonBody("/Id", "FanMode.Change.ActionInfo"); |
| resp.SetKeyInJsonBody("/Name", "FanMode Change Action Info"); |
| nlohmann::json parameters = {{{"AllowableValues", {"Manual", "Auto"}}, |
| {"DataType", "String"}, |
| {"Name", "FanMode"}, |
| {"Required", true}}}; |
| resp.SetKeyInJsonBody("/Parameters", parameters); |
| } |
| |
| void HandlePostManagerActionsManagerFanModeChange(const RedfishApp& app, |
| const RedfishRequest& req, |
| RedfishResponse& resp) { |
| nlohmann::json json_body = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (json_body.is_discarded()) { |
| resp.SetToBadRequest("Malformed JSON"); |
| return; |
| } |
| |
| const std::string* fan_mode = |
| milotic::authz::GetValueAsString(json_body, "FanMode"); |
| if (fan_mode == nullptr || (*fan_mode != "Manual" && *fan_mode != "Auto")) { |
| resp.SetToBadRequest("Invalid FanMode entry"); |
| return; |
| } |
| |
| bool enable_manual_mode = (*fan_mode == "Manual"); |
| |
| app.GetStore()->SetManualMode(enable_manual_mode); |
| |
| resp.SetKeyInJsonBody("/@odata.type", "#Message.v1_1_2.Message"); |
| resp.SetKeyInJsonBody("/MessageId", "Base.1.14.Success"); |
| resp.SetKeyInJsonBody( |
| "/Message", |
| absl::StrFormat("Successfully changed fan mode to `%s`.", *fan_mode)); |
| resp.SetKeyInJsonBody("/Severity", "OK"); |
| resp.SetKeyInJsonBody("/Resolution", "None"); |
| } |
| |
| void HandleGetManagerGoogleWipeActionInfo(const RedfishApp& app, |
| const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody("/@odata.id", |
| "/redfish/v1/Managers/bmc/Oem/Google/WipeActionInfo"); |
| resp.SetKeyInJsonBody("/@odata.type", "#ActionInfo.v1_1_2.ActionInfo"); |
| resp.SetKeyInJsonBody("/Id", "Wipe"); |
| resp.SetKeyInJsonBody("/Name", "Wipe corresponding node"); |
| nlohmann::json parameters = { |
| {{"DataType", "Boolean"}, {"Name", "WipeHostHss"}, {"Required", true}}, |
| {{"DataType", "Boolean"}, |
| {"Name", "WipeBmcNssAndWipeDioriteNss"}, |
| {"Required", true}}, |
| {{"DataType", "Boolean"}, {"Name", "WipeBmcRwfs"}, {"Required", true}}}; |
| resp.SetKeyInJsonBody("/Parameters", parameters); |
| } |
| |
| absl::Status WipeHostHss(const std::filesystem::path& root) { |
| std::error_code ec; |
| std::filesystem::path config_path = root / "usr/share/binaryblob/config.json"; |
| if (!std::filesystem::exists(config_path, ec)) { |
| // If the config file does not exist, this means BMC does not manage the |
| // host HSS. |
| return absl::InternalError("Host HSS config file does not exist."); |
| } |
| std::ifstream config_file(config_path); |
| nlohmann::json config = nlohmann::json::parse(config_file, nullptr, false); |
| if (config.is_discarded()) { |
| return absl::InternalError("Failed to parse host HSS config"); |
| } |
| |
| bool eeprom_written = false; |
| for (const auto& entry : config) { |
| const std::string* sys_path = |
| milotic::authz::GetValueAsString(entry, "sysFilePath"); |
| const uint64_t* offset = |
| milotic::authz::GetValueAsUint(entry, "offsetBytes"); |
| const uint64_t* size = |
| milotic::authz::GetValueAsUint(entry, "maxSizeBytes"); |
| |
| if (sys_path == nullptr || offset == nullptr || size == nullptr) { |
| return absl::InternalError("Invalid Host HSS config entry"); |
| } |
| |
| std::fstream eeprom(*sys_path, |
| std::ios::binary | std::ios::in | std::ios::out); |
| if (eeprom.is_open()) { |
| eeprom.seekp(static_cast<std::streamoff>(*offset)); |
| std::vector<char> ones(*size, 0xff); |
| if (eeprom.write(ones.data(), static_cast<std::streamsize>(*size))) { |
| eeprom_written = true; |
| } |
| } |
| } |
| |
| if (!eeprom_written) { |
| return absl::InternalError("Failed to write to any Host HSS EEPROM"); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status WipeBmcNssAndWipeDioriteNss(const std::filesystem::path& root) { |
| std::error_code ec; |
| // Ignore the return code as long as error_code is not set. |
| std::filesystem::remove(root / "var/google/bmc_nss", ec); |
| if (ec) { |
| return absl::InternalError( |
| absl::StrCat("Failed to remove NSS key file: ", ec.message())); |
| } |
| |
| std::filesystem::path key_config_path = |
| root / "usr/share/bmc-crypto/key.json"; |
| if (!std::filesystem::exists(key_config_path, ec)) { |
| return absl::InternalError("NSS key config file does not exist."); |
| } |
| std::ifstream key_config_file(key_config_path); |
| if (key_config_file.is_open()) { |
| nlohmann::json key_config = |
| nlohmann::json::parse(key_config_file, nullptr, false); |
| if (key_config.is_discarded()) { |
| return absl::InternalError("Failed to parse NSS key config."); |
| } |
| |
| const std::string* sys_path = |
| milotic::authz::GetValueAsString(key_config, "sysFilePath"); |
| const uint64_t* offset = |
| milotic::authz::GetValueAsUint(key_config, "offset"); |
| |
| if (sys_path == nullptr || offset == nullptr) { |
| return absl::InternalError("Invalid NSS key config."); |
| } |
| |
| std::fstream eeprom(*sys_path, |
| std::ios::binary | std::ios::in | std::ios::out); |
| if (eeprom.is_open()) { |
| uint64_t raw_size = 0; |
| eeprom.seekg(static_cast<std::streamoff>(*offset)); |
| if (eeprom.read(reinterpret_cast<char*>(&raw_size), sizeof(raw_size))) { |
| // Explicitly convert Little Endian bytes to Host integer |
| uint64_t key_size = le64toh(raw_size); |
| // Cap key_size to a reasonable limit, e.g., 64KB |
| constexpr uint64_t kMaxKeySize = 64 * 1024; |
| if (key_size > kMaxKeySize) { |
| return absl::InternalError("NSS key size is too large."); |
| } |
| // Key size + 8 bytes of size itself |
| uint64_t total_size = key_size + 8; |
| std::vector<char> ones(total_size, 0xff); |
| // Reset write position to the start of the section |
| eeprom.seekp(static_cast<std::streamoff>(*offset)); |
| eeprom.write(ones.data(), static_cast<std::streamsize>(total_size)); |
| } |
| } else { |
| return absl::InternalError("Failed to open NSS key eeprom"); |
| } |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status WipeRwfs(const std::filesystem::path& root) { |
| constexpr std::string_view kWipeRwfsFilePath = "var/google/do-rwfs-purge"; |
| std::filesystem::path file_path = root; |
| file_path /= kWipeRwfsFilePath; |
| |
| return FileManager::WriteToFile("", file_path.c_str()); |
| } |
| |
| void HandlePostManagerActionsGoogleWipe(const RedfishApp& app, |
| const std::string& root_path, |
| const RedfishRequest& req, |
| RedfishResponse& resp) { |
| nlohmann::json json_body = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (json_body.is_discarded()) { |
| resp.SetToBadRequest("Malformed JSON"); |
| return; |
| } |
| |
| const bool* wipe_host_hss_req = |
| milotic::authz::GetValueAsBool(json_body, "WipeHostHss"); |
| const bool* wipe_nss_req = |
| milotic::authz::GetValueAsBool(json_body, "WipeBmcNssAndWipeDioriteNss"); |
| const bool* wipe_rwfs_req = |
| milotic::authz::GetValueAsBool(json_body, "WipeBmcRwfs"); |
| |
| if (wipe_host_hss_req == nullptr || wipe_nss_req == nullptr || |
| wipe_rwfs_req == nullptr) { |
| resp.SetToBadRequest("Missing required parameters"); |
| return; |
| } |
| |
| std::filesystem::path root(root_path); |
| |
| auto process_wipe = [&resp](absl::string_view name, bool requested, |
| const std::function<absl::Status()>& wipe_func) { |
| nlohmann::json message; |
| message["@odata.type"] = "#Message.v1_1_1.Message"; |
| if (!requested) { |
| message["MessageId"] = "GoogleOem.1.0.OperationNotRequested"; |
| message["MessageArgs"] = {name}; |
| message["Message"] = absl::StrCat("Operation Not Requested: ", name, "."); |
| message["Severity"] = "OK"; |
| message["Resolution"] = "None"; |
| } else { |
| absl::Status status = wipe_func(); |
| if (status.ok()) { |
| message["MessageId"] = |
| absl::StrCat("GoogleOem.1.0.", name, "Succeeded"); |
| message["MessageArgs"] = {name}; |
| message["Message"] = |
| absl::StrCat("Successfully completed operation: ", name, "."); |
| message["Severity"] = "OK"; |
| message["Resolution"] = "None"; |
| } else { |
| message["MessageId"] = absl::StrCat("GoogleOem.1.0.", name, "Failed"); |
| message["MessageArgs"] = {name, status.message()}; |
| message["Message"] = |
| absl::StrCat("Failed to complete operation: ", name, |
| ". Reason: ", status.message()); |
| message["Severity"] = "Critical"; |
| message["Resolution"] = "Retry the operation later."; |
| } |
| } |
| resp.AppendToKeyInJsonBody("/@Message.ExtendedInfo", std::move(message)); |
| }; |
| |
| resp.SetKeyInJsonBody("/@odata.id", |
| "/redfish/v1/Managers/bmc/Actions/Oem/Google.Wipe"); |
| resp.SetKeyInJsonBody("/@odata.type", "#GoogleOem.v1_0_0.WipeResponse"); |
| resp.SetKeyInJsonBody("/Name", "Wipe Action Results"); |
| resp.SetKeyInJsonBody( |
| "/Message", "Wipe operations results. See ExtendedInfo for details."); |
| |
| process_wipe("WipeHostHss", *wipe_host_hss_req, |
| [&root]() { return WipeHostHss(root); }); |
| process_wipe("WipeBmcNssAndWipeDioriteNss", *wipe_nss_req, |
| [&root]() { return WipeBmcNssAndWipeDioriteNss(root); }); |
| process_wipe("WipeBmcRwfs", *wipe_rwfs_req, |
| [&root]() { return WipeRwfs(root); }); |
| } |
| |
| absl::StatusOr<nlohmann::json> GetManagerOem(const RedfishApp& app) { |
| nlohmann::json oem = nlohmann::json::parse(R"json( |
| { |
| "@odata.type": "#OemManager.Oem", |
| "@odata.id": "/redfish/v1/Managers/bmc#/Oem", |
| "OpenBmc": { |
| "@odata.type": "#OemManager.OpenBmc", |
| "@odata.id": "/redfish/v1/Managers/bmc#/Oem/OpenBmc", |
| "Certificates": { |
| "@odata.id": "/redfish/v1/Managers/bmc/Truststore/Certificates" |
| }, |
| "Fan": { |
| "@odata.id": "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan", |
| "@odata.type": "#OemManager.Fan", |
| "FanControllers": { |
| "@odata.id": "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanControllers", |
| "@odata.type": "#OemManager.FanControllers" |
| }, |
| "FanZones": { |
| "@odata.id": "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones", |
| "@odata.type": "#OemManager.FanZones" |
| }, |
| "PidControllers": { |
| "@odata.id": "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers", |
| "@odata.type": "#OemManager.PidControllers" |
| }, |
| "StepwiseControllers": { |
| "@odata.id": "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers", |
| "@odata.type": "#OemManager.StepwiseControllers" |
| } |
| } |
| }, |
| "Google": { |
| "BootTime": { |
| "@odata.id": "/redfish/v1/Managers/bmc/Oem/Google/BootTime" |
| } |
| } |
| } |
| )json"); |
| |
| auto fan_info = app.GetStore()->GetAllFanPidControllers(); |
| if (fan_info.ok()) { |
| for (const thermal::FanPidController* fan : *fan_info) { |
| const std::string fan_id = fan->GetId(); |
| nlohmann::json fan_json = fan->ToJson(); |
| fan_json["@odata.id"] = absl::StrCat( |
| "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanControllers/", fan_id); |
| fan_json["@odata.type"] = "#OemManager.FanController"; |
| fan_json["Zones"] = nlohmann::json::array(); |
| nlohmann::json zone; |
| zone["@odata.id"] = absl::StrCat( |
| "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Zone_", |
| fan->GetZoneManager()->GetId()); |
| fan_json["Zones"].push_back(zone); |
| oem["OpenBmc"]["Fan"]["FanControllers"][fan_id] = fan_json; |
| } |
| } |
| |
| auto pid_controllers = app.GetStore()->GetAllPidControllers(); |
| if (pid_controllers.ok()) { |
| for (const thermal::PidController* pid_controller : *pid_controllers) { |
| const std::string fan_id = pid_controller->GetId(); |
| nlohmann::json pid_ctlr_json = pid_controller->ToJson(); |
| pid_ctlr_json["@odata.id"] = absl::StrCat( |
| "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers/", fan_id); |
| pid_ctlr_json["@odata.type"] = "#OemManager.PidController"; |
| oem["OpenBmc"]["Fan"]["PidControllers"][fan_id] = pid_ctlr_json; |
| } |
| } |
| |
| ECCLESIA_ASSIGN_OR_RETURN(auto zones, app.GetStore()->GetAllThermalZones()); |
| std::sort(zones.begin(), zones.end(), |
| [](const thermal::ZoneManager* a, const thermal::ZoneManager* b) { |
| return a->GetId() < b->GetId(); |
| }); |
| nlohmann::json profiles = nlohmann::json::array(); |
| for (const milotic_tlbmc::thermal::ZoneManager* zone : zones) { |
| std::string name = absl::StrCat("Zone_", zone->GetId()); |
| nlohmann::json zone_json = zone->ToJson(); |
| zone_json["@odata.id"] = absl::StrCat( |
| "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/", name); |
| zone_json["@odata.type"] = "#OemManager.FanZone"; |
| oem["OpenBmc"]["Fan"]["FanZones"][name] = zone_json; |
| profiles.push_back(std::move(name)); |
| } |
| oem["OpenBmc"]["Fan"]["Profile@Redfish.AllowableValues"] = profiles; |
| |
| oem["Google"]["PsiMetrics"]["@odata.id"] = |
| "/redfish/v1/Managers/bmc/Oem/Google/PsiMetrics"; |
| |
| return oem; |
| } |
| |
| absl::StatusOr<nlohmann::json> GetManagerLinks(const RedfishApp& app) { |
| nlohmann::json links; |
| absl::StatusOr<const Fru*> manager_fru = app.GetStore()->GetFru("bmc"); |
| if (!manager_fru.ok()) { |
| return manager_fru.status(); |
| } |
| const ManagerInfo& manager_info = (*manager_fru)->data().manager_info(); |
| |
| links["ManagerForChassis"] = nlohmann::json::array(); |
| nlohmann::json manager_for_chassis_json; |
| manager_for_chassis_json["@odata.id"] = |
| absl::StrCat("/redfish/v1/Chassis/", manager_info.manager_for_chassis()); |
| links["ManagerForChassis"].push_back(std::move(manager_for_chassis_json)); |
| links["ManagerForChassis@odata.count"] = 1; |
| |
| links["ManagerForServers"] = nlohmann::json::array(); |
| for (const auto& manager_for_server : manager_info.manager_for_servers()) { |
| nlohmann::json manager_for_server_json; |
| manager_for_server_json["@odata.id"] = |
| absl::StrCat("/redfish/v1/Systems/", manager_for_server); |
| links["ManagerForServers"].push_back(std::move(manager_for_server_json)); |
| } |
| links["ManagerForServers@odata.count"] = |
| manager_info.manager_for_servers_size(); |
| |
| nlohmann::json manager_in_chassis_json; |
| manager_in_chassis_json["@odata.id"] = |
| absl::StrCat("/redfish/v1/Chassis/", manager_info.manager_in_chassis()); |
| links["ManagerInChassis"] = manager_in_chassis_json; |
| |
| if (!GetTlbmcConfig().install_module().enabled()) { |
| links["ActiveSoftwareImage"]["@odata.id"] = |
| absl::StrCat("/redfish/v1/UpdateService/FirmwareInventory/", |
| app.GetStore()->GetBmcFirmwareVersionId()); |
| nlohmann::json software_images; |
| software_images["@odata.id"] = |
| absl::StrCat("/redfish/v1/UpdateService/FirmwareInventory/", |
| app.GetStore()->GetBmcFirmwareVersionId()); |
| links["SoftwareImages"] = {software_images}; |
| links["SoftwareImages@odata.count"] = 1; |
| } |
| |
| return links; |
| } |
| |
| void HandleGetManagersBmc(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp) { |
| absl::StatusOr<nlohmann::json> links = GetManagerLinks(app); |
| absl::StatusOr<nlohmann::json> oem = GetManagerOem(app); |
| absl::StatusOr<const TopologyConfigNode*> topology_config_node = |
| app.GetStore()->GetFruTopology("bmc"); |
| if (!links.ok()) { |
| resp.SetToAbslStatus(links.status()); |
| return; |
| } |
| if (!oem.ok()) { |
| resp.SetToAbslStatus(oem.status()); |
| return; |
| } |
| if (!topology_config_node.ok()) { |
| resp.SetToAbslStatus(topology_config_node.status()); |
| return; |
| } |
| |
| resp.SetKeyInJsonBody("/Links", std::move(*links)); |
| resp.SetKeyInJsonBody("/Oem", std::move(*oem)); |
| |
| nlohmann::json location; |
| StableId stable_id = GetStableId(**topology_config_node); |
| location["PartLocation"]["ServiceLabel"] = stable_id.service_label(); |
| // location["PartLocation"]["LocationType"] = stable_id.location_type(); |
| // Currently the location type will always be Embedded |
| location["PartLocation"]["LocationType"] = "Embedded"; |
| location["PartLocationContext"] = stable_id.part_location_context(); |
| resp.SetKeyInJsonBody("/Location", std::move(location)); |
| |
| resp.SetKeyInJsonBody("/@odata.id", "/redfish/v1/Managers/bmc"); |
| resp.SetKeyInJsonBody("/@odata.type", "#Manager.v1_16_0.Manager"); |
| resp.SetKeyInJsonBody("/Id", "bmc"); |
| resp.SetKeyInJsonBody("/Name", "OpenBmc Manager"); |
| resp.SetKeyInJsonBody("/Description", "Baseboard Management Controller"); |
| resp.SetKeyInJsonBody("/PowerState", "On"); |
| resp.SetKeyInJsonBody("/ManagerType", "BMC"); |
| resp.SetKeyInJsonBody("/Model", "OpenBmc"); |
| resp.SetKeyInJsonBody("/Status/Health", "OK"); |
| resp.SetKeyInJsonBody("/Status/State", "Enabled"); |
| |
| resp.SetKeyInJsonBody("/FirmwareVersion", |
| app.GetStore()->GetBmcFirmwareVersion()); |
| resp.SetKeyInJsonBody("/UUID", app.GetStore()->GetBmcUuid()); |
| |
| resp.SetKeyInJsonBody( |
| "/DateTime", |
| absl::FormatTime(absl::RFC3339_sec, app.GetStore()->GetCurrentTime(), |
| absl::UTCTimeZone())); |
| resp.SetKeyInJsonBody("/DateTimeLocalOffset", "+00:00"); |
| |
| resp.SetKeyInJsonBody( |
| "/LastResetTime", |
| absl::FormatTime(absl::RFC3339_sec, app.GetStore()->GetLastResetTime(), |
| absl::UTCTimeZone())); |
| |
| resp.SetKeyInJsonBody("/LogServices/@odata.id", |
| "/redfish/v1/Managers/bmc/LogServices"); |
| resp.SetKeyInJsonBody("/NetworkProtocol/@odata.id", |
| "/redfish/v1/Managers/bmc/NetworkProtocol"); |
| resp.SetKeyInJsonBody("/EthernetInterfaces/@odata.id", |
| "/redfish/v1/Managers/bmc/EthernetInterfaces"); |
| resp.SetKeyInJsonBody("/DedicatedNetworkPorts/@odata.id", |
| "/redfish/v1/Managers/bmc/DedicatedNetworkPorts"); |
| resp.SetKeyInJsonBody("/ManagerDiagnosticData/@odata.id", |
| "/redfish/v1/Managers/bmc/ManagerDiagnosticData"); |
| resp.SetKeyInJsonBody("/Certificates/@odata.id", |
| "/redfish/v1/Managers/bmc/Certificates"); |
| |
| // Manager.Reset can be many values, OpenBmc only supports BMC reboot. |
| // ResetToDefaults (Factory Reset) has values like |
| // PreserveNetworkAndUsers and PreserveNetwork that aren't supported on |
| // OpenBmc. |
| nlohmann::json actions = nlohmann::json::parse(R"json( |
| { |
| "#Manager.Reset": { |
| "target": "/redfish/v1/Managers/bmc/Actions/Manager.Reset", |
| "@Redfish.ActionInfo": "/redfish/v1/Managers/bmc/ResetActionInfo" |
| }, |
| "#Manager.ResetToDefaults": { |
| "target": "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults", |
| "ResetType@Redfish.AllowableValues": ["ResetAll"] |
| }, |
| "#Manager.FanMode.Change": { |
| "target": "/redfish/v1/Managers/bmc/Actions/Manager.FanMode.Change", |
| "@Redfish.ActionInfo": "/redfish/v1/Managers/bmc/FanMode.Change.ActionInfo" |
| }, |
| "Oem": { |
| "#Google.Wipe": { |
| "target": "/redfish/v1/Managers/bmc/Actions/Oem/Google.Wipe", |
| "@Redfish.ActionInfo": "/redfish/v1/Managers/bmc/Oem/Google/WipeActionInfo" |
| }, |
| "#Google.InstallConfiguration": { |
| "target": "/redfish/v1/Managers/bmc/Actions/Oem/Google.InstallConfiguration", |
| "@Redfish.ActionInfo": "/redfish/v1/Managers/bmc/Oem/Google/InstallConfigurationActionInfo" |
| } |
| } |
| } |
| )json"); |
| resp.SetKeyInJsonBody("/Actions", std::move(actions)); |
| |
| nlohmann::json serial_console = nlohmann::json::parse(R"json( |
| { |
| "ServiceEnabled": true, |
| "MaxConcurrentSessions": 15, |
| "ConnectTypesSupported": ["IPMI", "SSH"] |
| } |
| )json"); |
| resp.SetKeyInJsonBody("/SerialConsole", std::move(serial_console)); |
| |
| nlohmann::json graphical_console = nlohmann::json::parse(R"json( |
| { |
| "ServiceEnabled": true, |
| "MaxConcurrentSessions": 4, |
| "ConnectTypesSupported": ["KVMIP"] |
| } |
| )json"); |
| resp.SetKeyInJsonBody("/GraphicalConsole", std::move(graphical_console)); |
| } |
| |
| absl::StatusOr<thermal::PidController::PidControllerCoefficients> |
| ParsePidControllerCoefficients(nlohmann::json& json, |
| absl::string_view pid_controller_id) { |
| thermal::PidController::PidControllerCoefficients pid_controller_coefficients; |
| |
| pid_controller_coefficients.coeff_proportional = |
| ProcessThermalCoefficient(json, "PCoefficient"); |
| pid_controller_coefficients.coeff_integral = |
| ProcessThermalCoefficient(json, "ICoefficient"); |
| pid_controller_coefficients.coeff_derivative = |
| ProcessThermalCoefficient(json, "DCoefficient"); |
| pid_controller_coefficients.feed_forward_offset = |
| ProcessThermalCoefficient(json, "FFOffCoefficient"); |
| pid_controller_coefficients.feed_forward_gain = |
| ProcessThermalCoefficient(json, "FFGainCoefficient"); |
| pid_controller_coefficients.setpoint = |
| ProcessThermalCoefficient(json, "SetPoint"); |
| |
| if (!json.empty()) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Unsupported PATCH attribute attempted for properties of " |
| "PidController ", |
| pid_controller_id, ": ", json.dump(2))); |
| } |
| |
| return pid_controller_coefficients; |
| } |
| |
| absl::StatusOr<thermal::FanPidController::FanPidControllerCoefficients> |
| ParseFanPidControllerCoefficients(nlohmann::json& json, |
| absl::string_view fan_pid_controller_id) { |
| thermal::FanPidController::FanPidControllerCoefficients |
| fan_pid_controller_coefficients; |
| |
| fan_pid_controller_coefficients.coeff_proportional = |
| ProcessThermalCoefficient(json, "PCoefficient"); |
| fan_pid_controller_coefficients.coeff_integral = |
| ProcessThermalCoefficient(json, "ICoefficient"); |
| fan_pid_controller_coefficients.coeff_derivative = |
| ProcessThermalCoefficient(json, "DCoefficient"); |
| fan_pid_controller_coefficients.feed_forward_offset = |
| ProcessThermalCoefficient(json, "FFOffCoefficient"); |
| fan_pid_controller_coefficients.feed_forward_gain = |
| ProcessThermalCoefficient(json, "FFGainCoefficient"); |
| |
| if (!json.empty()) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Unsupported PATCH attribute attempted for properties of " |
| "FanPidController ", |
| fan_pid_controller_id, ": ", json.dump(2))); |
| } |
| |
| return fan_pid_controller_coefficients; |
| } |
| |
| void HandlePatchManagersBmc(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp) { |
| nlohmann::json json_body = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (json_body.is_discarded()) { |
| resp.SetToBadRequest("Invalid JSON body"); |
| return; |
| } |
| |
| nlohmann::json::iterator oem_it = json_body.find("Oem"); |
| if (oem_it == json_body.end()) { |
| resp.SetToNoContent(); |
| return; |
| } |
| |
| nlohmann::json::iterator openbmc_it = oem_it->find("OpenBmc"); |
| if (openbmc_it == oem_it->end()) { |
| resp.SetToNoContent(); |
| return; |
| } |
| |
| nlohmann::json::iterator fan_it = openbmc_it->find("Fan"); |
| if (fan_it == openbmc_it->end()) { |
| resp.SetToNoContent(); |
| return; |
| } |
| |
| nlohmann::json::iterator fan_controllers_it = fan_it->find("FanControllers"); |
| if (fan_controllers_it != fan_it->end()) { |
| for (auto fan_controller_it = fan_controllers_it->begin(); |
| fan_controller_it != fan_controllers_it->end(); ++fan_controller_it) { |
| std::string fan_controller_id = fan_controller_it.key(); |
| nlohmann::json& fan_controller_json = fan_controller_it.value(); |
| auto fan_pid_controller_coeffs = ParseFanPidControllerCoefficients( |
| fan_controller_json, fan_controller_id); |
| if (!fan_pid_controller_coeffs.ok()) { |
| resp.SetToBadRequest(fan_pid_controller_coeffs.status().message()); |
| return; |
| } |
| // Expecting store to finishing PATCHing within 3 seconds |
| absl::Status status = app.GetStore()->SetFanPidControllerCoefficients( |
| fan_controller_id, *fan_pid_controller_coeffs, absl::Seconds(3)); |
| if (!status.ok()) { |
| resp.SetToAbslStatus(status); |
| return; |
| } |
| } |
| } |
| |
| nlohmann::json::iterator pid_controllers_it = fan_it->find("PidControllers"); |
| if (pid_controllers_it != fan_it->end()) { |
| for (auto pid_controller_it = pid_controllers_it->begin(); |
| pid_controller_it != pid_controllers_it->end(); ++pid_controller_it) { |
| std::string pid_controller_id = pid_controller_it.key(); |
| nlohmann::json& pid_controller_json = pid_controller_it.value(); |
| auto pid_controller_coeffs = ParsePidControllerCoefficients( |
| pid_controller_json, pid_controller_id); |
| if (!pid_controller_coeffs.ok()) { |
| resp.SetToBadRequest(pid_controller_coeffs.status().message()); |
| return; |
| } |
| // Expecting store to finishing PATCHing within 3 seconds |
| absl::Status status = app.GetStore()->SetPidControllerCoefficients( |
| pid_controller_id, *pid_controller_coeffs, absl::Seconds(3)); |
| if (!status.ok()) { |
| resp.SetToAbslStatus(status); |
| return; |
| } |
| } |
| } |
| |
| resp.SetToNoContent(); |
| } |
| |
| void RegisterRoutes(RedfishApp& app) { |
| TLBMC_ROUTE(app, "/redfish/v1/Managers/bmc/FanMode.Change.ActionInfo/") |
| .methods(boost::beast::http::verb::get)(absl::bind_front( |
| HandleGetManagerFanModeChangeActionInfo, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Managers/bmc/Actions/Manager.FanMode.Change/") |
| .methods(boost::beast::http::verb::post)(absl::bind_front( |
| HandlePostManagerActionsManagerFanModeChange, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Managers/bmc/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleGetManagersBmc, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Managers/bmc/") |
| .methods(boost::beast::http::verb::patch)( |
| absl::bind_front(HandlePatchManagersBmc, std::cref(app))); |
| } |
| |
| void RegisterManagerRecoveryRoutes(RedfishApp& app, |
| const std::string& root_path) { |
| TLBMC_ROUTE(app, "/redfish/v1/Managers/bmc/Oem/Google/WipeActionInfo/") |
| .methods(boost::beast::http::verb::get)(absl::bind_front( |
| HandleGetManagerGoogleWipeActionInfo, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Managers/bmc/Actions/Oem/Google.Wipe/") |
| .methods(boost::beast::http::verb::post)(absl::bind_front( |
| HandlePostManagerActionsGoogleWipe, std::cref(app), root_path)); |
| } |
| |
| void HandleGetManagerInstallConfigurationActionInfo(const RedfishApp& app, |
| const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody( |
| "/@odata.id", |
| "/redfish/v1/Managers/bmc/Oem/Google/InstallConfigurationActionInfo"); |
| resp.SetKeyInJsonBody("/@odata.type", "#ActionInfo.v1_1_2.ActionInfo"); |
| resp.SetKeyInJsonBody("/Id", "InstallConfigurationActionInfo"); |
| resp.SetKeyInJsonBody("/Name", "Action info of the InstallConfiguration RPC"); |
| nlohmann::json parameters = { |
| {{"DataType", "String"}, |
| {"Name", "Type"}, |
| {"Required", true}, |
| {"AllowableValues", {"Uhmm", "Dag", "Network"}}}, |
| {{"DataType", "String"}, |
| {"Name", "Base64EncodedBinary"}, |
| {"Required", true}}}; |
| resp.SetKeyInJsonBody("/Parameters", parameters); |
| } |
| |
| void HandlePostManagerActionsInstallConfiguration(const RedfishApp& app, |
| const std::string& root_path, |
| const RedfishRequest& req, |
| RedfishResponse& resp) { |
| nlohmann::json json_body = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (json_body.is_discarded()) { |
| resp.SetToBadRequest("Malformed JSON"); |
| return; |
| } |
| |
| const std::string* type = milotic::authz::GetValueAsString(json_body, "Type"); |
| const std::string* base64_encoded_binary = |
| milotic::authz::GetValueAsString(json_body, "Base64EncodedBinary"); |
| |
| if (type == nullptr || base64_encoded_binary == nullptr) { |
| resp.SetToBadRequest("Missing required parameters"); |
| return; |
| } |
| |
| if (*type != "Uhmm") { |
| resp.SetToBadRequest("Invalid Type entry or not implemented yet"); |
| return; |
| } |
| |
| std::string decoded_binary; |
| if (!absl::Base64Unescape(*base64_encoded_binary, &decoded_binary)) { |
| resp.SetToBadRequest("Malformed Base64EncodedBinary"); |
| return; |
| } |
| |
| uhmm::OfflineData offline_data; |
| if (!offline_data.ParseFromString(decoded_binary)) { |
| resp.SetToBadRequest("Failed to parse binary as uhmm::OfflineData"); |
| return; |
| } |
| |
| std::string txtpb_content; |
| if (!::google::protobuf::TextFormat::PrintToString(offline_data, &txtpb_content)) { |
| resp.SetToInternalError("Failed to serialize uhmm::OfflineData to text"); |
| return; |
| } |
| |
| std::filesystem::path file_path = |
| std::filesystem::path(root_path) / "var/google/uhmm/data.txtpb"; |
| absl::Status status = |
| FileManager::WriteToFile(txtpb_content, file_path.c_str()); |
| if (!status.ok()) { |
| resp.SetToAbslStatus(status); |
| return; |
| } |
| |
| // TODO(nanzhou): Trigger asynchronous background task |
| resp.SetToNoContent(); |
| } |
| |
| void RegisterManagerInstallConfigurationRoutes(RedfishApp& app, |
| const std::string& root_path) { |
| TLBMC_ROUTE( |
| app, |
| "/redfish/v1/Managers/bmc/Oem/Google/InstallConfigurationActionInfo/") |
| .methods(boost::beast::http::verb::get)(absl::bind_front( |
| HandleGetManagerInstallConfigurationActionInfo, std::cref(app))); |
| TLBMC_ROUTE( |
| app, "/redfish/v1/Managers/bmc/Actions/Oem/Google.InstallConfiguration/") |
| .methods(boost::beast::http::verb::post)( |
| absl::bind_front(HandlePostManagerActionsInstallConfiguration, |
| std::cref(app), root_path)); |
| } |
| |
| } // namespace milotic_tlbmc::managers |