xref: /openbmc/bmcweb/features/redfish/lib/managers.hpp (revision e69d9de2d9453ff301e727554dc74fa8bc0f8482)
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 "health.hpp"
19 #include "node.hpp"
20 #include "redfish_util.hpp"
21 
22 #include <boost/algorithm/string/replace.hpp>
23 #include <boost/date_time.hpp>
24 #include <dbus_utility.hpp>
25 #include <memory>
26 #include <sstream>
27 #include <utils/fw_utils.hpp>
28 #include <utils/systemd_utils.hpp>
29 #include <variant>
30 
31 namespace redfish
32 {
33 
34 /**
35  * ManagerActionsReset class supports handle POST method for Reset action.
36  * The class retrieves and sends data directly to dbus.
37  */
38 class ManagerActionsReset : public Node
39 {
40   public:
41     ManagerActionsReset(CrowApp& app) :
42         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
43     {
44         entityPrivileges = {
45             {boost::beast::http::verb::get, {{"Login"}}},
46             {boost::beast::http::verb::head, {{"Login"}}},
47             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
48             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
49             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
50             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
51     }
52 
53   private:
54     /**
55      * Function handles POST method request.
56      * Analyzes POST body message before sends Reset request data to dbus.
57      * OpenBMC allows for ResetType is GracefulRestart only.
58      */
59     void doPost(crow::Response& res, const crow::Request& req,
60                 const std::vector<std::string>& params) override
61     {
62         std::string resetType;
63 
64         if (!json_util::readJson(req, res, "ResetType", resetType))
65         {
66             return;
67         }
68 
69         if (resetType != "GracefulRestart")
70         {
71             res.result(boost::beast::http::status::bad_request);
72             messages::actionParameterNotSupported(res, resetType, "ResetType");
73             BMCWEB_LOG_ERROR << "Request incorrect action parameter: "
74                              << resetType;
75             res.end();
76             return;
77         }
78         doBMCGracefulRestart(res, req, params);
79     }
80 
81     /**
82      * Function transceives data with dbus directly.
83      * All BMC state properties will be retrieved before sending reset request.
84      */
85     void doBMCGracefulRestart(crow::Response& res, const crow::Request& req,
86                               const std::vector<std::string>& params)
87     {
88         const char* processName = "xyz.openbmc_project.State.BMC";
89         const char* objectPath = "/xyz/openbmc_project/state/bmc0";
90         const char* interfaceName = "xyz.openbmc_project.State.BMC";
91         const std::string& propertyValue =
92             "xyz.openbmc_project.State.BMC.Transition.Reboot";
93         const char* destProperty = "RequestedBMCTransition";
94 
95         // Create the D-Bus variant for D-Bus call.
96         VariantType dbusPropertyValue(propertyValue);
97 
98         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
99 
100         crow::connections::systemBus->async_method_call(
101             [asyncResp](const boost::system::error_code ec) {
102                 // Use "Set" method to set the property value.
103                 if (ec)
104                 {
105                     BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec;
106                     messages::internalError(asyncResp->res);
107                     return;
108                 }
109 
110                 messages::success(asyncResp->res);
111             },
112             processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
113             interfaceName, destProperty, dbusPropertyValue);
114     }
115 };
116 
117 static constexpr const char* objectManagerIface =
118     "org.freedesktop.DBus.ObjectManager";
119 static constexpr const char* pidConfigurationIface =
120     "xyz.openbmc_project.Configuration.Pid";
121 static constexpr const char* pidZoneConfigurationIface =
122     "xyz.openbmc_project.Configuration.Pid.Zone";
123 static constexpr const char* stepwiseConfigurationIface =
124     "xyz.openbmc_project.Configuration.Stepwise";
125 static constexpr const char* thermalModeIface =
126     "xyz.openbmc_project.Control.ThermalMode";
127 
128 static void asyncPopulatePid(const std::string& connection,
129                              const std::string& path,
130                              const std::string& currentProfile,
131                              const std::vector<std::string>& supportedProfiles,
132                              std::shared_ptr<AsyncResp> asyncResp)
133 {
134 
135     crow::connections::systemBus->async_method_call(
136         [asyncResp, currentProfile, supportedProfiles](
137             const boost::system::error_code ec,
138             const dbus::utility::ManagedObjectType& managedObj) {
139             if (ec)
140             {
141                 BMCWEB_LOG_ERROR << ec;
142                 asyncResp->res.jsonValue.clear();
143                 messages::internalError(asyncResp->res);
144                 return;
145             }
146             nlohmann::json& configRoot =
147                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
148             nlohmann::json& fans = configRoot["FanControllers"];
149             fans["@odata.type"] = "#OemManager.FanControllers";
150             fans["@odata.context"] =
151                 "/redfish/v1/$metadata#OemManager.FanControllers";
152             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
153                                 "Fan/FanControllers";
154 
155             nlohmann::json& pids = configRoot["PidControllers"];
156             pids["@odata.type"] = "#OemManager.PidControllers";
157             pids["@odata.context"] =
158                 "/redfish/v1/$metadata#OemManager.PidControllers";
159             pids["@odata.id"] =
160                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
161 
162             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
163             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
164             stepwise["@odata.context"] =
165                 "/redfish/v1/$metadata#OemManager.StepwiseControllers";
166             stepwise["@odata.id"] =
167                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
168 
169             nlohmann::json& zones = configRoot["FanZones"];
170             zones["@odata.id"] =
171                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
172             zones["@odata.type"] = "#OemManager.FanZones";
173             zones["@odata.context"] =
174                 "/redfish/v1/$metadata#OemManager.FanZones";
175             configRoot["@odata.id"] =
176                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
177             configRoot["@odata.type"] = "#OemManager.Fan";
178             configRoot["@odata.context"] =
179                 "/redfish/v1/$metadata#OemManager.Fan";
180             configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
181 
182             if (!currentProfile.empty())
183             {
184                 configRoot["Profile"] = currentProfile;
185             }
186             BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !";
187 
188             for (const auto& pathPair : managedObj)
189             {
190                 for (const auto& intfPair : pathPair.second)
191                 {
192                     if (intfPair.first != pidConfigurationIface &&
193                         intfPair.first != pidZoneConfigurationIface &&
194                         intfPair.first != stepwiseConfigurationIface)
195                     {
196                         continue;
197                     }
198                     auto findName = intfPair.second.find("Name");
199                     if (findName == intfPair.second.end())
200                     {
201                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
202                         messages::internalError(asyncResp->res);
203                         return;
204                     }
205 
206                     const std::string* namePtr =
207                         std::get_if<std::string>(&findName->second);
208                     if (namePtr == nullptr)
209                     {
210                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
211                         messages::internalError(asyncResp->res);
212                         return;
213                     }
214                     std::string name = *namePtr;
215                     dbus::utility::escapePathForDbus(name);
216 
217                     auto findProfiles = intfPair.second.find("Profiles");
218                     if (findProfiles != intfPair.second.end())
219                     {
220                         const std::vector<std::string>* profiles =
221                             std::get_if<std::vector<std::string>>(
222                                 &findProfiles->second);
223                         if (profiles == nullptr)
224                         {
225                             BMCWEB_LOG_ERROR << "Pid Profiles Field illegal";
226                             messages::internalError(asyncResp->res);
227                             return;
228                         }
229                         if (std::find(profiles->begin(), profiles->end(),
230                                       currentProfile) == profiles->end())
231                         {
232                             BMCWEB_LOG_INFO
233                                 << name << " not supported in current profile";
234                             continue;
235                         }
236                     }
237                     nlohmann::json* config = nullptr;
238 
239                     const std::string* classPtr = nullptr;
240                     auto findClass = intfPair.second.find("Class");
241                     if (findClass != intfPair.second.end())
242                     {
243                         classPtr = std::get_if<std::string>(&findClass->second);
244                     }
245 
246                     if (intfPair.first == pidZoneConfigurationIface)
247                     {
248                         std::string chassis;
249                         if (!dbus::utility::getNthStringFromPath(
250                                 pathPair.first.str, 5, chassis))
251                         {
252                             chassis = "#IllegalValue";
253                         }
254                         nlohmann::json& zone = zones[name];
255                         zone["Chassis"] = {
256                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
257                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
258                                             "OpenBmc/Fan/FanZones/" +
259                                             name;
260                         zone["@odata.type"] = "#OemManager.FanZone";
261                         zone["@odata.context"] =
262                             "/redfish/v1/$metadata#OemManager.FanZone";
263                         config = &zone;
264                     }
265 
266                     else if (intfPair.first == stepwiseConfigurationIface)
267                     {
268                         if (classPtr == nullptr)
269                         {
270                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
271                             messages::internalError(asyncResp->res);
272                             return;
273                         }
274 
275                         nlohmann::json& controller = stepwise[name];
276                         config = &controller;
277 
278                         controller["@odata.id"] =
279                             "/redfish/v1/Managers/bmc#/Oem/"
280                             "OpenBmc/Fan/StepwiseControllers/" +
281                             name;
282                         controller["@odata.type"] =
283                             "#OemManager.StepwiseController";
284 
285                         controller["@odata.context"] =
286                             "/redfish/v1/"
287                             "$metadata#OemManager.StepwiseController";
288                         controller["Direction"] = *classPtr;
289                     }
290 
291                     // pid and fans are off the same configuration
292                     else if (intfPair.first == pidConfigurationIface)
293                     {
294 
295                         if (classPtr == nullptr)
296                         {
297                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
298                             messages::internalError(asyncResp->res);
299                             return;
300                         }
301                         bool isFan = *classPtr == "fan";
302                         nlohmann::json& element =
303                             isFan ? fans[name] : pids[name];
304                         config = &element;
305                         if (isFan)
306                         {
307                             element["@odata.id"] =
308                                 "/redfish/v1/Managers/bmc#/Oem/"
309                                 "OpenBmc/Fan/FanControllers/" +
310                                 name;
311                             element["@odata.type"] =
312                                 "#OemManager.FanController";
313 
314                             element["@odata.context"] =
315                                 "/redfish/v1/"
316                                 "$metadata#OemManager.FanController";
317                         }
318                         else
319                         {
320                             element["@odata.id"] =
321                                 "/redfish/v1/Managers/bmc#/Oem/"
322                                 "OpenBmc/Fan/PidControllers/" +
323                                 name;
324                             element["@odata.type"] =
325                                 "#OemManager.PidController";
326                             element["@odata.context"] =
327                                 "/redfish/v1/$metadata"
328                                 "#OemManager.PidController";
329                         }
330                     }
331                     else
332                     {
333                         BMCWEB_LOG_ERROR << "Unexpected configuration";
334                         messages::internalError(asyncResp->res);
335                         return;
336                     }
337 
338                     // used for making maps out of 2 vectors
339                     const std::vector<double>* keys = nullptr;
340                     const std::vector<double>* values = nullptr;
341 
342                     for (const auto& propertyPair : intfPair.second)
343                     {
344                         if (propertyPair.first == "Type" ||
345                             propertyPair.first == "Class" ||
346                             propertyPair.first == "Name")
347                         {
348                             continue;
349                         }
350 
351                         // zones
352                         if (intfPair.first == pidZoneConfigurationIface)
353                         {
354                             const double* ptr =
355                                 std::get_if<double>(&propertyPair.second);
356                             if (ptr == nullptr)
357                             {
358                                 BMCWEB_LOG_ERROR << "Field Illegal "
359                                                  << propertyPair.first;
360                                 messages::internalError(asyncResp->res);
361                                 return;
362                             }
363                             (*config)[propertyPair.first] = *ptr;
364                         }
365 
366                         if (intfPair.first == stepwiseConfigurationIface)
367                         {
368                             if (propertyPair.first == "Reading" ||
369                                 propertyPair.first == "Output")
370                             {
371                                 const std::vector<double>* ptr =
372                                     std::get_if<std::vector<double>>(
373                                         &propertyPair.second);
374 
375                                 if (ptr == nullptr)
376                                 {
377                                     BMCWEB_LOG_ERROR << "Field Illegal "
378                                                      << propertyPair.first;
379                                     messages::internalError(asyncResp->res);
380                                     return;
381                                 }
382 
383                                 if (propertyPair.first == "Reading")
384                                 {
385                                     keys = ptr;
386                                 }
387                                 else
388                                 {
389                                     values = ptr;
390                                 }
391                                 if (keys && values)
392                                 {
393                                     if (keys->size() != values->size())
394                                     {
395                                         BMCWEB_LOG_ERROR
396                                             << "Reading and Output size don't "
397                                                "match ";
398                                         messages::internalError(asyncResp->res);
399                                         return;
400                                     }
401                                     nlohmann::json& steps = (*config)["Steps"];
402                                     steps = nlohmann::json::array();
403                                     for (size_t ii = 0; ii < keys->size(); ii++)
404                                     {
405                                         steps.push_back(
406                                             {{"Target", (*keys)[ii]},
407                                              {"Output", (*values)[ii]}});
408                                     }
409                                 }
410                             }
411                             if (propertyPair.first == "NegativeHysteresis" ||
412                                 propertyPair.first == "PositiveHysteresis")
413                             {
414                                 const double* ptr =
415                                     std::get_if<double>(&propertyPair.second);
416                                 if (ptr == nullptr)
417                                 {
418                                     BMCWEB_LOG_ERROR << "Field Illegal "
419                                                      << propertyPair.first;
420                                     messages::internalError(asyncResp->res);
421                                     return;
422                                 }
423                                 (*config)[propertyPair.first] = *ptr;
424                             }
425                         }
426 
427                         // pid and fans are off the same configuration
428                         if (intfPair.first == pidConfigurationIface ||
429                             intfPair.first == stepwiseConfigurationIface)
430                         {
431 
432                             if (propertyPair.first == "Zones")
433                             {
434                                 const std::vector<std::string>* inputs =
435                                     std::get_if<std::vector<std::string>>(
436                                         &propertyPair.second);
437 
438                                 if (inputs == nullptr)
439                                 {
440                                     BMCWEB_LOG_ERROR
441                                         << "Zones Pid Field Illegal";
442                                     messages::internalError(asyncResp->res);
443                                     return;
444                                 }
445                                 auto& data = (*config)[propertyPair.first];
446                                 data = nlohmann::json::array();
447                                 for (std::string itemCopy : *inputs)
448                                 {
449                                     dbus::utility::escapePathForDbus(itemCopy);
450                                     data.push_back(
451                                         {{"@odata.id",
452                                           "/redfish/v1/Managers/bmc#/Oem/"
453                                           "OpenBmc/Fan/FanZones/" +
454                                               itemCopy}});
455                                 }
456                             }
457                             // todo(james): may never happen, but this
458                             // assumes configuration data referenced in the
459                             // PID config is provided by the same daemon, we
460                             // could add another loop to cover all cases,
461                             // but I'm okay kicking this can down the road a
462                             // bit
463 
464                             else if (propertyPair.first == "Inputs" ||
465                                      propertyPair.first == "Outputs")
466                             {
467                                 auto& data = (*config)[propertyPair.first];
468                                 const std::vector<std::string>* inputs =
469                                     std::get_if<std::vector<std::string>>(
470                                         &propertyPair.second);
471 
472                                 if (inputs == nullptr)
473                                 {
474                                     BMCWEB_LOG_ERROR << "Field Illegal "
475                                                      << propertyPair.first;
476                                     messages::internalError(asyncResp->res);
477                                     return;
478                                 }
479                                 data = *inputs;
480                             }
481                             else if (propertyPair.first == "SetPointOffset")
482                             {
483                                 const std::string* ptr =
484                                     std::get_if<std::string>(
485                                         &propertyPair.second);
486 
487                                 if (ptr == nullptr)
488                                 {
489                                     BMCWEB_LOG_ERROR << "Field Illegal "
490                                                      << propertyPair.first;
491                                     messages::internalError(asyncResp->res);
492                                     return;
493                                 }
494                                 // translate from dbus to redfish
495                                 if (*ptr == "WarningHigh")
496                                 {
497                                     (*config)["SetPointOffset"] =
498                                         "UpperThresholdNonCritical";
499                                 }
500                                 else if (*ptr == "WarningLow")
501                                 {
502                                     (*config)["SetPointOffset"] =
503                                         "LowerThresholdNonCritical";
504                                 }
505                                 else if (*ptr == "CriticalHigh")
506                                 {
507                                     (*config)["SetPointOffset"] =
508                                         "UpperThresholdCritical";
509                                 }
510                                 else if (*ptr == "CriticalLow")
511                                 {
512                                     (*config)["SetPointOffset"] =
513                                         "LowerThresholdCritical";
514                                 }
515                                 else
516                                 {
517                                     BMCWEB_LOG_ERROR << "Value Illegal "
518                                                      << *ptr;
519                                     messages::internalError(asyncResp->res);
520                                     return;
521                                 }
522                             }
523                             // doubles
524                             else if (propertyPair.first ==
525                                          "FFGainCoefficient" ||
526                                      propertyPair.first == "FFOffCoefficient" ||
527                                      propertyPair.first == "ICoefficient" ||
528                                      propertyPair.first == "ILimitMax" ||
529                                      propertyPair.first == "ILimitMin" ||
530                                      propertyPair.first ==
531                                          "PositiveHysteresis" ||
532                                      propertyPair.first ==
533                                          "NegativeHysteresis" ||
534                                      propertyPair.first == "OutLimitMax" ||
535                                      propertyPair.first == "OutLimitMin" ||
536                                      propertyPair.first == "PCoefficient" ||
537                                      propertyPair.first == "SetPoint" ||
538                                      propertyPair.first == "SlewNeg" ||
539                                      propertyPair.first == "SlewPos")
540                             {
541                                 const double* ptr =
542                                     std::get_if<double>(&propertyPair.second);
543                                 if (ptr == nullptr)
544                                 {
545                                     BMCWEB_LOG_ERROR << "Field Illegal "
546                                                      << propertyPair.first;
547                                     messages::internalError(asyncResp->res);
548                                     return;
549                                 }
550                                 (*config)[propertyPair.first] = *ptr;
551                             }
552                         }
553                     }
554                 }
555             }
556         },
557         connection, path, objectManagerIface, "GetManagedObjects");
558 }
559 
560 enum class CreatePIDRet
561 {
562     fail,
563     del,
564     patch
565 };
566 
567 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response,
568                                 std::vector<nlohmann::json>& config,
569                                 std::vector<std::string>& zones)
570 {
571     if (config.empty())
572     {
573         BMCWEB_LOG_ERROR << "Empty Zones";
574         messages::propertyValueFormatError(response->res,
575                                            nlohmann::json::array(), "Zones");
576         return false;
577     }
578     for (auto& odata : config)
579     {
580         std::string path;
581         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
582                                           path))
583         {
584             return false;
585         }
586         std::string input;
587 
588         // 8 below comes from
589         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
590         //     0    1     2      3    4    5      6     7      8
591         if (!dbus::utility::getNthStringFromPath(path, 8, input))
592         {
593             BMCWEB_LOG_ERROR << "Got invalid path " << path;
594             BMCWEB_LOG_ERROR << "Illegal Type Zones";
595             messages::propertyValueFormatError(response->res, odata.dump(),
596                                                "Zones");
597             return false;
598         }
599         boost::replace_all(input, "_", " ");
600         zones.emplace_back(std::move(input));
601     }
602     return true;
603 }
604 
605 static const dbus::utility::ManagedItem*
606     findChassis(const dbus::utility::ManagedObjectType& managedObj,
607                 const std::string& value, std::string& chassis)
608 {
609     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
610 
611     std::string escaped = boost::replace_all_copy(value, " ", "_");
612     escaped = "/" + escaped;
613     auto it = std::find_if(
614         managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) {
615             if (boost::algorithm::ends_with(obj.first.str, escaped))
616             {
617                 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n";
618                 return true;
619             }
620             return false;
621         });
622 
623     if (it == managedObj.end())
624     {
625         return nullptr;
626     }
627     // 5 comes from <chassis-name> being the 5th element
628     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
629     if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
630     {
631         return &(*it);
632     }
633 
634     return nullptr;
635 }
636 
637 static CreatePIDRet createPidInterface(
638     const std::shared_ptr<AsyncResp>& response, const std::string& type,
639     nlohmann::json::iterator it, const std::string& path,
640     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
641     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
642         output,
643     std::string& chassis, const std::string& profile)
644 {
645 
646     // common deleter
647     if (it.value() == nullptr)
648     {
649         std::string iface;
650         if (type == "PidControllers" || type == "FanControllers")
651         {
652             iface = pidConfigurationIface;
653         }
654         else if (type == "FanZones")
655         {
656             iface = pidZoneConfigurationIface;
657         }
658         else if (type == "StepwiseControllers")
659         {
660             iface = stepwiseConfigurationIface;
661         }
662         else
663         {
664             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
665                              << type;
666             messages::propertyUnknown(response->res, type);
667             return CreatePIDRet::fail;
668         }
669 
670         BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n";
671         // delete interface
672         crow::connections::systemBus->async_method_call(
673             [response, path](const boost::system::error_code ec) {
674                 if (ec)
675                 {
676                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
677                     messages::internalError(response->res);
678                     return;
679                 }
680                 messages::success(response->res);
681             },
682             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
683         return CreatePIDRet::del;
684     }
685 
686     const dbus::utility::ManagedItem* managedItem = nullptr;
687     if (!createNewObject)
688     {
689         // if we aren't creating a new object, we should be able to find it on
690         // d-bus
691         managedItem = findChassis(managedObj, it.key(), chassis);
692         if (managedItem == nullptr)
693         {
694             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
695             messages::invalidObject(response->res, it.key());
696             return CreatePIDRet::fail;
697         }
698     }
699 
700     if (profile.size() &&
701         (type == "PidControllers" || type == "FanControllers" ||
702          type == "StepwiseControllers"))
703     {
704         if (managedItem == nullptr)
705         {
706             output["Profiles"] = std::vector<std::string>{profile};
707         }
708         else
709         {
710             std::string interface;
711             if (type == "StepwiseControllers")
712             {
713                 interface = stepwiseConfigurationIface;
714             }
715             else
716             {
717                 interface = pidConfigurationIface;
718             }
719             auto findConfig = managedItem->second.find(interface);
720             if (findConfig == managedItem->second.end())
721             {
722                 BMCWEB_LOG_ERROR
723                     << "Failed to find interface in managed object";
724                 messages::internalError(response->res);
725                 return CreatePIDRet::fail;
726             }
727             auto findProfiles = findConfig->second.find("Profiles");
728             if (findProfiles != findConfig->second.end())
729             {
730                 const std::vector<std::string>* curProfiles =
731                     std::get_if<std::vector<std::string>>(
732                         &(findProfiles->second));
733                 if (curProfiles == nullptr)
734                 {
735                     BMCWEB_LOG_ERROR << "Illegal profiles in managed object";
736                     messages::internalError(response->res);
737                     return CreatePIDRet::fail;
738                 }
739                 if (std::find(curProfiles->begin(), curProfiles->end(),
740                               profile) == curProfiles->end())
741                 {
742                     std::vector<std::string> newProfiles = *curProfiles;
743                     newProfiles.push_back(profile);
744                     output["Profiles"] = newProfiles;
745                 }
746             }
747         }
748     }
749 
750     if (type == "PidControllers" || type == "FanControllers")
751     {
752         if (createNewObject)
753         {
754             output["Class"] = type == "PidControllers" ? std::string("temp")
755                                                        : std::string("fan");
756             output["Type"] = std::string("Pid");
757         }
758 
759         std::optional<std::vector<nlohmann::json>> zones;
760         std::optional<std::vector<std::string>> inputs;
761         std::optional<std::vector<std::string>> outputs;
762         std::map<std::string, std::optional<double>> doubles;
763         std::optional<std::string> setpointOffset;
764         if (!redfish::json_util::readJson(
765                 it.value(), response->res, "Inputs", inputs, "Outputs", outputs,
766                 "Zones", zones, "FFGainCoefficient",
767                 doubles["FFGainCoefficient"], "FFOffCoefficient",
768                 doubles["FFOffCoefficient"], "ICoefficient",
769                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
770                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
771                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
772                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
773                 doubles["SetPoint"], "SetPointOffset", setpointOffset,
774                 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"],
775                 "PositiveHysteresis", doubles["PositiveHysteresis"],
776                 "NegativeHysteresis", doubles["NegativeHysteresis"]))
777         {
778             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
779                              << it.value().dump();
780             return CreatePIDRet::fail;
781         }
782         if (zones)
783         {
784             std::vector<std::string> zonesStr;
785             if (!getZonesFromJsonReq(response, *zones, zonesStr))
786             {
787                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
788                 return CreatePIDRet::fail;
789             }
790             if (chassis.empty() &&
791                 !findChassis(managedObj, zonesStr[0], chassis))
792             {
793                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
794                 messages::invalidObject(response->res, it.key());
795                 return CreatePIDRet::fail;
796             }
797 
798             output["Zones"] = std::move(zonesStr);
799         }
800         if (inputs || outputs)
801         {
802             std::array<std::optional<std::vector<std::string>>*, 2> containers =
803                 {&inputs, &outputs};
804             size_t index = 0;
805             for (const auto& containerPtr : containers)
806             {
807                 std::optional<std::vector<std::string>>& container =
808                     *containerPtr;
809                 if (!container)
810                 {
811                     index++;
812                     continue;
813                 }
814 
815                 for (std::string& value : *container)
816                 {
817                     boost::replace_all(value, "_", " ");
818                 }
819                 std::string key;
820                 if (index == 0)
821                 {
822                     key = "Inputs";
823                 }
824                 else
825                 {
826                     key = "Outputs";
827                 }
828                 output[key] = *container;
829                 index++;
830             }
831         }
832 
833         if (setpointOffset)
834         {
835             // translate between redfish and dbus names
836             if (*setpointOffset == "UpperThresholdNonCritical")
837             {
838                 output["SetPointOffset"] = std::string("WarningLow");
839             }
840             else if (*setpointOffset == "LowerThresholdNonCritical")
841             {
842                 output["SetPointOffset"] = std::string("WarningHigh");
843             }
844             else if (*setpointOffset == "LowerThresholdCritical")
845             {
846                 output["SetPointOffset"] = std::string("CriticalLow");
847             }
848             else if (*setpointOffset == "UpperThresholdCritical")
849             {
850                 output["SetPointOffset"] = std::string("CriticalHigh");
851             }
852             else
853             {
854                 BMCWEB_LOG_ERROR << "Invalid setpointoffset "
855                                  << *setpointOffset;
856                 messages::invalidObject(response->res, it.key());
857                 return CreatePIDRet::fail;
858             }
859         }
860 
861         // doubles
862         for (const auto& pairs : doubles)
863         {
864             if (!pairs.second)
865             {
866                 continue;
867             }
868             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
869             output[pairs.first] = *(pairs.second);
870         }
871     }
872 
873     else if (type == "FanZones")
874     {
875         output["Type"] = std::string("Pid.Zone");
876 
877         std::optional<nlohmann::json> chassisContainer;
878         std::optional<double> failSafePercent;
879         std::optional<double> minThermalOutput;
880         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
881                                           chassisContainer, "FailSafePercent",
882                                           failSafePercent, "MinThermalOutput",
883                                           minThermalOutput))
884         {
885             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
886                              << it.value().dump();
887             return CreatePIDRet::fail;
888         }
889 
890         if (chassisContainer)
891         {
892 
893             std::string chassisId;
894             if (!redfish::json_util::readJson(*chassisContainer, response->res,
895                                               "@odata.id", chassisId))
896             {
897                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
898                                  << chassisContainer->dump();
899                 return CreatePIDRet::fail;
900             }
901 
902             // /redfish/v1/chassis/chassis_name/
903             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
904             {
905                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
906                 messages::invalidObject(response->res, chassisId);
907                 return CreatePIDRet::fail;
908             }
909         }
910         if (minThermalOutput)
911         {
912             output["MinThermalOutput"] = *minThermalOutput;
913         }
914         if (failSafePercent)
915         {
916             output["FailSafePercent"] = *failSafePercent;
917         }
918     }
919     else if (type == "StepwiseControllers")
920     {
921         output["Type"] = std::string("Stepwise");
922 
923         std::optional<std::vector<nlohmann::json>> zones;
924         std::optional<std::vector<nlohmann::json>> steps;
925         std::optional<std::vector<std::string>> inputs;
926         std::optional<double> positiveHysteresis;
927         std::optional<double> negativeHysteresis;
928         std::optional<std::string> direction; // upper clipping curve vs lower
929         if (!redfish::json_util::readJson(
930                 it.value(), response->res, "Zones", zones, "Steps", steps,
931                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
932                 "NegativeHysteresis", negativeHysteresis, "Direction",
933                 direction))
934         {
935             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
936                              << it.value().dump();
937             return CreatePIDRet::fail;
938         }
939 
940         if (zones)
941         {
942             std::vector<std::string> zonesStrs;
943             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
944             {
945                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
946                 return CreatePIDRet::fail;
947             }
948             if (chassis.empty() &&
949                 !findChassis(managedObj, zonesStrs[0], chassis))
950             {
951                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
952                 messages::invalidObject(response->res, it.key());
953                 return CreatePIDRet::fail;
954             }
955             output["Zones"] = std::move(zonesStrs);
956         }
957         if (steps)
958         {
959             std::vector<double> readings;
960             std::vector<double> outputs;
961             for (auto& step : *steps)
962             {
963                 double target;
964                 double output;
965 
966                 if (!redfish::json_util::readJson(step, response->res, "Target",
967                                                   target, "Output", output))
968                 {
969                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
970                                      << ", Illegal Property "
971                                      << it.value().dump();
972                     return CreatePIDRet::fail;
973                 }
974                 readings.emplace_back(target);
975                 outputs.emplace_back(output);
976             }
977             output["Reading"] = std::move(readings);
978             output["Output"] = std::move(outputs);
979         }
980         if (inputs)
981         {
982             for (std::string& value : *inputs)
983             {
984                 boost::replace_all(value, "_", " ");
985             }
986             output["Inputs"] = std::move(*inputs);
987         }
988         if (negativeHysteresis)
989         {
990             output["NegativeHysteresis"] = *negativeHysteresis;
991         }
992         if (positiveHysteresis)
993         {
994             output["PositiveHysteresis"] = *positiveHysteresis;
995         }
996         if (direction)
997         {
998             constexpr const std::array<const char*, 2> allowedDirections = {
999                 "Ceiling", "Floor"};
1000             if (std::find(allowedDirections.begin(), allowedDirections.end(),
1001                           *direction) == allowedDirections.end())
1002             {
1003                 messages::propertyValueTypeError(response->res, "Direction",
1004                                                  *direction);
1005                 return CreatePIDRet::fail;
1006             }
1007             output["Class"] = *direction;
1008         }
1009     }
1010     else
1011     {
1012         BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type;
1013         messages::propertyUnknown(response->res, type);
1014         return CreatePIDRet::fail;
1015     }
1016     return CreatePIDRet::patch;
1017 }
1018 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
1019 {
1020 
1021     GetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp) :
1022         asyncResp(asyncResp)
1023 
1024     {
1025     }
1026 
1027     void run()
1028     {
1029         std::shared_ptr<GetPIDValues> self = shared_from_this();
1030 
1031         // get all configurations
1032         crow::connections::systemBus->async_method_call(
1033             [self](const boost::system::error_code ec,
1034                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
1035                 if (ec)
1036                 {
1037                     BMCWEB_LOG_ERROR << ec;
1038                     messages::internalError(self->asyncResp->res);
1039                     return;
1040                 }
1041                 self->subtree = subtree;
1042             },
1043             "xyz.openbmc_project.ObjectMapper",
1044             "/xyz/openbmc_project/object_mapper",
1045             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1046             std::array<const char*, 4>{
1047                 pidConfigurationIface, pidZoneConfigurationIface,
1048                 objectManagerIface, stepwiseConfigurationIface});
1049 
1050         // at the same time get the selected profile
1051         crow::connections::systemBus->async_method_call(
1052             [self](const boost::system::error_code ec,
1053                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
1054                 if (ec || subtree.empty())
1055                 {
1056                     return;
1057                 }
1058                 if (subtree[0].second.size() != 1)
1059                 {
1060                     // invalid mapper response, should never happen
1061                     BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error";
1062                     messages::internalError(self->asyncResp->res);
1063                     return;
1064                 }
1065 
1066                 const std::string& path = subtree[0].first;
1067                 const std::string& owner = subtree[0].second[0].first;
1068                 crow::connections::systemBus->async_method_call(
1069                     [path, owner, self](
1070                         const boost::system::error_code ec,
1071                         const boost::container::flat_map<
1072                             std::string, std::variant<std::vector<std::string>,
1073                                                       std::string>>& resp) {
1074                         if (ec)
1075                         {
1076                             BMCWEB_LOG_ERROR << "GetPIDValues: Can't get "
1077                                                 "thermalModeIface "
1078                                              << path;
1079                             messages::internalError(self->asyncResp->res);
1080                             return;
1081                         }
1082                         const std::string* current = nullptr;
1083                         const std::vector<std::string>* supported = nullptr;
1084                         for (auto& [key, value] : resp)
1085                         {
1086                             if (key == "Current")
1087                             {
1088                                 current = std::get_if<std::string>(&value);
1089                                 if (current == nullptr)
1090                                 {
1091                                     BMCWEB_LOG_ERROR
1092                                         << "GetPIDValues: thermal mode "
1093                                            "iface invalid "
1094                                         << path;
1095                                     messages::internalError(
1096                                         self->asyncResp->res);
1097                                     return;
1098                                 }
1099                             }
1100                             if (key == "Supported")
1101                             {
1102                                 supported =
1103                                     std::get_if<std::vector<std::string>>(
1104                                         &value);
1105                                 if (supported == nullptr)
1106                                 {
1107                                     BMCWEB_LOG_ERROR
1108                                         << "GetPIDValues: thermal mode "
1109                                            "iface invalid"
1110                                         << path;
1111                                     messages::internalError(
1112                                         self->asyncResp->res);
1113                                     return;
1114                                 }
1115                             }
1116                         }
1117                         if (current == nullptr || supported == nullptr)
1118                         {
1119                             BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode "
1120                                                 "iface invalid "
1121                                              << path;
1122                             messages::internalError(self->asyncResp->res);
1123                             return;
1124                         }
1125                         self->currentProfile = *current;
1126                         self->supportedProfiles = *supported;
1127                     },
1128                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1129                     thermalModeIface);
1130             },
1131             "xyz.openbmc_project.ObjectMapper",
1132             "/xyz/openbmc_project/object_mapper",
1133             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1134             std::array<const char*, 1>{thermalModeIface});
1135     }
1136 
1137     ~GetPIDValues()
1138     {
1139         if (asyncResp->res.result() != boost::beast::http::status::ok)
1140         {
1141             return;
1142         }
1143         // create map of <connection, path to objMgr>>
1144         boost::container::flat_map<std::string, std::string> objectMgrPaths;
1145         boost::container::flat_set<std::string> calledConnections;
1146         for (const auto& pathGroup : subtree)
1147         {
1148             for (const auto& connectionGroup : pathGroup.second)
1149             {
1150                 auto findConnection =
1151                     calledConnections.find(connectionGroup.first);
1152                 if (findConnection != calledConnections.end())
1153                 {
1154                     break;
1155                 }
1156                 for (const std::string& interface : connectionGroup.second)
1157                 {
1158                     if (interface == objectManagerIface)
1159                     {
1160                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1161                     }
1162                     // this list is alphabetical, so we
1163                     // should have found the objMgr by now
1164                     if (interface == pidConfigurationIface ||
1165                         interface == pidZoneConfigurationIface ||
1166                         interface == stepwiseConfigurationIface)
1167                     {
1168                         auto findObjMgr =
1169                             objectMgrPaths.find(connectionGroup.first);
1170                         if (findObjMgr == objectMgrPaths.end())
1171                         {
1172                             BMCWEB_LOG_DEBUG << connectionGroup.first
1173                                              << "Has no Object Manager";
1174                             continue;
1175                         }
1176 
1177                         calledConnections.insert(connectionGroup.first);
1178 
1179                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1180                                          currentProfile, supportedProfiles,
1181                                          asyncResp);
1182                         break;
1183                     }
1184                 }
1185             }
1186         }
1187     }
1188 
1189     std::vector<std::string> supportedProfiles;
1190     std::string currentProfile;
1191     crow::openbmc_mapper::GetSubTreeType subtree;
1192     std::shared_ptr<AsyncResp> asyncResp;
1193 };
1194 
1195 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1196 {
1197 
1198     SetPIDValues(const std::shared_ptr<AsyncResp>& asyncRespIn,
1199                  nlohmann::json& data) :
1200         asyncResp(asyncRespIn)
1201     {
1202 
1203         std::optional<nlohmann::json> pidControllers;
1204         std::optional<nlohmann::json> fanControllers;
1205         std::optional<nlohmann::json> fanZones;
1206         std::optional<nlohmann::json> stepwiseControllers;
1207 
1208         if (!redfish::json_util::readJson(
1209                 data, asyncResp->res, "PidControllers", pidControllers,
1210                 "FanControllers", fanControllers, "FanZones", fanZones,
1211                 "StepwiseControllers", stepwiseControllers, "Profile", profile))
1212         {
1213             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1214                              << data.dump();
1215             return;
1216         }
1217         configuration.emplace_back("PidControllers", std::move(pidControllers));
1218         configuration.emplace_back("FanControllers", std::move(fanControllers));
1219         configuration.emplace_back("FanZones", std::move(fanZones));
1220         configuration.emplace_back("StepwiseControllers",
1221                                    std::move(stepwiseControllers));
1222     }
1223     void run()
1224     {
1225         if (asyncResp->res.result() != boost::beast::http::status::ok)
1226         {
1227             return;
1228         }
1229 
1230         std::shared_ptr<SetPIDValues> self = shared_from_this();
1231 
1232         // todo(james): might make sense to do a mapper call here if this
1233         // interface gets more traction
1234         crow::connections::systemBus->async_method_call(
1235             [self](const boost::system::error_code ec,
1236                    dbus::utility::ManagedObjectType& mObj) {
1237                 if (ec)
1238                 {
1239                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
1240                     messages::internalError(self->asyncResp->res);
1241                     return;
1242                 }
1243                 const std::array<const char*, 3> configurations = {
1244                     pidConfigurationIface, pidZoneConfigurationIface,
1245                     stepwiseConfigurationIface};
1246 
1247                 // erase the paths we don't care about
1248                 for (auto it = mObj.begin(); it != mObj.end();)
1249                 {
1250                     bool found = false;
1251                     for (const auto& [interface, _] : it->second)
1252                     {
1253                         if (std::find(configurations.begin(),
1254                                       configurations.end(),
1255                                       interface) != configurations.end())
1256                         {
1257                             found = true;
1258                             it++;
1259                             break;
1260                         }
1261                     }
1262                     if (!found)
1263                     {
1264                         it = mObj.erase(it);
1265                     }
1266                 }
1267                 self->managedObj = std::move(mObj);
1268             },
1269             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
1270             "GetManagedObjects");
1271 
1272         // at the same time get the profile information
1273         crow::connections::systemBus->async_method_call(
1274             [self](const boost::system::error_code ec,
1275                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
1276                 if (ec || subtree.empty())
1277                 {
1278                     return;
1279                 }
1280                 if (subtree[0].second.empty())
1281                 {
1282                     // invalid mapper response, should never happen
1283                     BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error";
1284                     messages::internalError(self->asyncResp->res);
1285                     return;
1286                 }
1287 
1288                 const std::string& path = subtree[0].first;
1289                 const std::string& owner = subtree[0].second[0].first;
1290                 crow::connections::systemBus->async_method_call(
1291                     [self, path, owner](
1292                         const boost::system::error_code ec,
1293                         const boost::container::flat_map<
1294                             std::string, std::variant<std::vector<std::string>,
1295                                                       std::string>>& r) {
1296                         if (ec)
1297                         {
1298                             BMCWEB_LOG_ERROR << "SetPIDValues: Can't get "
1299                                                 "thermalModeIface "
1300                                              << path;
1301                             messages::internalError(self->asyncResp->res);
1302                             return;
1303                         }
1304                         const std::string* current = nullptr;
1305                         const std::vector<std::string>* supported = nullptr;
1306                         for (auto& [key, value] : r)
1307                         {
1308                             if (key == "Current")
1309                             {
1310                                 current = std::get_if<std::string>(&value);
1311                                 if (current == nullptr)
1312                                 {
1313                                     BMCWEB_LOG_ERROR
1314                                         << "SetPIDValues: thermal mode "
1315                                            "iface invalid "
1316                                         << path;
1317                                     messages::internalError(
1318                                         self->asyncResp->res);
1319                                     return;
1320                                 }
1321                             }
1322                             if (key == "Supported")
1323                             {
1324                                 supported =
1325                                     std::get_if<std::vector<std::string>>(
1326                                         &value);
1327                                 if (supported == nullptr)
1328                                 {
1329                                     BMCWEB_LOG_ERROR
1330                                         << "SetPIDValues: thermal mode "
1331                                            "iface invalid"
1332                                         << path;
1333                                     messages::internalError(
1334                                         self->asyncResp->res);
1335                                     return;
1336                                 }
1337                             }
1338                         }
1339                         if (current == nullptr || supported == nullptr)
1340                         {
1341                             BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode "
1342                                                 "iface invalid "
1343                                              << path;
1344                             messages::internalError(self->asyncResp->res);
1345                             return;
1346                         }
1347                         self->currentProfile = *current;
1348                         self->supportedProfiles = *supported;
1349                         self->profileConnection = owner;
1350                         self->profilePath = path;
1351                     },
1352                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1353                     thermalModeIface);
1354             },
1355             "xyz.openbmc_project.ObjectMapper",
1356             "/xyz/openbmc_project/object_mapper",
1357             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1358             std::array<const char*, 1>{thermalModeIface});
1359     }
1360     ~SetPIDValues()
1361     {
1362         if (asyncResp->res.result() != boost::beast::http::status::ok)
1363         {
1364             return;
1365         }
1366 
1367         std::shared_ptr<AsyncResp> response = asyncResp;
1368 
1369         if (profile)
1370         {
1371             if (std::find(supportedProfiles.begin(), supportedProfiles.end(),
1372                           *profile) == supportedProfiles.end())
1373             {
1374                 messages::actionParameterUnknown(response->res, "Profile",
1375                                                  *profile);
1376                 return;
1377             }
1378             currentProfile = *profile;
1379             crow::connections::systemBus->async_method_call(
1380                 [response](const boost::system::error_code ec) {
1381                     if (ec)
1382                     {
1383                         BMCWEB_LOG_ERROR << "Error patching profile" << ec;
1384                         messages::internalError(response->res);
1385                     }
1386                 },
1387                 profileConnection, profilePath,
1388                 "org.freedesktop.DBus.Properties", "Set", thermalModeIface,
1389                 "Current", std::variant<std::string>(*profile));
1390         }
1391 
1392         for (auto& containerPair : configuration)
1393         {
1394             auto& container = containerPair.second;
1395             if (!container)
1396             {
1397                 continue;
1398             }
1399             BMCWEB_LOG_DEBUG << *container;
1400 
1401             std::string& type = containerPair.first;
1402 
1403             for (nlohmann::json::iterator it = container->begin();
1404                  it != container->end(); it++)
1405             {
1406                 const auto& name = it.key();
1407                 BMCWEB_LOG_DEBUG << "looking for " << name;
1408 
1409                 auto pathItr =
1410                     std::find_if(managedObj.begin(), managedObj.end(),
1411                                  [&name](const auto& obj) {
1412                                      return boost::algorithm::ends_with(
1413                                          obj.first.str, "/" + name);
1414                                  });
1415                 boost::container::flat_map<std::string,
1416                                            dbus::utility::DbusVariantType>
1417                     output;
1418 
1419                 output.reserve(16); // The pid interface length
1420 
1421                 // determines if we're patching entity-manager or
1422                 // creating a new object
1423                 bool createNewObject = (pathItr == managedObj.end());
1424                 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject;
1425 
1426                 std::string iface;
1427                 if (type == "PidControllers" || type == "FanControllers")
1428                 {
1429                     iface = pidConfigurationIface;
1430                     if (!createNewObject &&
1431                         pathItr->second.find(pidConfigurationIface) ==
1432                             pathItr->second.end())
1433                     {
1434                         createNewObject = true;
1435                     }
1436                 }
1437                 else if (type == "FanZones")
1438                 {
1439                     iface = pidZoneConfigurationIface;
1440                     if (!createNewObject &&
1441                         pathItr->second.find(pidZoneConfigurationIface) ==
1442                             pathItr->second.end())
1443                     {
1444 
1445                         createNewObject = true;
1446                     }
1447                 }
1448                 else if (type == "StepwiseControllers")
1449                 {
1450                     iface = stepwiseConfigurationIface;
1451                     if (!createNewObject &&
1452                         pathItr->second.find(stepwiseConfigurationIface) ==
1453                             pathItr->second.end())
1454                     {
1455                         createNewObject = true;
1456                     }
1457                 }
1458 
1459                 if (createNewObject && it.value() == nullptr)
1460                 {
1461                     // can't delete a non-existant object
1462                     messages::invalidObject(response->res, name);
1463                     continue;
1464                 }
1465 
1466                 std::string path;
1467                 if (pathItr != managedObj.end())
1468                 {
1469                     path = pathItr->first.str;
1470                 }
1471 
1472                 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n";
1473 
1474                 // arbitrary limit to avoid attacks
1475                 constexpr const size_t controllerLimit = 500;
1476                 if (createNewObject && managedObj.size() >= controllerLimit)
1477                 {
1478                     messages::resourceExhaustion(response->res, type);
1479                     continue;
1480                 }
1481 
1482                 output["Name"] = boost::replace_all_copy(name, "_", " ");
1483 
1484                 std::string chassis;
1485                 CreatePIDRet ret = createPidInterface(
1486                     response, type, it, path, managedObj, createNewObject,
1487                     output, chassis, currentProfile);
1488                 if (ret == CreatePIDRet::fail)
1489                 {
1490                     return;
1491                 }
1492                 else if (ret == CreatePIDRet::del)
1493                 {
1494                     continue;
1495                 }
1496 
1497                 if (!createNewObject)
1498                 {
1499                     for (const auto& property : output)
1500                     {
1501                         crow::connections::systemBus->async_method_call(
1502                             [response,
1503                              propertyName{std::string(property.first)}](
1504                                 const boost::system::error_code ec) {
1505                                 if (ec)
1506                                 {
1507                                     BMCWEB_LOG_ERROR << "Error patching "
1508                                                      << propertyName << ": "
1509                                                      << ec;
1510                                     messages::internalError(response->res);
1511                                     return;
1512                                 }
1513                                 messages::success(response->res);
1514                             },
1515                             "xyz.openbmc_project.EntityManager", path,
1516                             "org.freedesktop.DBus.Properties", "Set", iface,
1517                             property.first, property.second);
1518                     }
1519                 }
1520                 else
1521                 {
1522                     if (chassis.empty())
1523                     {
1524                         BMCWEB_LOG_ERROR << "Failed to get chassis from config";
1525                         messages::invalidObject(response->res, name);
1526                         return;
1527                     }
1528 
1529                     bool foundChassis = false;
1530                     for (const auto& obj : managedObj)
1531                     {
1532                         if (boost::algorithm::ends_with(obj.first.str, chassis))
1533                         {
1534                             chassis = obj.first.str;
1535                             foundChassis = true;
1536                             break;
1537                         }
1538                     }
1539                     if (!foundChassis)
1540                     {
1541                         BMCWEB_LOG_ERROR << "Failed to find chassis on dbus";
1542                         messages::resourceMissingAtURI(
1543                             response->res, "/redfish/v1/Chassis/" + chassis);
1544                         return;
1545                     }
1546 
1547                     crow::connections::systemBus->async_method_call(
1548                         [response](const boost::system::error_code ec) {
1549                             if (ec)
1550                             {
1551                                 BMCWEB_LOG_ERROR << "Error Adding Pid Object "
1552                                                  << ec;
1553                                 messages::internalError(response->res);
1554                                 return;
1555                             }
1556                             messages::success(response->res);
1557                         },
1558                         "xyz.openbmc_project.EntityManager", chassis,
1559                         "xyz.openbmc_project.AddObject", "AddObject", output);
1560                 }
1561             }
1562         }
1563     }
1564     std::shared_ptr<AsyncResp> asyncResp;
1565     std::vector<std::pair<std::string, std::optional<nlohmann::json>>>
1566         configuration;
1567     std::optional<std::string> profile;
1568     dbus::utility::ManagedObjectType managedObj;
1569     std::vector<std::string> supportedProfiles;
1570     std::string currentProfile;
1571     std::string profileConnection;
1572     std::string profilePath;
1573 };
1574 
1575 class Manager : public Node
1576 {
1577   public:
1578     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
1579     {
1580         uuid = app.template getMiddleware<crow::persistent_data::Middleware>()
1581                    .systemUuid;
1582         entityPrivileges = {
1583             {boost::beast::http::verb::get, {{"Login"}}},
1584             {boost::beast::http::verb::head, {{"Login"}}},
1585             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1586             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1587             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1588             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1589     }
1590 
1591   private:
1592     void doGet(crow::Response& res, const crow::Request& req,
1593                const std::vector<std::string>& params) override
1594     {
1595         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
1596         res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager";
1597         res.jsonValue["@odata.context"] =
1598             "/redfish/v1/$metadata#Manager.Manager";
1599         res.jsonValue["Id"] = "bmc";
1600         res.jsonValue["Name"] = "OpenBmc Manager";
1601         res.jsonValue["Description"] = "Baseboard Management Controller";
1602         res.jsonValue["PowerState"] = "On";
1603         res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}};
1604         res.jsonValue["ManagerType"] = "BMC";
1605         res.jsonValue["UUID"] = systemd_utils::getUuid();
1606         res.jsonValue["ServiceEntryPointUUID"] = uuid;
1607         res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
1608 
1609         res.jsonValue["LogServices"] = {
1610             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
1611 
1612         res.jsonValue["NetworkProtocol"] = {
1613             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
1614 
1615         res.jsonValue["EthernetInterfaces"] = {
1616             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
1617 
1618 #ifdef BMCWEB_ENABLE_VM_NBDPROXY
1619         res.jsonValue["VirtualMedia"] = {
1620             {"@odata.id", "/redfish/v1/Managers/bmc/VirtualMedia"}};
1621 #endif // BMCWEB_ENABLE_VM_NBDPROXY
1622 
1623         // default oem data
1624         nlohmann::json& oem = res.jsonValue["Oem"];
1625         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
1626         oem["@odata.type"] = "#OemManager.Oem";
1627         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
1628         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
1629         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
1630         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
1631         oemOpenbmc["@odata.context"] =
1632             "/redfish/v1/$metadata#OemManager.OpenBmc";
1633         oemOpenbmc["Certificates"] = {
1634             {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates"}};
1635 
1636         // Update Actions object.
1637         nlohmann::json& manager_reset =
1638             res.jsonValue["Actions"]["#Manager.Reset"];
1639         manager_reset["target"] =
1640             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
1641         manager_reset["ResetType@Redfish.AllowableValues"] = {
1642             "GracefulRestart"};
1643 
1644         res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
1645 
1646         // Fill in SerialConsole info
1647         res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
1648         res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15;
1649         res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI",
1650                                                                    "SSH"};
1651 #ifdef BMCWEB_ENABLE_KVM
1652         // Fill in GraphicalConsole info
1653         res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true;
1654         res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] = 4;
1655         res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"};
1656 #endif // BMCWEB_ENABLE_KVM
1657 
1658         res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
1659         res.jsonValue["Links"]["ManagerForServers"] = {
1660             {{"@odata.id", "/redfish/v1/Systems/system"}}};
1661 
1662         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1663 
1664         auto health = std::make_shared<HealthPopulate>(asyncResp);
1665         health->isManagersHealth = true;
1666         health->populate();
1667 
1668         fw_util::getActiveFwVersion(asyncResp, fw_util::bmcPurpose,
1669                                     "FirmwareVersion");
1670 
1671         auto pids = std::make_shared<GetPIDValues>(asyncResp);
1672         pids->run();
1673 
1674         getMainChassisId(asyncResp, [](const std::string& chassisId,
1675                                        const std::shared_ptr<AsyncResp> aRsp) {
1676             aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1;
1677             aRsp->res.jsonValue["Links"]["ManagerForChassis"] = {
1678                 {{"@odata.id", "/redfish/v1/Chassis/" + chassisId}}};
1679             aRsp->res.jsonValue["Links"]["ManagerInChassis"] = {
1680                 {"@odata.id", "/redfish/v1/Chassis/" + chassisId}};
1681         });
1682 
1683         static bool started = false;
1684 
1685         if (!started)
1686         {
1687             crow::connections::systemBus->async_method_call(
1688                 [asyncResp](const boost::system::error_code ec,
1689                             const std::variant<double>& resp) {
1690                     if (ec)
1691                     {
1692                         BMCWEB_LOG_ERROR << "Error while getting progress";
1693                         messages::internalError(asyncResp->res);
1694                         return;
1695                     }
1696                     const double* val = std::get_if<double>(&resp);
1697                     if (val == nullptr)
1698                     {
1699                         BMCWEB_LOG_ERROR
1700                             << "Invalid response while getting progress";
1701                         messages::internalError(asyncResp->res);
1702                         return;
1703                     }
1704                     if (*val < 1.0)
1705                     {
1706                         asyncResp->res.jsonValue["Status"]["State"] =
1707                             "Starting";
1708                         started = true;
1709                     }
1710                 },
1711                 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1712                 "org.freedesktop.DBus.Properties", "Get",
1713                 "org.freedesktop.systemd1.Manager", "Progress");
1714         }
1715     }
1716 
1717     void doPatch(crow::Response& res, const crow::Request& req,
1718                  const std::vector<std::string>& params) override
1719     {
1720         std::optional<nlohmann::json> oem;
1721         std::optional<std::string> datetime;
1722         std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res);
1723 
1724         if (!json_util::readJson(req, response->res, "Oem", oem, "DateTime",
1725                                  datetime))
1726         {
1727             return;
1728         }
1729 
1730         if (oem)
1731         {
1732             std::optional<nlohmann::json> openbmc;
1733             if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc))
1734             {
1735                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1736                                  << oem->dump();
1737                 return;
1738             }
1739             if (openbmc)
1740             {
1741                 std::optional<nlohmann::json> fan;
1742                 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan))
1743                 {
1744                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1745                                      << ", Illegal Property "
1746                                      << openbmc->dump();
1747                     return;
1748                 }
1749                 if (fan)
1750                 {
1751                     auto pid = std::make_shared<SetPIDValues>(response, *fan);
1752                     pid->run();
1753                 }
1754             }
1755         }
1756         if (datetime)
1757         {
1758             setDateTime(response, std::move(*datetime));
1759         }
1760     }
1761 
1762     void setDateTime(std::shared_ptr<AsyncResp> aResp,
1763                      std::string datetime) const
1764     {
1765         BMCWEB_LOG_DEBUG << "Set date time: " << datetime;
1766 
1767         std::stringstream stream(datetime);
1768         // Convert from ISO 8601 to boost local_time
1769         // (BMC only has time in UTC)
1770         boost::posix_time::ptime posixTime;
1771         boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
1772         // Facet gets deleted with the stringsteam
1773         auto ifc = std::make_unique<boost::local_time::local_time_input_facet>(
1774             "%Y-%m-%d %H:%M:%S%F %ZP");
1775         stream.imbue(std::locale(stream.getloc(), ifc.release()));
1776 
1777         boost::local_time::local_date_time ldt(
1778             boost::local_time::not_a_date_time);
1779 
1780         if (stream >> ldt)
1781         {
1782             posixTime = ldt.utc_time();
1783             boost::posix_time::time_duration dur = posixTime - epoch;
1784             uint64_t durMicroSecs =
1785                 static_cast<uint64_t>(dur.total_microseconds());
1786             crow::connections::systemBus->async_method_call(
1787                 [aResp{std::move(aResp)}, datetime{std::move(datetime)}](
1788                     const boost::system::error_code ec) {
1789                     if (ec)
1790                     {
1791                         BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
1792                                             "DBUS response error "
1793                                          << ec;
1794                         messages::internalError(aResp->res);
1795                         return;
1796                     }
1797                     aResp->res.jsonValue["DateTime"] = datetime;
1798                 },
1799                 "xyz.openbmc_project.Time.Manager",
1800                 "/xyz/openbmc_project/time/bmc",
1801                 "org.freedesktop.DBus.Properties", "Set",
1802                 "xyz.openbmc_project.Time.EpochTime", "Elapsed",
1803                 std::variant<uint64_t>(durMicroSecs));
1804         }
1805         else
1806         {
1807             messages::propertyValueFormatError(aResp->res, datetime,
1808                                                "DateTime");
1809             return;
1810         }
1811     }
1812 
1813     std::string uuid;
1814 };
1815 
1816 class ManagerCollection : public Node
1817 {
1818   public:
1819     ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
1820     {
1821         entityPrivileges = {
1822             {boost::beast::http::verb::get, {{"Login"}}},
1823             {boost::beast::http::verb::head, {{"Login"}}},
1824             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1825             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1826             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1827             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1828     }
1829 
1830   private:
1831     void doGet(crow::Response& res, const crow::Request& req,
1832                const std::vector<std::string>& params) override
1833     {
1834         // Collections don't include the static data added by SubRoute
1835         // because it has a duplicate entry for members
1836         res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
1837         res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection";
1838         res.jsonValue["@odata.context"] =
1839             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
1840         res.jsonValue["Name"] = "Manager Collection";
1841         res.jsonValue["Members@odata.count"] = 1;
1842         res.jsonValue["Members"] = {
1843             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
1844         res.end();
1845     }
1846 };
1847 } // namespace redfish
1848