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