1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 
20 #include <boost/algorithm/string/replace.hpp>
21 #include <dbus_utility.hpp>
22 
23 namespace redfish
24 {
25 
26 /**
27  * ManagerActionsReset class supports handle POST method for Reset action.
28  * The class retrieves and sends data directly to dbus.
29  */
30 class ManagerActionsReset : public Node
31 {
32   public:
33     ManagerActionsReset(CrowApp& app) :
34         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
35     {
36         entityPrivileges = {
37             {boost::beast::http::verb::get, {{"Login"}}},
38             {boost::beast::http::verb::head, {{"Login"}}},
39             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
40             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
41             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
42             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
43     }
44 
45   private:
46     /**
47      * Function handles POST method request.
48      * Analyzes POST body message before sends Reset request data to dbus.
49      * OpenBMC allows for ResetType is GracefulRestart only.
50      */
51     void doPost(crow::Response& res, const crow::Request& req,
52                 const std::vector<std::string>& params) override
53     {
54         std::string resetType;
55 
56         if (!json_util::readJson(req, res, "ResetType", resetType))
57         {
58             return;
59         }
60 
61         if (resetType != "GracefulRestart")
62         {
63             res.result(boost::beast::http::status::bad_request);
64             messages::actionParameterNotSupported(res, resetType, "ResetType");
65             BMCWEB_LOG_ERROR << "Request incorrect action parameter: "
66                              << resetType;
67             res.end();
68             return;
69         }
70         doBMCGracefulRestart(res, req, params);
71     }
72 
73     /**
74      * Function transceives data with dbus directly.
75      * All BMC state properties will be retrieved before sending reset request.
76      */
77     void doBMCGracefulRestart(crow::Response& res, const crow::Request& req,
78                               const std::vector<std::string>& params)
79     {
80         const char* processName = "xyz.openbmc_project.State.BMC";
81         const char* objectPath = "/xyz/openbmc_project/state/bmc0";
82         const char* interfaceName = "xyz.openbmc_project.State.BMC";
83         const std::string& propertyValue =
84             "xyz.openbmc_project.State.BMC.Transition.Reboot";
85         const char* destProperty = "RequestedBMCTransition";
86 
87         // Create the D-Bus variant for D-Bus call.
88         VariantType dbusPropertyValue(propertyValue);
89 
90         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
91 
92         crow::connections::systemBus->async_method_call(
93             [asyncResp](const boost::system::error_code ec) {
94                 // Use "Set" method to set the property value.
95                 if (ec)
96                 {
97                     BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec;
98                     messages::internalError(asyncResp->res);
99                     return;
100                 }
101 
102                 messages::success(asyncResp->res);
103             },
104             processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
105             interfaceName, destProperty, dbusPropertyValue);
106     }
107 };
108 
109 static constexpr const char* objectManagerIface =
110     "org.freedesktop.DBus.ObjectManager";
111 static constexpr const char* pidConfigurationIface =
112     "xyz.openbmc_project.Configuration.Pid";
113 static constexpr const char* pidZoneConfigurationIface =
114     "xyz.openbmc_project.Configuration.Pid.Zone";
115 static constexpr const char* stepwiseConfigurationIface =
116     "xyz.openbmc_project.Configuration.Stepwise";
117 
118 static void asyncPopulatePid(const std::string& connection,
119                              const std::string& path,
120                              std::shared_ptr<AsyncResp> asyncResp)
121 {
122 
123     crow::connections::systemBus->async_method_call(
124         [asyncResp](const boost::system::error_code ec,
125                     const dbus::utility::ManagedObjectType& managedObj) {
126             if (ec)
127             {
128                 BMCWEB_LOG_ERROR << ec;
129                 asyncResp->res.jsonValue.clear();
130                 messages::internalError(asyncResp->res);
131                 return;
132             }
133             nlohmann::json& configRoot =
134                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
135             nlohmann::json& fans = configRoot["FanControllers"];
136             fans["@odata.type"] = "#OemManager.FanControllers";
137             fans["@odata.context"] =
138                 "/redfish/v1/$metadata#OemManager.FanControllers";
139             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
140                                 "Fan/FanControllers";
141 
142             nlohmann::json& pids = configRoot["PidControllers"];
143             pids["@odata.type"] = "#OemManager.PidControllers";
144             pids["@odata.context"] =
145                 "/redfish/v1/$metadata#OemManager.PidControllers";
146             pids["@odata.id"] =
147                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
148 
149             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
150             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
151             stepwise["@odata.context"] =
152                 "/redfish/v1/$metadata#OemManager.StepwiseControllers";
153             stepwise["@odata.id"] =
154                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
155 
156             nlohmann::json& zones = configRoot["FanZones"];
157             zones["@odata.id"] =
158                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
159             zones["@odata.type"] = "#OemManager.FanZones";
160             zones["@odata.context"] =
161                 "/redfish/v1/$metadata#OemManager.FanZones";
162             configRoot["@odata.id"] =
163                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
164             configRoot["@odata.type"] = "#OemManager.Fan";
165             configRoot["@odata.context"] =
166                 "/redfish/v1/$metadata#OemManager.Fan";
167 
168             bool propertyError = false;
169             for (const auto& pathPair : managedObj)
170             {
171                 for (const auto& intfPair : pathPair.second)
172                 {
173                     if (intfPair.first != pidConfigurationIface &&
174                         intfPair.first != pidZoneConfigurationIface &&
175                         intfPair.first != stepwiseConfigurationIface)
176                     {
177                         continue;
178                     }
179                     auto findName = intfPair.second.find("Name");
180                     if (findName == intfPair.second.end())
181                     {
182                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
183                         messages::internalError(asyncResp->res);
184                         return;
185                     }
186                     const std::string* namePtr =
187                         sdbusplus::message::variant_ns::get_if<std::string>(
188                             &findName->second);
189                     if (namePtr == nullptr)
190                     {
191                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
192                         messages::internalError(asyncResp->res);
193                         return;
194                     }
195 
196                     std::string name = *namePtr;
197                     dbus::utility::escapePathForDbus(name);
198                     nlohmann::json* config = nullptr;
199                     if (intfPair.first == pidZoneConfigurationIface)
200                     {
201                         std::string chassis;
202                         if (!dbus::utility::getNthStringFromPath(
203                                 pathPair.first.str, 5, chassis))
204                         {
205                             chassis = "#IllegalValue";
206                         }
207                         nlohmann::json& zone = zones[name];
208                         zone["Chassis"] = {
209                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
210                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
211                                             "OpenBmc/Fan/FanZones/" +
212                                             name;
213                         zone["@odata.type"] = "#OemManager.FanZone";
214                         zone["@odata.context"] =
215                             "/redfish/v1/$metadata#OemManager.FanZone";
216                         config = &zone;
217                     }
218 
219                     else if (intfPair.first == stepwiseConfigurationIface)
220                     {
221                         nlohmann::json& controller = stepwise[name];
222                         config = &controller;
223 
224                         controller["@odata.id"] =
225                             "/redfish/v1/Managers/bmc#/Oem/"
226                             "OpenBmc/Fan/StepwiseControllers/" +
227                             std::string(name);
228                         controller["@odata.type"] =
229                             "#OemManager.StepwiseController";
230 
231                         controller["@odata.context"] =
232                             "/redfish/v1/"
233                             "$metadata#OemManager.StepwiseController";
234                     }
235 
236                     // pid and fans are off the same configuration
237                     else if (intfPair.first == pidConfigurationIface)
238                     {
239                         const std::string* classPtr = nullptr;
240                         auto findClass = intfPair.second.find("Class");
241                         if (findClass != intfPair.second.end())
242                         {
243                             classPtr = sdbusplus::message::variant_ns::get_if<
244                                 std::string>(&findClass->second);
245                         }
246                         if (classPtr == nullptr)
247                         {
248                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
249                             messages::internalError(asyncResp->res);
250                             return;
251                         }
252                         bool isFan = *classPtr == "fan";
253                         nlohmann::json& element =
254                             isFan ? fans[name] : pids[name];
255                         config = &element;
256                         if (isFan)
257                         {
258                             element["@odata.id"] =
259                                 "/redfish/v1/Managers/bmc#/Oem/"
260                                 "OpenBmc/Fan/FanControllers/" +
261                                 std::string(name);
262                             element["@odata.type"] =
263                                 "#OemManager.FanController";
264 
265                             element["@odata.context"] =
266                                 "/redfish/v1/"
267                                 "$metadata#OemManager.FanController";
268                         }
269                         else
270                         {
271                             element["@odata.id"] =
272                                 "/redfish/v1/Managers/bmc#/Oem/"
273                                 "OpenBmc/Fan/PidControllers/" +
274                                 std::string(name);
275                             element["@odata.type"] =
276                                 "#OemManager.PidController";
277                             element["@odata.context"] =
278                                 "/redfish/v1/$metadata"
279                                 "#OemManager.PidController";
280                         }
281                     }
282                     else
283                     {
284                         BMCWEB_LOG_ERROR << "Unexpected configuration";
285                         messages::internalError(asyncResp->res);
286                         return;
287                     }
288 
289                     // used for making maps out of 2 vectors
290                     const std::vector<double>* keys = nullptr;
291                     const std::vector<double>* values = nullptr;
292 
293                     for (const auto& propertyPair : intfPair.second)
294                     {
295                         if (propertyPair.first == "Type" ||
296                             propertyPair.first == "Class" ||
297                             propertyPair.first == "Name")
298                         {
299                             continue;
300                         }
301 
302                         // zones
303                         if (intfPair.first == pidZoneConfigurationIface)
304                         {
305                             const double* ptr =
306                                 sdbusplus::message::variant_ns::get_if<double>(
307                                     &propertyPair.second);
308                             if (ptr == nullptr)
309                             {
310                                 BMCWEB_LOG_ERROR << "Field Illegal "
311                                                  << propertyPair.first;
312                                 messages::internalError(asyncResp->res);
313                                 return;
314                             }
315                             (*config)[propertyPair.first] = *ptr;
316                         }
317 
318                         if (intfPair.first == stepwiseConfigurationIface)
319                         {
320                             if (propertyPair.first == "Reading" ||
321                                 propertyPair.first == "Output")
322                             {
323                                 const std::vector<double>* ptr =
324                                     sdbusplus::message::variant_ns::get_if<
325                                         std::vector<double>>(
326                                         &propertyPair.second);
327 
328                                 if (ptr == nullptr)
329                                 {
330                                     BMCWEB_LOG_ERROR << "Field Illegal "
331                                                      << propertyPair.first;
332                                     messages::internalError(asyncResp->res);
333                                     return;
334                                 }
335 
336                                 if (propertyPair.first == "Reading")
337                                 {
338                                     keys = ptr;
339                                 }
340                                 else
341                                 {
342                                     values = ptr;
343                                 }
344                                 if (keys && values)
345                                 {
346                                     if (keys->size() != values->size())
347                                     {
348                                         BMCWEB_LOG_ERROR
349                                             << "Reading and Output size don't "
350                                                "match ";
351                                         messages::internalError(asyncResp->res);
352                                         return;
353                                     }
354                                     nlohmann::json& steps = (*config)["Steps"];
355                                     steps = nlohmann::json::array();
356                                     for (size_t ii = 0; ii < keys->size(); ii++)
357                                     {
358                                         steps.push_back(
359                                             {{"Target", (*keys)[ii]},
360                                              {"Output", (*values)[ii]}});
361                                     }
362                                 }
363                             }
364                             if (propertyPair.first == "NegativeHysteresis" ||
365                                 propertyPair.first == "PositiveHysteresis")
366                             {
367                                 const double* ptr =
368                                     sdbusplus::message::variant_ns::get_if<
369                                         double>(&propertyPair.second);
370                                 if (ptr == nullptr)
371                                 {
372                                     BMCWEB_LOG_ERROR << "Field Illegal "
373                                                      << propertyPair.first;
374                                     messages::internalError(asyncResp->res);
375                                     return;
376                                 }
377                                 (*config)[propertyPair.first] = *ptr;
378                             }
379                         }
380 
381                         // pid and fans are off the same configuration
382                         if (intfPair.first == pidConfigurationIface ||
383                             intfPair.first == stepwiseConfigurationIface)
384                         {
385 
386                             if (propertyPair.first == "Zones")
387                             {
388                                 const std::vector<std::string>* inputs =
389                                     sdbusplus::message::variant_ns::get_if<
390                                         std::vector<std::string>>(
391                                         &propertyPair.second);
392 
393                                 if (inputs == nullptr)
394                                 {
395                                     BMCWEB_LOG_ERROR
396                                         << "Zones Pid Field Illegal";
397                                     messages::internalError(asyncResp->res);
398                                     return;
399                                 }
400                                 auto& data = (*config)[propertyPair.first];
401                                 data = nlohmann::json::array();
402                                 for (std::string itemCopy : *inputs)
403                                 {
404                                     dbus::utility::escapePathForDbus(itemCopy);
405                                     data.push_back(
406                                         {{"@odata.id",
407                                           "/redfish/v1/Managers/bmc#/Oem/"
408                                           "OpenBmc/Fan/FanZones/" +
409                                               itemCopy}});
410                                 }
411                             }
412                             // todo(james): may never happen, but this
413                             // assumes configuration data referenced in the
414                             // PID config is provided by the same daemon, we
415                             // could add another loop to cover all cases,
416                             // but I'm okay kicking this can down the road a
417                             // bit
418 
419                             else if (propertyPair.first == "Inputs" ||
420                                      propertyPair.first == "Outputs")
421                             {
422                                 auto& data = (*config)[propertyPair.first];
423                                 const std::vector<std::string>* inputs =
424                                     sdbusplus::message::variant_ns::get_if<
425                                         std::vector<std::string>>(
426                                         &propertyPair.second);
427 
428                                 if (inputs == nullptr)
429                                 {
430                                     BMCWEB_LOG_ERROR << "Field Illegal "
431                                                      << propertyPair.first;
432                                     messages::internalError(asyncResp->res);
433                                     return;
434                                 }
435                                 data = *inputs;
436                             } // doubles
437                             else if (propertyPair.first ==
438                                          "FFGainCoefficient" ||
439                                      propertyPair.first == "FFOffCoefficient" ||
440                                      propertyPair.first == "ICoefficient" ||
441                                      propertyPair.first == "ILimitMax" ||
442                                      propertyPair.first == "ILimitMin" ||
443                                      propertyPair.first == "OutLimitMax" ||
444                                      propertyPair.first == "OutLimitMin" ||
445                                      propertyPair.first == "PCoefficient" ||
446                                      propertyPair.first == "SetPoint" ||
447                                      propertyPair.first == "SlewNeg" ||
448                                      propertyPair.first == "SlewPos")
449                             {
450                                 const double* ptr =
451                                     sdbusplus::message::variant_ns::get_if<
452                                         double>(&propertyPair.second);
453                                 if (ptr == nullptr)
454                                 {
455                                     BMCWEB_LOG_ERROR << "Field Illegal "
456                                                      << propertyPair.first;
457                                     messages::internalError(asyncResp->res);
458                                     return;
459                                 }
460                                 (*config)[propertyPair.first] = *ptr;
461                             }
462                         }
463                     }
464                 }
465             }
466         },
467         connection, path, objectManagerIface, "GetManagedObjects");
468 }
469 
470 enum class CreatePIDRet
471 {
472     fail,
473     del,
474     patch
475 };
476 
477 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response,
478                                 std::vector<nlohmann::json>& config,
479                                 std::vector<std::string>& zones)
480 {
481 
482     for (auto& odata : config)
483     {
484         std::string path;
485         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
486                                           path))
487         {
488             return false;
489         }
490         std::string input;
491         if (!dbus::utility::getNthStringFromPath(path, 4, input))
492         {
493             BMCWEB_LOG_ERROR << "Got invalid path " << path;
494             BMCWEB_LOG_ERROR << "Illegal Type Zones";
495             messages::propertyValueFormatError(response->res, odata.dump(),
496                                                "Zones");
497             return false;
498         }
499         boost::replace_all(input, "_", " ");
500         zones.emplace_back(std::move(input));
501     }
502     return true;
503 }
504 
505 static CreatePIDRet createPidInterface(
506     const std::shared_ptr<AsyncResp>& response, const std::string& type,
507     nlohmann::json&& record, const std::string& path,
508     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
509     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
510         output,
511     std::string& chassis)
512 {
513 
514     // common deleter
515     if (record == nullptr)
516     {
517         std::string iface;
518         if (type == "PidControllers" || type == "FanControllers")
519         {
520             iface = pidConfigurationIface;
521         }
522         else if (type == "FanZones")
523         {
524             iface = pidZoneConfigurationIface;
525         }
526         else if (type == "StepwiseControllers")
527         {
528             iface = stepwiseConfigurationIface;
529         }
530         else
531         {
532             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
533                              << type;
534             messages::propertyUnknown(response->res, type);
535             return CreatePIDRet::fail;
536         }
537         // delete interface
538         crow::connections::systemBus->async_method_call(
539             [response, path](const boost::system::error_code ec) {
540                 if (ec)
541                 {
542                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
543                     messages::internalError(response->res);
544                 }
545             },
546             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
547         return CreatePIDRet::del;
548     }
549 
550     if (type == "PidControllers" || type == "FanControllers")
551     {
552         if (createNewObject)
553         {
554             output["Class"] = type == "PidControllers" ? std::string("temp")
555                                                        : std::string("fan");
556             output["Type"] = std::string("Pid");
557         }
558 
559         std::optional<std::vector<nlohmann::json>> zones;
560         std::optional<std::vector<std::string>> inputs;
561         std::optional<std::vector<std::string>> outputs;
562         std::map<std::string, std::optional<double>> doubles;
563         if (!redfish::json_util::readJson(
564                 record, response->res, "Inputs", inputs, "Outputs", outputs,
565                 "Zones", zones, "FFGainCoefficient",
566                 doubles["FFGainCoefficient"], "FFOffCoefficient",
567                 doubles["FFOffCoefficient"], "ICoefficient",
568                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
569                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
570                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
571                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
572                 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos",
573                 doubles["SlewPos"]))
574         {
575             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
576                              << record.dump();
577             return CreatePIDRet::fail;
578         }
579         if (zones)
580         {
581             std::vector<std::string> zonesStr;
582             if (!getZonesFromJsonReq(response, *zones, zonesStr))
583             {
584                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
585                 return CreatePIDRet::fail;
586             }
587             output["Zones"] = std::move(zonesStr);
588         }
589         if (inputs || outputs)
590         {
591             std::array<std::optional<std::vector<std::string>>*, 2> containers =
592                 {&inputs, &outputs};
593             size_t index = 0;
594             for (const auto& containerPtr : containers)
595             {
596                 std::optional<std::vector<std::string>>& container =
597                     *containerPtr;
598                 if (!container)
599                 {
600                     index++;
601                     continue;
602                 }
603 
604                 for (std::string& value : *container)
605                 {
606 
607                     // try to find the sensor in the
608                     // configuration
609                     if (chassis.empty())
610                     {
611                         std::string escaped =
612                             boost::replace_all_copy(value, " ", "_");
613                         std::find_if(
614                             managedObj.begin(), managedObj.end(),
615                             [&chassis, &escaped](const auto& obj) {
616                                 if (boost::algorithm::ends_with(obj.first.str,
617                                                                 escaped))
618                                 {
619                                     return dbus::utility::getNthStringFromPath(
620                                         obj.first.str, 5, chassis);
621                                 }
622                                 return false;
623                             });
624                     }
625                     boost::replace_all(value, "_", " ");
626                 }
627                 std::string key;
628                 if (index == 0)
629                 {
630                     key = "Inputs";
631                 }
632                 else
633                 {
634                     key = "Outputs";
635                 }
636                 output[key] = *container;
637                 index++;
638             }
639         }
640 
641         // doubles
642         for (const auto& pairs : doubles)
643         {
644             if (!pairs.second)
645             {
646                 continue;
647             }
648             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
649             output[pairs.first] = *(pairs.second);
650         }
651     }
652 
653     else if (type == "FanZones")
654     {
655         output["Type"] = std::string("Pid.Zone");
656 
657         std::optional<nlohmann::json> chassisContainer;
658         std::optional<double> failSafePercent;
659         std::optional<double> minThermalRpm;
660         if (!redfish::json_util::readJson(record, response->res, "Chassis",
661                                           chassisContainer, "FailSafePercent",
662                                           failSafePercent, "MinThermalRpm",
663                                           minThermalRpm))
664         {
665             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
666                              << record.dump();
667             return CreatePIDRet::fail;
668         }
669 
670         if (chassisContainer)
671         {
672 
673             std::string chassisId;
674             if (!redfish::json_util::readJson(*chassisContainer, response->res,
675                                               "@odata.id", chassisId))
676             {
677                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
678                                  << chassisContainer->dump();
679                 return CreatePIDRet::fail;
680             }
681 
682             // /refish/v1/chassis/chassis_name/
683             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
684             {
685                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
686                 messages::invalidObject(response->res, chassisId);
687                 return CreatePIDRet::fail;
688             }
689         }
690         if (minThermalRpm)
691         {
692             output["MinThermalRpm"] = *minThermalRpm;
693         }
694         if (failSafePercent)
695         {
696             output["FailSafePercent"] = *failSafePercent;
697         }
698     }
699     else if (type == "StepwiseControllers")
700     {
701         output["Type"] = std::string("Stepwise");
702 
703         std::optional<std::vector<nlohmann::json>> zones;
704         std::optional<std::vector<nlohmann::json>> steps;
705         std::optional<std::vector<std::string>> inputs;
706         std::optional<double> positiveHysteresis;
707         std::optional<double> negativeHysteresis;
708         if (!redfish::json_util::readJson(
709                 record, response->res, "Zones", zones, "Steps", steps, "Inputs",
710                 inputs, "PositiveHysteresis", positiveHysteresis,
711                 "NegativeHysteresis", negativeHysteresis))
712         {
713             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
714                              << record.dump();
715             return CreatePIDRet::fail;
716         }
717 
718         if (zones)
719         {
720             std::vector<std::string> zoneStrs;
721             if (!getZonesFromJsonReq(response, *zones, zoneStrs))
722             {
723                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
724                 return CreatePIDRet::fail;
725             }
726             output["Zones"] = std::move(zoneStrs);
727         }
728         if (steps)
729         {
730             std::vector<double> readings;
731             std::vector<double> outputs;
732             for (auto& step : *steps)
733             {
734                 double target;
735                 double output;
736 
737                 if (!redfish::json_util::readJson(step, response->res, "Target",
738                                                   target, "Output", output))
739                 {
740                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
741                                      << ", Illegal Property " << record.dump();
742                     return CreatePIDRet::fail;
743                 }
744                 readings.emplace_back(target);
745                 outputs.emplace_back(output);
746             }
747             output["Reading"] = std::move(readings);
748             output["Output"] = std::move(outputs);
749         }
750         if (inputs)
751         {
752             for (std::string& value : *inputs)
753             {
754                 if (chassis.empty())
755                 {
756                     std::string escaped =
757                         boost::replace_all_copy(value, " ", "_");
758                     std::find_if(
759                         managedObj.begin(), managedObj.end(),
760                         [&chassis, &escaped](const auto& obj) {
761                             if (boost::algorithm::ends_with(obj.first.str,
762                                                             escaped))
763                             {
764                                 return dbus::utility::getNthStringFromPath(
765                                     obj.first.str, 5, chassis);
766                             }
767                             return false;
768                         });
769                 }
770                 boost::replace_all(value, "_", " ");
771             }
772             output["Inputs"] = std::move(*inputs);
773         }
774         if (negativeHysteresis)
775         {
776             output["NegativeHysteresis"] = *negativeHysteresis;
777         }
778         if (positiveHysteresis)
779         {
780             output["PositiveHysteresis"] = *positiveHysteresis;
781         }
782     }
783     else
784     {
785         BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type;
786         messages::propertyUnknown(response->res, type);
787         return CreatePIDRet::fail;
788     }
789     return CreatePIDRet::patch;
790 }
791 
792 class Manager : public Node
793 {
794   public:
795     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
796     {
797         uuid = app.template getMiddleware<crow::persistent_data::Middleware>()
798                    .systemUuid;
799         entityPrivileges = {
800             {boost::beast::http::verb::get, {{"Login"}}},
801             {boost::beast::http::verb::head, {{"Login"}}},
802             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
803             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
804             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
805             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
806     }
807 
808   private:
809     void getPidValues(std::shared_ptr<AsyncResp> asyncResp)
810     {
811         crow::connections::systemBus->async_method_call(
812             [asyncResp](const boost::system::error_code ec,
813                         const crow::openbmc_mapper::GetSubTreeType& subtree) {
814                 if (ec)
815                 {
816                     BMCWEB_LOG_ERROR << ec;
817                     messages::internalError(asyncResp->res);
818                     return;
819                 }
820 
821                 // create map of <connection, path to objMgr>>
822                 boost::container::flat_map<std::string, std::string>
823                     objectMgrPaths;
824                 boost::container::flat_set<std::string> calledConnections;
825                 for (const auto& pathGroup : subtree)
826                 {
827                     for (const auto& connectionGroup : pathGroup.second)
828                     {
829                         auto findConnection =
830                             calledConnections.find(connectionGroup.first);
831                         if (findConnection != calledConnections.end())
832                         {
833                             break;
834                         }
835                         for (const std::string& interface :
836                              connectionGroup.second)
837                         {
838                             if (interface == objectManagerIface)
839                             {
840                                 objectMgrPaths[connectionGroup.first] =
841                                     pathGroup.first;
842                             }
843                             // this list is alphabetical, so we
844                             // should have found the objMgr by now
845                             if (interface == pidConfigurationIface ||
846                                 interface == pidZoneConfigurationIface ||
847                                 interface == stepwiseConfigurationIface)
848                             {
849                                 auto findObjMgr =
850                                     objectMgrPaths.find(connectionGroup.first);
851                                 if (findObjMgr == objectMgrPaths.end())
852                                 {
853                                     BMCWEB_LOG_DEBUG << connectionGroup.first
854                                                      << "Has no Object Manager";
855                                     continue;
856                                 }
857 
858                                 calledConnections.insert(connectionGroup.first);
859 
860                                 asyncPopulatePid(findObjMgr->first,
861                                                  findObjMgr->second, asyncResp);
862                                 break;
863                             }
864                         }
865                     }
866                 }
867             },
868             "xyz.openbmc_project.ObjectMapper",
869             "/xyz/openbmc_project/object_mapper",
870             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
871             std::array<const char*, 4>{
872                 pidConfigurationIface, pidZoneConfigurationIface,
873                 objectManagerIface, stepwiseConfigurationIface});
874     }
875 
876     void doGet(crow::Response& res, const crow::Request& req,
877                const std::vector<std::string>& params) override
878     {
879         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
880         res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager";
881         res.jsonValue["@odata.context"] =
882             "/redfish/v1/$metadata#Manager.Manager";
883         res.jsonValue["Id"] = "bmc";
884         res.jsonValue["Name"] = "OpenBmc Manager";
885         res.jsonValue["Description"] = "Baseboard Management Controller";
886         res.jsonValue["PowerState"] = "On";
887         res.jsonValue["ManagerType"] = "BMC";
888         res.jsonValue["UUID"] = uuid;
889         res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
890 
891         res.jsonValue["LogServices"] = {
892             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
893 
894         res.jsonValue["NetworkProtocol"] = {
895             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
896 
897         res.jsonValue["EthernetInterfaces"] = {
898             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
899         // default oem data
900         nlohmann::json& oem = res.jsonValue["Oem"];
901         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
902         oem["@odata.type"] = "#OemManager.Oem";
903         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
904         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
905         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
906         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
907         oemOpenbmc["@odata.context"] =
908             "/redfish/v1/$metadata#OemManager.OpenBmc";
909 
910         // Update Actions object.
911         nlohmann::json& manager_reset =
912             res.jsonValue["Actions"]["#Manager.Reset"];
913         manager_reset["target"] =
914             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
915         manager_reset["ResetType@Redfish.AllowableValues"] = {
916             "GracefulRestart"};
917 
918         res.jsonValue["DateTime"] = getDateTime();
919         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                                         sdbusplus::message::variant_ns::get_if<
949                                             std::string>(&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