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