xref: /openbmc/bmcweb/features/redfish/lib/managers.hpp (revision c5d03ff4737b1f4e4eb7e59f533d6c5a1faeeab5)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 #include "redfish_util.hpp"
20 
21 #include <boost/algorithm/string/replace.hpp>
22 #include <boost/date_time.hpp>
23 #include <dbus_utility.hpp>
24 #include <memory>
25 #include <sstream>
26 #include <utils/systemd_utils.hpp>
27 #include <variant>
28 
29 namespace redfish
30 {
31 
32 /**
33  * ManagerActionsReset class supports handle POST method for Reset action.
34  * The class retrieves and sends data directly to dbus.
35  */
36 class ManagerActionsReset : public Node
37 {
38   public:
39     ManagerActionsReset(CrowApp& app) :
40         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
41     {
42         entityPrivileges = {
43             {boost::beast::http::verb::get, {{"Login"}}},
44             {boost::beast::http::verb::head, {{"Login"}}},
45             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
46             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
47             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
48             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
49     }
50 
51   private:
52     /**
53      * Function handles POST method request.
54      * Analyzes POST body message before sends Reset request data to dbus.
55      * OpenBMC allows for ResetType is GracefulRestart only.
56      */
57     void doPost(crow::Response& res, const crow::Request& req,
58                 const std::vector<std::string>& params) override
59     {
60         std::string resetType;
61 
62         if (!json_util::readJson(req, res, "ResetType", resetType))
63         {
64             return;
65         }
66 
67         if (resetType != "GracefulRestart")
68         {
69             res.result(boost::beast::http::status::bad_request);
70             messages::actionParameterNotSupported(res, resetType, "ResetType");
71             BMCWEB_LOG_ERROR << "Request incorrect action parameter: "
72                              << resetType;
73             res.end();
74             return;
75         }
76         doBMCGracefulRestart(res, req, params);
77     }
78 
79     /**
80      * Function transceives data with dbus directly.
81      * All BMC state properties will be retrieved before sending reset request.
82      */
83     void doBMCGracefulRestart(crow::Response& res, const crow::Request& req,
84                               const std::vector<std::string>& params)
85     {
86         const char* processName = "xyz.openbmc_project.State.BMC";
87         const char* objectPath = "/xyz/openbmc_project/state/bmc0";
88         const char* interfaceName = "xyz.openbmc_project.State.BMC";
89         const std::string& propertyValue =
90             "xyz.openbmc_project.State.BMC.Transition.Reboot";
91         const char* destProperty = "RequestedBMCTransition";
92 
93         // Create the D-Bus variant for D-Bus call.
94         VariantType dbusPropertyValue(propertyValue);
95 
96         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
97 
98         crow::connections::systemBus->async_method_call(
99             [asyncResp](const boost::system::error_code ec) {
100                 // Use "Set" method to set the property value.
101                 if (ec)
102                 {
103                     BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec;
104                     messages::internalError(asyncResp->res);
105                     return;
106                 }
107 
108                 messages::success(asyncResp->res);
109             },
110             processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
111             interfaceName, destProperty, dbusPropertyValue);
112     }
113 };
114 
115 static constexpr const char* objectManagerIface =
116     "org.freedesktop.DBus.ObjectManager";
117 static constexpr const char* pidConfigurationIface =
118     "xyz.openbmc_project.Configuration.Pid";
119 static constexpr const char* pidZoneConfigurationIface =
120     "xyz.openbmc_project.Configuration.Pid.Zone";
121 static constexpr const char* stepwiseConfigurationIface =
122     "xyz.openbmc_project.Configuration.Stepwise";
123 static constexpr const char* thermalModeIface =
124     "xyz.openbmc_project.Control.ThermalMode";
125 
126 static void asyncPopulatePid(const std::string& connection,
127                              const std::string& path,
128                              const std::string& currentProfile,
129                              const std::vector<std::string>& supportedProfiles,
130                              std::shared_ptr<AsyncResp> asyncResp)
131 {
132 
133     crow::connections::systemBus->async_method_call(
134         [asyncResp, currentProfile, supportedProfiles](
135             const boost::system::error_code ec,
136             const dbus::utility::ManagedObjectType& managedObj) {
137             if (ec)
138             {
139                 BMCWEB_LOG_ERROR << ec;
140                 asyncResp->res.jsonValue.clear();
141                 messages::internalError(asyncResp->res);
142                 return;
143             }
144             nlohmann::json& configRoot =
145                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
146             nlohmann::json& fans = configRoot["FanControllers"];
147             fans["@odata.type"] = "#OemManager.FanControllers";
148             fans["@odata.context"] =
149                 "/redfish/v1/$metadata#OemManager.FanControllers";
150             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
151                                 "Fan/FanControllers";
152 
153             nlohmann::json& pids = configRoot["PidControllers"];
154             pids["@odata.type"] = "#OemManager.PidControllers";
155             pids["@odata.context"] =
156                 "/redfish/v1/$metadata#OemManager.PidControllers";
157             pids["@odata.id"] =
158                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
159 
160             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
161             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
162             stepwise["@odata.context"] =
163                 "/redfish/v1/$metadata#OemManager.StepwiseControllers";
164             stepwise["@odata.id"] =
165                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
166 
167             nlohmann::json& zones = configRoot["FanZones"];
168             zones["@odata.id"] =
169                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
170             zones["@odata.type"] = "#OemManager.FanZones";
171             zones["@odata.context"] =
172                 "/redfish/v1/$metadata#OemManager.FanZones";
173             configRoot["@odata.id"] =
174                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
175             configRoot["@odata.type"] = "#OemManager.Fan";
176             configRoot["@odata.context"] =
177                 "/redfish/v1/$metadata#OemManager.Fan";
178             configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
179 
180             if (!currentProfile.empty())
181             {
182                 configRoot["Profile"] = currentProfile;
183             }
184             BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !";
185 
186             for (const auto& pathPair : managedObj)
187             {
188                 for (const auto& intfPair : pathPair.second)
189                 {
190                     if (intfPair.first != pidConfigurationIface &&
191                         intfPair.first != pidZoneConfigurationIface &&
192                         intfPair.first != stepwiseConfigurationIface)
193                     {
194                         continue;
195                     }
196                     auto findName = intfPair.second.find("Name");
197                     if (findName == intfPair.second.end())
198                     {
199                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
200                         messages::internalError(asyncResp->res);
201                         return;
202                     }
203 
204                     const std::string* namePtr =
205                         std::get_if<std::string>(&findName->second);
206                     if (namePtr == nullptr)
207                     {
208                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
209                         messages::internalError(asyncResp->res);
210                         return;
211                     }
212                     std::string name = *namePtr;
213                     dbus::utility::escapePathForDbus(name);
214 
215                     auto findProfiles = intfPair.second.find("Profiles");
216                     if (findProfiles != intfPair.second.end())
217                     {
218                         const std::vector<std::string>* profiles =
219                             std::get_if<std::vector<std::string>>(
220                                 &findProfiles->second);
221                         if (profiles == nullptr)
222                         {
223                             BMCWEB_LOG_ERROR << "Pid Profiles Field illegal";
224                             messages::internalError(asyncResp->res);
225                             return;
226                         }
227                         if (std::find(profiles->begin(), profiles->end(),
228                                       currentProfile) == profiles->end())
229                         {
230                             BMCWEB_LOG_INFO
231                                 << name << " not supported in current profile";
232                             continue;
233                         }
234                     }
235                     nlohmann::json* config = nullptr;
236 
237                     const std::string* classPtr = nullptr;
238                     auto findClass = intfPair.second.find("Class");
239                     if (findClass != intfPair.second.end())
240                     {
241                         classPtr = std::get_if<std::string>(&findClass->second);
242                     }
243 
244                     if (intfPair.first == pidZoneConfigurationIface)
245                     {
246                         std::string chassis;
247                         if (!dbus::utility::getNthStringFromPath(
248                                 pathPair.first.str, 5, chassis))
249                         {
250                             chassis = "#IllegalValue";
251                         }
252                         nlohmann::json& zone = zones[name];
253                         zone["Chassis"] = {
254                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
255                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
256                                             "OpenBmc/Fan/FanZones/" +
257                                             name;
258                         zone["@odata.type"] = "#OemManager.FanZone";
259                         zone["@odata.context"] =
260                             "/redfish/v1/$metadata#OemManager.FanZone";
261                         config = &zone;
262                     }
263 
264                     else if (intfPair.first == stepwiseConfigurationIface)
265                     {
266                         if (classPtr == nullptr)
267                         {
268                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
269                             messages::internalError(asyncResp->res);
270                             return;
271                         }
272 
273                         nlohmann::json& controller = stepwise[name];
274                         config = &controller;
275 
276                         controller["@odata.id"] =
277                             "/redfish/v1/Managers/bmc#/Oem/"
278                             "OpenBmc/Fan/StepwiseControllers/" +
279                             std::string(name);
280                         controller["@odata.type"] =
281                             "#OemManager.StepwiseController";
282 
283                         controller["@odata.context"] =
284                             "/redfish/v1/"
285                             "$metadata#OemManager.StepwiseController";
286                         controller["Direction"] = *classPtr;
287                     }
288 
289                     // pid and fans are off the same configuration
290                     else if (intfPair.first == pidConfigurationIface)
291                     {
292 
293                         if (classPtr == nullptr)
294                         {
295                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
296                             messages::internalError(asyncResp->res);
297                             return;
298                         }
299                         bool isFan = *classPtr == "fan";
300                         nlohmann::json& element =
301                             isFan ? fans[name] : pids[name];
302                         config = &element;
303                         if (isFan)
304                         {
305                             element["@odata.id"] =
306                                 "/redfish/v1/Managers/bmc#/Oem/"
307                                 "OpenBmc/Fan/FanControllers/" +
308                                 std::string(name);
309                             element["@odata.type"] =
310                                 "#OemManager.FanController";
311 
312                             element["@odata.context"] =
313                                 "/redfish/v1/"
314                                 "$metadata#OemManager.FanController";
315                         }
316                         else
317                         {
318                             element["@odata.id"] =
319                                 "/redfish/v1/Managers/bmc#/Oem/"
320                                 "OpenBmc/Fan/PidControllers/" +
321                                 std::string(name);
322                             element["@odata.type"] =
323                                 "#OemManager.PidController";
324                             element["@odata.context"] =
325                                 "/redfish/v1/$metadata"
326                                 "#OemManager.PidController";
327                         }
328                     }
329                     else
330                     {
331                         BMCWEB_LOG_ERROR << "Unexpected configuration";
332                         messages::internalError(asyncResp->res);
333                         return;
334                     }
335 
336                     // used for making maps out of 2 vectors
337                     const std::vector<double>* keys = nullptr;
338                     const std::vector<double>* values = nullptr;
339 
340                     for (const auto& propertyPair : intfPair.second)
341                     {
342                         if (propertyPair.first == "Type" ||
343                             propertyPair.first == "Class" ||
344                             propertyPair.first == "Name")
345                         {
346                             continue;
347                         }
348 
349                         // zones
350                         if (intfPair.first == pidZoneConfigurationIface)
351                         {
352                             const double* ptr =
353                                 std::get_if<double>(&propertyPair.second);
354                             if (ptr == nullptr)
355                             {
356                                 BMCWEB_LOG_ERROR << "Field Illegal "
357                                                  << propertyPair.first;
358                                 messages::internalError(asyncResp->res);
359                                 return;
360                             }
361                             (*config)[propertyPair.first] = *ptr;
362                         }
363 
364                         if (intfPair.first == stepwiseConfigurationIface)
365                         {
366                             if (propertyPair.first == "Reading" ||
367                                 propertyPair.first == "Output")
368                             {
369                                 const std::vector<double>* ptr =
370                                     std::get_if<std::vector<double>>(
371                                         &propertyPair.second);
372 
373                                 if (ptr == nullptr)
374                                 {
375                                     BMCWEB_LOG_ERROR << "Field Illegal "
376                                                      << propertyPair.first;
377                                     messages::internalError(asyncResp->res);
378                                     return;
379                                 }
380 
381                                 if (propertyPair.first == "Reading")
382                                 {
383                                     keys = ptr;
384                                 }
385                                 else
386                                 {
387                                     values = ptr;
388                                 }
389                                 if (keys && values)
390                                 {
391                                     if (keys->size() != values->size())
392                                     {
393                                         BMCWEB_LOG_ERROR
394                                             << "Reading and Output size don't "
395                                                "match ";
396                                         messages::internalError(asyncResp->res);
397                                         return;
398                                     }
399                                     nlohmann::json& steps = (*config)["Steps"];
400                                     steps = nlohmann::json::array();
401                                     for (size_t ii = 0; ii < keys->size(); ii++)
402                                     {
403                                         steps.push_back(
404                                             {{"Target", (*keys)[ii]},
405                                              {"Output", (*values)[ii]}});
406                                     }
407                                 }
408                             }
409                             if (propertyPair.first == "NegativeHysteresis" ||
410                                 propertyPair.first == "PositiveHysteresis")
411                             {
412                                 const double* ptr =
413                                     std::get_if<double>(&propertyPair.second);
414                                 if (ptr == nullptr)
415                                 {
416                                     BMCWEB_LOG_ERROR << "Field Illegal "
417                                                      << propertyPair.first;
418                                     messages::internalError(asyncResp->res);
419                                     return;
420                                 }
421                                 (*config)[propertyPair.first] = *ptr;
422                             }
423                         }
424 
425                         // pid and fans are off the same configuration
426                         if (intfPair.first == pidConfigurationIface ||
427                             intfPair.first == stepwiseConfigurationIface)
428                         {
429 
430                             if (propertyPair.first == "Zones")
431                             {
432                                 const std::vector<std::string>* inputs =
433                                     std::get_if<std::vector<std::string>>(
434                                         &propertyPair.second);
435 
436                                 if (inputs == nullptr)
437                                 {
438                                     BMCWEB_LOG_ERROR
439                                         << "Zones Pid Field Illegal";
440                                     messages::internalError(asyncResp->res);
441                                     return;
442                                 }
443                                 auto& data = (*config)[propertyPair.first];
444                                 data = nlohmann::json::array();
445                                 for (std::string itemCopy : *inputs)
446                                 {
447                                     dbus::utility::escapePathForDbus(itemCopy);
448                                     data.push_back(
449                                         {{"@odata.id",
450                                           "/redfish/v1/Managers/bmc#/Oem/"
451                                           "OpenBmc/Fan/FanZones/" +
452                                               itemCopy}});
453                                 }
454                             }
455                             // todo(james): may never happen, but this
456                             // assumes configuration data referenced in the
457                             // PID config is provided by the same daemon, we
458                             // could add another loop to cover all cases,
459                             // but I'm okay kicking this can down the road a
460                             // bit
461 
462                             else if (propertyPair.first == "Inputs" ||
463                                      propertyPair.first == "Outputs")
464                             {
465                                 auto& data = (*config)[propertyPair.first];
466                                 const std::vector<std::string>* inputs =
467                                     std::get_if<std::vector<std::string>>(
468                                         &propertyPair.second);
469 
470                                 if (inputs == nullptr)
471                                 {
472                                     BMCWEB_LOG_ERROR << "Field Illegal "
473                                                      << propertyPair.first;
474                                     messages::internalError(asyncResp->res);
475                                     return;
476                                 }
477                                 data = *inputs;
478                             } // doubles
479                             else if (propertyPair.first ==
480                                          "FFGainCoefficient" ||
481                                      propertyPair.first == "FFOffCoefficient" ||
482                                      propertyPair.first == "ICoefficient" ||
483                                      propertyPair.first == "ILimitMax" ||
484                                      propertyPair.first == "ILimitMin" ||
485                                      propertyPair.first ==
486                                          "PositiveHysteresis" ||
487                                      propertyPair.first ==
488                                          "NegativeHysteresis" ||
489                                      propertyPair.first == "OutLimitMax" ||
490                                      propertyPair.first == "OutLimitMin" ||
491                                      propertyPair.first == "PCoefficient" ||
492                                      propertyPair.first == "SetPoint" ||
493                                      propertyPair.first == "SlewNeg" ||
494                                      propertyPair.first == "SlewPos")
495                             {
496                                 const double* ptr =
497                                     std::get_if<double>(&propertyPair.second);
498                                 if (ptr == nullptr)
499                                 {
500                                     BMCWEB_LOG_ERROR << "Field Illegal "
501                                                      << propertyPair.first;
502                                     messages::internalError(asyncResp->res);
503                                     return;
504                                 }
505                                 (*config)[propertyPair.first] = *ptr;
506                             }
507                         }
508                     }
509                 }
510             }
511         },
512         connection, path, objectManagerIface, "GetManagedObjects");
513 }
514 
515 enum class CreatePIDRet
516 {
517     fail,
518     del,
519     patch
520 };
521 
522 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response,
523                                 std::vector<nlohmann::json>& config,
524                                 std::vector<std::string>& zones)
525 {
526     if (config.empty())
527     {
528         BMCWEB_LOG_ERROR << "Empty Zones";
529         messages::propertyValueFormatError(response->res,
530                                            nlohmann::json::array(), "Zones");
531         return false;
532     }
533     for (auto& odata : config)
534     {
535         std::string path;
536         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
537                                           path))
538         {
539             return false;
540         }
541         std::string input;
542 
543         // 8 below comes from
544         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
545         //     0    1     2      3    4    5      6     7      8
546         if (!dbus::utility::getNthStringFromPath(path, 8, input))
547         {
548             BMCWEB_LOG_ERROR << "Got invalid path " << path;
549             BMCWEB_LOG_ERROR << "Illegal Type Zones";
550             messages::propertyValueFormatError(response->res, odata.dump(),
551                                                "Zones");
552             return false;
553         }
554         boost::replace_all(input, "_", " ");
555         zones.emplace_back(std::move(input));
556     }
557     return true;
558 }
559 
560 static const dbus::utility::ManagedItem*
561     findChassis(const dbus::utility::ManagedObjectType& managedObj,
562                 const std::string& value, std::string& chassis)
563 {
564     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
565 
566     std::string escaped = boost::replace_all_copy(value, " ", "_");
567     escaped = "/" + escaped;
568     auto it = std::find_if(
569         managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) {
570             if (boost::algorithm::ends_with(obj.first.str, escaped))
571             {
572                 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n";
573                 return true;
574             }
575             return false;
576         });
577 
578     if (it == managedObj.end())
579     {
580         return nullptr;
581     }
582     // 5 comes from <chassis-name> being the 5th element
583     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
584     if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
585     {
586         return &(*it);
587     }
588 
589     return nullptr;
590 }
591 
592 static CreatePIDRet createPidInterface(
593     const std::shared_ptr<AsyncResp>& response, const std::string& type,
594     nlohmann::json::iterator it, const std::string& path,
595     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
596     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
597         output,
598     std::string& chassis, const std::string& profile)
599 {
600 
601     // common deleter
602     if (it.value() == nullptr)
603     {
604         std::string iface;
605         if (type == "PidControllers" || type == "FanControllers")
606         {
607             iface = pidConfigurationIface;
608         }
609         else if (type == "FanZones")
610         {
611             iface = pidZoneConfigurationIface;
612         }
613         else if (type == "StepwiseControllers")
614         {
615             iface = stepwiseConfigurationIface;
616         }
617         else
618         {
619             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
620                              << type;
621             messages::propertyUnknown(response->res, type);
622             return CreatePIDRet::fail;
623         }
624         // delete interface
625         crow::connections::systemBus->async_method_call(
626             [response, path](const boost::system::error_code ec) {
627                 if (ec)
628                 {
629                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
630                     messages::internalError(response->res);
631                     return;
632                 }
633                 messages::success(response->res);
634             },
635             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
636         return CreatePIDRet::del;
637     }
638 
639     const dbus::utility::ManagedItem* managedItem = nullptr;
640     if (!createNewObject)
641     {
642         // if we aren't creating a new object, we should be able to find it on
643         // d-bus
644         managedItem = findChassis(managedObj, it.key(), chassis);
645         if (managedItem == nullptr)
646         {
647             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
648             messages::invalidObject(response->res, it.key());
649             return CreatePIDRet::fail;
650         }
651     }
652 
653     if (profile.size() &&
654         (type == "PidControllers" || type == "FanControllers" ||
655          type == "StepwiseControllers"))
656     {
657         if (managedItem == nullptr)
658         {
659             output["Profiles"] = std::vector<std::string>{profile};
660         }
661         else
662         {
663             std::string interface;
664             if (type == "StepwiseControllers")
665             {
666                 interface = stepwiseConfigurationIface;
667             }
668             else
669             {
670                 interface = pidConfigurationIface;
671             }
672             auto findConfig = managedItem->second.find(interface);
673             if (findConfig == managedItem->second.end())
674             {
675                 BMCWEB_LOG_ERROR
676                     << "Failed to find interface in managed object";
677                 messages::internalError(response->res);
678                 return CreatePIDRet::fail;
679             }
680             auto findProfiles = findConfig->second.find("Profiles");
681             if (findProfiles != findConfig->second.end())
682             {
683                 const std::vector<std::string>* curProfiles =
684                     std::get_if<std::vector<std::string>>(
685                         &(findProfiles->second));
686                 if (curProfiles == nullptr)
687                 {
688                     BMCWEB_LOG_ERROR << "Illegal profiles in managed object";
689                     messages::internalError(response->res);
690                     return CreatePIDRet::fail;
691                 }
692                 if (std::find(curProfiles->begin(), curProfiles->end(),
693                               profile) == curProfiles->end())
694                 {
695                     std::vector<std::string> newProfiles = *curProfiles;
696                     newProfiles.push_back(profile);
697                     output["Profiles"] = newProfiles;
698                 }
699             }
700         }
701     }
702 
703     if (type == "PidControllers" || type == "FanControllers")
704     {
705         if (createNewObject)
706         {
707             output["Class"] = type == "PidControllers" ? std::string("temp")
708                                                        : std::string("fan");
709             output["Type"] = std::string("Pid");
710         }
711 
712         std::optional<std::vector<nlohmann::json>> zones;
713         std::optional<std::vector<std::string>> inputs;
714         std::optional<std::vector<std::string>> outputs;
715         std::map<std::string, std::optional<double>> doubles;
716         if (!redfish::json_util::readJson(
717                 it.value(), response->res, "Inputs", inputs, "Outputs", outputs,
718                 "Zones", zones, "FFGainCoefficient",
719                 doubles["FFGainCoefficient"], "FFOffCoefficient",
720                 doubles["FFOffCoefficient"], "ICoefficient",
721                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
722                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
723                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
724                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
725                 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos",
726                 doubles["SlewPos"], "PositiveHysteresis",
727                 doubles["PositiveHysteresis"], "NegativeHysteresis",
728                 doubles["NegativeHysteresis"]))
729         {
730             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
731                              << it.value().dump();
732             return CreatePIDRet::fail;
733         }
734         if (zones)
735         {
736             std::vector<std::string> zonesStr;
737             if (!getZonesFromJsonReq(response, *zones, zonesStr))
738             {
739                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
740                 return CreatePIDRet::fail;
741             }
742             if (chassis.empty() &&
743                 !findChassis(managedObj, zonesStr[0], chassis))
744             {
745                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
746                 messages::invalidObject(response->res, it.key());
747                 return CreatePIDRet::fail;
748             }
749 
750             output["Zones"] = std::move(zonesStr);
751         }
752         if (inputs || outputs)
753         {
754             std::array<std::optional<std::vector<std::string>>*, 2> containers =
755                 {&inputs, &outputs};
756             size_t index = 0;
757             for (const auto& containerPtr : containers)
758             {
759                 std::optional<std::vector<std::string>>& container =
760                     *containerPtr;
761                 if (!container)
762                 {
763                     index++;
764                     continue;
765                 }
766 
767                 for (std::string& value : *container)
768                 {
769                     boost::replace_all(value, "_", " ");
770                 }
771                 std::string key;
772                 if (index == 0)
773                 {
774                     key = "Inputs";
775                 }
776                 else
777                 {
778                     key = "Outputs";
779                 }
780                 output[key] = *container;
781                 index++;
782             }
783         }
784 
785         // doubles
786         for (const auto& pairs : doubles)
787         {
788             if (!pairs.second)
789             {
790                 continue;
791             }
792             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
793             output[pairs.first] = *(pairs.second);
794         }
795     }
796 
797     else if (type == "FanZones")
798     {
799         output["Type"] = std::string("Pid.Zone");
800 
801         std::optional<nlohmann::json> chassisContainer;
802         std::optional<double> failSafePercent;
803         std::optional<double> minThermalOutput;
804         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
805                                           chassisContainer, "FailSafePercent",
806                                           failSafePercent, "MinThermalOutput",
807                                           minThermalOutput))
808         {
809             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
810                              << it.value().dump();
811             return CreatePIDRet::fail;
812         }
813 
814         if (chassisContainer)
815         {
816 
817             std::string chassisId;
818             if (!redfish::json_util::readJson(*chassisContainer, response->res,
819                                               "@odata.id", chassisId))
820             {
821                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
822                                  << chassisContainer->dump();
823                 return CreatePIDRet::fail;
824             }
825 
826             // /refish/v1/chassis/chassis_name/
827             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
828             {
829                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
830                 messages::invalidObject(response->res, chassisId);
831                 return CreatePIDRet::fail;
832             }
833         }
834         if (minThermalOutput)
835         {
836             output["MinThermalOutput"] = *minThermalOutput;
837         }
838         if (failSafePercent)
839         {
840             output["FailSafePercent"] = *failSafePercent;
841         }
842     }
843     else if (type == "StepwiseControllers")
844     {
845         output["Type"] = std::string("Stepwise");
846 
847         std::optional<std::vector<nlohmann::json>> zones;
848         std::optional<std::vector<nlohmann::json>> steps;
849         std::optional<std::vector<std::string>> inputs;
850         std::optional<double> positiveHysteresis;
851         std::optional<double> negativeHysteresis;
852         std::optional<std::string> direction; // upper clipping curve vs lower
853         if (!redfish::json_util::readJson(
854                 it.value(), response->res, "Zones", zones, "Steps", steps,
855                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
856                 "NegativeHysteresis", negativeHysteresis, "Direction",
857                 direction))
858         {
859             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
860                              << it.value().dump();
861             return CreatePIDRet::fail;
862         }
863 
864         if (zones)
865         {
866             std::vector<std::string> zonesStrs;
867             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
868             {
869                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
870                 return CreatePIDRet::fail;
871             }
872             if (chassis.empty() &&
873                 !findChassis(managedObj, zonesStrs[0], chassis))
874             {
875                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
876                 messages::invalidObject(response->res, it.key());
877                 return CreatePIDRet::fail;
878             }
879             output["Zones"] = std::move(zonesStrs);
880         }
881         if (steps)
882         {
883             std::vector<double> readings;
884             std::vector<double> outputs;
885             for (auto& step : *steps)
886             {
887                 double target;
888                 double output;
889 
890                 if (!redfish::json_util::readJson(step, response->res, "Target",
891                                                   target, "Output", output))
892                 {
893                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
894                                      << ", Illegal Property "
895                                      << it.value().dump();
896                     return CreatePIDRet::fail;
897                 }
898                 readings.emplace_back(target);
899                 outputs.emplace_back(output);
900             }
901             output["Reading"] = std::move(readings);
902             output["Output"] = std::move(outputs);
903         }
904         if (inputs)
905         {
906             for (std::string& value : *inputs)
907             {
908                 boost::replace_all(value, "_", " ");
909             }
910             output["Inputs"] = std::move(*inputs);
911         }
912         if (negativeHysteresis)
913         {
914             output["NegativeHysteresis"] = *negativeHysteresis;
915         }
916         if (positiveHysteresis)
917         {
918             output["PositiveHysteresis"] = *positiveHysteresis;
919         }
920         if (direction)
921         {
922             constexpr const std::array<const char*, 2> allowedDirections = {
923                 "Ceiling", "Floor"};
924             if (std::find(allowedDirections.begin(), allowedDirections.end(),
925                           *direction) == allowedDirections.end())
926             {
927                 messages::propertyValueTypeError(response->res, "Direction",
928                                                  *direction);
929                 return CreatePIDRet::fail;
930             }
931             output["Class"] = *direction;
932         }
933     }
934     else
935     {
936         BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type;
937         messages::propertyUnknown(response->res, type);
938         return CreatePIDRet::fail;
939     }
940     return CreatePIDRet::patch;
941 }
942 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
943 {
944 
945     GetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp) :
946         asyncResp(asyncResp)
947 
948     {
949     }
950 
951     void run()
952     {
953         std::shared_ptr<GetPIDValues> self = shared_from_this();
954 
955         // get all configurations
956         crow::connections::systemBus->async_method_call(
957             [self](const boost::system::error_code ec,
958                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
959                 if (ec)
960                 {
961                     BMCWEB_LOG_ERROR << ec;
962                     messages::internalError(self->asyncResp->res);
963                     return;
964                 }
965                 self->subtree = subtree;
966             },
967             "xyz.openbmc_project.ObjectMapper",
968             "/xyz/openbmc_project/object_mapper",
969             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
970             std::array<const char*, 4>{
971                 pidConfigurationIface, pidZoneConfigurationIface,
972                 objectManagerIface, stepwiseConfigurationIface});
973 
974         // at the same time get the selected profile
975         crow::connections::systemBus->async_method_call(
976             [self](const boost::system::error_code ec,
977                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
978                 if (ec || subtree.empty())
979                 {
980                     return;
981                 }
982                 if (subtree[0].second.size() != 1)
983                 {
984                     // invalid mapper response, should never happen
985                     BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error";
986                     messages::internalError(self->asyncResp->res);
987                     return;
988                 }
989 
990                 const std::string& path = subtree[0].first;
991                 const std::string& owner = subtree[0].second[0].first;
992                 crow::connections::systemBus->async_method_call(
993                     [path, owner, self](
994                         const boost::system::error_code ec,
995                         const boost::container::flat_map<
996                             std::string, std::variant<std::vector<std::string>,
997                                                       std::string>>& resp) {
998                         if (ec)
999                         {
1000                             BMCWEB_LOG_ERROR << "GetPIDValues: Can't get "
1001                                                 "thermalModeIface "
1002                                              << path;
1003                             messages::internalError(self->asyncResp->res);
1004                             return;
1005                         }
1006                         const std::string* current;
1007                         const std::vector<std::string>* supported;
1008                         for (auto& [key, value] : resp)
1009                         {
1010                             if (key == "Current")
1011                             {
1012                                 current = std::get_if<std::string>(&value);
1013                                 if (current == nullptr)
1014                                 {
1015                                     BMCWEB_LOG_ERROR
1016                                         << "GetPIDValues: thermal mode "
1017                                            "iface invalid "
1018                                         << path;
1019                                     messages::internalError(
1020                                         self->asyncResp->res);
1021                                     return;
1022                                 }
1023                             }
1024                             if (key == "Supported")
1025                             {
1026                                 supported =
1027                                     std::get_if<std::vector<std::string>>(
1028                                         &value);
1029                                 if (supported == nullptr)
1030                                 {
1031                                     BMCWEB_LOG_ERROR
1032                                         << "GetPIDValues: thermal mode "
1033                                            "iface invalid"
1034                                         << path;
1035                                     messages::internalError(
1036                                         self->asyncResp->res);
1037                                     return;
1038                                 }
1039                             }
1040                         }
1041                         if (current == nullptr || supported == nullptr)
1042                         {
1043                             BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode "
1044                                                 "iface invalid "
1045                                              << path;
1046                             messages::internalError(self->asyncResp->res);
1047                             return;
1048                         }
1049                         self->currentProfile = *current;
1050                         self->supportedProfiles = *supported;
1051                     },
1052                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1053                     thermalModeIface);
1054             },
1055             "xyz.openbmc_project.ObjectMapper",
1056             "/xyz/openbmc_project/object_mapper",
1057             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1058             std::array<const char*, 1>{thermalModeIface});
1059     }
1060 
1061     ~GetPIDValues()
1062     {
1063         if (asyncResp->res.result() != boost::beast::http::status::ok)
1064         {
1065             return;
1066         }
1067         // create map of <connection, path to objMgr>>
1068         boost::container::flat_map<std::string, std::string> objectMgrPaths;
1069         boost::container::flat_set<std::string> calledConnections;
1070         for (const auto& pathGroup : subtree)
1071         {
1072             for (const auto& connectionGroup : pathGroup.second)
1073             {
1074                 auto findConnection =
1075                     calledConnections.find(connectionGroup.first);
1076                 if (findConnection != calledConnections.end())
1077                 {
1078                     break;
1079                 }
1080                 for (const std::string& interface : connectionGroup.second)
1081                 {
1082                     if (interface == objectManagerIface)
1083                     {
1084                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1085                     }
1086                     // this list is alphabetical, so we
1087                     // should have found the objMgr by now
1088                     if (interface == pidConfigurationIface ||
1089                         interface == pidZoneConfigurationIface ||
1090                         interface == stepwiseConfigurationIface)
1091                     {
1092                         auto findObjMgr =
1093                             objectMgrPaths.find(connectionGroup.first);
1094                         if (findObjMgr == objectMgrPaths.end())
1095                         {
1096                             BMCWEB_LOG_DEBUG << connectionGroup.first
1097                                              << "Has no Object Manager";
1098                             continue;
1099                         }
1100 
1101                         calledConnections.insert(connectionGroup.first);
1102 
1103                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1104                                          currentProfile, supportedProfiles,
1105                                          asyncResp);
1106                         break;
1107                     }
1108                 }
1109             }
1110         }
1111     }
1112 
1113     std::vector<std::string> supportedProfiles;
1114     std::string currentProfile;
1115     crow::openbmc_mapper::GetSubTreeType subtree;
1116     std::shared_ptr<AsyncResp> asyncResp;
1117 };
1118 
1119 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1120 {
1121 
1122     SetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp,
1123                  nlohmann::json& data) :
1124         asyncResp(asyncResp)
1125     {
1126 
1127         std::optional<nlohmann::json> pidControllers;
1128         std::optional<nlohmann::json> fanControllers;
1129         std::optional<nlohmann::json> fanZones;
1130         std::optional<nlohmann::json> stepwiseControllers;
1131 
1132         if (!redfish::json_util::readJson(
1133                 data, asyncResp->res, "PidControllers", pidControllers,
1134                 "FanControllers", fanControllers, "FanZones", fanZones,
1135                 "StepwiseControllers", stepwiseControllers, "Profile", profile))
1136         {
1137             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1138                              << data.dump();
1139             return;
1140         }
1141         configuration.emplace_back("PidControllers", std::move(pidControllers));
1142         configuration.emplace_back("FanControllers", std::move(fanControllers));
1143         configuration.emplace_back("FanZones", std::move(fanZones));
1144         configuration.emplace_back("StepwiseControllers",
1145                                    std::move(stepwiseControllers));
1146     }
1147     void run()
1148     {
1149         if (asyncResp->res.result() != boost::beast::http::status::ok)
1150         {
1151             return;
1152         }
1153 
1154         std::shared_ptr<SetPIDValues> self = shared_from_this();
1155 
1156         // todo(james): might make sense to do a mapper call here if this
1157         // interface gets more traction
1158         crow::connections::systemBus->async_method_call(
1159             [self](const boost::system::error_code ec,
1160                    dbus::utility::ManagedObjectType& managedObj) {
1161                 if (ec)
1162                 {
1163                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
1164                     messages::internalError(self->asyncResp->res);
1165                     return;
1166                 }
1167                 self->managedObj = std::move(managedObj);
1168             },
1169             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
1170             "GetManagedObjects");
1171 
1172         // at the same time get the profile information
1173         crow::connections::systemBus->async_method_call(
1174             [self](const boost::system::error_code ec,
1175                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
1176                 if (ec || subtree.empty())
1177                 {
1178                     return;
1179                 }
1180                 if (subtree[0].second.empty())
1181                 {
1182                     // invalid mapper response, should never happen
1183                     BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error";
1184                     messages::internalError(self->asyncResp->res);
1185                     return;
1186                 }
1187 
1188                 const std::string& path = subtree[0].first;
1189                 const std::string& owner = subtree[0].second[0].first;
1190                 crow::connections::systemBus->async_method_call(
1191                     [self, path, owner](
1192                         const boost::system::error_code ec,
1193                         const boost::container::flat_map<
1194                             std::string, std::variant<std::vector<std::string>,
1195                                                       std::string>>& resp) {
1196                         if (ec)
1197                         {
1198                             BMCWEB_LOG_ERROR << "SetPIDValues: Can't get "
1199                                                 "thermalModeIface "
1200                                              << path;
1201                             messages::internalError(self->asyncResp->res);
1202                             return;
1203                         }
1204                         const std::string* current;
1205                         const std::vector<std::string>* supported;
1206                         for (auto& [key, value] : resp)
1207                         {
1208                             if (key == "Current")
1209                             {
1210                                 current = std::get_if<std::string>(&value);
1211                                 if (current == nullptr)
1212                                 {
1213                                     BMCWEB_LOG_ERROR
1214                                         << "SetPIDValues: thermal mode "
1215                                            "iface invalid "
1216                                         << path;
1217                                     messages::internalError(
1218                                         self->asyncResp->res);
1219                                     return;
1220                                 }
1221                             }
1222                             if (key == "Supported")
1223                             {
1224                                 supported =
1225                                     std::get_if<std::vector<std::string>>(
1226                                         &value);
1227                                 if (supported == nullptr)
1228                                 {
1229                                     BMCWEB_LOG_ERROR
1230                                         << "SetPIDValues: thermal mode "
1231                                            "iface invalid"
1232                                         << path;
1233                                     messages::internalError(
1234                                         self->asyncResp->res);
1235                                     return;
1236                                 }
1237                             }
1238                         }
1239                         if (current == nullptr || supported == nullptr)
1240                         {
1241                             BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode "
1242                                                 "iface invalid "
1243                                              << path;
1244                             messages::internalError(self->asyncResp->res);
1245                             return;
1246                         }
1247                         self->currentProfile = *current;
1248                         self->supportedProfiles = *supported;
1249                         self->profileConnection = owner;
1250                         self->profilePath = path;
1251                     },
1252                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1253                     thermalModeIface);
1254             },
1255             "xyz.openbmc_project.ObjectMapper",
1256             "/xyz/openbmc_project/object_mapper",
1257             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1258             std::array<const char*, 1>{thermalModeIface});
1259     }
1260     ~SetPIDValues()
1261     {
1262         if (asyncResp->res.result() != boost::beast::http::status::ok)
1263         {
1264             return;
1265         }
1266 
1267         std::shared_ptr<AsyncResp> response = asyncResp;
1268 
1269         if (profile)
1270         {
1271             if (std::find(supportedProfiles.begin(), supportedProfiles.end(),
1272                           *profile) == supportedProfiles.end())
1273             {
1274                 messages::actionParameterUnknown(response->res, "Profile",
1275                                                  *profile);
1276                 return;
1277             }
1278             currentProfile = *profile;
1279             crow::connections::systemBus->async_method_call(
1280                 [response](const boost::system::error_code ec) {
1281                     if (ec)
1282                     {
1283                         BMCWEB_LOG_ERROR << "Error patching profile" << ec;
1284                         messages::internalError(response->res);
1285                     }
1286                 },
1287                 profileConnection, profilePath,
1288                 "org.freedesktop.DBus.Properties", "Set", thermalModeIface,
1289                 "Current", std::variant<std::string>(*profile));
1290         }
1291 
1292         for (auto& containerPair : configuration)
1293         {
1294             auto& container = containerPair.second;
1295             if (!container)
1296             {
1297                 continue;
1298             }
1299             std::string& type = containerPair.first;
1300 
1301             for (nlohmann::json::iterator it = container->begin();
1302                  it != container->end(); it++)
1303             {
1304                 const auto& name = it.key();
1305                 auto pathItr =
1306                     std::find_if(managedObj.begin(), managedObj.end(),
1307                                  [&name](const auto& obj) {
1308                                      return boost::algorithm::ends_with(
1309                                          obj.first.str, "/" + name);
1310                                  });
1311                 boost::container::flat_map<std::string,
1312                                            dbus::utility::DbusVariantType>
1313                     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                 std::string iface;
1321                 if (type == "PidControllers" || type == "FanControllers")
1322                 {
1323                     iface = pidConfigurationIface;
1324                     if (!createNewObject &&
1325                         pathItr->second.find(pidConfigurationIface) ==
1326                             pathItr->second.end())
1327                     {
1328                         createNewObject = true;
1329                     }
1330                 }
1331                 else if (type == "FanZones")
1332                 {
1333                     iface = pidZoneConfigurationIface;
1334                     if (!createNewObject &&
1335                         pathItr->second.find(pidZoneConfigurationIface) ==
1336                             pathItr->second.end())
1337                     {
1338 
1339                         createNewObject = true;
1340                     }
1341                 }
1342                 else if (type == "StepwiseControllers")
1343                 {
1344                     iface = stepwiseConfigurationIface;
1345                     if (!createNewObject &&
1346                         pathItr->second.find(stepwiseConfigurationIface) ==
1347                             pathItr->second.end())
1348                     {
1349                         createNewObject = true;
1350                     }
1351                 }
1352                 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n";
1353                 output["Name"] = boost::replace_all_copy(name, "_", " ");
1354 
1355                 std::string chassis;
1356                 CreatePIDRet ret = createPidInterface(
1357                     response, type, it, pathItr->first.str, managedObj,
1358                     createNewObject, output, chassis, currentProfile);
1359                 if (ret == CreatePIDRet::fail)
1360                 {
1361                     return;
1362                 }
1363                 else if (ret == CreatePIDRet::del)
1364                 {
1365                     continue;
1366                 }
1367 
1368                 if (!createNewObject)
1369                 {
1370                     for (const auto& property : output)
1371                     {
1372                         crow::connections::systemBus->async_method_call(
1373                             [response,
1374                              propertyName{std::string(property.first)}](
1375                                 const boost::system::error_code ec) {
1376                                 if (ec)
1377                                 {
1378                                     BMCWEB_LOG_ERROR << "Error patching "
1379                                                      << propertyName << ": "
1380                                                      << ec;
1381                                     messages::internalError(response->res);
1382                                     return;
1383                                 }
1384                                 messages::success(response->res);
1385                             },
1386                             "xyz.openbmc_project.EntityManager",
1387                             pathItr->first.str,
1388                             "org.freedesktop.DBus.Properties", "Set", iface,
1389                             property.first, property.second);
1390                     }
1391                 }
1392                 else
1393                 {
1394                     if (chassis.empty())
1395                     {
1396                         BMCWEB_LOG_ERROR << "Failed to get chassis from config";
1397                         messages::invalidObject(response->res, name);
1398                         return;
1399                     }
1400 
1401                     bool foundChassis = false;
1402                     for (const auto& obj : managedObj)
1403                     {
1404                         if (boost::algorithm::ends_with(obj.first.str, chassis))
1405                         {
1406                             chassis = obj.first.str;
1407                             foundChassis = true;
1408                             break;
1409                         }
1410                     }
1411                     if (!foundChassis)
1412                     {
1413                         BMCWEB_LOG_ERROR << "Failed to find chassis on dbus";
1414                         messages::resourceMissingAtURI(
1415                             response->res, "/redfish/v1/Chassis/" + chassis);
1416                         return;
1417                     }
1418 
1419                     crow::connections::systemBus->async_method_call(
1420                         [response](const boost::system::error_code ec) {
1421                             if (ec)
1422                             {
1423                                 BMCWEB_LOG_ERROR << "Error Adding Pid Object "
1424                                                  << ec;
1425                                 messages::internalError(response->res);
1426                                 return;
1427                             }
1428                             messages::success(response->res);
1429                         },
1430                         "xyz.openbmc_project.EntityManager", chassis,
1431                         "xyz.openbmc_project.AddObject", "AddObject", output);
1432                 }
1433             }
1434         }
1435     }
1436     std::shared_ptr<AsyncResp> asyncResp;
1437     std::vector<std::pair<std::string, std::optional<nlohmann::json>>>
1438         configuration;
1439     std::optional<std::string> profile;
1440     dbus::utility::ManagedObjectType managedObj;
1441     std::vector<std::string> supportedProfiles;
1442     std::string currentProfile;
1443     std::string profileConnection;
1444     std::string profilePath;
1445 };
1446 
1447 class Manager : public Node
1448 {
1449   public:
1450     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
1451     {
1452         uuid = app.template getMiddleware<crow::persistent_data::Middleware>()
1453                    .systemUuid;
1454         entityPrivileges = {
1455             {boost::beast::http::verb::get, {{"Login"}}},
1456             {boost::beast::http::verb::head, {{"Login"}}},
1457             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1458             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1459             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1460             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1461     }
1462 
1463   private:
1464     void doGet(crow::Response& res, const crow::Request& req,
1465                const std::vector<std::string>& params) override
1466     {
1467         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
1468         res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager";
1469         res.jsonValue["@odata.context"] =
1470             "/redfish/v1/$metadata#Manager.Manager";
1471         res.jsonValue["Id"] = "bmc";
1472         res.jsonValue["Name"] = "OpenBmc Manager";
1473         res.jsonValue["Description"] = "Baseboard Management Controller";
1474         res.jsonValue["PowerState"] = "On";
1475         res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}};
1476         res.jsonValue["ManagerType"] = "BMC";
1477         res.jsonValue["UUID"] = systemd_utils::getUuid();
1478         res.jsonValue["ServiceEntryPointUUID"] = uuid;
1479         res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
1480 
1481         res.jsonValue["LogServices"] = {
1482             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
1483 
1484         res.jsonValue["NetworkProtocol"] = {
1485             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
1486 
1487         res.jsonValue["EthernetInterfaces"] = {
1488             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
1489         // default oem data
1490         nlohmann::json& oem = res.jsonValue["Oem"];
1491         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
1492         oem["@odata.type"] = "#OemManager.Oem";
1493         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
1494         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
1495         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
1496         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
1497         oemOpenbmc["@odata.context"] =
1498             "/redfish/v1/$metadata#OemManager.OpenBmc";
1499 
1500         // Update Actions object.
1501         nlohmann::json& manager_reset =
1502             res.jsonValue["Actions"]["#Manager.Reset"];
1503         manager_reset["target"] =
1504             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
1505         manager_reset["ResetType@Redfish.AllowableValues"] = {
1506             "GracefulRestart"};
1507 
1508         res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
1509 
1510         // Fill in GraphicalConsole and SerialConsole info
1511         res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
1512         res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI",
1513                                                                    "SSH"};
1514 #ifdef BMCWEB_ENABLE_KVM
1515         res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true;
1516         res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"};
1517 #endif // BMCWEB_ENABLE_KVM
1518 
1519         res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
1520         res.jsonValue["Links"]["ManagerForServers"] = {
1521             {{"@odata.id", "/redfish/v1/Systems/system"}}};
1522 
1523         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1524 
1525         crow::connections::systemBus->async_method_call(
1526             [asyncResp](const boost::system::error_code ec,
1527                         const dbus::utility::ManagedObjectType& resp) {
1528                 if (ec)
1529                 {
1530                     BMCWEB_LOG_ERROR << "Error while getting Software Version";
1531                     messages::internalError(asyncResp->res);
1532                     return;
1533                 }
1534 
1535                 for (auto& objpath : resp)
1536                 {
1537                     for (auto& interface : objpath.second)
1538                     {
1539                         // If interface is
1540                         // xyz.openbmc_project.Software.Version, this is
1541                         // what we're looking for.
1542                         if (interface.first ==
1543                             "xyz.openbmc_project.Software.Version")
1544                         {
1545                             // Cut out everyting until last "/", ...
1546                             for (auto& property : interface.second)
1547                             {
1548                                 if (property.first == "Version")
1549                                 {
1550                                     const std::string* value =
1551                                         std::get_if<std::string>(
1552                                             &property.second);
1553                                     if (value == nullptr)
1554                                     {
1555                                         continue;
1556                                     }
1557                                     asyncResp->res
1558                                         .jsonValue["FirmwareVersion"] = *value;
1559                                 }
1560                             }
1561                         }
1562                     }
1563                 }
1564             },
1565             "xyz.openbmc_project.Software.BMC.Updater",
1566             "/xyz/openbmc_project/software",
1567             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1568         auto pids = std::make_shared<GetPIDValues>(asyncResp);
1569         pids->run();
1570 
1571         getMainChassisId(asyncResp, [](const std::string& chassisId,
1572                                        const std::shared_ptr<AsyncResp> aRsp) {
1573             aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1;
1574             aRsp->res.jsonValue["Links"]["ManagerForChassis"] = {
1575                 {{"@odata.id", "/redfish/v1/Chassis/" + chassisId}}};
1576         });
1577     }
1578 
1579     void doPatch(crow::Response& res, const crow::Request& req,
1580                  const std::vector<std::string>& params) override
1581     {
1582         std::optional<nlohmann::json> oem;
1583         std::optional<std::string> datetime;
1584 
1585         if (!json_util::readJson(req, res, "Oem", oem, "DateTime", datetime))
1586         {
1587             return;
1588         }
1589 
1590         std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res);
1591 
1592         if (oem)
1593         {
1594             std::optional<nlohmann::json> openbmc;
1595             if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc))
1596             {
1597                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1598                                  << oem->dump();
1599                 return;
1600             }
1601             if (openbmc)
1602             {
1603                 std::optional<nlohmann::json> fan;
1604                 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan))
1605                 {
1606                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1607                                      << ", Illegal Property "
1608                                      << openbmc->dump();
1609                     return;
1610                 }
1611                 if (fan)
1612                 {
1613                     auto pid = std::make_shared<SetPIDValues>(response, *fan);
1614                     pid->run();
1615                 }
1616             }
1617         }
1618         if (datetime)
1619         {
1620             setDateTime(response, std::move(*datetime));
1621         }
1622     }
1623 
1624     void setDateTime(std::shared_ptr<AsyncResp> aResp,
1625                      std::string datetime) const
1626     {
1627         BMCWEB_LOG_DEBUG << "Set date time: " << datetime;
1628 
1629         std::stringstream stream(datetime);
1630         // Convert from ISO 8601 to boost local_time
1631         // (BMC only has time in UTC)
1632         boost::posix_time::ptime posixTime;
1633         boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
1634         // Facet gets deleted with the stringsteam
1635         auto ifc = std::make_unique<boost::local_time::local_time_input_facet>(
1636             "%Y-%m-%d %H:%M:%S%F %ZP");
1637         stream.imbue(std::locale(stream.getloc(), ifc.release()));
1638 
1639         boost::local_time::local_date_time ldt(
1640             boost::local_time::not_a_date_time);
1641 
1642         if (stream >> ldt)
1643         {
1644             posixTime = ldt.utc_time();
1645             boost::posix_time::time_duration dur = posixTime - epoch;
1646             uint64_t durMicroSecs =
1647                 static_cast<uint64_t>(dur.total_microseconds());
1648             crow::connections::systemBus->async_method_call(
1649                 [aResp{std::move(aResp)}, datetime{std::move(datetime)}](
1650                     const boost::system::error_code ec) {
1651                     if (ec)
1652                     {
1653                         BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
1654                                             "DBUS response error "
1655                                          << ec;
1656                         messages::internalError(aResp->res);
1657                         return;
1658                     }
1659                     aResp->res.jsonValue["DateTime"] = datetime;
1660                 },
1661                 "xyz.openbmc_project.Time.Manager",
1662                 "/xyz/openbmc_project/time/bmc",
1663                 "org.freedesktop.DBus.Properties", "Set",
1664                 "xyz.openbmc_project.Time.EpochTime", "Elapsed",
1665                 std::variant<uint64_t>(durMicroSecs));
1666         }
1667         else
1668         {
1669             messages::propertyValueFormatError(aResp->res, datetime,
1670                                                "DateTime");
1671             return;
1672         }
1673     }
1674 
1675     std::string uuid;
1676 };
1677 
1678 class ManagerCollection : public Node
1679 {
1680   public:
1681     ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
1682     {
1683         entityPrivileges = {
1684             {boost::beast::http::verb::get, {{"Login"}}},
1685             {boost::beast::http::verb::head, {{"Login"}}},
1686             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1687             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1688             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1689             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1690     }
1691 
1692   private:
1693     void doGet(crow::Response& res, const crow::Request& req,
1694                const std::vector<std::string>& params) override
1695     {
1696         // Collections don't include the static data added by SubRoute
1697         // because it has a duplicate entry for members
1698         res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
1699         res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection";
1700         res.jsonValue["@odata.context"] =
1701             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
1702         res.jsonValue["Name"] = "Manager Collection";
1703         res.jsonValue["Members@odata.count"] = 1;
1704         res.jsonValue["Members"] = {
1705             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
1706         res.end();
1707     }
1708 };
1709 } // namespace redfish
1710