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