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