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