xref: /openbmc/bmcweb/features/redfish/lib/managers.hpp (revision 5f2caaef9fec67f3b4da92b7eddcf125b6987f46)
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 
23 namespace redfish
24 {
25 
26 /**
27  * ManagerActionsReset class supports handle POST method for Reset action.
28  * The class retrieves and sends data directly to dbus.
29  */
30 class ManagerActionsReset : public Node
31 {
32   public:
33     ManagerActionsReset(CrowApp& app) :
34         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
35     {
36         entityPrivileges = {
37             {boost::beast::http::verb::get, {{"Login"}}},
38             {boost::beast::http::verb::head, {{"Login"}}},
39             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
40             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
41             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
42             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
43     }
44 
45   private:
46     /**
47      * Function handles POST method request.
48      * Analyzes POST body message before sends Reset request data to dbus.
49      * OpenBMC allows for ResetType is GracefulRestart only.
50      */
51     void doPost(crow::Response& res, const crow::Request& req,
52                 const std::vector<std::string>& params) override
53     {
54         std::string resetType;
55 
56         if (!json_util::readJson(req, res, "ResetType", resetType))
57         {
58             return;
59         }
60 
61         if (resetType != "GracefulRestart")
62         {
63             res.result(boost::beast::http::status::bad_request);
64             messages::actionParameterNotSupported(res, resetType, "ResetType");
65             BMCWEB_LOG_ERROR << "Request incorrect action parameter: "
66                              << resetType;
67             res.end();
68             return;
69         }
70         doBMCGracefulRestart(res, req, params);
71     }
72 
73     /**
74      * Function transceives data with dbus directly.
75      * All BMC state properties will be retrieved before sending reset request.
76      */
77     void doBMCGracefulRestart(crow::Response& res, const crow::Request& req,
78                               const std::vector<std::string>& params)
79     {
80         const char* processName = "xyz.openbmc_project.State.BMC";
81         const char* objectPath = "/xyz/openbmc_project/state/bmc0";
82         const char* interfaceName = "xyz.openbmc_project.State.BMC";
83         const std::string& propertyValue =
84             "xyz.openbmc_project.State.BMC.Transition.Reboot";
85         const char* destProperty = "RequestedBMCTransition";
86 
87         // Create the D-Bus variant for D-Bus call.
88         VariantType dbusPropertyValue(propertyValue);
89 
90         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
91 
92         crow::connections::systemBus->async_method_call(
93             [asyncResp](const boost::system::error_code ec) {
94                 // Use "Set" method to set the property value.
95                 if (ec)
96                 {
97                     BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec;
98                     messages::internalError(asyncResp->res);
99                     return;
100                 }
101 
102                 messages::success(asyncResp->res);
103             },
104             processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
105             interfaceName, destProperty, dbusPropertyValue);
106     }
107 };
108 
109 static constexpr const char* objectManagerIface =
110     "org.freedesktop.DBus.ObjectManager";
111 static constexpr const char* pidConfigurationIface =
112     "xyz.openbmc_project.Configuration.Pid";
113 static constexpr const char* pidZoneConfigurationIface =
114     "xyz.openbmc_project.Configuration.Pid.Zone";
115 static constexpr const char* stepwiseConfigurationIface =
116     "xyz.openbmc_project.Configuration.Stepwise";
117 
118 static void asyncPopulatePid(const std::string& connection,
119                              const std::string& path,
120                              std::shared_ptr<AsyncResp> asyncResp)
121 {
122 
123     crow::connections::systemBus->async_method_call(
124         [asyncResp](const boost::system::error_code ec,
125                     const dbus::utility::ManagedObjectType& managedObj) {
126             if (ec)
127             {
128                 BMCWEB_LOG_ERROR << ec;
129                 asyncResp->res.jsonValue.clear();
130                 messages::internalError(asyncResp->res);
131                 return;
132             }
133             nlohmann::json& configRoot =
134                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
135             nlohmann::json& fans = configRoot["FanControllers"];
136             fans["@odata.type"] = "#OemManager.FanControllers";
137             fans["@odata.context"] =
138                 "/redfish/v1/$metadata#OemManager.FanControllers";
139             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
140                                 "Fan/FanControllers";
141 
142             nlohmann::json& pids = configRoot["PidControllers"];
143             pids["@odata.type"] = "#OemManager.PidControllers";
144             pids["@odata.context"] =
145                 "/redfish/v1/$metadata#OemManager.PidControllers";
146             pids["@odata.id"] =
147                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
148 
149             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
150             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
151             stepwise["@odata.context"] =
152                 "/redfish/v1/$metadata#OemManager.StepwiseControllers";
153             stepwise["@odata.id"] =
154                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
155 
156             nlohmann::json& zones = configRoot["FanZones"];
157             zones["@odata.id"] =
158                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
159             zones["@odata.type"] = "#OemManager.FanZones";
160             zones["@odata.context"] =
161                 "/redfish/v1/$metadata#OemManager.FanZones";
162             configRoot["@odata.id"] =
163                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
164             configRoot["@odata.type"] = "#OemManager.Fan";
165             configRoot["@odata.context"] =
166                 "/redfish/v1/$metadata#OemManager.Fan";
167 
168             bool propertyError = false;
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                         sdbusplus::message::variant_ns::get_if<std::string>(
188                             &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 = sdbusplus::message::variant_ns::get_if<
244                                 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                                 sdbusplus::message::variant_ns::get_if<double>(
307                                     &propertyPair.second);
308                             if (ptr == nullptr)
309                             {
310                                 BMCWEB_LOG_ERROR << "Field Illegal "
311                                                  << propertyPair.first;
312                                 messages::internalError(asyncResp->res);
313                                 return;
314                             }
315                             (*config)[propertyPair.first] = *ptr;
316                         }
317 
318                         if (intfPair.first == stepwiseConfigurationIface)
319                         {
320                             if (propertyPair.first == "Reading" ||
321                                 propertyPair.first == "Output")
322                             {
323                                 const std::vector<double>* ptr =
324                                     sdbusplus::message::variant_ns::get_if<
325                                         std::vector<double>>(
326                                         &propertyPair.second);
327 
328                                 if (ptr == nullptr)
329                                 {
330                                     BMCWEB_LOG_ERROR << "Field Illegal "
331                                                      << propertyPair.first;
332                                     messages::internalError(asyncResp->res);
333                                     return;
334                                 }
335 
336                                 if (propertyPair.first == "Reading")
337                                 {
338                                     keys = ptr;
339                                 }
340                                 else
341                                 {
342                                     values = ptr;
343                                 }
344                                 if (keys && values)
345                                 {
346                                     if (keys->size() != values->size())
347                                     {
348                                         BMCWEB_LOG_ERROR
349                                             << "Reading and Output size don't "
350                                                "match ";
351                                         messages::internalError(asyncResp->res);
352                                         return;
353                                     }
354                                     nlohmann::json& steps = (*config)["Steps"];
355                                     steps = nlohmann::json::array();
356                                     for (size_t ii = 0; ii < keys->size(); ii++)
357                                     {
358                                         steps.push_back(
359                                             {{"Target", (*keys)[ii]},
360                                              {"Output", (*values)[ii]}});
361                                     }
362                                 }
363                             }
364                             if (propertyPair.first == "NegativeHysteresis" ||
365                                 propertyPair.first == "PositiveHysteresis")
366                             {
367                                 const double* ptr =
368                                     sdbusplus::message::variant_ns::get_if<
369                                         double>(&propertyPair.second);
370                                 if (ptr == nullptr)
371                                 {
372                                     BMCWEB_LOG_ERROR << "Field Illegal "
373                                                      << propertyPair.first;
374                                     messages::internalError(asyncResp->res);
375                                     return;
376                                 }
377                                 (*config)[propertyPair.first] = *ptr;
378                             }
379                         }
380 
381                         // pid and fans are off the same configuration
382                         if (intfPair.first == pidConfigurationIface ||
383                             intfPair.first == stepwiseConfigurationIface)
384                         {
385 
386                             if (propertyPair.first == "Zones")
387                             {
388                                 const std::vector<std::string>* inputs =
389                                     sdbusplus::message::variant_ns::get_if<
390                                         std::vector<std::string>>(
391                                         &propertyPair.second);
392 
393                                 if (inputs == nullptr)
394                                 {
395                                     BMCWEB_LOG_ERROR
396                                         << "Zones Pid Field Illegal";
397                                     messages::internalError(asyncResp->res);
398                                     return;
399                                 }
400                                 auto& data = (*config)[propertyPair.first];
401                                 data = nlohmann::json::array();
402                                 for (std::string itemCopy : *inputs)
403                                 {
404                                     dbus::utility::escapePathForDbus(itemCopy);
405                                     data.push_back(
406                                         {{"@odata.id",
407                                           "/redfish/v1/Managers/bmc#/Oem/"
408                                           "OpenBmc/Fan/FanZones/" +
409                                               itemCopy}});
410                                 }
411                             }
412                             // todo(james): may never happen, but this
413                             // assumes configuration data referenced in the
414                             // PID config is provided by the same daemon, we
415                             // could add another loop to cover all cases,
416                             // but I'm okay kicking this can down the road a
417                             // bit
418 
419                             else if (propertyPair.first == "Inputs" ||
420                                      propertyPair.first == "Outputs")
421                             {
422                                 auto& data = (*config)[propertyPair.first];
423                                 const std::vector<std::string>* inputs =
424                                     sdbusplus::message::variant_ns::get_if<
425                                         std::vector<std::string>>(
426                                         &propertyPair.second);
427 
428                                 if (inputs == nullptr)
429                                 {
430                                     BMCWEB_LOG_ERROR << "Field Illegal "
431                                                      << propertyPair.first;
432                                     messages::internalError(asyncResp->res);
433                                     return;
434                                 }
435                                 data = *inputs;
436                             } // doubles
437                             else if (propertyPair.first ==
438                                          "FFGainCoefficient" ||
439                                      propertyPair.first == "FFOffCoefficient" ||
440                                      propertyPair.first == "ICoefficient" ||
441                                      propertyPair.first == "ILimitMax" ||
442                                      propertyPair.first == "ILimitMin" ||
443                                      propertyPair.first == "OutLimitMax" ||
444                                      propertyPair.first == "OutLimitMin" ||
445                                      propertyPair.first == "PCoefficient" ||
446                                      propertyPair.first == "SlewNeg" ||
447                                      propertyPair.first == "SlewPos")
448                             {
449                                 const double* ptr =
450                                     sdbusplus::message::variant_ns::get_if<
451                                         double>(&propertyPair.second);
452                                 if (ptr == nullptr)
453                                 {
454                                     BMCWEB_LOG_ERROR << "Field Illegal "
455                                                      << propertyPair.first;
456                                     messages::internalError(asyncResp->res);
457                                     return;
458                                 }
459                                 (*config)[propertyPair.first] = *ptr;
460                             }
461                         }
462                     }
463                 }
464             }
465         },
466         connection, path, objectManagerIface, "GetManagedObjects");
467 }
468 
469 enum class CreatePIDRet
470 {
471     fail,
472     del,
473     patch
474 };
475 
476 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response,
477                                 std::vector<nlohmann::json>& config,
478                                 std::vector<std::string>& zones)
479 {
480 
481     for (auto& odata : config)
482     {
483         std::string path;
484         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
485                                           path))
486         {
487             return false;
488         }
489         std::string input;
490         if (!dbus::utility::getNthStringFromPath(path, 4, input))
491         {
492             BMCWEB_LOG_ERROR << "Got invalid path " << path;
493             BMCWEB_LOG_ERROR << "Illegal Type Zones";
494             messages::propertyValueFormatError(response->res, odata.dump(),
495                                                "Zones");
496             return false;
497         }
498         boost::replace_all(input, "_", " ");
499         zones.emplace_back(std::move(input));
500     }
501     return true;
502 }
503 
504 static CreatePIDRet createPidInterface(
505     const std::shared_ptr<AsyncResp>& response, const std::string& type,
506     nlohmann::json&& record, const std::string& path,
507     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
508     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
509         output,
510     std::string& chassis)
511 {
512 
513     // common deleter
514     if (record == nullptr)
515     {
516         std::string iface;
517         if (type == "PidControllers" || type == "FanControllers")
518         {
519             iface = pidConfigurationIface;
520         }
521         else if (type == "FanZones")
522         {
523             iface = pidZoneConfigurationIface;
524         }
525         else if (type == "StepwiseControllers")
526         {
527             iface = stepwiseConfigurationIface;
528         }
529         else
530         {
531             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
532                              << type;
533             messages::propertyUnknown(response->res, type);
534             return CreatePIDRet::fail;
535         }
536         // delete interface
537         crow::connections::systemBus->async_method_call(
538             [response, path](const boost::system::error_code ec) {
539                 if (ec)
540                 {
541                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
542                     messages::internalError(response->res);
543                 }
544             },
545             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
546         return CreatePIDRet::del;
547     }
548 
549     if (type == "PidControllers" || type == "FanControllers")
550     {
551         if (createNewObject)
552         {
553             output["Class"] = type == "PidControllers" ? std::string("temp")
554                                                        : std::string("fan");
555             output["Type"] = std::string("Pid");
556         }
557 
558         std::optional<std::vector<nlohmann::json>> zones;
559         std::optional<std::vector<std::string>> inputs;
560         std::optional<std::vector<std::string>> outputs;
561         std::map<std::string, std::optional<double>> doubles;
562         if (!redfish::json_util::readJson(
563                 record, response->res, "Inputs", inputs, "Outputs", outputs,
564                 "Zones", zones, "FFGainCoefficient",
565                 doubles["FFGainCoefficient"], "FFOffCoefficient",
566                 doubles["FFOffCoefficient"], "ICoefficient",
567                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
568                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
569                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
570                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
571                 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos",
572                 doubles["SlewPos"]))
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["ManagerType"] = "BMC";
887         res.jsonValue["UUID"] = uuid;
888         res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
889 
890         res.jsonValue["LogServices"] = {
891             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
892 
893         res.jsonValue["NetworkProtocol"] = {
894             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
895 
896         res.jsonValue["EthernetInterfaces"] = {
897             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
898         // default oem data
899         nlohmann::json& oem = res.jsonValue["Oem"];
900         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
901         oem["@odata.type"] = "#OemManager.Oem";
902         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
903         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
904         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
905         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
906         oemOpenbmc["@odata.context"] =
907             "/redfish/v1/$metadata#OemManager.OpenBmc";
908 
909         // Update Actions object.
910         nlohmann::json& manager_reset =
911             res.jsonValue["Actions"]["#Manager.Reset"];
912         manager_reset["target"] =
913             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
914         manager_reset["ResetType@Redfish.AllowableValues"] = {
915             "GracefulRestart"};
916 
917         res.jsonValue["DateTime"] = getDateTime();
918         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
919 
920         crow::connections::systemBus->async_method_call(
921             [asyncResp](const boost::system::error_code ec,
922                         const dbus::utility::ManagedObjectType& resp) {
923                 if (ec)
924                 {
925                     BMCWEB_LOG_ERROR << "Error while getting Software Version";
926                     messages::internalError(asyncResp->res);
927                     return;
928                 }
929 
930                 for (auto& objpath : resp)
931                 {
932                     for (auto& interface : objpath.second)
933                     {
934                         // If interface is
935                         // xyz.openbmc_project.Software.Version, this is
936                         // what we're looking for.
937                         if (interface.first ==
938                             "xyz.openbmc_project.Software.Version")
939                         {
940                             // Cut out everyting until last "/", ...
941                             const std::string& iface_id = objpath.first;
942                             for (auto& property : interface.second)
943                             {
944                                 if (property.first == "Version")
945                                 {
946                                     const std::string* value =
947                                         sdbusplus::message::variant_ns::get_if<
948                                             std::string>(&property.second);
949                                     if (value == nullptr)
950                                     {
951                                         continue;
952                                     }
953                                     asyncResp->res
954                                         .jsonValue["FirmwareVersion"] = *value;
955                                 }
956                             }
957                         }
958                     }
959                 }
960             },
961             "xyz.openbmc_project.Software.BMC.Updater",
962             "/xyz/openbmc_project/software",
963             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
964         getPidValues(asyncResp);
965     }
966     void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data)
967     {
968 
969         // todo(james): might make sense to do a mapper call here if this
970         // interface gets more traction
971         crow::connections::systemBus->async_method_call(
972             [response,
973              data](const boost::system::error_code ec,
974                    const dbus::utility::ManagedObjectType& managedObj) {
975                 if (ec)
976                 {
977                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
978                     messages::internalError(response->res);
979                     return;
980                 }
981 
982                 // todo(james) mutable doesn't work with asio bindings
983                 nlohmann::json jsonData = data;
984 
985                 std::optional<nlohmann::json> pidControllers;
986                 std::optional<nlohmann::json> fanControllers;
987                 std::optional<nlohmann::json> fanZones;
988                 std::optional<nlohmann::json> stepwiseControllers;
989                 if (!redfish::json_util::readJson(
990                         jsonData, response->res, "PidControllers",
991                         pidControllers, "FanControllers", fanControllers,
992                         "FanZones", fanZones, "StepwiseControllers",
993                         stepwiseControllers))
994                 {
995                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
996                                      << ", Illegal Property "
997                                      << jsonData.dump();
998                     return;
999                 }
1000                 std::array<
1001                     std::pair<const char*, std::optional<nlohmann::json>*>, 4>
1002                     sections = {
1003                         std::make_pair("PidControllers", &pidControllers),
1004                         std::make_pair("FanControllers", &fanControllers),
1005                         std::make_pair("FanZones", &fanZones),
1006                         std::make_pair("StepwiseControllers",
1007                                        &stepwiseControllers)};
1008 
1009                 for (auto& containerPair : sections)
1010                 {
1011                     auto& container = *(containerPair.second);
1012                     if (!container)
1013                     {
1014                         continue;
1015                     }
1016                     const char* type = containerPair.first;
1017 
1018                     for (auto& record : container->items())
1019                     {
1020                         const auto& name = record.key();
1021                         auto pathItr =
1022                             std::find_if(managedObj.begin(), managedObj.end(),
1023                                          [&name](const auto& obj) {
1024                                              return boost::algorithm::ends_with(
1025                                                  obj.first.str, name);
1026                                          });
1027                         boost::container::flat_map<
1028                             std::string, dbus::utility::DbusVariantType>
1029                             output;
1030 
1031                         output.reserve(16); // The pid interface length
1032 
1033                         // determines if we're patching entity-manager or
1034                         // creating a new object
1035                         bool createNewObject = (pathItr == managedObj.end());
1036                         std::string iface;
1037                         if (type == "PidControllers" ||
1038                             type == "FanControllers")
1039                         {
1040                             iface = pidConfigurationIface;
1041                             if (!createNewObject &&
1042                                 pathItr->second.find(pidConfigurationIface) ==
1043                                     pathItr->second.end())
1044                             {
1045                                 createNewObject = true;
1046                             }
1047                         }
1048                         else if (type == "FanZones")
1049                         {
1050                             iface = pidZoneConfigurationIface;
1051                             if (!createNewObject &&
1052                                 pathItr->second.find(
1053                                     pidZoneConfigurationIface) ==
1054                                     pathItr->second.end())
1055                             {
1056 
1057                                 createNewObject = true;
1058                             }
1059                         }
1060                         else if (type == "StepwiseControllers")
1061                         {
1062                             iface = stepwiseConfigurationIface;
1063                             if (!createNewObject &&
1064                                 pathItr->second.find(
1065                                     stepwiseConfigurationIface) ==
1066                                     pathItr->second.end())
1067                             {
1068                                 createNewObject = true;
1069                             }
1070                         }
1071                         output["Name"] =
1072                             boost::replace_all_copy(name, "_", " ");
1073 
1074                         std::string chassis;
1075                         CreatePIDRet ret = createPidInterface(
1076                             response, type, std::move(record.value()),
1077                             pathItr->first.str, managedObj, createNewObject,
1078                             output, chassis);
1079                         if (ret == CreatePIDRet::fail)
1080                         {
1081                             return;
1082                         }
1083                         else if (ret == CreatePIDRet::del)
1084                         {
1085                             continue;
1086                         }
1087 
1088                         if (!createNewObject)
1089                         {
1090                             for (const auto& property : output)
1091                             {
1092                                 crow::connections::systemBus->async_method_call(
1093                                     [response,
1094                                      propertyName{std::string(property.first)}](
1095                                         const boost::system::error_code ec) {
1096                                         if (ec)
1097                                         {
1098                                             BMCWEB_LOG_ERROR
1099                                                 << "Error patching "
1100                                                 << propertyName << ": " << ec;
1101                                             messages::internalError(
1102                                                 response->res);
1103                                         }
1104                                     },
1105                                     "xyz.openbmc_project.EntityManager",
1106                                     pathItr->first.str,
1107                                     "org.freedesktop.DBus.Properties", "Set",
1108                                     iface, property.first, property.second);
1109                             }
1110                         }
1111                         else
1112                         {
1113                             if (chassis.empty())
1114                             {
1115                                 BMCWEB_LOG_ERROR
1116                                     << "Failed to get chassis from config";
1117                                 messages::invalidObject(response->res, name);
1118                                 return;
1119                             }
1120 
1121                             bool foundChassis = false;
1122                             for (const auto& obj : managedObj)
1123                             {
1124                                 if (boost::algorithm::ends_with(obj.first.str,
1125                                                                 chassis))
1126                                 {
1127                                     chassis = obj.first.str;
1128                                     foundChassis = true;
1129                                     break;
1130                                 }
1131                             }
1132                             if (!foundChassis)
1133                             {
1134                                 BMCWEB_LOG_ERROR
1135                                     << "Failed to find chassis on dbus";
1136                                 messages::resourceMissingAtURI(
1137                                     response->res,
1138                                     "/redfish/v1/Chassis/" + chassis);
1139                                 return;
1140                             }
1141 
1142                             crow::connections::systemBus->async_method_call(
1143                                 [response](const boost::system::error_code ec) {
1144                                     if (ec)
1145                                     {
1146                                         BMCWEB_LOG_ERROR
1147                                             << "Error Adding Pid Object " << ec;
1148                                         messages::internalError(response->res);
1149                                     }
1150                                 },
1151                                 "xyz.openbmc_project.EntityManager", chassis,
1152                                 "xyz.openbmc_project.AddObject", "AddObject",
1153                                 output);
1154                         }
1155                     }
1156                 }
1157             },
1158             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
1159             "GetManagedObjects");
1160     }
1161 
1162     void doPatch(crow::Response& res, const crow::Request& req,
1163                  const std::vector<std::string>& params) override
1164     {
1165         std::optional<nlohmann::json> oem;
1166 
1167         if (!json_util::readJson(req, res, "Oem", oem))
1168         {
1169             return;
1170         }
1171 
1172         std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res);
1173 
1174         if (oem)
1175         {
1176             for (const auto& oemLevel : oem->items())
1177             {
1178                 std::optional<nlohmann::json> openbmc;
1179                 if (!redfish::json_util::readJson(*oem, res, "OpenBmc",
1180                                                   openbmc))
1181                 {
1182                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1183                                      << ", Illegal Property " << oem->dump();
1184                     return;
1185                 }
1186                 if (openbmc)
1187                 {
1188                     std::optional<nlohmann::json> fan;
1189                     if (!redfish::json_util::readJson(*openbmc, res, "Fan",
1190                                                       fan))
1191                     {
1192                         BMCWEB_LOG_ERROR << "Line:" << __LINE__
1193                                          << ", Illegal Property "
1194                                          << openbmc->dump();
1195                         return;
1196                     }
1197                     if (fan)
1198                     {
1199                         setPidValues(response, *fan);
1200                     }
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