xref: /openbmc/bmcweb/redfish-core/lib/managers.hpp (revision b2ba30728fc644b206af43b3f807098b5fa470b6)
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 "app.hpp"
19 #include "dbus_utility.hpp"
20 #include "health.hpp"
21 #include "query.hpp"
22 #include "redfish_util.hpp"
23 #include "registries/privilege_registry.hpp"
24 #include "utils/dbus_utils.hpp"
25 #include "utils/json_utils.hpp"
26 #include "utils/sw_utils.hpp"
27 #include "utils/systemd_utils.hpp"
28 #include "utils/time_utils.hpp"
29 
30 #include <boost/system/error_code.hpp>
31 #include <sdbusplus/asio/property.hpp>
32 #include <sdbusplus/unpack_properties.hpp>
33 
34 #include <algorithm>
35 #include <array>
36 #include <cstdint>
37 #include <memory>
38 #include <sstream>
39 #include <string_view>
40 #include <variant>
41 
42 namespace redfish
43 {
44 
45 /**
46  * Function reboots the BMC.
47  *
48  * @param[in] asyncResp - Shared pointer for completing asynchronous calls
49  */
50 inline void
51     doBMCGracefulRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
52 {
53     const char* processName = "xyz.openbmc_project.State.BMC";
54     const char* objectPath = "/xyz/openbmc_project/state/bmc0";
55     const char* interfaceName = "xyz.openbmc_project.State.BMC";
56     const std::string& propertyValue =
57         "xyz.openbmc_project.State.BMC.Transition.Reboot";
58     const char* destProperty = "RequestedBMCTransition";
59 
60     // Create the D-Bus variant for D-Bus call.
61     dbus::utility::DbusVariantType dbusPropertyValue(propertyValue);
62 
63     crow::connections::systemBus->async_method_call(
64         [asyncResp](const boost::system::error_code& ec) {
65         // Use "Set" method to set the property value.
66         if (ec)
67         {
68             BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec;
69             messages::internalError(asyncResp->res);
70             return;
71         }
72 
73         messages::success(asyncResp->res);
74         },
75         processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
76         interfaceName, destProperty, dbusPropertyValue);
77 }
78 
79 inline void
80     doBMCForceRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
81 {
82     const char* processName = "xyz.openbmc_project.State.BMC";
83     const char* objectPath = "/xyz/openbmc_project/state/bmc0";
84     const char* interfaceName = "xyz.openbmc_project.State.BMC";
85     const std::string& propertyValue =
86         "xyz.openbmc_project.State.BMC.Transition.HardReboot";
87     const char* destProperty = "RequestedBMCTransition";
88 
89     // Create the D-Bus variant for D-Bus call.
90     dbus::utility::DbusVariantType dbusPropertyValue(propertyValue);
91 
92     crow::connections::systemBus->async_method_call(
93         [asyncResp](const boost::system::error_code& ec) {
94         // Use "Set" method to set the property value.
95         if (ec)
96         {
97             BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec;
98             messages::internalError(asyncResp->res);
99             return;
100         }
101 
102         messages::success(asyncResp->res);
103         },
104         processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
105         interfaceName, destProperty, dbusPropertyValue);
106 }
107 
108 /**
109  * ManagerResetAction class supports the POST method for the Reset (reboot)
110  * action.
111  */
112 inline void requestRoutesManagerResetAction(App& app)
113 {
114     /**
115      * Function handles POST method request.
116      * Analyzes POST body before sending Reset (Reboot) request data to D-Bus.
117      * OpenBMC supports ResetType "GracefulRestart" and "ForceRestart".
118      */
119 
120     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
121         .privileges(redfish::privileges::postManager)
122         .methods(boost::beast::http::verb::post)(
123             [&app](const crow::Request& req,
124                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
125         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
126         {
127             return;
128         }
129         BMCWEB_LOG_DEBUG << "Post Manager Reset.";
130 
131         std::string resetType;
132 
133         if (!json_util::readJsonAction(req, asyncResp->res, "ResetType",
134                                        resetType))
135         {
136             return;
137         }
138 
139         if (resetType == "GracefulRestart")
140         {
141             BMCWEB_LOG_DEBUG << "Proceeding with " << resetType;
142             doBMCGracefulRestart(asyncResp);
143             return;
144         }
145         if (resetType == "ForceRestart")
146         {
147             BMCWEB_LOG_DEBUG << "Proceeding with " << resetType;
148             doBMCForceRestart(asyncResp);
149             return;
150         }
151         BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: "
152                          << resetType;
153         messages::actionParameterNotSupported(asyncResp->res, resetType,
154                                               "ResetType");
155 
156         return;
157         });
158 }
159 
160 /**
161  * ManagerResetToDefaultsAction class supports POST method for factory reset
162  * action.
163  */
164 inline void requestRoutesManagerResetToDefaultsAction(App& app)
165 {
166     /**
167      * Function handles ResetToDefaults POST method request.
168      *
169      * Analyzes POST body message and factory resets BMC by calling
170      * BMC code updater factory reset followed by a BMC reboot.
171      *
172      * BMC code updater factory reset wipes the whole BMC read-write
173      * filesystem which includes things like the network settings.
174      *
175      * OpenBMC only supports ResetToDefaultsType "ResetAll".
176      */
177 
178     BMCWEB_ROUTE(app,
179                  "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults/")
180         .privileges(redfish::privileges::postManager)
181         .methods(boost::beast::http::verb::post)(
182             [&app](const crow::Request& req,
183                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
184         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
185         {
186             return;
187         }
188         BMCWEB_LOG_DEBUG << "Post ResetToDefaults.";
189 
190         std::string resetType;
191 
192         if (!json_util::readJsonAction(req, asyncResp->res,
193                                        "ResetToDefaultsType", resetType))
194         {
195             BMCWEB_LOG_DEBUG << "Missing property ResetToDefaultsType.";
196 
197             messages::actionParameterMissing(asyncResp->res, "ResetToDefaults",
198                                              "ResetToDefaultsType");
199             return;
200         }
201 
202         if (resetType != "ResetAll")
203         {
204             BMCWEB_LOG_DEBUG
205                 << "Invalid property value for ResetToDefaultsType: "
206                 << resetType;
207             messages::actionParameterNotSupported(asyncResp->res, resetType,
208                                                   "ResetToDefaultsType");
209             return;
210         }
211 
212         crow::connections::systemBus->async_method_call(
213             [asyncResp](const boost::system::error_code& ec) {
214             if (ec)
215             {
216                 BMCWEB_LOG_DEBUG << "Failed to ResetToDefaults: " << ec;
217                 messages::internalError(asyncResp->res);
218                 return;
219             }
220             // Factory Reset doesn't actually happen until a reboot
221             // Can't erase what the BMC is running on
222             doBMCGracefulRestart(asyncResp);
223             },
224             "xyz.openbmc_project.Software.BMC.Updater",
225             "/xyz/openbmc_project/software",
226             "xyz.openbmc_project.Common.FactoryReset", "Reset");
227         });
228 }
229 
230 /**
231  * ManagerResetActionInfo derived class for delivering Manager
232  * ResetType AllowableValues using ResetInfo schema.
233  */
234 inline void requestRoutesManagerResetActionInfo(App& app)
235 {
236     /**
237      * Functions triggers appropriate requests on DBus
238      */
239 
240     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/ResetActionInfo/")
241         .privileges(redfish::privileges::getActionInfo)
242         .methods(boost::beast::http::verb::get)(
243             [&app](const crow::Request& req,
244                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
245         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
246         {
247             return;
248         }
249 
250         asyncResp->res.jsonValue["@odata.type"] =
251             "#ActionInfo.v1_1_2.ActionInfo";
252         asyncResp->res.jsonValue["@odata.id"] =
253             "/redfish/v1/Managers/bmc/ResetActionInfo";
254         asyncResp->res.jsonValue["Name"] = "Reset Action Info";
255         asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
256         nlohmann::json::object_t parameter;
257         parameter["Name"] = "ResetType";
258         parameter["Required"] = true;
259         parameter["DataType"] = "String";
260 
261         nlohmann::json::array_t allowableValues;
262         allowableValues.emplace_back("GracefulRestart");
263         allowableValues.emplace_back("ForceRestart");
264         parameter["AllowableValues"] = std::move(allowableValues);
265 
266         nlohmann::json::array_t parameters;
267         parameters.emplace_back(std::move(parameter));
268 
269         asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
270         });
271 }
272 
273 static constexpr const char* objectManagerIface =
274     "org.freedesktop.DBus.ObjectManager";
275 static constexpr const char* pidConfigurationIface =
276     "xyz.openbmc_project.Configuration.Pid";
277 static constexpr const char* pidZoneConfigurationIface =
278     "xyz.openbmc_project.Configuration.Pid.Zone";
279 static constexpr const char* stepwiseConfigurationIface =
280     "xyz.openbmc_project.Configuration.Stepwise";
281 static constexpr const char* thermalModeIface =
282     "xyz.openbmc_project.Control.ThermalMode";
283 
284 inline void
285     asyncPopulatePid(const std::string& connection, const std::string& path,
286                      const std::string& currentProfile,
287                      const std::vector<std::string>& supportedProfiles,
288                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
289 {
290     crow::connections::systemBus->async_method_call(
291         [asyncResp, currentProfile, supportedProfiles](
292             const boost::system::error_code& ec,
293             const dbus::utility::ManagedObjectType& managedObj) {
294         if (ec)
295         {
296             BMCWEB_LOG_ERROR << ec;
297             asyncResp->res.jsonValue.clear();
298             messages::internalError(asyncResp->res);
299             return;
300         }
301         nlohmann::json& configRoot =
302             asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
303         nlohmann::json& fans = configRoot["FanControllers"];
304         fans["@odata.type"] = "#OemManager.FanControllers";
305         fans["@odata.id"] =
306             "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanControllers";
307 
308         nlohmann::json& pids = configRoot["PidControllers"];
309         pids["@odata.type"] = "#OemManager.PidControllers";
310         pids["@odata.id"] =
311             "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
312 
313         nlohmann::json& stepwise = configRoot["StepwiseControllers"];
314         stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
315         stepwise["@odata.id"] =
316             "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
317 
318         nlohmann::json& zones = configRoot["FanZones"];
319         zones["@odata.id"] =
320             "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
321         zones["@odata.type"] = "#OemManager.FanZones";
322         configRoot["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
323         configRoot["@odata.type"] = "#OemManager.Fan";
324         configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
325 
326         if (!currentProfile.empty())
327         {
328             configRoot["Profile"] = currentProfile;
329         }
330         BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !";
331 
332         for (const auto& pathPair : managedObj)
333         {
334             for (const auto& intfPair : pathPair.second)
335             {
336                 if (intfPair.first != pidConfigurationIface &&
337                     intfPair.first != pidZoneConfigurationIface &&
338                     intfPair.first != stepwiseConfigurationIface)
339                 {
340                     continue;
341                 }
342 
343                 std::string name;
344 
345                 for (const std::pair<std::string,
346                                      dbus::utility::DbusVariantType>& propPair :
347                      intfPair.second)
348                 {
349                     if (propPair.first == "Name")
350                     {
351                         const std::string* namePtr =
352                             std::get_if<std::string>(&propPair.second);
353                         if (namePtr == nullptr)
354                         {
355                             BMCWEB_LOG_ERROR << "Pid Name Field illegal";
356                             messages::internalError(asyncResp->res);
357                             return;
358                         }
359                         name = *namePtr;
360                         dbus::utility::escapePathForDbus(name);
361                     }
362                     else if (propPair.first == "Profiles")
363                     {
364                         const std::vector<std::string>* profiles =
365                             std::get_if<std::vector<std::string>>(
366                                 &propPair.second);
367                         if (profiles == nullptr)
368                         {
369                             BMCWEB_LOG_ERROR << "Pid Profiles Field illegal";
370                             messages::internalError(asyncResp->res);
371                             return;
372                         }
373                         if (std::find(profiles->begin(), profiles->end(),
374                                       currentProfile) == profiles->end())
375                         {
376                             BMCWEB_LOG_INFO
377                                 << name << " not supported in current profile";
378                             continue;
379                         }
380                     }
381                 }
382                 nlohmann::json* config = nullptr;
383                 const std::string* classPtr = nullptr;
384 
385                 for (const std::pair<std::string,
386                                      dbus::utility::DbusVariantType>& propPair :
387                      intfPair.second)
388                 {
389                     if (propPair.first == "Class")
390                     {
391                         classPtr = std::get_if<std::string>(&propPair.second);
392                     }
393                 }
394 
395                 boost::urls::url url = crow::utility::urlFromPieces(
396                     "redfish", "v1", "Managers", "bmc");
397                 if (intfPair.first == pidZoneConfigurationIface)
398                 {
399                     std::string chassis;
400                     if (!dbus::utility::getNthStringFromPath(pathPair.first.str,
401                                                              5, chassis))
402                     {
403                         chassis = "#IllegalValue";
404                     }
405                     nlohmann::json& zone = zones[name];
406                     zone["Chassis"]["@odata.id"] = crow::utility::urlFromPieces(
407                         "redfish", "v1", "Chassis", chassis);
408                     url.set_fragment(
409                         ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name)
410                             .to_string());
411                     zone["@odata.id"] = std::move(url);
412                     zone["@odata.type"] = "#OemManager.FanZone";
413                     config = &zone;
414                 }
415 
416                 else if (intfPair.first == stepwiseConfigurationIface)
417                 {
418                     if (classPtr == nullptr)
419                     {
420                         BMCWEB_LOG_ERROR << "Pid Class Field illegal";
421                         messages::internalError(asyncResp->res);
422                         return;
423                     }
424 
425                     nlohmann::json& controller = stepwise[name];
426                     config = &controller;
427                     url.set_fragment(
428                         ("/Oem/OpenBmc/Fan/StepwiseControllers"_json_pointer /
429                          name)
430                             .to_string());
431                     controller["@odata.id"] = std::move(url);
432                     controller["@odata.type"] =
433                         "#OemManager.StepwiseController";
434 
435                     controller["Direction"] = *classPtr;
436                 }
437 
438                 // pid and fans are off the same configuration
439                 else if (intfPair.first == pidConfigurationIface)
440                 {
441                     if (classPtr == nullptr)
442                     {
443                         BMCWEB_LOG_ERROR << "Pid Class Field illegal";
444                         messages::internalError(asyncResp->res);
445                         return;
446                     }
447                     bool isFan = *classPtr == "fan";
448                     nlohmann::json& element = isFan ? fans[name] : pids[name];
449                     config = &element;
450                     if (isFan)
451                     {
452                         url.set_fragment(
453                             ("/Oem/OpenBmc/Fan/FanControllers"_json_pointer /
454                              name)
455                                 .to_string());
456                         element["@odata.id"] = std::move(url);
457                         element["@odata.type"] = "#OemManager.FanController";
458                     }
459                     else
460                     {
461                         url.set_fragment(
462                             ("/Oem/OpenBmc/Fan/PidControllers"_json_pointer /
463                              name)
464                                 .to_string());
465                         element["@odata.id"] = std::move(url);
466                         element["@odata.type"] = "#OemManager.PidController";
467                     }
468                 }
469                 else
470                 {
471                     BMCWEB_LOG_ERROR << "Unexpected configuration";
472                     messages::internalError(asyncResp->res);
473                     return;
474                 }
475 
476                 // used for making maps out of 2 vectors
477                 const std::vector<double>* keys = nullptr;
478                 const std::vector<double>* values = nullptr;
479 
480                 for (const auto& propertyPair : intfPair.second)
481                 {
482                     if (propertyPair.first == "Type" ||
483                         propertyPair.first == "Class" ||
484                         propertyPair.first == "Name")
485                     {
486                         continue;
487                     }
488 
489                     // zones
490                     if (intfPair.first == pidZoneConfigurationIface)
491                     {
492                         const double* ptr =
493                             std::get_if<double>(&propertyPair.second);
494                         if (ptr == nullptr)
495                         {
496                             BMCWEB_LOG_ERROR << "Field Illegal "
497                                              << propertyPair.first;
498                             messages::internalError(asyncResp->res);
499                             return;
500                         }
501                         (*config)[propertyPair.first] = *ptr;
502                     }
503 
504                     if (intfPair.first == stepwiseConfigurationIface)
505                     {
506                         if (propertyPair.first == "Reading" ||
507                             propertyPair.first == "Output")
508                         {
509                             const std::vector<double>* ptr =
510                                 std::get_if<std::vector<double>>(
511                                     &propertyPair.second);
512 
513                             if (ptr == nullptr)
514                             {
515                                 BMCWEB_LOG_ERROR << "Field Illegal "
516                                                  << propertyPair.first;
517                                 messages::internalError(asyncResp->res);
518                                 return;
519                             }
520 
521                             if (propertyPair.first == "Reading")
522                             {
523                                 keys = ptr;
524                             }
525                             else
526                             {
527                                 values = ptr;
528                             }
529                             if (keys != nullptr && values != nullptr)
530                             {
531                                 if (keys->size() != values->size())
532                                 {
533                                     BMCWEB_LOG_ERROR
534                                         << "Reading and Output size don't match ";
535                                     messages::internalError(asyncResp->res);
536                                     return;
537                                 }
538                                 nlohmann::json& steps = (*config)["Steps"];
539                                 steps = nlohmann::json::array();
540                                 for (size_t ii = 0; ii < keys->size(); ii++)
541                                 {
542                                     nlohmann::json::object_t step;
543                                     step["Target"] = (*keys)[ii];
544                                     step["Output"] = (*values)[ii];
545                                     steps.emplace_back(std::move(step));
546                                 }
547                             }
548                         }
549                         if (propertyPair.first == "NegativeHysteresis" ||
550                             propertyPair.first == "PositiveHysteresis")
551                         {
552                             const double* ptr =
553                                 std::get_if<double>(&propertyPair.second);
554                             if (ptr == nullptr)
555                             {
556                                 BMCWEB_LOG_ERROR << "Field Illegal "
557                                                  << propertyPair.first;
558                                 messages::internalError(asyncResp->res);
559                                 return;
560                             }
561                             (*config)[propertyPair.first] = *ptr;
562                         }
563                     }
564 
565                     // pid and fans are off the same configuration
566                     if (intfPair.first == pidConfigurationIface ||
567                         intfPair.first == stepwiseConfigurationIface)
568                     {
569                         if (propertyPair.first == "Zones")
570                         {
571                             const std::vector<std::string>* inputs =
572                                 std::get_if<std::vector<std::string>>(
573                                     &propertyPair.second);
574 
575                             if (inputs == nullptr)
576                             {
577                                 BMCWEB_LOG_ERROR << "Zones Pid Field Illegal";
578                                 messages::internalError(asyncResp->res);
579                                 return;
580                             }
581                             auto& data = (*config)[propertyPair.first];
582                             data = nlohmann::json::array();
583                             for (std::string itemCopy : *inputs)
584                             {
585                                 dbus::utility::escapePathForDbus(itemCopy);
586                                 nlohmann::json::object_t input;
587                                 boost::urls::url managerUrl =
588                                     crow::utility::urlFromPieces(
589                                         "redfish", "v1", "Managers", "bmc");
590                                 managerUrl.set_fragment(
591                                     ("/Oem/OpenBmc/Fan/FanZones"_json_pointer /
592                                      itemCopy)
593                                         .to_string());
594                                 input["@odata.id"] = std::move(managerUrl);
595                                 data.emplace_back(std::move(input));
596                             }
597                         }
598                         // todo(james): may never happen, but this
599                         // assumes configuration data referenced in the
600                         // PID config is provided by the same daemon, we
601                         // could add another loop to cover all cases,
602                         // but I'm okay kicking this can down the road a
603                         // bit
604 
605                         else if (propertyPair.first == "Inputs" ||
606                                  propertyPair.first == "Outputs")
607                         {
608                             auto& data = (*config)[propertyPair.first];
609                             const std::vector<std::string>* inputs =
610                                 std::get_if<std::vector<std::string>>(
611                                     &propertyPair.second);
612 
613                             if (inputs == nullptr)
614                             {
615                                 BMCWEB_LOG_ERROR << "Field Illegal "
616                                                  << propertyPair.first;
617                                 messages::internalError(asyncResp->res);
618                                 return;
619                             }
620                             data = *inputs;
621                         }
622                         else if (propertyPair.first == "SetPointOffset")
623                         {
624                             const std::string* ptr =
625                                 std::get_if<std::string>(&propertyPair.second);
626 
627                             if (ptr == nullptr)
628                             {
629                                 BMCWEB_LOG_ERROR << "Field Illegal "
630                                                  << propertyPair.first;
631                                 messages::internalError(asyncResp->res);
632                                 return;
633                             }
634                             // translate from dbus to redfish
635                             if (*ptr == "WarningHigh")
636                             {
637                                 (*config)["SetPointOffset"] =
638                                     "UpperThresholdNonCritical";
639                             }
640                             else if (*ptr == "WarningLow")
641                             {
642                                 (*config)["SetPointOffset"] =
643                                     "LowerThresholdNonCritical";
644                             }
645                             else if (*ptr == "CriticalHigh")
646                             {
647                                 (*config)["SetPointOffset"] =
648                                     "UpperThresholdCritical";
649                             }
650                             else if (*ptr == "CriticalLow")
651                             {
652                                 (*config)["SetPointOffset"] =
653                                     "LowerThresholdCritical";
654                             }
655                             else
656                             {
657                                 BMCWEB_LOG_ERROR << "Value Illegal " << *ptr;
658                                 messages::internalError(asyncResp->res);
659                                 return;
660                             }
661                         }
662                         // doubles
663                         else if (propertyPair.first == "FFGainCoefficient" ||
664                                  propertyPair.first == "FFOffCoefficient" ||
665                                  propertyPair.first == "ICoefficient" ||
666                                  propertyPair.first == "ILimitMax" ||
667                                  propertyPair.first == "ILimitMin" ||
668                                  propertyPair.first == "PositiveHysteresis" ||
669                                  propertyPair.first == "NegativeHysteresis" ||
670                                  propertyPair.first == "OutLimitMax" ||
671                                  propertyPair.first == "OutLimitMin" ||
672                                  propertyPair.first == "PCoefficient" ||
673                                  propertyPair.first == "SetPoint" ||
674                                  propertyPair.first == "SlewNeg" ||
675                                  propertyPair.first == "SlewPos")
676                         {
677                             const double* ptr =
678                                 std::get_if<double>(&propertyPair.second);
679                             if (ptr == nullptr)
680                             {
681                                 BMCWEB_LOG_ERROR << "Field Illegal "
682                                                  << propertyPair.first;
683                                 messages::internalError(asyncResp->res);
684                                 return;
685                             }
686                             (*config)[propertyPair.first] = *ptr;
687                         }
688                     }
689                 }
690             }
691         }
692         },
693         connection, path, objectManagerIface, "GetManagedObjects");
694 }
695 
696 enum class CreatePIDRet
697 {
698     fail,
699     del,
700     patch
701 };
702 
703 inline bool
704     getZonesFromJsonReq(const std::shared_ptr<bmcweb::AsyncResp>& response,
705                         std::vector<nlohmann::json>& config,
706                         std::vector<std::string>& zones)
707 {
708     if (config.empty())
709     {
710         BMCWEB_LOG_ERROR << "Empty Zones";
711         messages::propertyValueFormatError(response->res, "[]", "Zones");
712         return false;
713     }
714     for (auto& odata : config)
715     {
716         std::string path;
717         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
718                                           path))
719         {
720             return false;
721         }
722         std::string input;
723 
724         // 8 below comes from
725         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
726         //     0    1     2      3    4    5      6     7      8
727         if (!dbus::utility::getNthStringFromPath(path, 8, input))
728         {
729             BMCWEB_LOG_ERROR << "Got invalid path " << path;
730             BMCWEB_LOG_ERROR << "Illegal Type Zones";
731             messages::propertyValueFormatError(response->res, odata.dump(),
732                                                "Zones");
733             return false;
734         }
735         std::replace(input.begin(), input.end(), '_', ' ');
736         zones.emplace_back(std::move(input));
737     }
738     return true;
739 }
740 
741 inline const dbus::utility::ManagedObjectType::value_type*
742     findChassis(const dbus::utility::ManagedObjectType& managedObj,
743                 const std::string& value, std::string& chassis)
744 {
745     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
746 
747     std::string escaped = value;
748     std::replace(escaped.begin(), escaped.end(), ' ', '_');
749     escaped = "/" + escaped;
750     auto it = std::find_if(managedObj.begin(), managedObj.end(),
751                            [&escaped](const auto& obj) {
752         if (boost::algorithm::ends_with(obj.first.str, escaped))
753         {
754             BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n";
755             return true;
756         }
757         return false;
758     });
759 
760     if (it == managedObj.end())
761     {
762         return nullptr;
763     }
764     // 5 comes from <chassis-name> being the 5th element
765     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
766     if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
767     {
768         return &(*it);
769     }
770 
771     return nullptr;
772 }
773 
774 inline CreatePIDRet createPidInterface(
775     const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type,
776     const nlohmann::json::iterator& it, const std::string& path,
777     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
778     dbus::utility::DBusPropertiesMap& output, std::string& chassis,
779     const std::string& profile)
780 {
781     // common deleter
782     if (it.value() == nullptr)
783     {
784         std::string iface;
785         if (type == "PidControllers" || type == "FanControllers")
786         {
787             iface = pidConfigurationIface;
788         }
789         else if (type == "FanZones")
790         {
791             iface = pidZoneConfigurationIface;
792         }
793         else if (type == "StepwiseControllers")
794         {
795             iface = stepwiseConfigurationIface;
796         }
797         else
798         {
799             BMCWEB_LOG_ERROR << "Illegal Type " << type;
800             messages::propertyUnknown(response->res, type);
801             return CreatePIDRet::fail;
802         }
803 
804         BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n";
805         // delete interface
806         crow::connections::systemBus->async_method_call(
807             [response, path](const boost::system::error_code& ec) {
808             if (ec)
809             {
810                 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
811                 messages::internalError(response->res);
812                 return;
813             }
814             messages::success(response->res);
815             },
816             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
817         return CreatePIDRet::del;
818     }
819 
820     const dbus::utility::ManagedObjectType::value_type* managedItem = nullptr;
821     if (!createNewObject)
822     {
823         // if we aren't creating a new object, we should be able to find it on
824         // d-bus
825         managedItem = findChassis(managedObj, it.key(), chassis);
826         if (managedItem == nullptr)
827         {
828             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
829             messages::invalidObject(response->res,
830                                     crow::utility::urlFromPieces(
831                                         "redfish", "v1", "Chassis", chassis));
832             return CreatePIDRet::fail;
833         }
834     }
835 
836     if (!profile.empty() &&
837         (type == "PidControllers" || type == "FanControllers" ||
838          type == "StepwiseControllers"))
839     {
840         if (managedItem == nullptr)
841         {
842             output.emplace_back("Profiles", std::vector<std::string>{profile});
843         }
844         else
845         {
846             std::string interface;
847             if (type == "StepwiseControllers")
848             {
849                 interface = stepwiseConfigurationIface;
850             }
851             else
852             {
853                 interface = pidConfigurationIface;
854             }
855             bool ifaceFound = false;
856             for (const auto& iface : managedItem->second)
857             {
858                 if (iface.first == interface)
859                 {
860                     ifaceFound = true;
861                     for (const auto& prop : iface.second)
862                     {
863                         if (prop.first == "Profiles")
864                         {
865                             const std::vector<std::string>* curProfiles =
866                                 std::get_if<std::vector<std::string>>(
867                                     &(prop.second));
868                             if (curProfiles == nullptr)
869                             {
870                                 BMCWEB_LOG_ERROR
871                                     << "Illegal profiles in managed object";
872                                 messages::internalError(response->res);
873                                 return CreatePIDRet::fail;
874                             }
875                             if (std::find(curProfiles->begin(),
876                                           curProfiles->end(),
877                                           profile) == curProfiles->end())
878                             {
879                                 std::vector<std::string> newProfiles =
880                                     *curProfiles;
881                                 newProfiles.push_back(profile);
882                                 output.emplace_back("Profiles", newProfiles);
883                             }
884                         }
885                     }
886                 }
887             }
888 
889             if (!ifaceFound)
890             {
891                 BMCWEB_LOG_ERROR
892                     << "Failed to find interface in managed object";
893                 messages::internalError(response->res);
894                 return CreatePIDRet::fail;
895             }
896         }
897     }
898 
899     if (type == "PidControllers" || type == "FanControllers")
900     {
901         if (createNewObject)
902         {
903             output.emplace_back("Class",
904                                 type == "PidControllers" ? "temp" : "fan");
905             output.emplace_back("Type", "Pid");
906         }
907 
908         std::optional<std::vector<nlohmann::json>> zones;
909         std::optional<std::vector<std::string>> inputs;
910         std::optional<std::vector<std::string>> outputs;
911         std::map<std::string, std::optional<double>> doubles;
912         std::optional<std::string> setpointOffset;
913         if (!redfish::json_util::readJson(
914                 it.value(), response->res, "Inputs", inputs, "Outputs", outputs,
915                 "Zones", zones, "FFGainCoefficient",
916                 doubles["FFGainCoefficient"], "FFOffCoefficient",
917                 doubles["FFOffCoefficient"], "ICoefficient",
918                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
919                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
920                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
921                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
922                 doubles["SetPoint"], "SetPointOffset", setpointOffset,
923                 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"],
924                 "PositiveHysteresis", doubles["PositiveHysteresis"],
925                 "NegativeHysteresis", doubles["NegativeHysteresis"]))
926         {
927             return CreatePIDRet::fail;
928         }
929         if (zones)
930         {
931             std::vector<std::string> zonesStr;
932             if (!getZonesFromJsonReq(response, *zones, zonesStr))
933             {
934                 BMCWEB_LOG_ERROR << "Illegal Zones";
935                 return CreatePIDRet::fail;
936             }
937             if (chassis.empty() &&
938                 findChassis(managedObj, zonesStr[0], chassis) == nullptr)
939             {
940                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
941                 messages::invalidObject(
942                     response->res, crow::utility::urlFromPieces(
943                                        "redfish", "v1", "Chassis", chassis));
944                 return CreatePIDRet::fail;
945             }
946             output.emplace_back("Zones", std::move(zonesStr));
947         }
948 
949         if (inputs)
950         {
951             for (std::string& value : *inputs)
952             {
953                 std::replace(value.begin(), value.end(), '_', ' ');
954             }
955             output.emplace_back("Inputs", *inputs);
956         }
957 
958         if (outputs)
959         {
960             for (std::string& value : *outputs)
961             {
962                 std::replace(value.begin(), value.end(), '_', ' ');
963             }
964             output.emplace_back("Outputs", *outputs);
965         }
966 
967         if (setpointOffset)
968         {
969             // translate between redfish and dbus names
970             if (*setpointOffset == "UpperThresholdNonCritical")
971             {
972                 output.emplace_back("SetPointOffset", "WarningLow");
973             }
974             else if (*setpointOffset == "LowerThresholdNonCritical")
975             {
976                 output.emplace_back("SetPointOffset", "WarningHigh");
977             }
978             else if (*setpointOffset == "LowerThresholdCritical")
979             {
980                 output.emplace_back("SetPointOffset", "CriticalLow");
981             }
982             else if (*setpointOffset == "UpperThresholdCritical")
983             {
984                 output.emplace_back("SetPointOffset", "CriticalHigh");
985             }
986             else
987             {
988                 BMCWEB_LOG_ERROR << "Invalid setpointoffset "
989                                  << *setpointOffset;
990                 messages::propertyValueNotInList(response->res, it.key(),
991                                                  "SetPointOffset");
992                 return CreatePIDRet::fail;
993             }
994         }
995 
996         // doubles
997         for (const auto& pairs : doubles)
998         {
999             if (!pairs.second)
1000             {
1001                 continue;
1002             }
1003             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
1004             output.emplace_back(pairs.first, *pairs.second);
1005         }
1006     }
1007 
1008     else if (type == "FanZones")
1009     {
1010         output.emplace_back("Type", "Pid.Zone");
1011 
1012         std::optional<nlohmann::json> chassisContainer;
1013         std::optional<double> failSafePercent;
1014         std::optional<double> minThermalOutput;
1015         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
1016                                           chassisContainer, "FailSafePercent",
1017                                           failSafePercent, "MinThermalOutput",
1018                                           minThermalOutput))
1019         {
1020             return CreatePIDRet::fail;
1021         }
1022 
1023         if (chassisContainer)
1024         {
1025             std::string chassisId;
1026             if (!redfish::json_util::readJson(*chassisContainer, response->res,
1027                                               "@odata.id", chassisId))
1028             {
1029                 return CreatePIDRet::fail;
1030             }
1031 
1032             // /redfish/v1/chassis/chassis_name/
1033             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
1034             {
1035                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
1036                 messages::invalidObject(
1037                     response->res, crow::utility::urlFromPieces(
1038                                        "redfish", "v1", "Chassis", chassisId));
1039                 return CreatePIDRet::fail;
1040             }
1041         }
1042         if (minThermalOutput)
1043         {
1044             output.emplace_back("MinThermalOutput", *minThermalOutput);
1045         }
1046         if (failSafePercent)
1047         {
1048             output.emplace_back("FailSafePercent", *failSafePercent);
1049         }
1050     }
1051     else if (type == "StepwiseControllers")
1052     {
1053         output.emplace_back("Type", "Stepwise");
1054 
1055         std::optional<std::vector<nlohmann::json>> zones;
1056         std::optional<std::vector<nlohmann::json>> steps;
1057         std::optional<std::vector<std::string>> inputs;
1058         std::optional<double> positiveHysteresis;
1059         std::optional<double> negativeHysteresis;
1060         std::optional<std::string> direction; // upper clipping curve vs lower
1061         if (!redfish::json_util::readJson(
1062                 it.value(), response->res, "Zones", zones, "Steps", steps,
1063                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
1064                 "NegativeHysteresis", negativeHysteresis, "Direction",
1065                 direction))
1066         {
1067             return CreatePIDRet::fail;
1068         }
1069 
1070         if (zones)
1071         {
1072             std::vector<std::string> zonesStrs;
1073             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
1074             {
1075                 BMCWEB_LOG_ERROR << "Illegal Zones";
1076                 return CreatePIDRet::fail;
1077             }
1078             if (chassis.empty() &&
1079                 findChassis(managedObj, zonesStrs[0], chassis) == nullptr)
1080             {
1081                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
1082                 messages::invalidObject(
1083                     response->res, crow::utility::urlFromPieces(
1084                                        "redfish", "v1", "Chassis", chassis));
1085                 return CreatePIDRet::fail;
1086             }
1087             output.emplace_back("Zones", std::move(zonesStrs));
1088         }
1089         if (steps)
1090         {
1091             std::vector<double> readings;
1092             std::vector<double> outputs;
1093             for (auto& step : *steps)
1094             {
1095                 double target = 0.0;
1096                 double out = 0.0;
1097 
1098                 if (!redfish::json_util::readJson(step, response->res, "Target",
1099                                                   target, "Output", out))
1100                 {
1101                     return CreatePIDRet::fail;
1102                 }
1103                 readings.emplace_back(target);
1104                 outputs.emplace_back(out);
1105             }
1106             output.emplace_back("Reading", std::move(readings));
1107             output.emplace_back("Output", std::move(outputs));
1108         }
1109         if (inputs)
1110         {
1111             for (std::string& value : *inputs)
1112             {
1113                 std::replace(value.begin(), value.end(), '_', ' ');
1114             }
1115             output.emplace_back("Inputs", std::move(*inputs));
1116         }
1117         if (negativeHysteresis)
1118         {
1119             output.emplace_back("NegativeHysteresis", *negativeHysteresis);
1120         }
1121         if (positiveHysteresis)
1122         {
1123             output.emplace_back("PositiveHysteresis", *positiveHysteresis);
1124         }
1125         if (direction)
1126         {
1127             constexpr const std::array<const char*, 2> allowedDirections = {
1128                 "Ceiling", "Floor"};
1129             if (std::find(allowedDirections.begin(), allowedDirections.end(),
1130                           *direction) == allowedDirections.end())
1131             {
1132                 messages::propertyValueTypeError(response->res, "Direction",
1133                                                  *direction);
1134                 return CreatePIDRet::fail;
1135             }
1136             output.emplace_back("Class", *direction);
1137         }
1138     }
1139     else
1140     {
1141         BMCWEB_LOG_ERROR << "Illegal Type " << type;
1142         messages::propertyUnknown(response->res, type);
1143         return CreatePIDRet::fail;
1144     }
1145     return CreatePIDRet::patch;
1146 }
1147 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
1148 {
1149     struct CompletionValues
1150     {
1151         std::vector<std::string> supportedProfiles;
1152         std::string currentProfile;
1153         dbus::utility::MapperGetSubTreeResponse subtree;
1154     };
1155 
1156     explicit GetPIDValues(
1157         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
1158         asyncResp(asyncRespIn)
1159 
1160     {}
1161 
1162     void run()
1163     {
1164         std::shared_ptr<GetPIDValues> self = shared_from_this();
1165 
1166         // get all configurations
1167         constexpr std::array<std::string_view, 4> interfaces = {
1168             pidConfigurationIface, pidZoneConfigurationIface,
1169             objectManagerIface, stepwiseConfigurationIface};
1170         dbus::utility::getSubTree(
1171             "/", 0, interfaces,
1172             [self](
1173                 const boost::system::error_code& ec,
1174                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1175             if (ec)
1176             {
1177                 BMCWEB_LOG_ERROR << ec;
1178                 messages::internalError(self->asyncResp->res);
1179                 return;
1180             }
1181             self->complete.subtree = subtreeLocal;
1182             });
1183 
1184         // at the same time get the selected profile
1185         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1186             thermalModeIface};
1187         dbus::utility::getSubTree(
1188             "/", 0, thermalModeIfaces,
1189             [self](
1190                 const boost::system::error_code& ec,
1191                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1192             if (ec || subtreeLocal.empty())
1193             {
1194                 return;
1195             }
1196             if (subtreeLocal[0].second.size() != 1)
1197             {
1198                 // invalid mapper response, should never happen
1199                 BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error";
1200                 messages::internalError(self->asyncResp->res);
1201                 return;
1202             }
1203 
1204             const std::string& path = subtreeLocal[0].first;
1205             const std::string& owner = subtreeLocal[0].second[0].first;
1206 
1207             sdbusplus::asio::getAllProperties(
1208                 *crow::connections::systemBus, owner, path, thermalModeIface,
1209                 [path, owner,
1210                  self](const boost::system::error_code& ec2,
1211                        const dbus::utility::DBusPropertiesMap& resp) {
1212                 if (ec2)
1213                 {
1214                     BMCWEB_LOG_ERROR
1215                         << "GetPIDValues: Can't get thermalModeIface " << path;
1216                     messages::internalError(self->asyncResp->res);
1217                     return;
1218                 }
1219 
1220                 const std::string* current = nullptr;
1221                 const std::vector<std::string>* supported = nullptr;
1222 
1223                 const bool success = sdbusplus::unpackPropertiesNoThrow(
1224                     dbus_utils::UnpackErrorPrinter(), resp, "Current", current,
1225                     "Supported", supported);
1226 
1227                 if (!success)
1228                 {
1229                     messages::internalError(self->asyncResp->res);
1230                     return;
1231                 }
1232 
1233                 if (current == nullptr || supported == nullptr)
1234                 {
1235                     BMCWEB_LOG_ERROR
1236                         << "GetPIDValues: thermal mode iface invalid " << path;
1237                     messages::internalError(self->asyncResp->res);
1238                     return;
1239                 }
1240                 self->complete.currentProfile = *current;
1241                 self->complete.supportedProfiles = *supported;
1242                 });
1243             });
1244     }
1245 
1246     static void
1247         processingComplete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1248                            const CompletionValues& completion)
1249     {
1250         if (asyncResp->res.result() != boost::beast::http::status::ok)
1251         {
1252             return;
1253         }
1254         // create map of <connection, path to objMgr>>
1255         boost::container::flat_map<
1256             std::string, std::string, std::less<>,
1257             std::vector<std::pair<std::string, std::string>>>
1258             objectMgrPaths;
1259         boost::container::flat_set<std::string, std::less<>,
1260                                    std::vector<std::string>>
1261             calledConnections;
1262         for (const auto& pathGroup : completion.subtree)
1263         {
1264             for (const auto& connectionGroup : pathGroup.second)
1265             {
1266                 auto findConnection =
1267                     calledConnections.find(connectionGroup.first);
1268                 if (findConnection != calledConnections.end())
1269                 {
1270                     break;
1271                 }
1272                 for (const std::string& interface : connectionGroup.second)
1273                 {
1274                     if (interface == objectManagerIface)
1275                     {
1276                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1277                     }
1278                     // this list is alphabetical, so we
1279                     // should have found the objMgr by now
1280                     if (interface == pidConfigurationIface ||
1281                         interface == pidZoneConfigurationIface ||
1282                         interface == stepwiseConfigurationIface)
1283                     {
1284                         auto findObjMgr =
1285                             objectMgrPaths.find(connectionGroup.first);
1286                         if (findObjMgr == objectMgrPaths.end())
1287                         {
1288                             BMCWEB_LOG_DEBUG << connectionGroup.first
1289                                              << "Has no Object Manager";
1290                             continue;
1291                         }
1292 
1293                         calledConnections.insert(connectionGroup.first);
1294 
1295                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1296                                          completion.currentProfile,
1297                                          completion.supportedProfiles,
1298                                          asyncResp);
1299                         break;
1300                     }
1301                 }
1302             }
1303         }
1304     }
1305 
1306     ~GetPIDValues()
1307     {
1308         boost::asio::post(crow::connections::systemBus->get_io_context(),
1309                           std::bind_front(&processingComplete, asyncResp,
1310                                           std::move(complete)));
1311     }
1312 
1313     GetPIDValues(const GetPIDValues&) = delete;
1314     GetPIDValues(GetPIDValues&&) = delete;
1315     GetPIDValues& operator=(const GetPIDValues&) = delete;
1316     GetPIDValues& operator=(GetPIDValues&&) = delete;
1317 
1318     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1319     CompletionValues complete;
1320 };
1321 
1322 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1323 {
1324     SetPIDValues(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
1325                  nlohmann::json& data) :
1326         asyncResp(asyncRespIn)
1327     {
1328         std::optional<nlohmann::json> pidControllers;
1329         std::optional<nlohmann::json> fanControllers;
1330         std::optional<nlohmann::json> fanZones;
1331         std::optional<nlohmann::json> stepwiseControllers;
1332 
1333         if (!redfish::json_util::readJson(
1334                 data, asyncResp->res, "PidControllers", pidControllers,
1335                 "FanControllers", fanControllers, "FanZones", fanZones,
1336                 "StepwiseControllers", stepwiseControllers, "Profile", profile))
1337         {
1338             return;
1339         }
1340         configuration.emplace_back("PidControllers", std::move(pidControllers));
1341         configuration.emplace_back("FanControllers", std::move(fanControllers));
1342         configuration.emplace_back("FanZones", std::move(fanZones));
1343         configuration.emplace_back("StepwiseControllers",
1344                                    std::move(stepwiseControllers));
1345     }
1346 
1347     SetPIDValues(const SetPIDValues&) = delete;
1348     SetPIDValues(SetPIDValues&&) = delete;
1349     SetPIDValues& operator=(const SetPIDValues&) = delete;
1350     SetPIDValues& operator=(SetPIDValues&&) = delete;
1351 
1352     void run()
1353     {
1354         if (asyncResp->res.result() != boost::beast::http::status::ok)
1355         {
1356             return;
1357         }
1358 
1359         std::shared_ptr<SetPIDValues> self = shared_from_this();
1360 
1361         // todo(james): might make sense to do a mapper call here if this
1362         // interface gets more traction
1363         crow::connections::systemBus->async_method_call(
1364             [self](const boost::system::error_code& ec,
1365                    const dbus::utility::ManagedObjectType& mObj) {
1366             if (ec)
1367             {
1368                 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
1369                 messages::internalError(self->asyncResp->res);
1370                 return;
1371             }
1372             const std::array<const char*, 3> configurations = {
1373                 pidConfigurationIface, pidZoneConfigurationIface,
1374                 stepwiseConfigurationIface};
1375 
1376             for (const auto& [path, object] : mObj)
1377             {
1378                 for (const auto& [interface, _] : object)
1379                 {
1380                     if (std::find(configurations.begin(), configurations.end(),
1381                                   interface) != configurations.end())
1382                     {
1383                         self->objectCount++;
1384                         break;
1385                     }
1386                 }
1387             }
1388             self->managedObj = mObj;
1389             },
1390             "xyz.openbmc_project.EntityManager",
1391             "/xyz/openbmc_project/inventory", objectManagerIface,
1392             "GetManagedObjects");
1393 
1394         // at the same time get the profile information
1395         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1396             thermalModeIface};
1397         dbus::utility::getSubTree(
1398             "/", 0, thermalModeIfaces,
1399             [self](const boost::system::error_code& ec,
1400                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
1401             if (ec || subtree.empty())
1402             {
1403                 return;
1404             }
1405             if (subtree[0].second.empty())
1406             {
1407                 // invalid mapper response, should never happen
1408                 BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error";
1409                 messages::internalError(self->asyncResp->res);
1410                 return;
1411             }
1412 
1413             const std::string& path = subtree[0].first;
1414             const std::string& owner = subtree[0].second[0].first;
1415             sdbusplus::asio::getAllProperties(
1416                 *crow::connections::systemBus, owner, path, thermalModeIface,
1417                 [self, path, owner](const boost::system::error_code& ec2,
1418                                     const dbus::utility::DBusPropertiesMap& r) {
1419                 if (ec2)
1420                 {
1421                     BMCWEB_LOG_ERROR
1422                         << "SetPIDValues: Can't get thermalModeIface " << path;
1423                     messages::internalError(self->asyncResp->res);
1424                     return;
1425                 }
1426                 const std::string* current = nullptr;
1427                 const std::vector<std::string>* supported = nullptr;
1428 
1429                 const bool success = sdbusplus::unpackPropertiesNoThrow(
1430                     dbus_utils::UnpackErrorPrinter(), r, "Current", current,
1431                     "Supported", supported);
1432 
1433                 if (!success)
1434                 {
1435                     messages::internalError(self->asyncResp->res);
1436                     return;
1437                 }
1438 
1439                 if (current == nullptr || supported == nullptr)
1440                 {
1441                     BMCWEB_LOG_ERROR
1442                         << "SetPIDValues: thermal mode iface invalid " << path;
1443                     messages::internalError(self->asyncResp->res);
1444                     return;
1445                 }
1446                 self->currentProfile = *current;
1447                 self->supportedProfiles = *supported;
1448                 self->profileConnection = owner;
1449                 self->profilePath = path;
1450                 });
1451             });
1452     }
1453     void pidSetDone()
1454     {
1455         if (asyncResp->res.result() != boost::beast::http::status::ok)
1456         {
1457             return;
1458         }
1459         std::shared_ptr<bmcweb::AsyncResp> response = asyncResp;
1460         if (profile)
1461         {
1462             if (std::find(supportedProfiles.begin(), supportedProfiles.end(),
1463                           *profile) == supportedProfiles.end())
1464             {
1465                 messages::actionParameterUnknown(response->res, "Profile",
1466                                                  *profile);
1467                 return;
1468             }
1469             currentProfile = *profile;
1470             crow::connections::systemBus->async_method_call(
1471                 [response](const boost::system::error_code& ec) {
1472                 if (ec)
1473                 {
1474                     BMCWEB_LOG_ERROR << "Error patching profile" << ec;
1475                     messages::internalError(response->res);
1476                 }
1477                 },
1478                 profileConnection, profilePath,
1479                 "org.freedesktop.DBus.Properties", "Set", thermalModeIface,
1480                 "Current", dbus::utility::DbusVariantType(*profile));
1481         }
1482 
1483         for (auto& containerPair : configuration)
1484         {
1485             auto& container = containerPair.second;
1486             if (!container)
1487             {
1488                 continue;
1489             }
1490             BMCWEB_LOG_DEBUG << *container;
1491 
1492             const std::string& type = containerPair.first;
1493 
1494             for (nlohmann::json::iterator it = container->begin();
1495                  it != container->end(); ++it)
1496             {
1497                 const auto& name = it.key();
1498                 std::string dbusObjName = name;
1499                 std::replace(dbusObjName.begin(), dbusObjName.end(), ' ', '_');
1500                 BMCWEB_LOG_DEBUG << "looking for " << name;
1501 
1502                 auto pathItr = std::find_if(managedObj.begin(),
1503                                             managedObj.end(),
1504                                             [&dbusObjName](const auto& obj) {
1505                     return boost::algorithm::ends_with(obj.first.str,
1506                                                        "/" + dbusObjName);
1507                 });
1508                 dbus::utility::DBusPropertiesMap output;
1509 
1510                 output.reserve(16); // The pid interface length
1511 
1512                 // determines if we're patching entity-manager or
1513                 // creating a new object
1514                 bool createNewObject = (pathItr == managedObj.end());
1515                 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject;
1516 
1517                 std::string iface;
1518                 if (!createNewObject)
1519                 {
1520                     bool findInterface = false;
1521                     for (const auto& interface : pathItr->second)
1522                     {
1523                         if (interface.first == pidConfigurationIface)
1524                         {
1525                             if (type == "PidControllers" ||
1526                                 type == "FanControllers")
1527                             {
1528                                 iface = pidConfigurationIface;
1529                                 findInterface = true;
1530                                 break;
1531                             }
1532                         }
1533                         else if (interface.first == pidZoneConfigurationIface)
1534                         {
1535                             if (type == "FanZones")
1536                             {
1537                                 iface = pidConfigurationIface;
1538                                 findInterface = true;
1539                                 break;
1540                             }
1541                         }
1542                         else if (interface.first == stepwiseConfigurationIface)
1543                         {
1544                             if (type == "StepwiseControllers")
1545                             {
1546                                 iface = stepwiseConfigurationIface;
1547                                 findInterface = true;
1548                                 break;
1549                             }
1550                         }
1551                     }
1552 
1553                     // create new object if interface not found
1554                     if (!findInterface)
1555                     {
1556                         createNewObject = true;
1557                     }
1558                 }
1559 
1560                 if (createNewObject && it.value() == nullptr)
1561                 {
1562                     // can't delete a non-existent object
1563                     messages::propertyValueNotInList(response->res,
1564                                                      it.value().dump(), name);
1565                     continue;
1566                 }
1567 
1568                 std::string path;
1569                 if (pathItr != managedObj.end())
1570                 {
1571                     path = pathItr->first.str;
1572                 }
1573 
1574                 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n";
1575 
1576                 // arbitrary limit to avoid attacks
1577                 constexpr const size_t controllerLimit = 500;
1578                 if (createNewObject && objectCount >= controllerLimit)
1579                 {
1580                     messages::resourceExhaustion(response->res, type);
1581                     continue;
1582                 }
1583                 std::string escaped = name;
1584                 std::replace(escaped.begin(), escaped.end(), '_', ' ');
1585                 output.emplace_back("Name", escaped);
1586 
1587                 std::string chassis;
1588                 CreatePIDRet ret = createPidInterface(
1589                     response, type, it, path, managedObj, createNewObject,
1590                     output, chassis, currentProfile);
1591                 if (ret == CreatePIDRet::fail)
1592                 {
1593                     return;
1594                 }
1595                 if (ret == CreatePIDRet::del)
1596                 {
1597                     continue;
1598                 }
1599 
1600                 if (!createNewObject)
1601                 {
1602                     for (const auto& property : output)
1603                     {
1604                         crow::connections::systemBus->async_method_call(
1605                             [response,
1606                              propertyName{std::string(property.first)}](
1607                                 const boost::system::error_code& ec) {
1608                             if (ec)
1609                             {
1610                                 BMCWEB_LOG_ERROR << "Error patching "
1611                                                  << propertyName << ": " << ec;
1612                                 messages::internalError(response->res);
1613                                 return;
1614                             }
1615                             messages::success(response->res);
1616                             },
1617                             "xyz.openbmc_project.EntityManager", path,
1618                             "org.freedesktop.DBus.Properties", "Set", iface,
1619                             property.first, property.second);
1620                     }
1621                 }
1622                 else
1623                 {
1624                     if (chassis.empty())
1625                     {
1626                         BMCWEB_LOG_ERROR << "Failed to get chassis from config";
1627                         messages::internalError(response->res);
1628                         return;
1629                     }
1630 
1631                     bool foundChassis = false;
1632                     for (const auto& obj : managedObj)
1633                     {
1634                         if (boost::algorithm::ends_with(obj.first.str, chassis))
1635                         {
1636                             chassis = obj.first.str;
1637                             foundChassis = true;
1638                             break;
1639                         }
1640                     }
1641                     if (!foundChassis)
1642                     {
1643                         BMCWEB_LOG_ERROR << "Failed to find chassis on dbus";
1644                         messages::resourceMissingAtURI(
1645                             response->res,
1646                             crow::utility::urlFromPieces("redfish", "v1",
1647                                                          "Chassis", chassis));
1648                         return;
1649                     }
1650 
1651                     crow::connections::systemBus->async_method_call(
1652                         [response](const boost::system::error_code& ec) {
1653                         if (ec)
1654                         {
1655                             BMCWEB_LOG_ERROR << "Error Adding Pid Object "
1656                                              << ec;
1657                             messages::internalError(response->res);
1658                             return;
1659                         }
1660                         messages::success(response->res);
1661                         },
1662                         "xyz.openbmc_project.EntityManager", chassis,
1663                         "xyz.openbmc_project.AddObject", "AddObject", output);
1664                 }
1665             }
1666         }
1667     }
1668 
1669     ~SetPIDValues()
1670     {
1671         try
1672         {
1673             pidSetDone();
1674         }
1675         catch (...)
1676         {
1677             BMCWEB_LOG_CRITICAL << "pidSetDone threw exception";
1678         }
1679     }
1680 
1681     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1682     std::vector<std::pair<std::string, std::optional<nlohmann::json>>>
1683         configuration;
1684     std::optional<std::string> profile;
1685     dbus::utility::ManagedObjectType managedObj;
1686     std::vector<std::string> supportedProfiles;
1687     std::string currentProfile;
1688     std::string profileConnection;
1689     std::string profilePath;
1690     size_t objectCount = 0;
1691 };
1692 
1693 /**
1694  * @brief Retrieves BMC manager location data over DBus
1695  *
1696  * @param[in] aResp Shared pointer for completing asynchronous calls
1697  * @param[in] connectionName - service name
1698  * @param[in] path - object path
1699  * @return none
1700  */
1701 inline void getLocation(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
1702                         const std::string& connectionName,
1703                         const std::string& path)
1704 {
1705     BMCWEB_LOG_DEBUG << "Get BMC manager Location data.";
1706 
1707     sdbusplus::asio::getProperty<std::string>(
1708         *crow::connections::systemBus, connectionName, path,
1709         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
1710         [aResp](const boost::system::error_code& ec,
1711                 const std::string& property) {
1712         if (ec)
1713         {
1714             BMCWEB_LOG_DEBUG << "DBUS response error for "
1715                                 "Location";
1716             messages::internalError(aResp->res);
1717             return;
1718         }
1719 
1720         aResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
1721             property;
1722         });
1723 }
1724 // avoid name collision systems.hpp
1725 inline void
1726     managerGetLastResetTime(const std::shared_ptr<bmcweb::AsyncResp>& aResp)
1727 {
1728     BMCWEB_LOG_DEBUG << "Getting Manager Last Reset Time";
1729 
1730     sdbusplus::asio::getProperty<uint64_t>(
1731         *crow::connections::systemBus, "xyz.openbmc_project.State.BMC",
1732         "/xyz/openbmc_project/state/bmc0", "xyz.openbmc_project.State.BMC",
1733         "LastRebootTime",
1734         [aResp](const boost::system::error_code& ec,
1735                 const uint64_t lastResetTime) {
1736         if (ec)
1737         {
1738             BMCWEB_LOG_DEBUG << "D-BUS response error " << ec;
1739             return;
1740         }
1741 
1742         // LastRebootTime is epoch time, in milliseconds
1743         // https://github.com/openbmc/phosphor-dbus-interfaces/blob/7f9a128eb9296e926422ddc312c148b625890bb6/xyz/openbmc_project/State/BMC.interface.yaml#L19
1744         uint64_t lastResetTimeStamp = lastResetTime / 1000;
1745 
1746         // Convert to ISO 8601 standard
1747         aResp->res.jsonValue["LastResetTime"] =
1748             redfish::time_utils::getDateTimeUint(lastResetTimeStamp);
1749         });
1750 }
1751 
1752 /**
1753  * @brief Set the running firmware image
1754  *
1755  * @param[i,o] aResp - Async response object
1756  * @param[i] runningFirmwareTarget - Image to make the running image
1757  *
1758  * @return void
1759  */
1760 inline void
1761     setActiveFirmwareImage(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
1762                            const std::string& runningFirmwareTarget)
1763 {
1764     // Get the Id from /redfish/v1/UpdateService/FirmwareInventory/<Id>
1765     std::string::size_type idPos = runningFirmwareTarget.rfind('/');
1766     if (idPos == std::string::npos)
1767     {
1768         messages::propertyValueNotInList(aResp->res, runningFirmwareTarget,
1769                                          "@odata.id");
1770         BMCWEB_LOG_DEBUG << "Can't parse firmware ID!";
1771         return;
1772     }
1773     idPos++;
1774     if (idPos >= runningFirmwareTarget.size())
1775     {
1776         messages::propertyValueNotInList(aResp->res, runningFirmwareTarget,
1777                                          "@odata.id");
1778         BMCWEB_LOG_DEBUG << "Invalid firmware ID.";
1779         return;
1780     }
1781     std::string firmwareId = runningFirmwareTarget.substr(idPos);
1782 
1783     // Make sure the image is valid before setting priority
1784     crow::connections::systemBus->async_method_call(
1785         [aResp, firmwareId,
1786          runningFirmwareTarget](const boost::system::error_code& ec,
1787                                 dbus::utility::ManagedObjectType& subtree) {
1788         if (ec)
1789         {
1790             BMCWEB_LOG_DEBUG << "D-Bus response error getting objects.";
1791             messages::internalError(aResp->res);
1792             return;
1793         }
1794 
1795         if (subtree.empty())
1796         {
1797             BMCWEB_LOG_DEBUG << "Can't find image!";
1798             messages::internalError(aResp->res);
1799             return;
1800         }
1801 
1802         bool foundImage = false;
1803         for (const auto& object : subtree)
1804         {
1805             const std::string& path =
1806                 static_cast<const std::string&>(object.first);
1807             std::size_t idPos2 = path.rfind('/');
1808 
1809             if (idPos2 == std::string::npos)
1810             {
1811                 continue;
1812             }
1813 
1814             idPos2++;
1815             if (idPos2 >= path.size())
1816             {
1817                 continue;
1818             }
1819 
1820             if (path.substr(idPos2) == firmwareId)
1821             {
1822                 foundImage = true;
1823                 break;
1824             }
1825         }
1826 
1827         if (!foundImage)
1828         {
1829             messages::propertyValueNotInList(aResp->res, runningFirmwareTarget,
1830                                              "@odata.id");
1831             BMCWEB_LOG_DEBUG << "Invalid firmware ID.";
1832             return;
1833         }
1834 
1835         BMCWEB_LOG_DEBUG << "Setting firmware version " << firmwareId
1836                          << " to priority 0.";
1837 
1838         // Only support Immediate
1839         // An addition could be a Redfish Setting like
1840         // ActiveSoftwareImageApplyTime and support OnReset
1841         crow::connections::systemBus->async_method_call(
1842             [aResp](const boost::system::error_code& ec2) {
1843             if (ec2)
1844             {
1845                 BMCWEB_LOG_DEBUG << "D-Bus response error setting.";
1846                 messages::internalError(aResp->res);
1847                 return;
1848             }
1849             doBMCGracefulRestart(aResp);
1850             },
1851 
1852             "xyz.openbmc_project.Software.BMC.Updater",
1853             "/xyz/openbmc_project/software/" + firmwareId,
1854             "org.freedesktop.DBus.Properties", "Set",
1855             "xyz.openbmc_project.Software.RedundancyPriority", "Priority",
1856             dbus::utility::DbusVariantType(static_cast<uint8_t>(0)));
1857         },
1858         "xyz.openbmc_project.Software.BMC.Updater",
1859         "/xyz/openbmc_project/software", "org.freedesktop.DBus.ObjectManager",
1860         "GetManagedObjects");
1861 }
1862 
1863 inline void setDateTime(std::shared_ptr<bmcweb::AsyncResp> aResp,
1864                         std::string datetime)
1865 {
1866     BMCWEB_LOG_DEBUG << "Set date time: " << datetime;
1867 
1868     std::optional<redfish::time_utils::usSinceEpoch> us =
1869         redfish::time_utils::dateStringToEpoch(datetime);
1870     if (!us)
1871     {
1872         messages::propertyValueFormatError(aResp->res, datetime, "DateTime");
1873         return;
1874     }
1875     crow::connections::systemBus->async_method_call(
1876         [aResp{std::move(aResp)},
1877          datetime{std::move(datetime)}](const boost::system::error_code& ec) {
1878         if (ec)
1879         {
1880             BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
1881                                 "DBUS response error "
1882                              << ec;
1883             messages::internalError(aResp->res);
1884             return;
1885         }
1886         aResp->res.jsonValue["DateTime"] = datetime;
1887         },
1888         "xyz.openbmc_project.Time.Manager", "/xyz/openbmc_project/time/bmc",
1889         "org.freedesktop.DBus.Properties", "Set",
1890         "xyz.openbmc_project.Time.EpochTime", "Elapsed",
1891         dbus::utility::DbusVariantType(us->count()));
1892 }
1893 
1894 inline void requestRoutesManager(App& app)
1895 {
1896     std::string uuid = persistent_data::getConfig().systemUuid;
1897 
1898     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/")
1899         .privileges(redfish::privileges::getManager)
1900         .methods(boost::beast::http::verb::get)(
1901             [&app, uuid](const crow::Request& req,
1902                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1903         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1904         {
1905             return;
1906         }
1907         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
1908         asyncResp->res.jsonValue["@odata.type"] = "#Manager.v1_14_0.Manager";
1909         asyncResp->res.jsonValue["Id"] = "bmc";
1910         asyncResp->res.jsonValue["Name"] = "OpenBmc Manager";
1911         asyncResp->res.jsonValue["Description"] =
1912             "Baseboard Management Controller";
1913         asyncResp->res.jsonValue["PowerState"] = "On";
1914         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
1915         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
1916 
1917         asyncResp->res.jsonValue["ManagerType"] = "BMC";
1918         asyncResp->res.jsonValue["UUID"] = systemd_utils::getUuid();
1919         asyncResp->res.jsonValue["ServiceEntryPointUUID"] = uuid;
1920         asyncResp->res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
1921 
1922         asyncResp->res.jsonValue["LogServices"]["@odata.id"] =
1923             "/redfish/v1/Managers/bmc/LogServices";
1924         asyncResp->res.jsonValue["NetworkProtocol"]["@odata.id"] =
1925             "/redfish/v1/Managers/bmc/NetworkProtocol";
1926         asyncResp->res.jsonValue["EthernetInterfaces"]["@odata.id"] =
1927             "/redfish/v1/Managers/bmc/EthernetInterfaces";
1928 
1929 #ifdef BMCWEB_ENABLE_VM_NBDPROXY
1930         asyncResp->res.jsonValue["VirtualMedia"]["@odata.id"] =
1931             "/redfish/v1/Managers/bmc/VirtualMedia";
1932 #endif // BMCWEB_ENABLE_VM_NBDPROXY
1933 
1934         // default oem data
1935         nlohmann::json& oem = asyncResp->res.jsonValue["Oem"];
1936         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
1937         oem["@odata.type"] = "#OemManager.Oem";
1938         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
1939         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
1940         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
1941 
1942         nlohmann::json::object_t certificates;
1943         certificates["@odata.id"] =
1944             "/redfish/v1/Managers/bmc/Truststore/Certificates";
1945         oemOpenbmc["Certificates"] = std::move(certificates);
1946 
1947         // Manager.Reset (an action) can be many values, OpenBMC only
1948         // supports BMC reboot.
1949         nlohmann::json& managerReset =
1950             asyncResp->res.jsonValue["Actions"]["#Manager.Reset"];
1951         managerReset["target"] =
1952             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
1953         managerReset["@Redfish.ActionInfo"] =
1954             "/redfish/v1/Managers/bmc/ResetActionInfo";
1955 
1956         // ResetToDefaults (Factory Reset) has values like
1957         // PreserveNetworkAndUsers and PreserveNetwork that aren't supported
1958         // on OpenBMC
1959         nlohmann::json& resetToDefaults =
1960             asyncResp->res.jsonValue["Actions"]["#Manager.ResetToDefaults"];
1961         resetToDefaults["target"] =
1962             "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults";
1963         resetToDefaults["ResetType@Redfish.AllowableValues"] =
1964             nlohmann::json::array_t({"ResetAll"});
1965 
1966         std::pair<std::string, std::string> redfishDateTimeOffset =
1967             redfish::time_utils::getDateTimeOffsetNow();
1968 
1969         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
1970         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
1971             redfishDateTimeOffset.second;
1972 
1973         // TODO (Gunnar): Remove these one day since moved to ComputerSystem
1974         // Still used by OCP profiles
1975         // https://github.com/opencomputeproject/OCP-Profiles/issues/23
1976         // Fill in SerialConsole info
1977         asyncResp->res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
1978         asyncResp->res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15;
1979         asyncResp->res.jsonValue["SerialConsole"]["ConnectTypesSupported"] =
1980             nlohmann::json::array_t({"IPMI", "SSH"});
1981 #ifdef BMCWEB_ENABLE_KVM
1982         // Fill in GraphicalConsole info
1983         asyncResp->res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true;
1984         asyncResp->res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] =
1985             4;
1986         asyncResp->res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] =
1987             nlohmann::json::array_t({"KVMIP"});
1988 #endif // BMCWEB_ENABLE_KVM
1989 
1990         asyncResp->res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
1991 
1992         nlohmann::json::array_t managerForServers;
1993         nlohmann::json::object_t manager;
1994         manager["@odata.id"] = "/redfish/v1/Systems/system";
1995         managerForServers.emplace_back(std::move(manager));
1996 
1997         asyncResp->res.jsonValue["Links"]["ManagerForServers"] =
1998             std::move(managerForServers);
1999 
2000         auto health = std::make_shared<HealthPopulate>(asyncResp);
2001         health->isManagersHealth = true;
2002         health->populate();
2003 
2004         sw_util::populateSoftwareInformation(asyncResp, sw_util::bmcPurpose,
2005                                              "FirmwareVersion", true);
2006 
2007         managerGetLastResetTime(asyncResp);
2008 
2009         // ManagerDiagnosticData is added for all BMCs.
2010         nlohmann::json& managerDiagnosticData =
2011             asyncResp->res.jsonValue["ManagerDiagnosticData"];
2012         managerDiagnosticData["@odata.id"] =
2013             "/redfish/v1/Managers/bmc/ManagerDiagnosticData";
2014 
2015 #ifdef BMCWEB_ENABLE_REDFISH_OEM_MANAGER_FAN_DATA
2016         auto pids = std::make_shared<GetPIDValues>(asyncResp);
2017         pids->run();
2018 #endif
2019 
2020         getMainChassisId(asyncResp,
2021                          [](const std::string& chassisId,
2022                             const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
2023             aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1;
2024             nlohmann::json::array_t managerForChassis;
2025             nlohmann::json::object_t managerObj;
2026             boost::urls::url chassiUrl = crow::utility::urlFromPieces(
2027                 "redfish", "v1", "Chassis", chassisId);
2028             managerObj["@odata.id"] = chassiUrl;
2029             managerForChassis.emplace_back(std::move(managerObj));
2030             aRsp->res.jsonValue["Links"]["ManagerForChassis"] =
2031                 std::move(managerForChassis);
2032             aRsp->res.jsonValue["Links"]["ManagerInChassis"]["@odata.id"] =
2033                 chassiUrl;
2034         });
2035 
2036         static bool started = false;
2037 
2038         if (!started)
2039         {
2040             sdbusplus::asio::getProperty<double>(
2041                 *crow::connections::systemBus, "org.freedesktop.systemd1",
2042                 "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager",
2043                 "Progress",
2044                 [asyncResp](const boost::system::error_code& ec,
2045                             const double& val) {
2046                 if (ec)
2047                 {
2048                     BMCWEB_LOG_ERROR << "Error while getting progress";
2049                     messages::internalError(asyncResp->res);
2050                     return;
2051                 }
2052                 if (val < 1.0)
2053                 {
2054                     asyncResp->res.jsonValue["Status"]["State"] = "Starting";
2055                     started = true;
2056                 }
2057                 });
2058         }
2059 
2060         constexpr std::array<std::string_view, 1> interfaces = {
2061             "xyz.openbmc_project.Inventory.Item.Bmc"};
2062         dbus::utility::getSubTree(
2063             "/xyz/openbmc_project/inventory", 0, interfaces,
2064             [asyncResp](
2065                 const boost::system::error_code& ec,
2066                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
2067             if (ec)
2068             {
2069                 BMCWEB_LOG_DEBUG << "D-Bus response error on GetSubTree " << ec;
2070                 return;
2071             }
2072             if (subtree.empty())
2073             {
2074                 BMCWEB_LOG_DEBUG << "Can't find bmc D-Bus object!";
2075                 return;
2076             }
2077             // Assume only 1 bmc D-Bus object
2078             // Throw an error if there is more than 1
2079             if (subtree.size() > 1)
2080             {
2081                 BMCWEB_LOG_DEBUG << "Found more than 1 bmc D-Bus object!";
2082                 messages::internalError(asyncResp->res);
2083                 return;
2084             }
2085 
2086             if (subtree[0].first.empty() || subtree[0].second.size() != 1)
2087             {
2088                 BMCWEB_LOG_DEBUG << "Error getting bmc D-Bus object!";
2089                 messages::internalError(asyncResp->res);
2090                 return;
2091             }
2092 
2093             const std::string& path = subtree[0].first;
2094             const std::string& connectionName = subtree[0].second[0].first;
2095 
2096             for (const auto& interfaceName : subtree[0].second[0].second)
2097             {
2098                 if (interfaceName ==
2099                     "xyz.openbmc_project.Inventory.Decorator.Asset")
2100                 {
2101                     sdbusplus::asio::getAllProperties(
2102                         *crow::connections::systemBus, connectionName, path,
2103                         "xyz.openbmc_project.Inventory.Decorator.Asset",
2104                         [asyncResp](const boost::system::error_code& ec2,
2105                                     const dbus::utility::DBusPropertiesMap&
2106                                         propertiesList) {
2107                         if (ec2)
2108                         {
2109                             BMCWEB_LOG_DEBUG << "Can't get bmc asset!";
2110                             return;
2111                         }
2112 
2113                         const std::string* partNumber = nullptr;
2114                         const std::string* serialNumber = nullptr;
2115                         const std::string* manufacturer = nullptr;
2116                         const std::string* model = nullptr;
2117                         const std::string* sparePartNumber = nullptr;
2118 
2119                         const bool success = sdbusplus::unpackPropertiesNoThrow(
2120                             dbus_utils::UnpackErrorPrinter(), propertiesList,
2121                             "PartNumber", partNumber, "SerialNumber",
2122                             serialNumber, "Manufacturer", manufacturer, "Model",
2123                             model, "SparePartNumber", sparePartNumber);
2124 
2125                         if (!success)
2126                         {
2127                             messages::internalError(asyncResp->res);
2128                             return;
2129                         }
2130 
2131                         if (partNumber != nullptr)
2132                         {
2133                             asyncResp->res.jsonValue["PartNumber"] =
2134                                 *partNumber;
2135                         }
2136 
2137                         if (serialNumber != nullptr)
2138                         {
2139                             asyncResp->res.jsonValue["SerialNumber"] =
2140                                 *serialNumber;
2141                         }
2142 
2143                         if (manufacturer != nullptr)
2144                         {
2145                             asyncResp->res.jsonValue["Manufacturer"] =
2146                                 *manufacturer;
2147                         }
2148 
2149                         if (model != nullptr)
2150                         {
2151                             asyncResp->res.jsonValue["Model"] = *model;
2152                         }
2153 
2154                         if (sparePartNumber != nullptr)
2155                         {
2156                             asyncResp->res.jsonValue["SparePartNumber"] =
2157                                 *sparePartNumber;
2158                         }
2159                         });
2160                 }
2161                 else if (interfaceName ==
2162                          "xyz.openbmc_project.Inventory.Decorator.LocationCode")
2163                 {
2164                     getLocation(asyncResp, connectionName, path);
2165                 }
2166             }
2167             });
2168         });
2169 
2170     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/")
2171         .privileges(redfish::privileges::patchManager)
2172         .methods(boost::beast::http::verb::patch)(
2173             [&app](const crow::Request& req,
2174                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2175         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2176         {
2177             return;
2178         }
2179         std::optional<nlohmann::json> oem;
2180         std::optional<nlohmann::json> links;
2181         std::optional<std::string> datetime;
2182 
2183         if (!json_util::readJsonPatch(req, asyncResp->res, "Oem", oem,
2184                                       "DateTime", datetime, "Links", links))
2185         {
2186             return;
2187         }
2188 
2189         if (oem)
2190         {
2191 #ifdef BMCWEB_ENABLE_REDFISH_OEM_MANAGER_FAN_DATA
2192             std::optional<nlohmann::json> openbmc;
2193             if (!redfish::json_util::readJson(*oem, asyncResp->res, "OpenBmc",
2194                                               openbmc))
2195             {
2196                 return;
2197             }
2198             if (openbmc)
2199             {
2200                 std::optional<nlohmann::json> fan;
2201                 if (!redfish::json_util::readJson(*openbmc, asyncResp->res,
2202                                                   "Fan", fan))
2203                 {
2204                     return;
2205                 }
2206                 if (fan)
2207                 {
2208                     auto pid = std::make_shared<SetPIDValues>(asyncResp, *fan);
2209                     pid->run();
2210                 }
2211             }
2212 #else
2213             messages::propertyUnknown(asyncResp->res, "Oem");
2214             return;
2215 #endif
2216         }
2217         if (links)
2218         {
2219             std::optional<nlohmann::json> activeSoftwareImage;
2220             if (!redfish::json_util::readJson(*links, asyncResp->res,
2221                                               "ActiveSoftwareImage",
2222                                               activeSoftwareImage))
2223             {
2224                 return;
2225             }
2226             if (activeSoftwareImage)
2227             {
2228                 std::optional<std::string> odataId;
2229                 if (!json_util::readJson(*activeSoftwareImage, asyncResp->res,
2230                                          "@odata.id", odataId))
2231                 {
2232                     return;
2233                 }
2234 
2235                 if (odataId)
2236                 {
2237                     setActiveFirmwareImage(asyncResp, *odataId);
2238                 }
2239             }
2240         }
2241         if (datetime)
2242         {
2243             setDateTime(asyncResp, std::move(*datetime));
2244         }
2245         });
2246 }
2247 
2248 inline void requestRoutesManagerCollection(App& app)
2249 {
2250     BMCWEB_ROUTE(app, "/redfish/v1/Managers/")
2251         .privileges(redfish::privileges::getManagerCollection)
2252         .methods(boost::beast::http::verb::get)(
2253             [&app](const crow::Request& req,
2254                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2255         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2256         {
2257             return;
2258         }
2259         // Collections don't include the static data added by SubRoute
2260         // because it has a duplicate entry for members
2261         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
2262         asyncResp->res.jsonValue["@odata.type"] =
2263             "#ManagerCollection.ManagerCollection";
2264         asyncResp->res.jsonValue["Name"] = "Manager Collection";
2265         asyncResp->res.jsonValue["Members@odata.count"] = 1;
2266         nlohmann::json::array_t members;
2267         nlohmann::json& bmc = members.emplace_back();
2268         bmc["@odata.id"] = "/redfish/v1/Managers/bmc";
2269         asyncResp->res.jsonValue["Members"] = std::move(members);
2270         });
2271 }
2272 } // namespace redfish
2273