blob: 7825c2922440e566bf6d8d58fe0597bb4dddf872 [file]
#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