xref: /openbmc/bmcweb/redfish-core/lib/openbmc/openbmc_managers.hpp (revision e5dd4999a035f11ce7bc4db0898235ae99227580)
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::find(profiles->begin(), profiles->end(),
154                                           currentProfile) == 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::replace(escaped.begin(), escaped.end(), ' ', '_');
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::replace(input.begin(), input.end(), '_', ' ');
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::find(curProfiles->begin(),
676                                           curProfiles->end(), profile) ==
677                                 curProfiles->end())
678                             {
679                                 std::vector<std::string> newProfiles =
680                                     *curProfiles;
681                                 newProfiles.push_back(profile);
682                                 output.emplace_back("Profiles", newProfiles);
683                             }
684                         }
685                     }
686                 }
687             }
688 
689             if (!ifaceFound)
690             {
691                 BMCWEB_LOG_ERROR("Failed to find interface in managed object");
692                 messages::internalError(response->res);
693                 return CreatePIDRet::fail;
694             }
695         }
696     }
697 
698     if (type == "PidControllers" || type == "FanControllers")
699     {
700         if (createNewObject)
701         {
702             output.emplace_back("Class",
703                                 type == "PidControllers" ? "temp" : "fan");
704             output.emplace_back("Type", "Pid");
705         }
706 
707         std::optional<std::vector<nlohmann::json::object_t>> zones;
708         std::optional<std::vector<std::string>> inputs;
709         std::optional<std::vector<std::string>> outputs;
710         std::map<std::string, std::optional<double>> doubles;
711         std::optional<std::string> setpointOffset;
712         if (!redfish::json_util::readJson(                           //
713                 jsonValue, response->res,                            //
714                 "FFGainCoefficient", doubles["FFGainCoefficient"],   //
715                 "FFOffCoefficient", doubles["FFOffCoefficient"],     //
716                 "ICoefficient", doubles["ICoefficient"],             //
717                 "ILimitMax", doubles["ILimitMax"],                   //
718                 "ILimitMin", doubles["ILimitMin"],                   //
719                 "Inputs", inputs,                                    //
720                 "NegativeHysteresis", doubles["NegativeHysteresis"], //
721                 "OutLimitMax", doubles["OutLimitMax"],               //
722                 "OutLimitMin", doubles["OutLimitMin"],               //
723                 "Outputs", outputs,                                  //
724                 "PCoefficient", doubles["PCoefficient"],             //
725                 "PositiveHysteresis", doubles["PositiveHysteresis"], //
726                 "SetPoint", doubles["SetPoint"],                     //
727                 "SetPointOffset", setpointOffset,                    //
728                 "SlewNeg", doubles["SlewNeg"],                       //
729                 "SlewPos", doubles["SlewPos"],                       //
730                 "Zones", zones                                       //
731                 ))
732         {
733             return CreatePIDRet::fail;
734         }
735 
736         if (zones)
737         {
738             std::vector<std::string> zonesStr;
739             if (!getZonesFromJsonReq(response, *zones, zonesStr))
740             {
741                 BMCWEB_LOG_ERROR("Illegal Zones");
742                 return CreatePIDRet::fail;
743             }
744             if (chassis.empty() &&
745                 findChassis(managedObj, zonesStr[0], chassis) == nullptr)
746             {
747                 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
748                 messages::invalidObject(
749                     response->res,
750                     boost::urls::format("/redfish/v1/Chassis/{}", chassis));
751                 return CreatePIDRet::fail;
752             }
753             output.emplace_back("Zones", std::move(zonesStr));
754         }
755 
756         if (inputs)
757         {
758             for (std::string& value : *inputs)
759             {
760                 std::replace(value.begin(), value.end(), '_', ' ');
761             }
762             output.emplace_back("Inputs", *inputs);
763         }
764 
765         if (outputs)
766         {
767             for (std::string& value : *outputs)
768             {
769                 std::replace(value.begin(), value.end(), '_', ' ');
770             }
771             output.emplace_back("Outputs", *outputs);
772         }
773 
774         if (setpointOffset)
775         {
776             // translate between redfish and dbus names
777             if (*setpointOffset == "UpperThresholdNonCritical")
778             {
779                 output.emplace_back("SetPointOffset", "WarningLow");
780             }
781             else if (*setpointOffset == "LowerThresholdNonCritical")
782             {
783                 output.emplace_back("SetPointOffset", "WarningHigh");
784             }
785             else if (*setpointOffset == "LowerThresholdCritical")
786             {
787                 output.emplace_back("SetPointOffset", "CriticalLow");
788             }
789             else if (*setpointOffset == "UpperThresholdCritical")
790             {
791                 output.emplace_back("SetPointOffset", "CriticalHigh");
792             }
793             else
794             {
795                 BMCWEB_LOG_ERROR("Invalid setpointoffset {}", *setpointOffset);
796                 messages::propertyValueNotInList(response->res, name,
797                                                  "SetPointOffset");
798                 return CreatePIDRet::fail;
799             }
800         }
801 
802         // doubles
803         for (const auto& pairs : doubles)
804         {
805             if (!pairs.second)
806             {
807                 continue;
808             }
809             BMCWEB_LOG_DEBUG("{} = {}", pairs.first, *pairs.second);
810             output.emplace_back(pairs.first, *pairs.second);
811         }
812     }
813 
814     else if (type == "FanZones")
815     {
816         output.emplace_back("Type", "Pid.Zone");
817 
818         std::optional<std::string> chassisId;
819         std::optional<double> failSafePercent;
820         std::optional<double> minThermalOutput;
821         if (!redfish::json_util::readJson(          //
822                 jsonValue, response->res,           //
823                 "Chassis/@odata.id", chassisId,     //
824                 "FailSafePercent", failSafePercent, //
825                 "MinThermalOutput", minThermalOutput))
826         {
827             return CreatePIDRet::fail;
828         }
829 
830         if (chassisId)
831         {
832             boost::system::result<boost::urls::url_view> parsed =
833                 boost::urls::parse_relative_ref(*chassisId);
834             if (!parsed)
835             {
836                 BMCWEB_LOG_WARNING("Got invalid path {}", *chassisId);
837                 messages::propertyValueFormatError(response->res, *chassisId,
838                                                    "Chassis/@odata.id");
839                 return CreatePIDRet::fail;
840             }
841 
842             if (!crow::utility::readUrlSegments(*parsed, "redfish", "v1",
843                                                 "Chassis", std::ref(chassis)))
844             {
845                 BMCWEB_LOG_WARNING("Got invalid path {}", *parsed);
846                 messages::propertyValueFormatError(response->res, *chassisId,
847                                                    "Chassis/@odata.id");
848                 return CreatePIDRet::fail;
849             }
850         }
851 
852         if (minThermalOutput)
853         {
854             output.emplace_back("MinThermalOutput", *minThermalOutput);
855         }
856         if (failSafePercent)
857         {
858             output.emplace_back("FailSafePercent", *failSafePercent);
859         }
860     }
861     else if (type == "StepwiseControllers")
862     {
863         output.emplace_back("Type", "Stepwise");
864 
865         std::optional<std::vector<nlohmann::json::object_t>> zones;
866         std::optional<std::vector<nlohmann::json::object_t>> steps;
867         std::optional<std::vector<std::string>> inputs;
868         std::optional<double> positiveHysteresis;
869         std::optional<double> negativeHysteresis;
870         std::optional<std::string> direction; // upper clipping curve vs lower
871         if (!redfish::json_util::readJson(    //
872                 jsonValue, response->res,     //
873                 "Direction", direction,       //
874                 "Inputs", inputs,             //
875                 "NegativeHysteresis", negativeHysteresis, //
876                 "PositiveHysteresis", positiveHysteresis, //
877                 "Steps", steps,                           //
878                 "Zones", zones                            //
879                 ))
880         {
881             return CreatePIDRet::fail;
882         }
883 
884         if (zones)
885         {
886             std::vector<std::string> zonesStrs;
887             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
888             {
889                 BMCWEB_LOG_ERROR("Illegal Zones");
890                 return CreatePIDRet::fail;
891             }
892             if (chassis.empty() &&
893                 findChassis(managedObj, zonesStrs[0], chassis) == nullptr)
894             {
895                 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
896                 messages::invalidObject(
897                     response->res,
898                     boost::urls::format("/redfish/v1/Chassis/{}", chassis));
899                 return CreatePIDRet::fail;
900             }
901             output.emplace_back("Zones", std::move(zonesStrs));
902         }
903         if (steps)
904         {
905             std::vector<double> readings;
906             std::vector<double> outputs;
907             for (auto& step : *steps)
908             {
909                 double target = 0.0;
910                 double out = 0.0;
911 
912                 if (!redfish::json_util::readJsonObject( //
913                         step, response->res,             //
914                         "Output", out,                   //
915                         "Target", target                 //
916                         ))
917                 {
918                     return CreatePIDRet::fail;
919                 }
920                 readings.emplace_back(target);
921                 outputs.emplace_back(out);
922             }
923             output.emplace_back("Reading", std::move(readings));
924             output.emplace_back("Output", std::move(outputs));
925         }
926         if (inputs)
927         {
928             for (std::string& value : *inputs)
929             {
930                 std::replace(value.begin(), value.end(), '_', ' ');
931             }
932             output.emplace_back("Inputs", std::move(*inputs));
933         }
934         if (negativeHysteresis)
935         {
936             output.emplace_back("NegativeHysteresis", *negativeHysteresis);
937         }
938         if (positiveHysteresis)
939         {
940             output.emplace_back("PositiveHysteresis", *positiveHysteresis);
941         }
942         if (direction)
943         {
944             constexpr const std::array<const char*, 2> allowedDirections = {
945                 "Ceiling", "Floor"};
946             if (std::ranges::find(allowedDirections, *direction) ==
947                 allowedDirections.end())
948             {
949                 messages::propertyValueTypeError(response->res, "Direction",
950                                                  *direction);
951                 return CreatePIDRet::fail;
952             }
953             output.emplace_back("Class", *direction);
954         }
955     }
956     else
957     {
958         BMCWEB_LOG_ERROR("Illegal Type {}", type);
959         messages::propertyUnknown(response->res, type);
960         return CreatePIDRet::fail;
961     }
962     return CreatePIDRet::patch;
963 }
964 
965 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
966 {
967     struct CompletionValues
968     {
969         std::vector<std::string> supportedProfiles;
970         std::string currentProfile;
971         dbus::utility::MapperGetSubTreeResponse subtree;
972     };
973 
GetPIDValuesredfish::GetPIDValues974     explicit GetPIDValues(
975         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
976         asyncResp(asyncRespIn)
977 
978     {}
979 
runredfish::GetPIDValues980     void run()
981     {
982         std::shared_ptr<GetPIDValues> self = shared_from_this();
983 
984         // get all configurations
985         constexpr std::array<std::string_view, 4> interfaces = {
986             pidConfigurationIface, pidZoneConfigurationIface,
987             objectManagerIface, stepwiseConfigurationIface};
988         dbus::utility::getSubTree(
989             "/", 0, interfaces,
990             [self](
991                 const boost::system::error_code& ec,
992                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
993                 if (ec)
994                 {
995                     BMCWEB_LOG_ERROR("{}", ec);
996                     messages::internalError(self->asyncResp->res);
997                     return;
998                 }
999                 self->complete.subtree = subtreeLocal;
1000             });
1001 
1002         // at the same time get the selected profile
1003         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1004             thermalModeIface};
1005         dbus::utility::getSubTree(
1006             "/", 0, thermalModeIfaces,
1007             [self](
1008                 const boost::system::error_code& ec,
1009                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1010                 if (ec || subtreeLocal.empty())
1011                 {
1012                     return;
1013                 }
1014                 if (subtreeLocal[0].second.size() != 1)
1015                 {
1016                     // invalid mapper response, should never happen
1017                     BMCWEB_LOG_ERROR("GetPIDValues: Mapper Error");
1018                     messages::internalError(self->asyncResp->res);
1019                     return;
1020                 }
1021 
1022                 const std::string& path = subtreeLocal[0].first;
1023                 const std::string& owner = subtreeLocal[0].second[0].first;
1024 
1025                 dbus::utility::getAllProperties(
1026                     *crow::connections::systemBus, owner, path,
1027                     thermalModeIface,
1028                     [path, owner,
1029                      self](const boost::system::error_code& ec2,
1030                            const dbus::utility::DBusPropertiesMap& resp) {
1031                         if (ec2)
1032                         {
1033                             BMCWEB_LOG_ERROR(
1034                                 "GetPIDValues: Can't get thermalModeIface {}",
1035                                 path);
1036                             messages::internalError(self->asyncResp->res);
1037                             return;
1038                         }
1039 
1040                         const std::string* current = nullptr;
1041                         const std::vector<std::string>* supported = nullptr;
1042 
1043                         const bool success = sdbusplus::unpackPropertiesNoThrow(
1044                             dbus_utils::UnpackErrorPrinter(), resp, "Current",
1045                             current, "Supported", supported);
1046 
1047                         if (!success)
1048                         {
1049                             messages::internalError(self->asyncResp->res);
1050                             return;
1051                         }
1052 
1053                         if (current == nullptr || supported == nullptr)
1054                         {
1055                             BMCWEB_LOG_ERROR(
1056                                 "GetPIDValues: thermal mode iface invalid {}",
1057                                 path);
1058                             messages::internalError(self->asyncResp->res);
1059                             return;
1060                         }
1061                         self->complete.currentProfile = *current;
1062                         self->complete.supportedProfiles = *supported;
1063                     });
1064             });
1065     }
1066 
processingCompleteredfish::GetPIDValues1067     static void processingComplete(
1068         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1069         const CompletionValues& completion)
1070     {
1071         if (asyncResp->res.result() != boost::beast::http::status::ok)
1072         {
1073             return;
1074         }
1075         // create map of <connection, path to objMgr>>
1076         boost::container::flat_map<
1077             std::string, std::string, std::less<>,
1078             std::vector<std::pair<std::string, std::string>>>
1079             objectMgrPaths;
1080         boost::container::flat_set<std::string, std::less<>,
1081                                    std::vector<std::string>>
1082             calledConnections;
1083         for (const auto& pathGroup : completion.subtree)
1084         {
1085             for (const auto& connectionGroup : pathGroup.second)
1086             {
1087                 auto findConnection =
1088                     calledConnections.find(connectionGroup.first);
1089                 if (findConnection != calledConnections.end())
1090                 {
1091                     break;
1092                 }
1093                 for (const std::string& interface : connectionGroup.second)
1094                 {
1095                     if (interface == objectManagerIface)
1096                     {
1097                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1098                     }
1099                     // this list is alphabetical, so we
1100                     // should have found the objMgr by now
1101                     if (interface == pidConfigurationIface ||
1102                         interface == pidZoneConfigurationIface ||
1103                         interface == stepwiseConfigurationIface)
1104                     {
1105                         auto findObjMgr =
1106                             objectMgrPaths.find(connectionGroup.first);
1107                         if (findObjMgr == objectMgrPaths.end())
1108                         {
1109                             BMCWEB_LOG_DEBUG("{}Has no Object Manager",
1110                                              connectionGroup.first);
1111                             continue;
1112                         }
1113 
1114                         calledConnections.insert(connectionGroup.first);
1115 
1116                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1117                                          completion.currentProfile,
1118                                          completion.supportedProfiles,
1119                                          asyncResp);
1120                         break;
1121                     }
1122                 }
1123             }
1124         }
1125     }
1126 
~GetPIDValuesredfish::GetPIDValues1127     ~GetPIDValues()
1128     {
1129         boost::asio::post(getIoContext(),
1130                           std::bind_front(&processingComplete, asyncResp,
1131                                           std::move(complete)));
1132     }
1133 
1134     GetPIDValues(const GetPIDValues&) = delete;
1135     GetPIDValues(GetPIDValues&&) = delete;
1136     GetPIDValues& operator=(const GetPIDValues&) = delete;
1137     GetPIDValues& operator=(GetPIDValues&&) = delete;
1138 
1139     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1140     CompletionValues complete;
1141 };
1142 
1143 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1144 {
SetPIDValuesredfish::SetPIDValues1145     SetPIDValues(
1146         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
1147         std::vector<
1148             std::pair<std::string, std::optional<nlohmann::json::object_t>>>&&
1149             configurationsIn,
1150         std::optional<std::string>& profileIn) :
1151         asyncResp(asyncRespIn), configuration(std::move(configurationsIn)),
1152         profile(std::move(profileIn))
1153     {}
1154 
1155     SetPIDValues(const SetPIDValues&) = delete;
1156     SetPIDValues(SetPIDValues&&) = delete;
1157     SetPIDValues& operator=(const SetPIDValues&) = delete;
1158     SetPIDValues& operator=(SetPIDValues&&) = delete;
1159 
runredfish::SetPIDValues1160     void run()
1161     {
1162         if (asyncResp->res.result() != boost::beast::http::status::ok)
1163         {
1164             return;
1165         }
1166 
1167         std::shared_ptr<SetPIDValues> self = shared_from_this();
1168 
1169         // todo(james): might make sense to do a mapper call here if this
1170         // interface gets more traction
1171         sdbusplus::message::object_path objPath(
1172             "/xyz/openbmc_project/inventory");
1173         dbus::utility::getManagedObjects(
1174             "xyz.openbmc_project.EntityManager", objPath,
1175             [self](const boost::system::error_code& ec,
1176                    const dbus::utility::ManagedObjectType& mObj) {
1177                 if (ec)
1178                 {
1179                     BMCWEB_LOG_ERROR("Error communicating to Entity Manager");
1180                     messages::internalError(self->asyncResp->res);
1181                     return;
1182                 }
1183                 const std::array<const char*, 3> configurations = {
1184                     pidConfigurationIface, pidZoneConfigurationIface,
1185                     stepwiseConfigurationIface};
1186 
1187                 for (const auto& [path, object] : mObj)
1188                 {
1189                     for (const auto& [interface, _] : object)
1190                     {
1191                         if (std::ranges::find(configurations, interface) !=
1192                             configurations.end())
1193                         {
1194                             self->objectCount++;
1195                             break;
1196                         }
1197                     }
1198                 }
1199                 self->managedObj = mObj;
1200             });
1201 
1202         // at the same time get the profile information
1203         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1204             thermalModeIface};
1205         dbus::utility::getSubTree(
1206             "/", 0, thermalModeIfaces,
1207             [self](const boost::system::error_code& ec,
1208                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
1209                 if (ec || subtree.empty())
1210                 {
1211                     return;
1212                 }
1213                 if (subtree[0].second.empty())
1214                 {
1215                     // invalid mapper response, should never happen
1216                     BMCWEB_LOG_ERROR("SetPIDValues: Mapper Error");
1217                     messages::internalError(self->asyncResp->res);
1218                     return;
1219                 }
1220 
1221                 const std::string& path = subtree[0].first;
1222                 const std::string& owner = subtree[0].second[0].first;
1223                 dbus::utility::getAllProperties(
1224                     *crow::connections::systemBus, owner, path,
1225                     thermalModeIface,
1226                     [self, path,
1227                      owner](const boost::system::error_code& ec2,
1228                             const dbus::utility::DBusPropertiesMap& r) {
1229                         if (ec2)
1230                         {
1231                             BMCWEB_LOG_ERROR(
1232                                 "SetPIDValues: Can't get thermalModeIface {}",
1233                                 path);
1234                             messages::internalError(self->asyncResp->res);
1235                             return;
1236                         }
1237                         const std::string* current = nullptr;
1238                         const std::vector<std::string>* supported = nullptr;
1239 
1240                         const bool success = sdbusplus::unpackPropertiesNoThrow(
1241                             dbus_utils::UnpackErrorPrinter(), r, "Current",
1242                             current, "Supported", supported);
1243 
1244                         if (!success)
1245                         {
1246                             messages::internalError(self->asyncResp->res);
1247                             return;
1248                         }
1249 
1250                         if (current == nullptr || supported == nullptr)
1251                         {
1252                             BMCWEB_LOG_ERROR(
1253                                 "SetPIDValues: thermal mode iface invalid {}",
1254                                 path);
1255                             messages::internalError(self->asyncResp->res);
1256                             return;
1257                         }
1258                         self->currentProfile = *current;
1259                         self->supportedProfiles = *supported;
1260                         self->profileConnection = owner;
1261                         self->profilePath = path;
1262                     });
1263             });
1264     }
pidSetDoneredfish::SetPIDValues1265     void pidSetDone()
1266     {
1267         if (asyncResp->res.result() != boost::beast::http::status::ok)
1268         {
1269             return;
1270         }
1271         std::shared_ptr<bmcweb::AsyncResp> response = asyncResp;
1272         if (profile)
1273         {
1274             if (std::ranges::find(supportedProfiles, *profile) ==
1275                 supportedProfiles.end())
1276             {
1277                 messages::actionParameterUnknown(response->res, "Profile",
1278                                                  *profile);
1279                 return;
1280             }
1281             currentProfile = *profile;
1282             sdbusplus::asio::setProperty(
1283                 *crow::connections::systemBus, profileConnection, profilePath,
1284                 thermalModeIface, "Current", *profile,
1285                 [response](const boost::system::error_code& ec) {
1286                     if (ec)
1287                     {
1288                         BMCWEB_LOG_ERROR("Error patching profile{}", ec);
1289                         messages::internalError(response->res);
1290                     }
1291                 });
1292         }
1293 
1294         for (auto& containerPair : configuration)
1295         {
1296             auto& container = containerPair.second;
1297             if (!container)
1298             {
1299                 continue;
1300             }
1301 
1302             const std::string& type = containerPair.first;
1303 
1304             for (auto& [name, value] : *container)
1305             {
1306                 std::string dbusObjName = name;
1307                 std::replace(dbusObjName.begin(), dbusObjName.end(), ' ', '_');
1308                 BMCWEB_LOG_DEBUG("looking for {}", name);
1309 
1310                 auto pathItr = std::ranges::find_if(
1311                     managedObj, [&dbusObjName](const auto& obj) {
1312                         return obj.first.filename() == dbusObjName;
1313                     });
1314                 dbus::utility::DBusPropertiesMap output;
1315 
1316                 output.reserve(16); // The pid interface length
1317 
1318                 // determines if we're patching entity-manager or
1319                 // creating a new object
1320                 bool createNewObject = (pathItr == managedObj.end());
1321                 BMCWEB_LOG_DEBUG("Found = {}", !createNewObject);
1322 
1323                 std::string iface;
1324                 if (!createNewObject)
1325                 {
1326                     bool findInterface = false;
1327                     for (const auto& interface : pathItr->second)
1328                     {
1329                         if (interface.first == pidConfigurationIface)
1330                         {
1331                             if (type == "PidControllers" ||
1332                                 type == "FanControllers")
1333                             {
1334                                 iface = pidConfigurationIface;
1335                                 findInterface = true;
1336                                 break;
1337                             }
1338                         }
1339                         else if (interface.first == pidZoneConfigurationIface)
1340                         {
1341                             if (type == "FanZones")
1342                             {
1343                                 iface = pidZoneConfigurationIface;
1344                                 findInterface = true;
1345                                 break;
1346                             }
1347                         }
1348                         else if (interface.first == stepwiseConfigurationIface)
1349                         {
1350                             if (type == "StepwiseControllers")
1351                             {
1352                                 iface = stepwiseConfigurationIface;
1353                                 findInterface = true;
1354                                 break;
1355                             }
1356                         }
1357                     }
1358 
1359                     // create new object if interface not found
1360                     if (!findInterface)
1361                     {
1362                         createNewObject = true;
1363                     }
1364                 }
1365 
1366                 if (createNewObject && value == nullptr)
1367                 {
1368                     // can't delete a non-existent object
1369                     messages::propertyValueNotInList(response->res, value,
1370                                                      name);
1371                     continue;
1372                 }
1373 
1374                 std::string path;
1375                 if (pathItr != managedObj.end())
1376                 {
1377                     path = pathItr->first.str;
1378                 }
1379 
1380                 BMCWEB_LOG_DEBUG("Create new = {}", createNewObject);
1381 
1382                 // arbitrary limit to avoid attacks
1383                 constexpr const size_t controllerLimit = 500;
1384                 if (createNewObject && objectCount >= controllerLimit)
1385                 {
1386                     messages::resourceExhaustion(response->res, type);
1387                     continue;
1388                 }
1389                 std::string escaped = name;
1390                 std::replace(escaped.begin(), escaped.end(), '_', ' ');
1391                 output.emplace_back("Name", escaped);
1392 
1393                 std::string chassis;
1394                 CreatePIDRet ret = createPidInterface(
1395                     response, type, name, value, path, managedObj,
1396                     createNewObject, output, chassis, currentProfile);
1397                 if (ret == CreatePIDRet::fail)
1398                 {
1399                     return;
1400                 }
1401                 if (ret == CreatePIDRet::del)
1402                 {
1403                     continue;
1404                 }
1405 
1406                 if (!createNewObject)
1407                 {
1408                     for (const auto& property : output)
1409                     {
1410                         dbus::utility::async_method_call(
1411                             asyncResp,
1412                             [response,
1413                              propertyName{std::string(property.first)}](
1414                                 const boost::system::error_code& ec) {
1415                                 if (ec)
1416                                 {
1417                                     BMCWEB_LOG_ERROR("Error patching {}: {}",
1418                                                      propertyName, ec);
1419                                     messages::internalError(response->res);
1420                                     return;
1421                                 }
1422                                 messages::success(response->res);
1423                             },
1424                             "xyz.openbmc_project.EntityManager", path,
1425                             "org.freedesktop.DBus.Properties", "Set", iface,
1426                             property.first, property.second);
1427                     }
1428                 }
1429                 else
1430                 {
1431                     if (chassis.empty())
1432                     {
1433                         BMCWEB_LOG_ERROR("Failed to get chassis from config");
1434                         messages::internalError(response->res);
1435                         return;
1436                     }
1437 
1438                     bool foundChassis = false;
1439                     for (const auto& obj : managedObj)
1440                     {
1441                         if (obj.first.filename() == chassis)
1442                         {
1443                             chassis = obj.first.str;
1444                             foundChassis = true;
1445                             break;
1446                         }
1447                     }
1448                     if (!foundChassis)
1449                     {
1450                         BMCWEB_LOG_ERROR("Failed to find chassis on dbus");
1451                         messages::resourceMissingAtURI(
1452                             response->res,
1453                             boost::urls::format("/redfish/v1/Chassis/{}",
1454                                                 chassis));
1455                         return;
1456                     }
1457 
1458                     dbus::utility::async_method_call(
1459                         asyncResp,
1460                         [response](const boost::system::error_code& ec) {
1461                             if (ec)
1462                             {
1463                                 BMCWEB_LOG_ERROR("Error Adding Pid Object {}",
1464                                                  ec);
1465                                 messages::internalError(response->res);
1466                                 return;
1467                             }
1468                             messages::success(response->res);
1469                         },
1470                         "xyz.openbmc_project.EntityManager", chassis,
1471                         "xyz.openbmc_project.AddObject", "AddObject", output);
1472                 }
1473             }
1474         }
1475     }
1476 
~SetPIDValuesredfish::SetPIDValues1477     ~SetPIDValues()
1478     {
1479         try
1480         {
1481             pidSetDone();
1482         }
1483         catch (...)
1484         {
1485             BMCWEB_LOG_CRITICAL("pidSetDone threw exception");
1486         }
1487     }
1488 
1489     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1490     std::vector<std::pair<std::string, std::optional<nlohmann::json::object_t>>>
1491         configuration;
1492     std::optional<std::string> profile;
1493     dbus::utility::ManagedObjectType managedObj;
1494     std::vector<std::string> supportedProfiles;
1495     std::string currentProfile;
1496     std::string profileConnection;
1497     std::string profilePath;
1498     size_t objectCount = 0;
1499 };
1500 
handleGetManagerOpenBmc(const SubRequest &,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)1501 inline void handleGetManagerOpenBmc(
1502     const SubRequest& /*req*/,
1503     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1504     const std::string& /*managerId*/)
1505 {
1506     // Default OEM data
1507     nlohmann::json& oemOpenbmc = asyncResp->res.jsonValue;
1508     oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager";
1509     oemOpenbmc["@odata.id"] =
1510         boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc",
1511                             BMCWEB_REDFISH_MANAGER_URI_NAME);
1512 
1513     nlohmann::json::object_t certificates;
1514     certificates["@odata.id"] =
1515         boost::urls::format("/redfish/v1/Managers/{}/Truststore/Certificates",
1516                             BMCWEB_REDFISH_MANAGER_URI_NAME);
1517     oemOpenbmc["Certificates"] = std::move(certificates);
1518 
1519     if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
1520     {
1521         auto pids = std::make_shared<GetPIDValues>(asyncResp);
1522         pids->run();
1523     }
1524 }
1525 
handlePatchManagerOpenBmc(const SubRequest & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)1526 inline void handlePatchManagerOpenBmc(
1527     const SubRequest& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1528     const std::string& /*managerId*/)
1529 {
1530     nlohmann::json::object_t payload = req.payload();
1531 
1532     std::optional<nlohmann::json::object_t> pidControllers;
1533     std::optional<nlohmann::json::object_t> fanControllers;
1534     std::optional<nlohmann::json::object_t> fanZones;
1535     std::optional<nlohmann::json::object_t> stepwiseControllers;
1536     std::optional<std::string> profile;
1537 
1538     if (!json_util::readJsonObject(
1539             payload, asyncResp->res, "OpenBmc/Fan/PidControllers",
1540             pidControllers, "OpenBmc/Fan/FanControllers", fanControllers,
1541             "OpenBmc/Fan/FanZones", fanZones, "OpenBmc/Fan/StepwiseControllers",
1542             stepwiseControllers, "OpenBmc/Fan/Profile", profile))
1543     {
1544         return;
1545     }
1546 
1547     if (pidControllers || fanControllers || fanZones || stepwiseControllers ||
1548         profile)
1549     {
1550         if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
1551         {
1552             std::vector<
1553                 std::pair<std::string, std::optional<nlohmann::json::object_t>>>
1554                 configuration;
1555 
1556             if (pidControllers)
1557             {
1558                 configuration.emplace_back("PidControllers",
1559                                            std::move(pidControllers));
1560             }
1561             if (fanControllers)
1562             {
1563                 configuration.emplace_back("FanControllers",
1564                                            std::move(fanControllers));
1565             }
1566             if (fanZones)
1567             {
1568                 configuration.emplace_back("FanZones", std::move(fanZones));
1569             }
1570             if (stepwiseControllers)
1571             {
1572                 configuration.emplace_back("StepwiseControllers",
1573                                            std::move(stepwiseControllers));
1574             }
1575 
1576             auto pid = std::make_shared<SetPIDValues>(
1577                 asyncResp, std::move(configuration), profile);
1578             pid->run();
1579         }
1580         else
1581         {
1582             messages::propertyUnknown(asyncResp->res, "Oem");
1583             return;
1584         }
1585     }
1586 }
1587 
requestRoutesOpenBmcManager(RedfishService & service)1588 inline void requestRoutesOpenBmcManager(RedfishService& service)
1589 {
1590     REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/OpenBmc">(
1591         service, HttpVerb::Get)(handleGetManagerOpenBmc);
1592 
1593     REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/OpenBmc">(
1594         service, HttpVerb::Patch)(handlePatchManagerOpenBmc);
1595 }
1596 
1597 } // namespace redfish
1598