xref: /openbmc/bmcweb/features/redfish/lib/managers.hpp (revision 3602e232fda4a6ddac4dcf6d3a024bd5ff6e7835)
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 <boost/date_time.hpp>
22 #include <dbus_utility.hpp>
23 #include <sstream>
24 #include <utils/systemd_utils.hpp>
25 #include <variant>
26 
27 namespace redfish
28 {
29 
30 /**
31  * ManagerActionsReset class supports handle POST method for Reset action.
32  * The class retrieves and sends data directly to dbus.
33  */
34 class ManagerActionsReset : public Node
35 {
36   public:
37     ManagerActionsReset(CrowApp& app) :
38         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
39     {
40         entityPrivileges = {
41             {boost::beast::http::verb::get, {{"Login"}}},
42             {boost::beast::http::verb::head, {{"Login"}}},
43             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
44             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
45             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
46             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
47     }
48 
49   private:
50     /**
51      * Function handles POST method request.
52      * Analyzes POST body message before sends Reset request data to dbus.
53      * OpenBMC allows for ResetType is GracefulRestart only.
54      */
55     void doPost(crow::Response& res, const crow::Request& req,
56                 const std::vector<std::string>& params) override
57     {
58         std::string resetType;
59 
60         if (!json_util::readJson(req, res, "ResetType", resetType))
61         {
62             return;
63         }
64 
65         if (resetType != "GracefulRestart")
66         {
67             res.result(boost::beast::http::status::bad_request);
68             messages::actionParameterNotSupported(res, resetType, "ResetType");
69             BMCWEB_LOG_ERROR << "Request incorrect action parameter: "
70                              << resetType;
71             res.end();
72             return;
73         }
74         doBMCGracefulRestart(res, req, params);
75     }
76 
77     /**
78      * Function transceives data with dbus directly.
79      * All BMC state properties will be retrieved before sending reset request.
80      */
81     void doBMCGracefulRestart(crow::Response& res, const crow::Request& req,
82                               const std::vector<std::string>& params)
83     {
84         const char* processName = "xyz.openbmc_project.State.BMC";
85         const char* objectPath = "/xyz/openbmc_project/state/bmc0";
86         const char* interfaceName = "xyz.openbmc_project.State.BMC";
87         const std::string& propertyValue =
88             "xyz.openbmc_project.State.BMC.Transition.Reboot";
89         const char* destProperty = "RequestedBMCTransition";
90 
91         // Create the D-Bus variant for D-Bus call.
92         VariantType dbusPropertyValue(propertyValue);
93 
94         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
95 
96         crow::connections::systemBus->async_method_call(
97             [asyncResp](const boost::system::error_code ec) {
98                 // Use "Set" method to set the property value.
99                 if (ec)
100                 {
101                     BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec;
102                     messages::internalError(asyncResp->res);
103                     return;
104                 }
105 
106                 messages::success(asyncResp->res);
107             },
108             processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
109             interfaceName, destProperty, dbusPropertyValue);
110     }
111 };
112 
113 static constexpr const char* objectManagerIface =
114     "org.freedesktop.DBus.ObjectManager";
115 static constexpr const char* pidConfigurationIface =
116     "xyz.openbmc_project.Configuration.Pid";
117 static constexpr const char* pidZoneConfigurationIface =
118     "xyz.openbmc_project.Configuration.Pid.Zone";
119 static constexpr const char* stepwiseConfigurationIface =
120     "xyz.openbmc_project.Configuration.Stepwise";
121 
122 static void asyncPopulatePid(const std::string& connection,
123                              const std::string& path,
124                              std::shared_ptr<AsyncResp> asyncResp)
125 {
126 
127     crow::connections::systemBus->async_method_call(
128         [asyncResp](const boost::system::error_code ec,
129                     const dbus::utility::ManagedObjectType& managedObj) {
130             if (ec)
131             {
132                 BMCWEB_LOG_ERROR << ec;
133                 asyncResp->res.jsonValue.clear();
134                 messages::internalError(asyncResp->res);
135                 return;
136             }
137             nlohmann::json& configRoot =
138                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
139             nlohmann::json& fans = configRoot["FanControllers"];
140             fans["@odata.type"] = "#OemManager.FanControllers";
141             fans["@odata.context"] =
142                 "/redfish/v1/$metadata#OemManager.FanControllers";
143             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
144                                 "Fan/FanControllers";
145 
146             nlohmann::json& pids = configRoot["PidControllers"];
147             pids["@odata.type"] = "#OemManager.PidControllers";
148             pids["@odata.context"] =
149                 "/redfish/v1/$metadata#OemManager.PidControllers";
150             pids["@odata.id"] =
151                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
152 
153             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
154             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
155             stepwise["@odata.context"] =
156                 "/redfish/v1/$metadata#OemManager.StepwiseControllers";
157             stepwise["@odata.id"] =
158                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
159 
160             nlohmann::json& zones = configRoot["FanZones"];
161             zones["@odata.id"] =
162                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
163             zones["@odata.type"] = "#OemManager.FanZones";
164             zones["@odata.context"] =
165                 "/redfish/v1/$metadata#OemManager.FanZones";
166             configRoot["@odata.id"] =
167                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
168             configRoot["@odata.type"] = "#OemManager.Fan";
169             configRoot["@odata.context"] =
170                 "/redfish/v1/$metadata#OemManager.Fan";
171 
172             for (const auto& pathPair : managedObj)
173             {
174                 for (const auto& intfPair : pathPair.second)
175                 {
176                     if (intfPair.first != pidConfigurationIface &&
177                         intfPair.first != pidZoneConfigurationIface &&
178                         intfPair.first != stepwiseConfigurationIface)
179                     {
180                         continue;
181                     }
182                     auto findName = intfPair.second.find("Name");
183                     if (findName == intfPair.second.end())
184                     {
185                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
186                         messages::internalError(asyncResp->res);
187                         return;
188                     }
189                     const std::string* namePtr =
190                         std::get_if<std::string>(&findName->second);
191                     if (namePtr == nullptr)
192                     {
193                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
194                         messages::internalError(asyncResp->res);
195                         return;
196                     }
197 
198                     std::string name = *namePtr;
199                     dbus::utility::escapePathForDbus(name);
200                     nlohmann::json* config = nullptr;
201 
202                     const std::string* classPtr = nullptr;
203                     auto findClass = intfPair.second.find("Class");
204                     if (findClass != intfPair.second.end())
205                     {
206                         classPtr = std::get_if<std::string>(&findClass->second);
207                     }
208 
209                     if (intfPair.first == pidZoneConfigurationIface)
210                     {
211                         std::string chassis;
212                         if (!dbus::utility::getNthStringFromPath(
213                                 pathPair.first.str, 5, chassis))
214                         {
215                             chassis = "#IllegalValue";
216                         }
217                         nlohmann::json& zone = zones[name];
218                         zone["Chassis"] = {
219                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
220                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
221                                             "OpenBmc/Fan/FanZones/" +
222                                             name;
223                         zone["@odata.type"] = "#OemManager.FanZone";
224                         zone["@odata.context"] =
225                             "/redfish/v1/$metadata#OemManager.FanZone";
226                         config = &zone;
227                     }
228 
229                     else if (intfPair.first == stepwiseConfigurationIface)
230                     {
231                         if (classPtr == nullptr)
232                         {
233                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
234                             messages::internalError(asyncResp->res);
235                             return;
236                         }
237 
238                         nlohmann::json& controller = stepwise[name];
239                         config = &controller;
240 
241                         controller["@odata.id"] =
242                             "/redfish/v1/Managers/bmc#/Oem/"
243                             "OpenBmc/Fan/StepwiseControllers/" +
244                             std::string(name);
245                         controller["@odata.type"] =
246                             "#OemManager.StepwiseController";
247 
248                         controller["@odata.context"] =
249                             "/redfish/v1/"
250                             "$metadata#OemManager.StepwiseController";
251                         controller["Direction"] = *classPtr;
252                     }
253 
254                     // pid and fans are off the same configuration
255                     else if (intfPair.first == pidConfigurationIface)
256                     {
257 
258                         if (classPtr == nullptr)
259                         {
260                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
261                             messages::internalError(asyncResp->res);
262                             return;
263                         }
264                         bool isFan = *classPtr == "fan";
265                         nlohmann::json& element =
266                             isFan ? fans[name] : pids[name];
267                         config = &element;
268                         if (isFan)
269                         {
270                             element["@odata.id"] =
271                                 "/redfish/v1/Managers/bmc#/Oem/"
272                                 "OpenBmc/Fan/FanControllers/" +
273                                 std::string(name);
274                             element["@odata.type"] =
275                                 "#OemManager.FanController";
276 
277                             element["@odata.context"] =
278                                 "/redfish/v1/"
279                                 "$metadata#OemManager.FanController";
280                         }
281                         else
282                         {
283                             element["@odata.id"] =
284                                 "/redfish/v1/Managers/bmc#/Oem/"
285                                 "OpenBmc/Fan/PidControllers/" +
286                                 std::string(name);
287                             element["@odata.type"] =
288                                 "#OemManager.PidController";
289                             element["@odata.context"] =
290                                 "/redfish/v1/$metadata"
291                                 "#OemManager.PidController";
292                         }
293                     }
294                     else
295                     {
296                         BMCWEB_LOG_ERROR << "Unexpected configuration";
297                         messages::internalError(asyncResp->res);
298                         return;
299                     }
300 
301                     // used for making maps out of 2 vectors
302                     const std::vector<double>* keys = nullptr;
303                     const std::vector<double>* values = nullptr;
304 
305                     for (const auto& propertyPair : intfPair.second)
306                     {
307                         if (propertyPair.first == "Type" ||
308                             propertyPair.first == "Class" ||
309                             propertyPair.first == "Name")
310                         {
311                             continue;
312                         }
313 
314                         // zones
315                         if (intfPair.first == pidZoneConfigurationIface)
316                         {
317                             const double* ptr =
318                                 std::get_if<double>(&propertyPair.second);
319                             if (ptr == nullptr)
320                             {
321                                 BMCWEB_LOG_ERROR << "Field Illegal "
322                                                  << propertyPair.first;
323                                 messages::internalError(asyncResp->res);
324                                 return;
325                             }
326                             (*config)[propertyPair.first] = *ptr;
327                         }
328 
329                         if (intfPair.first == stepwiseConfigurationIface)
330                         {
331                             if (propertyPair.first == "Reading" ||
332                                 propertyPair.first == "Output")
333                             {
334                                 const std::vector<double>* ptr =
335                                     std::get_if<std::vector<double>>(
336                                         &propertyPair.second);
337 
338                                 if (ptr == nullptr)
339                                 {
340                                     BMCWEB_LOG_ERROR << "Field Illegal "
341                                                      << propertyPair.first;
342                                     messages::internalError(asyncResp->res);
343                                     return;
344                                 }
345 
346                                 if (propertyPair.first == "Reading")
347                                 {
348                                     keys = ptr;
349                                 }
350                                 else
351                                 {
352                                     values = ptr;
353                                 }
354                                 if (keys && values)
355                                 {
356                                     if (keys->size() != values->size())
357                                     {
358                                         BMCWEB_LOG_ERROR
359                                             << "Reading and Output size don't "
360                                                "match ";
361                                         messages::internalError(asyncResp->res);
362                                         return;
363                                     }
364                                     nlohmann::json& steps = (*config)["Steps"];
365                                     steps = nlohmann::json::array();
366                                     for (size_t ii = 0; ii < keys->size(); ii++)
367                                     {
368                                         steps.push_back(
369                                             {{"Target", (*keys)[ii]},
370                                              {"Output", (*values)[ii]}});
371                                     }
372                                 }
373                             }
374                             if (propertyPair.first == "NegativeHysteresis" ||
375                                 propertyPair.first == "PositiveHysteresis")
376                             {
377                                 const double* ptr =
378                                     std::get_if<double>(&propertyPair.second);
379                                 if (ptr == nullptr)
380                                 {
381                                     BMCWEB_LOG_ERROR << "Field Illegal "
382                                                      << propertyPair.first;
383                                     messages::internalError(asyncResp->res);
384                                     return;
385                                 }
386                                 (*config)[propertyPair.first] = *ptr;
387                             }
388                         }
389 
390                         // pid and fans are off the same configuration
391                         if (intfPair.first == pidConfigurationIface ||
392                             intfPair.first == stepwiseConfigurationIface)
393                         {
394 
395                             if (propertyPair.first == "Zones")
396                             {
397                                 const std::vector<std::string>* inputs =
398                                     std::get_if<std::vector<std::string>>(
399                                         &propertyPair.second);
400 
401                                 if (inputs == nullptr)
402                                 {
403                                     BMCWEB_LOG_ERROR
404                                         << "Zones Pid Field Illegal";
405                                     messages::internalError(asyncResp->res);
406                                     return;
407                                 }
408                                 auto& data = (*config)[propertyPair.first];
409                                 data = nlohmann::json::array();
410                                 for (std::string itemCopy : *inputs)
411                                 {
412                                     dbus::utility::escapePathForDbus(itemCopy);
413                                     data.push_back(
414                                         {{"@odata.id",
415                                           "/redfish/v1/Managers/bmc#/Oem/"
416                                           "OpenBmc/Fan/FanZones/" +
417                                               itemCopy}});
418                                 }
419                             }
420                             // todo(james): may never happen, but this
421                             // assumes configuration data referenced in the
422                             // PID config is provided by the same daemon, we
423                             // could add another loop to cover all cases,
424                             // but I'm okay kicking this can down the road a
425                             // bit
426 
427                             else if (propertyPair.first == "Inputs" ||
428                                      propertyPair.first == "Outputs")
429                             {
430                                 auto& data = (*config)[propertyPair.first];
431                                 const std::vector<std::string>* inputs =
432                                     std::get_if<std::vector<std::string>>(
433                                         &propertyPair.second);
434 
435                                 if (inputs == nullptr)
436                                 {
437                                     BMCWEB_LOG_ERROR << "Field Illegal "
438                                                      << propertyPair.first;
439                                     messages::internalError(asyncResp->res);
440                                     return;
441                                 }
442                                 data = *inputs;
443                             } // doubles
444                             else if (propertyPair.first ==
445                                          "FFGainCoefficient" ||
446                                      propertyPair.first == "FFOffCoefficient" ||
447                                      propertyPair.first == "ICoefficient" ||
448                                      propertyPair.first == "ILimitMax" ||
449                                      propertyPair.first == "ILimitMin" ||
450                                      propertyPair.first ==
451                                          "PositiveHysteresis" ||
452                                      propertyPair.first ==
453                                          "NegativeHysteresis" ||
454                                      propertyPair.first == "OutLimitMax" ||
455                                      propertyPair.first == "OutLimitMin" ||
456                                      propertyPair.first == "PCoefficient" ||
457                                      propertyPair.first == "SetPoint" ||
458                                      propertyPair.first == "SlewNeg" ||
459                                      propertyPair.first == "SlewPos")
460                             {
461                                 const double* ptr =
462                                     std::get_if<double>(&propertyPair.second);
463                                 if (ptr == nullptr)
464                                 {
465                                     BMCWEB_LOG_ERROR << "Field Illegal "
466                                                      << propertyPair.first;
467                                     messages::internalError(asyncResp->res);
468                                     return;
469                                 }
470                                 (*config)[propertyPair.first] = *ptr;
471                             }
472                         }
473                     }
474                 }
475             }
476         },
477         connection, path, objectManagerIface, "GetManagedObjects");
478 }
479 
480 enum class CreatePIDRet
481 {
482     fail,
483     del,
484     patch
485 };
486 
487 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response,
488                                 std::vector<nlohmann::json>& config,
489                                 std::vector<std::string>& zones)
490 {
491     if (config.empty())
492     {
493         BMCWEB_LOG_ERROR << "Empty Zones";
494         messages::propertyValueFormatError(response->res,
495                                            nlohmann::json::array(), "Zones");
496         return false;
497     }
498     for (auto& odata : config)
499     {
500         std::string path;
501         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
502                                           path))
503         {
504             return false;
505         }
506         std::string input;
507 
508         // 8 below comes from
509         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
510         //     0    1     2      3    4    5      6     7      8
511         if (!dbus::utility::getNthStringFromPath(path, 8, input))
512         {
513             BMCWEB_LOG_ERROR << "Got invalid path " << path;
514             BMCWEB_LOG_ERROR << "Illegal Type Zones";
515             messages::propertyValueFormatError(response->res, odata.dump(),
516                                                "Zones");
517             return false;
518         }
519         boost::replace_all(input, "_", " ");
520         zones.emplace_back(std::move(input));
521     }
522     return true;
523 }
524 
525 static bool findChassis(const dbus::utility::ManagedObjectType& managedObj,
526                         const std::string& value, std::string& chassis)
527 {
528     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
529 
530     std::string escaped = boost::replace_all_copy(value, " ", "_");
531     escaped = "/" + escaped;
532     auto it = std::find_if(
533         managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) {
534             if (boost::algorithm::ends_with(obj.first.str, escaped))
535             {
536                 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n";
537                 return true;
538             }
539             return false;
540         });
541 
542     if (it == managedObj.end())
543     {
544         return false;
545     }
546     // 5 comes from <chassis-name> being the 5th element
547     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
548     return dbus::utility::getNthStringFromPath(it->first.str, 5, chassis);
549 }
550 
551 static CreatePIDRet createPidInterface(
552     const std::shared_ptr<AsyncResp>& response, const std::string& type,
553     nlohmann::json::iterator it, const std::string& path,
554     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
555     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
556         output,
557     std::string& chassis)
558 {
559 
560     // common deleter
561     if (it.value() == nullptr)
562     {
563         std::string iface;
564         if (type == "PidControllers" || type == "FanControllers")
565         {
566             iface = pidConfigurationIface;
567         }
568         else if (type == "FanZones")
569         {
570             iface = pidZoneConfigurationIface;
571         }
572         else if (type == "StepwiseControllers")
573         {
574             iface = stepwiseConfigurationIface;
575         }
576         else
577         {
578             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
579                              << type;
580             messages::propertyUnknown(response->res, type);
581             return CreatePIDRet::fail;
582         }
583         // delete interface
584         crow::connections::systemBus->async_method_call(
585             [response, path](const boost::system::error_code ec) {
586                 if (ec)
587                 {
588                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
589                     messages::internalError(response->res);
590                     return;
591                 }
592                 messages::success(response->res);
593             },
594             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
595         return CreatePIDRet::del;
596     }
597 
598     if (!createNewObject)
599     {
600         // if we aren't creating a new object, we should be able to find it on
601         // d-bus
602         if (!findChassis(managedObj, it.key(), chassis))
603         {
604             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
605             messages::invalidObject(response->res, it.key());
606             return CreatePIDRet::fail;
607         }
608     }
609 
610     if (type == "PidControllers" || type == "FanControllers")
611     {
612         if (createNewObject)
613         {
614             output["Class"] = type == "PidControllers" ? std::string("temp")
615                                                        : std::string("fan");
616             output["Type"] = std::string("Pid");
617         }
618 
619         std::optional<std::vector<nlohmann::json>> zones;
620         std::optional<std::vector<std::string>> inputs;
621         std::optional<std::vector<std::string>> outputs;
622         std::map<std::string, std::optional<double>> doubles;
623         if (!redfish::json_util::readJson(
624                 it.value(), response->res, "Inputs", inputs, "Outputs", outputs,
625                 "Zones", zones, "FFGainCoefficient",
626                 doubles["FFGainCoefficient"], "FFOffCoefficient",
627                 doubles["FFOffCoefficient"], "ICoefficient",
628                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
629                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
630                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
631                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
632                 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos",
633                 doubles["SlewPos"], "PositiveHysteresis",
634                 doubles["PositiveHysteresis"], "NegativeHysteresis",
635                 doubles["NegativeHysteresis"]))
636         {
637             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
638                              << it.value().dump();
639             return CreatePIDRet::fail;
640         }
641         if (zones)
642         {
643             std::vector<std::string> zonesStr;
644             if (!getZonesFromJsonReq(response, *zones, zonesStr))
645             {
646                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
647                 return CreatePIDRet::fail;
648             }
649             if (chassis.empty() &&
650                 !findChassis(managedObj, zonesStr[0], chassis))
651             {
652                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
653                 messages::invalidObject(response->res, it.key());
654                 return CreatePIDRet::fail;
655             }
656 
657             output["Zones"] = std::move(zonesStr);
658         }
659         if (inputs || outputs)
660         {
661             std::array<std::optional<std::vector<std::string>>*, 2> containers =
662                 {&inputs, &outputs};
663             size_t index = 0;
664             for (const auto& containerPtr : containers)
665             {
666                 std::optional<std::vector<std::string>>& container =
667                     *containerPtr;
668                 if (!container)
669                 {
670                     index++;
671                     continue;
672                 }
673 
674                 for (std::string& value : *container)
675                 {
676                     boost::replace_all(value, "_", " ");
677                 }
678                 std::string key;
679                 if (index == 0)
680                 {
681                     key = "Inputs";
682                 }
683                 else
684                 {
685                     key = "Outputs";
686                 }
687                 output[key] = *container;
688                 index++;
689             }
690         }
691 
692         // doubles
693         for (const auto& pairs : doubles)
694         {
695             if (!pairs.second)
696             {
697                 continue;
698             }
699             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
700             output[pairs.first] = *(pairs.second);
701         }
702     }
703 
704     else if (type == "FanZones")
705     {
706         output["Type"] = std::string("Pid.Zone");
707 
708         std::optional<nlohmann::json> chassisContainer;
709         std::optional<double> failSafePercent;
710         std::optional<double> minThermalOutput;
711         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
712                                           chassisContainer, "FailSafePercent",
713                                           failSafePercent, "MinThermalOutput",
714                                           minThermalOutput))
715         {
716             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
717                              << it.value().dump();
718             return CreatePIDRet::fail;
719         }
720 
721         if (chassisContainer)
722         {
723 
724             std::string chassisId;
725             if (!redfish::json_util::readJson(*chassisContainer, response->res,
726                                               "@odata.id", chassisId))
727             {
728                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
729                                  << chassisContainer->dump();
730                 return CreatePIDRet::fail;
731             }
732 
733             // /refish/v1/chassis/chassis_name/
734             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
735             {
736                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
737                 messages::invalidObject(response->res, chassisId);
738                 return CreatePIDRet::fail;
739             }
740         }
741         if (minThermalOutput)
742         {
743             output["MinThermalOutput"] = *minThermalOutput;
744         }
745         if (failSafePercent)
746         {
747             output["FailSafePercent"] = *failSafePercent;
748         }
749     }
750     else if (type == "StepwiseControllers")
751     {
752         output["Type"] = std::string("Stepwise");
753 
754         std::optional<std::vector<nlohmann::json>> zones;
755         std::optional<std::vector<nlohmann::json>> steps;
756         std::optional<std::vector<std::string>> inputs;
757         std::optional<double> positiveHysteresis;
758         std::optional<double> negativeHysteresis;
759         std::optional<std::string> direction; // upper clipping curve vs lower
760         if (!redfish::json_util::readJson(
761                 it.value(), response->res, "Zones", zones, "Steps", steps,
762                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
763                 "NegativeHysteresis", negativeHysteresis, "Direction",
764                 direction))
765         {
766             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
767                              << it.value().dump();
768             return CreatePIDRet::fail;
769         }
770 
771         if (zones)
772         {
773             std::vector<std::string> zonesStrs;
774             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
775             {
776                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
777                 return CreatePIDRet::fail;
778             }
779             if (chassis.empty() &&
780                 !findChassis(managedObj, zonesStrs[0], chassis))
781             {
782                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
783                 messages::invalidObject(response->res, it.key());
784                 return CreatePIDRet::fail;
785             }
786             output["Zones"] = std::move(zonesStrs);
787         }
788         if (steps)
789         {
790             std::vector<double> readings;
791             std::vector<double> outputs;
792             for (auto& step : *steps)
793             {
794                 double target;
795                 double output;
796 
797                 if (!redfish::json_util::readJson(step, response->res, "Target",
798                                                   target, "Output", output))
799                 {
800                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
801                                      << ", Illegal Property "
802                                      << it.value().dump();
803                     return CreatePIDRet::fail;
804                 }
805                 readings.emplace_back(target);
806                 outputs.emplace_back(output);
807             }
808             output["Reading"] = std::move(readings);
809             output["Output"] = std::move(outputs);
810         }
811         if (inputs)
812         {
813             for (std::string& value : *inputs)
814             {
815                 boost::replace_all(value, "_", " ");
816             }
817             output["Inputs"] = std::move(*inputs);
818         }
819         if (negativeHysteresis)
820         {
821             output["NegativeHysteresis"] = *negativeHysteresis;
822         }
823         if (positiveHysteresis)
824         {
825             output["PositiveHysteresis"] = *positiveHysteresis;
826         }
827         if (direction)
828         {
829             constexpr const std::array<const char*, 2> allowedDirections = {
830                 "Ceiling", "Floor"};
831             if (std::find(allowedDirections.begin(), allowedDirections.end(),
832                           *direction) == allowedDirections.end())
833             {
834                 messages::propertyValueTypeError(response->res, "Direction",
835                                                  *direction);
836                 return CreatePIDRet::fail;
837             }
838             output["Class"] = *direction;
839         }
840     }
841     else
842     {
843         BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type;
844         messages::propertyUnknown(response->res, type);
845         return CreatePIDRet::fail;
846     }
847     return CreatePIDRet::patch;
848 }
849 
850 class Manager : public Node
851 {
852   public:
853     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
854     {
855         uuid = app.template getMiddleware<crow::persistent_data::Middleware>()
856                    .systemUuid;
857         entityPrivileges = {
858             {boost::beast::http::verb::get, {{"Login"}}},
859             {boost::beast::http::verb::head, {{"Login"}}},
860             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
861             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
862             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
863             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
864     }
865 
866   private:
867     void getPidValues(std::shared_ptr<AsyncResp> asyncResp)
868     {
869         crow::connections::systemBus->async_method_call(
870             [asyncResp](const boost::system::error_code ec,
871                         const crow::openbmc_mapper::GetSubTreeType& subtree) {
872                 if (ec)
873                 {
874                     BMCWEB_LOG_ERROR << ec;
875                     messages::internalError(asyncResp->res);
876                     return;
877                 }
878 
879                 // create map of <connection, path to objMgr>>
880                 boost::container::flat_map<std::string, std::string>
881                     objectMgrPaths;
882                 boost::container::flat_set<std::string> calledConnections;
883                 for (const auto& pathGroup : subtree)
884                 {
885                     for (const auto& connectionGroup : pathGroup.second)
886                     {
887                         auto findConnection =
888                             calledConnections.find(connectionGroup.first);
889                         if (findConnection != calledConnections.end())
890                         {
891                             break;
892                         }
893                         for (const std::string& interface :
894                              connectionGroup.second)
895                         {
896                             if (interface == objectManagerIface)
897                             {
898                                 objectMgrPaths[connectionGroup.first] =
899                                     pathGroup.first;
900                             }
901                             // this list is alphabetical, so we
902                             // should have found the objMgr by now
903                             if (interface == pidConfigurationIface ||
904                                 interface == pidZoneConfigurationIface ||
905                                 interface == stepwiseConfigurationIface)
906                             {
907                                 auto findObjMgr =
908                                     objectMgrPaths.find(connectionGroup.first);
909                                 if (findObjMgr == objectMgrPaths.end())
910                                 {
911                                     BMCWEB_LOG_DEBUG << connectionGroup.first
912                                                      << "Has no Object Manager";
913                                     continue;
914                                 }
915 
916                                 calledConnections.insert(connectionGroup.first);
917 
918                                 asyncPopulatePid(findObjMgr->first,
919                                                  findObjMgr->second, asyncResp);
920                                 break;
921                             }
922                         }
923                     }
924                 }
925             },
926             "xyz.openbmc_project.ObjectMapper",
927             "/xyz/openbmc_project/object_mapper",
928             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
929             std::array<const char*, 4>{
930                 pidConfigurationIface, pidZoneConfigurationIface,
931                 objectManagerIface, stepwiseConfigurationIface});
932     }
933 
934     void doGet(crow::Response& res, const crow::Request& req,
935                const std::vector<std::string>& params) override
936     {
937         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
938         res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager";
939         res.jsonValue["@odata.context"] =
940             "/redfish/v1/$metadata#Manager.Manager";
941         res.jsonValue["Id"] = "bmc";
942         res.jsonValue["Name"] = "OpenBmc Manager";
943         res.jsonValue["Description"] = "Baseboard Management Controller";
944         res.jsonValue["PowerState"] = "On";
945         res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}};
946         res.jsonValue["ManagerType"] = "BMC";
947         res.jsonValue["UUID"] = systemd_utils::getUuid();
948         res.jsonValue["ServiceEntryPointUUID"] = uuid;
949         res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
950 
951         res.jsonValue["LogServices"] = {
952             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
953 
954         res.jsonValue["NetworkProtocol"] = {
955             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
956 
957         res.jsonValue["EthernetInterfaces"] = {
958             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
959         // default oem data
960         nlohmann::json& oem = res.jsonValue["Oem"];
961         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
962         oem["@odata.type"] = "#OemManager.Oem";
963         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
964         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
965         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
966         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
967         oemOpenbmc["@odata.context"] =
968             "/redfish/v1/$metadata#OemManager.OpenBmc";
969 
970         // Update Actions object.
971         nlohmann::json& manager_reset =
972             res.jsonValue["Actions"]["#Manager.Reset"];
973         manager_reset["target"] =
974             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
975         manager_reset["ResetType@Redfish.AllowableValues"] = {
976             "GracefulRestart"};
977 
978         res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
979 
980         // Fill in GraphicalConsole and SerialConsole info
981         res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
982         res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI",
983                                                                    "SSH"};
984         // TODO (Santosh) : Uncomment when KVM support is in.
985         // res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true;
986         // res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] =
987         //    {"KVMIP"};
988 
989         res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
990         res.jsonValue["Links"]["ManagerForServers"] = {
991             {{"@odata.id", "/redfish/v1/Systems/system"}}};
992 
993         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
994 
995         crow::connections::systemBus->async_method_call(
996             [asyncResp](const boost::system::error_code ec,
997                         const dbus::utility::ManagedObjectType& resp) {
998                 if (ec)
999                 {
1000                     BMCWEB_LOG_ERROR << "Error while getting Software Version";
1001                     messages::internalError(asyncResp->res);
1002                     return;
1003                 }
1004 
1005                 for (auto& objpath : resp)
1006                 {
1007                     for (auto& interface : objpath.second)
1008                     {
1009                         // If interface is
1010                         // xyz.openbmc_project.Software.Version, this is
1011                         // what we're looking for.
1012                         if (interface.first ==
1013                             "xyz.openbmc_project.Software.Version")
1014                         {
1015                             // Cut out everyting until last "/", ...
1016                             for (auto& property : interface.second)
1017                             {
1018                                 if (property.first == "Version")
1019                                 {
1020                                     const std::string* value =
1021                                         std::get_if<std::string>(
1022                                             &property.second);
1023                                     if (value == nullptr)
1024                                     {
1025                                         continue;
1026                                     }
1027                                     asyncResp->res
1028                                         .jsonValue["FirmwareVersion"] = *value;
1029                                 }
1030                             }
1031                         }
1032                     }
1033                 }
1034             },
1035             "xyz.openbmc_project.Software.BMC.Updater",
1036             "/xyz/openbmc_project/software",
1037             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1038         getPidValues(asyncResp);
1039     }
1040     void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data)
1041     {
1042 
1043         // todo(james): might make sense to do a mapper call here if this
1044         // interface gets more traction
1045         crow::connections::systemBus->async_method_call(
1046             [response,
1047              data](const boost::system::error_code ec,
1048                    const dbus::utility::ManagedObjectType& managedObj) {
1049                 if (ec)
1050                 {
1051                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
1052                     messages::internalError(response->res);
1053                     return;
1054                 }
1055 
1056                 // todo(james) mutable doesn't work with asio bindings
1057                 nlohmann::json jsonData = data;
1058 
1059                 std::optional<nlohmann::json> pidControllers;
1060                 std::optional<nlohmann::json> fanControllers;
1061                 std::optional<nlohmann::json> fanZones;
1062                 std::optional<nlohmann::json> stepwiseControllers;
1063                 if (!redfish::json_util::readJson(
1064                         jsonData, response->res, "PidControllers",
1065                         pidControllers, "FanControllers", fanControllers,
1066                         "FanZones", fanZones, "StepwiseControllers",
1067                         stepwiseControllers))
1068                 {
1069                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1070                                      << ", Illegal Property "
1071                                      << jsonData.dump();
1072                     return;
1073                 }
1074                 std::array<
1075                     std::pair<std::string, std::optional<nlohmann::json>*>, 4>
1076                     sections = {
1077                         std::make_pair("PidControllers", &pidControllers),
1078                         std::make_pair("FanControllers", &fanControllers),
1079                         std::make_pair("FanZones", &fanZones),
1080                         std::make_pair("StepwiseControllers",
1081                                        &stepwiseControllers)};
1082 
1083                 for (auto& containerPair : sections)
1084                 {
1085                     auto& container = *(containerPair.second);
1086                     if (!container)
1087                     {
1088                         continue;
1089                     }
1090                     std::string& type = containerPair.first;
1091 
1092                     for (nlohmann::json::iterator it = container->begin();
1093                          it != container->end(); it++)
1094                     {
1095                         const auto& name = it.key();
1096                         auto pathItr =
1097                             std::find_if(managedObj.begin(), managedObj.end(),
1098                                          [&name](const auto& obj) {
1099                                              return boost::algorithm::ends_with(
1100                                                  obj.first.str, "/" + name);
1101                                          });
1102                         boost::container::flat_map<
1103                             std::string, dbus::utility::DbusVariantType>
1104                             output;
1105 
1106                         output.reserve(16); // The pid interface length
1107 
1108                         // determines if we're patching entity-manager or
1109                         // creating a new object
1110                         bool createNewObject = (pathItr == managedObj.end());
1111                         std::string iface;
1112                         if (type == "PidControllers" ||
1113                             type == "FanControllers")
1114                         {
1115                             iface = pidConfigurationIface;
1116                             if (!createNewObject &&
1117                                 pathItr->second.find(pidConfigurationIface) ==
1118                                     pathItr->second.end())
1119                             {
1120                                 createNewObject = true;
1121                             }
1122                         }
1123                         else if (type == "FanZones")
1124                         {
1125                             iface = pidZoneConfigurationIface;
1126                             if (!createNewObject &&
1127                                 pathItr->second.find(
1128                                     pidZoneConfigurationIface) ==
1129                                     pathItr->second.end())
1130                             {
1131 
1132                                 createNewObject = true;
1133                             }
1134                         }
1135                         else if (type == "StepwiseControllers")
1136                         {
1137                             iface = stepwiseConfigurationIface;
1138                             if (!createNewObject &&
1139                                 pathItr->second.find(
1140                                     stepwiseConfigurationIface) ==
1141                                     pathItr->second.end())
1142                             {
1143                                 createNewObject = true;
1144                             }
1145                         }
1146                         BMCWEB_LOG_DEBUG << "Create new = " << createNewObject
1147                                          << "\n";
1148                         output["Name"] =
1149                             boost::replace_all_copy(name, "_", " ");
1150 
1151                         std::string chassis;
1152                         CreatePIDRet ret = createPidInterface(
1153                             response, type, it, pathItr->first.str, managedObj,
1154                             createNewObject, output, chassis);
1155                         if (ret == CreatePIDRet::fail)
1156                         {
1157                             return;
1158                         }
1159                         else if (ret == CreatePIDRet::del)
1160                         {
1161                             continue;
1162                         }
1163 
1164                         if (!createNewObject)
1165                         {
1166                             for (const auto& property : output)
1167                             {
1168                                 crow::connections::systemBus->async_method_call(
1169                                     [response,
1170                                      propertyName{std::string(property.first)}](
1171                                         const boost::system::error_code ec) {
1172                                         if (ec)
1173                                         {
1174                                             BMCWEB_LOG_ERROR
1175                                                 << "Error patching "
1176                                                 << propertyName << ": " << ec;
1177                                             messages::internalError(
1178                                                 response->res);
1179                                             return;
1180                                         }
1181                                         messages::success(response->res);
1182                                     },
1183                                     "xyz.openbmc_project.EntityManager",
1184                                     pathItr->first.str,
1185                                     "org.freedesktop.DBus.Properties", "Set",
1186                                     iface, property.first, property.second);
1187                             }
1188                         }
1189                         else
1190                         {
1191                             if (chassis.empty())
1192                             {
1193                                 BMCWEB_LOG_ERROR
1194                                     << "Failed to get chassis from config";
1195                                 messages::invalidObject(response->res, name);
1196                                 return;
1197                             }
1198 
1199                             bool foundChassis = false;
1200                             for (const auto& obj : managedObj)
1201                             {
1202                                 if (boost::algorithm::ends_with(obj.first.str,
1203                                                                 chassis))
1204                                 {
1205                                     chassis = obj.first.str;
1206                                     foundChassis = true;
1207                                     break;
1208                                 }
1209                             }
1210                             if (!foundChassis)
1211                             {
1212                                 BMCWEB_LOG_ERROR
1213                                     << "Failed to find chassis on dbus";
1214                                 messages::resourceMissingAtURI(
1215                                     response->res,
1216                                     "/redfish/v1/Chassis/" + chassis);
1217                                 return;
1218                             }
1219 
1220                             crow::connections::systemBus->async_method_call(
1221                                 [response](const boost::system::error_code ec) {
1222                                     if (ec)
1223                                     {
1224                                         BMCWEB_LOG_ERROR
1225                                             << "Error Adding Pid Object " << ec;
1226                                         messages::internalError(response->res);
1227                                         return;
1228                                     }
1229                                     messages::success(response->res);
1230                                 },
1231                                 "xyz.openbmc_project.EntityManager", chassis,
1232                                 "xyz.openbmc_project.AddObject", "AddObject",
1233                                 output);
1234                         }
1235                     }
1236                 }
1237             },
1238             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
1239             "GetManagedObjects");
1240     }
1241 
1242     void doPatch(crow::Response& res, const crow::Request& req,
1243                  const std::vector<std::string>& params) override
1244     {
1245         std::optional<nlohmann::json> oem;
1246         std::optional<std::string> datetime;
1247 
1248         if (!json_util::readJson(req, res, "Oem", oem, "DateTime", datetime))
1249         {
1250             return;
1251         }
1252 
1253         std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res);
1254 
1255         if (oem)
1256         {
1257             std::optional<nlohmann::json> openbmc;
1258             if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc))
1259             {
1260                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1261                                  << oem->dump();
1262                 return;
1263             }
1264             if (openbmc)
1265             {
1266                 std::optional<nlohmann::json> fan;
1267                 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan))
1268                 {
1269                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1270                                      << ", Illegal Property "
1271                                      << openbmc->dump();
1272                     return;
1273                 }
1274                 if (fan)
1275                 {
1276                     setPidValues(response, *fan);
1277                 }
1278             }
1279         }
1280         if (datetime)
1281         {
1282             setDateTime(response, std::move(*datetime));
1283         }
1284     }
1285 
1286     void setDateTime(std::shared_ptr<AsyncResp> aResp,
1287                      std::string datetime) const
1288     {
1289         BMCWEB_LOG_DEBUG << "Set date time: " << datetime;
1290 
1291         std::stringstream stream(datetime);
1292         // Convert from ISO 8601 to boost local_time
1293         // (BMC only has time in UTC)
1294         boost::posix_time::ptime posixTime;
1295         boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
1296         // Facet gets deleted with the stringsteam
1297         auto ifc = std::make_unique<boost::local_time::local_time_input_facet>(
1298             "%Y-%m-%d %H:%M:%S%F %ZP");
1299         stream.imbue(std::locale(stream.getloc(), ifc.release()));
1300 
1301         boost::local_time::local_date_time ldt(
1302             boost::local_time::not_a_date_time);
1303 
1304         if (stream >> ldt)
1305         {
1306             posixTime = ldt.utc_time();
1307             boost::posix_time::time_duration dur = posixTime - epoch;
1308             uint64_t durMicroSecs =
1309                 static_cast<uint64_t>(dur.total_microseconds());
1310             crow::connections::systemBus->async_method_call(
1311                 [aResp{std::move(aResp)}, datetime{std::move(datetime)}](
1312                     const boost::system::error_code ec) {
1313                     if (ec)
1314                     {
1315                         BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
1316                                             "DBUS response error "
1317                                          << ec;
1318                         messages::internalError(aResp->res);
1319                         return;
1320                     }
1321                     aResp->res.jsonValue["DateTime"] = datetime;
1322                 },
1323                 "xyz.openbmc_project.Time.Manager",
1324                 "/xyz/openbmc_project/time/bmc",
1325                 "org.freedesktop.DBus.Properties", "Set",
1326                 "xyz.openbmc_project.Time.EpochTime", "Elapsed",
1327                 std::variant<uint64_t>(durMicroSecs));
1328         }
1329         else
1330         {
1331             messages::propertyValueFormatError(aResp->res, datetime,
1332                                                "DateTime");
1333             return;
1334         }
1335     }
1336 
1337     std::string uuid;
1338 };
1339 
1340 class ManagerCollection : public Node
1341 {
1342   public:
1343     ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
1344     {
1345         entityPrivileges = {
1346             {boost::beast::http::verb::get, {{"Login"}}},
1347             {boost::beast::http::verb::head, {{"Login"}}},
1348             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1349             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1350             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1351             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1352     }
1353 
1354   private:
1355     void doGet(crow::Response& res, const crow::Request& req,
1356                const std::vector<std::string>& params) override
1357     {
1358         // Collections don't include the static data added by SubRoute
1359         // because it has a duplicate entry for members
1360         res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
1361         res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection";
1362         res.jsonValue["@odata.context"] =
1363             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
1364         res.jsonValue["Name"] = "Manager Collection";
1365         res.jsonValue["Members@odata.count"] = 1;
1366         res.jsonValue["Members"] = {
1367             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
1368         res.end();
1369     }
1370 };
1371 } // namespace redfish
1372