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