xref: /openbmc/bmcweb/features/redfish/lib/managers.hpp (revision 43b761d0c8f5c4c39199093ee4bcd60698e77138)
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 "node.hpp"
19 
20 #include <boost/algorithm/string/replace.hpp>
21 #include <dbus_utility.hpp>
22 #include <variant>
23 
24 namespace redfish
25 {
26 
27 /**
28  * ManagerActionsReset class supports handle POST method for Reset action.
29  * The class retrieves and sends data directly to dbus.
30  */
31 class ManagerActionsReset : public Node
32 {
33   public:
34     ManagerActionsReset(CrowApp& app) :
35         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
36     {
37         entityPrivileges = {
38             {boost::beast::http::verb::get, {{"Login"}}},
39             {boost::beast::http::verb::head, {{"Login"}}},
40             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
41             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
42             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
43             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
44     }
45 
46   private:
47     /**
48      * Function handles POST method request.
49      * Analyzes POST body message before sends Reset request data to dbus.
50      * OpenBMC allows for ResetType is GracefulRestart only.
51      */
52     void doPost(crow::Response& res, const crow::Request& req,
53                 const std::vector<std::string>& params) override
54     {
55         std::string resetType;
56 
57         if (!json_util::readJson(req, res, "ResetType", resetType))
58         {
59             return;
60         }
61 
62         if (resetType != "GracefulRestart")
63         {
64             res.result(boost::beast::http::status::bad_request);
65             messages::actionParameterNotSupported(res, resetType, "ResetType");
66             BMCWEB_LOG_ERROR << "Request incorrect action parameter: "
67                              << resetType;
68             res.end();
69             return;
70         }
71         doBMCGracefulRestart(res, req, params);
72     }
73 
74     /**
75      * Function transceives data with dbus directly.
76      * All BMC state properties will be retrieved before sending reset request.
77      */
78     void doBMCGracefulRestart(crow::Response& res, const crow::Request& req,
79                               const std::vector<std::string>& params)
80     {
81         const char* processName = "xyz.openbmc_project.State.BMC";
82         const char* objectPath = "/xyz/openbmc_project/state/bmc0";
83         const char* interfaceName = "xyz.openbmc_project.State.BMC";
84         const std::string& propertyValue =
85             "xyz.openbmc_project.State.BMC.Transition.Reboot";
86         const char* destProperty = "RequestedBMCTransition";
87 
88         // Create the D-Bus variant for D-Bus call.
89         VariantType dbusPropertyValue(propertyValue);
90 
91         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
92 
93         crow::connections::systemBus->async_method_call(
94             [asyncResp](const boost::system::error_code ec) {
95                 // Use "Set" method to set the property value.
96                 if (ec)
97                 {
98                     BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec;
99                     messages::internalError(asyncResp->res);
100                     return;
101                 }
102 
103                 messages::success(asyncResp->res);
104             },
105             processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
106             interfaceName, destProperty, dbusPropertyValue);
107     }
108 };
109 
110 static constexpr const char* objectManagerIface =
111     "org.freedesktop.DBus.ObjectManager";
112 static constexpr const char* pidConfigurationIface =
113     "xyz.openbmc_project.Configuration.Pid";
114 static constexpr const char* pidZoneConfigurationIface =
115     "xyz.openbmc_project.Configuration.Pid.Zone";
116 static constexpr const char* stepwiseConfigurationIface =
117     "xyz.openbmc_project.Configuration.Stepwise";
118 
119 static void asyncPopulatePid(const std::string& connection,
120                              const std::string& path,
121                              std::shared_ptr<AsyncResp> asyncResp)
122 {
123 
124     crow::connections::systemBus->async_method_call(
125         [asyncResp](const boost::system::error_code ec,
126                     const dbus::utility::ManagedObjectType& managedObj) {
127             if (ec)
128             {
129                 BMCWEB_LOG_ERROR << ec;
130                 asyncResp->res.jsonValue.clear();
131                 messages::internalError(asyncResp->res);
132                 return;
133             }
134             nlohmann::json& configRoot =
135                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
136             nlohmann::json& fans = configRoot["FanControllers"];
137             fans["@odata.type"] = "#OemManager.FanControllers";
138             fans["@odata.context"] =
139                 "/redfish/v1/$metadata#OemManager.FanControllers";
140             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
141                                 "Fan/FanControllers";
142 
143             nlohmann::json& pids = configRoot["PidControllers"];
144             pids["@odata.type"] = "#OemManager.PidControllers";
145             pids["@odata.context"] =
146                 "/redfish/v1/$metadata#OemManager.PidControllers";
147             pids["@odata.id"] =
148                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
149 
150             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
151             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
152             stepwise["@odata.context"] =
153                 "/redfish/v1/$metadata#OemManager.StepwiseControllers";
154             stepwise["@odata.id"] =
155                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
156 
157             nlohmann::json& zones = configRoot["FanZones"];
158             zones["@odata.id"] =
159                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
160             zones["@odata.type"] = "#OemManager.FanZones";
161             zones["@odata.context"] =
162                 "/redfish/v1/$metadata#OemManager.FanZones";
163             configRoot["@odata.id"] =
164                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
165             configRoot["@odata.type"] = "#OemManager.Fan";
166             configRoot["@odata.context"] =
167                 "/redfish/v1/$metadata#OemManager.Fan";
168 
169             for (const auto& pathPair : managedObj)
170             {
171                 for (const auto& intfPair : pathPair.second)
172                 {
173                     if (intfPair.first != pidConfigurationIface &&
174                         intfPair.first != pidZoneConfigurationIface &&
175                         intfPair.first != stepwiseConfigurationIface)
176                     {
177                         continue;
178                     }
179                     auto findName = intfPair.second.find("Name");
180                     if (findName == intfPair.second.end())
181                     {
182                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
183                         messages::internalError(asyncResp->res);
184                         return;
185                     }
186                     const std::string* namePtr =
187                         std::get_if<std::string>(&findName->second);
188                     if (namePtr == nullptr)
189                     {
190                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
191                         messages::internalError(asyncResp->res);
192                         return;
193                     }
194 
195                     std::string name = *namePtr;
196                     dbus::utility::escapePathForDbus(name);
197                     nlohmann::json* config = nullptr;
198                     if (intfPair.first == pidZoneConfigurationIface)
199                     {
200                         std::string chassis;
201                         if (!dbus::utility::getNthStringFromPath(
202                                 pathPair.first.str, 5, chassis))
203                         {
204                             chassis = "#IllegalValue";
205                         }
206                         nlohmann::json& zone = zones[name];
207                         zone["Chassis"] = {
208                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
209                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
210                                             "OpenBmc/Fan/FanZones/" +
211                                             name;
212                         zone["@odata.type"] = "#OemManager.FanZone";
213                         zone["@odata.context"] =
214                             "/redfish/v1/$metadata#OemManager.FanZone";
215                         config = &zone;
216                     }
217 
218                     else if (intfPair.first == stepwiseConfigurationIface)
219                     {
220                         nlohmann::json& controller = stepwise[name];
221                         config = &controller;
222 
223                         controller["@odata.id"] =
224                             "/redfish/v1/Managers/bmc#/Oem/"
225                             "OpenBmc/Fan/StepwiseControllers/" +
226                             std::string(name);
227                         controller["@odata.type"] =
228                             "#OemManager.StepwiseController";
229 
230                         controller["@odata.context"] =
231                             "/redfish/v1/"
232                             "$metadata#OemManager.StepwiseController";
233                     }
234 
235                     // pid and fans are off the same configuration
236                     else if (intfPair.first == pidConfigurationIface)
237                     {
238                         const std::string* classPtr = nullptr;
239                         auto findClass = intfPair.second.find("Class");
240                         if (findClass != intfPair.second.end())
241                         {
242                             classPtr =
243                                 std::get_if<std::string>(&findClass->second);
244                         }
245                         if (classPtr == nullptr)
246                         {
247                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
248                             messages::internalError(asyncResp->res);
249                             return;
250                         }
251                         bool isFan = *classPtr == "fan";
252                         nlohmann::json& element =
253                             isFan ? fans[name] : pids[name];
254                         config = &element;
255                         if (isFan)
256                         {
257                             element["@odata.id"] =
258                                 "/redfish/v1/Managers/bmc#/Oem/"
259                                 "OpenBmc/Fan/FanControllers/" +
260                                 std::string(name);
261                             element["@odata.type"] =
262                                 "#OemManager.FanController";
263 
264                             element["@odata.context"] =
265                                 "/redfish/v1/"
266                                 "$metadata#OemManager.FanController";
267                         }
268                         else
269                         {
270                             element["@odata.id"] =
271                                 "/redfish/v1/Managers/bmc#/Oem/"
272                                 "OpenBmc/Fan/PidControllers/" +
273                                 std::string(name);
274                             element["@odata.type"] =
275                                 "#OemManager.PidController";
276                             element["@odata.context"] =
277                                 "/redfish/v1/$metadata"
278                                 "#OemManager.PidController";
279                         }
280                     }
281                     else
282                     {
283                         BMCWEB_LOG_ERROR << "Unexpected configuration";
284                         messages::internalError(asyncResp->res);
285                         return;
286                     }
287 
288                     // used for making maps out of 2 vectors
289                     const std::vector<double>* keys = nullptr;
290                     const std::vector<double>* values = nullptr;
291 
292                     for (const auto& propertyPair : intfPair.second)
293                     {
294                         if (propertyPair.first == "Type" ||
295                             propertyPair.first == "Class" ||
296                             propertyPair.first == "Name")
297                         {
298                             continue;
299                         }
300 
301                         // zones
302                         if (intfPair.first == pidZoneConfigurationIface)
303                         {
304                             const double* ptr =
305                                 std::get_if<double>(&propertyPair.second);
306                             if (ptr == nullptr)
307                             {
308                                 BMCWEB_LOG_ERROR << "Field Illegal "
309                                                  << propertyPair.first;
310                                 messages::internalError(asyncResp->res);
311                                 return;
312                             }
313                             (*config)[propertyPair.first] = *ptr;
314                         }
315 
316                         if (intfPair.first == stepwiseConfigurationIface)
317                         {
318                             if (propertyPair.first == "Reading" ||
319                                 propertyPair.first == "Output")
320                             {
321                                 const std::vector<double>* ptr =
322                                     std::get_if<std::vector<double>>(
323                                         &propertyPair.second);
324 
325                                 if (ptr == nullptr)
326                                 {
327                                     BMCWEB_LOG_ERROR << "Field Illegal "
328                                                      << propertyPair.first;
329                                     messages::internalError(asyncResp->res);
330                                     return;
331                                 }
332 
333                                 if (propertyPair.first == "Reading")
334                                 {
335                                     keys = ptr;
336                                 }
337                                 else
338                                 {
339                                     values = ptr;
340                                 }
341                                 if (keys && values)
342                                 {
343                                     if (keys->size() != values->size())
344                                     {
345                                         BMCWEB_LOG_ERROR
346                                             << "Reading and Output size don't "
347                                                "match ";
348                                         messages::internalError(asyncResp->res);
349                                         return;
350                                     }
351                                     nlohmann::json& steps = (*config)["Steps"];
352                                     steps = nlohmann::json::array();
353                                     for (size_t ii = 0; ii < keys->size(); ii++)
354                                     {
355                                         steps.push_back(
356                                             {{"Target", (*keys)[ii]},
357                                              {"Output", (*values)[ii]}});
358                                     }
359                                 }
360                             }
361                             if (propertyPair.first == "NegativeHysteresis" ||
362                                 propertyPair.first == "PositiveHysteresis")
363                             {
364                                 const double* ptr =
365                                     std::get_if<double>(&propertyPair.second);
366                                 if (ptr == nullptr)
367                                 {
368                                     BMCWEB_LOG_ERROR << "Field Illegal "
369                                                      << propertyPair.first;
370                                     messages::internalError(asyncResp->res);
371                                     return;
372                                 }
373                                 (*config)[propertyPair.first] = *ptr;
374                             }
375                         }
376 
377                         // pid and fans are off the same configuration
378                         if (intfPair.first == pidConfigurationIface ||
379                             intfPair.first == stepwiseConfigurationIface)
380                         {
381 
382                             if (propertyPair.first == "Zones")
383                             {
384                                 const std::vector<std::string>* inputs =
385                                     std::get_if<std::vector<std::string>>(
386                                         &propertyPair.second);
387 
388                                 if (inputs == nullptr)
389                                 {
390                                     BMCWEB_LOG_ERROR
391                                         << "Zones Pid Field Illegal";
392                                     messages::internalError(asyncResp->res);
393                                     return;
394                                 }
395                                 auto& data = (*config)[propertyPair.first];
396                                 data = nlohmann::json::array();
397                                 for (std::string itemCopy : *inputs)
398                                 {
399                                     dbus::utility::escapePathForDbus(itemCopy);
400                                     data.push_back(
401                                         {{"@odata.id",
402                                           "/redfish/v1/Managers/bmc#/Oem/"
403                                           "OpenBmc/Fan/FanZones/" +
404                                               itemCopy}});
405                                 }
406                             }
407                             // todo(james): may never happen, but this
408                             // assumes configuration data referenced in the
409                             // PID config is provided by the same daemon, we
410                             // could add another loop to cover all cases,
411                             // but I'm okay kicking this can down the road a
412                             // bit
413 
414                             else if (propertyPair.first == "Inputs" ||
415                                      propertyPair.first == "Outputs")
416                             {
417                                 auto& data = (*config)[propertyPair.first];
418                                 const std::vector<std::string>* inputs =
419                                     std::get_if<std::vector<std::string>>(
420                                         &propertyPair.second);
421 
422                                 if (inputs == nullptr)
423                                 {
424                                     BMCWEB_LOG_ERROR << "Field Illegal "
425                                                      << propertyPair.first;
426                                     messages::internalError(asyncResp->res);
427                                     return;
428                                 }
429                                 data = *inputs;
430                             } // doubles
431                             else if (propertyPair.first ==
432                                          "FFGainCoefficient" ||
433                                      propertyPair.first == "FFOffCoefficient" ||
434                                      propertyPair.first == "ICoefficient" ||
435                                      propertyPair.first == "ILimitMax" ||
436                                      propertyPair.first == "ILimitMin" ||
437                                      propertyPair.first ==
438                                          "PositiveHysteresis" ||
439                                      propertyPair.first ==
440                                          "NegativeHysteresis" ||
441                                      propertyPair.first == "OutLimitMax" ||
442                                      propertyPair.first == "OutLimitMin" ||
443                                      propertyPair.first == "PCoefficient" ||
444                                      propertyPair.first == "SetPoint" ||
445                                      propertyPair.first == "SlewNeg" ||
446                                      propertyPair.first == "SlewPos")
447                             {
448                                 const double* ptr =
449                                     std::get_if<double>(&propertyPair.second);
450                                 if (ptr == nullptr)
451                                 {
452                                     BMCWEB_LOG_ERROR << "Field Illegal "
453                                                      << propertyPair.first;
454                                     messages::internalError(asyncResp->res);
455                                     return;
456                                 }
457                                 (*config)[propertyPair.first] = *ptr;
458                             }
459                         }
460                     }
461                 }
462             }
463         },
464         connection, path, objectManagerIface, "GetManagedObjects");
465 }
466 
467 enum class CreatePIDRet
468 {
469     fail,
470     del,
471     patch
472 };
473 
474 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response,
475                                 std::vector<nlohmann::json>& config,
476                                 std::vector<std::string>& zones)
477 {
478 
479     for (auto& odata : config)
480     {
481         std::string path;
482         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
483                                           path))
484         {
485             return false;
486         }
487         std::string input;
488         if (!dbus::utility::getNthStringFromPath(path, 4, input))
489         {
490             BMCWEB_LOG_ERROR << "Got invalid path " << path;
491             BMCWEB_LOG_ERROR << "Illegal Type Zones";
492             messages::propertyValueFormatError(response->res, odata.dump(),
493                                                "Zones");
494             return false;
495         }
496         boost::replace_all(input, "_", " ");
497         zones.emplace_back(std::move(input));
498     }
499     return true;
500 }
501 
502 static CreatePIDRet createPidInterface(
503     const std::shared_ptr<AsyncResp>& response, const std::string& type,
504     nlohmann::json&& record, const std::string& path,
505     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
506     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
507         output,
508     std::string& chassis)
509 {
510 
511     // common deleter
512     if (record == nullptr)
513     {
514         std::string iface;
515         if (type == "PidControllers" || type == "FanControllers")
516         {
517             iface = pidConfigurationIface;
518         }
519         else if (type == "FanZones")
520         {
521             iface = pidZoneConfigurationIface;
522         }
523         else if (type == "StepwiseControllers")
524         {
525             iface = stepwiseConfigurationIface;
526         }
527         else
528         {
529             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
530                              << type;
531             messages::propertyUnknown(response->res, type);
532             return CreatePIDRet::fail;
533         }
534         // delete interface
535         crow::connections::systemBus->async_method_call(
536             [response, path](const boost::system::error_code ec) {
537                 if (ec)
538                 {
539                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
540                     messages::internalError(response->res);
541                 }
542             },
543             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
544         return CreatePIDRet::del;
545     }
546 
547     if (type == "PidControllers" || type == "FanControllers")
548     {
549         if (createNewObject)
550         {
551             output["Class"] = type == "PidControllers" ? std::string("temp")
552                                                        : std::string("fan");
553             output["Type"] = std::string("Pid");
554         }
555 
556         std::optional<std::vector<nlohmann::json>> zones;
557         std::optional<std::vector<std::string>> inputs;
558         std::optional<std::vector<std::string>> outputs;
559         std::map<std::string, std::optional<double>> doubles;
560         if (!redfish::json_util::readJson(
561                 record, response->res, "Inputs", inputs, "Outputs", outputs,
562                 "Zones", zones, "FFGainCoefficient",
563                 doubles["FFGainCoefficient"], "FFOffCoefficient",
564                 doubles["FFOffCoefficient"], "ICoefficient",
565                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
566                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
567                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
568                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
569                 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos",
570                 doubles["SlewPos"], "PositiveHysteresis",
571                 doubles["PositiveHysteresis"], "NegativeHysteresis",
572                 doubles["NegativeHysteresis"]))
573         {
574             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
575                              << record.dump();
576             return CreatePIDRet::fail;
577         }
578         if (zones)
579         {
580             std::vector<std::string> zonesStr;
581             if (!getZonesFromJsonReq(response, *zones, zonesStr))
582             {
583                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
584                 return CreatePIDRet::fail;
585             }
586             output["Zones"] = std::move(zonesStr);
587         }
588         if (inputs || outputs)
589         {
590             std::array<std::optional<std::vector<std::string>>*, 2> containers =
591                 {&inputs, &outputs};
592             size_t index = 0;
593             for (const auto& containerPtr : containers)
594             {
595                 std::optional<std::vector<std::string>>& container =
596                     *containerPtr;
597                 if (!container)
598                 {
599                     index++;
600                     continue;
601                 }
602 
603                 for (std::string& value : *container)
604                 {
605 
606                     // try to find the sensor in the
607                     // configuration
608                     if (chassis.empty())
609                     {
610                         std::string escaped =
611                             boost::replace_all_copy(value, " ", "_");
612                         std::find_if(
613                             managedObj.begin(), managedObj.end(),
614                             [&chassis, &escaped](const auto& obj) {
615                                 if (boost::algorithm::ends_with(obj.first.str,
616                                                                 escaped))
617                                 {
618                                     return dbus::utility::getNthStringFromPath(
619                                         obj.first.str, 5, chassis);
620                                 }
621                                 return false;
622                             });
623                     }
624                     boost::replace_all(value, "_", " ");
625                 }
626                 std::string key;
627                 if (index == 0)
628                 {
629                     key = "Inputs";
630                 }
631                 else
632                 {
633                     key = "Outputs";
634                 }
635                 output[key] = *container;
636                 index++;
637             }
638         }
639 
640         // doubles
641         for (const auto& pairs : doubles)
642         {
643             if (!pairs.second)
644             {
645                 continue;
646             }
647             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
648             output[pairs.first] = *(pairs.second);
649         }
650     }
651 
652     else if (type == "FanZones")
653     {
654         output["Type"] = std::string("Pid.Zone");
655 
656         std::optional<nlohmann::json> chassisContainer;
657         std::optional<double> failSafePercent;
658         std::optional<double> minThermalRpm;
659         if (!redfish::json_util::readJson(record, response->res, "Chassis",
660                                           chassisContainer, "FailSafePercent",
661                                           failSafePercent, "MinThermalRpm",
662                                           minThermalRpm))
663         {
664             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
665                              << record.dump();
666             return CreatePIDRet::fail;
667         }
668 
669         if (chassisContainer)
670         {
671 
672             std::string chassisId;
673             if (!redfish::json_util::readJson(*chassisContainer, response->res,
674                                               "@odata.id", chassisId))
675             {
676                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
677                                  << chassisContainer->dump();
678                 return CreatePIDRet::fail;
679             }
680 
681             // /refish/v1/chassis/chassis_name/
682             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
683             {
684                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
685                 messages::invalidObject(response->res, chassisId);
686                 return CreatePIDRet::fail;
687             }
688         }
689         if (minThermalRpm)
690         {
691             output["MinThermalRpm"] = *minThermalRpm;
692         }
693         if (failSafePercent)
694         {
695             output["FailSafePercent"] = *failSafePercent;
696         }
697     }
698     else if (type == "StepwiseControllers")
699     {
700         output["Type"] = std::string("Stepwise");
701 
702         std::optional<std::vector<nlohmann::json>> zones;
703         std::optional<std::vector<nlohmann::json>> steps;
704         std::optional<std::vector<std::string>> inputs;
705         std::optional<double> positiveHysteresis;
706         std::optional<double> negativeHysteresis;
707         if (!redfish::json_util::readJson(
708                 record, response->res, "Zones", zones, "Steps", steps, "Inputs",
709                 inputs, "PositiveHysteresis", positiveHysteresis,
710                 "NegativeHysteresis", negativeHysteresis))
711         {
712             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
713                              << record.dump();
714             return CreatePIDRet::fail;
715         }
716 
717         if (zones)
718         {
719             std::vector<std::string> zoneStrs;
720             if (!getZonesFromJsonReq(response, *zones, zoneStrs))
721             {
722                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
723                 return CreatePIDRet::fail;
724             }
725             output["Zones"] = std::move(zoneStrs);
726         }
727         if (steps)
728         {
729             std::vector<double> readings;
730             std::vector<double> outputs;
731             for (auto& step : *steps)
732             {
733                 double target;
734                 double output;
735 
736                 if (!redfish::json_util::readJson(step, response->res, "Target",
737                                                   target, "Output", output))
738                 {
739                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
740                                      << ", Illegal Property " << record.dump();
741                     return CreatePIDRet::fail;
742                 }
743                 readings.emplace_back(target);
744                 outputs.emplace_back(output);
745             }
746             output["Reading"] = std::move(readings);
747             output["Output"] = std::move(outputs);
748         }
749         if (inputs)
750         {
751             for (std::string& value : *inputs)
752             {
753                 if (chassis.empty())
754                 {
755                     std::string escaped =
756                         boost::replace_all_copy(value, " ", "_");
757                     std::find_if(
758                         managedObj.begin(), managedObj.end(),
759                         [&chassis, &escaped](const auto& obj) {
760                             if (boost::algorithm::ends_with(obj.first.str,
761                                                             escaped))
762                             {
763                                 return dbus::utility::getNthStringFromPath(
764                                     obj.first.str, 5, chassis);
765                             }
766                             return false;
767                         });
768                 }
769                 boost::replace_all(value, "_", " ");
770             }
771             output["Inputs"] = std::move(*inputs);
772         }
773         if (negativeHysteresis)
774         {
775             output["NegativeHysteresis"] = *negativeHysteresis;
776         }
777         if (positiveHysteresis)
778         {
779             output["PositiveHysteresis"] = *positiveHysteresis;
780         }
781     }
782     else
783     {
784         BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type;
785         messages::propertyUnknown(response->res, type);
786         return CreatePIDRet::fail;
787     }
788     return CreatePIDRet::patch;
789 }
790 
791 class Manager : public Node
792 {
793   public:
794     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
795     {
796         uuid = app.template getMiddleware<crow::persistent_data::Middleware>()
797                    .systemUuid;
798         entityPrivileges = {
799             {boost::beast::http::verb::get, {{"Login"}}},
800             {boost::beast::http::verb::head, {{"Login"}}},
801             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
802             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
803             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
804             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
805     }
806 
807   private:
808     void getPidValues(std::shared_ptr<AsyncResp> asyncResp)
809     {
810         crow::connections::systemBus->async_method_call(
811             [asyncResp](const boost::system::error_code ec,
812                         const crow::openbmc_mapper::GetSubTreeType& subtree) {
813                 if (ec)
814                 {
815                     BMCWEB_LOG_ERROR << ec;
816                     messages::internalError(asyncResp->res);
817                     return;
818                 }
819 
820                 // create map of <connection, path to objMgr>>
821                 boost::container::flat_map<std::string, std::string>
822                     objectMgrPaths;
823                 boost::container::flat_set<std::string> calledConnections;
824                 for (const auto& pathGroup : subtree)
825                 {
826                     for (const auto& connectionGroup : pathGroup.second)
827                     {
828                         auto findConnection =
829                             calledConnections.find(connectionGroup.first);
830                         if (findConnection != calledConnections.end())
831                         {
832                             break;
833                         }
834                         for (const std::string& interface :
835                              connectionGroup.second)
836                         {
837                             if (interface == objectManagerIface)
838                             {
839                                 objectMgrPaths[connectionGroup.first] =
840                                     pathGroup.first;
841                             }
842                             // this list is alphabetical, so we
843                             // should have found the objMgr by now
844                             if (interface == pidConfigurationIface ||
845                                 interface == pidZoneConfigurationIface ||
846                                 interface == stepwiseConfigurationIface)
847                             {
848                                 auto findObjMgr =
849                                     objectMgrPaths.find(connectionGroup.first);
850                                 if (findObjMgr == objectMgrPaths.end())
851                                 {
852                                     BMCWEB_LOG_DEBUG << connectionGroup.first
853                                                      << "Has no Object Manager";
854                                     continue;
855                                 }
856 
857                                 calledConnections.insert(connectionGroup.first);
858 
859                                 asyncPopulatePid(findObjMgr->first,
860                                                  findObjMgr->second, asyncResp);
861                                 break;
862                             }
863                         }
864                     }
865                 }
866             },
867             "xyz.openbmc_project.ObjectMapper",
868             "/xyz/openbmc_project/object_mapper",
869             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
870             std::array<const char*, 4>{
871                 pidConfigurationIface, pidZoneConfigurationIface,
872                 objectManagerIface, stepwiseConfigurationIface});
873     }
874 
875     void doGet(crow::Response& res, const crow::Request& req,
876                const std::vector<std::string>& params) override
877     {
878         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
879         res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager";
880         res.jsonValue["@odata.context"] =
881             "/redfish/v1/$metadata#Manager.Manager";
882         res.jsonValue["Id"] = "bmc";
883         res.jsonValue["Name"] = "OpenBmc Manager";
884         res.jsonValue["Description"] = "Baseboard Management Controller";
885         res.jsonValue["PowerState"] = "On";
886         res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}};
887         res.jsonValue["ManagerType"] = "BMC";
888         res.jsonValue["UUID"] = uuid;
889         res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
890 
891         res.jsonValue["LogServices"] = {
892             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
893 
894         res.jsonValue["NetworkProtocol"] = {
895             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
896 
897         res.jsonValue["EthernetInterfaces"] = {
898             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
899         // default oem data
900         nlohmann::json& oem = res.jsonValue["Oem"];
901         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
902         oem["@odata.type"] = "#OemManager.Oem";
903         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
904         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
905         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
906         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
907         oemOpenbmc["@odata.context"] =
908             "/redfish/v1/$metadata#OemManager.OpenBmc";
909 
910         // Update Actions object.
911         nlohmann::json& manager_reset =
912             res.jsonValue["Actions"]["#Manager.Reset"];
913         manager_reset["target"] =
914             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
915         manager_reset["ResetType@Redfish.AllowableValues"] = {
916             "GracefulRestart"};
917 
918         res.jsonValue["DateTime"] = getDateTime();
919         res.jsonValue["Links"] = {
920             {"ManagerForServers@odata.count", 1},
921             {"ManagerForServers",
922              {{{"@odata.id", "/redfish/v1/Systems/system"}}}},
923             {"ManagerForServers", nlohmann::json::array()}};
924         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
925 
926         crow::connections::systemBus->async_method_call(
927             [asyncResp](const boost::system::error_code ec,
928                         const dbus::utility::ManagedObjectType& resp) {
929                 if (ec)
930                 {
931                     BMCWEB_LOG_ERROR << "Error while getting Software Version";
932                     messages::internalError(asyncResp->res);
933                     return;
934                 }
935 
936                 for (auto& objpath : resp)
937                 {
938                     for (auto& interface : objpath.second)
939                     {
940                         // If interface is
941                         // xyz.openbmc_project.Software.Version, this is
942                         // what we're looking for.
943                         if (interface.first ==
944                             "xyz.openbmc_project.Software.Version")
945                         {
946                             // Cut out everyting until last "/", ...
947                             for (auto& property : interface.second)
948                             {
949                                 if (property.first == "Version")
950                                 {
951                                     const std::string* value =
952                                         std::get_if<std::string>(
953                                             &property.second);
954                                     if (value == nullptr)
955                                     {
956                                         continue;
957                                     }
958                                     asyncResp->res
959                                         .jsonValue["FirmwareVersion"] = *value;
960                                 }
961                             }
962                         }
963                     }
964                 }
965             },
966             "xyz.openbmc_project.Software.BMC.Updater",
967             "/xyz/openbmc_project/software",
968             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
969         getPidValues(asyncResp);
970     }
971     void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data)
972     {
973 
974         // todo(james): might make sense to do a mapper call here if this
975         // interface gets more traction
976         crow::connections::systemBus->async_method_call(
977             [response,
978              data](const boost::system::error_code ec,
979                    const dbus::utility::ManagedObjectType& managedObj) {
980                 if (ec)
981                 {
982                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
983                     messages::internalError(response->res);
984                     return;
985                 }
986 
987                 // todo(james) mutable doesn't work with asio bindings
988                 nlohmann::json jsonData = data;
989 
990                 std::optional<nlohmann::json> pidControllers;
991                 std::optional<nlohmann::json> fanControllers;
992                 std::optional<nlohmann::json> fanZones;
993                 std::optional<nlohmann::json> stepwiseControllers;
994                 if (!redfish::json_util::readJson(
995                         jsonData, response->res, "PidControllers",
996                         pidControllers, "FanControllers", fanControllers,
997                         "FanZones", fanZones, "StepwiseControllers",
998                         stepwiseControllers))
999                 {
1000                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1001                                      << ", Illegal Property "
1002                                      << jsonData.dump();
1003                     return;
1004                 }
1005                 std::array<
1006                     std::pair<std::string, std::optional<nlohmann::json>*>, 4>
1007                     sections = {
1008                         std::make_pair("PidControllers", &pidControllers),
1009                         std::make_pair("FanControllers", &fanControllers),
1010                         std::make_pair("FanZones", &fanZones),
1011                         std::make_pair("StepwiseControllers",
1012                                        &stepwiseControllers)};
1013 
1014                 for (auto& containerPair : sections)
1015                 {
1016                     auto& container = *(containerPair.second);
1017                     if (!container)
1018                     {
1019                         continue;
1020                     }
1021                     std::string& type = containerPair.first;
1022 
1023                     for (auto& record : container->items())
1024                     {
1025                         const auto& name = record.key();
1026                         auto pathItr =
1027                             std::find_if(managedObj.begin(), managedObj.end(),
1028                                          [&name](const auto& obj) {
1029                                              return boost::algorithm::ends_with(
1030                                                  obj.first.str, name);
1031                                          });
1032                         boost::container::flat_map<
1033                             std::string, dbus::utility::DbusVariantType>
1034                             output;
1035 
1036                         output.reserve(16); // The pid interface length
1037 
1038                         // determines if we're patching entity-manager or
1039                         // creating a new object
1040                         bool createNewObject = (pathItr == managedObj.end());
1041                         std::string iface;
1042                         if (type == "PidControllers" ||
1043                             type == "FanControllers")
1044                         {
1045                             iface = pidConfigurationIface;
1046                             if (!createNewObject &&
1047                                 pathItr->second.find(pidConfigurationIface) ==
1048                                     pathItr->second.end())
1049                             {
1050                                 createNewObject = true;
1051                             }
1052                         }
1053                         else if (type == "FanZones")
1054                         {
1055                             iface = pidZoneConfigurationIface;
1056                             if (!createNewObject &&
1057                                 pathItr->second.find(
1058                                     pidZoneConfigurationIface) ==
1059                                     pathItr->second.end())
1060                             {
1061 
1062                                 createNewObject = true;
1063                             }
1064                         }
1065                         else if (type == "StepwiseControllers")
1066                         {
1067                             iface = stepwiseConfigurationIface;
1068                             if (!createNewObject &&
1069                                 pathItr->second.find(
1070                                     stepwiseConfigurationIface) ==
1071                                     pathItr->second.end())
1072                             {
1073                                 createNewObject = true;
1074                             }
1075                         }
1076                         output["Name"] =
1077                             boost::replace_all_copy(name, "_", " ");
1078 
1079                         std::string chassis;
1080                         CreatePIDRet ret = createPidInterface(
1081                             response, type, std::move(record.value()),
1082                             pathItr->first.str, managedObj, createNewObject,
1083                             output, chassis);
1084                         if (ret == CreatePIDRet::fail)
1085                         {
1086                             return;
1087                         }
1088                         else if (ret == CreatePIDRet::del)
1089                         {
1090                             continue;
1091                         }
1092 
1093                         if (!createNewObject)
1094                         {
1095                             for (const auto& property : output)
1096                             {
1097                                 crow::connections::systemBus->async_method_call(
1098                                     [response,
1099                                      propertyName{std::string(property.first)}](
1100                                         const boost::system::error_code ec) {
1101                                         if (ec)
1102                                         {
1103                                             BMCWEB_LOG_ERROR
1104                                                 << "Error patching "
1105                                                 << propertyName << ": " << ec;
1106                                             messages::internalError(
1107                                                 response->res);
1108                                         }
1109                                     },
1110                                     "xyz.openbmc_project.EntityManager",
1111                                     pathItr->first.str,
1112                                     "org.freedesktop.DBus.Properties", "Set",
1113                                     iface, property.first, property.second);
1114                             }
1115                         }
1116                         else
1117                         {
1118                             if (chassis.empty())
1119                             {
1120                                 BMCWEB_LOG_ERROR
1121                                     << "Failed to get chassis from config";
1122                                 messages::invalidObject(response->res, name);
1123                                 return;
1124                             }
1125 
1126                             bool foundChassis = false;
1127                             for (const auto& obj : managedObj)
1128                             {
1129                                 if (boost::algorithm::ends_with(obj.first.str,
1130                                                                 chassis))
1131                                 {
1132                                     chassis = obj.first.str;
1133                                     foundChassis = true;
1134                                     break;
1135                                 }
1136                             }
1137                             if (!foundChassis)
1138                             {
1139                                 BMCWEB_LOG_ERROR
1140                                     << "Failed to find chassis on dbus";
1141                                 messages::resourceMissingAtURI(
1142                                     response->res,
1143                                     "/redfish/v1/Chassis/" + chassis);
1144                                 return;
1145                             }
1146 
1147                             crow::connections::systemBus->async_method_call(
1148                                 [response](const boost::system::error_code ec) {
1149                                     if (ec)
1150                                     {
1151                                         BMCWEB_LOG_ERROR
1152                                             << "Error Adding Pid Object " << ec;
1153                                         messages::internalError(response->res);
1154                                     }
1155                                 },
1156                                 "xyz.openbmc_project.EntityManager", chassis,
1157                                 "xyz.openbmc_project.AddObject", "AddObject",
1158                                 output);
1159                         }
1160                     }
1161                 }
1162             },
1163             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
1164             "GetManagedObjects");
1165     }
1166 
1167     void doPatch(crow::Response& res, const crow::Request& req,
1168                  const std::vector<std::string>& params) override
1169     {
1170         std::optional<nlohmann::json> oem;
1171 
1172         if (!json_util::readJson(req, res, "Oem", oem))
1173         {
1174             return;
1175         }
1176 
1177         std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res);
1178 
1179         if (oem)
1180         {
1181             std::optional<nlohmann::json> openbmc;
1182             if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc))
1183             {
1184                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1185                                  << oem->dump();
1186                 return;
1187             }
1188             if (openbmc)
1189             {
1190                 std::optional<nlohmann::json> fan;
1191                 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan))
1192                 {
1193                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1194                                      << ", Illegal Property "
1195                                      << openbmc->dump();
1196                     return;
1197                 }
1198                 if (fan)
1199                 {
1200                     setPidValues(response, *fan);
1201                 }
1202             }
1203         }
1204     }
1205 
1206     std::string getDateTime() const
1207     {
1208         std::array<char, 128> dateTime;
1209         std::string redfishDateTime("0000-00-00T00:00:00Z00:00");
1210         std::time_t time = std::time(nullptr);
1211 
1212         if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z",
1213                           std::localtime(&time)))
1214         {
1215             // insert the colon required by the ISO 8601 standard
1216             redfishDateTime = std::string(dateTime.data());
1217             redfishDateTime.insert(redfishDateTime.end() - 2, ':');
1218         }
1219 
1220         return redfishDateTime;
1221     }
1222 
1223     std::string uuid;
1224 };
1225 
1226 class ManagerCollection : public Node
1227 {
1228   public:
1229     ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
1230     {
1231         entityPrivileges = {
1232             {boost::beast::http::verb::get, {{"Login"}}},
1233             {boost::beast::http::verb::head, {{"Login"}}},
1234             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1235             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1236             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1237             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1238     }
1239 
1240   private:
1241     void doGet(crow::Response& res, const crow::Request& req,
1242                const std::vector<std::string>& params) override
1243     {
1244         // Collections don't include the static data added by SubRoute
1245         // because it has a duplicate entry for members
1246         res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
1247         res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection";
1248         res.jsonValue["@odata.context"] =
1249             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
1250         res.jsonValue["Name"] = "Manager Collection";
1251         res.jsonValue["Members@odata.count"] = 1;
1252         res.jsonValue["Members"] = {
1253             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
1254         res.end();
1255     }
1256 };
1257 } // namespace redfish
1258