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