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