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