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);
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                                 return;
256                             }
257                             bool isFan = *classPtr == "fan";
258                             nlohmann::json& element =
259                                 isFan ? fans[name] : pids[name];
260                             if (isFan)
261                             {
262                                 element["@odata.id"] =
263                                     "/redfish/v1/Managers/bmc#/Oem/"
264                                     "OpenBmc/Fan/FanControllers/" +
265                                     std::string(name);
266                                 element["@odata.type"] =
267                                     "#OemManager.FanController";
268 
269                                 element["@odata.context"] =
270                                     "/redfish/v1/"
271                                     "$metadata#OemManager.FanController";
272                             }
273                             else
274                             {
275                                 element["@odata.id"] =
276                                     "/redfish/v1/Managers/bmc#/Oem/"
277                                     "OpenBmc/Fan/PidControllers/" +
278                                     std::string(name);
279                                 element["@odata.type"] =
280                                     "#OemManager.PidController";
281                                 element["@odata.context"] =
282                                     "/redfish/v1/$metadata"
283                                     "#OemManager.PidController";
284                             }
285 
286                             if (propertyPair.first == "Zones")
287                             {
288                                 const std::vector<std::string>* inputs =
289                                     mapbox::getPtr<
290                                         const std::vector<std::string>>(
291                                         propertyPair.second);
292 
293                                 if (inputs == nullptr)
294                                 {
295                                     BMCWEB_LOG_ERROR
296                                         << "Zones Pid Field Illegal";
297                                     messages::internalError(asyncResp->res);
298                                     return;
299                                 }
300                                 auto& data = element[propertyPair.first];
301                                 data = nlohmann::json::array();
302                                 for (std::string itemCopy : *inputs)
303                                 {
304                                     dbus::utility::escapePathForDbus(itemCopy);
305                                     data.push_back(
306                                         {{"@odata.id",
307                                           "/redfish/v1/Managers/bmc#/Oem/"
308                                           "OpenBmc/Fan/FanZones/" +
309                                               itemCopy}});
310                                 }
311                             }
312                             // todo(james): may never happen, but this
313                             // assumes configuration data referenced in the
314                             // PID config is provided by the same daemon, we
315                             // could add another loop to cover all cases,
316                             // but I'm okay kicking this can down the road a
317                             // bit
318 
319                             else if (propertyPair.first == "Inputs" ||
320                                      propertyPair.first == "Outputs")
321                             {
322                                 auto& data = element[propertyPair.first];
323                                 const std::vector<std::string>* inputs =
324                                     mapbox::getPtr<
325                                         const std::vector<std::string>>(
326                                         propertyPair.second);
327 
328                                 if (inputs == nullptr)
329                                 {
330                                     BMCWEB_LOG_ERROR << "Field Illegal "
331                                                      << propertyPair.first;
332                                     messages::internalError(asyncResp->res);
333                                     return;
334                                 }
335                                 data = *inputs;
336                             } // doubles
337                             else if (propertyPair.first ==
338                                          "FFGainCoefficient" ||
339                                      propertyPair.first == "FFOffCoefficient" ||
340                                      propertyPair.first == "ICoefficient" ||
341                                      propertyPair.first == "ILimitMax" ||
342                                      propertyPair.first == "ILimitMin" ||
343                                      propertyPair.first == "OutLimitMax" ||
344                                      propertyPair.first == "OutLimitMin" ||
345                                      propertyPair.first == "PCoefficient" ||
346                                      propertyPair.first == "SlewNeg" ||
347                                      propertyPair.first == "SlewPos")
348                             {
349                                 const double* ptr =
350                                     mapbox::getPtr<const double>(
351                                         propertyPair.second);
352                                 if (ptr == nullptr)
353                                 {
354                                     BMCWEB_LOG_ERROR << "Field Illegal "
355                                                      << propertyPair.first;
356                                     messages::internalError(asyncResp->res);
357                                     return;
358                                 }
359                                 element[propertyPair.first] = *ptr;
360                             }
361                         }
362                     }
363                 }
364             }
365         },
366         connection, path, objectManagerIface, "GetManagedObjects");
367 }
368 
369 enum class CreatePIDRet
370 {
371     fail,
372     del,
373     patch
374 };
375 
376 static CreatePIDRet createPidInterface(
377     const std::shared_ptr<AsyncResp>& response, const std::string& type,
378     const nlohmann::json& record, const std::string& path,
379     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
380     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
381         output,
382     std::string& chassis)
383 {
384 
385     if (type == "PidControllers" || type == "FanControllers")
386     {
387         if (createNewObject)
388         {
389             output["Class"] = type == "PidControllers" ? std::string("temp")
390                                                        : std::string("fan");
391             output["Type"] = std::string("Pid");
392         }
393         else if (record == nullptr)
394         {
395             // delete interface
396             crow::connections::systemBus->async_method_call(
397                 [response,
398                  path{std::string(path)}](const boost::system::error_code ec) {
399                     if (ec)
400                     {
401                         BMCWEB_LOG_ERROR << "Error patching " << path << ": "
402                                          << ec;
403                         messages::internalError(response->res);
404                     }
405                 },
406                 "xyz.openbmc_project.EntityManager", path,
407                 pidConfigurationIface, "Delete");
408             return CreatePIDRet::del;
409         }
410 
411         for (auto& field : record.items())
412         {
413             if (field.key() == "Zones")
414             {
415                 if (!field.value().is_array())
416                 {
417                     BMCWEB_LOG_ERROR << "Illegal Type " << field.key();
418                     messages::propertyValueFormatError(
419                         response->res, field.value(), field.key());
420                     return CreatePIDRet::fail;
421                 }
422                 std::vector<std::string> inputs;
423                 for (const auto& odata : field.value().items())
424                 {
425                     for (const auto& value : odata.value().items())
426                     {
427                         const std::string* path =
428                             value.value().get_ptr<const std::string*>();
429                         if (path == nullptr)
430                         {
431                             BMCWEB_LOG_ERROR << "Illegal Type " << field.key();
432                             messages::propertyValueFormatError(
433                                 response->res, field.value().dump(),
434                                 field.key());
435                             return CreatePIDRet::fail;
436                         }
437                         std::string input;
438                         if (!dbus::utility::getNthStringFromPath(*path, 4,
439                                                                  input))
440                         {
441                             BMCWEB_LOG_ERROR << "Got invalid path " << *path;
442                             messages::propertyValueFormatError(
443                                 response->res, field.value().dump(),
444                                 field.key());
445                             return CreatePIDRet::fail;
446                         }
447                         boost::replace_all(input, "_", " ");
448                         inputs.emplace_back(std::move(input));
449                     }
450                 }
451                 output["Zones"] = std::move(inputs);
452             }
453             else if (field.key() == "Inputs" || field.key() == "Outputs")
454             {
455                 if (!field.value().is_array())
456                 {
457                     BMCWEB_LOG_ERROR << "Illegal Type " << field.key();
458                     messages::propertyValueFormatError(
459                         response->res, field.value().dump(), field.key());
460                     return CreatePIDRet::fail;
461                 }
462                 std::vector<std::string> inputs;
463                 for (const auto& value : field.value().items())
464                 {
465                     const std::string* sensor =
466                         value.value().get_ptr<const std::string*>();
467 
468                     if (sensor == nullptr)
469                     {
470                         BMCWEB_LOG_ERROR << "Illegal Type "
471                                          << field.value().dump();
472                         messages::propertyValueFormatError(
473                             response->res, field.value().dump(), field.key());
474                         return CreatePIDRet::fail;
475                     }
476 
477                     std::string input =
478                         boost::replace_all_copy(*sensor, "_", " ");
479                     inputs.push_back(std::move(input));
480                     // try to find the sensor in the
481                     // configuration
482                     if (chassis.empty())
483                     {
484                         std::find_if(
485                             managedObj.begin(), managedObj.end(),
486                             [&chassis, sensor](const auto& obj) {
487                                 if (boost::algorithm::ends_with(obj.first.str,
488                                                                 *sensor))
489                                 {
490                                     return dbus::utility::getNthStringFromPath(
491                                         obj.first.str, 5, chassis);
492                                 }
493                                 return false;
494                             });
495                     }
496                 }
497                 output[field.key()] = inputs;
498             }
499 
500             // doubles
501             else if (field.key() == "FFGainCoefficient" ||
502                      field.key() == "FFOffCoefficient" ||
503                      field.key() == "ICoefficient" ||
504                      field.key() == "ILimitMax" || field.key() == "ILimitMin" ||
505                      field.key() == "OutLimitMax" ||
506                      field.key() == "OutLimitMin" ||
507                      field.key() == "PCoefficient" ||
508                      field.key() == "SetPoint" || field.key() == "SlewNeg" ||
509                      field.key() == "SlewPos")
510             {
511                 const double* ptr = field.value().get_ptr<const double*>();
512                 if (ptr == nullptr)
513                 {
514                     BMCWEB_LOG_ERROR << "Illegal Type " << field.key();
515                     messages::propertyValueFormatError(
516                         response->res, field.value().dump(), field.key());
517                     return CreatePIDRet::fail;
518                 }
519                 output[field.key()] = *ptr;
520             }
521 
522             else
523             {
524                 BMCWEB_LOG_ERROR << "Illegal Type " << field.key();
525                 messages::propertyUnknown(response->res, field.key());
526                 return CreatePIDRet::fail;
527             }
528         }
529     }
530     else if (type == "FanZones")
531     {
532         if (!createNewObject && record == nullptr)
533         {
534             // delete interface
535             crow::connections::systemBus->async_method_call(
536                 [response,
537                  path{std::string(path)}](const boost::system::error_code ec) {
538                     if (ec)
539                     {
540                         BMCWEB_LOG_ERROR << "Error patching " << path << ": "
541                                          << ec;
542                         messages::internalError(response->res);
543                     }
544                 },
545                 "xyz.openbmc_project.EntityManager", path,
546                 pidZoneConfigurationIface, "Delete");
547             return CreatePIDRet::del;
548         }
549         output["Type"] = std::string("Pid.Zone");
550 
551         for (auto& field : record.items())
552         {
553             if (field.key() == "Chassis")
554             {
555                 const std::string* chassisId = nullptr;
556                 for (const auto& id : field.value().items())
557                 {
558                     if (id.key() != "@odata.id")
559                     {
560                         BMCWEB_LOG_ERROR << "Illegal Type " << id.key();
561                         messages::propertyUnknown(response->res, field.key());
562                         return CreatePIDRet::fail;
563                     }
564                     chassisId = id.value().get_ptr<const std::string*>();
565                     if (chassisId == nullptr)
566                     {
567                         messages::createFailedMissingReqProperties(
568                             response->res, field.key());
569                         return CreatePIDRet::fail;
570                     }
571                 }
572 
573                 // /refish/v1/chassis/chassis_name/
574                 if (!dbus::utility::getNthStringFromPath(*chassisId, 3,
575                                                          chassis))
576                 {
577                     BMCWEB_LOG_ERROR << "Got invalid path " << *chassisId;
578                     messages::invalidObject(response->res, *chassisId);
579                     return CreatePIDRet::fail;
580                 }
581             }
582             else if (field.key() == "FailSafePercent" ||
583                      field.key() == "MinThermalRpm")
584             {
585                 const double* ptr = field.value().get_ptr<const double*>();
586                 if (ptr == nullptr)
587                 {
588                     BMCWEB_LOG_ERROR << "Illegal Type " << field.key();
589                     messages::propertyValueFormatError(
590                         response->res, field.value().dump(), field.key());
591                     return CreatePIDRet::fail;
592                 }
593                 output[field.key()] = *ptr;
594             }
595             else
596             {
597                 BMCWEB_LOG_ERROR << "Illegal Type " << field.key();
598                 messages::propertyUnknown(response->res, field.key());
599                 return CreatePIDRet::fail;
600             }
601         }
602     }
603     else
604     {
605         BMCWEB_LOG_ERROR << "Illegal Type " << type;
606         messages::propertyUnknown(response->res, type);
607         return CreatePIDRet::fail;
608     }
609     return CreatePIDRet::patch;
610 }
611 
612 class Manager : public Node
613 {
614   public:
615     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
616     {
617         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc";
618         Node::json["@odata.type"] = "#Manager.v1_3_0.Manager";
619         Node::json["@odata.context"] = "/redfish/v1/$metadata#Manager.Manager";
620         Node::json["Id"] = "bmc";
621         Node::json["Name"] = "OpenBmc Manager";
622         Node::json["Description"] = "Baseboard Management Controller";
623         Node::json["PowerState"] = "On";
624         Node::json["ManagerType"] = "BMC";
625         Node::json["UUID"] =
626             app.template getMiddleware<crow::persistent_data::Middleware>()
627                 .systemUuid;
628         Node::json["Model"] = "OpenBmc"; // TODO(ed), get model
629         Node::json["EthernetInterfaces"] = {
630             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
631 
632         entityPrivileges = {
633             {boost::beast::http::verb::get, {{"Login"}}},
634             {boost::beast::http::verb::head, {{"Login"}}},
635             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
636             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
637             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
638             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
639 
640         // default oem data
641         nlohmann::json& oem = Node::json["Oem"];
642         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
643         oem["@odata.type"] = "#OemManager.Oem";
644         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
645         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
646         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
647         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
648         oemOpenbmc["@odata.context"] =
649             "/redfish/v1/$metadata#OemManager.OpenBmc";
650     }
651 
652   private:
653     void getPidValues(std::shared_ptr<AsyncResp> asyncResp)
654     {
655         crow::connections::systemBus->async_method_call(
656             [asyncResp](const boost::system::error_code ec,
657                         const crow::openbmc_mapper::GetSubTreeType& subtree) {
658                 if (ec)
659                 {
660                     BMCWEB_LOG_ERROR << ec;
661                     messages::internalError(asyncResp->res);
662                     return;
663                 }
664 
665                 // create map of <connection, path to objMgr>>
666                 boost::container::flat_map<std::string, std::string>
667                     objectMgrPaths;
668                 boost::container::flat_set<std::string> calledConnections;
669                 for (const auto& pathGroup : subtree)
670                 {
671                     for (const auto& connectionGroup : pathGroup.second)
672                     {
673                         auto findConnection =
674                             calledConnections.find(connectionGroup.first);
675                         if (findConnection != calledConnections.end())
676                         {
677                             break;
678                         }
679                         for (const std::string& interface :
680                              connectionGroup.second)
681                         {
682                             if (interface == objectManagerIface)
683                             {
684                                 objectMgrPaths[connectionGroup.first] =
685                                     pathGroup.first;
686                             }
687                             // this list is alphabetical, so we
688                             // should have found the objMgr by now
689                             if (interface == pidConfigurationIface ||
690                                 interface == pidZoneConfigurationIface)
691                             {
692                                 auto findObjMgr =
693                                     objectMgrPaths.find(connectionGroup.first);
694                                 if (findObjMgr == objectMgrPaths.end())
695                                 {
696                                     BMCWEB_LOG_DEBUG << connectionGroup.first
697                                                      << "Has no Object Manager";
698                                     continue;
699                                 }
700 
701                                 calledConnections.insert(connectionGroup.first);
702 
703                                 asyncPopulatePid(findObjMgr->first,
704                                                  findObjMgr->second, asyncResp);
705                                 break;
706                             }
707                         }
708                     }
709                 }
710             },
711             "xyz.openbmc_project.ObjectMapper",
712             "/xyz/openbmc_project/object_mapper",
713             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
714             std::array<const char*, 3>{pidConfigurationIface,
715                                        pidZoneConfigurationIface,
716                                        objectManagerIface});
717     }
718 
719     void doGet(crow::Response& res, const crow::Request& req,
720                const std::vector<std::string>& params) override
721     {
722         // Update Actions object.
723         nlohmann::json& manager_reset = Node::json["Actions"]["#Manager.Reset"];
724         manager_reset["target"] =
725             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
726         manager_reset["ResetType@Redfish.AllowableValues"] = {
727             "GracefulRestart"};
728 
729         Node::json["DateTime"] = getDateTime();
730         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
731         asyncResp->res.jsonValue = Node::json;
732 
733         crow::connections::systemBus->async_method_call(
734             [asyncResp](const boost::system::error_code ec,
735                         const dbus::utility::ManagedObjectType& resp) {
736                 if (ec)
737                 {
738                     BMCWEB_LOG_ERROR << "Error while getting Software Version";
739                     messages::internalError(asyncResp->res);
740                     return;
741                 }
742 
743                 for (auto& objpath : resp)
744                 {
745                     for (auto& interface : objpath.second)
746                     {
747                         // If interface is xyz.openbmc_project.Software.Version,
748                         // this is what we're looking for.
749                         if (interface.first ==
750                             "xyz.openbmc_project.Software.Version")
751                         {
752                             // Cut out everyting until last "/", ...
753                             const std::string& iface_id = objpath.first;
754                             for (auto& property : interface.second)
755                             {
756                                 if (property.first == "Version")
757                                 {
758                                     const std::string* value =
759                                         mapbox::getPtr<const std::string>(
760                                             property.second);
761                                     if (value == nullptr)
762                                     {
763                                         continue;
764                                     }
765                                     asyncResp->res
766                                         .jsonValue["FirmwareVersion"] = *value;
767                                 }
768                             }
769                         }
770                     }
771                 }
772             },
773             "xyz.openbmc_project.Software.BMC.Updater",
774             "/xyz/openbmc_project/software",
775             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
776         getPidValues(asyncResp);
777     }
778     void setPidValues(std::shared_ptr<AsyncResp> response,
779                       const nlohmann::json& data)
780     {
781         // todo(james): might make sense to do a mapper call here if this
782         // interface gets more traction
783         crow::connections::systemBus->async_method_call(
784             [response,
785              data](const boost::system::error_code ec,
786                    const dbus::utility::ManagedObjectType& managedObj) {
787                 if (ec)
788                 {
789                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
790                     messages::internalError(response->res);
791                     return;
792                 }
793                 for (const auto& type : data.items())
794                 {
795                     if (!type.value().is_object())
796                     {
797                         BMCWEB_LOG_ERROR << "Illegal Type " << type.key();
798                         messages::propertyValueFormatError(
799                             response->res, type.value(), type.key());
800                         return;
801                     }
802                     for (const auto& record : type.value().items())
803                     {
804                         const std::string& name = record.key();
805                         auto pathItr =
806                             std::find_if(managedObj.begin(), managedObj.end(),
807                                          [&name](const auto& obj) {
808                                              return boost::algorithm::ends_with(
809                                                  obj.first.str, name);
810                                          });
811                         boost::container::flat_map<
812                             std::string, dbus::utility::DbusVariantType>
813                             output;
814 
815                         output.reserve(16); // The pid interface length
816 
817                         // determines if we're patching entity-manager or
818                         // creating a new object
819                         bool createNewObject = (pathItr == managedObj.end());
820                         if (type.key() == "PidControllers" ||
821                             type.key() == "FanControllers")
822                         {
823                             if (!createNewObject &&
824                                 pathItr->second.find(pidConfigurationIface) ==
825                                     pathItr->second.end())
826                             {
827                                 createNewObject = true;
828                             }
829                         }
830                         else if (!createNewObject &&
831                                  pathItr->second.find(
832                                      pidZoneConfigurationIface) ==
833                                      pathItr->second.end())
834                         {
835                             createNewObject = true;
836                         }
837                         output["Name"] =
838                             boost::replace_all_copy(name, "_", " ");
839 
840                         std::string chassis;
841                         CreatePIDRet ret = createPidInterface(
842                             response, type.key(), record.value(),
843                             pathItr->first.str, managedObj, createNewObject,
844                             output, chassis);
845                         if (ret == CreatePIDRet::fail)
846                         {
847                             return;
848                         }
849                         else if (ret == CreatePIDRet::del)
850                         {
851                             continue;
852                         }
853 
854                         if (!createNewObject)
855                         {
856                             for (const auto& property : output)
857                             {
858                                 const char* iface =
859                                     type.key() == "FanZones"
860                                         ? pidZoneConfigurationIface
861                                         : pidConfigurationIface;
862                                 crow::connections::systemBus->async_method_call(
863                                     [response,
864                                      propertyName{std::string(property.first)}](
865                                         const boost::system::error_code ec) {
866                                         if (ec)
867                                         {
868                                             BMCWEB_LOG_ERROR
869                                                 << "Error patching "
870                                                 << propertyName << ": " << ec;
871                                             messages::internalError(
872                                                 response->res);
873                                         }
874                                     },
875                                     "xyz.openbmc_project.EntityManager",
876                                     pathItr->first.str,
877                                     "org.freedesktop.DBus.Properties", "Set",
878                                     std::string(iface), property.first,
879                                     property.second);
880                             }
881                         }
882                         else
883                         {
884                             if (chassis.empty())
885                             {
886                                 BMCWEB_LOG_ERROR
887                                     << "Failed to get chassis from config";
888                                 messages::invalidObject(response->res, name);
889                                 return;
890                             }
891 
892                             bool foundChassis = false;
893                             for (const auto& obj : managedObj)
894                             {
895                                 if (boost::algorithm::ends_with(obj.first.str,
896                                                                 chassis))
897                                 {
898                                     chassis = obj.first.str;
899                                     foundChassis = true;
900                                     break;
901                                 }
902                             }
903                             if (!foundChassis)
904                             {
905                                 BMCWEB_LOG_ERROR
906                                     << "Failed to find chassis on dbus";
907                                 messages::resourceMissingAtURI(
908                                     response->res,
909                                     "/redfish/v1/Chassis/" + chassis);
910                                 return;
911                             }
912 
913                             crow::connections::systemBus->async_method_call(
914                                 [response](const boost::system::error_code ec) {
915                                     if (ec)
916                                     {
917                                         BMCWEB_LOG_ERROR
918                                             << "Error Adding Pid Object " << ec;
919                                         messages::internalError(response->res);
920                                     }
921                                 },
922                                 "xyz.openbmc_project.EntityManager", chassis,
923                                 "xyz.openbmc_project.AddObject", "AddObject",
924                                 output);
925                         }
926                     }
927                 }
928             },
929             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
930             "GetManagedObjects");
931     }
932 
933     void doPatch(crow::Response& res, const crow::Request& req,
934                  const std::vector<std::string>& params) override
935     {
936         nlohmann::json patch;
937         if (!json_util::processJsonFromRequest(res, req, patch))
938         {
939             return;
940         }
941         std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res);
942         for (const auto& topLevel : patch.items())
943         {
944             if (topLevel.key() == "Oem")
945             {
946                 if (!topLevel.value().is_object())
947                 {
948                     BMCWEB_LOG_ERROR << "Bad Patch " << topLevel.key();
949                     messages::propertyValueFormatError(
950                         response->res, topLevel.key(), "OemManager.Oem");
951                     return;
952                 }
953             }
954             else
955             {
956                 BMCWEB_LOG_ERROR << "Bad Patch " << topLevel.key();
957                 messages::propertyUnknown(response->res, topLevel.key());
958                 return;
959             }
960             for (const auto& oemLevel : topLevel.value().items())
961             {
962                 if (oemLevel.key() == "OpenBmc")
963                 {
964                     if (!oemLevel.value().is_object())
965                     {
966                         BMCWEB_LOG_ERROR << "Bad Patch " << oemLevel.key();
967                         messages::propertyValueFormatError(
968                             response->res, topLevel.key(),
969                             "OemManager.OpenBmc");
970                         return;
971                     }
972                     for (const auto& typeLevel : oemLevel.value().items())
973                     {
974 
975                         if (typeLevel.key() == "Fan")
976                         {
977                             if (!typeLevel.value().is_object())
978                             {
979                                 BMCWEB_LOG_ERROR << "Bad Patch "
980                                                  << typeLevel.key();
981                                 messages::propertyValueFormatError(
982                                     response->res, typeLevel.value().dump(),
983                                     typeLevel.key());
984                                 return;
985                             }
986                             setPidValues(response,
987                                          std::move(typeLevel.value()));
988                         }
989                         else
990                         {
991                             BMCWEB_LOG_ERROR << "Bad Patch " << typeLevel.key();
992                             messages::propertyUnknown(response->res,
993                                                       typeLevel.key());
994                             return;
995                         }
996                     }
997                 }
998                 else
999                 {
1000                     BMCWEB_LOG_ERROR << "Bad Patch " << oemLevel.key();
1001                     messages::propertyUnknown(response->res, oemLevel.key());
1002                     return;
1003                 }
1004             }
1005         }
1006     }
1007 
1008     std::string getDateTime() const
1009     {
1010         std::array<char, 128> dateTime;
1011         std::string redfishDateTime("0000-00-00T00:00:00Z00:00");
1012         std::time_t time = std::time(nullptr);
1013 
1014         if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z",
1015                           std::localtime(&time)))
1016         {
1017             // insert the colon required by the ISO 8601 standard
1018             redfishDateTime = std::string(dateTime.data());
1019             redfishDateTime.insert(redfishDateTime.end() - 2, ':');
1020         }
1021 
1022         return redfishDateTime;
1023     }
1024 };
1025 
1026 class ManagerCollection : public Node
1027 {
1028   public:
1029     ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
1030     {
1031         Node::json["@odata.id"] = "/redfish/v1/Managers";
1032         Node::json["@odata.type"] = "#ManagerCollection.ManagerCollection";
1033         Node::json["@odata.context"] =
1034             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
1035         Node::json["Name"] = "Manager Collection";
1036         Node::json["Members@odata.count"] = 1;
1037         Node::json["Members"] = {{{"@odata.id", "/redfish/v1/Managers/bmc"}}};
1038 
1039         entityPrivileges = {
1040             {boost::beast::http::verb::get, {{"Login"}}},
1041             {boost::beast::http::verb::head, {{"Login"}}},
1042             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1043             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1044             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1045             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1046     }
1047 
1048   private:
1049     void doGet(crow::Response& res, const crow::Request& req,
1050                const std::vector<std::string>& params) override
1051     {
1052         // Collections don't include the static data added by SubRoute
1053         // because it has a duplicate entry for members
1054         res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
1055         res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection";
1056         res.jsonValue["@odata.context"] =
1057             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
1058         res.jsonValue["Name"] = "Manager Collection";
1059         res.jsonValue["Members@odata.count"] = 1;
1060         res.jsonValue["Members"] = {
1061             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
1062         res.end();
1063     }
1064 };
1065 } // namespace redfish
1066