diff options
| author | Ed Tanous <ed@tanous.net> | 2026-04-07 20:45:51 +0300 |
|---|---|---|
| committer | Ed Tanous <ed@tanous.net> | 2026-06-22 20:18:23 +0300 |
| commit | 6aa2c55fdf775a667fcfd033c7c3d2721ade00a2 (patch) | |
| tree | ef3664b71fd8046b0434b04ec110eb84c119fe65 | |
| parent | 6dd6f276a2dc8a7bd293b417e1a710ecd987c9fc (diff) | |
| download | bmcweb-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.hpp | 729 | ||||
| -rw-r--r-- | test/meson.build | 1 | ||||
| -rw-r--r-- | test/redfish-core/lib/openbmc_managers_test.cpp | 482 |
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 |
