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