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