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