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