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 <utils/fw_utils.hpp>
26 #include <utils/systemd_utils.hpp>
27 
28 #include <memory>
29 #include <sstream>
30 #include <variant>
31 
32 namespace redfish
33 {
34 
35 /**
36  * Function reboots the BMC.
37  *
38  * @param[in] asyncResp - Shared pointer for completing asynchronous calls
39  */
40 void doBMCGracefulRestart(std::shared_ptr<AsyncResp> asyncResp)
41 {
42     const char* processName = "xyz.openbmc_project.State.BMC";
43     const char* objectPath = "/xyz/openbmc_project/state/bmc0";
44     const char* interfaceName = "xyz.openbmc_project.State.BMC";
45     const std::string& propertyValue =
46         "xyz.openbmc_project.State.BMC.Transition.Reboot";
47     const char* destProperty = "RequestedBMCTransition";
48 
49     // Create the D-Bus variant for D-Bus call.
50     VariantType dbusPropertyValue(propertyValue);
51 
52     crow::connections::systemBus->async_method_call(
53         [asyncResp](const boost::system::error_code ec) {
54             // Use "Set" method to set the property value.
55             if (ec)
56             {
57                 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec;
58                 messages::internalError(asyncResp->res);
59                 return;
60             }
61 
62             messages::success(asyncResp->res);
63         },
64         processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
65         interfaceName, destProperty, dbusPropertyValue);
66 }
67 
68 /**
69  * ManagerResetAction class supports the POST method for the Reset (reboot)
70  * action.
71  */
72 class ManagerResetAction : public Node
73 {
74   public:
75     ManagerResetAction(CrowApp& app) :
76         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/")
77     {
78         entityPrivileges = {
79             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
80     }
81 
82   private:
83     /**
84      * Function handles POST method request.
85      * Analyzes POST body before sending Reset (Reboot) request data to D-Bus.
86      * OpenBMC only supports ResetType "GracefulRestart".
87      */
88     void doPost(crow::Response& res, const crow::Request& req,
89                 const std::vector<std::string>& params) override
90     {
91         BMCWEB_LOG_DEBUG << "Post Manager Reset.";
92 
93         std::string resetType;
94         auto asyncResp = std::make_shared<AsyncResp>(res);
95 
96         if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType))
97         {
98             return;
99         }
100 
101         if (resetType != "GracefulRestart")
102         {
103             BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: "
104                              << resetType;
105             messages::actionParameterNotSupported(asyncResp->res, resetType,
106                                                   "ResetType");
107 
108             return;
109         }
110         doBMCGracefulRestart(asyncResp);
111     }
112 };
113 
114 /**
115  * ManagerResetToDefaultsAction class supports POST method for factory reset
116  * action.
117  */
118 class ManagerResetToDefaultsAction : public Node
119 {
120   public:
121     ManagerResetToDefaultsAction(CrowApp& app) :
122         Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults/")
123     {
124         entityPrivileges = {
125             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
126     }
127 
128   private:
129     /**
130      * Function handles ResetToDefaults POST method request.
131      *
132      * Analyzes POST body message and factory resets BMC by calling
133      * BMC code updater factory reset followed by a BMC reboot.
134      *
135      * BMC code updater factory reset wipes the whole BMC read-write
136      * filesystem which includes things like the network settings.
137      *
138      * OpenBMC only supports ResetToDefaultsType "ResetAll".
139      */
140     void doPost(crow::Response& res, const crow::Request& req,
141                 const std::vector<std::string>& params) override
142     {
143         BMCWEB_LOG_DEBUG << "Post ResetToDefaults.";
144 
145         std::string resetType;
146         auto asyncResp = std::make_shared<AsyncResp>(res);
147 
148         if (!json_util::readJson(req, asyncResp->res, "ResetToDefaultsType",
149                                  resetType))
150         {
151             BMCWEB_LOG_DEBUG << "Missing property ResetToDefaultsType.";
152 
153             messages::actionParameterMissing(asyncResp->res, "ResetToDefaults",
154                                              "ResetToDefaultsType");
155             return;
156         }
157 
158         if (resetType != "ResetAll")
159         {
160             BMCWEB_LOG_DEBUG << "Invalid property value for "
161                                 "ResetToDefaultsType: "
162                              << resetType;
163             messages::actionParameterNotSupported(asyncResp->res, resetType,
164                                                   "ResetToDefaultsType");
165             return;
166         }
167 
168         crow::connections::systemBus->async_method_call(
169             [asyncResp](const boost::system::error_code ec) {
170                 if (ec)
171                 {
172                     BMCWEB_LOG_DEBUG << "Failed to ResetToDefaults: " << ec;
173                     messages::internalError(asyncResp->res);
174                     return;
175                 }
176                 // Factory Reset doesn't actually happen until a reboot
177                 // Can't erase what the BMC is running on
178                 doBMCGracefulRestart(asyncResp);
179             },
180             "xyz.openbmc_project.Software.BMC.Updater",
181             "/xyz/openbmc_project/software",
182             "xyz.openbmc_project.Common.FactoryReset", "Reset");
183     }
184 };
185 
186 static constexpr const char* objectManagerIface =
187     "org.freedesktop.DBus.ObjectManager";
188 static constexpr const char* pidConfigurationIface =
189     "xyz.openbmc_project.Configuration.Pid";
190 static constexpr const char* pidZoneConfigurationIface =
191     "xyz.openbmc_project.Configuration.Pid.Zone";
192 static constexpr const char* stepwiseConfigurationIface =
193     "xyz.openbmc_project.Configuration.Stepwise";
194 static constexpr const char* thermalModeIface =
195     "xyz.openbmc_project.Control.ThermalMode";
196 
197 static void asyncPopulatePid(const std::string& connection,
198                              const std::string& path,
199                              const std::string& currentProfile,
200                              const std::vector<std::string>& supportedProfiles,
201                              std::shared_ptr<AsyncResp> asyncResp)
202 {
203 
204     crow::connections::systemBus->async_method_call(
205         [asyncResp, currentProfile, supportedProfiles](
206             const boost::system::error_code ec,
207             const dbus::utility::ManagedObjectType& managedObj) {
208             if (ec)
209             {
210                 BMCWEB_LOG_ERROR << ec;
211                 asyncResp->res.jsonValue.clear();
212                 messages::internalError(asyncResp->res);
213                 return;
214             }
215             nlohmann::json& configRoot =
216                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
217             nlohmann::json& fans = configRoot["FanControllers"];
218             fans["@odata.type"] = "#OemManager.FanControllers";
219             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
220                                 "Fan/FanControllers";
221 
222             nlohmann::json& pids = configRoot["PidControllers"];
223             pids["@odata.type"] = "#OemManager.PidControllers";
224             pids["@odata.id"] =
225                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
226 
227             nlohmann::json& stepwise = configRoot["StepwiseControllers"];
228             stepwise["@odata.type"] = "#OemManager.StepwiseControllers";
229             stepwise["@odata.id"] =
230                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers";
231 
232             nlohmann::json& zones = configRoot["FanZones"];
233             zones["@odata.id"] =
234                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
235             zones["@odata.type"] = "#OemManager.FanZones";
236             configRoot["@odata.id"] =
237                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
238             configRoot["@odata.type"] = "#OemManager.Fan";
239             configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
240 
241             if (!currentProfile.empty())
242             {
243                 configRoot["Profile"] = currentProfile;
244             }
245             BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !";
246 
247             for (const auto& pathPair : managedObj)
248             {
249                 for (const auto& intfPair : pathPair.second)
250                 {
251                     if (intfPair.first != pidConfigurationIface &&
252                         intfPair.first != pidZoneConfigurationIface &&
253                         intfPair.first != stepwiseConfigurationIface)
254                     {
255                         continue;
256                     }
257                     auto findName = intfPair.second.find("Name");
258                     if (findName == intfPair.second.end())
259                     {
260                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
261                         messages::internalError(asyncResp->res);
262                         return;
263                     }
264 
265                     const std::string* namePtr =
266                         std::get_if<std::string>(&findName->second);
267                     if (namePtr == nullptr)
268                     {
269                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
270                         messages::internalError(asyncResp->res);
271                         return;
272                     }
273                     std::string name = *namePtr;
274                     dbus::utility::escapePathForDbus(name);
275 
276                     auto findProfiles = intfPair.second.find("Profiles");
277                     if (findProfiles != intfPair.second.end())
278                     {
279                         const std::vector<std::string>* profiles =
280                             std::get_if<std::vector<std::string>>(
281                                 &findProfiles->second);
282                         if (profiles == nullptr)
283                         {
284                             BMCWEB_LOG_ERROR << "Pid Profiles Field illegal";
285                             messages::internalError(asyncResp->res);
286                             return;
287                         }
288                         if (std::find(profiles->begin(), profiles->end(),
289                                       currentProfile) == profiles->end())
290                         {
291                             BMCWEB_LOG_INFO
292                                 << name << " not supported in current profile";
293                             continue;
294                         }
295                     }
296                     nlohmann::json* config = nullptr;
297 
298                     const std::string* classPtr = nullptr;
299                     auto findClass = intfPair.second.find("Class");
300                     if (findClass != intfPair.second.end())
301                     {
302                         classPtr = std::get_if<std::string>(&findClass->second);
303                     }
304 
305                     if (intfPair.first == pidZoneConfigurationIface)
306                     {
307                         std::string chassis;
308                         if (!dbus::utility::getNthStringFromPath(
309                                 pathPair.first.str, 5, chassis))
310                         {
311                             chassis = "#IllegalValue";
312                         }
313                         nlohmann::json& zone = zones[name];
314                         zone["Chassis"] = {
315                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
316                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
317                                             "OpenBmc/Fan/FanZones/" +
318                                             name;
319                         zone["@odata.type"] = "#OemManager.FanZone";
320                         config = &zone;
321                     }
322 
323                     else if (intfPair.first == stepwiseConfigurationIface)
324                     {
325                         if (classPtr == nullptr)
326                         {
327                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
328                             messages::internalError(asyncResp->res);
329                             return;
330                         }
331 
332                         nlohmann::json& controller = stepwise[name];
333                         config = &controller;
334 
335                         controller["@odata.id"] =
336                             "/redfish/v1/Managers/bmc#/Oem/"
337                             "OpenBmc/Fan/StepwiseControllers/" +
338                             name;
339                         controller["@odata.type"] =
340                             "#OemManager.StepwiseController";
341 
342                         controller["Direction"] = *classPtr;
343                     }
344 
345                     // pid and fans are off the same configuration
346                     else if (intfPair.first == pidConfigurationIface)
347                     {
348 
349                         if (classPtr == nullptr)
350                         {
351                             BMCWEB_LOG_ERROR << "Pid Class Field illegal";
352                             messages::internalError(asyncResp->res);
353                             return;
354                         }
355                         bool isFan = *classPtr == "fan";
356                         nlohmann::json& element =
357                             isFan ? fans[name] : pids[name];
358                         config = &element;
359                         if (isFan)
360                         {
361                             element["@odata.id"] =
362                                 "/redfish/v1/Managers/bmc#/Oem/"
363                                 "OpenBmc/Fan/FanControllers/" +
364                                 name;
365                             element["@odata.type"] =
366                                 "#OemManager.FanController";
367                         }
368                         else
369                         {
370                             element["@odata.id"] =
371                                 "/redfish/v1/Managers/bmc#/Oem/"
372                                 "OpenBmc/Fan/PidControllers/" +
373                                 name;
374                             element["@odata.type"] =
375                                 "#OemManager.PidController";
376                         }
377                     }
378                     else
379                     {
380                         BMCWEB_LOG_ERROR << "Unexpected configuration";
381                         messages::internalError(asyncResp->res);
382                         return;
383                     }
384 
385                     // used for making maps out of 2 vectors
386                     const std::vector<double>* keys = nullptr;
387                     const std::vector<double>* values = nullptr;
388 
389                     for (const auto& propertyPair : intfPair.second)
390                     {
391                         if (propertyPair.first == "Type" ||
392                             propertyPair.first == "Class" ||
393                             propertyPair.first == "Name")
394                         {
395                             continue;
396                         }
397 
398                         // zones
399                         if (intfPair.first == pidZoneConfigurationIface)
400                         {
401                             const double* ptr =
402                                 std::get_if<double>(&propertyPair.second);
403                             if (ptr == nullptr)
404                             {
405                                 BMCWEB_LOG_ERROR << "Field Illegal "
406                                                  << propertyPair.first;
407                                 messages::internalError(asyncResp->res);
408                                 return;
409                             }
410                             (*config)[propertyPair.first] = *ptr;
411                         }
412 
413                         if (intfPair.first == stepwiseConfigurationIface)
414                         {
415                             if (propertyPair.first == "Reading" ||
416                                 propertyPair.first == "Output")
417                             {
418                                 const std::vector<double>* ptr =
419                                     std::get_if<std::vector<double>>(
420                                         &propertyPair.second);
421 
422                                 if (ptr == nullptr)
423                                 {
424                                     BMCWEB_LOG_ERROR << "Field Illegal "
425                                                      << propertyPair.first;
426                                     messages::internalError(asyncResp->res);
427                                     return;
428                                 }
429 
430                                 if (propertyPair.first == "Reading")
431                                 {
432                                     keys = ptr;
433                                 }
434                                 else
435                                 {
436                                     values = ptr;
437                                 }
438                                 if (keys && values)
439                                 {
440                                     if (keys->size() != values->size())
441                                     {
442                                         BMCWEB_LOG_ERROR
443                                             << "Reading and Output size don't "
444                                                "match ";
445                                         messages::internalError(asyncResp->res);
446                                         return;
447                                     }
448                                     nlohmann::json& steps = (*config)["Steps"];
449                                     steps = nlohmann::json::array();
450                                     for (size_t ii = 0; ii < keys->size(); ii++)
451                                     {
452                                         steps.push_back(
453                                             {{"Target", (*keys)[ii]},
454                                              {"Output", (*values)[ii]}});
455                                     }
456                                 }
457                             }
458                             if (propertyPair.first == "NegativeHysteresis" ||
459                                 propertyPair.first == "PositiveHysteresis")
460                             {
461                                 const double* ptr =
462                                     std::get_if<double>(&propertyPair.second);
463                                 if (ptr == nullptr)
464                                 {
465                                     BMCWEB_LOG_ERROR << "Field Illegal "
466                                                      << propertyPair.first;
467                                     messages::internalError(asyncResp->res);
468                                     return;
469                                 }
470                                 (*config)[propertyPair.first] = *ptr;
471                             }
472                         }
473 
474                         // pid and fans are off the same configuration
475                         if (intfPair.first == pidConfigurationIface ||
476                             intfPair.first == stepwiseConfigurationIface)
477                         {
478 
479                             if (propertyPair.first == "Zones")
480                             {
481                                 const std::vector<std::string>* inputs =
482                                     std::get_if<std::vector<std::string>>(
483                                         &propertyPair.second);
484 
485                                 if (inputs == nullptr)
486                                 {
487                                     BMCWEB_LOG_ERROR
488                                         << "Zones Pid Field Illegal";
489                                     messages::internalError(asyncResp->res);
490                                     return;
491                                 }
492                                 auto& data = (*config)[propertyPair.first];
493                                 data = nlohmann::json::array();
494                                 for (std::string itemCopy : *inputs)
495                                 {
496                                     dbus::utility::escapePathForDbus(itemCopy);
497                                     data.push_back(
498                                         {{"@odata.id",
499                                           "/redfish/v1/Managers/bmc#/Oem/"
500                                           "OpenBmc/Fan/FanZones/" +
501                                               itemCopy}});
502                                 }
503                             }
504                             // todo(james): may never happen, but this
505                             // assumes configuration data referenced in the
506                             // PID config is provided by the same daemon, we
507                             // could add another loop to cover all cases,
508                             // but I'm okay kicking this can down the road a
509                             // bit
510 
511                             else if (propertyPair.first == "Inputs" ||
512                                      propertyPair.first == "Outputs")
513                             {
514                                 auto& data = (*config)[propertyPair.first];
515                                 const std::vector<std::string>* inputs =
516                                     std::get_if<std::vector<std::string>>(
517                                         &propertyPair.second);
518 
519                                 if (inputs == nullptr)
520                                 {
521                                     BMCWEB_LOG_ERROR << "Field Illegal "
522                                                      << propertyPair.first;
523                                     messages::internalError(asyncResp->res);
524                                     return;
525                                 }
526                                 data = *inputs;
527                             }
528                             else if (propertyPair.first == "SetPointOffset")
529                             {
530                                 const std::string* ptr =
531                                     std::get_if<std::string>(
532                                         &propertyPair.second);
533 
534                                 if (ptr == nullptr)
535                                 {
536                                     BMCWEB_LOG_ERROR << "Field Illegal "
537                                                      << propertyPair.first;
538                                     messages::internalError(asyncResp->res);
539                                     return;
540                                 }
541                                 // translate from dbus to redfish
542                                 if (*ptr == "WarningHigh")
543                                 {
544                                     (*config)["SetPointOffset"] =
545                                         "UpperThresholdNonCritical";
546                                 }
547                                 else if (*ptr == "WarningLow")
548                                 {
549                                     (*config)["SetPointOffset"] =
550                                         "LowerThresholdNonCritical";
551                                 }
552                                 else if (*ptr == "CriticalHigh")
553                                 {
554                                     (*config)["SetPointOffset"] =
555                                         "UpperThresholdCritical";
556                                 }
557                                 else if (*ptr == "CriticalLow")
558                                 {
559                                     (*config)["SetPointOffset"] =
560                                         "LowerThresholdCritical";
561                                 }
562                                 else
563                                 {
564                                     BMCWEB_LOG_ERROR << "Value Illegal "
565                                                      << *ptr;
566                                     messages::internalError(asyncResp->res);
567                                     return;
568                                 }
569                             }
570                             // doubles
571                             else if (propertyPair.first ==
572                                          "FFGainCoefficient" ||
573                                      propertyPair.first == "FFOffCoefficient" ||
574                                      propertyPair.first == "ICoefficient" ||
575                                      propertyPair.first == "ILimitMax" ||
576                                      propertyPair.first == "ILimitMin" ||
577                                      propertyPair.first ==
578                                          "PositiveHysteresis" ||
579                                      propertyPair.first ==
580                                          "NegativeHysteresis" ||
581                                      propertyPair.first == "OutLimitMax" ||
582                                      propertyPair.first == "OutLimitMin" ||
583                                      propertyPair.first == "PCoefficient" ||
584                                      propertyPair.first == "SetPoint" ||
585                                      propertyPair.first == "SlewNeg" ||
586                                      propertyPair.first == "SlewPos")
587                             {
588                                 const double* ptr =
589                                     std::get_if<double>(&propertyPair.second);
590                                 if (ptr == nullptr)
591                                 {
592                                     BMCWEB_LOG_ERROR << "Field Illegal "
593                                                      << propertyPair.first;
594                                     messages::internalError(asyncResp->res);
595                                     return;
596                                 }
597                                 (*config)[propertyPair.first] = *ptr;
598                             }
599                         }
600                     }
601                 }
602             }
603         },
604         connection, path, objectManagerIface, "GetManagedObjects");
605 }
606 
607 enum class CreatePIDRet
608 {
609     fail,
610     del,
611     patch
612 };
613 
614 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response,
615                                 std::vector<nlohmann::json>& config,
616                                 std::vector<std::string>& zones)
617 {
618     if (config.empty())
619     {
620         BMCWEB_LOG_ERROR << "Empty Zones";
621         messages::propertyValueFormatError(response->res,
622                                            nlohmann::json::array(), "Zones");
623         return false;
624     }
625     for (auto& odata : config)
626     {
627         std::string path;
628         if (!redfish::json_util::readJson(odata, response->res, "@odata.id",
629                                           path))
630         {
631             return false;
632         }
633         std::string input;
634 
635         // 8 below comes from
636         // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
637         //     0    1     2      3    4    5      6     7      8
638         if (!dbus::utility::getNthStringFromPath(path, 8, input))
639         {
640             BMCWEB_LOG_ERROR << "Got invalid path " << path;
641             BMCWEB_LOG_ERROR << "Illegal Type Zones";
642             messages::propertyValueFormatError(response->res, odata.dump(),
643                                                "Zones");
644             return false;
645         }
646         boost::replace_all(input, "_", " ");
647         zones.emplace_back(std::move(input));
648     }
649     return true;
650 }
651 
652 static const dbus::utility::ManagedItem*
653     findChassis(const dbus::utility::ManagedObjectType& managedObj,
654                 const std::string& value, std::string& chassis)
655 {
656     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
657 
658     std::string escaped = boost::replace_all_copy(value, " ", "_");
659     escaped = "/" + escaped;
660     auto it = std::find_if(
661         managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) {
662             if (boost::algorithm::ends_with(obj.first.str, escaped))
663             {
664                 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n";
665                 return true;
666             }
667             return false;
668         });
669 
670     if (it == managedObj.end())
671     {
672         return nullptr;
673     }
674     // 5 comes from <chassis-name> being the 5th element
675     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
676     if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
677     {
678         return &(*it);
679     }
680 
681     return nullptr;
682 }
683 
684 static CreatePIDRet createPidInterface(
685     const std::shared_ptr<AsyncResp>& response, const std::string& type,
686     nlohmann::json::iterator it, const std::string& path,
687     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
688     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
689         output,
690     std::string& chassis, const std::string& profile)
691 {
692 
693     // common deleter
694     if (it.value() == nullptr)
695     {
696         std::string iface;
697         if (type == "PidControllers" || type == "FanControllers")
698         {
699             iface = pidConfigurationIface;
700         }
701         else if (type == "FanZones")
702         {
703             iface = pidZoneConfigurationIface;
704         }
705         else if (type == "StepwiseControllers")
706         {
707             iface = stepwiseConfigurationIface;
708         }
709         else
710         {
711             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type "
712                              << type;
713             messages::propertyUnknown(response->res, type);
714             return CreatePIDRet::fail;
715         }
716 
717         BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n";
718         // delete interface
719         crow::connections::systemBus->async_method_call(
720             [response, path](const boost::system::error_code ec) {
721                 if (ec)
722                 {
723                     BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec;
724                     messages::internalError(response->res);
725                     return;
726                 }
727                 messages::success(response->res);
728             },
729             "xyz.openbmc_project.EntityManager", path, iface, "Delete");
730         return CreatePIDRet::del;
731     }
732 
733     const dbus::utility::ManagedItem* managedItem = nullptr;
734     if (!createNewObject)
735     {
736         // if we aren't creating a new object, we should be able to find it on
737         // d-bus
738         managedItem = findChassis(managedObj, it.key(), chassis);
739         if (managedItem == nullptr)
740         {
741             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
742             messages::invalidObject(response->res, it.key());
743             return CreatePIDRet::fail;
744         }
745     }
746 
747     if (profile.size() &&
748         (type == "PidControllers" || type == "FanControllers" ||
749          type == "StepwiseControllers"))
750     {
751         if (managedItem == nullptr)
752         {
753             output["Profiles"] = std::vector<std::string>{profile};
754         }
755         else
756         {
757             std::string interface;
758             if (type == "StepwiseControllers")
759             {
760                 interface = stepwiseConfigurationIface;
761             }
762             else
763             {
764                 interface = pidConfigurationIface;
765             }
766             auto findConfig = managedItem->second.find(interface);
767             if (findConfig == managedItem->second.end())
768             {
769                 BMCWEB_LOG_ERROR
770                     << "Failed to find interface in managed object";
771                 messages::internalError(response->res);
772                 return CreatePIDRet::fail;
773             }
774             auto findProfiles = findConfig->second.find("Profiles");
775             if (findProfiles != findConfig->second.end())
776             {
777                 const std::vector<std::string>* curProfiles =
778                     std::get_if<std::vector<std::string>>(
779                         &(findProfiles->second));
780                 if (curProfiles == nullptr)
781                 {
782                     BMCWEB_LOG_ERROR << "Illegal profiles in managed object";
783                     messages::internalError(response->res);
784                     return CreatePIDRet::fail;
785                 }
786                 if (std::find(curProfiles->begin(), curProfiles->end(),
787                               profile) == curProfiles->end())
788                 {
789                     std::vector<std::string> newProfiles = *curProfiles;
790                     newProfiles.push_back(profile);
791                     output["Profiles"] = newProfiles;
792                 }
793             }
794         }
795     }
796 
797     if (type == "PidControllers" || type == "FanControllers")
798     {
799         if (createNewObject)
800         {
801             output["Class"] = type == "PidControllers" ? std::string("temp")
802                                                        : std::string("fan");
803             output["Type"] = std::string("Pid");
804         }
805 
806         std::optional<std::vector<nlohmann::json>> zones;
807         std::optional<std::vector<std::string>> inputs;
808         std::optional<std::vector<std::string>> outputs;
809         std::map<std::string, std::optional<double>> doubles;
810         std::optional<std::string> setpointOffset;
811         if (!redfish::json_util::readJson(
812                 it.value(), response->res, "Inputs", inputs, "Outputs", outputs,
813                 "Zones", zones, "FFGainCoefficient",
814                 doubles["FFGainCoefficient"], "FFOffCoefficient",
815                 doubles["FFOffCoefficient"], "ICoefficient",
816                 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
817                 "ILimitMin", doubles["ILimitMin"], "OutLimitMax",
818                 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
819                 "PCoefficient", doubles["PCoefficient"], "SetPoint",
820                 doubles["SetPoint"], "SetPointOffset", setpointOffset,
821                 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"],
822                 "PositiveHysteresis", doubles["PositiveHysteresis"],
823                 "NegativeHysteresis", doubles["NegativeHysteresis"]))
824         {
825             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
826                              << it.value().dump();
827             return CreatePIDRet::fail;
828         }
829         if (zones)
830         {
831             std::vector<std::string> zonesStr;
832             if (!getZonesFromJsonReq(response, *zones, zonesStr))
833             {
834                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
835                 return CreatePIDRet::fail;
836             }
837             if (chassis.empty() &&
838                 !findChassis(managedObj, zonesStr[0], chassis))
839             {
840                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
841                 messages::invalidObject(response->res, it.key());
842                 return CreatePIDRet::fail;
843             }
844 
845             output["Zones"] = std::move(zonesStr);
846         }
847         if (inputs || outputs)
848         {
849             std::array<std::optional<std::vector<std::string>>*, 2> containers =
850                 {&inputs, &outputs};
851             size_t index = 0;
852             for (const auto& containerPtr : containers)
853             {
854                 std::optional<std::vector<std::string>>& container =
855                     *containerPtr;
856                 if (!container)
857                 {
858                     index++;
859                     continue;
860                 }
861 
862                 for (std::string& value : *container)
863                 {
864                     boost::replace_all(value, "_", " ");
865                 }
866                 std::string key;
867                 if (index == 0)
868                 {
869                     key = "Inputs";
870                 }
871                 else
872                 {
873                     key = "Outputs";
874                 }
875                 output[key] = *container;
876                 index++;
877             }
878         }
879 
880         if (setpointOffset)
881         {
882             // translate between redfish and dbus names
883             if (*setpointOffset == "UpperThresholdNonCritical")
884             {
885                 output["SetPointOffset"] = std::string("WarningLow");
886             }
887             else if (*setpointOffset == "LowerThresholdNonCritical")
888             {
889                 output["SetPointOffset"] = std::string("WarningHigh");
890             }
891             else if (*setpointOffset == "LowerThresholdCritical")
892             {
893                 output["SetPointOffset"] = std::string("CriticalLow");
894             }
895             else if (*setpointOffset == "UpperThresholdCritical")
896             {
897                 output["SetPointOffset"] = std::string("CriticalHigh");
898             }
899             else
900             {
901                 BMCWEB_LOG_ERROR << "Invalid setpointoffset "
902                                  << *setpointOffset;
903                 messages::invalidObject(response->res, it.key());
904                 return CreatePIDRet::fail;
905             }
906         }
907 
908         // doubles
909         for (const auto& pairs : doubles)
910         {
911             if (!pairs.second)
912             {
913                 continue;
914             }
915             BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second;
916             output[pairs.first] = *(pairs.second);
917         }
918     }
919 
920     else if (type == "FanZones")
921     {
922         output["Type"] = std::string("Pid.Zone");
923 
924         std::optional<nlohmann::json> chassisContainer;
925         std::optional<double> failSafePercent;
926         std::optional<double> minThermalOutput;
927         if (!redfish::json_util::readJson(it.value(), response->res, "Chassis",
928                                           chassisContainer, "FailSafePercent",
929                                           failSafePercent, "MinThermalOutput",
930                                           minThermalOutput))
931         {
932             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
933                              << it.value().dump();
934             return CreatePIDRet::fail;
935         }
936 
937         if (chassisContainer)
938         {
939 
940             std::string chassisId;
941             if (!redfish::json_util::readJson(*chassisContainer, response->res,
942                                               "@odata.id", chassisId))
943             {
944                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
945                                  << chassisContainer->dump();
946                 return CreatePIDRet::fail;
947             }
948 
949             // /redfish/v1/chassis/chassis_name/
950             if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis))
951             {
952                 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId;
953                 messages::invalidObject(response->res, chassisId);
954                 return CreatePIDRet::fail;
955             }
956         }
957         if (minThermalOutput)
958         {
959             output["MinThermalOutput"] = *minThermalOutput;
960         }
961         if (failSafePercent)
962         {
963             output["FailSafePercent"] = *failSafePercent;
964         }
965     }
966     else if (type == "StepwiseControllers")
967     {
968         output["Type"] = std::string("Stepwise");
969 
970         std::optional<std::vector<nlohmann::json>> zones;
971         std::optional<std::vector<nlohmann::json>> steps;
972         std::optional<std::vector<std::string>> inputs;
973         std::optional<double> positiveHysteresis;
974         std::optional<double> negativeHysteresis;
975         std::optional<std::string> direction; // upper clipping curve vs lower
976         if (!redfish::json_util::readJson(
977                 it.value(), response->res, "Zones", zones, "Steps", steps,
978                 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
979                 "NegativeHysteresis", negativeHysteresis, "Direction",
980                 direction))
981         {
982             BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
983                              << it.value().dump();
984             return CreatePIDRet::fail;
985         }
986 
987         if (zones)
988         {
989             std::vector<std::string> zonesStrs;
990             if (!getZonesFromJsonReq(response, *zones, zonesStrs))
991             {
992                 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones";
993                 return CreatePIDRet::fail;
994             }
995             if (chassis.empty() &&
996                 !findChassis(managedObj, zonesStrs[0], chassis))
997             {
998                 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
999                 messages::invalidObject(response->res, it.key());
1000                 return CreatePIDRet::fail;
1001             }
1002             output["Zones"] = std::move(zonesStrs);
1003         }
1004         if (steps)
1005         {
1006             std::vector<double> readings;
1007             std::vector<double> outputs;
1008             for (auto& step : *steps)
1009             {
1010                 double target;
1011                 double output;
1012 
1013                 if (!redfish::json_util::readJson(step, response->res, "Target",
1014                                                   target, "Output", output))
1015                 {
1016                     BMCWEB_LOG_ERROR << "Line:" << __LINE__
1017                                      << ", Illegal Property "
1018                                      << it.value().dump();
1019                     return CreatePIDRet::fail;
1020                 }
1021                 readings.emplace_back(target);
1022                 outputs.emplace_back(output);
1023             }
1024             output["Reading"] = std::move(readings);
1025             output["Output"] = std::move(outputs);
1026         }
1027         if (inputs)
1028         {
1029             for (std::string& value : *inputs)
1030             {
1031                 boost::replace_all(value, "_", " ");
1032             }
1033             output["Inputs"] = std::move(*inputs);
1034         }
1035         if (negativeHysteresis)
1036         {
1037             output["NegativeHysteresis"] = *negativeHysteresis;
1038         }
1039         if (positiveHysteresis)
1040         {
1041             output["PositiveHysteresis"] = *positiveHysteresis;
1042         }
1043         if (direction)
1044         {
1045             constexpr const std::array<const char*, 2> allowedDirections = {
1046                 "Ceiling", "Floor"};
1047             if (std::find(allowedDirections.begin(), allowedDirections.end(),
1048                           *direction) == allowedDirections.end())
1049             {
1050                 messages::propertyValueTypeError(response->res, "Direction",
1051                                                  *direction);
1052                 return CreatePIDRet::fail;
1053             }
1054             output["Class"] = *direction;
1055         }
1056     }
1057     else
1058     {
1059         BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type;
1060         messages::propertyUnknown(response->res, type);
1061         return CreatePIDRet::fail;
1062     }
1063     return CreatePIDRet::patch;
1064 }
1065 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
1066 {
1067 
1068     GetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp) :
1069         asyncResp(asyncResp)
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