Migration of OpenBMC Manager OEM schema
With the support of OEM route infrastructure each OEM implementation
can be separated into an OEM route handler. The MR migrates OEM resource
of manager resource into new files and route handlers
Tested
- All unit tests are passing
- GET request on /redfish/v1/Managers/<bmcid> has OpenBMC OEM properties
Change-Id: I935524dcdad6a6cc38a5532b6e7e7ffa1cb0369f
Signed-off-by: rohitpai <rohitpai77@gmail.com>
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index cbe3081..e5a89f0 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -15,6 +15,7 @@
#include "generated/enums/resource.hpp"
#include "http_request.hpp"
#include "logging.hpp"
+#include "openbmc/openbmc_managers.hpp"
#include "persistent_data.hpp"
#include "query.hpp"
#include "redfish_util.hpp"
@@ -27,11 +28,8 @@
#include <systemd/sd-bus.h>
-#include <boost/asio/post.hpp>
#include <boost/beast/http/status.hpp>
#include <boost/beast/http/verb.hpp>
-#include <boost/container/flat_map.hpp>
-#include <boost/container/flat_set.hpp>
#include <boost/system/error_code.hpp>
#include <boost/url/format.hpp>
#include <boost/url/url.hpp>
@@ -41,7 +39,6 @@
#include <sdbusplus/message/native_types.hpp>
#include <sdbusplus/unpack_properties.hpp>
-#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
@@ -54,7 +51,6 @@
#include <string>
#include <string_view>
#include <utility>
-#include <variant>
#include <vector>
namespace redfish
@@ -340,1439 +336,6 @@
});
}
-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)
-{
- sdbusplus::message::object_path objPath(path);
- dbus::utility::getManagedObjects(
- connection, objPath,
- [asyncResp, currentProfile, supportedProfiles](
- const boost::system::error_code& ec,
- const dbus::utility::ManagedObjectType& managedObj) {
- if (ec)
- {
- BMCWEB_LOG_ERROR("{}", ec);
- messages::internalError(asyncResp->res);
- return;
- }
- nlohmann::json& configRoot =
- asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
- nlohmann::json& fans = configRoot["FanControllers"];
- fans["@odata.type"] =
- "#OpenBMCManager.v1_0_0.Manager.FanControllers";
- fans["@odata.id"] = boost::urls::format(
- "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanControllers",
- BMCWEB_REDFISH_MANAGER_URI_NAME);
-
- nlohmann::json& pids = configRoot["PidControllers"];
- pids["@odata.type"] =
- "#OpenBMCManager.v1_0_0.Manager.PidControllers";
- pids["@odata.id"] = boost::urls::format(
- "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/PidControllers",
- BMCWEB_REDFISH_MANAGER_URI_NAME);
-
- nlohmann::json& stepwise = configRoot["StepwiseControllers"];
- stepwise["@odata.type"] =
- "#OpenBMCManager.v1_0_0.Manager.StepwiseControllers";
- stepwise["@odata.id"] = boost::urls::format(
- "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/StepwiseControllers",
- BMCWEB_REDFISH_MANAGER_URI_NAME);
-
- nlohmann::json& zones = configRoot["FanZones"];
- zones["@odata.id"] = boost::urls::format(
- "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanZones",
- BMCWEB_REDFISH_MANAGER_URI_NAME);
- zones["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanZones";
- configRoot["@odata.id"] =
- boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan",
- BMCWEB_REDFISH_MANAGER_URI_NAME);
- configRoot["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.Fan";
- configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
-
- if (!currentProfile.empty())
- {
- configRoot["Profile"] = currentProfile;
- }
- BMCWEB_LOG_DEBUG("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(
- "{} not supported in current profile",
- name);
- continue;
- }
- }
- }
- nlohmann::json* config = nullptr;
- const std::string* classPtr = 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);
- }
- }
-
- boost::urls::url url(
- boost::urls::format("/redfish/v1/Managers/{}",
- BMCWEB_REDFISH_MANAGER_URI_NAME));
- if (intfPair.first == pidZoneConfigurationIface)
- {
- std::string chassis;
- if (!dbus::utility::getNthStringFromPath(
- pathPair.first.str, 5, chassis))
- {
- chassis = "#IllegalValue";
- }
- nlohmann::json& zone = zones[name];
- zone["Chassis"]["@odata.id"] = boost::urls::format(
- "/redfish/v1/Chassis/{}", chassis);
- url.set_fragment(
- ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name)
- .to_string());
- zone["@odata.id"] = std::move(url);
- zone["@odata.type"] =
- "#OpenBMCManager.v1_0_0.Manager.FanZone";
- config = &zone;
- }
-
- 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"] =
- "#OpenBMCManager.v1_0_0.Manager.StepwiseController";
-
- controller["Direction"] = *classPtr;
- }
-
- // pid and fans are off the same configuration
- else if (intfPair.first == pidConfigurationIface)
- {
- 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"] =
- "#OpenBMCManager.v1_0_0.Manager.FanController";
- }
- else
- {
- url.set_fragment(
- ("/Oem/OpenBmc/Fan/PidControllers"_json_pointer /
- name)
- .to_string());
- element["@odata.id"] = std::move(url);
- element["@odata.type"] =
- "#OpenBMCManager.v1_0_0.Manager.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 =
- boost::urls::format(
- "/redfish/v1/Managers/{}#{}",
- BMCWEB_REDFISH_MANAGER_URI_NAME,
- ("/Oem/OpenBmc/Fan/FanZones"_json_pointer /
- itemCopy)
- .to_string());
- input["@odata.id"] = std::move(managerUrl);
- data.emplace_back(std::move(input));
- }
- }
- // 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
-
- else if (propertyPair.first == "Inputs" ||
- propertyPair.first == "Outputs")
- {
- 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;
- }
- }
- // doubles
- 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")
- {
- 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
-{
- fail,
- del,
- patch
-};
-
-inline bool getZonesFromJsonReq(
- const std::shared_ptr<bmcweb::AsyncResp>& response,
- std::vector<nlohmann::json::object_t>& config,
- std::vector<std::string>& zones)
-{
- if (config.empty())
- {
- BMCWEB_LOG_ERROR("Empty Zones");
- messages::propertyValueFormatError(response->res, config, "Zones");
- return false;
- }
- for (auto& odata : config)
- {
- std::string path;
- if (!redfish::json_util::readJsonObject(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, "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, std::string_view value,
- std::string& chassis)
-{
- BMCWEB_LOG_DEBUG("Find Chassis: {}", value);
-
- std::string escaped(value);
- std::replace(escaped.begin(), escaped.end(), ' ', '_');
- escaped = "/" + escaped;
- auto it = std::ranges::find_if(managedObj, [&escaped](const auto& obj) {
- if (obj.first.str.ends_with(escaped))
- {
- BMCWEB_LOG_DEBUG("Matched {}", obj.first.str);
- 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,
- std::string_view name, nlohmann::json& jsonValue, 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 (jsonValue == 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);
- // delete interface
- crow::connections::systemBus->async_method_call(
- [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, name, chassis);
- if (managedItem == nullptr)
- {
- BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
- messages::invalidObject(
- response->res,
- boost::urls::format("/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.push_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::object_t>> 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( //
- jsonValue, response->res, //
- "FFGainCoefficient", doubles["FFGainCoefficient"], //
- "FFOffCoefficient", doubles["FFOffCoefficient"], //
- "ICoefficient", doubles["ICoefficient"], //
- "ILimitMax", doubles["ILimitMax"], //
- "ILimitMin", doubles["ILimitMin"], //
- "Inputs", inputs, //
- "NegativeHysteresis", doubles["NegativeHysteresis"], //
- "OutLimitMax", doubles["OutLimitMax"], //
- "OutLimitMin", doubles["OutLimitMin"], //
- "Outputs", outputs, //
- "PCoefficient", doubles["PCoefficient"], //
- "PositiveHysteresis", doubles["PositiveHysteresis"], //
- "SetPoint", doubles["SetPoint"], //
- "SetPointOffset", setpointOffset, //
- "SlewNeg", doubles["SlewNeg"], //
- "SlewPos", doubles["SlewPos"], //
- "Zones", zones //
- ))
- {
- 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,
- boost::urls::format("/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, name,
- "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<std::string> chassisId;
- std::optional<double> failSafePercent;
- std::optional<double> minThermalOutput;
- if (!redfish::json_util::readJson( //
- jsonValue, response->res, //
- "Chassis/@odata.id", chassisId, //
- "FailSafePercent", failSafePercent, //
- "MinThermalOutput", minThermalOutput))
- {
- return CreatePIDRet::fail;
- }
-
- if (chassisId)
- {
- // /redfish/v1/chassis/chassis_name/
- if (!dbus::utility::getNthStringFromPath(*chassisId, 3, chassis))
- {
- BMCWEB_LOG_ERROR("Got invalid path {}", *chassisId);
- messages::invalidObject(
- response->res,
- boost::urls::format("/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::object_t>> zones;
- std::optional<std::vector<nlohmann::json::object_t>> 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( //
- jsonValue, response->res, //
- "Direction", direction, //
- "Inputs", inputs, //
- "NegativeHysteresis", negativeHysteresis, //
- "PositiveHysteresis", positiveHysteresis, //
- "Steps", steps, //
- "Zones", zones //
- ))
- {
- 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,
- boost::urls::format("/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::readJsonObject( //
- step, response->res, //
- "Output", out, //
- "Target", target //
- ))
- {
- 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::ranges::find(allowedDirections, *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};
- dbus::utility::getSubTree(
- "/", 0, interfaces,
- [self](
- const boost::system::error_code& ec,
- const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
- if (ec)
- {
- BMCWEB_LOG_ERROR("{}", 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};
- dbus::utility::getSubTree(
- "/", 0, thermalModeIfaces,
- [self](
- const boost::system::error_code& ec,
- const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
- if (ec || subtreeLocal.empty())
- {
- 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;
-
- dbus::utility::getAllProperties(
- *crow::connections::systemBus, owner, path,
- thermalModeIface,
- [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);
- 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)
- {
- 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("{}Has no Object Manager",
- connectionGroup.first);
- continue;
- }
-
- calledConnections.insert(connectionGroup.first);
-
- asyncPopulatePid(findObjMgr->first, findObjMgr->second,
- completion.currentProfile,
- completion.supportedProfiles,
- asyncResp);
- break;
- }
- }
- }
- }
- }
-
- ~GetPIDValues()
- {
- boost::asio::post(crow::connections::systemBus->get_io_context(),
- 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,
- std::vector<
- std::pair<std::string, std::optional<nlohmann::json::object_t>>>&&
- configurationsIn,
- std::optional<std::string>& profileIn) :
- asyncResp(asyncRespIn), configuration(std::move(configurationsIn)),
- profile(std::move(profileIn))
- {}
-
- 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
- sdbusplus::message::object_path objPath(
- "/xyz/openbmc_project/inventory");
- dbus::utility::getManagedObjects(
- "xyz.openbmc_project.EntityManager", objPath,
- [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::ranges::find(configurations, 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};
- dbus::utility::getSubTree(
- "/", 0, thermalModeIfaces,
- [self](const boost::system::error_code& ec,
- const dbus::utility::MapperGetSubTreeResponse& subtree) {
- if (ec || subtree.empty())
- {
- 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;
- dbus::utility::getAllProperties(
- *crow::connections::systemBus, owner, path,
- thermalModeIface,
- [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::ranges::find(supportedProfiles, *profile) ==
- supportedProfiles.end())
- {
- messages::actionParameterUnknown(response->res, "Profile",
- *profile);
- return;
- }
- currentProfile = *profile;
- sdbusplus::asio::setProperty(
- *crow::connections::systemBus, profileConnection, profilePath,
- thermalModeIface, "Current", *profile,
- [response](const boost::system::error_code& ec) {
- if (ec)
- {
- BMCWEB_LOG_ERROR("Error patching profile{}", ec);
- messages::internalError(response->res);
- }
- });
- }
-
- for (auto& containerPair : configuration)
- {
- auto& container = containerPair.second;
- if (!container)
- {
- continue;
- }
-
- const std::string& type = containerPair.first;
-
- for (auto& [name, value] : *container)
- {
- std::string dbusObjName = name;
- std::replace(dbusObjName.begin(), dbusObjName.end(), ' ', '_');
- BMCWEB_LOG_DEBUG("looking for {}", name);
-
- auto pathItr = std::ranges::find_if(
- managedObj, [&dbusObjName](const auto& obj) {
- return obj.first.filename() == dbusObjName;
- });
- 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 = pidZoneConfigurationIface;
- 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 && value == nullptr)
- {
- // can't delete a non-existent object
- messages::propertyValueNotInList(response->res, value,
- name);
- continue;
- }
-
- std::string path;
- if (pathItr != managedObj.end())
- {
- path = pathItr->first.str;
- }
-
- BMCWEB_LOG_DEBUG("Create new = {}", createNewObject);
-
- // 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, name, value, path, managedObj,
- createNewObject, output, chassis, currentProfile);
- if (ret == CreatePIDRet::fail)
- {
- return;
- }
- if (ret == CreatePIDRet::del)
- {
- continue;
- }
-
- if (!createNewObject)
- {
- for (const auto& property : output)
- {
- crow::connections::systemBus->async_method_call(
- [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, 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 (obj.first.filename() == chassis)
- {
- chassis = obj.first.str;
- foundChassis = true;
- break;
- }
- }
- if (!foundChassis)
- {
- BMCWEB_LOG_ERROR("Failed to find chassis on dbus");
- messages::resourceMissingAtURI(
- response->res,
- boost::urls::format("/redfish/v1/Chassis/{}",
- chassis));
- return;
- }
-
- crow::connections::systemBus->async_method_call(
- [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::object_t>>>
- 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;
-};
-
/**
* @brief Retrieves BMC manager location data over DBus
*
@@ -2078,22 +641,7 @@
BMCWEB_REDFISH_MANAGER_URI_NAME);
}
- // default oem data
- nlohmann::json& oem = asyncResp->res.jsonValue["Oem"];
- nlohmann::json& oemOpenbmc = oem["OpenBmc"];
- oem["@odata.id"] =
- boost::urls::format("/redfish/v1/Managers/{}#/Oem",
- BMCWEB_REDFISH_MANAGER_URI_NAME);
- oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager";
- oemOpenbmc["@odata.id"] =
- boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc",
- BMCWEB_REDFISH_MANAGER_URI_NAME);
-
- nlohmann::json::object_t certificates;
- certificates["@odata.id"] = boost::urls::format(
- "/redfish/v1/Managers/{}/Truststore/Certificates",
- BMCWEB_REDFISH_MANAGER_URI_NAME);
- oemOpenbmc["Certificates"] = std::move(certificates);
+ getHandleOemOpenBmc(req, asyncResp, managerId);
// Manager.Reset (an action) can be many values, OpenBMC only
// supports BMC reboot.
diff --git a/redfish-core/lib/openbmc/openbmc_managers.hpp b/redfish-core/lib/openbmc/openbmc_managers.hpp
new file mode 100644
index 0000000..eaa61ee
--- /dev/null
+++ b/redfish-core/lib/openbmc/openbmc_managers.hpp
@@ -0,0 +1,1501 @@
+
+#pragma once
+
+#include "bmcweb_config.h"
+
+#include "async_resp.hpp"
+#include "dbus_singleton.hpp"
+#include "dbus_utility.hpp"
+#include "error_messages.hpp"
+#include "http_request.hpp"
+#include "logging.hpp"
+#include "utils/dbus_utils.hpp"
+#include "utils/json_utils.hpp"
+
+#include <boost/asio/post.hpp>
+#include <boost/beast/http/status.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <boost/url/format.hpp>
+#include <boost/url/url.hpp>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/asio/property.hpp>
+#include <sdbusplus/message/native_types.hpp>
+#include <sdbusplus/unpack_properties.hpp>
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <functional>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace redfish
+{
+
+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)
+{
+ sdbusplus::message::object_path objPath(path);
+ dbus::utility::getManagedObjects(
+ connection, objPath,
+ [asyncResp, currentProfile, supportedProfiles](
+ const boost::system::error_code& ec,
+ const dbus::utility::ManagedObjectType& managedObj) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR("{}", ec);
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ nlohmann::json& configRoot =
+ asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
+ nlohmann::json& fans = configRoot["FanControllers"];
+ fans["@odata.type"] =
+ "#OpenBMCManager.v1_0_0.Manager.FanControllers";
+ fans["@odata.id"] = boost::urls::format(
+ "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanControllers",
+ BMCWEB_REDFISH_MANAGER_URI_NAME);
+
+ nlohmann::json& pids = configRoot["PidControllers"];
+ pids["@odata.type"] =
+ "#OpenBMCManager.v1_0_0.Manager.PidControllers";
+ pids["@odata.id"] = boost::urls::format(
+ "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/PidControllers",
+ BMCWEB_REDFISH_MANAGER_URI_NAME);
+
+ nlohmann::json& stepwise = configRoot["StepwiseControllers"];
+ stepwise["@odata.type"] =
+ "#OpenBMCManager.v1_0_0.Manager.StepwiseControllers";
+ stepwise["@odata.id"] = boost::urls::format(
+ "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/StepwiseControllers",
+ BMCWEB_REDFISH_MANAGER_URI_NAME);
+
+ nlohmann::json& zones = configRoot["FanZones"];
+ zones["@odata.id"] = boost::urls::format(
+ "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanZones",
+ BMCWEB_REDFISH_MANAGER_URI_NAME);
+ zones["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanZones";
+ configRoot["@odata.id"] =
+ boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan",
+ BMCWEB_REDFISH_MANAGER_URI_NAME);
+ configRoot["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.Fan";
+ configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
+
+ if (!currentProfile.empty())
+ {
+ configRoot["Profile"] = currentProfile;
+ }
+ BMCWEB_LOG_DEBUG("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(
+ "{} not supported in current profile",
+ name);
+ continue;
+ }
+ }
+ }
+ nlohmann::json* config = nullptr;
+ const std::string* classPtr = 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);
+ }
+ }
+
+ boost::urls::url url(
+ boost::urls::format("/redfish/v1/Managers/{}",
+ BMCWEB_REDFISH_MANAGER_URI_NAME));
+ if (intfPair.first == pidZoneConfigurationIface)
+ {
+ std::string chassis;
+ if (!dbus::utility::getNthStringFromPath(
+ pathPair.first.str, 5, chassis))
+ {
+ chassis = "#IllegalValue";
+ }
+ nlohmann::json& zone = zones[name];
+ zone["Chassis"]["@odata.id"] = boost::urls::format(
+ "/redfish/v1/Chassis/{}", chassis);
+ url.set_fragment(
+ ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name)
+ .to_string());
+ zone["@odata.id"] = std::move(url);
+ zone["@odata.type"] =
+ "#OpenBMCManager.v1_0_0.Manager.FanZone";
+ config = &zone;
+ }
+
+ 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"] =
+ "#OpenBMCManager.v1_0_0.Manager.StepwiseController";
+
+ controller["Direction"] = *classPtr;
+ }
+
+ // pid and fans are off the same configuration
+ else if (intfPair.first == pidConfigurationIface)
+ {
+ 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"] =
+ "#OpenBMCManager.v1_0_0.Manager.FanController";
+ }
+ else
+ {
+ url.set_fragment(
+ ("/Oem/OpenBmc/Fan/PidControllers"_json_pointer /
+ name)
+ .to_string());
+ element["@odata.id"] = std::move(url);
+ element["@odata.type"] =
+ "#OpenBMCManager.v1_0_0.Manager.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 =
+ boost::urls::format(
+ "/redfish/v1/Managers/{}#{}",
+ BMCWEB_REDFISH_MANAGER_URI_NAME,
+ ("/Oem/OpenBmc/Fan/FanZones"_json_pointer /
+ itemCopy)
+ .to_string());
+ input["@odata.id"] = std::move(managerUrl);
+ data.emplace_back(std::move(input));
+ }
+ }
+ // 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
+
+ else if (propertyPair.first == "Inputs" ||
+ propertyPair.first == "Outputs")
+ {
+ 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;
+ }
+ }
+ // doubles
+ 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")
+ {
+ 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
+{
+ fail,
+ del,
+ patch
+};
+
+inline const dbus::utility::ManagedObjectType::value_type* findChassis(
+ const dbus::utility::ManagedObjectType& managedObj, std::string_view value,
+ std::string& chassis)
+{
+ BMCWEB_LOG_DEBUG("Find Chassis: {}", value);
+
+ std::string escaped(value);
+ std::replace(escaped.begin(), escaped.end(), ' ', '_');
+ escaped = "/" + escaped;
+ auto it = std::ranges::find_if(managedObj, [&escaped](const auto& obj) {
+ if (obj.first.str.ends_with(escaped))
+ {
+ BMCWEB_LOG_DEBUG("Matched {}", obj.first.str);
+ 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 bool getZonesFromJsonReq(
+ const std::shared_ptr<bmcweb::AsyncResp>& response,
+ std::vector<nlohmann::json::object_t>& config,
+ std::vector<std::string>& zones)
+{
+ if (config.empty())
+ {
+ BMCWEB_LOG_ERROR("Empty Zones");
+ messages::propertyValueFormatError(response->res, config, "Zones");
+ return false;
+ }
+ for (auto& odata : config)
+ {
+ std::string path;
+ if (!redfish::json_util::readJsonObject(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, "Zones");
+ return false;
+ }
+ std::replace(input.begin(), input.end(), '_', ' ');
+ zones.emplace_back(std::move(input));
+ }
+ return true;
+}
+
+inline CreatePIDRet createPidInterface(
+ const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type,
+ std::string_view name, nlohmann::json& jsonValue, 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 (jsonValue == 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);
+ // delete interface
+ crow::connections::systemBus->async_method_call(
+ [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, name, chassis);
+ if (managedItem == nullptr)
+ {
+ BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
+ messages::invalidObject(
+ response->res,
+ boost::urls::format("/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.push_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::object_t>> 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( //
+ jsonValue, response->res, //
+ "FFGainCoefficient", doubles["FFGainCoefficient"], //
+ "FFOffCoefficient", doubles["FFOffCoefficient"], //
+ "ICoefficient", doubles["ICoefficient"], //
+ "ILimitMax", doubles["ILimitMax"], //
+ "ILimitMin", doubles["ILimitMin"], //
+ "Inputs", inputs, //
+ "NegativeHysteresis", doubles["NegativeHysteresis"], //
+ "OutLimitMax", doubles["OutLimitMax"], //
+ "OutLimitMin", doubles["OutLimitMin"], //
+ "Outputs", outputs, //
+ "PCoefficient", doubles["PCoefficient"], //
+ "PositiveHysteresis", doubles["PositiveHysteresis"], //
+ "SetPoint", doubles["SetPoint"], //
+ "SetPointOffset", setpointOffset, //
+ "SlewNeg", doubles["SlewNeg"], //
+ "SlewPos", doubles["SlewPos"], //
+ "Zones", zones //
+ ))
+ {
+ 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,
+ boost::urls::format("/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, name,
+ "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<std::string> chassisId;
+ std::optional<double> failSafePercent;
+ std::optional<double> minThermalOutput;
+ if (!redfish::json_util::readJson( //
+ jsonValue, response->res, //
+ "Chassis/@odata.id", chassisId, //
+ "FailSafePercent", failSafePercent, //
+ "MinThermalOutput", minThermalOutput))
+ {
+ return CreatePIDRet::fail;
+ }
+
+ if (chassisId)
+ {
+ // /redfish/v1/chassis/chassis_name/
+ if (!dbus::utility::getNthStringFromPath(*chassisId, 3, chassis))
+ {
+ BMCWEB_LOG_ERROR("Got invalid path {}", *chassisId);
+ messages::invalidObject(
+ response->res,
+ boost::urls::format("/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::object_t>> zones;
+ std::optional<std::vector<nlohmann::json::object_t>> 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( //
+ jsonValue, response->res, //
+ "Direction", direction, //
+ "Inputs", inputs, //
+ "NegativeHysteresis", negativeHysteresis, //
+ "PositiveHysteresis", positiveHysteresis, //
+ "Steps", steps, //
+ "Zones", zones //
+ ))
+ {
+ 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,
+ boost::urls::format("/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::readJsonObject( //
+ step, response->res, //
+ "Output", out, //
+ "Target", target //
+ ))
+ {
+ 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::ranges::find(allowedDirections, *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};
+ dbus::utility::getSubTree(
+ "/", 0, interfaces,
+ [self](
+ const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR("{}", 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};
+ dbus::utility::getSubTree(
+ "/", 0, thermalModeIfaces,
+ [self](
+ const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
+ if (ec || subtreeLocal.empty())
+ {
+ 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;
+
+ dbus::utility::getAllProperties(
+ *crow::connections::systemBus, owner, path,
+ thermalModeIface,
+ [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);
+ 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)
+ {
+ 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("{}Has no Object Manager",
+ connectionGroup.first);
+ continue;
+ }
+
+ calledConnections.insert(connectionGroup.first);
+
+ asyncPopulatePid(findObjMgr->first, findObjMgr->second,
+ completion.currentProfile,
+ completion.supportedProfiles,
+ asyncResp);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ ~GetPIDValues()
+ {
+ boost::asio::post(crow::connections::systemBus->get_io_context(),
+ 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,
+ std::vector<
+ std::pair<std::string, std::optional<nlohmann::json::object_t>>>&&
+ configurationsIn,
+ std::optional<std::string>& profileIn) :
+ asyncResp(asyncRespIn), configuration(std::move(configurationsIn)),
+ profile(std::move(profileIn))
+ {}
+
+ 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
+ sdbusplus::message::object_path objPath(
+ "/xyz/openbmc_project/inventory");
+ dbus::utility::getManagedObjects(
+ "xyz.openbmc_project.EntityManager", objPath,
+ [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::ranges::find(configurations, 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};
+ dbus::utility::getSubTree(
+ "/", 0, thermalModeIfaces,
+ [self](const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreeResponse& subtree) {
+ if (ec || subtree.empty())
+ {
+ 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;
+ dbus::utility::getAllProperties(
+ *crow::connections::systemBus, owner, path,
+ thermalModeIface,
+ [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::ranges::find(supportedProfiles, *profile) ==
+ supportedProfiles.end())
+ {
+ messages::actionParameterUnknown(response->res, "Profile",
+ *profile);
+ return;
+ }
+ currentProfile = *profile;
+ sdbusplus::asio::setProperty(
+ *crow::connections::systemBus, profileConnection, profilePath,
+ thermalModeIface, "Current", *profile,
+ [response](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR("Error patching profile{}", ec);
+ messages::internalError(response->res);
+ }
+ });
+ }
+
+ for (auto& containerPair : configuration)
+ {
+ auto& container = containerPair.second;
+ if (!container)
+ {
+ continue;
+ }
+
+ const std::string& type = containerPair.first;
+
+ for (auto& [name, value] : *container)
+ {
+ std::string dbusObjName = name;
+ std::replace(dbusObjName.begin(), dbusObjName.end(), ' ', '_');
+ BMCWEB_LOG_DEBUG("looking for {}", name);
+
+ auto pathItr = std::ranges::find_if(
+ managedObj, [&dbusObjName](const auto& obj) {
+ return obj.first.filename() == dbusObjName;
+ });
+ 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 = pidZoneConfigurationIface;
+ 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 && value == nullptr)
+ {
+ // can't delete a non-existent object
+ messages::propertyValueNotInList(response->res, value,
+ name);
+ continue;
+ }
+
+ std::string path;
+ if (pathItr != managedObj.end())
+ {
+ path = pathItr->first.str;
+ }
+
+ BMCWEB_LOG_DEBUG("Create new = {}", createNewObject);
+
+ // 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, name, value, path, managedObj,
+ createNewObject, output, chassis, currentProfile);
+ if (ret == CreatePIDRet::fail)
+ {
+ return;
+ }
+ if (ret == CreatePIDRet::del)
+ {
+ continue;
+ }
+
+ if (!createNewObject)
+ {
+ for (const auto& property : output)
+ {
+ crow::connections::systemBus->async_method_call(
+ [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, 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 (obj.first.filename() == chassis)
+ {
+ chassis = obj.first.str;
+ foundChassis = true;
+ break;
+ }
+ }
+ if (!foundChassis)
+ {
+ BMCWEB_LOG_ERROR("Failed to find chassis on dbus");
+ messages::resourceMissingAtURI(
+ response->res,
+ boost::urls::format("/redfish/v1/Chassis/{}",
+ chassis));
+ return;
+ }
+
+ crow::connections::systemBus->async_method_call(
+ [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::object_t>>>
+ 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;
+};
+
+inline void getHandleOemOpenBmc(
+ const crow::Request& /*req*/,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& /*managerId*/)
+{
+ // Default OEM data
+ nlohmann::json& oemOpenbmc = asyncResp->res.jsonValue["Oem"]["OpenBmc"];
+ oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager";
+ oemOpenbmc["@odata.id"] =
+ boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc",
+ BMCWEB_REDFISH_MANAGER_URI_NAME);
+
+ nlohmann::json::object_t certificates;
+ certificates["@odata.id"] =
+ boost::urls::format("/redfish/v1/Managers/{}/Truststore/Certificates",
+ BMCWEB_REDFISH_MANAGER_URI_NAME);
+ oemOpenbmc["Certificates"] = std::move(certificates);
+
+ if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
+ {
+ auto pids = std::make_shared<GetPIDValues>(asyncResp);
+ pids->run();
+ }
+}
+
+} // namespace redfish