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