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