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