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             return CreatePIDRet::fail;
933         }
934         if (zones)
935         {
936             std::vector<std::string> zonesStr;
937             if (!getZonesFromJsonReq(response, *zones, zonesStr))
938             {
939                 BMCWEB_LOG_ERROR << "Illegal Zones";
940                 return CreatePIDRet::fail;
941             }
942             if (chassis.empty() &&
943                 findChassis(managedObj, zonesStr[0], chassis) == nullptr)
944             {
945                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
946                 messages::invalidObject(
947                     response->res, crow::utility::urlFromPieces(
948                                        "redfish", "v1", "Chassis", chassis));
949                 return CreatePIDRet::fail;
950             }
951             output.emplace_back("Zones", std::move(zonesStr));
952         }
953 
954         if (inputs)
955         {
956             for (std::string& value : *inputs)
957             {
958                 std::replace(value.begin(), value.end(), '_', ' ');
959             }
960             output.emplace_back("Inputs", *inputs);
961         }
962 
963         if (outputs)
964         {
965             for (std::string& value : *outputs)
966             {
967                 std::replace(value.begin(), value.end(), '_', ' ');
968             }
969             output.emplace_back("Outputs", *outputs);
970         }
971 
972         if (setpointOffset)
973         {
974             // translate between redfish and dbus names
975             if (*setpointOffset == "UpperThresholdNonCritical")
976             {
977                 output.emplace_back("SetPointOffset", "WarningLow");
978             }
979             else if (*setpointOffset == "LowerThresholdNonCritical")
980             {
981                 output.emplace_back("SetPointOffset", "WarningHigh");
982             }
983             else if (*setpointOffset == "LowerThresholdCritical")
984             {
985                 output.emplace_back("SetPointOffset", "CriticalLow");
986             }
987             else if (*setpointOffset == "UpperThresholdCritical")
988             {
989                 output.emplace_back("SetPointOffset", "CriticalHigh");
990             }
991             else
992             {
993                 BMCWEB_LOG_ERROR << "Invalid setpointoffset "
994                                  << *setpointOffset;
995                 messages::propertyValueNotInList(response->res, it.key(),
996                                                  "SetPointOffset");
997                 return CreatePIDRet::fail;
998             }
999         }
1000 
1001         // doubles
1002         for (const auto& pairs : doubles)
1003         {
1004             if (!pairs.second)
1005             {
1006                 continue;
1007             }
1008             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
1009             output.emplace_back(pairs.first, *pairs.second);
1010         }
1011     }
1012 
1013     else if (type == "FanZones")
1014     {
1015         output.emplace_back("Type", "Pid.Zone");
1016 
1017         std::optional<nlohmann::json> chassisContainer;
1018         std::optional<double> failSafePercent;
1019         std::optional<double> minThermalOutput;
1020         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
1021                                           chassisContainer, "FailSafePercent",
1022                                           failSafePercent, "MinThermalOutput",
1023                                           minThermalOutput))
1024         {
1025             return CreatePIDRet::fail;
1026         }
1027 
1028         if (chassisContainer)
1029         {
1030 
1031             std::string chassisId;
1032             if (!redfish::json_util::readJson(*chassisContainer, response->res,
1033                                               "@odata.id", chassisId))
1034             {
1035                 return CreatePIDRet::fail;
1036             }
1037 
1038             // /redfish/v1/chassis/chassis_name/
1039             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
1040             {
1041                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
1042                 messages::invalidObject(
1043                     response->res, crow::utility::urlFromPieces(
1044                                        "redfish", "v1", "Chassis", chassisId));
1045                 return CreatePIDRet::fail;
1046             }
1047         }
1048         if (minThermalOutput)
1049         {
1050             output.emplace_back("MinThermalOutput", *minThermalOutput);
1051         }
1052         if (failSafePercent)
1053         {
1054             output.emplace_back("FailSafePercent", *failSafePercent);
1055         }
1056     }
1057     else if (type == "StepwiseControllers")
1058     {
1059         output.emplace_back("Type", "Stepwise");
1060 
1061         std::optional<std::vector<nlohmann::json>> zones;
1062         std::optional<std::vector<nlohmann::json>> steps;
1063         std::optional<std::vector<std::string>> inputs;
1064         std::optional<double> positiveHysteresis;
1065         std::optional<double> negativeHysteresis;
1066         std::optional<std::string> direction; // upper clipping curve vs lower
1067         if (!redfish::json_util::readJson(
1068                 it.value(), response->res, "Zones", zones, "Steps", steps,
1069                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
1070                 "NegativeHysteresis", negativeHysteresis, "Direction",
1071                 direction))
1072         {
1073             return CreatePIDRet::fail;
1074         }
1075 
1076         if (zones)
1077         {
1078             std::vector<std::string> zonesStrs;
1079             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
1080             {
1081                 BMCWEB_LOG_ERROR << "Illegal Zones";
1082                 return CreatePIDRet::fail;
1083             }
1084             if (chassis.empty() &&
1085                 findChassis(managedObj, zonesStrs[0], chassis) == nullptr)
1086             {
1087                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
1088                 messages::invalidObject(
1089                     response->res, crow::utility::urlFromPieces(
1090                                        "redfish", "v1", "Chassis", chassis));
1091                 return CreatePIDRet::fail;
1092             }
1093             output.emplace_back("Zones", std::move(zonesStrs));
1094         }
1095         if (steps)
1096         {
1097             std::vector<double> readings;
1098             std::vector<double> outputs;
1099             for (auto& step : *steps)
1100             {
1101                 double target = 0.0;
1102                 double out = 0.0;
1103 
1104                 if (!redfish::json_util::readJson(step, response->res, "Target",
1105                                                   target, "Output", out))
1106                 {
1107                     return CreatePIDRet::fail;
1108                 }
1109                 readings.emplace_back(target);
1110                 outputs.emplace_back(out);
1111             }
1112             output.emplace_back("Reading", std::move(readings));
1113             output.emplace_back("Output", std::move(outputs));
1114         }
1115         if (inputs)
1116         {
1117             for (std::string& value : *inputs)
1118             {
1119 
1120                 std::replace(value.begin(), value.end(), '_', ' ');
1121             }
1122             output.emplace_back("Inputs", std::move(*inputs));
1123         }
1124         if (negativeHysteresis)
1125         {
1126             output.emplace_back("NegativeHysteresis", *negativeHysteresis);
1127         }
1128         if (positiveHysteresis)
1129         {
1130             output.emplace_back("PositiveHysteresis", *positiveHysteresis);
1131         }
1132         if (direction)
1133         {
1134             constexpr const std::array<const char*, 2> allowedDirections = {
1135                 "Ceiling", "Floor"};
1136             if (std::find(allowedDirections.begin(), allowedDirections.end(),
1137                           *direction) == allowedDirections.end())
1138             {
1139                 messages::propertyValueTypeError(response->res, "Direction",
1140                                                  *direction);
1141                 return CreatePIDRet::fail;
1142             }
1143             output.emplace_back("Class", *direction);
1144         }
1145     }
1146     else
1147     {
1148         BMCWEB_LOG_ERROR << "Illegal Type " << type;
1149         messages::propertyUnknown(response->res, type);
1150         return CreatePIDRet::fail;
1151     }
1152     return CreatePIDRet::patch;
1153 }
1154 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
1155 {
1156     struct CompletionValues
1157     {
1158         std::vector<std::string> supportedProfiles;
1159         std::string currentProfile;
1160         dbus::utility::MapperGetSubTreeResponse subtree;
1161     };
1162 
1163     explicit GetPIDValues(
1164         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
1165         asyncResp(asyncRespIn)
1166 
1167     {}
1168 
1169     void run()
1170     {
1171         std::shared_ptr<GetPIDValues> self = shared_from_this();
1172 
1173         // get all configurations
1174         constexpr std::array<std::string_view, 4> interfaces = {
1175             pidConfigurationIface, pidZoneConfigurationIface,
1176             objectManagerIface, stepwiseConfigurationIface};
1177         dbus::utility::getSubTree(
1178             "/", 0, interfaces,
1179             [self](
1180                 const boost::system::error_code& ec,
1181                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1182             if (ec)
1183             {
1184                 BMCWEB_LOG_ERROR << ec;
1185                 messages::internalError(self->asyncResp->res);
1186                 return;
1187             }
1188             self->complete.subtree = subtreeLocal;
1189             });
1190 
1191         // at the same time get the selected profile
1192         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1193             thermalModeIface};
1194         dbus::utility::getSubTree(
1195             "/", 0, thermalModeIfaces,
1196             [self](
1197                 const boost::system::error_code& ec,
1198                 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1199             if (ec || subtreeLocal.empty())
1200             {
1201                 return;
1202             }
1203             if (subtreeLocal[0].second.size() != 1)
1204             {
1205                 // invalid mapper response, should never happen
1206                 BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error";
1207                 messages::internalError(self->asyncResp->res);
1208                 return;
1209             }
1210 
1211             const std::string& path = subtreeLocal[0].first;
1212             const std::string& owner = subtreeLocal[0].second[0].first;
1213 
1214             sdbusplus::asio::getAllProperties(
1215                 *crow::connections::systemBus, owner, path, thermalModeIface,
1216                 [path, owner,
1217                  self](const boost::system::error_code& ec2,
1218                        const dbus::utility::DBusPropertiesMap& resp) {
1219                 if (ec2)
1220                 {
1221                     BMCWEB_LOG_ERROR
1222                         << "GetPIDValues: Can't get thermalModeIface " << path;
1223                     messages::internalError(self->asyncResp->res);
1224                     return;
1225                 }
1226 
1227                 const std::string* current = nullptr;
1228                 const std::vector<std::string>* supported = nullptr;
1229 
1230                 const bool success = sdbusplus::unpackPropertiesNoThrow(
1231                     dbus_utils::UnpackErrorPrinter(), resp, "Current", current,
1232                     "Supported", supported);
1233 
1234                 if (!success)
1235                 {
1236                     messages::internalError(self->asyncResp->res);
1237                     return;
1238                 }
1239 
1240                 if (current == nullptr || supported == nullptr)
1241                 {
1242                     BMCWEB_LOG_ERROR
1243                         << "GetPIDValues: thermal mode iface invalid " << path;
1244                     messages::internalError(self->asyncResp->res);
1245                     return;
1246                 }
1247                 self->complete.currentProfile = *current;
1248                 self->complete.supportedProfiles = *supported;
1249                 });
1250             });
1251     }
1252 
1253     static void
1254         processingComplete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1255                            const CompletionValues& completion)
1256     {
1257         if (asyncResp->res.result() != boost::beast::http::status::ok)
1258         {
1259             return;
1260         }
1261         // create map of <connection, path to objMgr>>
1262         boost::container::flat_map<
1263             std::string, std::string, std::less<>,
1264             std::vector<std::pair<std::string, std::string>>>
1265             objectMgrPaths;
1266         boost::container::flat_set<std::string, std::less<>,
1267                                    std::vector<std::string>>
1268             calledConnections;
1269         for (const auto& pathGroup : completion.subtree)
1270         {
1271             for (const auto& connectionGroup : pathGroup.second)
1272             {
1273                 auto findConnection =
1274                     calledConnections.find(connectionGroup.first);
1275                 if (findConnection != calledConnections.end())
1276                 {
1277                     break;
1278                 }
1279                 for (const std::string& interface : connectionGroup.second)
1280                 {
1281                     if (interface == objectManagerIface)
1282                     {
1283                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1284                     }
1285                     // this list is alphabetical, so we
1286                     // should have found the objMgr by now
1287                     if (interface == pidConfigurationIface ||
1288                         interface == pidZoneConfigurationIface ||
1289                         interface == stepwiseConfigurationIface)
1290                     {
1291                         auto findObjMgr =
1292                             objectMgrPaths.find(connectionGroup.first);
1293                         if (findObjMgr == objectMgrPaths.end())
1294                         {
1295                             BMCWEB_LOG_DEBUG << connectionGroup.first
1296                                              << "Has no Object Manager";
1297                             continue;
1298                         }
1299 
1300                         calledConnections.insert(connectionGroup.first);
1301 
1302                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1303                                          completion.currentProfile,
1304                                          completion.supportedProfiles,
1305                                          asyncResp);
1306                         break;
1307                     }
1308                 }
1309             }
1310         }
1311     }
1312 
1313     ~GetPIDValues()
1314     {
1315         boost::asio::post(crow::connections::systemBus->get_io_context(),
1316                           std::bind_front(&processingComplete, asyncResp,
1317                                           std::move(complete)));
1318     }
1319 
1320     GetPIDValues(const GetPIDValues&) = delete;
1321     GetPIDValues(GetPIDValues&&) = delete;
1322     GetPIDValues& operator=(const GetPIDValues&) = delete;
1323     GetPIDValues& operator=(GetPIDValues&&) = delete;
1324 
1325     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1326     CompletionValues complete;
1327 };
1328 
1329 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1330 {
1331 
1332     SetPIDValues(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
1333                  nlohmann::json& data) :
1334         asyncResp(asyncRespIn)
1335     {
1336 
1337         std::optional<nlohmann::json> pidControllers;
1338         std::optional<nlohmann::json> fanControllers;
1339         std::optional<nlohmann::json> fanZones;
1340         std::optional<nlohmann::json> stepwiseControllers;
1341 
1342         if (!redfish::json_util::readJson(
1343                 data, asyncResp->res, "PidControllers", pidControllers,
1344                 "FanControllers", fanControllers, "FanZones", fanZones,
1345                 "StepwiseControllers", stepwiseControllers, "Profile", profile))
1346         {
1347             return;
1348         }
1349         configuration.emplace_back("PidControllers", std::move(pidControllers));
1350         configuration.emplace_back("FanControllers", std::move(fanControllers));
1351         configuration.emplace_back("FanZones", std::move(fanZones));
1352         configuration.emplace_back("StepwiseControllers",
1353                                    std::move(stepwiseControllers));
1354     }
1355 
1356     SetPIDValues(const SetPIDValues&) = delete;
1357     SetPIDValues(SetPIDValues&&) = delete;
1358     SetPIDValues& operator=(const SetPIDValues&) = delete;
1359     SetPIDValues& operator=(SetPIDValues&&) = delete;
1360 
1361     void run()
1362     {
1363         if (asyncResp->res.result() != boost::beast::http::status::ok)
1364         {
1365             return;
1366         }
1367 
1368         std::shared_ptr<SetPIDValues> self = shared_from_this();
1369 
1370         // todo(james): might make sense to do a mapper call here if this
1371         // interface gets more traction
1372         crow::connections::systemBus->async_method_call(
1373             [self](const boost::system::error_code& ec,
1374                    const dbus::utility::ManagedObjectType& mObj) {
1375             if (ec)
1376             {
1377                 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
1378                 messages::internalError(self->asyncResp->res);
1379                 return;
1380             }
1381             const std::array<const char*, 3> configurations = {
1382                 pidConfigurationIface, pidZoneConfigurationIface,
1383                 stepwiseConfigurationIface};
1384 
1385             for (const auto& [path, object] : mObj)
1386             {
1387                 for (const auto& [interface, _] : object)
1388                 {
1389                     if (std::find(configurations.begin(), configurations.end(),
1390                                   interface) != configurations.end())
1391                     {
1392                         self->objectCount++;
1393                         break;
1394                     }
1395                 }
1396             }
1397             self->managedObj = mObj;
1398             },
1399             "xyz.openbmc_project.EntityManager",
1400             "/xyz/openbmc_project/inventory", objectManagerIface,
1401             "GetManagedObjects");
1402 
1403         // at the same time get the profile information
1404         constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1405             thermalModeIface};
1406         dbus::utility::getSubTree(
1407             "/", 0, thermalModeIfaces,
1408             [self](const boost::system::error_code& ec,
1409                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
1410             if (ec || subtree.empty())
1411             {
1412                 return;
1413             }
1414             if (subtree[0].second.empty())
1415             {
1416                 // invalid mapper response, should never happen
1417                 BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error";
1418                 messages::internalError(self->asyncResp->res);
1419                 return;
1420             }
1421 
1422             const std::string& path = subtree[0].first;
1423             const std::string& owner = subtree[0].second[0].first;
1424             sdbusplus::asio::getAllProperties(
1425                 *crow::connections::systemBus, owner, path, thermalModeIface,
1426                 [self, path, owner](const boost::system::error_code& ec2,
1427                                     const dbus::utility::DBusPropertiesMap& r) {
1428                 if (ec2)
1429                 {
1430                     BMCWEB_LOG_ERROR
1431                         << "SetPIDValues: Can't get thermalModeIface " << path;
1432                     messages::internalError(self->asyncResp->res);
1433                     return;
1434                 }
1435                 const std::string* current = nullptr;
1436                 const std::vector<std::string>* supported = nullptr;
1437 
1438                 const bool success = sdbusplus::unpackPropertiesNoThrow(
1439                     dbus_utils::UnpackErrorPrinter(), r, "Current", current,
1440                     "Supported", supported);
1441 
1442                 if (!success)
1443                 {
1444                     messages::internalError(self->asyncResp->res);
1445                     return;
1446                 }
1447 
1448                 if (current == nullptr || supported == nullptr)
1449                 {
1450                     BMCWEB_LOG_ERROR
1451                         << "SetPIDValues: thermal mode iface invalid " << path;
1452                     messages::internalError(self->asyncResp->res);
1453                     return;
1454                 }
1455                 self->currentProfile = *current;
1456                 self->supportedProfiles = *supported;
1457                 self->profileConnection = owner;
1458                 self->profilePath = path;
1459                 });
1460             });
1461     }
1462     void pidSetDone()
1463     {
1464         if (asyncResp->res.result() != boost::beast::http::status::ok)
1465         {
1466             return;
1467         }
1468         std::shared_ptr<bmcweb::AsyncResp> response = asyncResp;
1469         if (profile)
1470         {
1471             if (std::find(supportedProfiles.begin(), supportedProfiles.end(),
1472                           *profile) == supportedProfiles.end())
1473             {
1474                 messages::actionParameterUnknown(response->res, "Profile",
1475                                                  *profile);
1476                 return;
1477             }
1478             currentProfile = *profile;
1479             crow::connections::systemBus->async_method_call(
1480                 [response](const boost::system::error_code& ec) {
1481                 if (ec)
1482                 {
1483                     BMCWEB_LOG_ERROR << "Error patching profile" << ec;
1484                     messages::internalError(response->res);
1485                 }
1486                 },
1487                 profileConnection, profilePath,
1488                 "org.freedesktop.DBus.Properties", "Set", thermalModeIface,
1489                 "Current", dbus::utility::DbusVariantType(*profile));
1490         }
1491 
1492         for (auto& containerPair : configuration)
1493         {
1494             auto& container = containerPair.second;
1495             if (!container)
1496             {
1497                 continue;
1498             }
1499             BMCWEB_LOG_DEBUG << *container;
1500 
1501             const std::string& type = containerPair.first;
1502 
1503             for (nlohmann::json::iterator it = container->begin();
1504                  it != container->end(); ++it)
1505             {
1506                 const auto& name = it.key();
1507                 std::string dbusObjName = name;
1508                 std::replace(dbusObjName.begin(), dbusObjName.end(), ' ', '_');
1509                 BMCWEB_LOG_DEBUG << "looking for " << name;
1510 
1511                 auto pathItr =
1512                     std::find_if(managedObj.begin(), managedObj.end(),
1513                                  [&dbusObjName](const auto& obj) {
1514                     return boost::algorithm::ends_with(obj.first.str,
1515                                                        "/" + dbusObjName);
1516                     });
1517                 dbus::utility::DBusPropertiesMap output;
1518 
1519                 output.reserve(16); // The pid interface length
1520 
1521                 // determines if we're patching entity-manager or
1522                 // creating a new object
1523                 bool createNewObject = (pathItr == managedObj.end());
1524                 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject;
1525 
1526                 std::string iface;
1527                 if (!createNewObject)
1528                 {
1529                     bool findInterface = false;
1530                     for (const auto& interface : pathItr->second)
1531                     {
1532                         if (interface.first == pidConfigurationIface)
1533                         {
1534                             if (type == "PidControllers" ||
1535                                 type == "FanControllers")
1536                             {
1537                                 iface = pidConfigurationIface;
1538                                 findInterface = true;
1539                                 break;
1540                             }
1541                         }
1542                         else if (interface.first == pidZoneConfigurationIface)
1543                         {
1544                             if (type == "FanZones")
1545                             {
1546                                 iface = pidConfigurationIface;
1547                                 findInterface = true;
1548                                 break;
1549                             }
1550                         }
1551                         else if (interface.first == stepwiseConfigurationIface)
1552                         {
1553                             if (type == "StepwiseControllers")
1554                             {
1555                                 iface = stepwiseConfigurationIface;
1556                                 findInterface = true;
1557                                 break;
1558                             }
1559                         }
1560                     }
1561 
1562                     // create new object if interface not found
1563                     if (!findInterface)
1564                     {
1565                         createNewObject = true;
1566                     }
1567                 }
1568 
1569                 if (createNewObject && it.value() == nullptr)
1570                 {
1571                     // can't delete a non-existent object
1572                     messages::propertyValueNotInList(response->res,
1573                                                      it.value().dump(), name);
1574                     continue;
1575                 }
1576 
1577                 std::string path;
1578                 if (pathItr != managedObj.end())
1579                 {
1580                     path = pathItr->first.str;
1581                 }
1582 
1583                 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n";
1584 
1585                 // arbitrary limit to avoid attacks
1586                 constexpr const size_t controllerLimit = 500;
1587                 if (createNewObject && objectCount >= controllerLimit)
1588                 {
1589                     messages::resourceExhaustion(response->res, type);
1590                     continue;
1591                 }
1592                 std::string escaped = name;
1593                 std::replace(escaped.begin(), escaped.end(), '_', ' ');
1594                 output.emplace_back("Name", escaped);
1595 
1596                 std::string chassis;
1597                 CreatePIDRet ret = createPidInterface(
1598                     response, type, it, path, managedObj, createNewObject,
1599                     output, chassis, currentProfile);
1600                 if (ret == CreatePIDRet::fail)
1601                 {
1602                     return;
1603                 }
1604                 if (ret == CreatePIDRet::del)
1605                 {
1606                     continue;
1607                 }
1608 
1609                 if (!createNewObject)
1610                 {
1611                     for (const auto& property : output)
1612                     {
1613                         crow::connections::systemBus->async_method_call(
1614                             [response,
1615                              propertyName{std::string(property.first)}](
1616                                 const boost::system::error_code& ec) {
1617                             if (ec)
1618                             {
1619                                 BMCWEB_LOG_ERROR << "Error patching "
1620                                                  << propertyName << ": " << ec;
1621                                 messages::internalError(response->res);
1622                                 return;
1623                             }
1624                             messages::success(response->res);
1625                             },
1626                             "xyz.openbmc_project.EntityManager", path,
1627                             "org.freedesktop.DBus.Properties", "Set", iface,
1628                             property.first, property.second);
1629                     }
1630                 }
1631                 else
1632                 {
1633                     if (chassis.empty())
1634                     {
1635                         BMCWEB_LOG_ERROR << "Failed to get chassis from config";
1636                         messages::internalError(response->res);
1637                         return;
1638                     }
1639 
1640                     bool foundChassis = false;
1641                     for (const auto& obj : managedObj)
1642                     {
1643                         if (boost::algorithm::ends_with(obj.first.str, chassis))
1644                         {
1645                             chassis = obj.first.str;
1646                             foundChassis = true;
1647                             break;
1648                         }
1649                     }
1650                     if (!foundChassis)
1651                     {
1652                         BMCWEB_LOG_ERROR << "Failed to find chassis on dbus";
1653                         messages::resourceMissingAtURI(
1654                             response->res,
1655                             crow::utility::urlFromPieces("redfish", "v1",
1656                                                          "Chassis", chassis));
1657                         return;
1658                     }
1659 
1660                     crow::connections::systemBus->async_method_call(
1661                         [response](const boost::system::error_code& ec) {
1662                         if (ec)
1663                         {
1664                             BMCWEB_LOG_ERROR << "Error Adding Pid Object "
1665                                              << ec;
1666                             messages::internalError(response->res);
1667                             return;
1668                         }
1669                         messages::success(response->res);
1670                         },
1671                         "xyz.openbmc_project.EntityManager", chassis,
1672                         "xyz.openbmc_project.AddObject", "AddObject", output);
1673                 }
1674             }
1675         }
1676     }
1677 
1678     ~SetPIDValues()
1679     {
1680         try
1681         {
1682             pidSetDone();
1683         }
1684         catch (...)
1685         {
1686             BMCWEB_LOG_CRITICAL << "pidSetDone threw exception";
1687         }
1688     }
1689 
1690     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1691     std::vector<std::pair<std::string, std::optional<nlohmann::json>>>
1692         configuration;
1693     std::optional<std::string> profile;
1694     dbus::utility::ManagedObjectType managedObj;
1695     std::vector<std::string> supportedProfiles;
1696     std::string currentProfile;
1697     std::string profileConnection;
1698     std::string profilePath;
1699     size_t objectCount = 0;
1700 };
1701 
1702 /**
1703  * @brief Retrieves BMC manager location data over DBus
1704  *
1705  * @param[in] aResp Shared pointer for completing asynchronous calls
1706  * @param[in] connectionName - service name
1707  * @param[in] path - object path
1708  * @return none
1709  */
1710 inline void getLocation(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
1711                         const std::string& connectionName,
1712                         const std::string& path)
1713 {
1714     BMCWEB_LOG_DEBUG << "Get BMC manager Location data.";
1715 
1716     sdbusplus::asio::getProperty<std::string>(
1717         *crow::connections::systemBus, connectionName, path,
1718         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
1719         [aResp](const boost::system::error_code& ec,
1720                 const std::string& property) {
1721         if (ec)
1722         {
1723             BMCWEB_LOG_DEBUG << "DBUS response error for "
1724                                 "Location";
1725             messages::internalError(aResp->res);
1726             return;
1727         }
1728 
1729         aResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
1730             property;
1731         });
1732 }
1733 // avoid name collision systems.hpp
1734 inline void
1735     managerGetLastResetTime(const std::shared_ptr<bmcweb::AsyncResp>& aResp)
1736 {
1737     BMCWEB_LOG_DEBUG << "Getting Manager Last Reset Time";
1738 
1739     sdbusplus::asio::getProperty<uint64_t>(
1740         *crow::connections::systemBus, "xyz.openbmc_project.State.BMC",
1741         "/xyz/openbmc_project/state/bmc0", "xyz.openbmc_project.State.BMC",
1742         "LastRebootTime",
1743         [aResp](const boost::system::error_code& ec,
1744                 const uint64_t lastResetTime) {
1745         if (ec)
1746         {
1747             BMCWEB_LOG_DEBUG << "D-BUS response error " << ec;
1748             return;
1749         }
1750 
1751         // LastRebootTime is epoch time, in milliseconds
1752         // https://github.com/openbmc/phosphor-dbus-interfaces/blob/7f9a128eb9296e926422ddc312c148b625890bb6/xyz/openbmc_project/State/BMC.interface.yaml#L19
1753         uint64_t lastResetTimeStamp = lastResetTime / 1000;
1754 
1755         // Convert to ISO 8601 standard
1756         aResp->res.jsonValue["LastResetTime"] =
1757             redfish::time_utils::getDateTimeUint(lastResetTimeStamp);
1758         });
1759 }
1760 
1761 /**
1762  * @brief Set the running firmware image
1763  *
1764  * @param[i,o] aResp - Async response object
1765  * @param[i] runningFirmwareTarget - Image to make the running image
1766  *
1767  * @return void
1768  */
1769 inline void
1770     setActiveFirmwareImage(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
1771                            const std::string& runningFirmwareTarget)
1772 {
1773     // Get the Id from /redfish/v1/UpdateService/FirmwareInventory/<Id>
1774     std::string::size_type idPos = runningFirmwareTarget.rfind('/');
1775     if (idPos == std::string::npos)
1776     {
1777         messages::propertyValueNotInList(aResp->res, runningFirmwareTarget,
1778                                          "@odata.id");
1779         BMCWEB_LOG_DEBUG << "Can't parse firmware ID!";
1780         return;
1781     }
1782     idPos++;
1783     if (idPos >= runningFirmwareTarget.size())
1784     {
1785         messages::propertyValueNotInList(aResp->res, runningFirmwareTarget,
1786                                          "@odata.id");
1787         BMCWEB_LOG_DEBUG << "Invalid firmware ID.";
1788         return;
1789     }
1790     std::string firmwareId = runningFirmwareTarget.substr(idPos);
1791 
1792     // Make sure the image is valid before setting priority
1793     crow::connections::systemBus->async_method_call(
1794         [aResp, firmwareId,
1795          runningFirmwareTarget](const boost::system::error_code& ec,
1796                                 dbus::utility::ManagedObjectType& subtree) {
1797         if (ec)
1798         {
1799             BMCWEB_LOG_DEBUG << "D-Bus response error getting objects.";
1800             messages::internalError(aResp->res);
1801             return;
1802         }
1803 
1804         if (subtree.empty())
1805         {
1806             BMCWEB_LOG_DEBUG << "Can't find image!";
1807             messages::internalError(aResp->res);
1808             return;
1809         }
1810 
1811         bool foundImage = false;
1812         for (const auto& object : subtree)
1813         {
1814             const std::string& path =
1815                 static_cast<const std::string&>(object.first);
1816             std::size_t idPos2 = path.rfind('/');
1817 
1818             if (idPos2 == std::string::npos)
1819             {
1820                 continue;
1821             }
1822 
1823             idPos2++;
1824             if (idPos2 >= path.size())
1825             {
1826                 continue;
1827             }
1828 
1829             if (path.substr(idPos2) == firmwareId)
1830             {
1831                 foundImage = true;
1832                 break;
1833             }
1834         }
1835 
1836         if (!foundImage)
1837         {
1838             messages::propertyValueNotInList(aResp->res, runningFirmwareTarget,
1839                                              "@odata.id");
1840             BMCWEB_LOG_DEBUG << "Invalid firmware ID.";
1841             return;
1842         }
1843 
1844         BMCWEB_LOG_DEBUG << "Setting firmware version " << firmwareId
1845                          << " to priority 0.";
1846 
1847         // Only support Immediate
1848         // An addition could be a Redfish Setting like
1849         // ActiveSoftwareImageApplyTime and support OnReset
1850         crow::connections::systemBus->async_method_call(
1851             [aResp](const boost::system::error_code& ec2) {
1852             if (ec2)
1853             {
1854                 BMCWEB_LOG_DEBUG << "D-Bus response error setting.";
1855                 messages::internalError(aResp->res);
1856                 return;
1857             }
1858             doBMCGracefulRestart(aResp);
1859             },
1860 
1861             "xyz.openbmc_project.Software.BMC.Updater",
1862             "/xyz/openbmc_project/software/" + firmwareId,
1863             "org.freedesktop.DBus.Properties", "Set",
1864             "xyz.openbmc_project.Software.RedundancyPriority", "Priority",
1865             dbus::utility::DbusVariantType(static_cast<uint8_t>(0)));
1866         },
1867         "xyz.openbmc_project.Software.BMC.Updater",
1868         "/xyz/openbmc_project/software", "org.freedesktop.DBus.ObjectManager",
1869         "GetManagedObjects");
1870 }
1871 
1872 inline void setDateTime(std::shared_ptr<bmcweb::AsyncResp> aResp,
1873                         std::string datetime)
1874 {
1875     BMCWEB_LOG_DEBUG << "Set date time: " << datetime;
1876 
1877     std::optional<redfish::time_utils::usSinceEpoch> us =
1878         redfish::time_utils::dateStringToEpoch(datetime);
1879     if (!us)
1880     {
1881         messages::propertyValueFormatError(aResp->res, datetime, "DateTime");
1882         return;
1883     }
1884     crow::connections::systemBus->async_method_call(
1885         [aResp{std::move(aResp)},
1886          datetime{std::move(datetime)}](const boost::system::error_code& ec) {
1887         if (ec)
1888         {
1889             BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
1890                                 "DBUS response error "
1891                              << ec;
1892             messages::internalError(aResp->res);
1893             return;
1894         }
1895         aResp->res.jsonValue["DateTime"] = datetime;
1896         },
1897         "xyz.openbmc_project.Time.Manager", "/xyz/openbmc_project/time/bmc",
1898         "org.freedesktop.DBus.Properties", "Set",
1899         "xyz.openbmc_project.Time.EpochTime", "Elapsed",
1900         dbus::utility::DbusVariantType(us->count()));
1901 }
1902 
1903 inline void requestRoutesManager(App& app)
1904 {
1905     std::string uuid = persistent_data::getConfig().systemUuid;
1906 
1907     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/")
1908         .privileges(redfish::privileges::getManager)
1909         .methods(boost::beast::http::verb::get)(
1910             [&app, uuid](const crow::Request& req,
1911                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1912         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1913         {
1914             return;
1915         }
1916         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
1917         asyncResp->res.jsonValue["@odata.type"] = "#Manager.v1_14_0.Manager";
1918         asyncResp->res.jsonValue["Id"] = "bmc";
1919         asyncResp->res.jsonValue["Name"] = "OpenBmc Manager";
1920         asyncResp->res.jsonValue["Description"] =
1921             "Baseboard Management Controller";
1922         asyncResp->res.jsonValue["PowerState"] = "On";
1923         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
1924         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
1925 
1926         asyncResp->res.jsonValue["ManagerType"] = "BMC";
1927         asyncResp->res.jsonValue["UUID"] = systemd_utils::getUuid();
1928         asyncResp->res.jsonValue["ServiceEntryPointUUID"] = uuid;
1929         asyncResp->res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
1930 
1931         asyncResp->res.jsonValue["LogServices"]["@odata.id"] =
1932             "/redfish/v1/Managers/bmc/LogServices";
1933         asyncResp->res.jsonValue["NetworkProtocol"]["@odata.id"] =
1934             "/redfish/v1/Managers/bmc/NetworkProtocol";
1935         asyncResp->res.jsonValue["EthernetInterfaces"]["@odata.id"] =
1936             "/redfish/v1/Managers/bmc/EthernetInterfaces";
1937 
1938 #ifdef BMCWEB_ENABLE_VM_NBDPROXY
1939         asyncResp->res.jsonValue["VirtualMedia"]["@odata.id"] =
1940             "/redfish/v1/Managers/bmc/VirtualMedia";
1941 #endif // BMCWEB_ENABLE_VM_NBDPROXY
1942 
1943         // default oem data
1944         nlohmann::json& oem = asyncResp->res.jsonValue["Oem"];
1945         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
1946         oem["@odata.type"] = "#OemManager.Oem";
1947         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
1948         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
1949         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
1950 
1951         nlohmann::json::object_t certificates;
1952         certificates["@odata.id"] =
1953             "/redfish/v1/Managers/bmc/Truststore/Certificates";
1954         oemOpenbmc["Certificates"] = std::move(certificates);
1955 
1956         // Manager.Reset (an action) can be many values, OpenBMC only
1957         // supports BMC reboot.
1958         nlohmann::json& managerReset =
1959             asyncResp->res.jsonValue["Actions"]["#Manager.Reset"];
1960         managerReset["target"] =
1961             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
1962         managerReset["@Redfish.ActionInfo"] =
1963             "/redfish/v1/Managers/bmc/ResetActionInfo";
1964 
1965         // ResetToDefaults (Factory Reset) has values like
1966         // PreserveNetworkAndUsers and PreserveNetwork that aren't supported
1967         // on OpenBMC
1968         nlohmann::json& resetToDefaults =
1969             asyncResp->res.jsonValue["Actions"]["#Manager.ResetToDefaults"];
1970         resetToDefaults["target"] =
1971             "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults";
1972         resetToDefaults["ResetType@Redfish.AllowableValues"] =
1973             nlohmann::json::array_t({"ResetAll"});
1974 
1975         std::pair<std::string, std::string> redfishDateTimeOffset =
1976             redfish::time_utils::getDateTimeOffsetNow();
1977 
1978         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
1979         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
1980             redfishDateTimeOffset.second;
1981 
1982         // TODO (Gunnar): Remove these one day since moved to ComputerSystem
1983         // Still used by OCP profiles
1984         // https://github.com/opencomputeproject/OCP-Profiles/issues/23
1985         // Fill in SerialConsole info
1986         asyncResp->res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
1987         asyncResp->res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15;
1988         asyncResp->res.jsonValue["SerialConsole"]["ConnectTypesSupported"] =
1989             nlohmann::json::array_t({"IPMI", "SSH"});
1990 #ifdef BMCWEB_ENABLE_KVM
1991         // Fill in GraphicalConsole info
1992         asyncResp->res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true;
1993         asyncResp->res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] =
1994             4;
1995         asyncResp->res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] =
1996             nlohmann::json::array_t({"KVMIP"});
1997 #endif // BMCWEB_ENABLE_KVM
1998 
1999         asyncResp->res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
2000 
2001         nlohmann::json::array_t managerForServers;
2002         nlohmann::json::object_t manager;
2003         manager["@odata.id"] = "/redfish/v1/Systems/system";
2004         managerForServers.push_back(std::move(manager));
2005 
2006         asyncResp->res.jsonValue["Links"]["ManagerForServers"] =
2007             std::move(managerForServers);
2008 
2009         auto health = std::make_shared<HealthPopulate>(asyncResp);
2010         health->isManagersHealth = true;
2011         health->populate();
2012 
2013         sw_util::populateSoftwareInformation(asyncResp, sw_util::bmcPurpose,
2014                                              "FirmwareVersion", true);
2015 
2016         managerGetLastResetTime(asyncResp);
2017 
2018         // ManagerDiagnosticData is added for all BMCs.
2019         nlohmann::json& managerDiagnosticData =
2020             asyncResp->res.jsonValue["ManagerDiagnosticData"];
2021         managerDiagnosticData["@odata.id"] =
2022             "/redfish/v1/Managers/bmc/ManagerDiagnosticData";
2023 
2024 #ifdef BMCWEB_ENABLE_REDFISH_OEM_MANAGER_FAN_DATA
2025         auto pids = std::make_shared<GetPIDValues>(asyncResp);
2026         pids->run();
2027 #endif
2028 
2029         getMainChassisId(asyncResp,
2030                          [](const std::string& chassisId,
2031                             const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
2032             aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1;
2033             nlohmann::json::array_t managerForChassis;
2034             nlohmann::json::object_t managerObj;
2035             boost::urls::url chassiUrl = crow::utility::urlFromPieces(
2036                 "redfish", "v1", "Chassis", chassisId);
2037             managerObj["@odata.id"] = chassiUrl;
2038             managerForChassis.push_back(std::move(managerObj));
2039             aRsp->res.jsonValue["Links"]["ManagerForChassis"] =
2040                 std::move(managerForChassis);
2041             aRsp->res.jsonValue["Links"]["ManagerInChassis"]["@odata.id"] =
2042                 chassiUrl;
2043         });
2044 
2045         static bool started = false;
2046 
2047         if (!started)
2048         {
2049             sdbusplus::asio::getProperty<double>(
2050                 *crow::connections::systemBus, "org.freedesktop.systemd1",
2051                 "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager",
2052                 "Progress",
2053                 [asyncResp](const boost::system::error_code& ec,
2054                             const double& val) {
2055                 if (ec)
2056                 {
2057                     BMCWEB_LOG_ERROR << "Error while getting progress";
2058                     messages::internalError(asyncResp->res);
2059                     return;
2060                 }
2061                 if (val < 1.0)
2062                 {
2063                     asyncResp->res.jsonValue["Status"]["State"] = "Starting";
2064                     started = true;
2065                 }
2066                 });
2067         }
2068 
2069         constexpr std::array<std::string_view, 1> interfaces = {
2070             "xyz.openbmc_project.Inventory.Item.Bmc"};
2071         dbus::utility::getSubTree(
2072             "/xyz/openbmc_project/inventory", 0, interfaces,
2073             [asyncResp](
2074                 const boost::system::error_code& ec,
2075                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
2076             if (ec)
2077             {
2078                 BMCWEB_LOG_DEBUG << "D-Bus response error on GetSubTree " << ec;
2079                 return;
2080             }
2081             if (subtree.empty())
2082             {
2083                 BMCWEB_LOG_DEBUG << "Can't find bmc D-Bus object!";
2084                 return;
2085             }
2086             // Assume only 1 bmc D-Bus object
2087             // Throw an error if there is more than 1
2088             if (subtree.size() > 1)
2089             {
2090                 BMCWEB_LOG_DEBUG << "Found more than 1 bmc D-Bus object!";
2091                 messages::internalError(asyncResp->res);
2092                 return;
2093             }
2094 
2095             if (subtree[0].first.empty() || subtree[0].second.size() != 1)
2096             {
2097                 BMCWEB_LOG_DEBUG << "Error getting bmc D-Bus object!";
2098                 messages::internalError(asyncResp->res);
2099                 return;
2100             }
2101 
2102             const std::string& path = subtree[0].first;
2103             const std::string& connectionName = subtree[0].second[0].first;
2104 
2105             for (const auto& interfaceName : subtree[0].second[0].second)
2106             {
2107                 if (interfaceName ==
2108                     "xyz.openbmc_project.Inventory.Decorator.Asset")
2109                 {
2110 
2111                     sdbusplus::asio::getAllProperties(
2112                         *crow::connections::systemBus, connectionName, path,
2113                         "xyz.openbmc_project.Inventory.Decorator.Asset",
2114                         [asyncResp](const boost::system::error_code& ec2,
2115                                     const dbus::utility::DBusPropertiesMap&
2116                                         propertiesList) {
2117                         if (ec2)
2118                         {
2119                             BMCWEB_LOG_DEBUG << "Can't get bmc asset!";
2120                             return;
2121                         }
2122 
2123                         const std::string* partNumber = nullptr;
2124                         const std::string* serialNumber = nullptr;
2125                         const std::string* manufacturer = nullptr;
2126                         const std::string* model = nullptr;
2127                         const std::string* sparePartNumber = nullptr;
2128 
2129                         const bool success = sdbusplus::unpackPropertiesNoThrow(
2130                             dbus_utils::UnpackErrorPrinter(), propertiesList,
2131                             "PartNumber", partNumber, "SerialNumber",
2132                             serialNumber, "Manufacturer", manufacturer, "Model",
2133                             model, "SparePartNumber", sparePartNumber);
2134 
2135                         if (!success)
2136                         {
2137                             messages::internalError(asyncResp->res);
2138                             return;
2139                         }
2140 
2141                         if (partNumber != nullptr)
2142                         {
2143                             asyncResp->res.jsonValue["PartNumber"] =
2144                                 *partNumber;
2145                         }
2146 
2147                         if (serialNumber != nullptr)
2148                         {
2149                             asyncResp->res.jsonValue["SerialNumber"] =
2150                                 *serialNumber;
2151                         }
2152 
2153                         if (manufacturer != nullptr)
2154                         {
2155                             asyncResp->res.jsonValue["Manufacturer"] =
2156                                 *manufacturer;
2157                         }
2158 
2159                         if (model != nullptr)
2160                         {
2161                             asyncResp->res.jsonValue["Model"] = *model;
2162                         }
2163 
2164                         if (sparePartNumber != nullptr)
2165                         {
2166                             asyncResp->res.jsonValue["SparePartNumber"] =
2167                                 *sparePartNumber;
2168                         }
2169                         });
2170                 }
2171                 else if (interfaceName ==
2172                          "xyz.openbmc_project.Inventory.Decorator.LocationCode")
2173                 {
2174                     getLocation(asyncResp, connectionName, path);
2175                 }
2176             }
2177             });
2178         });
2179 
2180     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/")
2181         .privileges(redfish::privileges::patchManager)
2182         .methods(boost::beast::http::verb::patch)(
2183             [&app](const crow::Request& req,
2184                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2185         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2186         {
2187             return;
2188         }
2189         std::optional<nlohmann::json> oem;
2190         std::optional<nlohmann::json> links;
2191         std::optional<std::string> datetime;
2192 
2193         if (!json_util::readJsonPatch(req, asyncResp->res, "Oem", oem,
2194                                       "DateTime", datetime, "Links", links))
2195         {
2196             return;
2197         }
2198 
2199         if (oem)
2200         {
2201 #ifdef BMCWEB_ENABLE_REDFISH_OEM_MANAGER_FAN_DATA
2202             std::optional<nlohmann::json> openbmc;
2203             if (!redfish::json_util::readJson(*oem, asyncResp->res, "OpenBmc",
2204                                               openbmc))
2205             {
2206                 return;
2207             }
2208             if (openbmc)
2209             {
2210                 std::optional<nlohmann::json> fan;
2211                 if (!redfish::json_util::readJson(*openbmc, asyncResp->res,
2212                                                   "Fan", fan))
2213                 {
2214                     return;
2215                 }
2216                 if (fan)
2217                 {
2218                     auto pid = std::make_shared<SetPIDValues>(asyncResp, *fan);
2219                     pid->run();
2220                 }
2221             }
2222 #else
2223             messages::propertyUnknown(asyncResp->res, "Oem");
2224             return;
2225 #endif
2226         }
2227         if (links)
2228         {
2229             std::optional<nlohmann::json> activeSoftwareImage;
2230             if (!redfish::json_util::readJson(*links, asyncResp->res,
2231                                               "ActiveSoftwareImage",
2232                                               activeSoftwareImage))
2233             {
2234                 return;
2235             }
2236             if (activeSoftwareImage)
2237             {
2238                 std::optional<std::string> odataId;
2239                 if (!json_util::readJson(*activeSoftwareImage, asyncResp->res,
2240                                          "@odata.id", odataId))
2241                 {
2242                     return;
2243                 }
2244 
2245                 if (odataId)
2246                 {
2247                     setActiveFirmwareImage(asyncResp, *odataId);
2248                 }
2249             }
2250         }
2251         if (datetime)
2252         {
2253             setDateTime(asyncResp, std::move(*datetime));
2254         }
2255         });
2256 }
2257 
2258 inline void requestRoutesManagerCollection(App& app)
2259 {
2260     BMCWEB_ROUTE(app, "/redfish/v1/Managers/")
2261         .privileges(redfish::privileges::getManagerCollection)
2262         .methods(boost::beast::http::verb::get)(
2263             [&app](const crow::Request& req,
2264                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2265         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2266         {
2267             return;
2268         }
2269         // Collections don't include the static data added by SubRoute
2270         // because it has a duplicate entry for members
2271         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
2272         asyncResp->res.jsonValue["@odata.type"] =
2273             "#ManagerCollection.ManagerCollection";
2274         asyncResp->res.jsonValue["Name"] = "Manager Collection";
2275         asyncResp->res.jsonValue["Members@odata.count"] = 1;
2276         nlohmann::json::array_t members;
2277         nlohmann::json& bmc = members.emplace_back();
2278         bmc["@odata.id"] = "/redfish/v1/Managers/bmc";
2279         asyncResp->res.jsonValue["Members"] = std::move(members);
2280         });
2281 }
2282 } // namespace redfish
2283