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