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 "health.hpp"
19 #include "node.hpp"
20 #include "redfish_util.hpp"
21 
22 #include <boost/algorithm/string/replace.hpp>
23 #include <boost/date_time.hpp>
24 #include <dbus_utility.hpp>
25 #include <utils/fw_utils.hpp>
26 #include <utils/systemd_utils.hpp>
27 
28 #include <cstdint>
29 #include <memory>
30 #include <sstream>
31 #include <variant>
32 
33 namespace redfish
34 {
35 
36 /**
37  * Function reboots the BMC.
38  *
39  * @param[in] asyncResp - Shared pointer for completing asynchronous calls
40  */
41 inline void
42     doBMCGracefulRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
43 {
44     const char* processName = "xyz.openbmc_project.State.BMC";
45     const char* objectPath = "/xyz/openbmc_project/state/bmc0";
46     const char* interfaceName = "xyz.openbmc_project.State.BMC";
47     const std::string& propertyValue =
48         "xyz.openbmc_project.State.BMC.Transition.Reboot";
49     const char* destProperty = "RequestedBMCTransition";
50 
51     // Create the D-Bus variant for D-Bus call.
52     VariantType dbusPropertyValue(propertyValue);
53 
54     crow::connections::systemBus->async_method_call(
55         [asyncResp](const boost::system::error_code ec) {
56             // Use "Set" method to set the property value.
57             if (ec)
58             {
59                 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec;
60                 messages::internalError(asyncResp->res);
61                 return;
62             }
63 
64             messages::success(asyncResp->res);
65         },
66         processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
67         interfaceName, destProperty, dbusPropertyValue);
68 }
69 
70 inline void
71     doBMCForceRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
72 {
73     const char* processName = "xyz.openbmc_project.State.BMC";
74     const char* objectPath = "/xyz/openbmc_project/state/bmc0";
75     const char* interfaceName = "xyz.openbmc_project.State.BMC";
76     const std::string& propertyValue =
77         "xyz.openbmc_project.State.BMC.Transition.HardReboot";
78     const char* destProperty = "RequestedBMCTransition";
79 
80     // Create the D-Bus variant for D-Bus call.
81     VariantType dbusPropertyValue(propertyValue);
82 
83     crow::connections::systemBus->async_method_call(
84         [asyncResp](const boost::system::error_code ec) {
85             // Use "Set" method to set the property value.
86             if (ec)
87             {
88                 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec;
89                 messages::internalError(asyncResp->res);
90                 return;
91             }
92 
93             messages::success(asyncResp->res);
94         },
95         processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
96         interfaceName, destProperty, dbusPropertyValue);
97 }
98 
99 /**
100  * ManagerResetAction class supports the POST method for the Reset (reboot)
101  * action.
102  */
103 class ManagerResetAction : public Node
104 {
105   public:
106     ManagerResetAction(App& app) :
107         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
108     {
109         entityPrivileges = {
110             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
111     }
112 
113   private:
114     /**
115      * Function handles POST method request.
116      * Analyzes POST body before sending Reset (Reboot) request data to D-Bus.
117      * OpenBMC supports ResetType "GracefulRestart" and "ForceRestart".
118      */
119     void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
120                 const crow::Request& req,
121                 const std::vector<std::string>&) override
122     {
123         BMCWEB_LOG_DEBUG << "Post Manager Reset.";
124 
125         std::string resetType;
126 
127         if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType))
128         {
129             return;
130         }
131 
132         if (resetType == "GracefulRestart")
133         {
134             BMCWEB_LOG_DEBUG << "Proceeding with " << resetType;
135             doBMCGracefulRestart(asyncResp);
136             return;
137         }
138         if (resetType == "ForceRestart")
139         {
140             BMCWEB_LOG_DEBUG << "Proceeding with " << resetType;
141             doBMCForceRestart(asyncResp);
142             return;
143         }
144         BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: "
145                          << resetType;
146         messages::actionParameterNotSupported(asyncResp->res, resetType,
147                                               "ResetType");
148 
149         return;
150     }
151 };
152 
153 /**
154  * ManagerResetToDefaultsAction class supports POST method for factory reset
155  * action.
156  */
157 class ManagerResetToDefaultsAction : public Node
158 {
159   public:
160     ManagerResetToDefaultsAction(App& app) :
161         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults/")
162     {
163         entityPrivileges = {
164             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
165     }
166 
167   private:
168     /**
169      * Function handles ResetToDefaults POST method request.
170      *
171      * Analyzes POST body message and factory resets BMC by calling
172      * BMC code updater factory reset followed by a BMC reboot.
173      *
174      * BMC code updater factory reset wipes the whole BMC read-write
175      * filesystem which includes things like the network settings.
176      *
177      * OpenBMC only supports ResetToDefaultsType "ResetAll".
178      */
179     void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
180                 const crow::Request& req,
181                 const std::vector<std::string>&) override
182     {
183         BMCWEB_LOG_DEBUG << "Post ResetToDefaults.";
184 
185         std::string resetType;
186 
187         if (!json_util::readJson(req, asyncResp->res, "ResetToDefaultsType",
188                                  resetType))
189         {
190             BMCWEB_LOG_DEBUG << "Missing property ResetToDefaultsType.";
191 
192             messages::actionParameterMissing(asyncResp->res, "ResetToDefaults",
193                                              "ResetToDefaultsType");
194             return;
195         }
196 
197         if (resetType != "ResetAll")
198         {
199             BMCWEB_LOG_DEBUG << "Invalid property value for "
200                                 "ResetToDefaultsType: "
201                              << resetType;
202             messages::actionParameterNotSupported(asyncResp->res, resetType,
203                                                   "ResetToDefaultsType");
204             return;
205         }
206 
207         crow::connections::systemBus->async_method_call(
208             [asyncResp](const boost::system::error_code ec) {
209                 if (ec)
210                 {
211                     BMCWEB_LOG_DEBUG << "Failed to ResetToDefaults: " << ec;
212                     messages::internalError(asyncResp->res);
213                     return;
214                 }
215                 // Factory Reset doesn't actually happen until a reboot
216                 // Can't erase what the BMC is running on
217                 doBMCGracefulRestart(asyncResp);
218             },
219             "xyz.openbmc_project.Software.BMC.Updater",
220             "/xyz/openbmc_project/software",
221             "xyz.openbmc_project.Common.FactoryReset", "Reset");
222     }
223 };
224 
225 /**
226  * ManagerResetActionInfo derived class for delivering Manager
227  * ResetType AllowableValues using ResetInfo schema.
228  */
229 class ManagerResetActionInfo : public Node
230 {
231   public:
232     /*
233      * Default Constructor
234      */
235     ManagerResetActionInfo(App& app) :
236         Node(app, "/redfish/v1/Managers/bmc/ResetActionInfo/")
237     {
238         entityPrivileges = {
239             {boost::beast::http::verb::get, {{"Login"}}},
240             {boost::beast::http::verb::head, {{"Login"}}},
241             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
242             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
243             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
244             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
245     }
246 
247   private:
248     /**
249      * Functions triggers appropriate requests on DBus
250      */
251     void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
252                const crow::Request&, const std::vector<std::string>&) override
253     {
254         asyncResp->res.jsonValue = {
255             {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"},
256             {"@odata.id", "/redfish/v1/Managers/bmc/ResetActionInfo"},
257             {"Name", "Reset Action Info"},
258             {"Id", "ResetActionInfo"},
259             {"Parameters",
260              {{{"Name", "ResetType"},
261                {"Required", true},
262                {"DataType", "String"},
263                {"AllowableValues", {"GracefulRestart", "ForceRestart"}}}}}};
264     }
265 };
266 
267 static constexpr const char* objectManagerIface =
268     "org.freedesktop.DBus.ObjectManager";
269 static constexpr const char* pidConfigurationIface =
270     "xyz.openbmc_project.Configuration.Pid";
271 static constexpr const char* pidZoneConfigurationIface =
272     "xyz.openbmc_project.Configuration.Pid.Zone";
273 static constexpr const char* stepwiseConfigurationIface =
274     "xyz.openbmc_project.Configuration.Stepwise";
275 static constexpr const char* thermalModeIface =
276     "xyz.openbmc_project.Control.ThermalMode";
277 
278 inline void
279     asyncPopulatePid(const std::string& connection, const std::string& path,
280                      const std::string& currentProfile,
281                      const std::vector<std::string>& supportedProfiles,
282                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
283 {
284 
285     crow::connections::systemBus->async_method_call(
286         [asyncResp, currentProfile, supportedProfiles](
287             const boost::system::error_code ec,
288             const dbus::utility::ManagedObjectType& managedObj) {
289             if (ec)
290             {
291                 BMCWEB_LOG_ERROR << ec;
292                 asyncResp->res.jsonValue.clear();
293                 messages::internalError(asyncResp->res);
294                 return;
295             }
296             nlohmann::json& configRoot =
297                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
298             nlohmann::json& fans = configRoot["FanControllers"];
299             fans["@odata.type"] = "#OemManager.FanControllers";
300             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
301                                 "Fan/FanControllers";
302 
303             nlohmann::json& pids = configRoot["PidControllers"];
304             pids["@odata.type"] = "#OemManager.PidControllers";
305             pids["@odata.id"] =
306                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
307 
308             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
309             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
310             stepwise["@odata.id"] =
311                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
312 
313             nlohmann::json& zones = configRoot["FanZones"];
314             zones["@odata.id"] =
315                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
316             zones["@odata.type"] = "#OemManager.FanZones";
317             configRoot["@odata.id"] =
318                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
319             configRoot["@odata.type"] = "#OemManager.Fan";
320             configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
321 
322             if (!currentProfile.empty())
323             {
324                 configRoot["Profile"] = currentProfile;
325             }
326             BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !";
327 
328             for (const auto& pathPair : managedObj)
329             {
330                 for (const auto& intfPair : pathPair.second)
331                 {
332                     if (intfPair.first != pidConfigurationIface &&
333                         intfPair.first != pidZoneConfigurationIface &&
334                         intfPair.first != stepwiseConfigurationIface)
335                     {
336                         continue;
337                     }
338                     auto findName = intfPair.second.find("Name");
339                     if (findName == intfPair.second.end())
340                     {
341                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
342                         messages::internalError(asyncResp->res);
343                         return;
344                     }
345 
346                     const std::string* namePtr =
347                         std::get_if<std::string>(&findName->second);
348                     if (namePtr == nullptr)
349                     {
350                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
351                         messages::internalError(asyncResp->res);
352                         return;
353                     }
354                     std::string name = *namePtr;
355                     dbus::utility::escapePathForDbus(name);
356 
357                     auto findProfiles = intfPair.second.find("Profiles");
358                     if (findProfiles != intfPair.second.end())
359                     {
360                         const std::vector<std::string>* profiles =
361                             std::get_if<std::vector<std::string>>(
362                                 &findProfiles->second);
363                         if (profiles == nullptr)
364                         {
365                             BMCWEB_LOG_ERROR << "Pid Profiles Field illegal";
366                             messages::internalError(asyncResp->res);
367                             return;
368                         }
369                         if (std::find(profiles->begin(), profiles->end(),
370                                       currentProfile) == profiles->end())
371                         {
372                             BMCWEB_LOG_INFO
373                                 << name << " not supported in current profile";
374                             continue;
375                         }
376                     }
377                     nlohmann::json* config = nullptr;
378 
379                     const std::string* classPtr = nullptr;
380                     auto findClass = intfPair.second.find("Class");
381                     if (findClass != intfPair.second.end())
382                     {
383                         classPtr = std::get_if<std::string>(&findClass->second);
384                     }
385 
386                     if (intfPair.first == pidZoneConfigurationIface)
387                     {
388                         std::string chassis;
389                         if (!dbus::utility::getNthStringFromPath(
390                                 pathPair.first.str, 5, chassis))
391                         {
392                             chassis = "#IllegalValue";
393                         }
394                         nlohmann::json& zone = zones[name];
395                         zone["Chassis"] = {
396                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
397                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
398                                             "OpenBmc/Fan/FanZones/" +
399                                             name;
400                         zone["@odata.type"] = "#OemManager.FanZone";
401                         config = &zone;
402                     }
403 
404                     else if (intfPair.first == stepwiseConfigurationIface)
405                     {
406                         if (classPtr == nullptr)
407                         {
408                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
409                             messages::internalError(asyncResp->res);
410                             return;
411                         }
412 
413                         nlohmann::json& controller = stepwise[name];
414                         config = &controller;
415 
416                         controller["@odata.id"] =
417                             "/redfish/v1/Managers/bmc#/Oem/"
418                             "OpenBmc/Fan/StepwiseControllers/" +
419                             name;
420                         controller["@odata.type"] =
421                             "#OemManager.StepwiseController";
422 
423                         controller["Direction"] = *classPtr;
424                     }
425 
426                     // pid and fans are off the same configuration
427                     else if (intfPair.first == pidConfigurationIface)
428                     {
429 
430                         if (classPtr == nullptr)
431                         {
432                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
433                             messages::internalError(asyncResp->res);
434                             return;
435                         }
436                         bool isFan = *classPtr == "fan";
437                         nlohmann::json& element =
438                             isFan ? fans[name] : pids[name];
439                         config = &element;
440                         if (isFan)
441                         {
442                             element["@odata.id"] =
443                                 "/redfish/v1/Managers/bmc#/Oem/"
444                                 "OpenBmc/Fan/FanControllers/" +
445                                 name;
446                             element["@odata.type"] =
447                                 "#OemManager.FanController";
448                         }
449                         else
450                         {
451                             element["@odata.id"] =
452                                 "/redfish/v1/Managers/bmc#/Oem/"
453                                 "OpenBmc/Fan/PidControllers/" +
454                                 name;
455                             element["@odata.type"] =
456                                 "#OemManager.PidController";
457                         }
458                     }
459                     else
460                     {
461                         BMCWEB_LOG_ERROR << "Unexpected configuration";
462                         messages::internalError(asyncResp->res);
463                         return;
464                     }
465 
466                     // used for making maps out of 2 vectors
467                     const std::vector<double>* keys = nullptr;
468                     const std::vector<double>* values = nullptr;
469 
470                     for (const auto& propertyPair : intfPair.second)
471                     {
472                         if (propertyPair.first == "Type" ||
473                             propertyPair.first == "Class" ||
474                             propertyPair.first == "Name")
475                         {
476                             continue;
477                         }
478 
479                         // zones
480                         if (intfPair.first == pidZoneConfigurationIface)
481                         {
482                             const double* ptr =
483                                 std::get_if<double>(&propertyPair.second);
484                             if (ptr == nullptr)
485                             {
486                                 BMCWEB_LOG_ERROR << "Field Illegal "
487                                                  << propertyPair.first;
488                                 messages::internalError(asyncResp->res);
489                                 return;
490                             }
491                             (*config)[propertyPair.first] = *ptr;
492                         }
493 
494                         if (intfPair.first == stepwiseConfigurationIface)
495                         {
496                             if (propertyPair.first == "Reading" ||
497                                 propertyPair.first == "Output")
498                             {
499                                 const std::vector<double>* ptr =
500                                     std::get_if<std::vector<double>>(
501                                         &propertyPair.second);
502 
503                                 if (ptr == nullptr)
504                                 {
505                                     BMCWEB_LOG_ERROR << "Field Illegal "
506                                                      << propertyPair.first;
507                                     messages::internalError(asyncResp->res);
508                                     return;
509                                 }
510 
511                                 if (propertyPair.first == "Reading")
512                                 {
513                                     keys = ptr;
514                                 }
515                                 else
516                                 {
517                                     values = ptr;
518                                 }
519                                 if (keys && values)
520                                 {
521                                     if (keys->size() != values->size())
522                                     {
523                                         BMCWEB_LOG_ERROR
524                                             << "Reading and Output size don't "
525                                                "match ";
526                                         messages::internalError(asyncResp->res);
527                                         return;
528                                     }
529                                     nlohmann::json& steps = (*config)["Steps"];
530                                     steps = nlohmann::json::array();
531                                     for (size_t ii = 0; ii < keys->size(); ii++)
532                                     {
533                                         steps.push_back(
534                                             {{"Target", (*keys)[ii]},
535                                              {"Output", (*values)[ii]}});
536                                     }
537                                 }
538                             }
539                             if (propertyPair.first == "NegativeHysteresis" ||
540                                 propertyPair.first == "PositiveHysteresis")
541                             {
542                                 const double* ptr =
543                                     std::get_if<double>(&propertyPair.second);
544                                 if (ptr == nullptr)
545                                 {
546                                     BMCWEB_LOG_ERROR << "Field Illegal "
547                                                      << propertyPair.first;
548                                     messages::internalError(asyncResp->res);
549                                     return;
550                                 }
551                                 (*config)[propertyPair.first] = *ptr;
552                             }
553                         }
554 
555                         // pid and fans are off the same configuration
556                         if (intfPair.first == pidConfigurationIface ||
557                             intfPair.first == stepwiseConfigurationIface)
558                         {
559 
560                             if (propertyPair.first == "Zones")
561                             {
562                                 const std::vector<std::string>* inputs =
563                                     std::get_if<std::vector<std::string>>(
564                                         &propertyPair.second);
565 
566                                 if (inputs == nullptr)
567                                 {
568                                     BMCWEB_LOG_ERROR
569                                         << "Zones Pid Field Illegal";
570                                     messages::internalError(asyncResp->res);
571                                     return;
572                                 }
573                                 auto& data = (*config)[propertyPair.first];
574                                 data = nlohmann::json::array();
575                                 for (std::string itemCopy : *inputs)
576                                 {
577                                     dbus::utility::escapePathForDbus(itemCopy);
578                                     data.push_back(
579                                         {{"@odata.id",
580                                           "/redfish/v1/Managers/bmc#/Oem/"
581                                           "OpenBmc/Fan/FanZones/" +
582                                               itemCopy}});
583                                 }
584                             }
585                             // todo(james): may never happen, but this
586                             // assumes configuration data referenced in the
587                             // PID config is provided by the same daemon, we
588                             // could add another loop to cover all cases,
589                             // but I'm okay kicking this can down the road a
590                             // bit
591 
592                             else if (propertyPair.first == "Inputs" ||
593                                      propertyPair.first == "Outputs")
594                             {
595                                 auto& data = (*config)[propertyPair.first];
596                                 const std::vector<std::string>* inputs =
597                                     std::get_if<std::vector<std::string>>(
598                                         &propertyPair.second);
599 
600                                 if (inputs == nullptr)
601                                 {
602                                     BMCWEB_LOG_ERROR << "Field Illegal "
603                                                      << propertyPair.first;
604                                     messages::internalError(asyncResp->res);
605                                     return;
606                                 }
607                                 data = *inputs;
608                             }
609                             else if (propertyPair.first == "SetPointOffset")
610                             {
611                                 const std::string* ptr =
612                                     std::get_if<std::string>(
613                                         &propertyPair.second);
614 
615                                 if (ptr == nullptr)
616                                 {
617                                     BMCWEB_LOG_ERROR << "Field Illegal "
618                                                      << propertyPair.first;
619                                     messages::internalError(asyncResp->res);
620                                     return;
621                                 }
622                                 // translate from dbus to redfish
623                                 if (*ptr == "WarningHigh")
624                                 {
625                                     (*config)["SetPointOffset"] =
626                                         "UpperThresholdNonCritical";
627                                 }
628                                 else if (*ptr == "WarningLow")
629                                 {
630                                     (*config)["SetPointOffset"] =
631                                         "LowerThresholdNonCritical";
632                                 }
633                                 else if (*ptr == "CriticalHigh")
634                                 {
635                                     (*config)["SetPointOffset"] =
636                                         "UpperThresholdCritical";
637                                 }
638                                 else if (*ptr == "CriticalLow")
639                                 {
640                                     (*config)["SetPointOffset"] =
641                                         "LowerThresholdCritical";
642                                 }
643                                 else
644                                 {
645                                     BMCWEB_LOG_ERROR << "Value Illegal "
646                                                      << *ptr;
647                                     messages::internalError(asyncResp->res);
648                                     return;
649                                 }
650                             }
651                             // doubles
652                             else if (propertyPair.first ==
653                                          "FFGainCoefficient" ||
654                                      propertyPair.first == "FFOffCoefficient" ||
655                                      propertyPair.first == "ICoefficient" ||
656                                      propertyPair.first == "ILimitMax" ||
657                                      propertyPair.first == "ILimitMin" ||
658                                      propertyPair.first ==
659                                          "PositiveHysteresis" ||
660                                      propertyPair.first ==
661                                          "NegativeHysteresis" ||
662                                      propertyPair.first == "OutLimitMax" ||
663                                      propertyPair.first == "OutLimitMin" ||
664                                      propertyPair.first == "PCoefficient" ||
665                                      propertyPair.first == "SetPoint" ||
666                                      propertyPair.first == "SlewNeg" ||
667                                      propertyPair.first == "SlewPos")
668                             {
669                                 const double* ptr =
670                                     std::get_if<double>(&propertyPair.second);
671                                 if (ptr == nullptr)
672                                 {
673                                     BMCWEB_LOG_ERROR << "Field Illegal "
674                                                      << propertyPair.first;
675                                     messages::internalError(asyncResp->res);
676                                     return;
677                                 }
678                                 (*config)[propertyPair.first] = *ptr;
679                             }
680                         }
681                     }
682                 }
683             }
684         },
685         connection, path, objectManagerIface, "GetManagedObjects");
686 }
687 
688 enum class CreatePIDRet
689 {
690     fail,
691     del,
692     patch
693 };
694 
695 inline bool
696     getZonesFromJsonReq(const std::shared_ptr<bmcweb::AsyncResp>& response,
697                         std::vector<nlohmann::json>& config,
698                         std::vector<std::string>& zones)
699 {
700     if (config.empty())
701     {
702         BMCWEB_LOG_ERROR << "Empty Zones";
703         messages::propertyValueFormatError(response->res,
704                                            nlohmann::json::array(), "Zones");
705         return false;
706     }
707     for (auto& odata : config)
708     {
709         std::string path;
710         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
711                                           path))
712         {
713             return false;
714         }
715         std::string input;
716 
717         // 8 below comes from
718         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
719         //     0    1     2      3    4    5      6     7      8
720         if (!dbus::utility::getNthStringFromPath(path, 8, input))
721         {
722             BMCWEB_LOG_ERROR << "Got invalid path " << path;
723             BMCWEB_LOG_ERROR << "Illegal Type Zones";
724             messages::propertyValueFormatError(response->res, odata.dump(),
725                                                "Zones");
726             return false;
727         }
728         boost::replace_all(input, "_", " ");
729         zones.emplace_back(std::move(input));
730     }
731     return true;
732 }
733 
734 inline const dbus::utility::ManagedItem*
735     findChassis(const dbus::utility::ManagedObjectType& managedObj,
736                 const std::string& value, std::string& chassis)
737 {
738     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
739 
740     std::string escaped = boost::replace_all_copy(value, " ", "_");
741     escaped = "/" + escaped;
742     auto it = std::find_if(
743         managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) {
744             if (boost::algorithm::ends_with(obj.first.str, escaped))
745             {
746                 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n";
747                 return true;
748             }
749             return false;
750         });
751 
752     if (it == managedObj.end())
753     {
754         return nullptr;
755     }
756     // 5 comes from <chassis-name> being the 5th element
757     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
758     if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
759     {
760         return &(*it);
761     }
762 
763     return nullptr;
764 }
765 
766 inline CreatePIDRet createPidInterface(
767     const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type,
768     const nlohmann::json::iterator& it, const std::string& path,
769     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
770     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
771         output,
772     std::string& chassis, const std::string& profile)
773 {
774 
775     // common deleter
776     if (it.value() == nullptr)
777     {
778         std::string iface;
779         if (type == "PidControllers" || type == "FanControllers")
780         {
781             iface = pidConfigurationIface;
782         }
783         else if (type == "FanZones")
784         {
785             iface = pidZoneConfigurationIface;
786         }
787         else if (type == "StepwiseControllers")
788         {
789             iface = stepwiseConfigurationIface;
790         }
791         else
792         {
793             BMCWEB_LOG_ERROR << "Illegal Type " << type;
794             messages::propertyUnknown(response->res, type);
795             return CreatePIDRet::fail;
796         }
797 
798         BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n";
799         // delete interface
800         crow::connections::systemBus->async_method_call(
801             [response, path](const boost::system::error_code ec) {
802                 if (ec)
803                 {
804                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
805                     messages::internalError(response->res);
806                     return;
807                 }
808                 messages::success(response->res);
809             },
810             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
811         return CreatePIDRet::del;
812     }
813 
814     const dbus::utility::ManagedItem* managedItem = nullptr;
815     if (!createNewObject)
816     {
817         // if we aren't creating a new object, we should be able to find it on
818         // d-bus
819         managedItem = findChassis(managedObj, it.key(), chassis);
820         if (managedItem == nullptr)
821         {
822             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
823             messages::invalidObject(response->res, it.key());
824             return CreatePIDRet::fail;
825         }
826     }
827 
828     if (profile.size() &&
829         (type == "PidControllers" || type == "FanControllers" ||
830          type == "StepwiseControllers"))
831     {
832         if (managedItem == nullptr)
833         {
834             output["Profiles"] = std::vector<std::string>{profile};
835         }
836         else
837         {
838             std::string interface;
839             if (type == "StepwiseControllers")
840             {
841                 interface = stepwiseConfigurationIface;
842             }
843             else
844             {
845                 interface = pidConfigurationIface;
846             }
847             auto findConfig = managedItem->second.find(interface);
848             if (findConfig == managedItem->second.end())
849             {
850                 BMCWEB_LOG_ERROR
851                     << "Failed to find interface in managed object";
852                 messages::internalError(response->res);
853                 return CreatePIDRet::fail;
854             }
855             auto findProfiles = findConfig->second.find("Profiles");
856             if (findProfiles != findConfig->second.end())
857             {
858                 const std::vector<std::string>* curProfiles =
859                     std::get_if<std::vector<std::string>>(
860                         &(findProfiles->second));
861                 if (curProfiles == nullptr)
862                 {
863                     BMCWEB_LOG_ERROR << "Illegal profiles in managed object";
864                     messages::internalError(response->res);
865                     return CreatePIDRet::fail;
866                 }
867                 if (std::find(curProfiles->begin(), curProfiles->end(),
868                               profile) == curProfiles->end())
869                 {
870                     std::vector<std::string> newProfiles = *curProfiles;
871                     newProfiles.push_back(profile);
872                     output["Profiles"] = newProfiles;
873                 }
874             }
875         }
876     }
877 
878     if (type == "PidControllers" || type == "FanControllers")
879     {
880         if (createNewObject)
881         {
882             output["Class"] = type == "PidControllers" ? std::string("temp")
883                                                        : std::string("fan");
884             output["Type"] = std::string("Pid");
885         }
886 
887         std::optional<std::vector<nlohmann::json>> zones;
888         std::optional<std::vector<std::string>> inputs;
889         std::optional<std::vector<std::string>> outputs;
890         std::map<std::string, std::optional<double>> doubles;
891         std::optional<std::string> setpointOffset;
892         if (!redfish::json_util::readJson(
893                 it.value(), response->res, "Inputs", inputs, "Outputs", outputs,
894                 "Zones", zones, "FFGainCoefficient",
895                 doubles["FFGainCoefficient"], "FFOffCoefficient",
896                 doubles["FFOffCoefficient"], "ICoefficient",
897                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
898                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
899                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
900                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
901                 doubles["SetPoint"], "SetPointOffset", setpointOffset,
902                 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"],
903                 "PositiveHysteresis", doubles["PositiveHysteresis"],
904                 "NegativeHysteresis", doubles["NegativeHysteresis"]))
905         {
906             BMCWEB_LOG_ERROR
907                 << "Illegal Property "
908                 << it.value().dump(2, ' ', true,
909                                    nlohmann::json::error_handler_t::replace);
910             return CreatePIDRet::fail;
911         }
912         if (zones)
913         {
914             std::vector<std::string> zonesStr;
915             if (!getZonesFromJsonReq(response, *zones, zonesStr))
916             {
917                 BMCWEB_LOG_ERROR << "Illegal Zones";
918                 return CreatePIDRet::fail;
919             }
920             if (chassis.empty() &&
921                 !findChassis(managedObj, zonesStr[0], chassis))
922             {
923                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
924                 messages::invalidObject(response->res, it.key());
925                 return CreatePIDRet::fail;
926             }
927 
928             output["Zones"] = std::move(zonesStr);
929         }
930         if (inputs || outputs)
931         {
932             std::array<std::optional<std::vector<std::string>>*, 2> containers =
933                 {&inputs, &outputs};
934             size_t index = 0;
935             for (const auto& containerPtr : containers)
936             {
937                 std::optional<std::vector<std::string>>& container =
938                     *containerPtr;
939                 if (!container)
940                 {
941                     index++;
942                     continue;
943                 }
944 
945                 for (std::string& value : *container)
946                 {
947                     boost::replace_all(value, "_", " ");
948                 }
949                 std::string key;
950                 if (index == 0)
951                 {
952                     key = "Inputs";
953                 }
954                 else
955                 {
956                     key = "Outputs";
957                 }
958                 output[key] = *container;
959                 index++;
960             }
961         }
962 
963         if (setpointOffset)
964         {
965             // translate between redfish and dbus names
966             if (*setpointOffset == "UpperThresholdNonCritical")
967             {
968                 output["SetPointOffset"] = std::string("WarningLow");
969             }
970             else if (*setpointOffset == "LowerThresholdNonCritical")
971             {
972                 output["SetPointOffset"] = std::string("WarningHigh");
973             }
974             else if (*setpointOffset == "LowerThresholdCritical")
975             {
976                 output["SetPointOffset"] = std::string("CriticalLow");
977             }
978             else if (*setpointOffset == "UpperThresholdCritical")
979             {
980                 output["SetPointOffset"] = std::string("CriticalHigh");
981             }
982             else
983             {
984                 BMCWEB_LOG_ERROR << "Invalid setpointoffset "
985                                  << *setpointOffset;
986                 messages::invalidObject(response->res, it.key());
987                 return CreatePIDRet::fail;
988             }
989         }
990 
991         // doubles
992         for (const auto& pairs : doubles)
993         {
994             if (!pairs.second)
995             {
996                 continue;
997             }
998             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
999             output[pairs.first] = *(pairs.second);
1000         }
1001     }
1002 
1003     else if (type == "FanZones")
1004     {
1005         output["Type"] = std::string("Pid.Zone");
1006 
1007         std::optional<nlohmann::json> chassisContainer;
1008         std::optional<double> failSafePercent;
1009         std::optional<double> minThermalOutput;
1010         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
1011                                           chassisContainer, "FailSafePercent",
1012                                           failSafePercent, "MinThermalOutput",
1013                                           minThermalOutput))
1014         {
1015             BMCWEB_LOG_ERROR
1016                 << "Illegal Property "
1017                 << it.value().dump(2, ' ', true,
1018                                    nlohmann::json::error_handler_t::replace);
1019             return CreatePIDRet::fail;
1020         }
1021 
1022         if (chassisContainer)
1023         {
1024 
1025             std::string chassisId;
1026             if (!redfish::json_util::readJson(*chassisContainer, response->res,
1027                                               "@odata.id", chassisId))
1028             {
1029                 BMCWEB_LOG_ERROR
1030                     << "Illegal Property "
1031                     << chassisContainer->dump(
1032                            2, ' ', true,
1033                            nlohmann::json::error_handler_t::replace);
1034                 return CreatePIDRet::fail;
1035             }
1036 
1037             // /redfish/v1/chassis/chassis_name/
1038             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
1039             {
1040                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
1041                 messages::invalidObject(response->res, chassisId);
1042                 return CreatePIDRet::fail;
1043             }
1044         }
1045         if (minThermalOutput)
1046         {
1047             output["MinThermalOutput"] = *minThermalOutput;
1048         }
1049         if (failSafePercent)
1050         {
1051             output["FailSafePercent"] = *failSafePercent;
1052         }
1053     }
1054     else if (type == "StepwiseControllers")
1055     {
1056         output["Type"] = std::string("Stepwise");
1057 
1058         std::optional<std::vector<nlohmann::json>> zones;
1059         std::optional<std::vector<nlohmann::json>> steps;
1060         std::optional<std::vector<std::string>> inputs;
1061         std::optional<double> positiveHysteresis;
1062         std::optional<double> negativeHysteresis;
1063         std::optional<std::string> direction; // upper clipping curve vs lower
1064         if (!redfish::json_util::readJson(
1065                 it.value(), response->res, "Zones", zones, "Steps", steps,
1066                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
1067                 "NegativeHysteresis", negativeHysteresis, "Direction",
1068                 direction))
1069         {
1070             BMCWEB_LOG_ERROR
1071                 << "Illegal Property "
1072                 << it.value().dump(2, ' ', true,
1073                                    nlohmann::json::error_handler_t::replace);
1074             return CreatePIDRet::fail;
1075         }
1076 
1077         if (zones)
1078         {
1079             std::vector<std::string> zonesStrs;
1080             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
1081             {
1082                 BMCWEB_LOG_ERROR << "Illegal Zones";
1083                 return CreatePIDRet::fail;
1084             }
1085             if (chassis.empty() &&
1086                 !findChassis(managedObj, zonesStrs[0], chassis))
1087             {
1088                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
1089                 messages::invalidObject(response->res, it.key());
1090                 return CreatePIDRet::fail;
1091             }
1092             output["Zones"] = std::move(zonesStrs);
1093         }
1094         if (steps)
1095         {
1096             std::vector<double> readings;
1097             std::vector<double> outputs;
1098             for (auto& step : *steps)
1099             {
1100                 double target;
1101                 double out;
1102 
1103                 if (!redfish::json_util::readJson(step, response->res, "Target",
1104                                                   target, "Output", out))
1105                 {
1106                     BMCWEB_LOG_ERROR
1107                         << "Illegal Property "
1108                         << it.value().dump(
1109                                2, ' ', true,
1110                                nlohmann::json::error_handler_t::replace);
1111                     return CreatePIDRet::fail;
1112                 }
1113                 readings.emplace_back(target);
1114                 outputs.emplace_back(out);
1115             }
1116             output["Reading"] = std::move(readings);
1117             output["Output"] = std::move(outputs);
1118         }
1119         if (inputs)
1120         {
1121             for (std::string& value : *inputs)
1122             {
1123                 boost::replace_all(value, "_", " ");
1124             }
1125             output["Inputs"] = std::move(*inputs);
1126         }
1127         if (negativeHysteresis)
1128         {
1129             output["NegativeHysteresis"] = *negativeHysteresis;
1130         }
1131         if (positiveHysteresis)
1132         {
1133             output["PositiveHysteresis"] = *positiveHysteresis;
1134         }
1135         if (direction)
1136         {
1137             constexpr const std::array<const char*, 2> allowedDirections = {
1138                 "Ceiling", "Floor"};
1139             if (std::find(allowedDirections.begin(), allowedDirections.end(),
1140                           *direction) == allowedDirections.end())
1141             {
1142                 messages::propertyValueTypeError(response->res, "Direction",
1143                                                  *direction);
1144                 return CreatePIDRet::fail;
1145             }
1146             output["Class"] = *direction;
1147         }
1148     }
1149     else
1150     {
1151         BMCWEB_LOG_ERROR << "Illegal Type " << type;
1152         messages::propertyUnknown(response->res, type);
1153         return CreatePIDRet::fail;
1154     }
1155     return CreatePIDRet::patch;
1156 }
1157 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
1158 {
1159 
1160     GetPIDValues(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
1161         asyncResp(asyncRespIn)
1162 
1163     {}
1164 
1165     void run()
1166     {
1167         std::shared_ptr<GetPIDValues> self = shared_from_this();
1168 
1169         // get all configurations
1170         crow::connections::systemBus->async_method_call(
1171             [self](const boost::system::error_code ec,
1172                    const crow::openbmc_mapper::GetSubTreeType& subtreeLocal) {
1173                 if (ec)
1174                 {
1175                     BMCWEB_LOG_ERROR << ec;
1176                     messages::internalError(self->asyncResp->res);
1177                     return;
1178                 }
1179                 self->subtree = subtreeLocal;
1180             },
1181             "xyz.openbmc_project.ObjectMapper",
1182             "/xyz/openbmc_project/object_mapper",
1183             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1184             std::array<const char*, 4>{
1185                 pidConfigurationIface, pidZoneConfigurationIface,
1186                 objectManagerIface, stepwiseConfigurationIface});
1187 
1188         // at the same time get the selected profile
1189         crow::connections::systemBus->async_method_call(
1190             [self](const boost::system::error_code ec,
1191                    const crow::openbmc_mapper::GetSubTreeType& subtreeLocal) {
1192                 if (ec || subtreeLocal.empty())
1193                 {
1194                     return;
1195                 }
1196                 if (subtreeLocal[0].second.size() != 1)
1197                 {
1198                     // invalid mapper response, should never happen
1199                     BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error";
1200                     messages::internalError(self->asyncResp->res);
1201                     return;
1202                 }
1203 
1204                 const std::string& path = subtreeLocal[0].first;
1205                 const std::string& owner = subtreeLocal[0].second[0].first;
1206                 crow::connections::systemBus->async_method_call(
1207                     [path, owner, self](
1208                         const boost::system::error_code ec2,
1209                         const boost::container::flat_map<
1210                             std::string, std::variant<std::vector<std::string>,
1211                                                       std::string>>& resp) {
1212                         if (ec2)
1213                         {
1214                             BMCWEB_LOG_ERROR << "GetPIDValues: Can't get "
1215                                                 "thermalModeIface "
1216                                              << path;
1217                             messages::internalError(self->asyncResp->res);
1218                             return;
1219                         }
1220                         const std::string* current = nullptr;
1221                         const std::vector<std::string>* supported = nullptr;
1222                         for (auto& [key, value] : resp)
1223                         {
1224                             if (key == "Current")
1225                             {
1226                                 current = std::get_if<std::string>(&value);
1227                                 if (current == nullptr)
1228                                 {
1229                                     BMCWEB_LOG_ERROR
1230                                         << "GetPIDValues: thermal mode "
1231                                            "iface invalid "
1232                                         << path;
1233                                     messages::internalError(
1234                                         self->asyncResp->res);
1235                                     return;
1236                                 }
1237                             }
1238                             if (key == "Supported")
1239                             {
1240                                 supported =
1241                                     std::get_if<std::vector<std::string>>(
1242                                         &value);
1243                                 if (supported == nullptr)
1244                                 {
1245                                     BMCWEB_LOG_ERROR
1246                                         << "GetPIDValues: thermal mode "
1247                                            "iface invalid"
1248                                         << path;
1249                                     messages::internalError(
1250                                         self->asyncResp->res);
1251                                     return;
1252                                 }
1253                             }
1254                         }
1255                         if (current == nullptr || supported == nullptr)
1256                         {
1257                             BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode "
1258                                                 "iface invalid "
1259                                              << path;
1260                             messages::internalError(self->asyncResp->res);
1261                             return;
1262                         }
1263                         self->currentProfile = *current;
1264                         self->supportedProfiles = *supported;
1265                     },
1266                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1267                     thermalModeIface);
1268             },
1269             "xyz.openbmc_project.ObjectMapper",
1270             "/xyz/openbmc_project/object_mapper",
1271             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1272             std::array<const char*, 1>{thermalModeIface});
1273     }
1274 
1275     ~GetPIDValues()
1276     {
1277         if (asyncResp->res.result() != boost::beast::http::status::ok)
1278         {
1279             return;
1280         }
1281         // create map of <connection, path to objMgr>>
1282         boost::container::flat_map<std::string, std::string> objectMgrPaths;
1283         boost::container::flat_set<std::string> calledConnections;
1284         for (const auto& pathGroup : subtree)
1285         {
1286             for (const auto& connectionGroup : pathGroup.second)
1287             {
1288                 auto findConnection =
1289                     calledConnections.find(connectionGroup.first);
1290                 if (findConnection != calledConnections.end())
1291                 {
1292                     break;
1293                 }
1294                 for (const std::string& interface : connectionGroup.second)
1295                 {
1296                     if (interface == objectManagerIface)
1297                     {
1298                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1299                     }
1300                     // this list is alphabetical, so we
1301                     // should have found the objMgr by now
1302                     if (interface == pidConfigurationIface ||
1303                         interface == pidZoneConfigurationIface ||
1304                         interface == stepwiseConfigurationIface)
1305                     {
1306                         auto findObjMgr =
1307                             objectMgrPaths.find(connectionGroup.first);
1308                         if (findObjMgr == objectMgrPaths.end())
1309                         {
1310                             BMCWEB_LOG_DEBUG << connectionGroup.first
1311                                              << "Has no Object Manager";
1312                             continue;
1313                         }
1314 
1315                         calledConnections.insert(connectionGroup.first);
1316 
1317                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1318                                          currentProfile, supportedProfiles,
1319                                          asyncResp);
1320                         break;
1321                     }
1322                 }
1323             }
1324         }
1325     }
1326 
1327     std::vector<std::string> supportedProfiles;
1328     std::string currentProfile;
1329     crow::openbmc_mapper::GetSubTreeType subtree;
1330     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1331 };
1332 
1333 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1334 {
1335 
1336     SetPIDValues(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
1337                  nlohmann::json& data) :
1338         asyncResp(asyncRespIn)
1339     {
1340 
1341         std::optional<nlohmann::json> pidControllers;
1342         std::optional<nlohmann::json> fanControllers;
1343         std::optional<nlohmann::json> fanZones;
1344         std::optional<nlohmann::json> stepwiseControllers;
1345 
1346         if (!redfish::json_util::readJson(
1347                 data, asyncResp->res, "PidControllers", pidControllers,
1348                 "FanControllers", fanControllers, "FanZones", fanZones,
1349                 "StepwiseControllers", stepwiseControllers, "Profile", profile))
1350         {
1351             BMCWEB_LOG_ERROR
1352                 << "Illegal Property "
1353                 << data.dump(2, ' ', true,
1354                              nlohmann::json::error_handler_t::replace);
1355             return;
1356         }
1357         configuration.emplace_back("PidControllers", std::move(pidControllers));
1358         configuration.emplace_back("FanControllers", std::move(fanControllers));
1359         configuration.emplace_back("FanZones", std::move(fanZones));
1360         configuration.emplace_back("StepwiseControllers",
1361                                    std::move(stepwiseControllers));
1362     }
1363     void run()
1364     {
1365         if (asyncResp->res.result() != boost::beast::http::status::ok)
1366         {
1367             return;
1368         }
1369 
1370         std::shared_ptr<SetPIDValues> self = shared_from_this();
1371 
1372         // todo(james): might make sense to do a mapper call here if this
1373         // interface gets more traction
1374         crow::connections::systemBus->async_method_call(
1375             [self](const boost::system::error_code ec,
1376                    dbus::utility::ManagedObjectType& mObj) {
1377                 if (ec)
1378                 {
1379                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
1380                     messages::internalError(self->asyncResp->res);
1381                     return;
1382                 }
1383                 const std::array<const char*, 3> configurations = {
1384                     pidConfigurationIface, pidZoneConfigurationIface,
1385                     stepwiseConfigurationIface};
1386 
1387                 for (const auto& [path, object] : mObj)
1388                 {
1389                     for (const auto& [interface, _] : object)
1390                     {
1391                         if (std::find(configurations.begin(),
1392                                       configurations.end(),
1393                                       interface) != configurations.end())
1394                         {
1395                             self->objectCount++;
1396                             break;
1397                         }
1398                     }
1399                 }
1400                 self->managedObj = std::move(mObj);
1401             },
1402             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
1403             "GetManagedObjects");
1404 
1405         // at the same time get the profile information
1406         crow::connections::systemBus->async_method_call(
1407             [self](const boost::system::error_code ec,
1408                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
1409                 if (ec || subtree.empty())
1410                 {
1411                     return;
1412                 }
1413                 if (subtree[0].second.empty())
1414                 {
1415                     // invalid mapper response, should never happen
1416                     BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error";
1417                     messages::internalError(self->asyncResp->res);
1418                     return;
1419                 }
1420 
1421                 const std::string& path = subtree[0].first;
1422                 const std::string& owner = subtree[0].second[0].first;
1423                 crow::connections::systemBus->async_method_call(
1424                     [self, path, owner](
1425                         const boost::system::error_code ec2,
1426                         const boost::container::flat_map<
1427                             std::string, std::variant<std::vector<std::string>,
1428                                                       std::string>>& r) {
1429                         if (ec2)
1430                         {
1431                             BMCWEB_LOG_ERROR << "SetPIDValues: Can't get "
1432                                                 "thermalModeIface "
1433                                              << path;
1434                             messages::internalError(self->asyncResp->res);
1435                             return;
1436                         }
1437                         const std::string* current = nullptr;
1438                         const std::vector<std::string>* supported = nullptr;
1439                         for (auto& [key, value] : r)
1440                         {
1441                             if (key == "Current")
1442                             {
1443                                 current = std::get_if<std::string>(&value);
1444                                 if (current == nullptr)
1445                                 {
1446                                     BMCWEB_LOG_ERROR
1447                                         << "SetPIDValues: thermal mode "
1448                                            "iface invalid "
1449                                         << path;
1450                                     messages::internalError(
1451                                         self->asyncResp->res);
1452                                     return;
1453                                 }
1454                             }
1455                             if (key == "Supported")
1456                             {
1457                                 supported =
1458                                     std::get_if<std::vector<std::string>>(
1459                                         &value);
1460                                 if (supported == nullptr)
1461                                 {
1462                                     BMCWEB_LOG_ERROR
1463                                         << "SetPIDValues: thermal mode "
1464                                            "iface invalid"
1465                                         << path;
1466                                     messages::internalError(
1467                                         self->asyncResp->res);
1468                                     return;
1469                                 }
1470                             }
1471                         }
1472                         if (current == nullptr || supported == nullptr)
1473                         {
1474                             BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode "
1475                                                 "iface invalid "
1476                                              << path;
1477                             messages::internalError(self->asyncResp->res);
1478                             return;
1479                         }
1480                         self->currentProfile = *current;
1481                         self->supportedProfiles = *supported;
1482                         self->profileConnection = owner;
1483                         self->profilePath = path;
1484                     },
1485                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1486                     thermalModeIface);
1487             },
1488             "xyz.openbmc_project.ObjectMapper",
1489             "/xyz/openbmc_project/object_mapper",
1490             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1491             std::array<const char*, 1>{thermalModeIface});
1492     }
1493     ~SetPIDValues()
1494     {
1495         if (asyncResp->res.result() != boost::beast::http::status::ok)
1496         {
1497             return;
1498         }
1499         std::shared_ptr<bmcweb::AsyncResp> response = asyncResp;
1500         if (profile)
1501         {
1502             if (std::find(supportedProfiles.begin(), supportedProfiles.end(),
1503                           *profile) == supportedProfiles.end())
1504             {
1505                 messages::actionParameterUnknown(response->res, "Profile",
1506                                                  *profile);
1507                 return;
1508             }
1509             currentProfile = *profile;
1510             crow::connections::systemBus->async_method_call(
1511                 [response](const boost::system::error_code ec) {
1512                     if (ec)
1513                     {
1514                         BMCWEB_LOG_ERROR << "Error patching profile" << ec;
1515                         messages::internalError(response->res);
1516                     }
1517                 },
1518                 profileConnection, profilePath,
1519                 "org.freedesktop.DBus.Properties", "Set", thermalModeIface,
1520                 "Current", std::variant<std::string>(*profile));
1521         }
1522 
1523         for (auto& containerPair : configuration)
1524         {
1525             auto& container = containerPair.second;
1526             if (!container)
1527             {
1528                 continue;
1529             }
1530             BMCWEB_LOG_DEBUG << *container;
1531 
1532             std::string& type = containerPair.first;
1533 
1534             for (nlohmann::json::iterator it = container->begin();
1535                  it != container->end(); ++it)
1536             {
1537                 const auto& name = it.key();
1538                 BMCWEB_LOG_DEBUG << "looking for " << name;
1539 
1540                 auto pathItr =
1541                     std::find_if(managedObj.begin(), managedObj.end(),
1542                                  [&name](const auto& obj) {
1543                                      return boost::algorithm::ends_with(
1544                                          obj.first.str, "/" + name);
1545                                  });
1546                 boost::container::flat_map<std::string,
1547                                            dbus::utility::DbusVariantType>
1548                     output;
1549 
1550                 output.reserve(16); // The pid interface length
1551 
1552                 // determines if we're patching entity-manager or
1553                 // creating a new object
1554                 bool createNewObject = (pathItr == managedObj.end());
1555                 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject;
1556 
1557                 std::string iface;
1558                 if (type == "PidControllers" || type == "FanControllers")
1559                 {
1560                     iface = pidConfigurationIface;
1561                     if (!createNewObject &&
1562                         pathItr->second.find(pidConfigurationIface) ==
1563                             pathItr->second.end())
1564                     {
1565                         createNewObject = true;
1566                     }
1567                 }
1568                 else if (type == "FanZones")
1569                 {
1570                     iface = pidZoneConfigurationIface;
1571                     if (!createNewObject &&
1572                         pathItr->second.find(pidZoneConfigurationIface) ==
1573                             pathItr->second.end())
1574                     {
1575 
1576                         createNewObject = true;
1577                     }
1578                 }
1579                 else if (type == "StepwiseControllers")
1580                 {
1581                     iface = stepwiseConfigurationIface;
1582                     if (!createNewObject &&
1583                         pathItr->second.find(stepwiseConfigurationIface) ==
1584                             pathItr->second.end())
1585                     {
1586                         createNewObject = true;
1587                     }
1588                 }
1589 
1590                 if (createNewObject && it.value() == nullptr)
1591                 {
1592                     // can't delete a non-existent object
1593                     messages::invalidObject(response->res, name);
1594                     continue;
1595                 }
1596 
1597                 std::string path;
1598                 if (pathItr != managedObj.end())
1599                 {
1600                     path = pathItr->first.str;
1601                 }
1602 
1603                 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n";
1604 
1605                 // arbitrary limit to avoid attacks
1606                 constexpr const size_t controllerLimit = 500;
1607                 if (createNewObject && objectCount >= controllerLimit)
1608                 {
1609                     messages::resourceExhaustion(response->res, type);
1610                     continue;
1611                 }
1612 
1613                 output["Name"] = boost::replace_all_copy(name, "_", " ");
1614 
1615                 std::string chassis;
1616                 CreatePIDRet ret = createPidInterface(
1617                     response, type, it, path, managedObj, createNewObject,
1618                     output, chassis, currentProfile);
1619                 if (ret == CreatePIDRet::fail)
1620                 {
1621                     return;
1622                 }
1623                 if (ret == CreatePIDRet::del)
1624                 {
1625                     continue;
1626                 }
1627 
1628                 if (!createNewObject)
1629                 {
1630                     for (const auto& property : output)
1631                     {
1632                         crow::connections::systemBus->async_method_call(
1633                             [response,
1634                              propertyName{std::string(property.first)}](
1635                                 const boost::system::error_code ec) {
1636                                 if (ec)
1637                                 {
1638                                     BMCWEB_LOG_ERROR << "Error patching "
1639                                                      << propertyName << ": "
1640                                                      << ec;
1641                                     messages::internalError(response->res);
1642                                     return;
1643                                 }
1644                                 messages::success(response->res);
1645                             },
1646                             "xyz.openbmc_project.EntityManager", path,
1647                             "org.freedesktop.DBus.Properties", "Set", iface,
1648                             property.first, property.second);
1649                     }
1650                 }
1651                 else
1652                 {
1653                     if (chassis.empty())
1654                     {
1655                         BMCWEB_LOG_ERROR << "Failed to get chassis from config";
1656                         messages::invalidObject(response->res, name);
1657                         return;
1658                     }
1659 
1660                     bool foundChassis = false;
1661                     for (const auto& obj : managedObj)
1662                     {
1663                         if (boost::algorithm::ends_with(obj.first.str, chassis))
1664                         {
1665                             chassis = obj.first.str;
1666                             foundChassis = true;
1667                             break;
1668                         }
1669                     }
1670                     if (!foundChassis)
1671                     {
1672                         BMCWEB_LOG_ERROR << "Failed to find chassis on dbus";
1673                         messages::resourceMissingAtURI(
1674                             response->res, "/redfish/v1/Chassis/" + chassis);
1675                         return;
1676                     }
1677 
1678                     crow::connections::systemBus->async_method_call(
1679                         [response](const boost::system::error_code ec) {
1680                             if (ec)
1681                             {
1682                                 BMCWEB_LOG_ERROR << "Error Adding Pid Object "
1683                                                  << ec;
1684                                 messages::internalError(response->res);
1685                                 return;
1686                             }
1687                             messages::success(response->res);
1688                         },
1689                         "xyz.openbmc_project.EntityManager", chassis,
1690                         "xyz.openbmc_project.AddObject", "AddObject", output);
1691                 }
1692             }
1693         }
1694     }
1695     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1696     std::vector<std::pair<std::string, std::optional<nlohmann::json>>>
1697         configuration;
1698     std::optional<std::string> profile;
1699     dbus::utility::ManagedObjectType managedObj;
1700     std::vector<std::string> supportedProfiles;
1701     std::string currentProfile;
1702     std::string profileConnection;
1703     std::string profilePath;
1704     size_t objectCount = 0;
1705 };
1706 
1707 /**
1708  * @brief Retrieves BMC manager location data over DBus
1709  *
1710  * @param[in] aResp Shared pointer for completing asynchronous calls
1711  * @param[in] connectionName - service name
1712  * @param[in] path - object path
1713  * @return none
1714  */
1715 inline void getLocation(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
1716                         const std::string& connectionName,
1717                         const std::string& path)
1718 {
1719     BMCWEB_LOG_DEBUG << "Get BMC manager Location data.";
1720 
1721     crow::connections::systemBus->async_method_call(
1722         [aResp](const boost::system::error_code ec,
1723                 const std::variant<std::string>& property) {
1724             if (ec)
1725             {
1726                 BMCWEB_LOG_DEBUG << "DBUS response error for "
1727                                     "Location";
1728                 messages::internalError(aResp->res);
1729                 return;
1730             }
1731 
1732             const std::string* value = std::get_if<std::string>(&property);
1733 
1734             if (value == nullptr)
1735             {
1736                 // illegal value
1737                 messages::internalError(aResp->res);
1738                 return;
1739             }
1740 
1741             aResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
1742                 *value;
1743         },
1744         connectionName, path, "org.freedesktop.DBus.Properties", "Get",
1745         "xyz.openbmc_project.Inventory.Decorator."
1746         "LocationCode",
1747         "LocationCode");
1748 }
1749 
1750 class Manager : public Node
1751 {
1752   public:
1753     Manager(App& app) : Node(app, "/redfish/v1/Managers/bmc/")
1754     {
1755 
1756         uuid = persistent_data::getConfig().systemUuid;
1757         entityPrivileges = {
1758             {boost::beast::http::verb::get, {{"Login"}}},
1759             {boost::beast::http::verb::head, {{"Login"}}},
1760             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1761             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1762             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1763             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1764     }
1765 
1766   private:
1767     void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1768                const crow::Request&, const std::vector<std::string>&) override
1769     {
1770         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
1771         asyncResp->res.jsonValue["@odata.type"] = "#Manager.v1_11_0.Manager";
1772         asyncResp->res.jsonValue["Id"] = "bmc";
1773         asyncResp->res.jsonValue["Name"] = "OpenBmc Manager";
1774         asyncResp->res.jsonValue["Description"] =
1775             "Baseboard Management Controller";
1776         asyncResp->res.jsonValue["PowerState"] = "On";
1777         asyncResp->res.jsonValue["Status"] = {{"State", "Enabled"},
1778                                               {"Health", "OK"}};
1779         asyncResp->res.jsonValue["ManagerType"] = "BMC";
1780         asyncResp->res.jsonValue["UUID"] = systemd_utils::getUuid();
1781         asyncResp->res.jsonValue["ServiceEntryPointUUID"] = uuid;
1782         asyncResp->res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
1783 
1784         asyncResp->res.jsonValue["LogServices"] = {
1785             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
1786 
1787         asyncResp->res.jsonValue["NetworkProtocol"] = {
1788             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
1789 
1790         asyncResp->res.jsonValue["EthernetInterfaces"] = {
1791             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
1792 
1793 #ifdef BMCWEB_ENABLE_VM_NBDPROXY
1794         asyncResp->res.jsonValue["VirtualMedia"] = {
1795             {"@odata.id", "/redfish/v1/Managers/bmc/VirtualMedia"}};
1796 #endif // BMCWEB_ENABLE_VM_NBDPROXY
1797 
1798         // default oem data
1799         nlohmann::json& oem = asyncResp->res.jsonValue["Oem"];
1800         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
1801         oem["@odata.type"] = "#OemManager.Oem";
1802         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
1803         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
1804         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
1805         oemOpenbmc["Certificates"] = {
1806             {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates"}};
1807 
1808         // Manager.Reset (an action) can be many values, OpenBMC only supports
1809         // BMC reboot.
1810         nlohmann::json& managerReset =
1811             asyncResp->res.jsonValue["Actions"]["#Manager.Reset"];
1812         managerReset["target"] =
1813             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
1814         managerReset["@Redfish.ActionInfo"] =
1815             "/redfish/v1/Managers/bmc/ResetActionInfo";
1816 
1817         // ResetToDefaults (Factory Reset) has values like
1818         // PreserveNetworkAndUsers and PreserveNetwork that aren't supported
1819         // on OpenBMC
1820         nlohmann::json& resetToDefaults =
1821             asyncResp->res.jsonValue["Actions"]["#Manager.ResetToDefaults"];
1822         resetToDefaults["target"] =
1823             "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults";
1824         resetToDefaults["ResetType@Redfish.AllowableValues"] = {"ResetAll"};
1825 
1826         asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
1827 
1828         // Fill in SerialConsole info
1829         asyncResp->res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
1830         asyncResp->res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15;
1831         asyncResp->res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {
1832             "IPMI", "SSH"};
1833 #ifdef BMCWEB_ENABLE_KVM
1834         // Fill in GraphicalConsole info
1835         asyncResp->res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true;
1836         asyncResp->res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] =
1837             4;
1838         asyncResp->res
1839             .jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"};
1840 #endif // BMCWEB_ENABLE_KVM
1841 
1842         asyncResp->res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
1843         asyncResp->res.jsonValue["Links"]["ManagerForServers"] = {
1844             {{"@odata.id", "/redfish/v1/Systems/system"}}};
1845 
1846         auto health = std::make_shared<HealthPopulate>(asyncResp);
1847         health->isManagersHealth = true;
1848         health->populate();
1849 
1850         fw_util::populateFirmwareInformation(asyncResp, fw_util::bmcPurpose,
1851                                              "FirmwareVersion", true);
1852 
1853         getLastResetTime(asyncResp);
1854 
1855         auto pids = std::make_shared<GetPIDValues>(asyncResp);
1856         pids->run();
1857 
1858         getMainChassisId(
1859             asyncResp, [](const std::string& chassisId,
1860                           const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
1861                 aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] =
1862                     1;
1863                 aRsp->res.jsonValue["Links"]["ManagerForChassis"] = {
1864                     {{"@odata.id", "/redfish/v1/Chassis/" + chassisId}}};
1865                 aRsp->res.jsonValue["Links"]["ManagerInChassis"] = {
1866                     {"@odata.id", "/redfish/v1/Chassis/" + chassisId}};
1867             });
1868 
1869         static bool started = false;
1870 
1871         if (!started)
1872         {
1873             crow::connections::systemBus->async_method_call(
1874                 [asyncResp](const boost::system::error_code ec,
1875                             const std::variant<double>& resp) {
1876                     if (ec)
1877                     {
1878                         BMCWEB_LOG_ERROR << "Error while getting progress";
1879                         messages::internalError(asyncResp->res);
1880                         return;
1881                     }
1882                     const double* val = std::get_if<double>(&resp);
1883                     if (val == nullptr)
1884                     {
1885                         BMCWEB_LOG_ERROR
1886                             << "Invalid response while getting progress";
1887                         messages::internalError(asyncResp->res);
1888                         return;
1889                     }
1890                     if (*val < 1.0)
1891                     {
1892                         asyncResp->res.jsonValue["Status"]["State"] =
1893                             "Starting";
1894                         started = true;
1895                     }
1896                 },
1897                 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1898                 "org.freedesktop.DBus.Properties", "Get",
1899                 "org.freedesktop.systemd1.Manager", "Progress");
1900         }
1901 
1902         crow::connections::systemBus->async_method_call(
1903             [asyncResp](
1904                 const boost::system::error_code ec,
1905                 const std::vector<std::pair<
1906                     std::string, std::vector<std::pair<
1907                                      std::string, std::vector<std::string>>>>>&
1908                     subtree) {
1909                 if (ec)
1910                 {
1911                     BMCWEB_LOG_DEBUG << "D-Bus response error on GetSubTree "
1912                                      << ec;
1913                     return;
1914                 }
1915                 if (subtree.size() == 0)
1916                 {
1917                     BMCWEB_LOG_DEBUG << "Can't find bmc D-Bus object!";
1918                     return;
1919                 }
1920                 // Assume only 1 bmc D-Bus object
1921                 // Throw an error if there is more than 1
1922                 if (subtree.size() > 1)
1923                 {
1924                     BMCWEB_LOG_DEBUG << "Found more than 1 bmc D-Bus object!";
1925                     messages::internalError(asyncResp->res);
1926                     return;
1927                 }
1928 
1929                 if (subtree[0].first.empty() || subtree[0].second.size() != 1)
1930                 {
1931                     BMCWEB_LOG_DEBUG << "Error getting bmc D-Bus object!";
1932                     messages::internalError(asyncResp->res);
1933                     return;
1934                 }
1935 
1936                 const std::string& path = subtree[0].first;
1937                 const std::string& connectionName = subtree[0].second[0].first;
1938 
1939                 for (const auto& interfaceName : subtree[0].second[0].second)
1940                 {
1941                     if (interfaceName ==
1942                         "xyz.openbmc_project.Inventory.Decorator.Asset")
1943                     {
1944                         crow::connections::systemBus->async_method_call(
1945                             [asyncResp](
1946                                 const boost::system::error_code ec,
1947                                 const std::vector<std::pair<
1948                                     std::string, std::variant<std::string>>>&
1949                                     propertiesList) {
1950                                 if (ec)
1951                                 {
1952                                     BMCWEB_LOG_DEBUG << "Can't get bmc asset!";
1953                                     return;
1954                                 }
1955                                 for (const std::pair<std::string,
1956                                                      std::variant<std::string>>&
1957                                          property : propertiesList)
1958                                 {
1959                                     const std::string& propertyName =
1960                                         property.first;
1961 
1962                                     if ((propertyName == "PartNumber") ||
1963                                         (propertyName == "SerialNumber") ||
1964                                         (propertyName == "Manufacturer") ||
1965                                         (propertyName == "Model") ||
1966                                         (propertyName == "SparePartNumber"))
1967                                     {
1968                                         const std::string* value =
1969                                             std::get_if<std::string>(
1970                                                 &property.second);
1971                                         if (value == nullptr)
1972                                         {
1973                                             // illegal property
1974                                             messages::internalError(
1975                                                 asyncResp->res);
1976                                             return;
1977                                         }
1978                                         asyncResp->res.jsonValue[propertyName] =
1979                                             *value;
1980                                     }
1981                                 }
1982                             },
1983                             connectionName, path,
1984                             "org.freedesktop.DBus.Properties", "GetAll",
1985                             "xyz.openbmc_project.Inventory.Decorator.Asset");
1986                     }
1987                     else if (interfaceName == "xyz.openbmc_project.Inventory."
1988                                               "Decorator.LocationCode")
1989                     {
1990                         getLocation(asyncResp, connectionName, path);
1991                     }
1992                 }
1993             },
1994             "xyz.openbmc_project.ObjectMapper",
1995             "/xyz/openbmc_project/object_mapper",
1996             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
1997             "/xyz/openbmc_project/inventory", int32_t(0),
1998             std::array<const char*, 1>{
1999                 "xyz.openbmc_project.Inventory.Item.Bmc"});
2000     }
2001 
2002     void doPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2003                  const crow::Request& req,
2004                  const std::vector<std::string>&) override
2005     {
2006         std::optional<nlohmann::json> oem;
2007         std::optional<nlohmann::json> links;
2008         std::optional<std::string> datetime;
2009 
2010         if (!json_util::readJson(req, asyncResp->res, "Oem", oem, "DateTime",
2011                                  datetime, "Links", links))
2012         {
2013             return;
2014         }
2015 
2016         if (oem)
2017         {
2018             std::optional<nlohmann::json> openbmc;
2019             if (!redfish::json_util::readJson(*oem, asyncResp->res, "OpenBmc",
2020                                               openbmc))
2021             {
2022                 BMCWEB_LOG_ERROR
2023                     << "Illegal Property "
2024                     << oem->dump(2, ' ', true,
2025                                  nlohmann::json::error_handler_t::replace);
2026                 return;
2027             }
2028             if (openbmc)
2029             {
2030                 std::optional<nlohmann::json> fan;
2031                 if (!redfish::json_util::readJson(*openbmc, asyncResp->res,
2032                                                   "Fan", fan))
2033                 {
2034                     BMCWEB_LOG_ERROR
2035                         << "Illegal Property "
2036                         << openbmc->dump(
2037                                2, ' ', true,
2038                                nlohmann::json::error_handler_t::replace);
2039                     return;
2040                 }
2041                 if (fan)
2042                 {
2043                     auto pid = std::make_shared<SetPIDValues>(asyncResp, *fan);
2044                     pid->run();
2045                 }
2046             }
2047         }
2048         if (links)
2049         {
2050             std::optional<nlohmann::json> activeSoftwareImage;
2051             if (!redfish::json_util::readJson(*links, asyncResp->res,
2052                                               "ActiveSoftwareImage",
2053                                               activeSoftwareImage))
2054             {
2055                 return;
2056             }
2057             if (activeSoftwareImage)
2058             {
2059                 std::optional<std::string> odataId;
2060                 if (!json_util::readJson(*activeSoftwareImage, asyncResp->res,
2061                                          "@odata.id", odataId))
2062                 {
2063                     return;
2064                 }
2065 
2066                 if (odataId)
2067                 {
2068                     setActiveFirmwareImage(asyncResp, *odataId);
2069                 }
2070             }
2071         }
2072         if (datetime)
2073         {
2074             setDateTime(asyncResp, std::move(*datetime));
2075         }
2076     }
2077 
2078     void getLastResetTime(const std::shared_ptr<bmcweb::AsyncResp>& aResp)
2079     {
2080         BMCWEB_LOG_DEBUG << "Getting Manager Last Reset Time";
2081 
2082         crow::connections::systemBus->async_method_call(
2083             [aResp](const boost::system::error_code ec,
2084                     std::variant<uint64_t>& lastResetTime) {
2085                 if (ec)
2086                 {
2087                     BMCWEB_LOG_DEBUG << "D-BUS response error " << ec;
2088                     return;
2089                 }
2090 
2091                 const uint64_t* lastResetTimePtr =
2092                     std::get_if<uint64_t>(&lastResetTime);
2093 
2094                 if (!lastResetTimePtr)
2095                 {
2096                     messages::internalError(aResp->res);
2097                     return;
2098                 }
2099                 // LastRebootTime is epoch time, in milliseconds
2100                 // https://github.com/openbmc/phosphor-dbus-interfaces/blob/7f9a128eb9296e926422ddc312c148b625890bb6/xyz/openbmc_project/State/BMC.interface.yaml#L19
2101                 time_t lastResetTimeStamp =
2102                     static_cast<time_t>(*lastResetTimePtr / 1000);
2103 
2104                 // Convert to ISO 8601 standard
2105                 aResp->res.jsonValue["LastResetTime"] =
2106                     crow::utility::getDateTime(lastResetTimeStamp);
2107             },
2108             "xyz.openbmc_project.State.BMC", "/xyz/openbmc_project/state/bmc0",
2109             "org.freedesktop.DBus.Properties", "Get",
2110             "xyz.openbmc_project.State.BMC", "LastRebootTime");
2111     }
2112 
2113     /**
2114      * @brief Set the running firmware image
2115      *
2116      * @param[i,o] aResp - Async response object
2117      * @param[i] runningFirmwareTarget - Image to make the running image
2118      *
2119      * @return void
2120      */
2121     void setActiveFirmwareImage(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
2122                                 const std::string& runningFirmwareTarget)
2123     {
2124         // Get the Id from /redfish/v1/UpdateService/FirmwareInventory/<Id>
2125         std::string::size_type idPos = runningFirmwareTarget.rfind('/');
2126         if (idPos == std::string::npos)
2127         {
2128             messages::propertyValueNotInList(aResp->res, runningFirmwareTarget,
2129                                              "@odata.id");
2130             BMCWEB_LOG_DEBUG << "Can't parse firmware ID!";
2131             return;
2132         }
2133         idPos++;
2134         if (idPos >= runningFirmwareTarget.size())
2135         {
2136             messages::propertyValueNotInList(aResp->res, runningFirmwareTarget,
2137                                              "@odata.id");
2138             BMCWEB_LOG_DEBUG << "Invalid firmware ID.";
2139             return;
2140         }
2141         std::string firmwareId = runningFirmwareTarget.substr(idPos);
2142 
2143         // Make sure the image is valid before setting priority
2144         crow::connections::systemBus->async_method_call(
2145             [aResp, firmwareId,
2146              runningFirmwareTarget](const boost::system::error_code ec,
2147                                     ManagedObjectType& subtree) {
2148                 if (ec)
2149                 {
2150                     BMCWEB_LOG_DEBUG << "D-Bus response error getting objects.";
2151                     messages::internalError(aResp->res);
2152                     return;
2153                 }
2154 
2155                 if (subtree.size() == 0)
2156                 {
2157                     BMCWEB_LOG_DEBUG << "Can't find image!";
2158                     messages::internalError(aResp->res);
2159                     return;
2160                 }
2161 
2162                 bool foundImage = false;
2163                 for (auto& object : subtree)
2164                 {
2165                     const std::string& path =
2166                         static_cast<const std::string&>(object.first);
2167                     std::size_t idPos2 = path.rfind('/');
2168 
2169                     if (idPos2 == std::string::npos)
2170                     {
2171                         continue;
2172                     }
2173 
2174                     idPos2++;
2175                     if (idPos2 >= path.size())
2176                     {
2177                         continue;
2178                     }
2179 
2180                     if (path.substr(idPos2) == firmwareId)
2181                     {
2182                         foundImage = true;
2183                         break;
2184                     }
2185                 }
2186 
2187                 if (!foundImage)
2188                 {
2189                     messages::propertyValueNotInList(
2190                         aResp->res, runningFirmwareTarget, "@odata.id");
2191                     BMCWEB_LOG_DEBUG << "Invalid firmware ID.";
2192                     return;
2193                 }
2194 
2195                 BMCWEB_LOG_DEBUG << "Setting firmware version " + firmwareId +
2196                                         " to priority 0.";
2197 
2198                 // Only support Immediate
2199                 // An addition could be a Redfish Setting like
2200                 // ActiveSoftwareImageApplyTime and support OnReset
2201                 crow::connections::systemBus->async_method_call(
2202                     [aResp](const boost::system::error_code ec) {
2203                         if (ec)
2204                         {
2205                             BMCWEB_LOG_DEBUG << "D-Bus response error setting.";
2206                             messages::internalError(aResp->res);
2207                             return;
2208                         }
2209                         doBMCGracefulRestart(aResp);
2210                     },
2211 
2212                     "xyz.openbmc_project.Software.BMC.Updater",
2213                     "/xyz/openbmc_project/software/" + firmwareId,
2214                     "org.freedesktop.DBus.Properties", "Set",
2215                     "xyz.openbmc_project.Software.RedundancyPriority",
2216                     "Priority", std::variant<uint8_t>(static_cast<uint8_t>(0)));
2217             },
2218             "xyz.openbmc_project.Software.BMC.Updater",
2219             "/xyz/openbmc_project/software",
2220             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
2221     }
2222 
2223     void setDateTime(std::shared_ptr<bmcweb::AsyncResp> aResp,
2224                      std::string datetime) const
2225     {
2226         BMCWEB_LOG_DEBUG << "Set date time: " << datetime;
2227 
2228         std::stringstream stream(datetime);
2229         // Convert from ISO 8601 to boost local_time
2230         // (BMC only has time in UTC)
2231         boost::posix_time::ptime posixTime;
2232         boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
2233         // Facet gets deleted with the stringsteam
2234         auto ifc = std::make_unique<boost::local_time::local_time_input_facet>(
2235             "%Y-%m-%d %H:%M:%S%F %ZP");
2236         stream.imbue(std::locale(stream.getloc(), ifc.release()));
2237 
2238         boost::local_time::local_date_time ldt(
2239             boost::local_time::not_a_date_time);
2240 
2241         if (stream >> ldt)
2242         {
2243             posixTime = ldt.utc_time();
2244             boost::posix_time::time_duration dur = posixTime - epoch;
2245             uint64_t durMicroSecs =
2246                 static_cast<uint64_t>(dur.total_microseconds());
2247             crow::connections::systemBus->async_method_call(
2248                 [aResp{std::move(aResp)}, datetime{std::move(datetime)}](
2249                     const boost::system::error_code ec) {
2250                     if (ec)
2251                     {
2252                         BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
2253                                             "DBUS response error "
2254                                          << ec;
2255                         messages::internalError(aResp->res);
2256                         return;
2257                     }
2258                     aResp->res.jsonValue["DateTime"] = datetime;
2259                 },
2260                 "xyz.openbmc_project.Time.Manager",
2261                 "/xyz/openbmc_project/time/bmc",
2262                 "org.freedesktop.DBus.Properties", "Set",
2263                 "xyz.openbmc_project.Time.EpochTime", "Elapsed",
2264                 std::variant<uint64_t>(durMicroSecs));
2265         }
2266         else
2267         {
2268             messages::propertyValueFormatError(aResp->res, datetime,
2269                                                "DateTime");
2270             return;
2271         }
2272     }
2273 
2274     std::string uuid;
2275 };
2276 
2277 class ManagerCollection : public Node
2278 {
2279   public:
2280     ManagerCollection(App& app) : Node(app, "/redfish/v1/Managers/")
2281     {
2282         entityPrivileges = {
2283             {boost::beast::http::verb::get, {{"Login"}}},
2284             {boost::beast::http::verb::head, {{"Login"}}},
2285             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2286             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2287             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2288             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2289     }
2290 
2291   private:
2292     void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2293                const crow::Request&, const std::vector<std::string>&) override
2294     {
2295         // Collections don't include the static data added by SubRoute
2296         // because it has a duplicate entry for members
2297         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
2298         asyncResp->res.jsonValue["@odata.type"] =
2299             "#ManagerCollection.ManagerCollection";
2300         asyncResp->res.jsonValue["Name"] = "Manager Collection";
2301         asyncResp->res.jsonValue["Members@odata.count"] = 1;
2302         asyncResp->res.jsonValue["Members"] = {
2303             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
2304     }
2305 };
2306 } // namespace redfish
2307