xref: /openbmc/bmcweb/redfish-core/lib/openbmc/openbmc_managers.hpp (revision 96a194c4a8cbd2c10dd44650031c064eddf30252)
1 #pragma once
2 
3 #include "bmcweb_config.h"
4 
5 #include "async_resp.hpp"
6 #include "dbus_singleton.hpp"
7 #include "dbus_utility.hpp"
8 #include "error_messages.hpp"
9 #include "io_context_singleton.hpp"
10 #include "logging.hpp"
11 #include "redfish.hpp"
12 #include "sub_request.hpp"
13 #include "utils/dbus_utils.hpp"
14 #include "utils/json_utils.hpp"
15 #include "verb.hpp"
16 
17 #include <boost/asio/post.hpp>
18 #include <boost/beast/http/status.hpp>
19 #include <boost/container/flat_map.hpp>
20 #include <boost/container/flat_set.hpp>
21 #include <boost/url/format.hpp>
22 #include <boost/url/url.hpp>
23 #include <nlohmann/json.hpp>
24 #include <sdbusplus/asio/property.hpp>
25 #include <sdbusplus/message/native_types.hpp>
26 #include <sdbusplus/unpack_properties.hpp>
27 
28 #include <algorithm>
29 #include <array>
30 #include <cstddef>
31 #include <functional>
32 #include <map>
33 #include <memory>
34 #include <optional>
35 #include <string>
36 #include <string_view>
37 #include <utility>
38 #include <variant>
39 #include <vector>
40 
41 namespace redfish
42 {
43 
44 static constexpr const char* objectManagerIface =
45     "org.freedesktop.DBus.ObjectManager";
46 static constexpr const char* pidConfigurationIface =
47     "xyz.openbmc_project.Configuration.Pid";
48 static constexpr const char* pidZoneConfigurationIface =
49     "xyz.openbmc_project.Configuration.Pid.Zone";
50 static constexpr const char* stepwiseConfigurationIface =
51     "xyz.openbmc_project.Configuration.Stepwise";
52 static constexpr const char* thermalModeIface =
53     "xyz.openbmc_project.Control.ThermalMode";
54 
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)55 inline void asyncPopulatePid(
56     const std::string& connection, const std::string& path,
57     const std::string& currentProfile,
58     const std::vector<std::string>& supportedProfiles,
59     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
60 {
61     sdbusplus::message::object_path objPath(path);
62     dbus::utility::getManagedObjects(
63         connection, objPath,
64         [asyncResp, currentProfile, supportedProfiles](
65             const boost::system::error_code& ec,
66             const dbus::utility::ManagedObjectType& managedObj) {
67             if (ec)
68             {
69                 BMCWEB_LOG_ERROR("{}", ec);
70                 messages::internalError(asyncResp->res);
71                 return;
72             }
73             nlohmann::json& configRoot = asyncResp->res.jsonValue["Fan"];
74             nlohmann::json& fans = configRoot["FanControllers"];
75             fans["@odata.type"] =
76                 "#OpenBMCManager.v1_0_0.Manager.FanControllers";
77             fans["@odata.id"] = boost::urls::format(
78                 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanControllers",
79                 BMCWEB_REDFISH_MANAGER_URI_NAME);
80 
81             nlohmann::json& pids = configRoot["PidControllers"];
82             pids["@odata.type"] =
83                 "#OpenBMCManager.v1_0_0.Manager.PidControllers";
84             pids["@odata.id"] = boost::urls::format(
85                 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/PidControllers",
86                 BMCWEB_REDFISH_MANAGER_URI_NAME);
87 
88             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
89             stepwise["@odata.type"] =
90                 "#OpenBMCManager.v1_0_0.Manager.StepwiseControllers";
91             stepwise["@odata.id"] = boost::urls::format(
92                 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/StepwiseControllers",
93                 BMCWEB_REDFISH_MANAGER_URI_NAME);
94 
95             nlohmann::json& zones = configRoot["FanZones"];
96             zones["@odata.id"] = boost::urls::format(
97                 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanZones",
98                 BMCWEB_REDFISH_MANAGER_URI_NAME);
99             zones["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanZones";
100             configRoot["@odata.id"] =
101                 boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan",
102                                     BMCWEB_REDFISH_MANAGER_URI_NAME);
103             configRoot["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.Fan";
104             configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
105 
106             if (!currentProfile.empty())
107             {
108                 configRoot["Profile"] = currentProfile;
109             }
110             BMCWEB_LOG_DEBUG("profile = {} !", currentProfile);
111 
112             for (const auto& pathPair : managedObj)
113             {
114                 for (const auto& intfPair : pathPair.second)
115                 {
116                     if (intfPair.first != pidConfigurationIface &&
117                         intfPair.first != pidZoneConfigurationIface &&
118                         intfPair.first != stepwiseConfigurationIface)
119                     {
120                         continue;
121                     }
122 
123                     std::string name;
124 
125                     for (const std::pair<std::string,
126                                          dbus::utility::DbusVariantType>&
127                              propPair : intfPair.second)
128                     {
129                         if (propPair.first == "Name")
130                         {
131                             const std::string* namePtr =
132                                 std::get_if<std::string>(&propPair.second);
133                             if (namePtr == nullptr)
134                             {
135                                 BMCWEB_LOG_ERROR("Pid Name Field illegal");
136                                 messages::internalError(asyncResp->res);
137                                 return;
138                             }
139                             name = *namePtr;
140                             dbus::utility::escapePathForDbus(name);
141                         }
142                         else if (propPair.first == "Profiles")
143                         {
144                             const std::vector<std::string>* profiles =
145                                 std::get_if<std::vector<std::string>>(
146                                     &propPair.second);
147                             if (profiles == nullptr)
148                             {
149                                 BMCWEB_LOG_ERROR("Pid Profiles Field illegal");
150                                 messages::internalError(asyncResp->res);
151                                 return;
152                             }
153                             if (std::ranges::find(*profiles, currentProfile) ==
154                                 profiles->end())
155                             {
156                                 BMCWEB_LOG_INFO(
157                                     "{} not supported in current profile",
158                                     name);
159                                 continue;
160                             }
161                         }
162                     }
163                     nlohmann::json* config = nullptr;
164                     const std::string* classPtr = nullptr;
165 
166                     for (const std::pair<std::string,
167                                          dbus::utility::DbusVariantType>&
168                              propPair : intfPair.second)
169                     {
170                         if (propPair.first == "Class")
171                         {
172                             classPtr =
173                                 std::get_if<std::string>(&propPair.second);
174                         }
175                     }
176 
177                     boost::urls::url url(
178                         boost::urls::format("/redfish/v1/Managers/{}",
179                                             BMCWEB_REDFISH_MANAGER_URI_NAME));
180                     if (intfPair.first == pidZoneConfigurationIface)
181                     {
182                         sdbusplus::message::object_path pidPath(
183                             pathPair.first.str);
184                         std::string chassis = pidPath.filename();
185                         if (chassis.empty())
186                         {
187                             chassis = "#IllegalValue";
188                         }
189                         nlohmann::json& zone = zones[name];
190                         zone["Chassis"]["@odata.id"] = boost::urls::format(
191                             "/redfish/v1/Chassis/{}", chassis);
192                         url.set_fragment(
193                             ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name)
194                                 .to_string());
195                         zone["@odata.id"] = std::move(url);
196                         zone["@odata.type"] =
197                             "#OpenBMCManager.v1_0_0.Manager.FanZone";
198                         config = &zone;
199                     }
200 
201                     else if (intfPair.first == stepwiseConfigurationIface)
202                     {
203                         if (classPtr == nullptr)
204                         {
205                             BMCWEB_LOG_ERROR("Pid Class Field illegal");
206                             messages::internalError(asyncResp->res);
207                             return;
208                         }
209 
210                         nlohmann::json& controller = stepwise[name];
211                         config = &controller;
212                         url.set_fragment(
213                             ("/Oem/OpenBmc/Fan/StepwiseControllers"_json_pointer /
214                              name)
215                                 .to_string());
216                         controller["@odata.id"] = std::move(url);
217                         controller["@odata.type"] =
218                             "#OpenBMCManager.v1_0_0.Manager.StepwiseController";
219 
220                         controller["Direction"] = *classPtr;
221                     }
222 
223                     // pid and fans are off the same configuration
224                     else if (intfPair.first == pidConfigurationIface)
225                     {
226                         if (classPtr == nullptr)
227                         {
228                             BMCWEB_LOG_ERROR("Pid Class Field illegal");
229                             messages::internalError(asyncResp->res);
230                             return;
231                         }
232                         bool isFan = *classPtr == "fan";
233                         nlohmann::json& element =
234                             isFan ? fans[name] : pids[name];
235                         config = &element;
236                         if (isFan)
237                         {
238                             url.set_fragment(
239                                 ("/Oem/OpenBmc/Fan/FanControllers"_json_pointer /
240                                  name)
241                                     .to_string());
242                             element["@odata.id"] = std::move(url);
243                             element["@odata.type"] =
244                                 "#OpenBMCManager.v1_0_0.Manager.FanController";
245                         }
246                         else
247                         {
248                             url.set_fragment(
249                                 ("/Oem/OpenBmc/Fan/PidControllers"_json_pointer /
250                                  name)
251                                     .to_string());
252                             element["@odata.id"] = std::move(url);
253                             element["@odata.type"] =
254                                 "#OpenBMCManager.v1_0_0.Manager.PidController";
255                         }
256                     }
257                     else
258                     {
259                         BMCWEB_LOG_ERROR("Unexpected configuration");
260                         messages::internalError(asyncResp->res);
261                         return;
262                     }
263 
264                     // used for making maps out of 2 vectors
265                     const std::vector<double>* keys = nullptr;
266                     const std::vector<double>* values = nullptr;
267 
268                     for (const auto& propertyPair : intfPair.second)
269                     {
270                         if (propertyPair.first == "Type" ||
271                             propertyPair.first == "Class" ||
272                             propertyPair.first == "Name" ||
273                             propertyPair.first == "AccumulateSetPoint")
274                         {
275                             continue;
276                         }
277 
278                         // zones
279                         if (intfPair.first == pidZoneConfigurationIface)
280                         {
281                             const double* ptr =
282                                 std::get_if<double>(&propertyPair.second);
283                             if (ptr == nullptr)
284                             {
285                                 BMCWEB_LOG_ERROR("Field Illegal {}",
286                                                  propertyPair.first);
287                                 messages::internalError(asyncResp->res);
288                                 return;
289                             }
290                             (*config)[propertyPair.first] = *ptr;
291                         }
292 
293                         if (intfPair.first == stepwiseConfigurationIface)
294                         {
295                             if (propertyPair.first == "Reading" ||
296                                 propertyPair.first == "Output")
297                             {
298                                 const std::vector<double>* ptr =
299                                     std::get_if<std::vector<double>>(
300                                         &propertyPair.second);
301 
302                                 if (ptr == nullptr)
303                                 {
304                                     BMCWEB_LOG_ERROR("Field Illegal {}",
305                                                      propertyPair.first);
306                                     messages::internalError(asyncResp->res);
307                                     return;
308                                 }
309 
310                                 if (propertyPair.first == "Reading")
311                                 {
312                                     keys = ptr;
313                                 }
314                                 else
315                                 {
316                                     values = ptr;
317                                 }
318                                 if (keys != nullptr && values != nullptr)
319                                 {
320                                     if (keys->size() != values->size())
321                                     {
322                                         BMCWEB_LOG_ERROR(
323                                             "Reading and Output size don't match ");
324                                         messages::internalError(asyncResp->res);
325                                         return;
326                                     }
327                                     nlohmann::json& steps = (*config)["Steps"];
328                                     steps = nlohmann::json::array();
329                                     for (size_t ii = 0; ii < keys->size(); ii++)
330                                     {
331                                         nlohmann::json::object_t step;
332                                         step["Target"] = (*keys)[ii];
333                                         step["Output"] = (*values)[ii];
334                                         steps.emplace_back(std::move(step));
335                                     }
336                                 }
337                             }
338                             if (propertyPair.first == "NegativeHysteresis" ||
339                                 propertyPair.first == "PositiveHysteresis")
340                             {
341                                 const double* ptr =
342                                     std::get_if<double>(&propertyPair.second);
343                                 if (ptr == nullptr)
344                                 {
345                                     BMCWEB_LOG_ERROR("Field Illegal {}",
346                                                      propertyPair.first);
347                                     messages::internalError(asyncResp->res);
348                                     return;
349                                 }
350                                 (*config)[propertyPair.first] = *ptr;
351                             }
352                         }
353 
354                         // pid and fans are off the same configuration
355                         if (intfPair.first == pidConfigurationIface ||
356                             intfPair.first == stepwiseConfigurationIface)
357                         {
358                             if (propertyPair.first == "Zones")
359                             {
360                                 const std::vector<std::string>* inputs =
361                                     std::get_if<std::vector<std::string>>(
362                                         &propertyPair.second);
363 
364                                 if (inputs == nullptr)
365                                 {
366                                     BMCWEB_LOG_ERROR("Zones Pid Field Illegal");
367                                     messages::internalError(asyncResp->res);
368                                     return;
369                                 }
370                                 auto& data = (*config)[propertyPair.first];
371                                 data = nlohmann::json::array();
372                                 for (std::string itemCopy : *inputs)
373                                 {
374                                     dbus::utility::escapePathForDbus(itemCopy);
375                                     nlohmann::json::object_t input;
376                                     boost::urls::url managerUrl =
377                                         boost::urls::format(
378                                             "/redfish/v1/Managers/{}#{}",
379                                             BMCWEB_REDFISH_MANAGER_URI_NAME,
380                                             ("/Oem/OpenBmc/Fan/FanZones"_json_pointer /
381                                              itemCopy)
382                                                 .to_string());
383                                     input["@odata.id"] = std::move(managerUrl);
384                                     data.emplace_back(std::move(input));
385                                 }
386                             }
387                             // todo(james): may never happen, but this
388                             // assumes configuration data referenced in the
389                             // PID config is provided by the same daemon, we
390                             // could add another loop to cover all cases,
391                             // but I'm okay kicking this can down the road a
392                             // bit
393 
394                             else if (propertyPair.first == "Inputs" ||
395                                      propertyPair.first == "Outputs")
396                             {
397                                 auto& data = (*config)[propertyPair.first];
398                                 const std::vector<std::string>* inputs =
399                                     std::get_if<std::vector<std::string>>(
400                                         &propertyPair.second);
401 
402                                 if (inputs == nullptr)
403                                 {
404                                     BMCWEB_LOG_ERROR("Field Illegal {}",
405                                                      propertyPair.first);
406                                     messages::internalError(asyncResp->res);
407                                     return;
408                                 }
409                                 data = *inputs;
410                             }
411                             else if (propertyPair.first == "SetPointOffset")
412                             {
413                                 const std::string* ptr =
414                                     std::get_if<std::string>(
415                                         &propertyPair.second);
416 
417                                 if (ptr == nullptr)
418                                 {
419                                     BMCWEB_LOG_ERROR("Field Illegal {}",
420                                                      propertyPair.first);
421                                     messages::internalError(asyncResp->res);
422                                     return;
423                                 }
424                                 // translate from dbus to redfish
425                                 if (*ptr == "WarningHigh")
426                                 {
427                                     (*config)["SetPointOffset"] =
428                                         "UpperThresholdNonCritical";
429                                 }
430                                 else if (*ptr == "WarningLow")
431                                 {
432                                     (*config)["SetPointOffset"] =
433                                         "LowerThresholdNonCritical";
434                                 }
435                                 else if (*ptr == "CriticalHigh")
436                                 {
437                                     (*config)["SetPointOffset"] =
438                                         "UpperThresholdCritical";
439                                 }
440                                 else if (*ptr == "CriticalLow")
441                                 {
442                                     (*config)["SetPointOffset"] =
443                                         "LowerThresholdCritical";
444                                 }
445                                 else
446                                 {
447                                     BMCWEB_LOG_ERROR("Value Illegal {}", *ptr);
448                                     messages::internalError(asyncResp->res);
449                                     return;
450                                 }
451                             }
452                             // doubles
453                             else if (propertyPair.first ==
454                                          "FFGainCoefficient" ||
455                                      propertyPair.first == "FFOffCoefficient" ||
456                                      propertyPair.first == "ICoefficient" ||
457                                      propertyPair.first == "ILimitMax" ||
458                                      propertyPair.first == "ILimitMin" ||
459                                      propertyPair.first ==
460                                          "PositiveHysteresis" ||
461                                      propertyPair.first ==
462                                          "NegativeHysteresis" ||
463                                      propertyPair.first == "OutLimitMax" ||
464                                      propertyPair.first == "OutLimitMin" ||
465                                      propertyPair.first == "PCoefficient" ||
466                                      propertyPair.first == "SetPoint" ||
467                                      propertyPair.first == "SlewNeg" ||
468                                      propertyPair.first == "SlewPos")
469                             {
470                                 const double* ptr =
471                                     std::get_if<double>(&propertyPair.second);
472                                 if (ptr == nullptr)
473                                 {
474                                     BMCWEB_LOG_ERROR("Field Illegal {}",
475                                                      propertyPair.first);
476                                     messages::internalError(asyncResp->res);
477                                     return;
478                                 }
479                                 (*config)[propertyPair.first] = *ptr;
480                             }
481                         }
482                     }
483                 }
484             }
485         });
486 }
487 
488 enum class CreatePIDRet
489 {
490     fail,
491     del,
492     patch
493 };
494 
findChassis(const dbus::utility::ManagedObjectType & managedObj,std::string_view value,std::string & chassis)495 inline const dbus::utility::ManagedObjectType::value_type* findChassis(
496     const dbus::utility::ManagedObjectType& managedObj, std::string_view value,
497     std::string& chassis)
498 {
499     BMCWEB_LOG_DEBUG("Find Chassis: {}", value);
500 
501     std::string escaped(value);
502     std::ranges::replace(escaped, ' ', '_');
503     escaped = "/" + escaped;
504     auto it = std::ranges::find_if(managedObj, [&escaped](const auto& obj) {
505         if (obj.first.str.ends_with(escaped))
506         {
507             BMCWEB_LOG_DEBUG("Matched {}", obj.first.str);
508             return true;
509         }
510         return false;
511     });
512 
513     if (it == managedObj.end())
514     {
515         return nullptr;
516     }
517     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
518     sdbusplus::message::object_path path(it->first.str);
519     chassis = path.filename();
520 
521     return &(*it);
522 }
523 
getZonesFromJsonReq(const std::shared_ptr<bmcweb::AsyncResp> & response,std::vector<nlohmann::json::object_t> & config,std::vector<std::string> & zones)524 inline bool getZonesFromJsonReq(
525     const std::shared_ptr<bmcweb::AsyncResp>& response,
526     std::vector<nlohmann::json::object_t>& config,
527     std::vector<std::string>& zones)
528 {
529     if (config.empty())
530     {
531         BMCWEB_LOG_ERROR("Empty Zones");
532         messages::propertyValueFormatError(response->res, config, "Zones");
533         return false;
534     }
535     for (auto& odata : config)
536     {
537         std::string path;
538         if (!redfish::json_util::readJsonObject(odata, response->res,
539                                                 "@odata.id", path))
540         {
541             return false;
542         }
543 
544         boost::system::result<boost::urls::url_view> parsed =
545             boost::urls::parse_relative_ref(path);
546         if (!parsed)
547         {
548             BMCWEB_LOG_WARNING("Got invalid path {}", path);
549             messages::propertyValueFormatError(response->res, path, "Zones");
550             return false;
551         }
552 
553         std::string input;
554 
555         // 8 below comes from
556         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
557         std::string managerId;
558         if (!crow::utility::readUrlSegments(
559                 *parsed, "redfish", "v1", "Managers", std::ref(managerId),
560                 "Oem", "OpenBmc", "Fan", "FanZones", std::ref(input)))
561         {
562             BMCWEB_LOG_ERROR("Got invalid path {}", path);
563             BMCWEB_LOG_ERROR("Illegal Type Zones");
564             messages::propertyValueFormatError(response->res, odata, "Zones");
565             return false;
566         }
567         std::ranges::replace(input, '_', ' ');
568         zones.emplace_back(std::move(input));
569     }
570     return true;
571 }
572 
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)573 inline CreatePIDRet createPidInterface(
574     const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type,
575     std::string_view name, nlohmann::json& jsonValue, const std::string& path,
576     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
577     dbus::utility::DBusPropertiesMap& output, std::string& chassis,
578     const std::string& profile)
579 {
580     // common deleter
581     if (jsonValue == nullptr)
582     {
583         std::string iface;
584         if (type == "PidControllers" || type == "FanControllers")
585         {
586             iface = pidConfigurationIface;
587         }
588         else if (type == "FanZones")
589         {
590             iface = pidZoneConfigurationIface;
591         }
592         else if (type == "StepwiseControllers")
593         {
594             iface = stepwiseConfigurationIface;
595         }
596         else
597         {
598             BMCWEB_LOG_ERROR("Illegal Type {}", type);
599             messages::propertyUnknown(response->res, type);
600             return CreatePIDRet::fail;
601         }
602 
603         BMCWEB_LOG_DEBUG("del {} {}", path, iface);
604         // delete interface
605         dbus::utility::async_method_call(
606             response,
607             [response, path](const boost::system::error_code& ec) {
608                 if (ec)
609                 {
610                     BMCWEB_LOG_ERROR("Error patching {}: {}", path, ec);
611                     messages::internalError(response->res);
612                     return;
613                 }
614                 messages::success(response->res);
615             },
616             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
617         return CreatePIDRet::del;
618     }
619 
620     const dbus::utility::ManagedObjectType::value_type* managedItem = nullptr;
621     if (!createNewObject)
622     {
623         // if we aren't creating a new object, we should be able to find it on
624         // d-bus
625         managedItem = findChassis(managedObj, name, chassis);
626         if (managedItem == nullptr)
627         {
628             BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
629             messages::invalidObject(
630                 response->res,
631                 boost::urls::format("/redfish/v1/Chassis/{}", chassis));
632             return CreatePIDRet::fail;
633         }
634     }
635 
636     if (!profile.empty() &&
637         (type == "PidControllers" || type == "FanControllers" ||
638          type == "StepwiseControllers"))
639     {
640         if (managedItem == nullptr)
641         {
642             output.emplace_back("Profiles", std::vector<std::string>{profile});
643         }
644         else
645         {
646             std::string interface;
647             if (type == "StepwiseControllers")
648             {
649                 interface = stepwiseConfigurationIface;
650             }
651             else
652             {
653                 interface = pidConfigurationIface;
654             }
655             bool ifaceFound = false;
656             for (const auto& iface : managedItem->second)
657             {
658                 if (iface.first == interface)
659                 {
660                     ifaceFound = true;
661                     for (const auto& prop : iface.second)
662                     {
663                         if (prop.first == "Profiles")
664                         {
665                             const std::vector<std::string>* curProfiles =
666                                 std::get_if<std::vector<std::string>>(
667                                     &(prop.second));
668                             if (curProfiles == nullptr)
669                             {
670                                 BMCWEB_LOG_ERROR(
671                                     "Illegal profiles in managed object");
672                                 messages::internalError(response->res);
673                                 return CreatePIDRet::fail;
674                             }
675                             if (std::ranges::find(*curProfiles, profile) ==
676                                 curProfiles->end())
677                             {
678                                 std::vector<std::string> newProfiles =
679                                     *curProfiles;
680                                 newProfiles.push_back(profile);
681                                 output.emplace_back("Profiles", newProfiles);
682                             }
683                         }
684                     }
685                 }
686             }
687 
688             if (!ifaceFound)
689             {
690                 BMCWEB_LOG_ERROR("Failed to find interface in managed object");
691                 messages::internalError(response->res);
692                 return CreatePIDRet::fail;
693             }
694         }
695     }
696 
697     if (type == "PidControllers" || type == "FanControllers")
698     {
699         if (createNewObject)
700         {
701             output.emplace_back("Class",
702                                 type == "PidControllers" ? "temp" : "fan");
703             output.emplace_back("Type", "Pid");
704         }
705 
706         std::optional<std::vector<nlohmann::json::object_t>> zones;
707         std::optional<std::vector<std::string>> inputs;
708         std::optional<std::vector<std::string>> outputs;
709         std::map<std::string, std::optional<double>> doubles;
710         std::optional<std::string> setpointOffset;
711         if (!redfish::json_util::readJson(                           //
712                 jsonValue, response->res,                            //
713                 "FFGainCoefficient", doubles["FFGainCoefficient"],   //
714                 "FFOffCoefficient", doubles["FFOffCoefficient"],     //
715                 "ICoefficient", doubles["ICoefficient"],             //
716                 "ILimitMax", doubles["ILimitMax"],                   //
717                 "ILimitMin", doubles["ILimitMin"],                   //
718                 "Inputs", inputs,                                    //
719                 "NegativeHysteresis", doubles["NegativeHysteresis"], //
720                 "OutLimitMax", doubles["OutLimitMax"],               //
721                 "OutLimitMin", doubles["OutLimitMin"],               //
722                 "Outputs", outputs,                                  //
723                 "PCoefficient", doubles["PCoefficient"],             //
724                 "PositiveHysteresis", doubles["PositiveHysteresis"], //
725                 "SetPoint", doubles["SetPoint"],                     //
726                 "SetPointOffset", setpointOffset,                    //
727                 "SlewNeg", doubles["SlewNeg"],                       //
728                 "SlewPos", doubles["SlewPos"],                       //
729                 "Zones", zones                                       //
730                 ))
731         {
732             return CreatePIDRet::fail;
733         }
734 
735         if (zones)
736         {
737             std::vector<std::string> zonesStr;
738             if (!getZonesFromJsonReq(response, *zones, zonesStr))
739             {
740                 BMCWEB_LOG_ERROR("Illegal Zones");
741                 return CreatePIDRet::fail;
742             }
743             if (chassis.empty() &&
744                 findChassis(managedObj, zonesStr[0], chassis) == nullptr)
745             {
746                 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
747                 messages::invalidObject(
748                     response->res,
749                     boost::urls::format("/redfish/v1/Chassis/{}", chassis));
750                 return CreatePIDRet::fail;
751             }
752             output.emplace_back("Zones", std::move(zonesStr));
753         }
754 
755         if (inputs)
756         {
757             for (std::string& value : *inputs)
758             {
759                 std::ranges::replace(value, '_', ' ');
760             }
761             output.emplace_back("Inputs", *inputs);
762         }
763 
764         if (outputs)
765         {
766             for (std::string& value : *outputs)
767             {
768                 std::ranges::replace(value, '_', ' ');
769             }
770             output.emplace_back("Outputs", *outputs);
771         }
772 
773         if (setpointOffset)
774         {
775             // translate between redfish and dbus names
776             if (*setpointOffset == "UpperThresholdNonCritical")
777             {
778                 output.emplace_back("SetPointOffset", "WarningLow");
779             }
780             else if (*setpointOffset == "LowerThresholdNonCritical")
781             {
782                 output.emplace_back("SetPointOffset", "WarningHigh");
783             }
784             else if (*setpointOffset == "LowerThresholdCritical")
785             {
786                 output.emplace_back("SetPointOffset", "CriticalLow");
787             }
788             else if (*setpointOffset == "UpperThresholdCritical")
789             {
790                 output.emplace_back("SetPointOffset", "CriticalHigh");
791             }
792             else
793             {
794                 BMCWEB_LOG_ERROR("Invalid setpointoffset {}", *setpointOffset);
795                 messages::propertyValueNotInList(response->res, name,
796                                                  "SetPointOffset");
797                 return CreatePIDRet::fail;
798             }
799         }
800 
801         // doubles
802         for (const auto& pairs : doubles)
803         {
804             if (!pairs.second)
805             {
806                 continue;
807             }
808             BMCWEB_LOG_DEBUG("{} = {}", pairs.first, *pairs.second);
809             output.emplace_back(pairs.first, *pairs.second);
810         }
811     }
812 
813     else if (type == "FanZones")
814     {
815         output.emplace_back("Type", "Pid.Zone");
816 
817         std::optional<std::string> chassisId;
818         std::optional<double> failSafePercent;
819         std::optional<double> minThermalOutput;
820         if (!redfish::json_util::readJson(          //
821                 jsonValue, response->res,           //
822                 "Chassis/@odata.id", chassisId,     //
823                 "FailSafePercent", failSafePercent, //
824                 "MinThermalOutput", minThermalOutput))
825         {
826             return CreatePIDRet::fail;
827         }
828 
829         if (chassisId)
830         {
831             boost::system::result<boost::urls::url_view> parsed =
832                 boost::urls::parse_relative_ref(*chassisId);
833             if (!parsed)
834             {
835                 BMCWEB_LOG_WARNING("Got invalid path {}", *chassisId);
836                 messages::propertyValueFormatError(response->res, *chassisId,
837                                                    "Chassis/@odata.id");
838                 return CreatePIDRet::fail;
839             }
840 
841             if (!crow::utility::readUrlSegments(*parsed, "redfish", "v1",
842                                                 "Chassis", std::ref(chassis)))
843             {
844                 BMCWEB_LOG_WARNING("Got invalid path {}", *parsed);
845                 messages::propertyValueFormatError(response->res, *chassisId,
846                                                    "Chassis/@odata.id");
847                 return CreatePIDRet::fail;
848             }
849         }
850 
851         if (minThermalOutput)
852         {
853             output.emplace_back("MinThermalOutput", *minThermalOutput);
854         }
855         if (failSafePercent)
856         {
857             output.emplace_back("FailSafePercent", *failSafePercent);
858         }
859     }
860     else if (type == "StepwiseControllers")
861     {
862         output.emplace_back("Type", "Stepwise");
863 
864         std::optional<std::vector<nlohmann::json::object_t>> zones;
865         std::optional<std::vector<nlohmann::json::object_t>> steps;
866         std::optional<std::vector<std::string>> inputs;
867         std::optional<double> positiveHysteresis;
868         std::optional<double> negativeHysteresis;
869         std::optional<std::string> direction; // upper clipping curve vs lower
870         if (!redfish::json_util::readJson(    //
871                 jsonValue, response->res,     //
872                 "Direction", direction,       //
873                 "Inputs", inputs,             //
874                 "NegativeHysteresis", negativeHysteresis, //
875                 "PositiveHysteresis", positiveHysteresis, //
876                 "Steps", steps,                           //
877                 "Zones", zones                            //
878                 ))
879         {
880             return CreatePIDRet::fail;
881         }
882 
883         if (zones)
884         {
885             std::vector<std::string> zonesStrs;
886             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
887             {
888                 BMCWEB_LOG_ERROR("Illegal Zones");
889                 return CreatePIDRet::fail;
890             }
891             if (chassis.empty() &&
892                 findChassis(managedObj, zonesStrs[0], chassis) == nullptr)
893             {
894                 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
895                 messages::invalidObject(
896                     response->res,
897                     boost::urls::format("/redfish/v1/Chassis/{}", chassis));
898                 return CreatePIDRet::fail;
899             }
900             output.emplace_back("Zones", std::move(zonesStrs));
901         }
902         if (steps)
903         {
904             std::vector<double> readings;
905             std::vector<double> outputs;
906             for (auto& step : *steps)
907             {
908                 double target = 0.0;
909                 double out = 0.0;
910 
911                 if (!redfish::json_util::readJsonObject( //
912                         step, response->res,             //
913                         "Output", out,                   //
914                         "Target", target                 //
915                         ))
916                 {
917                     return CreatePIDRet::fail;
918                 }
919                 readings.emplace_back(target);
920                 outputs.emplace_back(out);
921             }
922             output.emplace_back("Reading", std::move(readings));
923             output.emplace_back("Output", std::move(outputs));
924         }
925         if (inputs)
926         {
927             for (std::string& value : *inputs)
928             {
929                 std::ranges::replace(value, '_', ' ');
930             }
931             output.emplace_back("Inputs", std::move(*inputs));
932         }
933         if (negativeHysteresis)
934         {
935             output.emplace_back("NegativeHysteresis", *negativeHysteresis);
936         }
937         if (positiveHysteresis)
938         {
939             output.emplace_back("PositiveHysteresis", *positiveHysteresis);
940         }
941         if (direction)
942         {
943             constexpr const std::array<const char*, 2> allowedDirections = {
944                 "Ceiling", "Floor"};
945             if (std::ranges::find(allowedDirections, *direction) ==
946                 allowedDirections.end())
947             {
948                 messages::propertyValueTypeError(response->res, "Direction",
949                                                  *direction);
950                 return CreatePIDRet::fail;
951             }
952             output.emplace_back("Class", *direction);
953         }
954     }
955     else
956     {
957         BMCWEB_LOG_ERROR("Illegal Type {}", type);
958         messages::propertyUnknown(response->res, type);
959         return CreatePIDRet::fail;
960     }
961     return CreatePIDRet::patch;
962 }
963 
964 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
965 {
966     struct CompletionValues
967     {
968         std::vector<std::string> supportedProfiles;
969         std::string currentProfile;
970         dbus::utility::MapperGetSubTreeResponse subtree;
971     };
972 
GetPIDValuesredfish::GetPIDValues973     explicit GetPIDValues(
974         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
975         asyncResp(asyncRespIn)
976 
977     {}
978 
runredfish::GetPIDValues979     void run()
980     {
981         std::shared_ptr<GetPIDValues> self = shared_from_this();
982 
983         // get all configurations
984         constexpr std::array<std::string_view, 4> interfaces = {
985             pidConfigurationIface, pidZoneConfigurationIface,
986             objectManagerIface, stepwiseConfigurationIface};
987         dbus::utility::getSubTree(
988             "/", 0, interfaces,
989             [self](
990                 const boost::system::error_code& ec,
991                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
992                 if (ec)
993                 {
994                     BMCWEB_LOG_ERROR("{}", ec);
995                     messages::internalError(self->asyncResp->res);
996                     return;
997                 }
998                 self->complete.subtree = subtreeLocal;
999             });
1000 
1001         // at the same time get the selected profile
1002         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1003             thermalModeIface};
1004         dbus::utility::getSubTree(
1005             "/", 0, thermalModeIfaces,
1006             [self](
1007                 const boost::system::error_code& ec,
1008                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1009                 if (ec || subtreeLocal.empty())
1010                 {
1011                     return;
1012                 }
1013                 if (subtreeLocal[0].second.size() != 1)
1014                 {
1015                     // invalid mapper response, should never happen
1016                     BMCWEB_LOG_ERROR("GetPIDValues: Mapper Error");
1017                     messages::internalError(self->asyncResp->res);
1018                     return;
1019                 }
1020 
1021                 const std::string& path = subtreeLocal[0].first;
1022                 const std::string& owner = subtreeLocal[0].second[0].first;
1023 
1024                 dbus::utility::getAllProperties(
1025                     *crow::connections::systemBus, owner, path,
1026                     thermalModeIface,
1027                     [path, owner,
1028                      self](const boost::system::error_code& ec2,
1029                            const dbus::utility::DBusPropertiesMap& resp) {
1030                         if (ec2)
1031                         {
1032                             BMCWEB_LOG_ERROR(
1033                                 "GetPIDValues: Can't get thermalModeIface {}",
1034                                 path);
1035                             messages::internalError(self->asyncResp->res);
1036                             return;
1037                         }
1038 
1039                         const std::string* current = nullptr;
1040                         const std::vector<std::string>* supported = nullptr;
1041 
1042                         const bool success = sdbusplus::unpackPropertiesNoThrow(
1043                             dbus_utils::UnpackErrorPrinter(), resp, "Current",
1044                             current, "Supported", supported);
1045 
1046                         if (!success)
1047                         {
1048                             messages::internalError(self->asyncResp->res);
1049                             return;
1050                         }
1051 
1052                         if (current == nullptr || supported == nullptr)
1053                         {
1054                             BMCWEB_LOG_ERROR(
1055                                 "GetPIDValues: thermal mode iface invalid {}",
1056                                 path);
1057                             messages::internalError(self->asyncResp->res);
1058                             return;
1059                         }
1060                         self->complete.currentProfile = *current;
1061                         self->complete.supportedProfiles = *supported;
1062                     });
1063             });
1064     }
1065 
processingCompleteredfish::GetPIDValues1066     static void processingComplete(
1067         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1068         const CompletionValues& completion)
1069     {
1070         if (asyncResp->res.result() != boost::beast::http::status::ok)
1071         {
1072             return;
1073         }
1074         // create map of <connection, path to objMgr>>
1075         boost::container::flat_map<
1076             std::string, std::string, std::less<>,
1077             std::vector<std::pair<std::string, std::string>>>
1078             objectMgrPaths;
1079         boost::container::flat_set<std::string, std::less<>,
1080                                    std::vector<std::string>>
1081             calledConnections;
1082         for (const auto& pathGroup : completion.subtree)
1083         {
1084             for (const auto& connectionGroup : pathGroup.second)
1085             {
1086                 auto findConnection =
1087                     calledConnections.find(connectionGroup.first);
1088                 if (findConnection != calledConnections.end())
1089                 {
1090                     break;
1091                 }
1092                 for (const std::string& interface : connectionGroup.second)
1093                 {
1094                     if (interface == objectManagerIface)
1095                     {
1096                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1097                     }
1098                     // this list is alphabetical, so we
1099                     // should have found the objMgr by now
1100                     if (interface == pidConfigurationIface ||
1101                         interface == pidZoneConfigurationIface ||
1102                         interface == stepwiseConfigurationIface)
1103                     {
1104                         auto findObjMgr =
1105                             objectMgrPaths.find(connectionGroup.first);
1106                         if (findObjMgr == objectMgrPaths.end())
1107                         {
1108                             BMCWEB_LOG_DEBUG("{}Has no Object Manager",
1109                                              connectionGroup.first);
1110                             continue;
1111                         }
1112 
1113                         calledConnections.insert(connectionGroup.first);
1114 
1115                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1116                                          completion.currentProfile,
1117                                          completion.supportedProfiles,
1118                                          asyncResp);
1119                         break;
1120                     }
1121                 }
1122             }
1123         }
1124     }
1125 
~GetPIDValuesredfish::GetPIDValues1126     ~GetPIDValues()
1127     {
1128         boost::asio::post(getIoContext(),
1129                           std::bind_front(&processingComplete, asyncResp,
1130                                           std::move(complete)));
1131     }
1132 
1133     GetPIDValues(const GetPIDValues&) = delete;
1134     GetPIDValues(GetPIDValues&&) = delete;
1135     GetPIDValues& operator=(const GetPIDValues&) = delete;
1136     GetPIDValues& operator=(GetPIDValues&&) = delete;
1137 
1138     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1139     CompletionValues complete;
1140 };
1141 
1142 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1143 {
SetPIDValuesredfish::SetPIDValues1144     SetPIDValues(
1145         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
1146         std::vector<
1147             std::pair<std::string, std::optional<nlohmann::json::object_t>>>&&
1148             configurationsIn,
1149         std::optional<std::string>& profileIn) :
1150         asyncResp(asyncRespIn), configuration(std::move(configurationsIn)),
1151         profile(std::move(profileIn))
1152     {}
1153 
1154     SetPIDValues(const SetPIDValues&) = delete;
1155     SetPIDValues(SetPIDValues&&) = delete;
1156     SetPIDValues& operator=(const SetPIDValues&) = delete;
1157     SetPIDValues& operator=(SetPIDValues&&) = delete;
1158 
runredfish::SetPIDValues1159     void run()
1160     {
1161         if (asyncResp->res.result() != boost::beast::http::status::ok)
1162         {
1163             return;
1164         }
1165 
1166         std::shared_ptr<SetPIDValues> self = shared_from_this();
1167 
1168         // todo(james): might make sense to do a mapper call here if this
1169         // interface gets more traction
1170         sdbusplus::message::object_path objPath(
1171             "/xyz/openbmc_project/inventory");
1172         dbus::utility::getManagedObjects(
1173             "xyz.openbmc_project.EntityManager", objPath,
1174             [self](const boost::system::error_code& ec,
1175                    const dbus::utility::ManagedObjectType& mObj) {
1176                 if (ec)
1177                 {
1178                     BMCWEB_LOG_ERROR("Error communicating to Entity Manager");
1179                     messages::internalError(self->asyncResp->res);
1180                     return;
1181                 }
1182                 const std::array<const char*, 3> configurations = {
1183                     pidConfigurationIface, pidZoneConfigurationIface,
1184                     stepwiseConfigurationIface};
1185 
1186                 for (const auto& [path, object] : mObj)
1187                 {
1188                     for (const auto& [interface, _] : object)
1189                     {
1190                         if (std::ranges::find(configurations, interface) !=
1191                             configurations.end())
1192                         {
1193                             self->objectCount++;
1194                             break;
1195                         }
1196                     }
1197                 }
1198                 self->managedObj = mObj;
1199             });
1200 
1201         // at the same time get the profile information
1202         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1203             thermalModeIface};
1204         dbus::utility::getSubTree(
1205             "/", 0, thermalModeIfaces,
1206             [self](const boost::system::error_code& ec,
1207                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
1208                 if (ec || subtree.empty())
1209                 {
1210                     return;
1211                 }
1212                 if (subtree[0].second.empty())
1213                 {
1214                     // invalid mapper response, should never happen
1215                     BMCWEB_LOG_ERROR("SetPIDValues: Mapper Error");
1216                     messages::internalError(self->asyncResp->res);
1217                     return;
1218                 }
1219 
1220                 const std::string& path = subtree[0].first;
1221                 const std::string& owner = subtree[0].second[0].first;
1222                 dbus::utility::getAllProperties(
1223                     *crow::connections::systemBus, owner, path,
1224                     thermalModeIface,
1225                     [self, path,
1226                      owner](const boost::system::error_code& ec2,
1227                             const dbus::utility::DBusPropertiesMap& r) {
1228                         if (ec2)
1229                         {
1230                             BMCWEB_LOG_ERROR(
1231                                 "SetPIDValues: Can't get thermalModeIface {}",
1232                                 path);
1233                             messages::internalError(self->asyncResp->res);
1234                             return;
1235                         }
1236                         const std::string* current = nullptr;
1237                         const std::vector<std::string>* supported = nullptr;
1238 
1239                         const bool success = sdbusplus::unpackPropertiesNoThrow(
1240                             dbus_utils::UnpackErrorPrinter(), r, "Current",
1241                             current, "Supported", supported);
1242 
1243                         if (!success)
1244                         {
1245                             messages::internalError(self->asyncResp->res);
1246                             return;
1247                         }
1248 
1249                         if (current == nullptr || supported == nullptr)
1250                         {
1251                             BMCWEB_LOG_ERROR(
1252                                 "SetPIDValues: thermal mode iface invalid {}",
1253                                 path);
1254                             messages::internalError(self->asyncResp->res);
1255                             return;
1256                         }
1257                         self->currentProfile = *current;
1258                         self->supportedProfiles = *supported;
1259                         self->profileConnection = owner;
1260                         self->profilePath = path;
1261                     });
1262             });
1263     }
pidSetDoneredfish::SetPIDValues1264     void pidSetDone()
1265     {
1266         if (asyncResp->res.result() != boost::beast::http::status::ok)
1267         {
1268             return;
1269         }
1270         std::shared_ptr<bmcweb::AsyncResp> response = asyncResp;
1271         if (profile)
1272         {
1273             if (std::ranges::find(supportedProfiles, *profile) ==
1274                 supportedProfiles.end())
1275             {
1276                 messages::actionParameterUnknown(response->res, "Profile",
1277                                                  *profile);
1278                 return;
1279             }
1280             currentProfile = *profile;
1281             sdbusplus::asio::setProperty(
1282                 *crow::connections::systemBus, profileConnection, profilePath,
1283                 thermalModeIface, "Current", *profile,
1284                 [response](const boost::system::error_code& ec) {
1285                     if (ec)
1286                     {
1287                         BMCWEB_LOG_ERROR("Error patching profile{}", ec);
1288                         messages::internalError(response->res);
1289                     }
1290                 });
1291         }
1292 
1293         for (auto& containerPair : configuration)
1294         {
1295             auto& container = containerPair.second;
1296             if (!container)
1297             {
1298                 continue;
1299             }
1300 
1301             const std::string& type = containerPair.first;
1302 
1303             for (auto& [name, value] : *container)
1304             {
1305                 std::string dbusObjName = name;
1306                 std::ranges::replace(dbusObjName, ' ', '_');
1307                 BMCWEB_LOG_DEBUG("looking for {}", name);
1308 
1309                 auto pathItr = std::ranges::find_if(
1310                     managedObj, [&dbusObjName](const auto& obj) {
1311                         return obj.first.filename() == dbusObjName;
1312                     });
1313                 dbus::utility::DBusPropertiesMap output;
1314 
1315                 output.reserve(16); // The pid interface length
1316 
1317                 // determines if we're patching entity-manager or
1318                 // creating a new object
1319                 bool createNewObject = (pathItr == managedObj.end());
1320                 BMCWEB_LOG_DEBUG("Found = {}", !createNewObject);
1321 
1322                 std::string iface;
1323                 if (!createNewObject)
1324                 {
1325                     bool findInterface = false;
1326                     for (const auto& interface : pathItr->second)
1327                     {
1328                         if (interface.first == pidConfigurationIface)
1329                         {
1330                             if (type == "PidControllers" ||
1331                                 type == "FanControllers")
1332                             {
1333                                 iface = pidConfigurationIface;
1334                                 findInterface = true;
1335                                 break;
1336                             }
1337                         }
1338                         else if (interface.first == pidZoneConfigurationIface)
1339                         {
1340                             if (type == "FanZones")
1341                             {
1342                                 iface = pidZoneConfigurationIface;
1343                                 findInterface = true;
1344                                 break;
1345                             }
1346                         }
1347                         else if (interface.first == stepwiseConfigurationIface)
1348                         {
1349                             if (type == "StepwiseControllers")
1350                             {
1351                                 iface = stepwiseConfigurationIface;
1352                                 findInterface = true;
1353                                 break;
1354                             }
1355                         }
1356                     }
1357 
1358                     // create new object if interface not found
1359                     if (!findInterface)
1360                     {
1361                         createNewObject = true;
1362                     }
1363                 }
1364 
1365                 if (createNewObject && value == nullptr)
1366                 {
1367                     // can't delete a non-existent object
1368                     messages::propertyValueNotInList(response->res, value,
1369                                                      name);
1370                     continue;
1371                 }
1372 
1373                 std::string path;
1374                 if (pathItr != managedObj.end())
1375                 {
1376                     path = pathItr->first.str;
1377                 }
1378 
1379                 BMCWEB_LOG_DEBUG("Create new = {}", createNewObject);
1380 
1381                 // arbitrary limit to avoid attacks
1382                 constexpr const size_t controllerLimit = 500;
1383                 if (createNewObject && objectCount >= controllerLimit)
1384                 {
1385                     messages::resourceExhaustion(response->res, type);
1386                     continue;
1387                 }
1388                 std::string escaped = name;
1389                 std::ranges::replace(escaped, '_', ' ');
1390                 output.emplace_back("Name", escaped);
1391 
1392                 std::string chassis;
1393                 CreatePIDRet ret = createPidInterface(
1394                     response, type, name, value, path, managedObj,
1395                     createNewObject, output, chassis, currentProfile);
1396                 if (ret == CreatePIDRet::fail)
1397                 {
1398                     return;
1399                 }
1400                 if (ret == CreatePIDRet::del)
1401                 {
1402                     continue;
1403                 }
1404 
1405                 if (!createNewObject)
1406                 {
1407                     for (const auto& property : output)
1408                     {
1409                         dbus::utility::async_method_call(
1410                             asyncResp,
1411                             [response,
1412                              propertyName{std::string(property.first)}](
1413                                 const boost::system::error_code& ec) {
1414                                 if (ec)
1415                                 {
1416                                     BMCWEB_LOG_ERROR("Error patching {}: {}",
1417                                                      propertyName, ec);
1418                                     messages::internalError(response->res);
1419                                     return;
1420                                 }
1421                                 messages::success(response->res);
1422                             },
1423                             "xyz.openbmc_project.EntityManager", path,
1424                             "org.freedesktop.DBus.Properties", "Set", iface,
1425                             property.first, property.second);
1426                     }
1427                 }
1428                 else
1429                 {
1430                     if (chassis.empty())
1431                     {
1432                         BMCWEB_LOG_ERROR("Failed to get chassis from config");
1433                         messages::internalError(response->res);
1434                         return;
1435                     }
1436 
1437                     bool foundChassis = false;
1438                     for (const auto& obj : managedObj)
1439                     {
1440                         if (obj.first.filename() == chassis)
1441                         {
1442                             chassis = obj.first.str;
1443                             foundChassis = true;
1444                             break;
1445                         }
1446                     }
1447                     if (!foundChassis)
1448                     {
1449                         BMCWEB_LOG_ERROR("Failed to find chassis on dbus");
1450                         messages::resourceMissingAtURI(
1451                             response->res,
1452                             boost::urls::format("/redfish/v1/Chassis/{}",
1453                                                 chassis));
1454                         return;
1455                     }
1456 
1457                     dbus::utility::async_method_call(
1458                         asyncResp,
1459                         [response](const boost::system::error_code& ec) {
1460                             if (ec)
1461                             {
1462                                 BMCWEB_LOG_ERROR("Error Adding Pid Object {}",
1463                                                  ec);
1464                                 messages::internalError(response->res);
1465                                 return;
1466                             }
1467                             messages::success(response->res);
1468                         },
1469                         "xyz.openbmc_project.EntityManager", chassis,
1470                         "xyz.openbmc_project.AddObject", "AddObject", output);
1471                 }
1472             }
1473         }
1474     }
1475 
~SetPIDValuesredfish::SetPIDValues1476     ~SetPIDValues()
1477     {
1478         try
1479         {
1480             pidSetDone();
1481         }
1482         catch (...)
1483         {
1484             BMCWEB_LOG_CRITICAL("pidSetDone threw exception");
1485         }
1486     }
1487 
1488     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1489     std::vector<std::pair<std::string, std::optional<nlohmann::json::object_t>>>
1490         configuration;
1491     std::optional<std::string> profile;
1492     dbus::utility::ManagedObjectType managedObj;
1493     std::vector<std::string> supportedProfiles;
1494     std::string currentProfile;
1495     std::string profileConnection;
1496     std::string profilePath;
1497     size_t objectCount = 0;
1498 };
1499 
handleGetManagerOpenBmc(const SubRequest &,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)1500 inline void handleGetManagerOpenBmc(
1501     const SubRequest& /*req*/,
1502     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1503     const std::string& /*managerId*/)
1504 {
1505     // Default OEM data
1506     nlohmann::json& oemOpenbmc = asyncResp->res.jsonValue;
1507     oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager";
1508     oemOpenbmc["@odata.id"] =
1509         boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc",
1510                             BMCWEB_REDFISH_MANAGER_URI_NAME);
1511 
1512     nlohmann::json::object_t certificates;
1513     certificates["@odata.id"] =
1514         boost::urls::format("/redfish/v1/Managers/{}/Truststore/Certificates",
1515                             BMCWEB_REDFISH_MANAGER_URI_NAME);
1516     oemOpenbmc["Certificates"] = std::move(certificates);
1517 
1518     if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
1519     {
1520         auto pids = std::make_shared<GetPIDValues>(asyncResp);
1521         pids->run();
1522     }
1523 }
1524 
handlePatchManagerOpenBmc(const SubRequest & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)1525 inline void handlePatchManagerOpenBmc(
1526     const SubRequest& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1527     const std::string& /*managerId*/)
1528 {
1529     nlohmann::json::object_t payload = req.payload();
1530 
1531     std::optional<nlohmann::json::object_t> pidControllers;
1532     std::optional<nlohmann::json::object_t> fanControllers;
1533     std::optional<nlohmann::json::object_t> fanZones;
1534     std::optional<nlohmann::json::object_t> stepwiseControllers;
1535     std::optional<std::string> profile;
1536 
1537     if (!json_util::readJsonObject(
1538             payload, asyncResp->res, "OpenBmc/Fan/PidControllers",
1539             pidControllers, "OpenBmc/Fan/FanControllers", fanControllers,
1540             "OpenBmc/Fan/FanZones", fanZones, "OpenBmc/Fan/StepwiseControllers",
1541             stepwiseControllers, "OpenBmc/Fan/Profile", profile))
1542     {
1543         return;
1544     }
1545 
1546     if (pidControllers || fanControllers || fanZones || stepwiseControllers ||
1547         profile)
1548     {
1549         if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
1550         {
1551             std::vector<
1552                 std::pair<std::string, std::optional<nlohmann::json::object_t>>>
1553                 configuration;
1554 
1555             if (pidControllers)
1556             {
1557                 configuration.emplace_back("PidControllers",
1558                                            std::move(pidControllers));
1559             }
1560             if (fanControllers)
1561             {
1562                 configuration.emplace_back("FanControllers",
1563                                            std::move(fanControllers));
1564             }
1565             if (fanZones)
1566             {
1567                 configuration.emplace_back("FanZones", std::move(fanZones));
1568             }
1569             if (stepwiseControllers)
1570             {
1571                 configuration.emplace_back("StepwiseControllers",
1572                                            std::move(stepwiseControllers));
1573             }
1574 
1575             auto pid = std::make_shared<SetPIDValues>(
1576                 asyncResp, std::move(configuration), profile);
1577             pid->run();
1578         }
1579         else
1580         {
1581             messages::propertyUnknown(asyncResp->res, "Oem");
1582             return;
1583         }
1584     }
1585 }
1586 
requestRoutesOpenBmcManager(RedfishService & service)1587 inline void requestRoutesOpenBmcManager(RedfishService& service)
1588 {
1589     REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/OpenBmc">(
1590         service, HttpVerb::Get)(handleGetManagerOpenBmc);
1591 
1592     REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/OpenBmc">(
1593         service, HttpVerb::Patch)(handlePatchManagerOpenBmc);
1594 }
1595 
1596 } // namespace redfish
1597