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