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