| #ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MANAGERS_H_ | |
| #define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MANAGERS_H_ | |
| /* | |
| // Copyright (c) 2018 Intel Corporation | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| */ | |
| #include <algorithm> | |
| #include <array> | |
| #include <cctype> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <functional> | |
| #include <map> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <variant> | |
| #include <vector> | |
| #include "absl/functional/any_invocable.h" // IWYU pragma: keep | |
| #include "absl/strings/ascii.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "boost/container/flat_set.hpp" // NOLINT | |
| #include "boost/system/error_code.hpp" // IWYU pragma: keep // NOLINT | |
| #include "boost/system/linux_error.hpp" // NOLINT | |
| #include "app.hpp" | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "persistent_data.hpp" | |
| #include "error_messages.hpp" | |
| #include "query.hpp" | |
| #include "registries/privilege_registry.hpp" | |
| #include "boottime.hpp" | |
| #include "chassis_utils.hpp" | |
| #include "collection.hpp" | |
| #include "dbus_utils.hpp" | |
| #include "hex_utils.hpp" | |
| #include "json_utils.hpp" | |
| #include "location_utils.hpp" | |
| #include "sw_utils.hpp" | |
| #include "system_utils.hpp" | |
| #include "systemd_utils.hpp" | |
| #include "time_utils.hpp" | |
| #include "chassis.hpp" | |
| #include "health.hpp" | |
| #include "redfish_util.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "sdbusplus/unpack_properties.hpp" | |
| namespace redfish { | |
| /** | |
| * Function reboots the BMC. | |
| * | |
| * @param[in] asyncResp - Shared pointer for completing asynchronous calls | |
| */ | |
| inline void doBMCGracefulRestart( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| const std::string processName = "xyz.openbmc_project.State.BMC"; | |
| const std::string objectPath = "/xyz/openbmc_project/state/bmc0"; | |
| const std::string interfaceName = "xyz.openbmc_project.State.BMC"; | |
| const std::string propertyValue = | |
| "xyz.openbmc_project.State.BMC.Transition.Reboot"; | |
| const std::string destProperty = "RequestedBMCTransition"; | |
| // Create the D-Bus variant for D-Bus call. | |
| dbus::utility::DbusVariantType dbusPropertyValue(propertyValue); | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void( | |
| const boost::system::error_code& // NOLINT(misc-include-cleaner) | |
| )>([asyncResp](const boost::system::error_code& ec) { | |
| // Use "Set" method to set the property value. | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| messages::success(asyncResp->res); | |
| }), | |
| processName, objectPath, "org.freedesktop.DBus.Properties", "Set", | |
| interfaceName, destProperty, std::move(dbusPropertyValue)); | |
| } | |
| inline void doBMCForceRestart( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| const std::string processName = "xyz.openbmc_project.State.BMC"; | |
| const std::string objectPath = "/xyz/openbmc_project/state/bmc0"; | |
| const std::string interfaceName = "xyz.openbmc_project.State.BMC"; | |
| const std::string propertyValue = | |
| "xyz.openbmc_project.State.BMC.Transition.HardReboot"; | |
| const std::string destProperty = "RequestedBMCTransition"; | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void( | |
| const boost::system::error_code& // NOLINT(misc-include-cleaner) | |
| )>([asyncResp](const boost::system::error_code& ec) { | |
| // Use "Set" method to set the property value. | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| messages::success(asyncResp->res); | |
| }), | |
| processName, objectPath, "org.freedesktop.DBus.Properties", "Set", | |
| interfaceName, destProperty, | |
| dbus::utility::DbusVariantType{propertyValue}); | |
| } | |
| inline void handlePostManagerResetAction( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Post Manager Reset."; | |
| std::string resetType; | |
| if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", resetType)) { | |
| return; | |
| } | |
| if (resetType == "GracefulRestart") { | |
| BMCWEB_LOG_DEBUG << "Proceeding with " << resetType; | |
| doBMCGracefulRestart(asyncResp); | |
| return; | |
| } | |
| if (resetType == "ForceRestart") { | |
| BMCWEB_LOG_DEBUG << "Proceeding with " << resetType; | |
| doBMCForceRestart(asyncResp); | |
| return; | |
| } | |
| if (resetType == "TrayPowerCycle") { | |
| BMCWEB_LOG_DEBUG << "Proceeding with " << resetType; | |
| // We don't support delay powercycle for now. If S4 wants to use it in | |
| // the future, we will add it. | |
| std::optional<int> delayTimeSecs = std::nullopt; | |
| doChassisPowerCycle(asyncResp, delayTimeSecs); | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: " << resetType; | |
| messages::actionParameterNotSupported(asyncResp->res, resetType, "ResetType"); | |
| } | |
| /** | |
| * ManagerResetAction class supports the POST method for the Reset (reboot) | |
| * action. | |
| */ | |
| inline void requestRoutesManagerResetAction(App& app) { | |
| /** | |
| * Function handles POST method request. | |
| * Analyzes POST body before sending Reset (Reboot) request data to D-Bus. | |
| * OpenBMC supports ResetType "GracefulRestart" and "ForceRestart". | |
| */ | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/") | |
| .privileges(redfish::privileges::postManager) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handlePostManagerResetAction, std::ref(app))); | |
| } | |
| inline void handlePostManagerResetToDefaultsAction( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Post ResetToDefaults."; | |
| std::string resetType; | |
| if (!json_util::readJsonAction(req, asyncResp->res, "ResetToDefaultsType", | |
| resetType)) { | |
| BMCWEB_LOG_DEBUG << "Missing property ResetToDefaultsType."; | |
| messages::actionParameterMissing(asyncResp->res, "ResetToDefaults", | |
| "ResetToDefaultsType"); | |
| return; | |
| } | |
| if (resetType != "ResetAll") { | |
| BMCWEB_LOG_DEBUG << "Invalid property value for ResetToDefaultsType: " | |
| << resetType; | |
| messages::actionParameterNotSupported(asyncResp->res, resetType, | |
| "ResetToDefaultsType"); | |
| return; | |
| } | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [asyncResp](const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "Failed to ResetToDefaults: " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // Factory Reset doesn't actually happen until a reboot | |
| // Can't erase what the BMC is running on | |
| doBMCGracefulRestart(asyncResp); | |
| }), | |
| "xyz.openbmc_project.Software.BMC.Updater", | |
| "/xyz/openbmc_project/software", | |
| "xyz.openbmc_project.Common.FactoryReset", "Reset"); | |
| } | |
| /** | |
| * ManagerResetToDefaultsAction class supports POST method for factory reset | |
| * action. | |
| */ | |
| inline void requestRoutesManagerResetToDefaultsAction(App& app) { | |
| /** | |
| * Function handles ResetToDefaults POST method request. | |
| * | |
| * Analyzes POST body message and factory resets BMC by calling | |
| * BMC code updater factory reset followed by a BMC reboot. | |
| * | |
| * BMC code updater factory reset wipes the whole BMC read-write | |
| * filesystem which includes things like the network settings. | |
| * | |
| * OpenBMC only supports ResetToDefaultsType "ResetAll". | |
| */ | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults/") | |
| .privileges(redfish::privileges::postManager) | |
| .methods(boost::beast::http::verb::post)(std::bind_front( | |
| handlePostManagerResetToDefaultsAction, std::ref(app))); | |
| } | |
| inline void handleManagerResetActionInfo( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_1_2.ActionInfo"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/ResetActionInfo"; | |
| asyncResp->res.jsonValue["Name"] = "Reset Action Info"; | |
| asyncResp->res.jsonValue["Id"] = "ResetActionInfo"; | |
| nlohmann::json::object_t parameter; | |
| parameter["Name"] = "ResetType"; | |
| parameter["Required"] = true; | |
| parameter["DataType"] = "String"; | |
| nlohmann::json::array_t allowableValues; | |
| allowableValues.emplace_back("GracefulRestart"); | |
| allowableValues.emplace_back("ForceRestart"); | |
| allowableValues.emplace_back("TrayPowerCycle"); | |
| parameter["AllowableValues"] = std::move(allowableValues); | |
| nlohmann::json::array_t parameters; | |
| parameters.emplace_back(std::move(parameter)); | |
| asyncResp->res.jsonValue["Parameters"] = std::move(parameters); | |
| } | |
| /** | |
| * ManagerResetActionInfo derived class for delivering Manager | |
| * ResetType AllowableValues using ResetInfo schema. | |
| */ | |
| inline void requestRoutesManagerResetActionInfo(App& app) { | |
| /** | |
| * Functions triggers appropriate requests on DBus | |
| */ | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/ResetActionInfo/") | |
| .privileges(redfish::privileges::getActionInfo) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleManagerResetActionInfo, std::ref(app))); | |
| } | |
| inline void handlePostManagerFanModeChangeAction( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| std::string fanMode; | |
| if (!json_util::readJsonAction(req, asyncResp->res, "FanMode", fanMode)) { | |
| messages::actionParameterUnknown(asyncResp->res, "FanMode", ""); | |
| return; | |
| } | |
| bool munualMode = false; | |
| if (fanMode == "Manual") { | |
| munualMode = true; | |
| } else if (fanMode == "Auto") { | |
| munualMode = false; | |
| } else { | |
| messages::actionParameterValueFormatError(asyncResp->res, "Manual", "Auto", | |
| ""); | |
| return; | |
| } | |
| std::array<std::string_view, 1> interfaces{ | |
| "xyz.openbmc_project.Control.Mode"}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/xyz/openbmc_project/settings/fanctrl", 1, interfaces, requestContext, | |
| [asyncResp{asyncResp}, munualMode]( | |
| const boost::system::error_code& ec, | |
| const std::vector<std::pair< | |
| std::string, | |
| std::vector<std::pair<std::string, std::vector<std::string>>>>>& | |
| subtree) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "fanctrl GetSubTree failed " | |
| << " ec = ( " << ec << " )\n"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| for (const auto& object : subtree) { | |
| auto iter = object.first.rfind('/'); | |
| if ((iter != std::string::npos) && (iter < object.first.size())) { | |
| std::string objName = object.first.substr(iter + 1); | |
| managedStore::GetManagedObjectStore() | |
| ->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [asyncResp, objName, | |
| munualMode](const boost::system::error_code& ec2) { | |
| if (ec2) { | |
| BMCWEB_LOG_ERROR << "Updated the Mode failed " | |
| << objName << ": " << munualMode | |
| << " ec = ( " << ec2 << " )\n"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| }), | |
| "xyz.openbmc_project.State.FanCtrl", | |
| "/xyz/openbmc_project/settings/fanctrl/" + objName, | |
| "org.freedesktop.DBus.Properties", "Set", | |
| "xyz.openbmc_project.Control.Mode", "Manual", | |
| dbus::utility::DbusVariantType{munualMode}); | |
| BMCWEB_LOG_DEBUG << "Updated the Mode success " << objName << ": " | |
| << munualMode; | |
| } | |
| } | |
| messages::success(asyncResp->res); | |
| }); | |
| } | |
| /** | |
| * ManagerFanModeChangeAction class supports POST method for Fan Mode Change | |
| * action. | |
| */ | |
| inline void requestRoutesManagerFanModeChangeAction(App& app) { | |
| /** | |
| * Function handles FanMode POST method request. | |
| * | |
| * OpenBMC only supports FanMode "Manual" or "Auto". | |
| */ | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Actions/Manager.FanMode.Change/") | |
| .privileges(redfish::privileges::postManager) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handlePostManagerFanModeChangeAction, std::ref(app))); | |
| } | |
| inline void handleGetManagerFanModeChangeActionInfo( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue = { | |
| {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, | |
| {"@odata.id", "/redfish/v1/Managers/bmc/FanMode.Change.ActionInfo"}, | |
| {"Name", "FanMode Change Action Info"}, | |
| {"Id", "FanMode.Change.ActionInfo"}, | |
| {"Parameters", | |
| {{{"Name", "FanMode"}, | |
| {"Required", true}, | |
| {"DataType", "String"}, | |
| {"AllowableValues", {"Manual", "Auto"}}}}}}; | |
| } | |
| /** | |
| * ManagerFanModeChangeActionInfo derived class for delivering Manager | |
| * FanMode AllowableValues using FanModeInfo schema. | |
| */ | |
| inline void requestRoutesManagerFanModeChangeActionInfo(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/FanMode.Change.ActionInfo/") | |
| .privileges(redfish::privileges::getActionInfo) | |
| .methods(boost::beast::http::verb::get)(std::bind_front( | |
| handleGetManagerFanModeChangeActionInfo, std::ref(app))); | |
| } | |
| inline void updateZoneLeaderInfo( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& zoneIndex, const std::string& name, | |
| const std::string& leader, | |
| const managedStore::ManagedObjectStoreContext& context) { | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| "xyz.openbmc_project.State.FanCtrl", | |
| crow::utility::urlFromPieces("xyz", "openbmc_project", "settings", | |
| "fanctrl", "zone" + zoneIndex, leader) | |
| .buffer(), | |
| "xyz.openbmc_project.Debug.Pid.ThermalPower", context, | |
| [asyncResp, zoneIndex, name, leader]( | |
| const boost::system::error_code ec, | |
| const ::dbus::utility::DBusPropertiesMap& leaderData) { | |
| if (ec) { | |
| BMCWEB_LOG_WARNING << "Get the Fan Zone Leader failed " << leader | |
| << " ec = ( " << ec << " )\n"; | |
| return; | |
| } | |
| for (const auto& data : leaderData) { | |
| std::string propertyName; | |
| if (data.first == "Leader") { | |
| propertyName = "Sensor"; | |
| } else { | |
| propertyName = data.first; | |
| } | |
| if (const std::string* strPtr = | |
| std::get_if<std::string>(&data.second)) { | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]["FanZones"][name] | |
| ["Leader"][propertyName] = *strPtr; | |
| } else if (const double* doublePtr = | |
| std::get_if<double>(&data.second)) { | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]["FanZones"][name] | |
| ["Leader"][propertyName] = *doublePtr; | |
| } else { | |
| BMCWEB_LOG_WARNING << "Get the Fan Zone Debug info " | |
| << "failed " << leader << ", " << propertyName | |
| << " ec = ( " << ec << " )\n"; | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]["FanZones"][name] | |
| ["Leader"][propertyName] = "n/a"; | |
| return; | |
| } | |
| } | |
| }); | |
| } | |
| static constexpr const char* objectManagerIface = | |
| "org.freedesktop.DBus.ObjectManager"; | |
| static constexpr const char* pidConfigurationIface = | |
| "xyz.openbmc_project.Configuration.Pid"; | |
| static constexpr const char* pidZoneConfigurationIface = | |
| "xyz.openbmc_project.Configuration.Pid.Zone"; | |
| static constexpr const char* stepwiseConfigurationIface = | |
| "xyz.openbmc_project.Configuration.Stepwise"; | |
| static constexpr const char* thermalModeIface = | |
| "xyz.openbmc_project.Control.ThermalMode"; | |
| inline void asyncPopulatePid( | |
| const std::string& connection, const std::string& path, | |
| const std::string& currentProfile, | |
| const std::vector<std::string>& supportedProfiles, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | |
| connection, path, context, | |
| [asyncResp, currentProfile, supportedProfiles, context]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::ManagedObjectType& managedObj) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "asyncPopulatePid: " << ec; | |
| asyncResp->res.jsonValue.clear(); | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| nlohmann::json& configRoot = | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; | |
| nlohmann::json& fans = configRoot["FanControllers"]; | |
| fans["@odata.type"] = "#OemManager.FanControllers"; | |
| fans["@odata.id"] = | |
| "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanControllers"; | |
| nlohmann::json& pids = configRoot["PidControllers"]; | |
| pids["@odata.type"] = "#OemManager.PidControllers"; | |
| pids["@odata.id"] = | |
| "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; | |
| nlohmann::json& stepwise = configRoot["StepwiseControllers"]; | |
| stepwise["@odata.type"] = "#OemManager.StepwiseControllers"; | |
| stepwise["@odata.id"] = | |
| "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers"; | |
| nlohmann::json& zones = configRoot["FanZones"]; | |
| zones["@odata.id"] = | |
| "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; | |
| zones["@odata.type"] = "#OemManager.FanZones"; | |
| configRoot["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; | |
| configRoot["@odata.type"] = "#OemManager.Fan"; | |
| configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles; | |
| if (!currentProfile.empty()) { | |
| configRoot["Profile"] = currentProfile; | |
| } | |
| BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !"; | |
| for (const auto& pathPair : managedObj) { | |
| for (const auto& intfPair : pathPair.second) { | |
| if (intfPair.first != pidConfigurationIface && | |
| intfPair.first != pidZoneConfigurationIface && | |
| intfPair.first != stepwiseConfigurationIface) { | |
| continue; | |
| } | |
| std::string name; | |
| for (const std::pair<std::string, dbus::utility::DbusVariantType>& | |
| propPair : intfPair.second) { | |
| if (propPair.first == "Name") { | |
| const std::string* namePtr = | |
| std::get_if<std::string>(&propPair.second); | |
| if (namePtr == nullptr) { | |
| BMCWEB_LOG_ERROR << "Pid Name Field illegal"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| name = *namePtr; | |
| dbus::utility::escapePathForDbus(name); | |
| } else if (propPair.first == "Profiles") { | |
| const std::vector<std::string>* profiles = | |
| std::get_if<std::vector<std::string>>(&propPair.second); | |
| if (profiles == nullptr) { | |
| BMCWEB_LOG_ERROR << "Pid Profiles Field illegal"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (std::find(profiles->begin(), profiles->end(), | |
| currentProfile) == profiles->end()) { | |
| BMCWEB_LOG_INFO << name | |
| << " not supported in current profile"; | |
| continue; | |
| } | |
| } | |
| } | |
| nlohmann::json* config = nullptr; | |
| const std::string* classPtr = nullptr; | |
| const double* zoneIndexPtr = nullptr; | |
| const double* zoneMinThermalOutputPtr = nullptr; | |
| for (const std::pair<std::string, dbus::utility::DbusVariantType>& | |
| propPair : intfPair.second) { | |
| if (propPair.first == "Class") { | |
| classPtr = std::get_if<std::string>(&propPair.second); | |
| } else if (propPair.first == "ZoneIndex") { | |
| zoneIndexPtr = std::get_if<double>(&propPair.second); | |
| } else if (propPair.first == "MinThermalOutput") { | |
| zoneMinThermalOutputPtr = std::get_if<double>(&propPair.second); | |
| } | |
| } | |
| boost::urls::url url = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Managers", "bmc"); | |
| if (intfPair.first == pidZoneConfigurationIface) { | |
| std::string chassis; | |
| if (dbus::utility::getNthStringFromPath(pathPair.first.str, 5, | |
| chassis)) { | |
| managedStore::GetManagedObjectStore()->getSubTreePaths( | |
| "/xyz/openbmc_project/inventory", 0, | |
| chassis_utils::chassisInterfaces, context, | |
| [asyncResp, name, chassis]( | |
| const boost::system::error_code& ec2, | |
| const dbus::utility::MapperGetSubTreePathsResponse& | |
| chassisList) { | |
| if (ec2) { | |
| BMCWEB_LOG_ERROR | |
| << "asyncPopulatePid: getSubTreePaths error: " | |
| << ec2; | |
| return; | |
| } | |
| for (const std::string& chassisPath : chassisList) { | |
| sdbusplus::message::object_path path(chassisPath); | |
| if (path.filename() == chassis) { | |
| asyncResp->res | |
| .jsonValue["Oem"]["OpenBmc"]["Fan"]["FanZones"] | |
| [name]["Chassis"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", | |
| "Chassis", chassis); | |
| return; | |
| } | |
| } | |
| }); | |
| } | |
| nlohmann::json& zone = zones[name]; | |
| url.set_fragment(("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name) | |
| .to_string()); | |
| zone["@odata.id"] = url; | |
| zone["@odata.type"] = "#OemManager.FanZone"; | |
| config = &zone; | |
| // Add FanMode status | |
| std::string zoneIndex; | |
| if (zoneIndexPtr == nullptr) { | |
| // ZoneIndex field is optional. If not given, attempt | |
| // to synthesize it out of the name string. | |
| // The idea of a zone index is legacy, for IPMI | |
| // OEM commands, and has no purpose under Redfish. | |
| // Long term, need to refactor to address all thermal | |
| // zones by name, not by index number. | |
| // This requires work in bmcweb, entity-manager, | |
| // phosphor-dbus-interfaces, phosphor-pid-control. | |
| auto len = name.size(); | |
| auto pos = len; | |
| while (pos > 0) { | |
| // Take suffix from right to left | |
| --pos; | |
| char digit = name[pos]; | |
| if (digit < '0' || digit > '9') { | |
| // Avoid taking anything non-numeric | |
| ++pos; | |
| break; | |
| } | |
| } | |
| if (pos < len) { | |
| // Extract the numeric suffix out of the zone name | |
| zoneIndex = name.substr(pos); | |
| } | |
| if (zoneIndex.empty()) { | |
| BMCWEB_LOG_ERROR << "Pid Zone Index Field illegal"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| } else { | |
| zoneIndex = std::to_string(static_cast<int>(*(zoneIndexPtr))); | |
| } | |
| if (name.starts_with("Zone") && !zoneIndex.empty()) { | |
| dbus_utils::getProperty<bool>( | |
| "xyz.openbmc_project.State.FanCtrl", | |
| "/xyz/openbmc_project/settings/fanctrl/zone" + zoneIndex, | |
| "xyz.openbmc_project.Control.Mode", "Manual", context, | |
| [asyncResp, name](const boost::system::error_code ec2, | |
| bool manualEnabled) { | |
| if (ec2) { | |
| BMCWEB_LOG_WARNING << "Get the FanMode failed " << name | |
| << " ec = ( " << ec2 << " )\n"; | |
| // If phosphor-pid-control service not running, | |
| // indicate Disabled instead of Auto/Manual, | |
| // but otherwise return successful. | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"] | |
| ["FanZones"][name]["FanMode"] = | |
| "Disabled"; | |
| return; | |
| } | |
| if (manualEnabled) { | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"] | |
| ["FanZones"][name]["FanMode"] = | |
| "Manual"; | |
| } else { | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"] | |
| ["FanZones"][name]["FanMode"] = | |
| "Auto"; | |
| } | |
| }); | |
| dbus_utils::getProperty<bool>( | |
| "xyz.openbmc_project.State.FanCtrl", | |
| "/xyz/openbmc_project/settings/fanctrl/zone" + zoneIndex, | |
| "xyz.openbmc_project.Control.Mode", "FailSafe", context, | |
| [asyncResp, name](const boost::system::error_code ec2, | |
| bool failsafeEnabled) { | |
| if (ec2) { | |
| BMCWEB_LOG_WARNING << "Get the FailSafe failed " << name | |
| << " ec = ( " << ec2 << " )\n"; | |
| // If phosphor-pid-control service not running, | |
| // indicate Disabled instead of Normal/Active, | |
| // but otherwise return successful. | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"] | |
| ["FanZones"][name]["FailSafe"] = | |
| "Disabled"; | |
| return; | |
| } | |
| if (failsafeEnabled) { | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"] | |
| ["FanZones"][name]["FailSafe"] = | |
| "Active"; | |
| } else { | |
| asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"] | |
| ["FanZones"][name]["FailSafe"] = | |
| "Normal"; | |
| } | |
| }); | |
| dbus_utils::getProperty<std::string>( | |
| "xyz.openbmc_project.State.FanCtrl", | |
| "/xyz/openbmc_project/settings/fanctrl/zone" + zoneIndex, | |
| "xyz.openbmc_project.Debug.Pid.Zone", "Leader", context, | |
| [asyncResp, zoneIndex, zoneMinThermalOutputPtr, name, | |
| context, | |
| url{std::move(url)}](const boost::system::error_code ec2, | |
| const std::string& leader) { | |
| if (ec2) { | |
| BMCWEB_LOG_WARNING << "Get the Fan Zone failed " << name | |
| << " ec = ( " << ec2 << " )\n"; | |
| // If phosphor-pid-control service not running, | |
| // supply no further information, | |
| // but otherwise return successful. | |
| return; | |
| } | |
| if (zoneMinThermalOutputPtr == nullptr) { | |
| BMCWEB_LOG_ERROR | |
| << "Pid Zone MinThermalOutput Field illegal"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| boost::urls::url leaderUrl = url; | |
| leaderUrl.set_fragment( | |
| ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name / | |
| "Leader") | |
| .to_string()); | |
| asyncResp->res | |
| .jsonValue["Oem"]["OpenBmc"]["Fan"]["FanZones"][name] | |
| ["Leader"]["@odata.id"] = | |
| std::move(leaderUrl); | |
| if (leader == "Minimum") { | |
| asyncResp->res | |
| .jsonValue["Oem"]["OpenBmc"]["Fan"]["FanZones"] | |
| [name]["Leader"]["Sensor"] = leader; | |
| asyncResp->res | |
| .jsonValue["Oem"]["OpenBmc"]["Fan"]["FanZones"] | |
| [name]["Leader"]["Output"] = | |
| *zoneMinThermalOutputPtr; | |
| } else { | |
| updateZoneLeaderInfo(asyncResp, zoneIndex, name, leader, | |
| context); | |
| } | |
| }); | |
| } | |
| } else if (intfPair.first == stepwiseConfigurationIface) { | |
| if (classPtr == nullptr) { | |
| BMCWEB_LOG_ERROR << "Pid Class Field illegal"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| nlohmann::json& controller = stepwise[name]; | |
| config = &controller; | |
| url.set_fragment( | |
| ("/Oem/OpenBmc/Fan/StepwiseControllers"_json_pointer / name) | |
| .to_string()); | |
| controller["@odata.id"] = std::move(url); | |
| controller["@odata.type"] = "#OemManager.StepwiseController"; | |
| controller["Direction"] = *classPtr; | |
| } else if (intfPair.first == pidConfigurationIface) { | |
| // pid and fans are off the same configuration | |
| if (classPtr == nullptr) { | |
| BMCWEB_LOG_ERROR << "Pid Class Field illegal"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| bool isFan = *classPtr == "fan"; | |
| nlohmann::json& element = isFan ? fans[name] : pids[name]; | |
| config = &element; | |
| if (isFan) { | |
| url.set_fragment( | |
| ("/Oem/OpenBmc/Fan/FanControllers"_json_pointer / name) | |
| .to_string()); | |
| element["@odata.id"] = std::move(url); | |
| element["@odata.type"] = "#OemManager.FanController"; | |
| } else { | |
| url.set_fragment( | |
| ("/Oem/OpenBmc/Fan/PidControllers"_json_pointer / name) | |
| .to_string()); | |
| element["@odata.id"] = std::move(url); | |
| element["@odata.type"] = "#OemManager.PidController"; | |
| } | |
| } else { | |
| BMCWEB_LOG_ERROR << "Unexpected configuration"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // used for making maps out of 2 vectors | |
| const std::vector<double>* keys = nullptr; | |
| const std::vector<double>* values = nullptr; | |
| for (const auto& propertyPair : intfPair.second) { | |
| if (propertyPair.first == "Type" || | |
| propertyPair.first == "Class" || | |
| propertyPair.first == "Name") { | |
| continue; | |
| } | |
| // zones | |
| if (intfPair.first == pidZoneConfigurationIface) { | |
| const double* ptr = std::get_if<double>(&propertyPair.second); | |
| if (ptr == nullptr) { | |
| BMCWEB_LOG_ERROR << "Field Illegal " << propertyPair.first; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| (*config)[propertyPair.first] = *ptr; | |
| } | |
| if (intfPair.first == stepwiseConfigurationIface) { | |
| if (propertyPair.first == "Reading" || | |
| propertyPair.first == "Output") { | |
| const std::vector<double>* ptr = | |
| std::get_if<std::vector<double>>(&propertyPair.second); | |
| if (ptr == nullptr) { | |
| BMCWEB_LOG_ERROR << "Field Illegal " << propertyPair.first; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (propertyPair.first == "Reading") { | |
| keys = ptr; | |
| } else { | |
| values = ptr; | |
| } | |
| if (keys != nullptr && values != nullptr) { | |
| if (keys->size() != values->size()) { | |
| BMCWEB_LOG_ERROR | |
| << "Reading and Output size don't match "; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| nlohmann::json& steps = (*config)["Steps"]; | |
| steps = nlohmann::json::array(); | |
| for (size_t ii = 0; ii < keys->size(); ii++) { | |
| nlohmann::json::object_t step; | |
| step["Target"] = (*keys)[ii]; | |
| step["Output"] = (*values)[ii]; | |
| steps.emplace_back(std::move(step)); | |
| } | |
| } | |
| } | |
| if (propertyPair.first == "NegativeHysteresis" || | |
| propertyPair.first == "PositiveHysteresis") { | |
| const double* ptr = std::get_if<double>(&propertyPair.second); | |
| if (ptr == nullptr) { | |
| BMCWEB_LOG_ERROR << "Field Illegal " << propertyPair.first; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| (*config)[propertyPair.first] = *ptr; | |
| } | |
| } | |
| // pid and fans are off the same configuration | |
| if (intfPair.first == pidConfigurationIface || | |
| intfPair.first == stepwiseConfigurationIface) { | |
| if (propertyPair.first == "Zones") { | |
| const std::vector<std::string>* inputs = | |
| std::get_if<std::vector<std::string>>( | |
| &propertyPair.second); | |
| if (inputs == nullptr) { | |
| BMCWEB_LOG_ERROR << "Zones Pid Field Illegal"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| auto& data = (*config)[propertyPair.first]; | |
| data = nlohmann::json::array(); | |
| for (std::string itemCopy : *inputs) { | |
| dbus::utility::escapePathForDbus(itemCopy); | |
| nlohmann::json::object_t input; | |
| boost::urls::url managerUrl = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Managers", "bmc"); | |
| managerUrl.set_fragment( | |
| ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / itemCopy) | |
| .to_string()); | |
| input["@odata.id"] = std::move(managerUrl); | |
| data.emplace_back(std::move(input)); | |
| } | |
| } else if (propertyPair.first == "Inputs" || | |
| propertyPair.first == "Outputs") { | |
| // todo(james): may never happen, but this | |
| // assumes configuration data referenced in the | |
| // PID config is provided by the same daemon, we | |
| // could add another loop to cover all cases, | |
| // but I'm okay kicking this can down the road a | |
| // bit | |
| auto& data = (*config)[propertyPair.first]; | |
| const std::vector<std::string>* inputs = | |
| std::get_if<std::vector<std::string>>( | |
| &propertyPair.second); | |
| if (inputs == nullptr) { | |
| BMCWEB_LOG_ERROR << "Field Illegal " << propertyPair.first; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| data = *inputs; | |
| } else if (propertyPair.first == "SetPointOffset") { | |
| const std::string* ptr = | |
| std::get_if<std::string>(&propertyPair.second); | |
| if (ptr == nullptr) { | |
| BMCWEB_LOG_ERROR << "Field Illegal " << propertyPair.first; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // translate from dbus to redfish | |
| if (*ptr == "WarningHigh") { | |
| (*config)["SetPointOffset"] = "UpperThresholdNonCritical"; | |
| } else if (*ptr == "WarningLow") { | |
| (*config)["SetPointOffset"] = "LowerThresholdNonCritical"; | |
| } else if (*ptr == "CriticalHigh") { | |
| (*config)["SetPointOffset"] = "UpperThresholdCritical"; | |
| } else if (*ptr == "CriticalLow") { | |
| (*config)["SetPointOffset"] = "LowerThresholdCritical"; | |
| } else { | |
| BMCWEB_LOG_ERROR << "Value Illegal " << *ptr; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| } else if (propertyPair.first == "FFGainCoefficient" || | |
| propertyPair.first == "FFOffCoefficient" || | |
| propertyPair.first == "ICoefficient" || | |
| propertyPair.first == "ILimitMax" || | |
| propertyPair.first == "ILimitMin" || | |
| propertyPair.first == "PositiveHysteresis" || | |
| propertyPair.first == "NegativeHysteresis" || | |
| propertyPair.first == "OutLimitMax" || | |
| propertyPair.first == "OutLimitMin" || | |
| propertyPair.first == "PCoefficient" || | |
| propertyPair.first == "SetPoint" || | |
| propertyPair.first == "SlewNeg" || | |
| propertyPair.first == "SlewPos") { // doubles | |
| const double* ptr = std::get_if<double>(&propertyPair.second); | |
| if (ptr == nullptr) { | |
| BMCWEB_LOG_ERROR << "Field Illegal " << propertyPair.first; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| (*config)[propertyPair.first] = *ptr; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| enum class CreatePIDRet : std::uint8_t { fail, del, patch }; | |
| inline bool getZonesFromJsonReq( | |
| const std::shared_ptr<bmcweb::AsyncResp>& response, | |
| std::vector<nlohmann::json>& config, std::vector<std::string>& zones) { | |
| if (config.empty()) { | |
| BMCWEB_LOG_ERROR << "Empty Zones"; | |
| messages::propertyValueFormatError(response->res, "[]", "Zones"); | |
| return false; | |
| } | |
| for (auto& odata : config) { | |
| std::string path; | |
| if (!redfish::json_util::readJson(odata, response->res, "@odata.id", | |
| path)) { | |
| return false; | |
| } | |
| std::string input; | |
| // 8 below comes from | |
| // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left | |
| // 0 1 2 3 4 5 6 7 8 | |
| if (!dbus::utility::getNthStringFromPath(path, 8, input)) { | |
| BMCWEB_LOG_ERROR << "Got invalid path " << path; | |
| BMCWEB_LOG_ERROR << "Illegal Type Zones"; | |
| messages::propertyValueFormatError(response->res, odata.dump(), "Zones"); | |
| return false; | |
| } | |
| std::replace(input.begin(), input.end(), '_', ' '); | |
| zones.emplace_back(std::move(input)); | |
| } | |
| return true; | |
| } | |
| inline const dbus::utility::ManagedObjectType::value_type* findChassis( | |
| const dbus::utility::ManagedObjectType& managedObj, | |
| const std::string& value, std::string& chassis) { | |
| BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n"; | |
| std::string escaped = value; | |
| std::replace(escaped.begin(), escaped.end(), ' ', '_'); | |
| escaped = "/" + escaped; | |
| auto it = std::find_if( | |
| managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) { | |
| if (boost::algorithm::ends_with(obj.first.str, escaped)) { | |
| BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n"; | |
| return true; | |
| } | |
| return false; | |
| }); | |
| if (it == managedObj.end()) { | |
| return nullptr; | |
| } | |
| // 5 comes from <chassis-name> being the 5th element | |
| // /xyz/openbmc_project/inventory/system/chassis/<chassis-name> | |
| if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis)) { | |
| return &(*it); | |
| } | |
| return nullptr; | |
| } | |
| inline CreatePIDRet createPidInterface( | |
| const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type, | |
| const nlohmann::json::iterator& it, const std::string& path, | |
| const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, | |
| dbus::utility::DBusPropertiesMap& output, std::string& chassis, | |
| const std::string& profile) { | |
| // common deleter | |
| if (it.value() == nullptr) { | |
| std::string iface; | |
| if (type == "PidControllers" || type == "FanControllers") { | |
| iface = pidConfigurationIface; | |
| } else if (type == "FanZones") { | |
| iface = pidZoneConfigurationIface; | |
| } else if (type == "StepwiseControllers") { | |
| iface = stepwiseConfigurationIface; | |
| } else { | |
| BMCWEB_LOG_ERROR << "Illegal Type " << type; | |
| messages::propertyUnknown(response->res, type); | |
| return CreatePIDRet::fail; | |
| } | |
| BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n"; | |
| // delete interface | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| response->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [response, path](const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; | |
| messages::internalError(response->res); | |
| return; | |
| } | |
| messages::success(response->res); | |
| }), | |
| "xyz.openbmc_project.EntityManager", path, iface, "Delete"); | |
| return CreatePIDRet::del; | |
| } | |
| const dbus::utility::ManagedObjectType::value_type* managedItem = nullptr; | |
| if (!createNewObject) { | |
| // if we aren't creating a new object, we should be able to find it on | |
| // d-bus | |
| managedItem = findChassis(managedObj, it.key(), chassis); | |
| if (managedItem == nullptr) { | |
| BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; | |
| messages::invalidObject( | |
| response->res, | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassis)); | |
| return CreatePIDRet::fail; | |
| } | |
| } | |
| if (!profile.empty() && | |
| (type == "PidControllers" || type == "FanControllers" || | |
| type == "StepwiseControllers")) { | |
| if (managedItem == nullptr) { | |
| output.emplace_back("Profiles", std::vector<std::string>{profile}); | |
| } else { | |
| std::string interface; | |
| if (type == "StepwiseControllers") { | |
| interface = stepwiseConfigurationIface; | |
| } else { | |
| interface = pidConfigurationIface; | |
| } | |
| bool ifaceFound = false; | |
| for (const auto& iface : managedItem->second) { | |
| if (iface.first == interface) { | |
| ifaceFound = true; | |
| for (const auto& prop : iface.second) { | |
| if (prop.first == "Profiles") { | |
| const std::vector<std::string>* curProfiles = | |
| std::get_if<std::vector<std::string>>(&(prop.second)); | |
| if (curProfiles == nullptr) { | |
| BMCWEB_LOG_ERROR << "Illegal profiles in managed object"; | |
| messages::internalError(response->res); | |
| return CreatePIDRet::fail; | |
| } | |
| if (std::find(curProfiles->begin(), curProfiles->end(), | |
| profile) == curProfiles->end()) { | |
| std::vector<std::string> newProfiles = *curProfiles; | |
| newProfiles.emplace_back(profile); | |
| output.emplace_back("Profiles", newProfiles); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!ifaceFound) { | |
| BMCWEB_LOG_ERROR << "Failed to find interface in managed object"; | |
| messages::internalError(response->res); | |
| return CreatePIDRet::fail; | |
| } | |
| } | |
| } | |
| if (type == "PidControllers" || type == "FanControllers") { | |
| if (createNewObject) { | |
| output.emplace_back("Class", type == "PidControllers" ? "temp" : "fan"); | |
| output.emplace_back("Type", "Pid"); | |
| } | |
| std::optional<std::vector<nlohmann::json>> zones; | |
| std::optional<std::vector<std::string>> inputs; | |
| std::optional<std::vector<std::string>> outputs; | |
| std::map<std::string, std::optional<double>> doubles; | |
| std::optional<std::string> setpointOffset; | |
| if (!redfish::json_util::readJson( | |
| it.value(), response->res, "Inputs", inputs, "Outputs", outputs, | |
| "Zones", zones, "FFGainCoefficient", doubles["FFGainCoefficient"], | |
| "FFOffCoefficient", doubles["FFOffCoefficient"], "ICoefficient", | |
| doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], | |
| "ILimitMin", doubles["ILimitMin"], "OutLimitMax", | |
| doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], | |
| "PCoefficient", doubles["PCoefficient"], "SetPoint", | |
| doubles["SetPoint"], "SetPointOffset", setpointOffset, "SlewNeg", | |
| doubles["SlewNeg"], "SlewPos", doubles["SlewPos"], | |
| "PositiveHysteresis", doubles["PositiveHysteresis"], | |
| "NegativeHysteresis", doubles["NegativeHysteresis"])) { | |
| return CreatePIDRet::fail; | |
| } | |
| if (zones) { | |
| std::vector<std::string> zonesStr; | |
| if (!getZonesFromJsonReq(response, *zones, zonesStr)) { | |
| BMCWEB_LOG_ERROR << "Illegal Zones"; | |
| return CreatePIDRet::fail; | |
| } | |
| if (chassis.empty() && | |
| findChassis(managedObj, zonesStr[0], chassis) == nullptr) { | |
| BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; | |
| messages::invalidObject( | |
| response->res, | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassis)); | |
| return CreatePIDRet::fail; | |
| } | |
| output.emplace_back("Zones", std::move(zonesStr)); | |
| } | |
| if (inputs) { | |
| for (std::string& value : *inputs) { | |
| std::replace(value.begin(), value.end(), '_', ' '); | |
| } | |
| output.emplace_back("Inputs", *inputs); | |
| } | |
| if (outputs) { | |
| for (std::string& value : *outputs) { | |
| std::replace(value.begin(), value.end(), '_', ' '); | |
| } | |
| output.emplace_back("Outputs", *outputs); | |
| } | |
| if (setpointOffset) { | |
| // translate between redfish and dbus names | |
| if (*setpointOffset == "UpperThresholdNonCritical") { | |
| output.emplace_back("SetPointOffset", "WarningLow"); | |
| } else if (*setpointOffset == "LowerThresholdNonCritical") { | |
| output.emplace_back("SetPointOffset", "WarningHigh"); | |
| } else if (*setpointOffset == "LowerThresholdCritical") { | |
| output.emplace_back("SetPointOffset", "CriticalLow"); | |
| } else if (*setpointOffset == "UpperThresholdCritical") { | |
| output.emplace_back("SetPointOffset", "CriticalHigh"); | |
| } else { | |
| BMCWEB_LOG_ERROR << "Invalid setpointoffset " << *setpointOffset; | |
| messages::propertyValueNotInList(response->res, it.key(), | |
| "SetPointOffset"); | |
| return CreatePIDRet::fail; | |
| } | |
| } | |
| // doubles | |
| for (const auto& pairs : doubles) { | |
| if (!pairs.second) { | |
| continue; | |
| } | |
| BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; | |
| output.emplace_back(pairs.first, *pairs.second); | |
| } | |
| } else if (type == "FanZones") { | |
| output.emplace_back("Type", "Pid.Zone"); | |
| std::optional<nlohmann::json> chassisContainer; | |
| std::optional<double> failSafePercent; | |
| std::optional<double> minThermalOutput; | |
| if (!redfish::json_util::readJson(it.value(), response->res, "Chassis", | |
| chassisContainer, "FailSafePercent", | |
| failSafePercent, "MinThermalOutput", | |
| minThermalOutput)) { | |
| return CreatePIDRet::fail; | |
| } | |
| if (chassisContainer) { | |
| std::string chassisId; | |
| if (!redfish::json_util::readJson(*chassisContainer, response->res, | |
| "@odata.id", chassisId)) { | |
| return CreatePIDRet::fail; | |
| } | |
| // /redfish/v1/chassis/chassis_name/ | |
| if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) { | |
| BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; | |
| messages::invalidObject( | |
| response->res, crow::utility::urlFromPieces("redfish", "v1", | |
| "Chassis", chassisId)); | |
| return CreatePIDRet::fail; | |
| } | |
| } | |
| if (minThermalOutput) { | |
| output.emplace_back("MinThermalOutput", *minThermalOutput); | |
| } | |
| if (failSafePercent) { | |
| output.emplace_back("FailSafePercent", *failSafePercent); | |
| } | |
| } else if (type == "StepwiseControllers") { | |
| output.emplace_back("Type", "Stepwise"); | |
| std::optional<std::vector<nlohmann::json>> zones; | |
| std::optional<std::vector<nlohmann::json>> steps; | |
| std::optional<std::vector<std::string>> inputs; | |
| std::optional<double> positiveHysteresis; | |
| std::optional<double> negativeHysteresis; | |
| std::optional<std::string> direction; // upper clipping curve vs lower | |
| if (!redfish::json_util::readJson( | |
| it.value(), response->res, "Zones", zones, "Steps", steps, "Inputs", | |
| inputs, "PositiveHysteresis", positiveHysteresis, | |
| "NegativeHysteresis", negativeHysteresis, "Direction", direction)) { | |
| return CreatePIDRet::fail; | |
| } | |
| if (zones) { | |
| std::vector<std::string> zonesStrs; | |
| if (!getZonesFromJsonReq(response, *zones, zonesStrs)) { | |
| BMCWEB_LOG_ERROR << "Illegal Zones"; | |
| return CreatePIDRet::fail; | |
| } | |
| if (chassis.empty() && | |
| findChassis(managedObj, zonesStrs[0], chassis) == nullptr) { | |
| BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; | |
| messages::invalidObject( | |
| response->res, | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassis)); | |
| return CreatePIDRet::fail; | |
| } | |
| output.emplace_back("Zones", std::move(zonesStrs)); | |
| } | |
| if (steps) { | |
| std::vector<double> readings; | |
| std::vector<double> outputs; | |
| for (auto& step : *steps) { | |
| double target = 0.0; | |
| double out = 0.0; | |
| if (!redfish::json_util::readJson(step, response->res, "Target", target, | |
| "Output", out)) { | |
| return CreatePIDRet::fail; | |
| } | |
| readings.emplace_back(target); | |
| outputs.emplace_back(out); | |
| } | |
| output.emplace_back("Reading", std::move(readings)); | |
| output.emplace_back("Output", std::move(outputs)); | |
| } | |
| if (inputs) { | |
| for (std::string& value : *inputs) { | |
| std::replace(value.begin(), value.end(), '_', ' '); | |
| } | |
| output.emplace_back("Inputs", std::move(*inputs)); | |
| } | |
| if (negativeHysteresis) { | |
| output.emplace_back("NegativeHysteresis", *negativeHysteresis); | |
| } | |
| if (positiveHysteresis) { | |
| output.emplace_back("PositiveHysteresis", *positiveHysteresis); | |
| } | |
| if (direction) { | |
| constexpr const std::array<const char*, 2> allowedDirections = {"Ceiling", | |
| "Floor"}; | |
| if (std::find(allowedDirections.begin(), allowedDirections.end(), | |
| *direction) == allowedDirections.end()) { | |
| messages::propertyValueTypeError(response->res, "Direction", | |
| *direction); | |
| return CreatePIDRet::fail; | |
| } | |
| output.emplace_back("Class", *direction); | |
| } | |
| } else { | |
| BMCWEB_LOG_ERROR << "Illegal Type " << type; | |
| messages::propertyUnknown(response->res, type); | |
| return CreatePIDRet::fail; | |
| } | |
| return CreatePIDRet::patch; | |
| } | |
| struct GetPIDValues : std::enable_shared_from_this<GetPIDValues> { | |
| struct CompletionValues { | |
| std::vector<std::string> supportedProfiles; | |
| std::string currentProfile; | |
| dbus::utility::MapperGetSubTreeResponse subtree; | |
| }; | |
| explicit GetPIDValues(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) | |
| : asyncResp(asyncRespIn) | |
| {} | |
| void run() { | |
| std::shared_ptr<GetPIDValues> self = shared_from_this(); | |
| // get all configurations | |
| constexpr std::array<std::string_view, 4> interfaces = { | |
| pidConfigurationIface, pidZoneConfigurationIface, objectManagerIface, | |
| stepwiseConfigurationIface}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/", 0, interfaces, requestContext, | |
| [self](const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "GetPIDValues: " << ec; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| self->complete.subtree = subtreeLocal; | |
| }); | |
| // at the same time get the selected profile | |
| constexpr std::array<std::string_view, 1> thermalModeIfaces = { | |
| thermalModeIface}; | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/", 0, thermalModeIfaces, requestContext, | |
| [self](const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "GetPIDValues: " << ec; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| if (subtreeLocal.empty()) { | |
| // This is not to be considered an error | |
| BMCWEB_LOG_WARNING << "GetPIDValues: Empty subtree"; | |
| return; | |
| } | |
| if (subtreeLocal[0].second.size() != 1) { | |
| // invalid mapper response, should never happen | |
| BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error"; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| const std::string& path = subtreeLocal[0].first; | |
| const std::string& owner = subtreeLocal[0].second[0].first; | |
| managedStore::ManagedObjectStoreContext context(self->asyncResp); | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| owner, path, thermalModeIface, context, | |
| [path, owner, self]( | |
| const boost::system::error_code& ec2, | |
| const dbus::utility::DBusPropertiesMap& resp) { | |
| if (ec2) { | |
| BMCWEB_LOG_ERROR | |
| << "GetPIDValues: Can't get thermalModeIface " << path | |
| << ": " << ec2; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| const std::string* current = nullptr; | |
| const std::vector<std::string>* supported = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), resp, "Current", current, | |
| "Supported", supported); | |
| if (!success) { | |
| BMCWEB_LOG_ERROR << "GetPIDValues: Error unpacking"; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| if (current == nullptr || supported == nullptr) { | |
| BMCWEB_LOG_ERROR | |
| << "GetPIDValues: thermal mode iface invalid " << path; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| self->complete.currentProfile = *current; | |
| self->complete.supportedProfiles = *supported; | |
| }); | |
| }); | |
| } | |
| static void processingComplete( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const CompletionValues& completion) { | |
| if (asyncResp->res.result() != boost::beast::http::status::ok) { | |
| return; | |
| } | |
| // create map of <connection, path to objMgr>> | |
| boost::container::flat_map<std::string, std::string, std::less<>, | |
| std::vector<std::pair<std::string, std::string>>> | |
| objectMgrPaths; | |
| boost::container::flat_set<std::string, std::less<>, | |
| std::vector<std::string>> | |
| calledConnections; | |
| for (const auto& pathGroup : completion.subtree) { | |
| for (const auto& connectionGroup : pathGroup.second) { | |
| auto findConnection = calledConnections.find(connectionGroup.first); | |
| if (findConnection != calledConnections.end()) { | |
| break; | |
| } | |
| for (const std::string& interface : connectionGroup.second) { | |
| if (interface == objectManagerIface) { | |
| objectMgrPaths[connectionGroup.first] = pathGroup.first; | |
| } | |
| // this list is alphabetical, so we | |
| // should have found the objMgr by now | |
| if (interface == pidConfigurationIface || | |
| interface == pidZoneConfigurationIface || | |
| interface == stepwiseConfigurationIface) { | |
| auto findObjMgr = objectMgrPaths.find(connectionGroup.first); | |
| if (findObjMgr == objectMgrPaths.end()) { | |
| BMCWEB_LOG_DEBUG << connectionGroup.first | |
| << "Has no Object Manager"; | |
| continue; | |
| } | |
| calledConnections.insert(connectionGroup.first); | |
| asyncPopulatePid(findObjMgr->first, findObjMgr->second, | |
| completion.currentProfile, | |
| completion.supportedProfiles, asyncResp); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ~GetPIDValues() { | |
| boost::asio::post( | |
| managedStore::GetManagedObjectStore()->GetIoContext(), | |
| std::bind_front(&processingComplete, asyncResp, std::move(complete))); | |
| } | |
| GetPIDValues(const GetPIDValues&) = delete; | |
| GetPIDValues(GetPIDValues&&) = delete; | |
| GetPIDValues& operator=(const GetPIDValues&) = delete; | |
| GetPIDValues& operator=(GetPIDValues&&) = delete; | |
| std::shared_ptr<bmcweb::AsyncResp> asyncResp; | |
| CompletionValues complete; | |
| }; | |
| struct SetPIDValues : std::enable_shared_from_this<SetPIDValues> { | |
| SetPIDValues(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn, | |
| nlohmann::json& data) | |
| : asyncResp(asyncRespIn) { | |
| std::optional<nlohmann::json> pidControllers; | |
| std::optional<nlohmann::json> fanControllers; | |
| std::optional<nlohmann::json> fanZones; | |
| std::optional<nlohmann::json> stepwiseControllers; | |
| if (!redfish::json_util::readJson( | |
| data, asyncResp->res, "PidControllers", pidControllers, | |
| "FanControllers", fanControllers, "FanZones", fanZones, | |
| "StepwiseControllers", stepwiseControllers, "Profile", profile)) { | |
| return; | |
| } | |
| configuration.emplace_back("PidControllers", std::move(pidControllers)); | |
| configuration.emplace_back("FanControllers", std::move(fanControllers)); | |
| configuration.emplace_back("FanZones", std::move(fanZones)); | |
| configuration.emplace_back("StepwiseControllers", | |
| std::move(stepwiseControllers)); | |
| } | |
| SetPIDValues(const SetPIDValues&) = delete; | |
| SetPIDValues(SetPIDValues&&) = delete; | |
| SetPIDValues& operator=(const SetPIDValues&) = delete; | |
| SetPIDValues& operator=(SetPIDValues&&) = delete; | |
| void run() { | |
| if (asyncResp->res.result() != boost::beast::http::status::ok) { | |
| return; | |
| } | |
| std::shared_ptr<SetPIDValues> self = shared_from_this(); | |
| // todo(james): might make sense to do a mapper call here if this | |
| // interface gets more traction | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | |
| "xyz.openbmc_project.EntityManager", {"/xyz/openbmc_project/inventory"}, | |
| context, | |
| [self](const boost::system::error_code& ec, | |
| const dbus::utility::ManagedObjectType& mObj) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| const std::array<const char*, 3> configurations = { | |
| pidConfigurationIface, pidZoneConfigurationIface, | |
| stepwiseConfigurationIface}; | |
| for (const auto& [path, object] : mObj) { | |
| for (const auto& [interface, _] : object) { | |
| if (std::find(configurations.begin(), configurations.end(), | |
| interface) != configurations.end()) { | |
| self->objectCount++; | |
| break; | |
| } | |
| } | |
| } | |
| self->managedObj = mObj; | |
| }); | |
| // at the same time get the profile information | |
| constexpr std::array<std::string_view, 1> thermalModeIfaces = { | |
| thermalModeIface}; | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/", 0, thermalModeIfaces, context, | |
| [self, context]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "SetPIDValues: " << ec; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| if (subtree.empty()) { | |
| // This is not to be considered an error | |
| BMCWEB_LOG_WARNING << "SetPIDValues: Empty subtree"; | |
| return; | |
| } | |
| if (subtree[0].second.empty()) { | |
| // invalid mapper response, should never happen | |
| BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error"; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| const std::string& path = subtree[0].first; | |
| const std::string& owner = subtree[0].second[0].first; | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| owner, path, thermalModeIface, context, | |
| [self, path, owner](const boost::system::error_code& ec2, | |
| const dbus::utility::DBusPropertiesMap& r) { | |
| if (ec2) { | |
| BMCWEB_LOG_ERROR | |
| << "SetPIDValues: Can't get thermalModeIface " << path; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| const std::string* current = nullptr; | |
| const std::vector<std::string>* supported = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), r, "Current", current, | |
| "Supported", supported); | |
| if (!success) { | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| if (current == nullptr || supported == nullptr) { | |
| BMCWEB_LOG_ERROR | |
| << "SetPIDValues: thermal mode iface invalid " << path; | |
| messages::internalError(self->asyncResp->res); | |
| return; | |
| } | |
| self->currentProfile = *current; | |
| self->supportedProfiles = *supported; | |
| self->profileConnection = owner; | |
| self->profilePath = path; | |
| }); | |
| }); | |
| } | |
| void pidSetDone() { | |
| if (asyncResp->res.result() != boost::beast::http::status::ok) { | |
| return; | |
| } | |
| std::shared_ptr<bmcweb::AsyncResp> response = asyncResp; | |
| if (profile) { | |
| if (std::find(supportedProfiles.begin(), supportedProfiles.end(), | |
| *profile) == supportedProfiles.end()) { | |
| messages::actionParameterUnknown(response->res, "Profile", *profile); | |
| return; | |
| } | |
| currentProfile = *profile; | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| response->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [response](const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Error patching profile" << ec; | |
| messages::internalError(response->res); | |
| } | |
| }), | |
| profileConnection, profilePath, "org.freedesktop.DBus.Properties", | |
| "Set", thermalModeIface, "Current", | |
| dbus::utility::DbusVariantType(*profile)); | |
| } | |
| for (auto& containerPair : configuration) { | |
| auto& container = containerPair.second; | |
| if (!container) { | |
| continue; | |
| } | |
| BMCWEB_LOG_DEBUG << *container; | |
| const std::string& type = containerPair.first; | |
| for (nlohmann::json::iterator it = container->begin(); | |
| it != container->end(); ++it) { | |
| const auto& name = it.key(); | |
| BMCWEB_LOG_DEBUG << "looking for " << name; | |
| auto pathItr = std::find_if( | |
| managedObj.begin(), managedObj.end(), [&name](const auto& obj) { | |
| return boost::algorithm::ends_with(obj.first.str, "/" + name); | |
| }); | |
| dbus::utility::DBusPropertiesMap output; | |
| output.reserve(16); // The pid interface length | |
| // determines if we're patching entity-manager or | |
| // creating a new object | |
| bool createNewObject = (pathItr == managedObj.end()); | |
| BMCWEB_LOG_DEBUG << "Found = " << !createNewObject; | |
| std::string iface; | |
| if (!createNewObject) { | |
| bool findInterface = false; | |
| for (const auto& interface : pathItr->second) { | |
| if (interface.first == pidConfigurationIface) { | |
| if (type == "PidControllers" || type == "FanControllers") { | |
| iface = pidConfigurationIface; | |
| findInterface = true; | |
| break; | |
| } | |
| } else if (interface.first == pidZoneConfigurationIface) { | |
| if (type == "FanZones") { | |
| iface = pidConfigurationIface; | |
| findInterface = true; | |
| break; | |
| } | |
| } else if (interface.first == stepwiseConfigurationIface) { | |
| if (type == "StepwiseControllers") { | |
| iface = stepwiseConfigurationIface; | |
| findInterface = true; | |
| break; | |
| } | |
| } | |
| } | |
| // create new object if interface not found | |
| if (!findInterface) { | |
| createNewObject = true; | |
| } | |
| } | |
| if (createNewObject && it.value() == nullptr) { | |
| // can't delete a non-existent object | |
| messages::propertyValueNotInList(response->res, it.value().dump(), | |
| name); | |
| continue; | |
| } | |
| std::string path; | |
| if (pathItr != managedObj.end()) { | |
| path = pathItr->first.str; | |
| } | |
| BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n"; | |
| // arbitrary limit to avoid attacks | |
| constexpr const size_t controllerLimit = 500; | |
| if (createNewObject && objectCount >= controllerLimit) { | |
| messages::resourceExhaustion(response->res, type); | |
| continue; | |
| } | |
| std::string escaped = name; | |
| std::replace(escaped.begin(), escaped.end(), '_', ' '); | |
| output.emplace_back("Name", escaped); | |
| std::string chassis; | |
| CreatePIDRet ret = createPidInterface(response, type, it, path, | |
| managedObj, createNewObject, | |
| output, chassis, currentProfile); | |
| if (ret == CreatePIDRet::fail) { | |
| return; | |
| } | |
| if (ret == CreatePIDRet::del) { | |
| continue; | |
| } | |
| if (!createNewObject) { | |
| for (const auto& property : output) { | |
| managedStore::GetManagedObjectStore() | |
| ->PostDbusCallToIoContextThreadSafe( | |
| response->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [response, propertyName{std::string(property.first)}]( | |
| const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Error patching " | |
| << propertyName << ": " << ec; | |
| messages::internalError(response->res); | |
| return; | |
| } | |
| messages::success(response->res); | |
| }), | |
| "xyz.openbmc_project.EntityManager", path, | |
| "org.freedesktop.DBus.Properties", "Set", iface, | |
| property.first, | |
| dbus::utility::DbusVariantType{property.second}); | |
| } | |
| } else { | |
| if (chassis.empty()) { | |
| BMCWEB_LOG_ERROR << "Failed to get chassis from config"; | |
| messages::internalError(response->res); | |
| return; | |
| } | |
| bool foundChassis = false; | |
| for (const auto& obj : managedObj) { | |
| if (boost::algorithm::ends_with(obj.first.str, chassis)) { | |
| chassis = obj.first.str; | |
| foundChassis = true; | |
| break; | |
| } | |
| } | |
| if (!foundChassis) { | |
| BMCWEB_LOG_ERROR << "Failed to find chassis on dbus"; | |
| messages::resourceMissingAtURI( | |
| response->res, crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassis)); | |
| return; | |
| } | |
| managedStore::GetManagedObjectStore() | |
| ->PostDbusCallToIoContextThreadSafe( | |
| response->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [response](const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Error Adding Pid Object " << ec; | |
| messages::internalError(response->res); | |
| return; | |
| } | |
| messages::success(response->res); | |
| }), | |
| "xyz.openbmc_project.EntityManager", chassis, | |
| "xyz.openbmc_project.AddObject", "AddObject", output); | |
| } | |
| } | |
| } | |
| } | |
| ~SetPIDValues() { | |
| try { | |
| pidSetDone(); | |
| } catch (...) { | |
| BMCWEB_LOG_CRITICAL << "pidSetDone threw exception"; | |
| } | |
| } | |
| std::shared_ptr<bmcweb::AsyncResp> asyncResp; | |
| std::vector<std::pair<std::string, std::optional<nlohmann::json>>> | |
| configuration; | |
| std::optional<std::string> profile; | |
| dbus::utility::ManagedObjectType managedObj; | |
| std::vector<std::string> supportedProfiles; | |
| std::string currentProfile; | |
| std::string profileConnection; | |
| std::string profilePath; | |
| size_t objectCount = 0; | |
| }; | |
| // avoid name collision systems.hpp | |
| inline void managerGetLastResetTime( | |
| const std::shared_ptr<bmcweb::AsyncResp>& aResp) { | |
| BMCWEB_LOG_DEBUG << "Getting Manager Last Reset Time"; | |
| managedStore::ManagedObjectStoreContext requestContext(aResp); | |
| dbus_utils::getProperty<uint64_t>( | |
| "xyz.openbmc_project.State.BMC", "/xyz/openbmc_project/state/bmc0", | |
| "xyz.openbmc_project.State.BMC", "LastRebootTime", requestContext, | |
| [aResp](const boost::system::error_code& ec, | |
| const uint64_t lastResetTime) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "D-BUS response error " << ec; | |
| return; | |
| } | |
| // LastRebootTime is epoch time, in milliseconds | |
| // https://github.com/openbmc/phosphor-dbus-interfaces/blob/7f9a128eb9296e926422ddc312c148b625890bb6/xyz/openbmc_project/State/BMC.interface.yaml#L19 | |
| uint64_t lastResetTimeStamp = lastResetTime / 1000; | |
| // Convert to ISO 8601 standard | |
| aResp->res.jsonValue["LastResetTime"] = | |
| redfish::time_utils::getDateTimeUint(lastResetTimeStamp); | |
| }); | |
| } | |
| /** | |
| * @brief Set the running firmware image | |
| * | |
| * @param[i,o] aResp - Async response object | |
| * @param[i] runningFirmwareTarget - Image to make the running image | |
| * | |
| * @return void | |
| */ | |
| inline void setActiveFirmwareImage( | |
| const std::shared_ptr<bmcweb::AsyncResp>& aResp, | |
| const std::string& runningFirmwareTarget) { | |
| // Get the Id from /redfish/v1/UpdateService/FirmwareInventory/<Id> | |
| std::string::size_type idPos = runningFirmwareTarget.rfind('/'); | |
| if (idPos == std::string::npos) { | |
| messages::propertyValueNotInList(aResp->res, runningFirmwareTarget, | |
| "@odata.id"); | |
| BMCWEB_LOG_DEBUG << "Can't parse firmware ID!"; | |
| return; | |
| } | |
| idPos++; | |
| if (idPos >= runningFirmwareTarget.size()) { | |
| messages::propertyValueNotInList(aResp->res, runningFirmwareTarget, | |
| "@odata.id"); | |
| BMCWEB_LOG_DEBUG << "Invalid firmware ID."; | |
| return; | |
| } | |
| std::string firmwareId = runningFirmwareTarget.substr(idPos); | |
| managedStore::ManagedObjectStoreContext context(aResp); | |
| // Make sure the image is valid before setting priority | |
| managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | |
| "xyz.openbmc_project.Software.BMC.Updater", | |
| {"/xyz/openbmc_project/software"}, context, | |
| [aResp, firmwareId, runningFirmwareTarget]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::ManagedObjectType& subtree) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "D-Bus response error getting objects."; | |
| messages::internalError(aResp->res); | |
| return; | |
| } | |
| if (subtree.empty()) { | |
| BMCWEB_LOG_DEBUG << "Can't find image!"; | |
| messages::internalError(aResp->res); | |
| return; | |
| } | |
| bool foundImage = false; | |
| for (const auto& object : subtree) { | |
| const std::string& path = | |
| static_cast<const std::string&>(object.first); | |
| std::size_t idPos2 = path.rfind('/'); | |
| if (idPos2 == std::string::npos) { | |
| continue; | |
| } | |
| idPos2++; | |
| if (idPos2 >= path.size()) { | |
| continue; | |
| } | |
| if (path.substr(idPos2) == firmwareId) { | |
| foundImage = true; | |
| break; | |
| } | |
| } | |
| if (!foundImage) { | |
| messages::propertyValueNotInList(aResp->res, runningFirmwareTarget, | |
| "@odata.id"); | |
| BMCWEB_LOG_DEBUG << "Invalid firmware ID."; | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Setting firmware version " << firmwareId | |
| << " to priority 0."; | |
| // Only support Immediate | |
| // An addition could be a Redfish Setting like | |
| // ActiveSoftwareImageApplyTime and support OnReset | |
| managedStore::GetManagedObjectStore() | |
| ->PostDbusCallToIoContextThreadSafe( | |
| aResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [aResp](const boost::system::error_code& ec2) { | |
| if (ec2) { | |
| BMCWEB_LOG_DEBUG << "D-Bus response error setting."; | |
| messages::internalError(aResp->res); | |
| return; | |
| } | |
| doBMCGracefulRestart(aResp); | |
| }), | |
| "xyz.openbmc_project.Software.BMC.Updater", | |
| "/xyz/openbmc_project/software/" + firmwareId, | |
| "org.freedesktop.DBus.Properties", "Set", | |
| "xyz.openbmc_project.Software.RedundancyPriority", "Priority", | |
| dbus::utility::DbusVariantType(static_cast<uint8_t>(0))); | |
| }); | |
| } | |
| inline void setDateTime(const std::shared_ptr<bmcweb::AsyncResp>& aResp, | |
| std::string datetime) { | |
| BMCWEB_LOG_DEBUG << "Set date time: " << datetime; | |
| std::optional<redfish::time_utils::usSinceEpoch> us = | |
| redfish::time_utils::dateStringToEpoch(datetime); | |
| if (!us) { | |
| messages::propertyValueFormatError(aResp->res, datetime, "DateTime"); | |
| return; | |
| } | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| aResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [aResp, | |
| datetime{std::move(datetime)}](const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "Failed to set elapsed time. " | |
| "DBUS response error " | |
| << ec; | |
| messages::internalError(aResp->res); | |
| return; | |
| } | |
| aResp->res.jsonValue["DateTime"] = datetime; | |
| }), | |
| "xyz.openbmc_project.Time.Manager", "/xyz/openbmc_project/time/bmc", | |
| "org.freedesktop.DBus.Properties", "Set", | |
| "xyz.openbmc_project.Time.EpochTime", "Elapsed", | |
| dbus::utility::DbusVariantType(us->count())); | |
| } | |
| inline void handleManagerGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& managerId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| if (managerId != "bmc") { | |
| messages::resourceNotFound(asyncResp->res, "Managers", managerId); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; | |
| asyncResp->res.jsonValue["@odata.type"] = "#Manager.v1_16_0.Manager"; | |
| asyncResp->res.jsonValue["Id"] = "bmc"; | |
| asyncResp->res.jsonValue["Name"] = "OpenBmc Manager"; | |
| asyncResp->res.jsonValue["Description"] = "Baseboard Management Controller"; | |
| asyncResp->res.jsonValue["PowerState"] = "On"; | |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; | |
| asyncResp->res.jsonValue["Status"]["Health"] = "OK"; | |
| asyncResp->res.jsonValue["ManagerType"] = "BMC"; | |
| asyncResp->res.jsonValue["UUID"] = systemd_utils::getUuid(); | |
| asyncResp->res.jsonValue["ServiceEntryPointUUID"] = | |
| persistent_data::getConfig().systemUuid; | |
| asyncResp->res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model | |
| asyncResp->res.jsonValue["LogServices"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/LogServices"; | |
| asyncResp->res.jsonValue["NetworkProtocol"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/NetworkProtocol"; | |
| asyncResp->res.jsonValue["EthernetInterfaces"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/EthernetInterfaces"; | |
| asyncResp->res.jsonValue["DedicatedNetworkPorts"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/DedicatedNetworkPorts"; | |
| // default oem data | |
| nlohmann::json& oem = asyncResp->res.jsonValue["Oem"]; | |
| nlohmann::json& oemOpenbmc = oem["OpenBmc"]; | |
| oem["@odata.type"] = "#OemManager.Oem"; | |
| oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; | |
| oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; | |
| oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; | |
| nlohmann::json::object_t certificates; | |
| certificates["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Truststore/Certificates"; | |
| oemOpenbmc["Certificates"] = std::move(certificates); | |
| // Manager.Reset (an action) can be many values, OpenBMC only | |
| // supports BMC reboot. | |
| nlohmann::json& managerReset = | |
| asyncResp->res.jsonValue["Actions"]["#Manager.Reset"]; | |
| managerReset["target"] = "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; | |
| managerReset["@Redfish.ActionInfo"] = | |
| "/redfish/v1/Managers/bmc/ResetActionInfo"; | |
| // ResetToDefaults (Factory Reset) has values like | |
| // PreserveNetworkAndUsers and PreserveNetwork that aren't supported | |
| // on OpenBMC | |
| nlohmann::json& resetToDefaults = | |
| asyncResp->res.jsonValue["Actions"]["#Manager.ResetToDefaults"]; | |
| resetToDefaults["target"] = | |
| "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults"; | |
| resetToDefaults["ResetType@Redfish.AllowableValues"] = | |
| nlohmann::json::array_t({"ResetAll"}); | |
| nlohmann::json& fanModeChange = | |
| asyncResp->res.jsonValue["Actions"]["#Manager.FanMode.Change"]; | |
| fanModeChange["target"] = | |
| "/redfish/v1/Managers/bmc/Actions/Manager.FanMode.Change"; | |
| fanModeChange["@Redfish.ActionInfo"] = | |
| "/redfish/v1/Managers/bmc/FanMode.Change.ActionInfo"; | |
| // Google.Wipe is a Google custom action to deal with the BMC wipe. | |
| nlohmann::json& googleWipe = | |
| asyncResp->res.jsonValue["Actions"]["Oem"]["#Google.Wipe"]; | |
| googleWipe["target"] = "/redfish/v1/Managers/bmc/Actions/Oem/Google.Wipe"; | |
| googleWipe["@Redfish.ActionInfo"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/WipeActionInfo"; | |
| // Google.InstallConfiguration is a Google custom action to deal with the BMC | |
| // install configuration. | |
| nlohmann::json& googleInstallConfiguration = | |
| asyncResp->res.jsonValue["Actions"]["Oem"]["#Google.InstallConfiguration"]; | |
| googleInstallConfiguration["target"] = | |
| "/redfish/v1/Managers/bmc/Actions/Oem/Google.InstallConfiguration"; | |
| googleInstallConfiguration["@Redfish.ActionInfo"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/InstallConfigurationActionInfo"; | |
| std::pair<std::string, std::string> redfishDateTimeOffset = | |
| redfish::time_utils::getDateTimeOffsetNow(); | |
| asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; | |
| asyncResp->res.jsonValue["DateTimeLocalOffset"] = | |
| redfishDateTimeOffset.second; | |
| // TODO (Gunnar): Remove these one day since moved to ComputerSystem | |
| // Still used by OCP profiles | |
| // https://github.com/opencomputeproject/OCP-Profiles/issues/23 | |
| // Fill in SerialConsole info | |
| asyncResp->res.jsonValue["SerialConsole"]["ServiceEnabled"] = true; | |
| asyncResp->res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15; | |
| asyncResp->res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = | |
| nlohmann::json::array_t({"IPMI", "SSH"}); | |
| system_utils::getAllSystems( | |
| asyncResp, | |
| [](const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const dbus::utility::MapperGetSubTreePathsResponse& objects) { | |
| nlohmann::json::array_t managerForServers; | |
| if (system_utils::isHardcodedSingleHost(objects.size())) { | |
| nlohmann::json::object_t system; | |
| system["@odata.id"] = "/redfish/v1/Systems/system"; | |
| managerForServers.push_back(std::move(system)); | |
| } else { | |
| for (const std::string& object : objects) { | |
| nlohmann::json::object_t system; | |
| system["@odata.id"] = absl::StrCat( | |
| "/redfish/v1/Systems", object.substr(object.rfind('/'))); | |
| managerForServers.push_back(std::move(system)); | |
| } | |
| } | |
| asyncResp->res.jsonValue["Links"]["ManagerForServers@odata.count"] = | |
| objects.size(); | |
| asyncResp->res.jsonValue["Links"]["ManagerForServers"] = | |
| std::move(managerForServers); | |
| }); | |
| #ifdef HEALTH_POPULATE | |
| auto health = std::make_shared<HealthPopulate>(asyncResp); | |
| health->isManagersHealth = true; | |
| health->populate(); | |
| #endif | |
| // system here doesn't matter, it's because BMC is not mapped to a specific | |
| // system | |
| sw_util::populateSoftwareInformation(asyncResp, sw_util::bmcPurpose, | |
| "FirmwareVersion", true, | |
| sw_util::SwAssociation::Manager); | |
| managerGetLastResetTime(asyncResp); | |
| // ManagerDiagnosticData is added for all BMCs. | |
| nlohmann::json& managerDiagnosticData = | |
| asyncResp->res.jsonValue["ManagerDiagnosticData"]; | |
| managerDiagnosticData["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/ManagerDiagnosticData"; | |
| // Certifactes is added for all BMCs. | |
| nlohmann::json& certificateData = asyncResp->res.jsonValue["Certificates"]; | |
| certificateData["@odata.id"] = "/redfish/v1/Managers/bmc/Certificates"; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["BootTime"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/BootTime"; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["PsiMetrics"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/PsiMetrics"; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["FruDevice"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/FruDevice"; | |
| #ifdef BMCWEB_ENABLE_REDFISH_OEM_MANAGER_FAN_DATA | |
| auto pids = std::make_shared<GetPIDValues>(asyncResp); | |
| pids->run(); | |
| #endif | |
| getMainChassisId( | |
| asyncResp, [](const std::string& chassisId, | |
| const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { | |
| aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1; | |
| nlohmann::json::array_t managerForChassis; | |
| nlohmann::json::object_t managerObj; | |
| boost::urls::url chassiUrl = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId); | |
| managerObj["@odata.id"] = chassiUrl; | |
| managerForChassis.emplace_back(std::move(managerObj)); | |
| aRsp->res.jsonValue["Links"]["ManagerForChassis"] = | |
| std::move(managerForChassis); | |
| }); | |
| getBmcChassisId( | |
| asyncResp, [](const std::string& chassisId, | |
| const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { | |
| boost::urls::url chassiUrl = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId); | |
| aRsp->res.jsonValue["Links"]["ManagerInChassis"]["@odata.id"] = | |
| chassiUrl; | |
| }); | |
| static bool started = false; | |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["LocationType"] = | |
| "Embedded"; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| if (!started) { | |
| dbus_utils::getProperty<double>( | |
| "org.freedesktop.systemd1", "/org/freedesktop/systemd1", | |
| "org.freedesktop.systemd1.Manager", "Progress", requestContext, | |
| [asyncResp](const boost::system::error_code& ec, const double& val) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Error while getting progress"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (val < 1.0) { | |
| asyncResp->res.jsonValue["Status"]["State"] = "Starting"; | |
| started = true; | |
| } | |
| }); | |
| } | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Bmc"}; | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, | |
| [asyncResp, requestContext]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "D-Bus response error on GetSubTree " << ec; | |
| return; | |
| } | |
| if (subtree.empty()) { | |
| BMCWEB_LOG_DEBUG << "Can't find bmc D-Bus object!"; | |
| return; | |
| } | |
| // Assume only 1 bmc D-Bus object | |
| // Throw an error if there is more than 1 | |
| if (subtree.size() > 1) { | |
| BMCWEB_LOG_DEBUG << "Found more than 1 bmc D-Bus object!"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (subtree[0].first.empty() || subtree[0].second.size() != 1) { | |
| BMCWEB_LOG_DEBUG << "Error getting bmc D-Bus object!"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| const std::string& path = subtree[0].first; | |
| const std::string& connectionName = subtree[0].second[0].first; | |
| for (const auto& interfaceName : subtree[0].second[0].second) { | |
| if (interfaceName == | |
| "xyz.openbmc_project.Inventory.Decorator.Asset") { | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| connectionName, path, | |
| "xyz.openbmc_project.Inventory.Decorator.Asset", requestContext, | |
| [asyncResp]( | |
| const boost::system::error_code& ec2, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec2) { | |
| BMCWEB_LOG_DEBUG << "Can't get bmc asset!"; | |
| return; | |
| } | |
| const std::string* partNumber = nullptr; | |
| const std::string* serialNumber = nullptr; | |
| const std::string* manufacturer = nullptr; | |
| const std::string* model = nullptr; | |
| const std::string* sparePartNumber = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), propertiesList, | |
| "PartNumber", partNumber, "SerialNumber", serialNumber, | |
| "Manufacturer", manufacturer, "Model", model, | |
| "SparePartNumber", sparePartNumber); | |
| if (!success) { | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (partNumber != nullptr) { | |
| asyncResp->res.jsonValue["PartNumber"] = *partNumber; | |
| } | |
| if (serialNumber != nullptr) { | |
| asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; | |
| } | |
| if (manufacturer != nullptr) { | |
| asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; | |
| } | |
| if (model != nullptr) { | |
| asyncResp->res.jsonValue["Model"] = *model; | |
| } | |
| if (sparePartNumber != nullptr) { | |
| asyncResp->res.jsonValue["SparePartNumber"] = | |
| *sparePartNumber; | |
| } | |
| }); | |
| } else if (interfaceName == | |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode") { | |
| location_util::getLocationCode(asyncResp, connectionName, path, | |
| "/Location"_json_pointer); | |
| location_util::getPartLocationContext( | |
| asyncResp, "/Location"_json_pointer, path + "/contained_by"); | |
| asyncResp->res.jsonValue["Location"]["Oem"]["Google"] | |
| ["EmbeddedLocationContext"] = | |
| "openbmc_manager"; | |
| } | |
| } | |
| }); | |
| } | |
| inline void handleManagerPatch( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| std::optional<nlohmann::json> oem; | |
| std::optional<nlohmann::json> links; | |
| std::optional<std::string> datetime; | |
| if (!json_util::readJsonPatch(req, asyncResp->res, "Oem", oem, "DateTime", | |
| datetime, "Links", links)) { | |
| return; | |
| } | |
| if (oem) { | |
| #ifdef BMCWEB_ENABLE_REDFISH_OEM_MANAGER_FAN_DATA | |
| std::optional<nlohmann::json> openbmc; | |
| if (!redfish::json_util::readJson(*oem, asyncResp->res, "OpenBmc", | |
| openbmc)) { | |
| return; | |
| } | |
| if (openbmc) { | |
| std::optional<nlohmann::json> fan; | |
| if (!redfish::json_util::readJson(*openbmc, asyncResp->res, "Fan", fan)) { | |
| return; | |
| } | |
| if (fan) { | |
| auto pid = std::make_shared<SetPIDValues>(asyncResp, *fan); | |
| pid->run(); | |
| } | |
| } | |
| #else | |
| messages::propertyUnknown(asyncResp->res, "Oem"); | |
| return; | |
| #endif | |
| } | |
| if (links) { | |
| std::optional<nlohmann::json> activeSoftwareImage; | |
| if (!redfish::json_util::readJson(*links, asyncResp->res, | |
| "ActiveSoftwareImage", | |
| activeSoftwareImage)) { | |
| return; | |
| } | |
| if (activeSoftwareImage) { | |
| std::optional<std::string> odataId; | |
| if (!json_util::readJson(*activeSoftwareImage, asyncResp->res, | |
| "@odata.id", odataId)) { | |
| return; | |
| } | |
| if (odataId) { | |
| setActiveFirmwareImage(asyncResp, *odataId); | |
| } | |
| } | |
| } | |
| if (datetime) { | |
| setDateTime(asyncResp, std::move(*datetime)); | |
| } | |
| } | |
| inline void requestRoutesManager(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleManagerGet, std::ref(app))); | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/") | |
| .privileges(redfish::privileges::patchManager) | |
| .methods(boost::beast::http::verb::patch)( | |
| std::bind_front(handleManagerPatch, std::ref(app))); | |
| } | |
| inline void handleGetManagerCollection( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| // Collections don't include the static data added by SubRoute | |
| // because it has a duplicate entry for members | |
| asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#ManagerCollection.ManagerCollection"; | |
| asyncResp->res.jsonValue["Name"] = "Manager Collection"; | |
| nlohmann::json& members = asyncResp->res.jsonValue["Members"]; | |
| if (!members.is_array()) { | |
| // With Redfish aggregation, top level collections can have their | |
| // Members array already created if a satellite response has been | |
| // processed first. In that case we don't want to clobber the | |
| // aggregated data | |
| members = nlohmann::json::array(); | |
| } | |
| nlohmann::json::object_t bmc; | |
| bmc["@odata.id"] = "/redfish/v1/Managers/bmc"; | |
| members.emplace_back(std::move(bmc)); | |
| asyncResp->res.jsonValue["Members@odata.count"] = members.size(); | |
| } | |
| inline void requestRoutesManagerCollection(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/") | |
| .privileges(redfish::privileges::getManagerCollection) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleGetManagerCollection, std::ref(app))); | |
| } | |
| inline void handleBmcBootTimeDataGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| #ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME | |
| auto status = boottime::PopulateBmcData(asyncResp); | |
| if (!status.ok()) { | |
| BMCWEB_LOG_ERROR << status.message(); | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| #else | |
| std::string oDataId = "/redfish/v1/Managers/bmc/Oem/Google/BootTime"; | |
| boottime::PopulateDisabledResponse(asyncResp, oDataId); | |
| #endif | |
| } | |
| inline void requestRoutesBmcBootTime(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Oem/Google/BootTime/") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleBmcBootTimeDataGet, std::ref(app))); | |
| } | |
| inline void handlePsiMetricsCollectionGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| // Return an empty but valid PsiMetrics collection. This fallback route | |
| // handles the case where TLBMC is not enabled and cannot serve the | |
| // request. When TLBMC is enabled, SmartRouter routes to TLBMC instead. | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#GooglePsiMetricsCollection.v1_0_0.GooglePsiMetricsCollection"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/PsiMetrics"; | |
| asyncResp->res.jsonValue["Id"] = "PsiMetrics"; | |
| asyncResp->res.jsonValue["Name"] = "Google PSI Metrics Collection"; | |
| asyncResp->res.jsonValue["Status"] = "Disabled"; | |
| } | |
| inline void requestRoutesPsiMetrics(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Oem/Google/PsiMetrics/") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handlePsiMetricsCollectionGet, std::ref(app))); | |
| } | |
| inline void handleManagersCertificatesCollectionGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| // Return an empty but valid Certificates collection. This fallback route | |
| // handles the case where tlBMC is not enabled and cannot serve the | |
| // request. When tlBMC is enabled, SmartRouter routes to tlBMC instead. | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#CertificateCollection.v1_0_0.CertificateCollection"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Certificates"; | |
| asyncResp->res.jsonValue["Description"] = | |
| "A Collection of Manager Certificate instances"; | |
| asyncResp->res.jsonValue["Name"] = "Managers Certificates Collection"; | |
| asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); | |
| asyncResp->res.jsonValue["Members@odata.count"] = 0; | |
| } | |
| inline void requestRoutesManagersCertificates(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Certificates/") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)(std::bind_front( | |
| handleManagersCertificatesCollectionGet, std::ref(app))); | |
| } | |
| inline void handleFruDeviceGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#GoogleFruDevice.v1_0_0.GoogleFruDeviceService"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/FruDevice"; | |
| asyncResp->res.jsonValue["Name"] = "Google FRU Device Service"; | |
| asyncResp->res.jsonValue["Id"] = "FruDevice"; | |
| asyncResp->res.jsonValue["IpmiFrus"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/FruDevice/IpmiFrus"; | |
| asyncResp->res.jsonValue["RawEeproms"]["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/Oem/Google/FruDevice/RawEeproms"; | |
| } | |
| inline void handleIpmiFruCollectionGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#GoogleFruDevice.v1_0_0.GoogleIpmiFruCollection"; | |
| boost::urls::url collectionUrl( | |
| "/redfish/v1/Managers/bmc/Oem/Google/FruDevice/IpmiFrus"); | |
| asyncResp->res.jsonValue["@odata.id"] = collectionUrl; | |
| asyncResp->res.jsonValue["Name"] = "Google IPMI FRU Collection"; | |
| asyncResp->res.jsonValue["Id"] = "IpmiFrus"; | |
| asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); | |
| asyncResp->res.jsonValue["Members@odata.count"] = 0; | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.FruDevice"}; | |
| collection_util::getCollectionMembers(asyncResp, collectionUrl, interfaces, | |
| "/xyz/openbmc_project/FruDevice"); | |
| } | |
| inline void handleRawEepromCollectionGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#GoogleFruDevice.v1_0_0.GoogleRawEepromCollection"; | |
| boost::urls::url collectionUrl( | |
| "/redfish/v1/Managers/bmc/Oem/Google/FruDevice/RawEeproms"); | |
| asyncResp->res.jsonValue["@odata.id"] = collectionUrl; | |
| asyncResp->res.jsonValue["Name"] = "Google Raw EEPROM Collection"; | |
| asyncResp->res.jsonValue["Id"] = "RawEeproms"; | |
| asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); | |
| asyncResp->res.jsonValue["Members@odata.count"] = 0; | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.Inventory.Item.I2CDevice"}; | |
| collection_util::getCollectionMembers(asyncResp, collectionUrl, interfaces, | |
| "/xyz/openbmc_project/FruDevice"); | |
| } | |
| inline std::string toTitleCase(std::string_view str) { | |
| std::string result; | |
| result.reserve(str.size()); | |
| bool capitalizeNext = true; | |
| for (char c : str) { | |
| if (c == '_') { | |
| capitalizeNext = true; | |
| } else if (capitalizeNext) { | |
| result.push_back( | |
| static_cast<char>(std::toupper(static_cast<unsigned char>(c)))); | |
| capitalizeNext = false; | |
| } else { | |
| result.push_back( | |
| static_cast<char>(std::tolower(static_cast<unsigned char>(c)))); | |
| } | |
| } | |
| return result; | |
| } | |
| inline bool isValidDbusPathSegment(std::string_view segment) { | |
| return !segment.empty() && | |
| std::all_of(segment.begin(), segment.end(), [](char c) { | |
| return absl::ascii_isalnum(static_cast<unsigned char>(c)) || | |
| c == '_'; | |
| }); | |
| } | |
| inline void handleIpmiFruMemberGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& fruId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| if (!isValidDbusPathSegment(fruId)) { | |
| messages::resourceNotFound(asyncResp->res, "IpmiFru", fruId); | |
| return; | |
| } | |
| // On modular and multi-node platforms, FRU D-Bus objects under | |
| // /xyz/openbmc_project/FruDevice/ can be published by different daemons: | |
| // baseboard FRUs are published by xyz.openbmc_project.FruDevice, while | |
| // accelerator or peripheral FRUs may be published by secondary daemons (such | |
| // as xyz.openbmc_project.NSM). | |
| // | |
| // Here, "xyz.openbmc_project.FruDevice" is the target D-Bus *interface* (not | |
| // daemon name). We query the ObjectMapper to dynamically resolve which daemon | |
| // owns this specific FRU path before fetching its properties. | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.FruDevice"}; | |
| std::string fruPath = absl::StrCat("/xyz/openbmc_project/FruDevice/", fruId); | |
| dbus_utils::getDbusObject( | |
| fruPath, interfaces, context, | |
| [asyncResp, fruId, fruPath, context]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetObject& object) { | |
| if (ec || object.empty()) { | |
| messages::resourceNotFound(asyncResp->res, "IpmiFru", fruId); | |
| return; | |
| } | |
| // Dynamically use the service discovered from the ObjectMapper. | |
| const std::string& service = object.begin()->first; | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| service, fruPath, "xyz.openbmc_project.FruDevice", context, | |
| [asyncResp, fruId]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| if (ec.value() == | |
| boost::system::errc::no_such_file_or_directory || | |
| ec.value() == | |
| boost::system::linux_error::bad_request_descriptor || | |
| ec.value() == boost::system::errc::invalid_argument) { | |
| messages::resourceNotFound(asyncResp->res, "IpmiFru", fruId); | |
| } else { | |
| messages::internalError(asyncResp->res); | |
| } | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#GoogleFruDevice.v1_0_0.IpmiFru"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Managers", | |
| "bmc", "Oem", "Google", | |
| "FruDevice", "IpmiFrus", fruId); | |
| asyncResp->res.jsonValue["Id"] = fruId; | |
| asyncResp->res.jsonValue["Name"] = "Google IPMI FRU"; | |
| for (const auto& [propertyName, value] : propertiesList) { | |
| std::string key = toTitleCase(propertyName); | |
| if (propertyName == "BUS" || propertyName == "ADDRESS") { | |
| const uint32_t* intVal = std::get_if<uint32_t>(&value); | |
| if (intVal != nullptr) { | |
| asyncResp->res.jsonValue[key] = *intVal; | |
| } | |
| } else { | |
| const std::string* strVal = std::get_if<std::string>(&value); | |
| if (strVal != nullptr) { | |
| asyncResp->res.jsonValue[key] = *strVal; | |
| } | |
| } | |
| } | |
| }); | |
| }); | |
| } | |
| inline void handleRawEepromMemberGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& rawId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| if (!isValidDbusPathSegment(rawId)) { | |
| messages::resourceNotFound(asyncResp->res, "RawEeprom", rawId); | |
| return; | |
| } | |
| // Query ObjectMapper to find the daemon implementing the I2CDevice interface | |
| // for this EEPROM path before reading Bus/Address properties and invoking GetRawFru. | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.Inventory.Item.I2CDevice"}; | |
| std::string rawPath = absl::StrCat("/xyz/openbmc_project/FruDevice/", rawId); | |
| dbus_utils::getDbusObject( | |
| rawPath, interfaces, context, | |
| [asyncResp, rawId, rawPath, context]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetObject& object) { | |
| if (ec || object.empty()) { | |
| messages::resourceNotFound(asyncResp->res, "RawEeprom", rawId); | |
| return; | |
| } | |
| // Dynamically use the service discovered from the ObjectMapper. | |
| const std::string& service = object.begin()->first; | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| service, rawPath, "xyz.openbmc_project.Inventory.Item.I2CDevice", | |
| context, | |
| [asyncResp, rawId, service]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| if (ec.value() == | |
| boost::system::errc::no_such_file_or_directory || | |
| ec.value() == | |
| boost::system::linux_error::bad_request_descriptor || | |
| ec.value() == boost::system::errc::invalid_argument) { | |
| messages::resourceNotFound(asyncResp->res, "RawEeprom", | |
| rawId); | |
| } else { | |
| messages::internalError(asyncResp->res); | |
| } | |
| return; | |
| } | |
| uint32_t bus = 0; | |
| uint32_t address = 0; | |
| bool hasBus = false; | |
| bool hasAddress = false; | |
| std::optional<bool> corrupted; | |
| for (const auto& [propertyName, value] : propertiesList) { | |
| if (propertyName == "Bus") { | |
| const uint32_t* b = std::get_if<uint32_t>(&value); | |
| if (b != nullptr) { | |
| bus = *b; | |
| hasBus = true; | |
| } else if (const uint64_t* b64 = | |
| std::get_if<uint64_t>(&value)) { | |
| bus = static_cast<uint32_t>(*b64); | |
| hasBus = true; | |
| } | |
| } else if (propertyName == "Address") { | |
| const uint32_t* a = std::get_if<uint32_t>(&value); | |
| if (a != nullptr) { | |
| address = *a; | |
| hasAddress = true; | |
| } else if (const uint64_t* a64 = | |
| std::get_if<uint64_t>(&value)) { | |
| address = static_cast<uint32_t>(*a64); | |
| hasAddress = true; | |
| } | |
| } else if (propertyName == "Corrupted") { | |
| if (const bool* corruptedValue = std::get_if<bool>(&value); | |
| corruptedValue != nullptr) { | |
| corrupted = *corruptedValue; | |
| } | |
| } | |
| } | |
| if (!hasBus || !hasAddress) { | |
| BMCWEB_LOG_ERROR | |
| << "Failed to find Bus or Address properties for " << rawId; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#GoogleFruDevice.v1_0_0.RawEeprom"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Managers", | |
| "bmc", "Oem", "Google", | |
| "FruDevice", "RawEeproms", | |
| rawId); | |
| asyncResp->res.jsonValue["Id"] = rawId; | |
| asyncResp->res.jsonValue["Name"] = "Google Raw EEPROM"; | |
| asyncResp->res.jsonValue["Bus"] = bus; | |
| asyncResp->res.jsonValue["Address"] = address; | |
| managedStore::GetManagedObjectStore() | |
| ->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const std::vector<uint8_t>&)>( | |
| [asyncResp, rawId, corrupted]( | |
| const boost::system::error_code& ec2, | |
| const std::vector<uint8_t>& rawData) { | |
| if (ec2) { | |
| if (ec2 == | |
| boost::system::errc::invalid_argument) { | |
| BMCWEB_LOG_WARNING | |
| << "GetRawFru DBus call failed for " | |
| << rawId | |
| << " (EEPROM not populated): " << ec2; | |
| asyncResp->res.jsonValue["Status"]["State"] = | |
| "Disabled"; | |
| return; | |
| } | |
| BMCWEB_LOG_ERROR << "GetRawFru DBus call failed: " | |
| << ec2; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (corrupted.has_value()) { | |
| asyncResp->res.jsonValue["Corrupted"] = | |
| *corrupted; | |
| } | |
| asyncResp->res.jsonValue["Status"]["State"] = | |
| "Enabled"; | |
| asyncResp->res.jsonValue["RawData"] = rawData; | |
| asyncResp->res | |
| .jsonValue["HexdumpFormattedRawData"] = | |
| bytesToHexDump(rawData); | |
| }), | |
| service, "/xyz/openbmc_project/FruDevice", | |
| "xyz.openbmc_project.FruDeviceManager", "GetRawFru", | |
| static_cast<uint16_t>(bus), | |
| static_cast<uint8_t>(address)); | |
| }); | |
| }); | |
| } | |
| inline void requestRoutesFruDevices(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Oem/Google/FruDevice") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleFruDeviceGet, std::ref(app))); | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Oem/Google/FruDevice/IpmiFrus") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleIpmiFruCollectionGet, std::ref(app))); | |
| BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Oem/Google/FruDevice/RawEeproms") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleRawEepromCollectionGet, std::ref(app))); | |
| BMCWEB_ROUTE(app, | |
| "/redfish/v1/Managers/bmc/Oem/Google/FruDevice/IpmiFrus/<str>/") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleIpmiFruMemberGet, std::ref(app))); | |
| BMCWEB_ROUTE( | |
| app, "/redfish/v1/Managers/bmc/Oem/Google/FruDevice/RawEeproms/<str>/") | |
| .privileges(redfish::privileges::getManager) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleRawEepromMemberGet, std::ref(app))); | |
| } | |
| } // namespace redfish | |
| #endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MANAGERS_H_ |