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