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