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