xref: /openbmc/bmcweb/features/redfish/lib/managers.hpp (revision 3e40fc742265c3ec1384e7e5994e62aed356331f)
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 "health.hpp"
19 #include "node.hpp"
20 #include "redfish_util.hpp"
21 
22 #include <boost/algorithm/string/replace.hpp>
23 #include <boost/date_time.hpp>
24 #include <dbus_utility.hpp>
25 #include <memory>
26 #include <sstream>
27 #include <utils/fw_utils.hpp>
28 #include <utils/systemd_utils.hpp>
29 #include <variant>
30 
31 namespace redfish
32 {
33 
34 /**
35  * Function reboots the BMC.
36  *
37  * @param[in] asyncResp - Shared pointer for completing asynchronous calls
38  */
39 void doBMCGracefulRestart(std::shared_ptr<AsyncResp> asyncResp)
40 {
41     const char* processName = "xyz.openbmc_project.State.BMC";
42     const char* objectPath = "/xyz/openbmc_project/state/bmc0";
43     const char* interfaceName = "xyz.openbmc_project.State.BMC";
44     const std::string& propertyValue =
45         "xyz.openbmc_project.State.BMC.Transition.Reboot";
46     const char* destProperty = "RequestedBMCTransition";
47 
48     // Create the D-Bus variant for D-Bus call.
49     VariantType dbusPropertyValue(propertyValue);
50 
51     crow::connections::systemBus->async_method_call(
52         [asyncResp](const boost::system::error_code ec) {
53             // Use "Set" method to set the property value.
54             if (ec)
55             {
56                 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec;
57                 messages::internalError(asyncResp->res);
58                 return;
59             }
60 
61             messages::success(asyncResp->res);
62         },
63         processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
64         interfaceName, destProperty, dbusPropertyValue);
65 }
66 
67 /**
68  * ManagerResetAction class supports the POST method for the Reset (reboot)
69  * action.
70  */
71 class ManagerResetAction : public Node
72 {
73   public:
74     ManagerResetAction(CrowApp& app) :
75         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
76     {
77         entityPrivileges = {
78             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
79     }
80 
81   private:
82     /**
83      * Function handles POST method request.
84      * Analyzes POST body before sending Reset (Reboot) request data to D-Bus.
85      * OpenBMC only supports ResetType "GracefulRestart".
86      */
87     void doPost(crow::Response& res, const crow::Request& req,
88                 const std::vector<std::string>& params) override
89     {
90         BMCWEB_LOG_DEBUG << "Post Manager Reset.";
91 
92         std::string resetType;
93         auto asyncResp = std::make_shared<AsyncResp>(res);
94 
95         if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType))
96         {
97             return;
98         }
99 
100         if (resetType != "GracefulRestart")
101         {
102             BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: "
103                              << resetType;
104             messages::actionParameterNotSupported(asyncResp->res, resetType,
105                                                   "ResetType");
106 
107             return;
108         }
109         doBMCGracefulRestart(asyncResp);
110     }
111 };
112 
113 /**
114  * ManagerResetToDefaultsAction class supports POST method for factory reset
115  * action.
116  */
117 class ManagerResetToDefaultsAction : public Node
118 {
119   public:
120     ManagerResetToDefaultsAction(CrowApp& app) :
121         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults/")
122     {
123         entityPrivileges = {
124             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
125     }
126 
127   private:
128     /**
129      * Function handles ResetToDefaults POST method request.
130      *
131      * Analyzes POST body message and factory resets BMC by calling
132      * BMC code updater factory reset followed by a BMC reboot.
133      *
134      * BMC code updater factory reset wipes the whole BMC read-write
135      * filesystem which includes things like the network settings.
136      *
137      * OpenBMC only supports ResetToDefaultsType "ResetAll".
138      */
139     void doPost(crow::Response& res, const crow::Request& req,
140                 const std::vector<std::string>& params) override
141     {
142         BMCWEB_LOG_DEBUG << "Post ResetToDefaults.";
143 
144         std::string resetType;
145         auto asyncResp = std::make_shared<AsyncResp>(res);
146 
147         if (!json_util::readJson(req, asyncResp->res, "ResetToDefaultsType",
148                                  resetType))
149         {
150             BMCWEB_LOG_DEBUG << "Missing property ResetToDefaultsType.";
151 
152             messages::actionParameterMissing(asyncResp->res, "ResetToDefaults",
153                                              "ResetToDefaultsType");
154             return;
155         }
156 
157         if (resetType != "ResetAll")
158         {
159             BMCWEB_LOG_DEBUG << "Invalid property value for "
160                                 "ResetToDefaultsType: "
161                              << resetType;
162             messages::actionParameterNotSupported(asyncResp->res, resetType,
163                                                   "ResetToDefaultsType");
164             return;
165         }
166 
167         crow::connections::systemBus->async_method_call(
168             [asyncResp](const boost::system::error_code ec) {
169                 if (ec)
170                 {
171                     BMCWEB_LOG_DEBUG << "Failed to ResetToDefaults: " << ec;
172                     messages::internalError(asyncResp->res);
173                     return;
174                 }
175                 // Factory Reset doesn't actually happen until a reboot
176                 // Can't erase what the BMC is running on
177                 doBMCGracefulRestart(asyncResp);
178             },
179             "xyz.openbmc_project.Software.BMC.Updater",
180             "/xyz/openbmc_project/software",
181             "xyz.openbmc_project.Common.FactoryReset", "Reset");
182     }
183 };
184 
185 static constexpr const char* objectManagerIface =
186     "org.freedesktop.DBus.ObjectManager";
187 static constexpr const char* pidConfigurationIface =
188     "xyz.openbmc_project.Configuration.Pid";
189 static constexpr const char* pidZoneConfigurationIface =
190     "xyz.openbmc_project.Configuration.Pid.Zone";
191 static constexpr const char* stepwiseConfigurationIface =
192     "xyz.openbmc_project.Configuration.Stepwise";
193 static constexpr const char* thermalModeIface =
194     "xyz.openbmc_project.Control.ThermalMode";
195 
196 static void asyncPopulatePid(const std::string& connection,
197                              const std::string& path,
198                              const std::string& currentProfile,
199                              const std::vector<std::string>& supportedProfiles,
200                              std::shared_ptr<AsyncResp> asyncResp)
201 {
202 
203     crow::connections::systemBus->async_method_call(
204         [asyncResp, currentProfile, supportedProfiles](
205             const boost::system::error_code ec,
206             const dbus::utility::ManagedObjectType& managedObj) {
207             if (ec)
208             {
209                 BMCWEB_LOG_ERROR << ec;
210                 asyncResp->res.jsonValue.clear();
211                 messages::internalError(asyncResp->res);
212                 return;
213             }
214             nlohmann::json& configRoot =
215                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
216             nlohmann::json& fans = configRoot["FanControllers"];
217             fans["@odata.type"] = "#OemManager.FanControllers";
218             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
219                                 "Fan/FanControllers";
220 
221             nlohmann::json& pids = configRoot["PidControllers"];
222             pids["@odata.type"] = "#OemManager.PidControllers";
223             pids["@odata.id"] =
224                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
225 
226             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
227             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
228             stepwise["@odata.id"] =
229                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
230 
231             nlohmann::json& zones = configRoot["FanZones"];
232             zones["@odata.id"] =
233                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
234             zones["@odata.type"] = "#OemManager.FanZones";
235             configRoot["@odata.id"] =
236                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
237             configRoot["@odata.type"] = "#OemManager.Fan";
238             configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
239 
240             if (!currentProfile.empty())
241             {
242                 configRoot["Profile"] = currentProfile;
243             }
244             BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !";
245 
246             for (const auto& pathPair : managedObj)
247             {
248                 for (const auto& intfPair : pathPair.second)
249                 {
250                     if (intfPair.first != pidConfigurationIface &&
251                         intfPair.first != pidZoneConfigurationIface &&
252                         intfPair.first != stepwiseConfigurationIface)
253                     {
254                         continue;
255                     }
256                     auto findName = intfPair.second.find("Name");
257                     if (findName == intfPair.second.end())
258                     {
259                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
260                         messages::internalError(asyncResp->res);
261                         return;
262                     }
263 
264                     const std::string* namePtr =
265                         std::get_if<std::string>(&findName->second);
266                     if (namePtr == nullptr)
267                     {
268                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
269                         messages::internalError(asyncResp->res);
270                         return;
271                     }
272                     std::string name = *namePtr;
273                     dbus::utility::escapePathForDbus(name);
274 
275                     auto findProfiles = intfPair.second.find("Profiles");
276                     if (findProfiles != intfPair.second.end())
277                     {
278                         const std::vector<std::string>* profiles =
279                             std::get_if<std::vector<std::string>>(
280                                 &findProfiles->second);
281                         if (profiles == nullptr)
282                         {
283                             BMCWEB_LOG_ERROR << "Pid Profiles Field illegal";
284                             messages::internalError(asyncResp->res);
285                             return;
286                         }
287                         if (std::find(profiles->begin(), profiles->end(),
288                                       currentProfile) == profiles->end())
289                         {
290                             BMCWEB_LOG_INFO
291                                 << name << " not supported in current profile";
292                             continue;
293                         }
294                     }
295                     nlohmann::json* config = nullptr;
296 
297                     const std::string* classPtr = nullptr;
298                     auto findClass = intfPair.second.find("Class");
299                     if (findClass != intfPair.second.end())
300                     {
301                         classPtr = std::get_if<std::string>(&findClass->second);
302                     }
303 
304                     if (intfPair.first == pidZoneConfigurationIface)
305                     {
306                         std::string chassis;
307                         if (!dbus::utility::getNthStringFromPath(
308                                 pathPair.first.str, 5, chassis))
309                         {
310                             chassis = "#IllegalValue";
311                         }
312                         nlohmann::json& zone = zones[name];
313                         zone["Chassis"] = {
314                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
315                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
316                                             "OpenBmc/Fan/FanZones/" +
317                                             name;
318                         zone["@odata.type"] = "#OemManager.FanZone";
319                         config = &zone;
320                     }
321 
322                     else if (intfPair.first == stepwiseConfigurationIface)
323                     {
324                         if (classPtr == nullptr)
325                         {
326                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
327                             messages::internalError(asyncResp->res);
328                             return;
329                         }
330 
331                         nlohmann::json& controller = stepwise[name];
332                         config = &controller;
333 
334                         controller["@odata.id"] =
335                             "/redfish/v1/Managers/bmc#/Oem/"
336                             "OpenBmc/Fan/StepwiseControllers/" +
337                             name;
338                         controller["@odata.type"] =
339                             "#OemManager.StepwiseController";
340 
341                         controller["Direction"] = *classPtr;
342                     }
343 
344                     // pid and fans are off the same configuration
345                     else if (intfPair.first == pidConfigurationIface)
346                     {
347 
348                         if (classPtr == nullptr)
349                         {
350                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
351                             messages::internalError(asyncResp->res);
352                             return;
353                         }
354                         bool isFan = *classPtr == "fan";
355                         nlohmann::json& element =
356                             isFan ? fans[name] : pids[name];
357                         config = &element;
358                         if (isFan)
359                         {
360                             element["@odata.id"] =
361                                 "/redfish/v1/Managers/bmc#/Oem/"
362                                 "OpenBmc/Fan/FanControllers/" +
363                                 name;
364                             element["@odata.type"] =
365                                 "#OemManager.FanController";
366                         }
367                         else
368                         {
369                             element["@odata.id"] =
370                                 "/redfish/v1/Managers/bmc#/Oem/"
371                                 "OpenBmc/Fan/PidControllers/" +
372                                 name;
373                             element["@odata.type"] =
374                                 "#OemManager.PidController";
375                         }
376                     }
377                     else
378                     {
379                         BMCWEB_LOG_ERROR << "Unexpected configuration";
380                         messages::internalError(asyncResp->res);
381                         return;
382                     }
383 
384                     // used for making maps out of 2 vectors
385                     const std::vector<double>* keys = nullptr;
386                     const std::vector<double>* values = nullptr;
387 
388                     for (const auto& propertyPair : intfPair.second)
389                     {
390                         if (propertyPair.first == "Type" ||
391                             propertyPair.first == "Class" ||
392                             propertyPair.first == "Name")
393                         {
394                             continue;
395                         }
396 
397                         // zones
398                         if (intfPair.first == pidZoneConfigurationIface)
399                         {
400                             const double* ptr =
401                                 std::get_if<double>(&propertyPair.second);
402                             if (ptr == nullptr)
403                             {
404                                 BMCWEB_LOG_ERROR << "Field Illegal "
405                                                  << propertyPair.first;
406                                 messages::internalError(asyncResp->res);
407                                 return;
408                             }
409                             (*config)[propertyPair.first] = *ptr;
410                         }
411 
412                         if (intfPair.first == stepwiseConfigurationIface)
413                         {
414                             if (propertyPair.first == "Reading" ||
415                                 propertyPair.first == "Output")
416                             {
417                                 const std::vector<double>* ptr =
418                                     std::get_if<std::vector<double>>(
419                                         &propertyPair.second);
420 
421                                 if (ptr == nullptr)
422                                 {
423                                     BMCWEB_LOG_ERROR << "Field Illegal "
424                                                      << propertyPair.first;
425                                     messages::internalError(asyncResp->res);
426                                     return;
427                                 }
428 
429                                 if (propertyPair.first == "Reading")
430                                 {
431                                     keys = ptr;
432                                 }
433                                 else
434                                 {
435                                     values = ptr;
436                                 }
437                                 if (keys && values)
438                                 {
439                                     if (keys->size() != values->size())
440                                     {
441                                         BMCWEB_LOG_ERROR
442                                             << "Reading and Output size don't "
443                                                "match ";
444                                         messages::internalError(asyncResp->res);
445                                         return;
446                                     }
447                                     nlohmann::json& steps = (*config)["Steps"];
448                                     steps = nlohmann::json::array();
449                                     for (size_t ii = 0; ii < keys->size(); ii++)
450                                     {
451                                         steps.push_back(
452                                             {{"Target", (*keys)[ii]},
453                                              {"Output", (*values)[ii]}});
454                                     }
455                                 }
456                             }
457                             if (propertyPair.first == "NegativeHysteresis" ||
458                                 propertyPair.first == "PositiveHysteresis")
459                             {
460                                 const double* ptr =
461                                     std::get_if<double>(&propertyPair.second);
462                                 if (ptr == nullptr)
463                                 {
464                                     BMCWEB_LOG_ERROR << "Field Illegal "
465                                                      << propertyPair.first;
466                                     messages::internalError(asyncResp->res);
467                                     return;
468                                 }
469                                 (*config)[propertyPair.first] = *ptr;
470                             }
471                         }
472 
473                         // pid and fans are off the same configuration
474                         if (intfPair.first == pidConfigurationIface ||
475                             intfPair.first == stepwiseConfigurationIface)
476                         {
477 
478                             if (propertyPair.first == "Zones")
479                             {
480                                 const std::vector<std::string>* inputs =
481                                     std::get_if<std::vector<std::string>>(
482                                         &propertyPair.second);
483 
484                                 if (inputs == nullptr)
485                                 {
486                                     BMCWEB_LOG_ERROR
487                                         << "Zones Pid Field Illegal";
488                                     messages::internalError(asyncResp->res);
489                                     return;
490                                 }
491                                 auto& data = (*config)[propertyPair.first];
492                                 data = nlohmann::json::array();
493                                 for (std::string itemCopy : *inputs)
494                                 {
495                                     dbus::utility::escapePathForDbus(itemCopy);
496                                     data.push_back(
497                                         {{"@odata.id",
498                                           "/redfish/v1/Managers/bmc#/Oem/"
499                                           "OpenBmc/Fan/FanZones/" +
500                                               itemCopy}});
501                                 }
502                             }
503                             // todo(james): may never happen, but this
504                             // assumes configuration data referenced in the
505                             // PID config is provided by the same daemon, we
506                             // could add another loop to cover all cases,
507                             // but I'm okay kicking this can down the road a
508                             // bit
509 
510                             else if (propertyPair.first == "Inputs" ||
511                                      propertyPair.first == "Outputs")
512                             {
513                                 auto& data = (*config)[propertyPair.first];
514                                 const std::vector<std::string>* inputs =
515                                     std::get_if<std::vector<std::string>>(
516                                         &propertyPair.second);
517 
518                                 if (inputs == nullptr)
519                                 {
520                                     BMCWEB_LOG_ERROR << "Field Illegal "
521                                                      << propertyPair.first;
522                                     messages::internalError(asyncResp->res);
523                                     return;
524                                 }
525                                 data = *inputs;
526                             }
527                             else if (propertyPair.first == "SetPointOffset")
528                             {
529                                 const std::string* ptr =
530                                     std::get_if<std::string>(
531                                         &propertyPair.second);
532 
533                                 if (ptr == nullptr)
534                                 {
535                                     BMCWEB_LOG_ERROR << "Field Illegal "
536                                                      << propertyPair.first;
537                                     messages::internalError(asyncResp->res);
538                                     return;
539                                 }
540                                 // translate from dbus to redfish
541                                 if (*ptr == "WarningHigh")
542                                 {
543                                     (*config)["SetPointOffset"] =
544                                         "UpperThresholdNonCritical";
545                                 }
546                                 else if (*ptr == "WarningLow")
547                                 {
548                                     (*config)["SetPointOffset"] =
549                                         "LowerThresholdNonCritical";
550                                 }
551                                 else if (*ptr == "CriticalHigh")
552                                 {
553                                     (*config)["SetPointOffset"] =
554                                         "UpperThresholdCritical";
555                                 }
556                                 else if (*ptr == "CriticalLow")
557                                 {
558                                     (*config)["SetPointOffset"] =
559                                         "LowerThresholdCritical";
560                                 }
561                                 else
562                                 {
563                                     BMCWEB_LOG_ERROR << "Value Illegal "
564                                                      << *ptr;
565                                     messages::internalError(asyncResp->res);
566                                     return;
567                                 }
568                             }
569                             // doubles
570                             else if (propertyPair.first ==
571                                          "FFGainCoefficient" ||
572                                      propertyPair.first == "FFOffCoefficient" ||
573                                      propertyPair.first == "ICoefficient" ||
574                                      propertyPair.first == "ILimitMax" ||
575                                      propertyPair.first == "ILimitMin" ||
576                                      propertyPair.first ==
577                                          "PositiveHysteresis" ||
578                                      propertyPair.first ==
579                                          "NegativeHysteresis" ||
580                                      propertyPair.first == "OutLimitMax" ||
581                                      propertyPair.first == "OutLimitMin" ||
582                                      propertyPair.first == "PCoefficient" ||
583                                      propertyPair.first == "SetPoint" ||
584                                      propertyPair.first == "SlewNeg" ||
585                                      propertyPair.first == "SlewPos")
586                             {
587                                 const double* ptr =
588                                     std::get_if<double>(&propertyPair.second);
589                                 if (ptr == nullptr)
590                                 {
591                                     BMCWEB_LOG_ERROR << "Field Illegal "
592                                                      << propertyPair.first;
593                                     messages::internalError(asyncResp->res);
594                                     return;
595                                 }
596                                 (*config)[propertyPair.first] = *ptr;
597                             }
598                         }
599                     }
600                 }
601             }
602         },
603         connection, path, objectManagerIface, "GetManagedObjects");
604 }
605 
606 enum class CreatePIDRet
607 {
608     fail,
609     del,
610     patch
611 };
612 
613 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response,
614                                 std::vector<nlohmann::json>& config,
615                                 std::vector<std::string>& zones)
616 {
617     if (config.empty())
618     {
619         BMCWEB_LOG_ERROR << "Empty Zones";
620         messages::propertyValueFormatError(response->res,
621                                            nlohmann::json::array(), "Zones");
622         return false;
623     }
624     for (auto& odata : config)
625     {
626         std::string path;
627         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
628                                           path))
629         {
630             return false;
631         }
632         std::string input;
633 
634         // 8 below comes from
635         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
636         //     0    1     2      3    4    5      6     7      8
637         if (!dbus::utility::getNthStringFromPath(path, 8, input))
638         {
639             BMCWEB_LOG_ERROR << "Got invalid path " << path;
640             BMCWEB_LOG_ERROR << "Illegal Type Zones";
641             messages::propertyValueFormatError(response->res, odata.dump(),
642                                                "Zones");
643             return false;
644         }
645         boost::replace_all(input, "_", " ");
646         zones.emplace_back(std::move(input));
647     }
648     return true;
649 }
650 
651 static const dbus::utility::ManagedItem*
652     findChassis(const dbus::utility::ManagedObjectType& managedObj,
653                 const std::string& value, std::string& chassis)
654 {
655     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
656 
657     std::string escaped = boost::replace_all_copy(value, " ", "_");
658     escaped = "/" + escaped;
659     auto it = std::find_if(
660         managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) {
661             if (boost::algorithm::ends_with(obj.first.str, escaped))
662             {
663                 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n";
664                 return true;
665             }
666             return false;
667         });
668 
669     if (it == managedObj.end())
670     {
671         return nullptr;
672     }
673     // 5 comes from <chassis-name> being the 5th element
674     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
675     if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
676     {
677         return &(*it);
678     }
679 
680     return nullptr;
681 }
682 
683 static CreatePIDRet createPidInterface(
684     const std::shared_ptr<AsyncResp>& response, const std::string& type,
685     nlohmann::json::iterator it, const std::string& path,
686     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
687     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
688         output,
689     std::string& chassis, const std::string& profile)
690 {
691 
692     // common deleter
693     if (it.value() == nullptr)
694     {
695         std::string iface;
696         if (type == "PidControllers" || type == "FanControllers")
697         {
698             iface = pidConfigurationIface;
699         }
700         else if (type == "FanZones")
701         {
702             iface = pidZoneConfigurationIface;
703         }
704         else if (type == "StepwiseControllers")
705         {
706             iface = stepwiseConfigurationIface;
707         }
708         else
709         {
710             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
711                              << type;
712             messages::propertyUnknown(response->res, type);
713             return CreatePIDRet::fail;
714         }
715 
716         BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n";
717         // delete interface
718         crow::connections::systemBus->async_method_call(
719             [response, path](const boost::system::error_code ec) {
720                 if (ec)
721                 {
722                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
723                     messages::internalError(response->res);
724                     return;
725                 }
726                 messages::success(response->res);
727             },
728             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
729         return CreatePIDRet::del;
730     }
731 
732     const dbus::utility::ManagedItem* managedItem = nullptr;
733     if (!createNewObject)
734     {
735         // if we aren't creating a new object, we should be able to find it on
736         // d-bus
737         managedItem = findChassis(managedObj, it.key(), chassis);
738         if (managedItem == nullptr)
739         {
740             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
741             messages::invalidObject(response->res, it.key());
742             return CreatePIDRet::fail;
743         }
744     }
745 
746     if (profile.size() &&
747         (type == "PidControllers" || type == "FanControllers" ||
748          type == "StepwiseControllers"))
749     {
750         if (managedItem == nullptr)
751         {
752             output["Profiles"] = std::vector<std::string>{profile};
753         }
754         else
755         {
756             std::string interface;
757             if (type == "StepwiseControllers")
758             {
759                 interface = stepwiseConfigurationIface;
760             }
761             else
762             {
763                 interface = pidConfigurationIface;
764             }
765             auto findConfig = managedItem->second.find(interface);
766             if (findConfig == managedItem->second.end())
767             {
768                 BMCWEB_LOG_ERROR
769                     << "Failed to find interface in managed object";
770                 messages::internalError(response->res);
771                 return CreatePIDRet::fail;
772             }
773             auto findProfiles = findConfig->second.find("Profiles");
774             if (findProfiles != findConfig->second.end())
775             {
776                 const std::vector<std::string>* curProfiles =
777                     std::get_if<std::vector<std::string>>(
778                         &(findProfiles->second));
779                 if (curProfiles == nullptr)
780                 {
781                     BMCWEB_LOG_ERROR << "Illegal profiles in managed object";
782                     messages::internalError(response->res);
783                     return CreatePIDRet::fail;
784                 }
785                 if (std::find(curProfiles->begin(), curProfiles->end(),
786                               profile) == curProfiles->end())
787                 {
788                     std::vector<std::string> newProfiles = *curProfiles;
789                     newProfiles.push_back(profile);
790                     output["Profiles"] = newProfiles;
791                 }
792             }
793         }
794     }
795 
796     if (type == "PidControllers" || type == "FanControllers")
797     {
798         if (createNewObject)
799         {
800             output["Class"] = type == "PidControllers" ? std::string("temp")
801                                                        : std::string("fan");
802             output["Type"] = std::string("Pid");
803         }
804 
805         std::optional<std::vector<nlohmann::json>> zones;
806         std::optional<std::vector<std::string>> inputs;
807         std::optional<std::vector<std::string>> outputs;
808         std::map<std::string, std::optional<double>> doubles;
809         std::optional<std::string> setpointOffset;
810         if (!redfish::json_util::readJson(
811                 it.value(), response->res, "Inputs", inputs, "Outputs", outputs,
812                 "Zones", zones, "FFGainCoefficient",
813                 doubles["FFGainCoefficient"], "FFOffCoefficient",
814                 doubles["FFOffCoefficient"], "ICoefficient",
815                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
816                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
817                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
818                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
819                 doubles["SetPoint"], "SetPointOffset", setpointOffset,
820                 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"],
821                 "PositiveHysteresis", doubles["PositiveHysteresis"],
822                 "NegativeHysteresis", doubles["NegativeHysteresis"]))
823         {
824             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
825                              << it.value().dump();
826             return CreatePIDRet::fail;
827         }
828         if (zones)
829         {
830             std::vector<std::string> zonesStr;
831             if (!getZonesFromJsonReq(response, *zones, zonesStr))
832             {
833                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
834                 return CreatePIDRet::fail;
835             }
836             if (chassis.empty() &&
837                 !findChassis(managedObj, zonesStr[0], chassis))
838             {
839                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
840                 messages::invalidObject(response->res, it.key());
841                 return CreatePIDRet::fail;
842             }
843 
844             output["Zones"] = std::move(zonesStr);
845         }
846         if (inputs || outputs)
847         {
848             std::array<std::optional<std::vector<std::string>>*, 2> containers =
849                 {&inputs, &outputs};
850             size_t index = 0;
851             for (const auto& containerPtr : containers)
852             {
853                 std::optional<std::vector<std::string>>& container =
854                     *containerPtr;
855                 if (!container)
856                 {
857                     index++;
858                     continue;
859                 }
860 
861                 for (std::string& value : *container)
862                 {
863                     boost::replace_all(value, "_", " ");
864                 }
865                 std::string key;
866                 if (index == 0)
867                 {
868                     key = "Inputs";
869                 }
870                 else
871                 {
872                     key = "Outputs";
873                 }
874                 output[key] = *container;
875                 index++;
876             }
877         }
878 
879         if (setpointOffset)
880         {
881             // translate between redfish and dbus names
882             if (*setpointOffset == "UpperThresholdNonCritical")
883             {
884                 output["SetPointOffset"] = std::string("WarningLow");
885             }
886             else if (*setpointOffset == "LowerThresholdNonCritical")
887             {
888                 output["SetPointOffset"] = std::string("WarningHigh");
889             }
890             else if (*setpointOffset == "LowerThresholdCritical")
891             {
892                 output["SetPointOffset"] = std::string("CriticalLow");
893             }
894             else if (*setpointOffset == "UpperThresholdCritical")
895             {
896                 output["SetPointOffset"] = std::string("CriticalHigh");
897             }
898             else
899             {
900                 BMCWEB_LOG_ERROR << "Invalid setpointoffset "
901                                  << *setpointOffset;
902                 messages::invalidObject(response->res, it.key());
903                 return CreatePIDRet::fail;
904             }
905         }
906 
907         // doubles
908         for (const auto& pairs : doubles)
909         {
910             if (!pairs.second)
911             {
912                 continue;
913             }
914             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
915             output[pairs.first] = *(pairs.second);
916         }
917     }
918 
919     else if (type == "FanZones")
920     {
921         output["Type"] = std::string("Pid.Zone");
922 
923         std::optional<nlohmann::json> chassisContainer;
924         std::optional<double> failSafePercent;
925         std::optional<double> minThermalOutput;
926         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
927                                           chassisContainer, "FailSafePercent",
928                                           failSafePercent, "MinThermalOutput",
929                                           minThermalOutput))
930         {
931             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
932                              << it.value().dump();
933             return CreatePIDRet::fail;
934         }
935 
936         if (chassisContainer)
937         {
938 
939             std::string chassisId;
940             if (!redfish::json_util::readJson(*chassisContainer, response->res,
941                                               "@odata.id", chassisId))
942             {
943                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
944                                  << chassisContainer->dump();
945                 return CreatePIDRet::fail;
946             }
947 
948             // /redfish/v1/chassis/chassis_name/
949             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
950             {
951                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
952                 messages::invalidObject(response->res, chassisId);
953                 return CreatePIDRet::fail;
954             }
955         }
956         if (minThermalOutput)
957         {
958             output["MinThermalOutput"] = *minThermalOutput;
959         }
960         if (failSafePercent)
961         {
962             output["FailSafePercent"] = *failSafePercent;
963         }
964     }
965     else if (type == "StepwiseControllers")
966     {
967         output["Type"] = std::string("Stepwise");
968 
969         std::optional<std::vector<nlohmann::json>> zones;
970         std::optional<std::vector<nlohmann::json>> steps;
971         std::optional<std::vector<std::string>> inputs;
972         std::optional<double> positiveHysteresis;
973         std::optional<double> negativeHysteresis;
974         std::optional<std::string> direction; // upper clipping curve vs lower
975         if (!redfish::json_util::readJson(
976                 it.value(), response->res, "Zones", zones, "Steps", steps,
977                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
978                 "NegativeHysteresis", negativeHysteresis, "Direction",
979                 direction))
980         {
981             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
982                              << it.value().dump();
983             return CreatePIDRet::fail;
984         }
985 
986         if (zones)
987         {
988             std::vector<std::string> zonesStrs;
989             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
990             {
991                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
992                 return CreatePIDRet::fail;
993             }
994             if (chassis.empty() &&
995                 !findChassis(managedObj, zonesStrs[0], chassis))
996             {
997                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
998                 messages::invalidObject(response->res, it.key());
999                 return CreatePIDRet::fail;
1000             }
1001             output["Zones"] = std::move(zonesStrs);
1002         }
1003         if (steps)
1004         {
1005             std::vector<double> readings;
1006             std::vector<double> outputs;
1007             for (auto& step : *steps)
1008             {
1009                 double target;
1010                 double output;
1011 
1012                 if (!redfish::json_util::readJson(step, response->res, "Target",
1013                                                   target, "Output", output))
1014                 {
1015                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1016                                      << ", Illegal Property "
1017                                      << it.value().dump();
1018                     return CreatePIDRet::fail;
1019                 }
1020                 readings.emplace_back(target);
1021                 outputs.emplace_back(output);
1022             }
1023             output["Reading"] = std::move(readings);
1024             output["Output"] = std::move(outputs);
1025         }
1026         if (inputs)
1027         {
1028             for (std::string& value : *inputs)
1029             {
1030                 boost::replace_all(value, "_", " ");
1031             }
1032             output["Inputs"] = std::move(*inputs);
1033         }
1034         if (negativeHysteresis)
1035         {
1036             output["NegativeHysteresis"] = *negativeHysteresis;
1037         }
1038         if (positiveHysteresis)
1039         {
1040             output["PositiveHysteresis"] = *positiveHysteresis;
1041         }
1042         if (direction)
1043         {
1044             constexpr const std::array<const char*, 2> allowedDirections = {
1045                 "Ceiling", "Floor"};
1046             if (std::find(allowedDirections.begin(), allowedDirections.end(),
1047                           *direction) == allowedDirections.end())
1048             {
1049                 messages::propertyValueTypeError(response->res, "Direction",
1050                                                  *direction);
1051                 return CreatePIDRet::fail;
1052             }
1053             output["Class"] = *direction;
1054         }
1055     }
1056     else
1057     {
1058         BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type;
1059         messages::propertyUnknown(response->res, type);
1060         return CreatePIDRet::fail;
1061     }
1062     return CreatePIDRet::patch;
1063 }
1064 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
1065 {
1066 
1067     GetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp) :
1068         asyncResp(asyncResp)
1069 
1070     {
1071     }
1072 
1073     void run()
1074     {
1075         std::shared_ptr<GetPIDValues> self = shared_from_this();
1076 
1077         // get all configurations
1078         crow::connections::systemBus->async_method_call(
1079             [self](const boost::system::error_code ec,
1080                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
1081                 if (ec)
1082                 {
1083                     BMCWEB_LOG_ERROR << ec;
1084                     messages::internalError(self->asyncResp->res);
1085                     return;
1086                 }
1087                 self->subtree = subtree;
1088             },
1089             "xyz.openbmc_project.ObjectMapper",
1090             "/xyz/openbmc_project/object_mapper",
1091             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1092             std::array<const char*, 4>{
1093                 pidConfigurationIface, pidZoneConfigurationIface,
1094                 objectManagerIface, stepwiseConfigurationIface});
1095 
1096         // at the same time get the selected profile
1097         crow::connections::systemBus->async_method_call(
1098             [self](const boost::system::error_code ec,
1099                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
1100                 if (ec || subtree.empty())
1101                 {
1102                     return;
1103                 }
1104                 if (subtree[0].second.size() != 1)
1105                 {
1106                     // invalid mapper response, should never happen
1107                     BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error";
1108                     messages::internalError(self->asyncResp->res);
1109                     return;
1110                 }
1111 
1112                 const std::string& path = subtree[0].first;
1113                 const std::string& owner = subtree[0].second[0].first;
1114                 crow::connections::systemBus->async_method_call(
1115                     [path, owner, self](
1116                         const boost::system::error_code ec,
1117                         const boost::container::flat_map<
1118                             std::string, std::variant<std::vector<std::string>,
1119                                                       std::string>>& resp) {
1120                         if (ec)
1121                         {
1122                             BMCWEB_LOG_ERROR << "GetPIDValues: Can't get "
1123                                                 "thermalModeIface "
1124                                              << path;
1125                             messages::internalError(self->asyncResp->res);
1126                             return;
1127                         }
1128                         const std::string* current = nullptr;
1129                         const std::vector<std::string>* supported = nullptr;
1130                         for (auto& [key, value] : resp)
1131                         {
1132                             if (key == "Current")
1133                             {
1134                                 current = std::get_if<std::string>(&value);
1135                                 if (current == nullptr)
1136                                 {
1137                                     BMCWEB_LOG_ERROR
1138                                         << "GetPIDValues: thermal mode "
1139                                            "iface invalid "
1140                                         << path;
1141                                     messages::internalError(
1142                                         self->asyncResp->res);
1143                                     return;
1144                                 }
1145                             }
1146                             if (key == "Supported")
1147                             {
1148                                 supported =
1149                                     std::get_if<std::vector<std::string>>(
1150                                         &value);
1151                                 if (supported == nullptr)
1152                                 {
1153                                     BMCWEB_LOG_ERROR
1154                                         << "GetPIDValues: thermal mode "
1155                                            "iface invalid"
1156                                         << path;
1157                                     messages::internalError(
1158                                         self->asyncResp->res);
1159                                     return;
1160                                 }
1161                             }
1162                         }
1163                         if (current == nullptr || supported == nullptr)
1164                         {
1165                             BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode "
1166                                                 "iface invalid "
1167                                              << path;
1168                             messages::internalError(self->asyncResp->res);
1169                             return;
1170                         }
1171                         self->currentProfile = *current;
1172                         self->supportedProfiles = *supported;
1173                     },
1174                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1175                     thermalModeIface);
1176             },
1177             "xyz.openbmc_project.ObjectMapper",
1178             "/xyz/openbmc_project/object_mapper",
1179             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1180             std::array<const char*, 1>{thermalModeIface});
1181     }
1182 
1183     ~GetPIDValues()
1184     {
1185         if (asyncResp->res.result() != boost::beast::http::status::ok)
1186         {
1187             return;
1188         }
1189         // create map of <connection, path to objMgr>>
1190         boost::container::flat_map<std::string, std::string> objectMgrPaths;
1191         boost::container::flat_set<std::string> calledConnections;
1192         for (const auto& pathGroup : subtree)
1193         {
1194             for (const auto& connectionGroup : pathGroup.second)
1195             {
1196                 auto findConnection =
1197                     calledConnections.find(connectionGroup.first);
1198                 if (findConnection != calledConnections.end())
1199                 {
1200                     break;
1201                 }
1202                 for (const std::string& interface : connectionGroup.second)
1203                 {
1204                     if (interface == objectManagerIface)
1205                     {
1206                         objectMgrPaths[connectionGroup.first] = pathGroup.first;
1207                     }
1208                     // this list is alphabetical, so we
1209                     // should have found the objMgr by now
1210                     if (interface == pidConfigurationIface ||
1211                         interface == pidZoneConfigurationIface ||
1212                         interface == stepwiseConfigurationIface)
1213                     {
1214                         auto findObjMgr =
1215                             objectMgrPaths.find(connectionGroup.first);
1216                         if (findObjMgr == objectMgrPaths.end())
1217                         {
1218                             BMCWEB_LOG_DEBUG << connectionGroup.first
1219                                              << "Has no Object Manager";
1220                             continue;
1221                         }
1222 
1223                         calledConnections.insert(connectionGroup.first);
1224 
1225                         asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1226                                          currentProfile, supportedProfiles,
1227                                          asyncResp);
1228                         break;
1229                     }
1230                 }
1231             }
1232         }
1233     }
1234 
1235     std::vector<std::string> supportedProfiles;
1236     std::string currentProfile;
1237     crow::openbmc_mapper::GetSubTreeType subtree;
1238     std::shared_ptr<AsyncResp> asyncResp;
1239 };
1240 
1241 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1242 {
1243 
1244     SetPIDValues(const std::shared_ptr<AsyncResp>& asyncRespIn,
1245                  nlohmann::json& data) :
1246         asyncResp(asyncRespIn)
1247     {
1248 
1249         std::optional<nlohmann::json> pidControllers;
1250         std::optional<nlohmann::json> fanControllers;
1251         std::optional<nlohmann::json> fanZones;
1252         std::optional<nlohmann::json> stepwiseControllers;
1253 
1254         if (!redfish::json_util::readJson(
1255                 data, asyncResp->res, "PidControllers", pidControllers,
1256                 "FanControllers", fanControllers, "FanZones", fanZones,
1257                 "StepwiseControllers", stepwiseControllers, "Profile", profile))
1258         {
1259             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1260                              << data.dump();
1261             return;
1262         }
1263         configuration.emplace_back("PidControllers", std::move(pidControllers));
1264         configuration.emplace_back("FanControllers", std::move(fanControllers));
1265         configuration.emplace_back("FanZones", std::move(fanZones));
1266         configuration.emplace_back("StepwiseControllers",
1267                                    std::move(stepwiseControllers));
1268     }
1269     void run()
1270     {
1271         if (asyncResp->res.result() != boost::beast::http::status::ok)
1272         {
1273             return;
1274         }
1275 
1276         std::shared_ptr<SetPIDValues> self = shared_from_this();
1277 
1278         // todo(james): might make sense to do a mapper call here if this
1279         // interface gets more traction
1280         crow::connections::systemBus->async_method_call(
1281             [self](const boost::system::error_code ec,
1282                    dbus::utility::ManagedObjectType& mObj) {
1283                 if (ec)
1284                 {
1285                     BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
1286                     messages::internalError(self->asyncResp->res);
1287                     return;
1288                 }
1289                 const std::array<const char*, 3> configurations = {
1290                     pidConfigurationIface, pidZoneConfigurationIface,
1291                     stepwiseConfigurationIface};
1292 
1293                 for (const auto& [path, object] : mObj)
1294                 {
1295                     for (const auto& [interface, _] : object)
1296                     {
1297                         if (std::find(configurations.begin(),
1298                                       configurations.end(),
1299                                       interface) != configurations.end())
1300                         {
1301                             self->objectCount++;
1302                             break;
1303                         }
1304                     }
1305                 }
1306                 self->managedObj = std::move(mObj);
1307             },
1308             "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
1309             "GetManagedObjects");
1310 
1311         // at the same time get the profile information
1312         crow::connections::systemBus->async_method_call(
1313             [self](const boost::system::error_code ec,
1314                    const crow::openbmc_mapper::GetSubTreeType& subtree) {
1315                 if (ec || subtree.empty())
1316                 {
1317                     return;
1318                 }
1319                 if (subtree[0].second.empty())
1320                 {
1321                     // invalid mapper response, should never happen
1322                     BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error";
1323                     messages::internalError(self->asyncResp->res);
1324                     return;
1325                 }
1326 
1327                 const std::string& path = subtree[0].first;
1328                 const std::string& owner = subtree[0].second[0].first;
1329                 crow::connections::systemBus->async_method_call(
1330                     [self, path, owner](
1331                         const boost::system::error_code ec,
1332                         const boost::container::flat_map<
1333                             std::string, std::variant<std::vector<std::string>,
1334                                                       std::string>>& r) {
1335                         if (ec)
1336                         {
1337                             BMCWEB_LOG_ERROR << "SetPIDValues: Can't get "
1338                                                 "thermalModeIface "
1339                                              << path;
1340                             messages::internalError(self->asyncResp->res);
1341                             return;
1342                         }
1343                         const std::string* current = nullptr;
1344                         const std::vector<std::string>* supported = nullptr;
1345                         for (auto& [key, value] : r)
1346                         {
1347                             if (key == "Current")
1348                             {
1349                                 current = std::get_if<std::string>(&value);
1350                                 if (current == nullptr)
1351                                 {
1352                                     BMCWEB_LOG_ERROR
1353                                         << "SetPIDValues: thermal mode "
1354                                            "iface invalid "
1355                                         << path;
1356                                     messages::internalError(
1357                                         self->asyncResp->res);
1358                                     return;
1359                                 }
1360                             }
1361                             if (key == "Supported")
1362                             {
1363                                 supported =
1364                                     std::get_if<std::vector<std::string>>(
1365                                         &value);
1366                                 if (supported == nullptr)
1367                                 {
1368                                     BMCWEB_LOG_ERROR
1369                                         << "SetPIDValues: thermal mode "
1370                                            "iface invalid"
1371                                         << path;
1372                                     messages::internalError(
1373                                         self->asyncResp->res);
1374                                     return;
1375                                 }
1376                             }
1377                         }
1378                         if (current == nullptr || supported == nullptr)
1379                         {
1380                             BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode "
1381                                                 "iface invalid "
1382                                              << path;
1383                             messages::internalError(self->asyncResp->res);
1384                             return;
1385                         }
1386                         self->currentProfile = *current;
1387                         self->supportedProfiles = *supported;
1388                         self->profileConnection = owner;
1389                         self->profilePath = path;
1390                     },
1391                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1392                     thermalModeIface);
1393             },
1394             "xyz.openbmc_project.ObjectMapper",
1395             "/xyz/openbmc_project/object_mapper",
1396             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
1397             std::array<const char*, 1>{thermalModeIface});
1398     }
1399     ~SetPIDValues()
1400     {
1401         if (asyncResp->res.result() != boost::beast::http::status::ok)
1402         {
1403             return;
1404         }
1405 
1406         std::shared_ptr<AsyncResp> response = asyncResp;
1407 
1408         if (profile)
1409         {
1410             if (std::find(supportedProfiles.begin(), supportedProfiles.end(),
1411                           *profile) == supportedProfiles.end())
1412             {
1413                 messages::actionParameterUnknown(response->res, "Profile",
1414                                                  *profile);
1415                 return;
1416             }
1417             currentProfile = *profile;
1418             crow::connections::systemBus->async_method_call(
1419                 [response](const boost::system::error_code ec) {
1420                     if (ec)
1421                     {
1422                         BMCWEB_LOG_ERROR << "Error patching profile" << ec;
1423                         messages::internalError(response->res);
1424                     }
1425                 },
1426                 profileConnection, profilePath,
1427                 "org.freedesktop.DBus.Properties", "Set", thermalModeIface,
1428                 "Current", std::variant<std::string>(*profile));
1429         }
1430 
1431         for (auto& containerPair : configuration)
1432         {
1433             auto& container = containerPair.second;
1434             if (!container)
1435             {
1436                 continue;
1437             }
1438             BMCWEB_LOG_DEBUG << *container;
1439 
1440             std::string& type = containerPair.first;
1441 
1442             for (nlohmann::json::iterator it = container->begin();
1443                  it != container->end(); it++)
1444             {
1445                 const auto& name = it.key();
1446                 BMCWEB_LOG_DEBUG << "looking for " << name;
1447 
1448                 auto pathItr =
1449                     std::find_if(managedObj.begin(), managedObj.end(),
1450                                  [&name](const auto& obj) {
1451                                      return boost::algorithm::ends_with(
1452                                          obj.first.str, "/" + name);
1453                                  });
1454                 boost::container::flat_map<std::string,
1455                                            dbus::utility::DbusVariantType>
1456                     output;
1457 
1458                 output.reserve(16); // The pid interface length
1459 
1460                 // determines if we're patching entity-manager or
1461                 // creating a new object
1462                 bool createNewObject = (pathItr == managedObj.end());
1463                 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject;
1464 
1465                 std::string iface;
1466                 if (type == "PidControllers" || type == "FanControllers")
1467                 {
1468                     iface = pidConfigurationIface;
1469                     if (!createNewObject &&
1470                         pathItr->second.find(pidConfigurationIface) ==
1471                             pathItr->second.end())
1472                     {
1473                         createNewObject = true;
1474                     }
1475                 }
1476                 else if (type == "FanZones")
1477                 {
1478                     iface = pidZoneConfigurationIface;
1479                     if (!createNewObject &&
1480                         pathItr->second.find(pidZoneConfigurationIface) ==
1481                             pathItr->second.end())
1482                     {
1483 
1484                         createNewObject = true;
1485                     }
1486                 }
1487                 else if (type == "StepwiseControllers")
1488                 {
1489                     iface = stepwiseConfigurationIface;
1490                     if (!createNewObject &&
1491                         pathItr->second.find(stepwiseConfigurationIface) ==
1492                             pathItr->second.end())
1493                     {
1494                         createNewObject = true;
1495                     }
1496                 }
1497 
1498                 if (createNewObject && it.value() == nullptr)
1499                 {
1500                     // can't delete a non-existant object
1501                     messages::invalidObject(response->res, name);
1502                     continue;
1503                 }
1504 
1505                 std::string path;
1506                 if (pathItr != managedObj.end())
1507                 {
1508                     path = pathItr->first.str;
1509                 }
1510 
1511                 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n";
1512 
1513                 // arbitrary limit to avoid attacks
1514                 constexpr const size_t controllerLimit = 500;
1515                 if (createNewObject && objectCount >= controllerLimit)
1516                 {
1517                     messages::resourceExhaustion(response->res, type);
1518                     continue;
1519                 }
1520 
1521                 output["Name"] = boost::replace_all_copy(name, "_", " ");
1522 
1523                 std::string chassis;
1524                 CreatePIDRet ret = createPidInterface(
1525                     response, type, it, path, managedObj, createNewObject,
1526                     output, chassis, currentProfile);
1527                 if (ret == CreatePIDRet::fail)
1528                 {
1529                     return;
1530                 }
1531                 else if (ret == CreatePIDRet::del)
1532                 {
1533                     continue;
1534                 }
1535 
1536                 if (!createNewObject)
1537                 {
1538                     for (const auto& property : output)
1539                     {
1540                         crow::connections::systemBus->async_method_call(
1541                             [response,
1542                              propertyName{std::string(property.first)}](
1543                                 const boost::system::error_code ec) {
1544                                 if (ec)
1545                                 {
1546                                     BMCWEB_LOG_ERROR << "Error patching "
1547                                                      << propertyName << ": "
1548                                                      << ec;
1549                                     messages::internalError(response->res);
1550                                     return;
1551                                 }
1552                                 messages::success(response->res);
1553                             },
1554                             "xyz.openbmc_project.EntityManager", path,
1555                             "org.freedesktop.DBus.Properties", "Set", iface,
1556                             property.first, property.second);
1557                     }
1558                 }
1559                 else
1560                 {
1561                     if (chassis.empty())
1562                     {
1563                         BMCWEB_LOG_ERROR << "Failed to get chassis from config";
1564                         messages::invalidObject(response->res, name);
1565                         return;
1566                     }
1567 
1568                     bool foundChassis = false;
1569                     for (const auto& obj : managedObj)
1570                     {
1571                         if (boost::algorithm::ends_with(obj.first.str, chassis))
1572                         {
1573                             chassis = obj.first.str;
1574                             foundChassis = true;
1575                             break;
1576                         }
1577                     }
1578                     if (!foundChassis)
1579                     {
1580                         BMCWEB_LOG_ERROR << "Failed to find chassis on dbus";
1581                         messages::resourceMissingAtURI(
1582                             response->res, "/redfish/v1/Chassis/" + chassis);
1583                         return;
1584                     }
1585 
1586                     crow::connections::systemBus->async_method_call(
1587                         [response](const boost::system::error_code ec) {
1588                             if (ec)
1589                             {
1590                                 BMCWEB_LOG_ERROR << "Error Adding Pid Object "
1591                                                  << ec;
1592                                 messages::internalError(response->res);
1593                                 return;
1594                             }
1595                             messages::success(response->res);
1596                         },
1597                         "xyz.openbmc_project.EntityManager", chassis,
1598                         "xyz.openbmc_project.AddObject", "AddObject", output);
1599                 }
1600             }
1601         }
1602     }
1603     std::shared_ptr<AsyncResp> asyncResp;
1604     std::vector<std::pair<std::string, std::optional<nlohmann::json>>>
1605         configuration;
1606     std::optional<std::string> profile;
1607     dbus::utility::ManagedObjectType managedObj;
1608     std::vector<std::string> supportedProfiles;
1609     std::string currentProfile;
1610     std::string profileConnection;
1611     std::string profilePath;
1612     size_t objectCount = 0;
1613 };
1614 
1615 class Manager : public Node
1616 {
1617   public:
1618     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
1619     {
1620         uuid = app.template getMiddleware<crow::persistent_data::Middleware>()
1621                    .systemUuid;
1622         entityPrivileges = {
1623             {boost::beast::http::verb::get, {{"Login"}}},
1624             {boost::beast::http::verb::head, {{"Login"}}},
1625             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1626             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1627             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1628             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1629     }
1630 
1631   private:
1632     void doGet(crow::Response& res, const crow::Request& req,
1633                const std::vector<std::string>& params) override
1634     {
1635         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc";
1636         res.jsonValue["@odata.type"] = "#Manager.v1_8_0.Manager";
1637         res.jsonValue["Id"] = "bmc";
1638         res.jsonValue["Name"] = "OpenBmc Manager";
1639         res.jsonValue["Description"] = "Baseboard Management Controller";
1640         res.jsonValue["PowerState"] = "On";
1641         res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}};
1642         res.jsonValue["ManagerType"] = "BMC";
1643         res.jsonValue["UUID"] = systemd_utils::getUuid();
1644         res.jsonValue["ServiceEntryPointUUID"] = uuid;
1645         res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
1646 
1647         res.jsonValue["LogServices"] = {
1648             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}};
1649 
1650         res.jsonValue["NetworkProtocol"] = {
1651             {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}};
1652 
1653         res.jsonValue["EthernetInterfaces"] = {
1654             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
1655 
1656 #ifdef BMCWEB_ENABLE_VM_NBDPROXY
1657         res.jsonValue["VirtualMedia"] = {
1658             {"@odata.id", "/redfish/v1/Managers/bmc/VirtualMedia"}};
1659 #endif // BMCWEB_ENABLE_VM_NBDPROXY
1660 
1661         // default oem data
1662         nlohmann::json& oem = res.jsonValue["Oem"];
1663         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
1664         oem["@odata.type"] = "#OemManager.Oem";
1665         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
1666         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
1667         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
1668         oemOpenbmc["Certificates"] = {
1669             {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates"}};
1670 
1671         // Manager.Reset (an action) can be many values, OpenBMC only supports
1672         // BMC reboot.
1673         nlohmann::json& managerReset =
1674             res.jsonValue["Actions"]["#Manager.Reset"];
1675         managerReset["target"] =
1676             "/redfish/v1/Managers/bmc/Actions/Manager.Reset";
1677         managerReset["ResetType@Redfish.AllowableValues"] = {"GracefulRestart"};
1678 
1679         // ResetToDefaults (Factory Reset) has values like
1680         // PreserveNetworkAndUsers and PreserveNetwork that aren't supported
1681         // on OpenBMC
1682         nlohmann::json& resetToDefaults =
1683             res.jsonValue["Actions"]["#Manager.ResetToDefaults"];
1684         resetToDefaults["target"] =
1685             "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults";
1686         resetToDefaults["ResetType@Redfish.AllowableValues"] = {"ResetAll"};
1687 
1688         res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
1689 
1690         // Fill in SerialConsole info
1691         res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
1692         res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15;
1693         res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI",
1694                                                                    "SSH"};
1695 #ifdef BMCWEB_ENABLE_KVM
1696         // Fill in GraphicalConsole info
1697         res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true;
1698         res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] = 4;
1699         res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"};
1700 #endif // BMCWEB_ENABLE_KVM
1701 
1702         res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
1703         res.jsonValue["Links"]["ManagerForServers"] = {
1704             {{"@odata.id", "/redfish/v1/Systems/system"}}};
1705 
1706         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1707 
1708         auto health = std::make_shared<HealthPopulate>(asyncResp);
1709         health->isManagersHealth = true;
1710         health->populate();
1711 
1712         fw_util::getActiveFwVersion(asyncResp, fw_util::bmcPurpose,
1713                                     "FirmwareVersion");
1714 
1715         auto pids = std::make_shared<GetPIDValues>(asyncResp);
1716         pids->run();
1717 
1718         getMainChassisId(asyncResp, [](const std::string& chassisId,
1719                                        const std::shared_ptr<AsyncResp> aRsp) {
1720             aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1;
1721             aRsp->res.jsonValue["Links"]["ManagerForChassis"] = {
1722                 {{"@odata.id", "/redfish/v1/Chassis/" + chassisId}}};
1723             aRsp->res.jsonValue["Links"]["ManagerInChassis"] = {
1724                 {"@odata.id", "/redfish/v1/Chassis/" + chassisId}};
1725         });
1726 
1727         static bool started = false;
1728 
1729         if (!started)
1730         {
1731             crow::connections::systemBus->async_method_call(
1732                 [asyncResp](const boost::system::error_code ec,
1733                             const std::variant<double>& resp) {
1734                     if (ec)
1735                     {
1736                         BMCWEB_LOG_ERROR << "Error while getting progress";
1737                         messages::internalError(asyncResp->res);
1738                         return;
1739                     }
1740                     const double* val = std::get_if<double>(&resp);
1741                     if (val == nullptr)
1742                     {
1743                         BMCWEB_LOG_ERROR
1744                             << "Invalid response while getting progress";
1745                         messages::internalError(asyncResp->res);
1746                         return;
1747                     }
1748                     if (*val < 1.0)
1749                     {
1750                         asyncResp->res.jsonValue["Status"]["State"] =
1751                             "Starting";
1752                         started = true;
1753                     }
1754                 },
1755                 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1756                 "org.freedesktop.DBus.Properties", "Get",
1757                 "org.freedesktop.systemd1.Manager", "Progress");
1758         }
1759     }
1760 
1761     void doPatch(crow::Response& res, const crow::Request& req,
1762                  const std::vector<std::string>& params) override
1763     {
1764         std::optional<nlohmann::json> oem;
1765         std::optional<std::string> datetime;
1766         std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res);
1767 
1768         if (!json_util::readJson(req, response->res, "Oem", oem, "DateTime",
1769                                  datetime))
1770         {
1771             return;
1772         }
1773 
1774         if (oem)
1775         {
1776             std::optional<nlohmann::json> openbmc;
1777             if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc))
1778             {
1779                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
1780                                  << oem->dump();
1781                 return;
1782             }
1783             if (openbmc)
1784             {
1785                 std::optional<nlohmann::json> fan;
1786                 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan))
1787                 {
1788                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1789                                      << ", Illegal Property "
1790                                      << openbmc->dump();
1791                     return;
1792                 }
1793                 if (fan)
1794                 {
1795                     auto pid = std::make_shared<SetPIDValues>(response, *fan);
1796                     pid->run();
1797                 }
1798             }
1799         }
1800         if (datetime)
1801         {
1802             setDateTime(response, std::move(*datetime));
1803         }
1804     }
1805 
1806     void setDateTime(std::shared_ptr<AsyncResp> aResp,
1807                      std::string datetime) const
1808     {
1809         BMCWEB_LOG_DEBUG << "Set date time: " << datetime;
1810 
1811         std::stringstream stream(datetime);
1812         // Convert from ISO 8601 to boost local_time
1813         // (BMC only has time in UTC)
1814         boost::posix_time::ptime posixTime;
1815         boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
1816         // Facet gets deleted with the stringsteam
1817         auto ifc = std::make_unique<boost::local_time::local_time_input_facet>(
1818             "%Y-%m-%d %H:%M:%S%F %ZP");
1819         stream.imbue(std::locale(stream.getloc(), ifc.release()));
1820 
1821         boost::local_time::local_date_time ldt(
1822             boost::local_time::not_a_date_time);
1823 
1824         if (stream >> ldt)
1825         {
1826             posixTime = ldt.utc_time();
1827             boost::posix_time::time_duration dur = posixTime - epoch;
1828             uint64_t durMicroSecs =
1829                 static_cast<uint64_t>(dur.total_microseconds());
1830             crow::connections::systemBus->async_method_call(
1831                 [aResp{std::move(aResp)}, datetime{std::move(datetime)}](
1832                     const boost::system::error_code ec) {
1833                     if (ec)
1834                     {
1835                         BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
1836                                             "DBUS response error "
1837                                          << ec;
1838                         messages::internalError(aResp->res);
1839                         return;
1840                     }
1841                     aResp->res.jsonValue["DateTime"] = datetime;
1842                 },
1843                 "xyz.openbmc_project.Time.Manager",
1844                 "/xyz/openbmc_project/time/bmc",
1845                 "org.freedesktop.DBus.Properties", "Set",
1846                 "xyz.openbmc_project.Time.EpochTime", "Elapsed",
1847                 std::variant<uint64_t>(durMicroSecs));
1848         }
1849         else
1850         {
1851             messages::propertyValueFormatError(aResp->res, datetime,
1852                                                "DateTime");
1853             return;
1854         }
1855     }
1856 
1857     std::string uuid;
1858 };
1859 
1860 class ManagerCollection : public Node
1861 {
1862   public:
1863     ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
1864     {
1865         entityPrivileges = {
1866             {boost::beast::http::verb::get, {{"Login"}}},
1867             {boost::beast::http::verb::head, {{"Login"}}},
1868             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1869             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1870             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1871             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1872     }
1873 
1874   private:
1875     void doGet(crow::Response& res, const crow::Request& req,
1876                const std::vector<std::string>& params) override
1877     {
1878         // Collections don't include the static data added by SubRoute
1879         // because it has a duplicate entry for members
1880         res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
1881         res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection";
1882         res.jsonValue["Name"] = "Manager Collection";
1883         res.jsonValue["Members@odata.count"] = 1;
1884         res.jsonValue["Members"] = {
1885             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
1886         res.end();
1887     }
1888 };
1889 } // namespace redfish
1890