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