summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEd Tanous <ed@tanous.net>2026-04-07 20:45:51 +0300
committerEd Tanous <ed@tanous.net>2026-06-22 20:18:23 +0300
commit6aa2c55fdf775a667fcfd033c7c3d2721ade00a2 (patch)
treeef3664b71fd8046b0434b04ec110eb84c119fe65
parent6dd6f276a2dc8a7bd293b417e1a710ecd987c9fc (diff)
downloadbmcweb-6aa2c55fdf775a667fcfd033c7c3d2721ade00a2.tar.xz
Remove giant inline lambda in OpenBMC Manager
This lambda needs to go away. There's no way it should've been accepted in the first place, but it was written in a different time. Tested: Functional in next commit. Unit tests in patch. Change-Id: I81360460c23329169f441b6bea02d28a8b410eca Signed-off-by: Ed Tanous <ed@tanous.net>
-rw-r--r--redfish-core/lib/openbmc/openbmc_managers.hpp729
-rw-r--r--test/meson.build1
-rw-r--r--test/redfish-core/lib/openbmc_managers_test.cpp482
3 files changed, 842 insertions, 370 deletions
diff --git a/redfish-core/lib/openbmc/openbmc_managers.hpp b/redfish-core/lib/openbmc/openbmc_managers.hpp
index b760a1577e..6a905e60ef 100644
--- a/redfish-core/lib/openbmc/openbmc_managers.hpp
+++ b/redfish-core/lib/openbmc/openbmc_managers.hpp
@@ -52,436 +52,425 @@ static constexpr const char* stepwiseConfigurationIface =
static constexpr const char* thermalModeIface =
"xyz.openbmc_project.Control.ThermalMode";
-inline void asyncPopulatePid(
- const std::string& connection, const std::string& path,
+inline void afterAsyncPopulatePid(
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& currentProfile,
const std::vector<std::string>& supportedProfiles,
- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+ const boost::system::error_code& ec,
+ const dbus::utility::ManagedObjectType& managedObj)
{
- sdbusplus::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["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())
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR("{}", ec);
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ nlohmann::json& configRoot = asyncResp->res.jsonValue["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)
{
- configRoot["Profile"] = currentProfile;
+ continue;
}
- BMCWEB_LOG_DEBUG("profile = {} !", currentProfile);
- for (const auto& pathPair : managedObj)
+ std::string name;
+
+ for (const std::pair<std::string, dbus::utility::DbusVariantType>&
+ propPair : intfPair.second)
{
- for (const auto& intfPair : pathPair.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")
{
- if (intfPair.first != pidConfigurationIface &&
- intfPair.first != pidZoneConfigurationIface &&
- intfPair.first != stepwiseConfigurationIface)
+ 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::ranges::find(*profiles, 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)
+ {
+ sdbusplus::object_path pidPath(pathPair.first.str);
+ std::string chassis = pidPath.filename();
+ if (chassis.empty())
+ {
+ 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";
- std::string name;
+ 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" ||
+ propertyPair.first == "AccumulateSetPoint")
+ {
+ 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;
+ }
- for (const std::pair<std::string,
- dbus::utility::DbusVariantType>&
- propPair : intfPair.second)
+ if (intfPair.first == stepwiseConfigurationIface)
+ {
+ if (propertyPair.first == "Reading" ||
+ propertyPair.first == "Output")
{
- if (propPair.first == "Name")
+ const std::vector<double>* ptr =
+ std::get_if<std::vector<double>>(
+ &propertyPair.second);
+
+ if (ptr == nullptr)
{
- 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);
+ BMCWEB_LOG_ERROR("Field Illegal {}",
+ propertyPair.first);
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ if (propertyPair.first == "Reading")
+ {
+ keys = ptr;
}
- else if (propPair.first == "Profiles")
+ else
{
- const std::vector<std::string>* profiles =
- std::get_if<std::vector<std::string>>(
- &propPair.second);
- if (profiles == nullptr)
+ values = ptr;
+ }
+ if (keys != nullptr && values != nullptr)
+ {
+ if (keys->size() != values->size())
{
- BMCWEB_LOG_ERROR("Pid Profiles Field illegal");
+ BMCWEB_LOG_ERROR(
+ "Reading and Output size don't match ");
messages::internalError(asyncResp->res);
return;
}
- if (std::ranges::find(*profiles, currentProfile) ==
- profiles->end())
+ nlohmann::json& steps = (*config)["Steps"];
+ steps = nlohmann::json::array();
+ for (size_t ii = 0; ii < keys->size(); ii++)
{
- BMCWEB_LOG_INFO(
- "{} not supported in current profile",
- name);
- continue;
+ nlohmann::json::object_t step;
+ step["Target"] = (*keys)[ii];
+ step["Output"] = (*values)[ii];
+ steps.emplace_back(std::move(step));
}
}
}
- nlohmann::json* config = nullptr;
- const std::string* classPtr = nullptr;
-
- for (const std::pair<std::string,
- dbus::utility::DbusVariantType>&
- propPair : intfPair.second)
+ if (propertyPair.first == "NegativeHysteresis" ||
+ propertyPair.first == "PositiveHysteresis")
{
- if (propPair.first == "Class")
+ const double* ptr =
+ std::get_if<double>(&propertyPair.second);
+ if (ptr == nullptr)
{
- classPtr =
- std::get_if<std::string>(&propPair.second);
+ BMCWEB_LOG_ERROR("Field Illegal {}",
+ propertyPair.first);
+ messages::internalError(asyncResp->res);
+ return;
}
+ (*config)[propertyPair.first] = *ptr;
}
+ }
- boost::urls::url url(
- boost::urls::format("/redfish/v1/Managers/{}",
- BMCWEB_REDFISH_MANAGER_URI_NAME));
- if (intfPair.first == pidZoneConfigurationIface)
+ // pid and fans are off the same configuration
+ if (intfPair.first == pidConfigurationIface ||
+ intfPair.first == stepwiseConfigurationIface)
+ {
+ if (propertyPair.first == "Zones")
{
- sdbusplus::object_path pidPath(pathPair.first.str);
- std::string chassis = pidPath.filename();
- if (chassis.empty())
+ const std::vector<std::string>* inputs =
+ std::get_if<std::vector<std::string>>(
+ &propertyPair.second);
+
+ if (inputs == nullptr)
{
- chassis = "#IllegalValue";
+ 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));
}
- 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)
+ // 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")
{
- if (classPtr == nullptr)
+ 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("Pid Class Field illegal");
+ BMCWEB_LOG_ERROR("Field Illegal {}",
+ propertyPair.first);
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;
+ data = *inputs;
}
-
- // pid and fans are off the same configuration
- else if (intfPair.first == pidConfigurationIface)
+ else if (propertyPair.first == "SetPointOffset")
{
- if (classPtr == nullptr)
+ const std::string* ptr =
+ std::get_if<std::string>(&propertyPair.second);
+
+ if (ptr == nullptr)
{
- BMCWEB_LOG_ERROR("Pid Class Field illegal");
+ BMCWEB_LOG_ERROR("Field Illegal {}",
+ propertyPair.first);
messages::internalError(asyncResp->res);
return;
}
- bool isFan = *classPtr == "fan";
- nlohmann::json& element =
- isFan ? fans[name] : pids[name];
- config = &element;
- if (isFan)
+ // translate from dbus to redfish
+ if (*ptr == "WarningHigh")
{
- 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";
+ (*config)["SetPointOffset"] =
+ "UpperThresholdNonCritical";
}
- else
+ else if (*ptr == "WarningLow")
{
- 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";
+ (*config)["SetPointOffset"] =
+ "LowerThresholdNonCritical";
}
- }
- 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" ||
- propertyPair.first == "AccumulateSetPoint")
+ else if (*ptr == "CriticalHigh")
{
- continue;
+ (*config)["SetPointOffset"] =
+ "UpperThresholdCritical";
}
-
- // zones
- if (intfPair.first == pidZoneConfigurationIface)
+ else if (*ptr == "CriticalLow")
{
- 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;
+ (*config)["SetPointOffset"] =
+ "LowerThresholdCritical";
}
-
- if (intfPair.first == stepwiseConfigurationIface)
+ else
{
- 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;
- }
+ BMCWEB_LOG_ERROR("Value Illegal {}", *ptr);
+ messages::internalError(asyncResp->res);
+ return;
}
-
- // pid and fans are off the same configuration
- if (intfPair.first == pidConfigurationIface ||
- intfPair.first == stepwiseConfigurationIface)
+ }
+ // 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)
{
- 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;
- }
+ BMCWEB_LOG_ERROR("Field Illegal {}",
+ propertyPair.first);
+ messages::internalError(asyncResp->res);
+ return;
}
+ (*config)[propertyPair.first] = *ptr;
}
}
}
- });
+ }
+ }
+}
+
+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::object_path objPath(path);
+ dbus::utility::getManagedObjects(
+ connection, objPath,
+ std::bind_front(&afterAsyncPopulatePid, asyncResp, currentProfile,
+ supportedProfiles));
}
enum class CreatePIDRet
diff --git a/test/meson.build b/test/meson.build
index 0a8afe25a2..3f6b5f0523 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -55,6 +55,7 @@ srcfiles_unittest = files(
'redfish-core/lib/log_services_dump_test.cpp',
'redfish-core/lib/manager_diagnostic_data_test.cpp',
'redfish-core/lib/metadata_test.cpp',
+ 'redfish-core/lib/openbmc_managers_test.cpp',
'redfish-core/lib/power_subsystem_test.cpp',
'redfish-core/lib/processor_test.cpp',
'redfish-core/lib/service_root_test.cpp',
diff --git a/test/redfish-core/lib/openbmc_managers_test.cpp b/test/redfish-core/lib/openbmc_managers_test.cpp
new file mode 100644
index 0000000000..1e45c055e7
--- /dev/null
+++ b/test/redfish-core/lib/openbmc_managers_test.cpp
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#include "async_resp.hpp"
+#include "dbus_utility.hpp"
+#include "openbmc/openbmc_managers.hpp"
+
+#include <boost/asio/error.hpp>
+#include <boost/beast/http/status.hpp>
+#include <boost/system/error_code.hpp>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <array>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace redfish
+{
+namespace
+{
+
+TEST(AfterAsyncPopulatePid, ErrorCodeReturnsInternalError)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+ boost::system::error_code ec = boost::asio::error::host_unreachable;
+
+ afterAsyncPopulatePid(asyncResp, "", {}, ec, {});
+
+ EXPECT_EQ(asyncResp->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(AfterAsyncPopulatePid, EmptyManagedObjectsPopulatesStaticFields)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ afterAsyncPopulatePid(asyncResp, "", {"ProfileA", "ProfileB"}, {}, {});
+
+ const nlohmann::json& fan = asyncResp->res.jsonValue["Fan"];
+ EXPECT_EQ(fan["@odata.type"], "#OpenBMCManager.v1_0_0.Manager.Fan");
+ EXPECT_EQ(fan["@odata.id"], "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan");
+ EXPECT_EQ(fan["FanControllers"]["@odata.type"],
+ "#OpenBMCManager.v1_0_0.Manager.FanControllers");
+ EXPECT_EQ(fan["PidControllers"]["@odata.type"],
+ "#OpenBMCManager.v1_0_0.Manager.PidControllers");
+ EXPECT_EQ(fan["StepwiseControllers"]["@odata.type"],
+ "#OpenBMCManager.v1_0_0.Manager.StepwiseControllers");
+ EXPECT_EQ(fan["FanZones"]["@odata.type"],
+ "#OpenBMCManager.v1_0_0.Manager.FanZones");
+
+ std::vector<std::string> profiles =
+ fan["Profile@Redfish.AllowableValues"].get<std::vector<std::string>>();
+ EXPECT_EQ(profiles, (std::vector<std::string>{"ProfileA", "ProfileB"}));
+
+ // No "Profile" key when currentProfile is empty.
+ EXPECT_FALSE(fan.contains("Profile"));
+}
+
+TEST(AfterAsyncPopulatePid, CurrentProfileSetsProfileField)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ afterAsyncPopulatePid(asyncResp, "ProfileA", {"ProfileA"}, {}, {});
+
+ EXPECT_EQ(asyncResp->res.jsonValue["Fan"]["Profile"], "ProfileA");
+}
+
+TEST(AfterAsyncPopulatePid, FanControllerEntryIsPopulated)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyFan");
+ props.emplace_back("Class", "fan");
+ props.emplace_back("PCoefficient", 1.5);
+ props.emplace_back("OutLimitMax", 100.0);
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/fan0"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ const nlohmann::json& fans =
+ asyncResp->res.jsonValue["Fan"]["FanControllers"];
+ ASSERT_TRUE(fans.contains("MyFan"));
+ EXPECT_EQ(fans["MyFan"]["@odata.type"],
+ "#OpenBMCManager.v1_0_0.Manager.FanController");
+ EXPECT_EQ(fans["MyFan"]["@odata.id"],
+ "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanControllers/MyFan");
+ EXPECT_DOUBLE_EQ(fans["MyFan"]["PCoefficient"].get<double>(), 1.5);
+ EXPECT_DOUBLE_EQ(fans["MyFan"]["OutLimitMax"].get<double>(), 100.0);
+ EXPECT_FALSE(
+ asyncResp->res.jsonValue["Fan"]["PidControllers"].contains("MyFan"));
+}
+
+TEST(AfterAsyncPopulatePid, PidControllerEntryIsPopulated)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyPid");
+ props.emplace_back("Class", "temperature");
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/pid0"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ const nlohmann::json& pids =
+ asyncResp->res.jsonValue["Fan"]["PidControllers"];
+ ASSERT_TRUE(pids.contains("MyPid"));
+ EXPECT_EQ(pids["MyPid"]["@odata.type"],
+ "#OpenBMCManager.v1_0_0.Manager.PidController");
+ EXPECT_EQ(pids["MyPid"]["@odata.id"],
+ "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers/MyPid");
+}
+
+TEST(AfterAsyncPopulatePid, StepwiseControllerStepsAssembled)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyStep");
+ props.emplace_back("Class", "Ceiling");
+ props.emplace_back("Reading", std::vector<double>{20.0, 30.0, 40.0});
+ props.emplace_back("Output", std::vector<double>{10.0, 50.0, 80.0});
+ props.emplace_back("PositiveHysteresis", 1.0);
+ props.emplace_back("NegativeHysteresis", 2.0);
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Stepwise",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/step0"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ const nlohmann::json& step =
+ asyncResp->res.jsonValue["Fan"]["StepwiseControllers"]["MyStep"];
+ EXPECT_EQ(step["@odata.type"],
+ "#OpenBMCManager.v1_0_0.Manager.StepwiseController");
+ EXPECT_EQ(step["Direction"], "Ceiling");
+ EXPECT_DOUBLE_EQ(step["PositiveHysteresis"].get<double>(), 1.0);
+ EXPECT_DOUBLE_EQ(step["NegativeHysteresis"].get<double>(), 2.0);
+
+ const nlohmann::json& steps = step["Steps"];
+ ASSERT_TRUE(steps.is_array());
+ ASSERT_EQ(steps.size(), 3U);
+ EXPECT_DOUBLE_EQ(steps[0]["Target"].get<double>(), 20.0);
+ EXPECT_DOUBLE_EQ(steps[0]["Output"].get<double>(), 10.0);
+ EXPECT_DOUBLE_EQ(steps[2]["Target"].get<double>(), 40.0);
+ EXPECT_DOUBLE_EQ(steps[2]["Output"].get<double>(), 80.0);
+}
+
+TEST(AfterAsyncPopulatePid, FanZoneIsPopulatedWithDoubles)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "Zone0");
+ props.emplace_back("MinThermalOutput", 25.0);
+ props.emplace_back("FailSafePercent", 75.0);
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid.Zone",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(
+ sdbusplus::message::object_path("/xyz/chassis/MyChassis"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ const nlohmann::json& zone =
+ asyncResp->res.jsonValue["Fan"]["FanZones"]["Zone0"];
+ EXPECT_EQ(zone["@odata.type"], "#OpenBMCManager.v1_0_0.Manager.FanZone");
+ EXPECT_EQ(zone["Chassis"]["@odata.id"], "/redfish/v1/Chassis/MyChassis");
+ EXPECT_EQ(zone["@odata.id"],
+ "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Zone0");
+ EXPECT_DOUBLE_EQ(zone["MinThermalOutput"].get<double>(), 25.0);
+ EXPECT_DOUBLE_EQ(zone["FailSafePercent"].get<double>(), 75.0);
+}
+
+TEST(AfterAsyncPopulatePid, FanZoneIllegalChassisGetsPlaceholder)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "Zone0");
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid.Zone",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ // empty filename portion -> chassis becomes "#IllegalValue"
+ managed.emplace_back(sdbusplus::message::object_path("/"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ const nlohmann::json& zone =
+ asyncResp->res.jsonValue["Fan"]["FanZones"]["Zone0"];
+ // The '#' must be percent-encoded in the path segment.
+ EXPECT_EQ(zone["Chassis"]["@odata.id"],
+ "/redfish/v1/Chassis/%23IllegalValue");
+}
+
+TEST(AfterAsyncPopulatePid, UnknownInterfacesAreIgnored)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "Other");
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.SomethingElse",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/x"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ EXPECT_EQ(asyncResp->res.result(), boost::beast::http::status::ok);
+ EXPECT_FALSE(
+ asyncResp->res.jsonValue["Fan"]["PidControllers"].contains("Other"));
+ EXPECT_FALSE(
+ asyncResp->res.jsonValue["Fan"]["FanControllers"].contains("Other"));
+}
+
+TEST(AfterAsyncPopulatePid, BadNameTypeReturnsInternalError)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", 42);
+ props.emplace_back("Class", "fan");
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/p"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ EXPECT_EQ(asyncResp->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(AfterAsyncPopulatePid, BadProfilesTypeReturnsInternalError)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyPid");
+ props.emplace_back("Profiles", "not-a-list");
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/p"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "ProfileA", {"ProfileA"}, {}, managed);
+
+ EXPECT_EQ(asyncResp->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(AfterAsyncPopulatePid, MissingClassOnPidReturnsInternalError)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyPid");
+ // No "Class" property at all.
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/p"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ EXPECT_EQ(asyncResp->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(AfterAsyncPopulatePid, MissingClassOnStepwiseReturnsInternalError)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyStep");
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Stepwise",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/s"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ EXPECT_EQ(asyncResp->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(AfterAsyncPopulatePid, SetPointOffsetTranslatesKnownValues)
+{
+ struct Translation
+ {
+ std::string dbus;
+ std::string redfish;
+ };
+
+ const auto translations = std::to_array<Translation>(
+ {{"WarningHigh", "UpperThresholdNonCritical"},
+ {"WarningLow", "LowerThresholdNonCritical"},
+ {"CriticalHigh", "UpperThresholdCritical"},
+ {"CriticalLow", "LowerThresholdCritical"}});
+
+ for (const auto& t : translations)
+ {
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyPid");
+ props.emplace_back("Class", "temperature");
+ props.emplace_back("SetPointOffset", t.dbus);
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/p"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ EXPECT_EQ(asyncResp->res.result(), boost::beast::http::status::ok)
+ << "dbus value: " << t.dbus;
+ EXPECT_EQ(asyncResp->res.jsonValue["Fan"]["PidControllers"]["MyPid"]
+ ["SetPointOffset"],
+ t.redfish);
+ }
+}
+
+TEST(AfterAsyncPopulatePid, SetPointOffsetUnknownValueReturnsInternalError)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyPid");
+ props.emplace_back("Class", "temperature");
+ props.emplace_back("SetPointOffset", "NotARealValue");
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/p"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ EXPECT_EQ(asyncResp->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(AfterAsyncPopulatePid, StepwiseReadingOutputSizeMismatchReturnsError)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyStep");
+ props.emplace_back("Class", "Ceiling");
+ props.emplace_back("Reading", std::vector<double>{1.0, 2.0});
+ props.emplace_back("Output", std::vector<double>{10.0, 20.0, 30.0});
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Stepwise",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/s"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ EXPECT_EQ(asyncResp->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(AfterAsyncPopulatePid, PidZonesReferenceUsesFanZonesUrl)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyPid");
+ props.emplace_back("Class", "temperature");
+ props.emplace_back("Zones", std::vector<std::string>{"Zone0", "Zone1"});
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/p"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ const nlohmann::json& zones =
+ asyncResp->res.jsonValue["Fan"]["PidControllers"]["MyPid"]["Zones"];
+ ASSERT_TRUE(zones.is_array());
+ ASSERT_EQ(zones.size(), 2U);
+ EXPECT_EQ(zones[0]["@odata.id"],
+ "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Zone0");
+ EXPECT_EQ(zones[1]["@odata.id"],
+ "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Zone1");
+}
+
+TEST(AfterAsyncPopulatePid, InputsAndOutputsArePassedThrough)
+{
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ dbus::utility::DBusPropertiesMap props;
+ props.emplace_back("Name", "MyPid");
+ props.emplace_back("Class", "temperature");
+ props.emplace_back("Inputs",
+ std::vector<std::string>{"Sensor1", "Sensor2"});
+ props.emplace_back("Outputs", std::vector<std::string>{"FanOut1"});
+
+ dbus::utility::DBusInterfacesMap interfaces;
+ interfaces.emplace_back("xyz.openbmc_project.Configuration.Pid",
+ std::move(props));
+
+ dbus::utility::ManagedObjectType managed;
+ managed.emplace_back(sdbusplus::message::object_path("/xyz/p"),
+ std::move(interfaces));
+
+ afterAsyncPopulatePid(asyncResp, "", {}, {}, managed);
+
+ const nlohmann::json& pid =
+ asyncResp->res.jsonValue["Fan"]["PidControllers"]["MyPid"];
+ EXPECT_EQ(pid["Inputs"], (std::vector<std::string>{"Sensor1", "Sensor2"}));
+ EXPECT_EQ(pid["Outputs"], (std::vector<std::string>{"FanOut1"}));
+}
+
+} // namespace
+} // namespace redfish