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