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 
505         // 8 below comes from
506         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
507         //     0    1     2      3    4    5      6     7      8
508         if (!dbus::utility::getNthStringFromPath(path, 8, input))
509         {
510             BMCWEB_LOG_ERROR << "Got invalid path " << path;
511             BMCWEB_LOG_ERROR << "Illegal Type Zones";
512             messages::propertyValueFormatError(response->res, odata.dump(),
513                                                "Zones");
514             return false;
515         }
516         boost::replace_all(input, "_", " ");
517         zones.emplace_back(std::move(input));
518     }
519     return true;
520 }
521 
522 static bool findChassis(const dbus::utility::ManagedObjectType& managedObj,
523                         const std::string& value, std::string& chassis)
524 {
525     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
526 
527     std::string escaped = boost::replace_all_copy(value, " ", "_");
528     escaped = "/" + escaped;
529     auto it = std::find_if(
530         managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) {
531             if (boost::algorithm::ends_with(obj.first.str, escaped))
532             {
533                 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n";
534                 return true;
535             }
536             return false;
537         });
538 
539     if (it == managedObj.end())
540     {
541         return false;
542     }
543     // 5 comes from <chassis-name> being the 5th element
544     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
545     return dbus::utility::getNthStringFromPath(it->first.str, 5, chassis);
546 }
547 
548 static CreatePIDRet createPidInterface(
549     const std::shared_ptr<AsyncResp>& response, const std::string& type,
550     nlohmann::json::iterator it, const std::string& path,
551     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
552     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
553         output,
554     std::string& chassis)
555 {
556 
557     // common deleter
558     if (it.value() == nullptr)
559     {
560         std::string iface;
561         if (type == "PidControllers" || type == "FanControllers")
562         {
563             iface = pidConfigurationIface;
564         }
565         else if (type == "FanZones")
566         {
567             iface = pidZoneConfigurationIface;
568         }
569         else if (type == "StepwiseControllers")
570         {
571             iface = stepwiseConfigurationIface;
572         }
573         else
574         {
575             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
576                              << type;
577             messages::propertyUnknown(response->res, type);
578             return CreatePIDRet::fail;
579         }
580         // delete interface
581         crow::connections::systemBus->async_method_call(
582             [response, path](const boost::system::error_code ec) {
583                 if (ec)
584                 {
585                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
586                     messages::internalError(response->res);
587                     return;
588                 }
589                 messages::success(response->res);
590             },
591             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
592         return CreatePIDRet::del;
593     }
594 
595     if (!createNewObject)
596     {
597         // if we aren't creating a new object, we should be able to find it on
598         // d-bus
599         if (!findChassis(managedObj, it.key(), chassis))
600         {
601             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
602             messages::invalidObject(response->res, it.key());
603             return CreatePIDRet::fail;
604         }
605     }
606 
607     if (type == "PidControllers" || type == "FanControllers")
608     {
609         if (createNewObject)
610         {
611             output["Class"] = type == "PidControllers" ? std::string("temp")
612                                                        : std::string("fan");
613             output["Type"] = std::string("Pid");
614         }
615 
616         std::optional<std::vector<nlohmann::json>> zones;
617         std::optional<std::vector<std::string>> inputs;
618         std::optional<std::vector<std::string>> outputs;
619         std::map<std::string, std::optional<double>> doubles;
620         if (!redfish::json_util::readJson(
621                 it.value(), response->res, "Inputs", inputs, "Outputs", outputs,
622                 "Zones", zones, "FFGainCoefficient",
623                 doubles["FFGainCoefficient"], "FFOffCoefficient",
624                 doubles["FFOffCoefficient"], "ICoefficient",
625                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
626                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
627                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
628                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
629                 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos",
630                 doubles["SlewPos"], "PositiveHysteresis",
631                 doubles["PositiveHysteresis"], "NegativeHysteresis",
632                 doubles["NegativeHysteresis"]))
633         {
634             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
635                              << it.value().dump();
636             return CreatePIDRet::fail;
637         }
638         if (zones)
639         {
640             std::vector<std::string> zonesStr;
641             if (!getZonesFromJsonReq(response, *zones, zonesStr))
642             {
643                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
644                 return CreatePIDRet::fail;
645             }
646             if (chassis.empty() &&
647                 !findChassis(managedObj, zonesStr[0], chassis))
648             {
649                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
650                 messages::invalidObject(response->res, it.key());
651                 return CreatePIDRet::fail;
652             }
653 
654             output["Zones"] = std::move(zonesStr);
655         }
656         if (inputs || outputs)
657         {
658             std::array<std::optional<std::vector<std::string>>*, 2> containers =
659                 {&inputs, &outputs};
660             size_t index = 0;
661             for (const auto& containerPtr : containers)
662             {
663                 std::optional<std::vector<std::string>>& container =
664                     *containerPtr;
665                 if (!container)
666                 {
667                     index++;
668                     continue;
669                 }
670 
671                 for (std::string& value : *container)
672                 {
673                     boost::replace_all(value, "_", " ");
674                 }
675                 std::string key;
676                 if (index == 0)
677                 {
678                     key = "Inputs";
679                 }
680                 else
681                 {
682                     key = "Outputs";
683                 }
684                 output[key] = *container;
685                 index++;
686             }
687         }
688 
689         // doubles
690         for (const auto& pairs : doubles)
691         {
692             if (!pairs.second)
693             {
694                 continue;
695             }
696             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
697             output[pairs.first] = *(pairs.second);
698         }
699     }
700 
701     else if (type == "FanZones")
702     {
703         output["Type"] = std::string("Pid.Zone");
704 
705         std::optional<nlohmann::json> chassisContainer;
706         std::optional<double> failSafePercent;
707         std::optional<double> minThermalOutput;
708         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
709                                           chassisContainer, "FailSafePercent",
710                                           failSafePercent, "MinThermalOutput",
711                                           minThermalOutput))
712         {
713             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
714                              << it.value().dump();
715             return CreatePIDRet::fail;
716         }
717 
718         if (chassisContainer)
719         {
720 
721             std::string chassisId;
722             if (!redfish::json_util::readJson(*chassisContainer, response->res,
723                                               "@odata.id", chassisId))
724             {
725                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
726                                  << chassisContainer->dump();
727                 return CreatePIDRet::fail;
728             }
729 
730             // /refish/v1/chassis/chassis_name/
731             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
732             {
733                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
734                 messages::invalidObject(response->res, chassisId);
735                 return CreatePIDRet::fail;
736             }
737         }
738         if (minThermalOutput)
739         {
740             output["MinThermalOutput"] = *minThermalOutput;
741         }
742         if (failSafePercent)
743         {
744             output["FailSafePercent"] = *failSafePercent;
745         }
746     }
747     else if (type == "StepwiseControllers")
748     {
749         output["Type"] = std::string("Stepwise");
750 
751         std::optional<std::vector<nlohmann::json>> zones;
752         std::optional<std::vector<nlohmann::json>> steps;
753         std::optional<std::vector<std::string>> inputs;
754         std::optional<double> positiveHysteresis;
755         std::optional<double> negativeHysteresis;
756         std::optional<std::string> direction; // upper clipping curve vs lower
757         if (!redfish::json_util::readJson(
758                 it.value(), response->res, "Zones", zones, "Steps", steps,
759                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
760                 "NegativeHysteresis", negativeHysteresis, "Direction",
761                 direction))
762         {
763             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
764                              << it.value().dump();
765             return CreatePIDRet::fail;
766         }
767 
768         if (zones)
769         {
770             std::vector<std::string> zonesStrs;
771             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
772             {
773                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
774                 return CreatePIDRet::fail;
775             }
776             if (chassis.empty() &&
777                 !findChassis(managedObj, zonesStrs[0], chassis))
778             {
779                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
780                 messages::invalidObject(response->res, it.key());
781                 return CreatePIDRet::fail;
782             }
783             output["Zones"] = std::move(zonesStrs);
784         }
785         if (steps)
786         {
787             std::vector<double> readings;
788             std::vector<double> outputs;
789             for (auto& step : *steps)
790             {
791                 double target;
792                 double output;
793 
794                 if (!redfish::json_util::readJson(step, response->res, "Target",
795                                                   target, "Output", output))
796                 {
797                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
798                                      << ", Illegal Property "
799                                      << it.value().dump();
800                     return CreatePIDRet::fail;
801                 }
802                 readings.emplace_back(target);
803                 outputs.emplace_back(output);
804             }
805             output["Reading"] = std::move(readings);
806             output["Output"] = std::move(outputs);
807         }
808         if (inputs)
809         {
810             for (std::string& value : *inputs)
811             {
812                 boost::replace_all(value, "_", " ");
813             }
814             output["Inputs"] = std::move(*inputs);
815         }
816         if (negativeHysteresis)
817         {
818             output["NegativeHysteresis"] = *negativeHysteresis;
819         }
820         if (positiveHysteresis)
821         {
822             output["PositiveHysteresis"] = *positiveHysteresis;
823         }
824         if (direction)
825         {
826             constexpr const std::array<const char*, 2> allowedDirections = {
827                 "Ceiling", "Floor"};
828             if (std::find(allowedDirections.begin(), allowedDirections.end(),
829                           *direction) == allowedDirections.end())
830             {
831                 messages::propertyValueTypeError(response->res, "Direction",
832                                                  *direction);
833                 return CreatePIDRet::fail;
834             }
835             output["Class"] = *direction;
836         }
837     }
838     else
839     {
840         BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type;
841         messages::propertyUnknown(response->res, type);
842         return CreatePIDRet::fail;
843     }
844     return CreatePIDRet::patch;
845 }
846 
847 class Manager : public Node
848 {
849   public:
850     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
851     {
852         uuid = app.template getMiddleware<crow::persistent_data::Middleware>()
853                    .systemUuid;
854         entityPrivileges = {
855             {boost::beast::http::verb::get, {{"Login"}}},
856             {boost::beast::http::verb::head, {{"Login"}}},
857             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
858             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
859             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
860             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
861     }
862 
863   private:
864     void getPidValues(std::shared_ptr<AsyncResp> asyncResp)
865     {
866         crow::connections::systemBus->async_method_call(
867             [asyncResp](const boost::system::error_code ec,
868                         const crow::openbmc_mapper::GetSubTreeType& subtree) {
869                 if (ec)
870                 {
871                     BMCWEB_LOG_ERROR << ec;
872                     messages::internalError(asyncResp->res);
873                     return;
874                 }
875 
876                 // create map of <connection, path to objMgr>>
877                 boost::container::flat_map<std::string, std::string>
878                     objectMgrPaths;
879                 boost::container::flat_set<std::string> calledConnections;
880                 for (const auto& pathGroup : subtree)
881                 {
882                     for (const auto& connectionGroup : pathGroup.second)
883                     {
884                         auto findConnection =
885                             calledConnections.find(connectionGroup.first);
886                         if (findConnection != calledConnections.end())
887                         {
888                             break;
889                         }
890                         for (const std::string& interface :
891                              connectionGroup.second)
892                         {
893                             if (interface == objectManagerIface)
894                             {
895                                 objectMgrPaths[connectionGroup.first] =
896                                     pathGroup.first;
897                             }
898                             // this list is alphabetical, so we
899                             // should have found the objMgr by now
900                             if (interface == pidConfigurationIface ||
901                                 interface == pidZoneConfigurationIface ||
902                                 interface == stepwiseConfigurationIface)
903                             {
904                                 auto findObjMgr =
905                                     objectMgrPaths.find(connectionGroup.first);
906                                 if (findObjMgr == objectMgrPaths.end())
907                                 {
908                                     BMCWEB_LOG_DEBUG << connectionGroup.first
909                                                      << "Has no Object Manager";
910                                     continue;
911                                 }
912 
913                                 calledConnections.insert(connectionGroup.first);
914 
915                                 asyncPopulatePid(findObjMgr->first,
916                                                  findObjMgr->second, asyncResp);
917                                 break;
918                             }
919                         }
920                     }
921                 }
922             },
923             "xyz.openbmc_project.ObjectMapper",
924             "/xyz/openbmc_project/object_mapper",
925             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
926             std::array<const char*, 4>{
927                 pidConfigurationIface, pidZoneConfigurationIface,
928                 objectManagerIface, stepwiseConfigurationIface});
929     }
930 
931     void doGet(crow::Response& res, const crow::Request& req,
932                const std::vector<std::string>& params) override
933     {
934         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
935         res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager";
936         res.jsonValue["@odata.context"] =
937             "/redfish/v1/$metadata#Manager.Manager";
938         res.jsonValue["Id"] = "bmc";
939         res.jsonValue["Name"] = "OpenBmc Manager";
940         res.jsonValue["Description"] = "Baseboard Management Controller";
941         res.jsonValue["PowerState"] = "On";
942         res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}};
943         res.jsonValue["ManagerType"] = "BMC";
944         res.jsonValue["UUID"] = uuid;
945         res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
946 
947         res.jsonValue["LogServices"] = {
948             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
949 
950         res.jsonValue["NetworkProtocol"] = {
951             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
952 
953         res.jsonValue["EthernetInterfaces"] = {
954             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
955         // default oem data
956         nlohmann::json& oem = res.jsonValue["Oem"];
957         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
958         oem["@odata.type"] = "#OemManager.Oem";
959         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
960         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
961         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
962         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
963         oemOpenbmc["@odata.context"] =
964             "/redfish/v1/$metadata#OemManager.OpenBmc";
965 
966         // Update Actions object.
967         nlohmann::json& manager_reset =
968             res.jsonValue["Actions"]["#Manager.Reset"];
969         manager_reset["target"] =
970             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
971         manager_reset["ResetType@Redfish.AllowableValues"] = {
972             "GracefulRestart"};
973 
974         res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
975 
976         // Fill in GraphicalConsole and SerialConsole info
977         res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
978         res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI",
979                                                                    "SSH"};
980         // TODO (Santosh) : Uncomment when KVM support is in.
981         // res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true;
982         // res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] =
983         //    {"KVMIP"};
984 
985         res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
986         res.jsonValue["Links"]["ManagerForServers"] = {
987             {{"@odata.id", "/redfish/v1/Systems/system"}}};
988 #ifdef BMCWEB_ENABLE_REDFISH_ONE_CHASSIS
989         res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1;
990         res.jsonValue["Links"]["ManagerForChassis"] = {
991             {{"@odata.id", "/redfish/v1/Chassis/chassis"}}};
992 #endif
993         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
994 
995         crow::connections::systemBus->async_method_call(
996             [asyncResp](const boost::system::error_code ec,
997                         const dbus::utility::ManagedObjectType& resp) {
998                 if (ec)
999                 {
1000                     BMCWEB_LOG_ERROR << "Error while getting Software Version";
1001                     messages::internalError(asyncResp->res);
1002                     return;
1003                 }
1004 
1005                 for (auto& objpath : resp)
1006                 {
1007                     for (auto& interface : objpath.second)
1008                     {
1009                         // If interface is
1010                         // xyz.openbmc_project.Software.Version, this is
1011                         // what we're looking for.
1012                         if (interface.first ==
1013                             "xyz.openbmc_project.Software.Version")
1014                         {
1015                             // Cut out everyting until last "/", ...
1016                             for (auto& property : interface.second)
1017                             {
1018                                 if (property.first == "Version")
1019                                 {
1020                                     const std::string* value =
1021                                         std::get_if<std::string>(
1022                                             &property.second);
1023                                     if (value == nullptr)
1024                                     {
1025                                         continue;
1026                                     }
1027                                     asyncResp->res
1028                                         .jsonValue["FirmwareVersion"] = *value;
1029                                 }
1030                             }
1031                         }
1032                     }
1033                 }
1034             },
1035             "xyz.openbmc_project.Software.BMC.Updater",
1036             "/xyz/openbmc_project/software",
1037             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1038         getPidValues(asyncResp);
1039     }
1040     void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data)
1041     {
1042 
1043         // todo(james): might make sense to do a mapper call here if this
1044         // interface gets more traction
1045         crow::connections::systemBus->async_method_call(
1046             [response,
1047              data](const boost::system::error_code ec,
1048                    const dbus::utility::ManagedObjectType& managedObj) {
1049                 if (ec)
1050                 {
1051                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
1052                     messages::internalError(response->res);
1053                     return;
1054                 }
1055 
1056                 // todo(james) mutable doesn't work with asio bindings
1057                 nlohmann::json jsonData = data;
1058 
1059                 std::optional<nlohmann::json> pidControllers;
1060                 std::optional<nlohmann::json> fanControllers;
1061                 std::optional<nlohmann::json> fanZones;
1062                 std::optional<nlohmann::json> stepwiseControllers;
1063                 if (!redfish::json_util::readJson(
1064                         jsonData, response->res, "PidControllers",
1065                         pidControllers, "FanControllers", fanControllers,
1066                         "FanZones", fanZones, "StepwiseControllers",
1067                         stepwiseControllers))
1068                 {
1069                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1070                                      << ", Illegal Property "
1071                                      << jsonData.dump();
1072                     return;
1073                 }
1074                 std::array<
1075                     std::pair<std::string, std::optional<nlohmann::json>*>, 4>
1076                     sections = {
1077                         std::make_pair("PidControllers", &pidControllers),
1078                         std::make_pair("FanControllers", &fanControllers),
1079                         std::make_pair("FanZones", &fanZones),
1080                         std::make_pair("StepwiseControllers",
1081                                        &stepwiseControllers)};
1082 
1083                 for (auto& containerPair : sections)
1084                 {
1085                     auto& container = *(containerPair.second);
1086                     if (!container)
1087                     {
1088                         continue;
1089                     }
1090                     std::string& type = containerPair.first;
1091 
1092                     for (nlohmann::json::iterator it = container->begin();
1093                          it != container->end(); it++)
1094                     {
1095                         const auto& name = it.key();
1096                         auto pathItr =
1097                             std::find_if(managedObj.begin(), managedObj.end(),
1098                                          [&name](const auto& obj) {
1099                                              return boost::algorithm::ends_with(
1100                                                  obj.first.str, "/" + name);
1101                                          });
1102                         boost::container::flat_map<
1103                             std::string, dbus::utility::DbusVariantType>
1104                             output;
1105 
1106                         output.reserve(16); // The pid interface length
1107 
1108                         // determines if we're patching entity-manager or
1109                         // creating a new object
1110                         bool createNewObject = (pathItr == managedObj.end());
1111                         std::string iface;
1112                         if (type == "PidControllers" ||
1113                             type == "FanControllers")
1114                         {
1115                             iface = pidConfigurationIface;
1116                             if (!createNewObject &&
1117                                 pathItr->second.find(pidConfigurationIface) ==
1118                                     pathItr->second.end())
1119                             {
1120                                 createNewObject = true;
1121                             }
1122                         }
1123                         else if (type == "FanZones")
1124                         {
1125                             iface = pidZoneConfigurationIface;
1126                             if (!createNewObject &&
1127                                 pathItr->second.find(
1128                                     pidZoneConfigurationIface) ==
1129                                     pathItr->second.end())
1130                             {
1131 
1132                                 createNewObject = true;
1133                             }
1134                         }
1135                         else if (type == "StepwiseControllers")
1136                         {
1137                             iface = stepwiseConfigurationIface;
1138                             if (!createNewObject &&
1139                                 pathItr->second.find(
1140                                     stepwiseConfigurationIface) ==
1141                                     pathItr->second.end())
1142                             {
1143                                 createNewObject = true;
1144                             }
1145                         }
1146                         BMCWEB_LOG_DEBUG << "Create new = " << createNewObject
1147                                          << "\n";
1148                         output["Name"] =
1149                             boost::replace_all_copy(name, "_", " ");
1150 
1151                         std::string chassis;
1152                         CreatePIDRet ret = createPidInterface(
1153                             response, type, it, pathItr->first.str, managedObj,
1154                             createNewObject, output, chassis);
1155                         if (ret == CreatePIDRet::fail)
1156                         {
1157                             return;
1158                         }
1159                         else if (ret == CreatePIDRet::del)
1160                         {
1161                             continue;
1162                         }
1163 
1164                         if (!createNewObject)
1165                         {
1166                             for (const auto& property : output)
1167                             {
1168                                 crow::connections::systemBus->async_method_call(
1169                                     [response,
1170                                      propertyName{std::string(property.first)}](
1171                                         const boost::system::error_code ec) {
1172                                         if (ec)
1173                                         {
1174                                             BMCWEB_LOG_ERROR
1175                                                 << "Error patching "
1176                                                 << propertyName << ": " << ec;
1177                                             messages::internalError(
1178                                                 response->res);
1179                                             return;
1180                                         }
1181                                         messages::success(response->res);
1182                                     },
1183                                     "xyz.openbmc_project.EntityManager",
1184                                     pathItr->first.str,
1185                                     "org.freedesktop.DBus.Properties", "Set",
1186                                     iface, property.first, property.second);
1187                             }
1188                         }
1189                         else
1190                         {
1191                             if (chassis.empty())
1192                             {
1193                                 BMCWEB_LOG_ERROR
1194                                     << "Failed to get chassis from config";
1195                                 messages::invalidObject(response->res, name);
1196                                 return;
1197                             }
1198 
1199                             bool foundChassis = false;
1200                             for (const auto& obj : managedObj)
1201                             {
1202                                 if (boost::algorithm::ends_with(obj.first.str,
1203                                                                 chassis))
1204                                 {
1205                                     chassis = obj.first.str;
1206                                     foundChassis = true;
1207                                     break;
1208                                 }
1209                             }
1210                             if (!foundChassis)
1211                             {
1212                                 BMCWEB_LOG_ERROR
1213                                     << "Failed to find chassis on dbus";
1214                                 messages::resourceMissingAtURI(
1215                                     response->res,
1216                                     "/redfish/v1/Chassis/" + chassis);
1217                                 return;
1218                             }
1219 
1220                             crow::connections::systemBus->async_method_call(
1221                                 [response](const boost::system::error_code ec) {
1222                                     if (ec)
1223                                     {
1224                                         BMCWEB_LOG_ERROR
1225                                             << "Error Adding Pid Object " << ec;
1226                                         messages::internalError(response->res);
1227                                         return;
1228                                     }
1229                                     messages::success(response->res);
1230                                 },
1231                                 "xyz.openbmc_project.EntityManager", chassis,
1232                                 "xyz.openbmc_project.AddObject", "AddObject",
1233                                 output);
1234                         }
1235                     }
1236                 }
1237             },
1238             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
1239             "GetManagedObjects");
1240     }
1241 
1242     void doPatch(crow::Response& res, const crow::Request& req,
1243                  const std::vector<std::string>& params) override
1244     {
1245         std::optional<nlohmann::json> oem;
1246 
1247         if (!json_util::readJson(req, res, "Oem", oem))
1248         {
1249             return;
1250         }
1251 
1252         std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res);
1253 
1254         if (oem)
1255         {
1256             std::optional<nlohmann::json> openbmc;
1257             if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc))
1258             {
1259                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1260                                  << oem->dump();
1261                 return;
1262             }
1263             if (openbmc)
1264             {
1265                 std::optional<nlohmann::json> fan;
1266                 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan))
1267                 {
1268                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1269                                      << ", Illegal Property "
1270                                      << openbmc->dump();
1271                     return;
1272                 }
1273                 if (fan)
1274                 {
1275                     setPidValues(response, *fan);
1276                 }
1277             }
1278         }
1279     }
1280 
1281     std::string uuid;
1282 };
1283 
1284 class ManagerCollection : public Node
1285 {
1286   public:
1287     ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
1288     {
1289         entityPrivileges = {
1290             {boost::beast::http::verb::get, {{"Login"}}},
1291             {boost::beast::http::verb::head, {{"Login"}}},
1292             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1293             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1294             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1295             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1296     }
1297 
1298   private:
1299     void doGet(crow::Response& res, const crow::Request& req,
1300                const std::vector<std::string>& params) override
1301     {
1302         // Collections don't include the static data added by SubRoute
1303         // because it has a duplicate entry for members
1304         res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
1305         res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection";
1306         res.jsonValue["@odata.context"] =
1307             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
1308         res.jsonValue["Name"] = "Manager Collection";
1309         res.jsonValue["Members@odata.count"] = 1;
1310         res.jsonValue["Members"] = {
1311             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
1312         res.end();
1313     }
1314 };
1315 } // namespace redfish
1316