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