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