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