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