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