1 #pragma once
2
3 #include "bmcweb_config.h"
4
5 #include "async_resp.hpp"
6 #include "dbus_singleton.hpp"
7 #include "dbus_utility.hpp"
8 #include "error_messages.hpp"
9 #include "io_context_singleton.hpp"
10 #include "logging.hpp"
11 #include "redfish.hpp"
12 #include "sub_request.hpp"
13 #include "utils/dbus_utils.hpp"
14 #include "utils/json_utils.hpp"
15 #include "verb.hpp"
16
17 #include <boost/asio/post.hpp>
18 #include <boost/beast/http/status.hpp>
19 #include <boost/container/flat_map.hpp>
20 #include <boost/container/flat_set.hpp>
21 #include <boost/url/format.hpp>
22 #include <boost/url/url.hpp>
23 #include <nlohmann/json.hpp>
24 #include <sdbusplus/asio/property.hpp>
25 #include <sdbusplus/message/native_types.hpp>
26 #include <sdbusplus/unpack_properties.hpp>
27
28 #include <algorithm>
29 #include <array>
30 #include <cstddef>
31 #include <functional>
32 #include <map>
33 #include <memory>
34 #include <optional>
35 #include <string>
36 #include <string_view>
37 #include <utility>
38 #include <variant>
39 #include <vector>
40
41 namespace redfish
42 {
43
44 static constexpr const char* objectManagerIface =
45 "org.freedesktop.DBus.ObjectManager";
46 static constexpr const char* pidConfigurationIface =
47 "xyz.openbmc_project.Configuration.Pid";
48 static constexpr const char* pidZoneConfigurationIface =
49 "xyz.openbmc_project.Configuration.Pid.Zone";
50 static constexpr const char* stepwiseConfigurationIface =
51 "xyz.openbmc_project.Configuration.Stepwise";
52 static constexpr const char* thermalModeIface =
53 "xyz.openbmc_project.Control.ThermalMode";
54
asyncPopulatePid(const std::string & connection,const std::string & path,const std::string & currentProfile,const std::vector<std::string> & supportedProfiles,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)55 inline void asyncPopulatePid(
56 const std::string& connection, const std::string& path,
57 const std::string& currentProfile,
58 const std::vector<std::string>& supportedProfiles,
59 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
60 {
61 sdbusplus::message::object_path objPath(path);
62 dbus::utility::getManagedObjects(
63 connection, objPath,
64 [asyncResp, currentProfile, supportedProfiles](
65 const boost::system::error_code& ec,
66 const dbus::utility::ManagedObjectType& managedObj) {
67 if (ec)
68 {
69 BMCWEB_LOG_ERROR("{}", ec);
70 messages::internalError(asyncResp->res);
71 return;
72 }
73 nlohmann::json& configRoot = asyncResp->res.jsonValue["Fan"];
74 nlohmann::json& fans = configRoot["FanControllers"];
75 fans["@odata.type"] =
76 "#OpenBMCManager.v1_0_0.Manager.FanControllers";
77 fans["@odata.id"] = boost::urls::format(
78 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanControllers",
79 BMCWEB_REDFISH_MANAGER_URI_NAME);
80
81 nlohmann::json& pids = configRoot["PidControllers"];
82 pids["@odata.type"] =
83 "#OpenBMCManager.v1_0_0.Manager.PidControllers";
84 pids["@odata.id"] = boost::urls::format(
85 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/PidControllers",
86 BMCWEB_REDFISH_MANAGER_URI_NAME);
87
88 nlohmann::json& stepwise = configRoot["StepwiseControllers"];
89 stepwise["@odata.type"] =
90 "#OpenBMCManager.v1_0_0.Manager.StepwiseControllers";
91 stepwise["@odata.id"] = boost::urls::format(
92 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/StepwiseControllers",
93 BMCWEB_REDFISH_MANAGER_URI_NAME);
94
95 nlohmann::json& zones = configRoot["FanZones"];
96 zones["@odata.id"] = boost::urls::format(
97 "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanZones",
98 BMCWEB_REDFISH_MANAGER_URI_NAME);
99 zones["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanZones";
100 configRoot["@odata.id"] =
101 boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan",
102 BMCWEB_REDFISH_MANAGER_URI_NAME);
103 configRoot["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.Fan";
104 configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
105
106 if (!currentProfile.empty())
107 {
108 configRoot["Profile"] = currentProfile;
109 }
110 BMCWEB_LOG_DEBUG("profile = {} !", currentProfile);
111
112 for (const auto& pathPair : managedObj)
113 {
114 for (const auto& intfPair : pathPair.second)
115 {
116 if (intfPair.first != pidConfigurationIface &&
117 intfPair.first != pidZoneConfigurationIface &&
118 intfPair.first != stepwiseConfigurationIface)
119 {
120 continue;
121 }
122
123 std::string name;
124
125 for (const std::pair<std::string,
126 dbus::utility::DbusVariantType>&
127 propPair : intfPair.second)
128 {
129 if (propPair.first == "Name")
130 {
131 const std::string* namePtr =
132 std::get_if<std::string>(&propPair.second);
133 if (namePtr == nullptr)
134 {
135 BMCWEB_LOG_ERROR("Pid Name Field illegal");
136 messages::internalError(asyncResp->res);
137 return;
138 }
139 name = *namePtr;
140 dbus::utility::escapePathForDbus(name);
141 }
142 else if (propPair.first == "Profiles")
143 {
144 const std::vector<std::string>* profiles =
145 std::get_if<std::vector<std::string>>(
146 &propPair.second);
147 if (profiles == nullptr)
148 {
149 BMCWEB_LOG_ERROR("Pid Profiles Field illegal");
150 messages::internalError(asyncResp->res);
151 return;
152 }
153 if (std::ranges::find(*profiles, currentProfile) ==
154 profiles->end())
155 {
156 BMCWEB_LOG_INFO(
157 "{} not supported in current profile",
158 name);
159 continue;
160 }
161 }
162 }
163 nlohmann::json* config = nullptr;
164 const std::string* classPtr = nullptr;
165
166 for (const std::pair<std::string,
167 dbus::utility::DbusVariantType>&
168 propPair : intfPair.second)
169 {
170 if (propPair.first == "Class")
171 {
172 classPtr =
173 std::get_if<std::string>(&propPair.second);
174 }
175 }
176
177 boost::urls::url url(
178 boost::urls::format("/redfish/v1/Managers/{}",
179 BMCWEB_REDFISH_MANAGER_URI_NAME));
180 if (intfPair.first == pidZoneConfigurationIface)
181 {
182 sdbusplus::message::object_path pidPath(
183 pathPair.first.str);
184 std::string chassis = pidPath.filename();
185 if (chassis.empty())
186 {
187 chassis = "#IllegalValue";
188 }
189 nlohmann::json& zone = zones[name];
190 zone["Chassis"]["@odata.id"] = boost::urls::format(
191 "/redfish/v1/Chassis/{}", chassis);
192 url.set_fragment(
193 ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name)
194 .to_string());
195 zone["@odata.id"] = std::move(url);
196 zone["@odata.type"] =
197 "#OpenBMCManager.v1_0_0.Manager.FanZone";
198 config = &zone;
199 }
200
201 else if (intfPair.first == stepwiseConfigurationIface)
202 {
203 if (classPtr == nullptr)
204 {
205 BMCWEB_LOG_ERROR("Pid Class Field illegal");
206 messages::internalError(asyncResp->res);
207 return;
208 }
209
210 nlohmann::json& controller = stepwise[name];
211 config = &controller;
212 url.set_fragment(
213 ("/Oem/OpenBmc/Fan/StepwiseControllers"_json_pointer /
214 name)
215 .to_string());
216 controller["@odata.id"] = std::move(url);
217 controller["@odata.type"] =
218 "#OpenBMCManager.v1_0_0.Manager.StepwiseController";
219
220 controller["Direction"] = *classPtr;
221 }
222
223 // pid and fans are off the same configuration
224 else if (intfPair.first == pidConfigurationIface)
225 {
226 if (classPtr == nullptr)
227 {
228 BMCWEB_LOG_ERROR("Pid Class Field illegal");
229 messages::internalError(asyncResp->res);
230 return;
231 }
232 bool isFan = *classPtr == "fan";
233 nlohmann::json& element =
234 isFan ? fans[name] : pids[name];
235 config = &element;
236 if (isFan)
237 {
238 url.set_fragment(
239 ("/Oem/OpenBmc/Fan/FanControllers"_json_pointer /
240 name)
241 .to_string());
242 element["@odata.id"] = std::move(url);
243 element["@odata.type"] =
244 "#OpenBMCManager.v1_0_0.Manager.FanController";
245 }
246 else
247 {
248 url.set_fragment(
249 ("/Oem/OpenBmc/Fan/PidControllers"_json_pointer /
250 name)
251 .to_string());
252 element["@odata.id"] = std::move(url);
253 element["@odata.type"] =
254 "#OpenBMCManager.v1_0_0.Manager.PidController";
255 }
256 }
257 else
258 {
259 BMCWEB_LOG_ERROR("Unexpected configuration");
260 messages::internalError(asyncResp->res);
261 return;
262 }
263
264 // used for making maps out of 2 vectors
265 const std::vector<double>* keys = nullptr;
266 const std::vector<double>* values = nullptr;
267
268 for (const auto& propertyPair : intfPair.second)
269 {
270 if (propertyPair.first == "Type" ||
271 propertyPair.first == "Class" ||
272 propertyPair.first == "Name" ||
273 propertyPair.first == "AccumulateSetPoint")
274 {
275 continue;
276 }
277
278 // zones
279 if (intfPair.first == pidZoneConfigurationIface)
280 {
281 const double* ptr =
282 std::get_if<double>(&propertyPair.second);
283 if (ptr == nullptr)
284 {
285 BMCWEB_LOG_ERROR("Field Illegal {}",
286 propertyPair.first);
287 messages::internalError(asyncResp->res);
288 return;
289 }
290 (*config)[propertyPair.first] = *ptr;
291 }
292
293 if (intfPair.first == stepwiseConfigurationIface)
294 {
295 if (propertyPair.first == "Reading" ||
296 propertyPair.first == "Output")
297 {
298 const std::vector<double>* ptr =
299 std::get_if<std::vector<double>>(
300 &propertyPair.second);
301
302 if (ptr == nullptr)
303 {
304 BMCWEB_LOG_ERROR("Field Illegal {}",
305 propertyPair.first);
306 messages::internalError(asyncResp->res);
307 return;
308 }
309
310 if (propertyPair.first == "Reading")
311 {
312 keys = ptr;
313 }
314 else
315 {
316 values = ptr;
317 }
318 if (keys != nullptr && values != nullptr)
319 {
320 if (keys->size() != values->size())
321 {
322 BMCWEB_LOG_ERROR(
323 "Reading and Output size don't match ");
324 messages::internalError(asyncResp->res);
325 return;
326 }
327 nlohmann::json& steps = (*config)["Steps"];
328 steps = nlohmann::json::array();
329 for (size_t ii = 0; ii < keys->size(); ii++)
330 {
331 nlohmann::json::object_t step;
332 step["Target"] = (*keys)[ii];
333 step["Output"] = (*values)[ii];
334 steps.emplace_back(std::move(step));
335 }
336 }
337 }
338 if (propertyPair.first == "NegativeHysteresis" ||
339 propertyPair.first == "PositiveHysteresis")
340 {
341 const double* ptr =
342 std::get_if<double>(&propertyPair.second);
343 if (ptr == nullptr)
344 {
345 BMCWEB_LOG_ERROR("Field Illegal {}",
346 propertyPair.first);
347 messages::internalError(asyncResp->res);
348 return;
349 }
350 (*config)[propertyPair.first] = *ptr;
351 }
352 }
353
354 // pid and fans are off the same configuration
355 if (intfPair.first == pidConfigurationIface ||
356 intfPair.first == stepwiseConfigurationIface)
357 {
358 if (propertyPair.first == "Zones")
359 {
360 const std::vector<std::string>* inputs =
361 std::get_if<std::vector<std::string>>(
362 &propertyPair.second);
363
364 if (inputs == nullptr)
365 {
366 BMCWEB_LOG_ERROR("Zones Pid Field Illegal");
367 messages::internalError(asyncResp->res);
368 return;
369 }
370 auto& data = (*config)[propertyPair.first];
371 data = nlohmann::json::array();
372 for (std::string itemCopy : *inputs)
373 {
374 dbus::utility::escapePathForDbus(itemCopy);
375 nlohmann::json::object_t input;
376 boost::urls::url managerUrl =
377 boost::urls::format(
378 "/redfish/v1/Managers/{}#{}",
379 BMCWEB_REDFISH_MANAGER_URI_NAME,
380 ("/Oem/OpenBmc/Fan/FanZones"_json_pointer /
381 itemCopy)
382 .to_string());
383 input["@odata.id"] = std::move(managerUrl);
384 data.emplace_back(std::move(input));
385 }
386 }
387 // todo(james): may never happen, but this
388 // assumes configuration data referenced in the
389 // PID config is provided by the same daemon, we
390 // could add another loop to cover all cases,
391 // but I'm okay kicking this can down the road a
392 // bit
393
394 else if (propertyPair.first == "Inputs" ||
395 propertyPair.first == "Outputs")
396 {
397 auto& data = (*config)[propertyPair.first];
398 const std::vector<std::string>* inputs =
399 std::get_if<std::vector<std::string>>(
400 &propertyPair.second);
401
402 if (inputs == nullptr)
403 {
404 BMCWEB_LOG_ERROR("Field Illegal {}",
405 propertyPair.first);
406 messages::internalError(asyncResp->res);
407 return;
408 }
409 data = *inputs;
410 }
411 else if (propertyPair.first == "SetPointOffset")
412 {
413 const std::string* ptr =
414 std::get_if<std::string>(
415 &propertyPair.second);
416
417 if (ptr == nullptr)
418 {
419 BMCWEB_LOG_ERROR("Field Illegal {}",
420 propertyPair.first);
421 messages::internalError(asyncResp->res);
422 return;
423 }
424 // translate from dbus to redfish
425 if (*ptr == "WarningHigh")
426 {
427 (*config)["SetPointOffset"] =
428 "UpperThresholdNonCritical";
429 }
430 else if (*ptr == "WarningLow")
431 {
432 (*config)["SetPointOffset"] =
433 "LowerThresholdNonCritical";
434 }
435 else if (*ptr == "CriticalHigh")
436 {
437 (*config)["SetPointOffset"] =
438 "UpperThresholdCritical";
439 }
440 else if (*ptr == "CriticalLow")
441 {
442 (*config)["SetPointOffset"] =
443 "LowerThresholdCritical";
444 }
445 else
446 {
447 BMCWEB_LOG_ERROR("Value Illegal {}", *ptr);
448 messages::internalError(asyncResp->res);
449 return;
450 }
451 }
452 // doubles
453 else if (propertyPair.first ==
454 "FFGainCoefficient" ||
455 propertyPair.first == "FFOffCoefficient" ||
456 propertyPair.first == "ICoefficient" ||
457 propertyPair.first == "ILimitMax" ||
458 propertyPair.first == "ILimitMin" ||
459 propertyPair.first ==
460 "PositiveHysteresis" ||
461 propertyPair.first ==
462 "NegativeHysteresis" ||
463 propertyPair.first == "OutLimitMax" ||
464 propertyPair.first == "OutLimitMin" ||
465 propertyPair.first == "PCoefficient" ||
466 propertyPair.first == "SetPoint" ||
467 propertyPair.first == "SlewNeg" ||
468 propertyPair.first == "SlewPos")
469 {
470 const double* ptr =
471 std::get_if<double>(&propertyPair.second);
472 if (ptr == nullptr)
473 {
474 BMCWEB_LOG_ERROR("Field Illegal {}",
475 propertyPair.first);
476 messages::internalError(asyncResp->res);
477 return;
478 }
479 (*config)[propertyPair.first] = *ptr;
480 }
481 }
482 }
483 }
484 }
485 });
486 }
487
488 enum class CreatePIDRet
489 {
490 fail,
491 del,
492 patch
493 };
494
findChassis(const dbus::utility::ManagedObjectType & managedObj,std::string_view value,std::string & chassis)495 inline const dbus::utility::ManagedObjectType::value_type* findChassis(
496 const dbus::utility::ManagedObjectType& managedObj, std::string_view value,
497 std::string& chassis)
498 {
499 BMCWEB_LOG_DEBUG("Find Chassis: {}", value);
500
501 std::string escaped(value);
502 std::ranges::replace(escaped, ' ', '_');
503 escaped = "/" + escaped;
504 auto it = std::ranges::find_if(managedObj, [&escaped](const auto& obj) {
505 if (obj.first.str.ends_with(escaped))
506 {
507 BMCWEB_LOG_DEBUG("Matched {}", obj.first.str);
508 return true;
509 }
510 return false;
511 });
512
513 if (it == managedObj.end())
514 {
515 return nullptr;
516 }
517 // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
518 sdbusplus::message::object_path path(it->first.str);
519 chassis = path.filename();
520
521 return &(*it);
522 }
523
getZonesFromJsonReq(const std::shared_ptr<bmcweb::AsyncResp> & response,std::vector<nlohmann::json::object_t> & config,std::vector<std::string> & zones)524 inline bool getZonesFromJsonReq(
525 const std::shared_ptr<bmcweb::AsyncResp>& response,
526 std::vector<nlohmann::json::object_t>& config,
527 std::vector<std::string>& zones)
528 {
529 if (config.empty())
530 {
531 BMCWEB_LOG_ERROR("Empty Zones");
532 messages::propertyValueFormatError(response->res, config, "Zones");
533 return false;
534 }
535 for (auto& odata : config)
536 {
537 std::string path;
538 if (!redfish::json_util::readJsonObject(odata, response->res,
539 "@odata.id", path))
540 {
541 return false;
542 }
543
544 boost::system::result<boost::urls::url_view> parsed =
545 boost::urls::parse_relative_ref(path);
546 if (!parsed)
547 {
548 BMCWEB_LOG_WARNING("Got invalid path {}", path);
549 messages::propertyValueFormatError(response->res, path, "Zones");
550 return false;
551 }
552
553 std::string input;
554
555 // 8 below comes from
556 // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
557 std::string managerId;
558 if (!crow::utility::readUrlSegments(
559 *parsed, "redfish", "v1", "Managers", std::ref(managerId),
560 "Oem", "OpenBmc", "Fan", "FanZones", std::ref(input)))
561 {
562 BMCWEB_LOG_ERROR("Got invalid path {}", path);
563 BMCWEB_LOG_ERROR("Illegal Type Zones");
564 messages::propertyValueFormatError(response->res, odata, "Zones");
565 return false;
566 }
567 std::ranges::replace(input, '_', ' ');
568 zones.emplace_back(std::move(input));
569 }
570 return true;
571 }
572
createPidInterface(const std::shared_ptr<bmcweb::AsyncResp> & response,const std::string & type,std::string_view name,nlohmann::json & jsonValue,const std::string & path,const dbus::utility::ManagedObjectType & managedObj,bool createNewObject,dbus::utility::DBusPropertiesMap & output,std::string & chassis,const std::string & profile)573 inline CreatePIDRet createPidInterface(
574 const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type,
575 std::string_view name, nlohmann::json& jsonValue, const std::string& path,
576 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
577 dbus::utility::DBusPropertiesMap& output, std::string& chassis,
578 const std::string& profile)
579 {
580 // common deleter
581 if (jsonValue == nullptr)
582 {
583 std::string iface;
584 if (type == "PidControllers" || type == "FanControllers")
585 {
586 iface = pidConfigurationIface;
587 }
588 else if (type == "FanZones")
589 {
590 iface = pidZoneConfigurationIface;
591 }
592 else if (type == "StepwiseControllers")
593 {
594 iface = stepwiseConfigurationIface;
595 }
596 else
597 {
598 BMCWEB_LOG_ERROR("Illegal Type {}", type);
599 messages::propertyUnknown(response->res, type);
600 return CreatePIDRet::fail;
601 }
602
603 BMCWEB_LOG_DEBUG("del {} {}", path, iface);
604 // delete interface
605 dbus::utility::async_method_call(
606 response,
607 [response, path](const boost::system::error_code& ec) {
608 if (ec)
609 {
610 BMCWEB_LOG_ERROR("Error patching {}: {}", path, ec);
611 messages::internalError(response->res);
612 return;
613 }
614 messages::success(response->res);
615 },
616 "xyz.openbmc_project.EntityManager", path, iface, "Delete");
617 return CreatePIDRet::del;
618 }
619
620 const dbus::utility::ManagedObjectType::value_type* managedItem = nullptr;
621 if (!createNewObject)
622 {
623 // if we aren't creating a new object, we should be able to find it on
624 // d-bus
625 managedItem = findChassis(managedObj, name, chassis);
626 if (managedItem == nullptr)
627 {
628 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
629 messages::invalidObject(
630 response->res,
631 boost::urls::format("/redfish/v1/Chassis/{}", chassis));
632 return CreatePIDRet::fail;
633 }
634 }
635
636 if (!profile.empty() &&
637 (type == "PidControllers" || type == "FanControllers" ||
638 type == "StepwiseControllers"))
639 {
640 if (managedItem == nullptr)
641 {
642 output.emplace_back("Profiles", std::vector<std::string>{profile});
643 }
644 else
645 {
646 std::string interface;
647 if (type == "StepwiseControllers")
648 {
649 interface = stepwiseConfigurationIface;
650 }
651 else
652 {
653 interface = pidConfigurationIface;
654 }
655 bool ifaceFound = false;
656 for (const auto& iface : managedItem->second)
657 {
658 if (iface.first == interface)
659 {
660 ifaceFound = true;
661 for (const auto& prop : iface.second)
662 {
663 if (prop.first == "Profiles")
664 {
665 const std::vector<std::string>* curProfiles =
666 std::get_if<std::vector<std::string>>(
667 &(prop.second));
668 if (curProfiles == nullptr)
669 {
670 BMCWEB_LOG_ERROR(
671 "Illegal profiles in managed object");
672 messages::internalError(response->res);
673 return CreatePIDRet::fail;
674 }
675 if (std::ranges::find(*curProfiles, profile) ==
676 curProfiles->end())
677 {
678 std::vector<std::string> newProfiles =
679 *curProfiles;
680 newProfiles.push_back(profile);
681 output.emplace_back("Profiles", newProfiles);
682 }
683 }
684 }
685 }
686 }
687
688 if (!ifaceFound)
689 {
690 BMCWEB_LOG_ERROR("Failed to find interface in managed object");
691 messages::internalError(response->res);
692 return CreatePIDRet::fail;
693 }
694 }
695 }
696
697 if (type == "PidControllers" || type == "FanControllers")
698 {
699 if (createNewObject)
700 {
701 output.emplace_back("Class",
702 type == "PidControllers" ? "temp" : "fan");
703 output.emplace_back("Type", "Pid");
704 }
705
706 std::optional<std::vector<nlohmann::json::object_t>> zones;
707 std::optional<std::vector<std::string>> inputs;
708 std::optional<std::vector<std::string>> outputs;
709 std::map<std::string, std::optional<double>> doubles;
710 std::optional<std::string> setpointOffset;
711 if (!redfish::json_util::readJson( //
712 jsonValue, response->res, //
713 "FFGainCoefficient", doubles["FFGainCoefficient"], //
714 "FFOffCoefficient", doubles["FFOffCoefficient"], //
715 "ICoefficient", doubles["ICoefficient"], //
716 "ILimitMax", doubles["ILimitMax"], //
717 "ILimitMin", doubles["ILimitMin"], //
718 "Inputs", inputs, //
719 "NegativeHysteresis", doubles["NegativeHysteresis"], //
720 "OutLimitMax", doubles["OutLimitMax"], //
721 "OutLimitMin", doubles["OutLimitMin"], //
722 "Outputs", outputs, //
723 "PCoefficient", doubles["PCoefficient"], //
724 "PositiveHysteresis", doubles["PositiveHysteresis"], //
725 "SetPoint", doubles["SetPoint"], //
726 "SetPointOffset", setpointOffset, //
727 "SlewNeg", doubles["SlewNeg"], //
728 "SlewPos", doubles["SlewPos"], //
729 "Zones", zones //
730 ))
731 {
732 return CreatePIDRet::fail;
733 }
734
735 if (zones)
736 {
737 std::vector<std::string> zonesStr;
738 if (!getZonesFromJsonReq(response, *zones, zonesStr))
739 {
740 BMCWEB_LOG_ERROR("Illegal Zones");
741 return CreatePIDRet::fail;
742 }
743 if (chassis.empty() &&
744 findChassis(managedObj, zonesStr[0], chassis) == nullptr)
745 {
746 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
747 messages::invalidObject(
748 response->res,
749 boost::urls::format("/redfish/v1/Chassis/{}", chassis));
750 return CreatePIDRet::fail;
751 }
752 output.emplace_back("Zones", std::move(zonesStr));
753 }
754
755 if (inputs)
756 {
757 for (std::string& value : *inputs)
758 {
759 std::ranges::replace(value, '_', ' ');
760 }
761 output.emplace_back("Inputs", *inputs);
762 }
763
764 if (outputs)
765 {
766 for (std::string& value : *outputs)
767 {
768 std::ranges::replace(value, '_', ' ');
769 }
770 output.emplace_back("Outputs", *outputs);
771 }
772
773 if (setpointOffset)
774 {
775 // translate between redfish and dbus names
776 if (*setpointOffset == "UpperThresholdNonCritical")
777 {
778 output.emplace_back("SetPointOffset", "WarningLow");
779 }
780 else if (*setpointOffset == "LowerThresholdNonCritical")
781 {
782 output.emplace_back("SetPointOffset", "WarningHigh");
783 }
784 else if (*setpointOffset == "LowerThresholdCritical")
785 {
786 output.emplace_back("SetPointOffset", "CriticalLow");
787 }
788 else if (*setpointOffset == "UpperThresholdCritical")
789 {
790 output.emplace_back("SetPointOffset", "CriticalHigh");
791 }
792 else
793 {
794 BMCWEB_LOG_ERROR("Invalid setpointoffset {}", *setpointOffset);
795 messages::propertyValueNotInList(response->res, name,
796 "SetPointOffset");
797 return CreatePIDRet::fail;
798 }
799 }
800
801 // doubles
802 for (const auto& pairs : doubles)
803 {
804 if (!pairs.second)
805 {
806 continue;
807 }
808 BMCWEB_LOG_DEBUG("{} = {}", pairs.first, *pairs.second);
809 output.emplace_back(pairs.first, *pairs.second);
810 }
811 }
812
813 else if (type == "FanZones")
814 {
815 output.emplace_back("Type", "Pid.Zone");
816
817 std::optional<std::string> chassisId;
818 std::optional<double> failSafePercent;
819 std::optional<double> minThermalOutput;
820 if (!redfish::json_util::readJson( //
821 jsonValue, response->res, //
822 "Chassis/@odata.id", chassisId, //
823 "FailSafePercent", failSafePercent, //
824 "MinThermalOutput", minThermalOutput))
825 {
826 return CreatePIDRet::fail;
827 }
828
829 if (chassisId)
830 {
831 boost::system::result<boost::urls::url_view> parsed =
832 boost::urls::parse_relative_ref(*chassisId);
833 if (!parsed)
834 {
835 BMCWEB_LOG_WARNING("Got invalid path {}", *chassisId);
836 messages::propertyValueFormatError(response->res, *chassisId,
837 "Chassis/@odata.id");
838 return CreatePIDRet::fail;
839 }
840
841 if (!crow::utility::readUrlSegments(*parsed, "redfish", "v1",
842 "Chassis", std::ref(chassis)))
843 {
844 BMCWEB_LOG_WARNING("Got invalid path {}", *parsed);
845 messages::propertyValueFormatError(response->res, *chassisId,
846 "Chassis/@odata.id");
847 return CreatePIDRet::fail;
848 }
849 }
850
851 if (minThermalOutput)
852 {
853 output.emplace_back("MinThermalOutput", *minThermalOutput);
854 }
855 if (failSafePercent)
856 {
857 output.emplace_back("FailSafePercent", *failSafePercent);
858 }
859 }
860 else if (type == "StepwiseControllers")
861 {
862 output.emplace_back("Type", "Stepwise");
863
864 std::optional<std::vector<nlohmann::json::object_t>> zones;
865 std::optional<std::vector<nlohmann::json::object_t>> steps;
866 std::optional<std::vector<std::string>> inputs;
867 std::optional<double> positiveHysteresis;
868 std::optional<double> negativeHysteresis;
869 std::optional<std::string> direction; // upper clipping curve vs lower
870 if (!redfish::json_util::readJson( //
871 jsonValue, response->res, //
872 "Direction", direction, //
873 "Inputs", inputs, //
874 "NegativeHysteresis", negativeHysteresis, //
875 "PositiveHysteresis", positiveHysteresis, //
876 "Steps", steps, //
877 "Zones", zones //
878 ))
879 {
880 return CreatePIDRet::fail;
881 }
882
883 if (zones)
884 {
885 std::vector<std::string> zonesStrs;
886 if (!getZonesFromJsonReq(response, *zones, zonesStrs))
887 {
888 BMCWEB_LOG_ERROR("Illegal Zones");
889 return CreatePIDRet::fail;
890 }
891 if (chassis.empty() &&
892 findChassis(managedObj, zonesStrs[0], chassis) == nullptr)
893 {
894 BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
895 messages::invalidObject(
896 response->res,
897 boost::urls::format("/redfish/v1/Chassis/{}", chassis));
898 return CreatePIDRet::fail;
899 }
900 output.emplace_back("Zones", std::move(zonesStrs));
901 }
902 if (steps)
903 {
904 std::vector<double> readings;
905 std::vector<double> outputs;
906 for (auto& step : *steps)
907 {
908 double target = 0.0;
909 double out = 0.0;
910
911 if (!redfish::json_util::readJsonObject( //
912 step, response->res, //
913 "Output", out, //
914 "Target", target //
915 ))
916 {
917 return CreatePIDRet::fail;
918 }
919 readings.emplace_back(target);
920 outputs.emplace_back(out);
921 }
922 output.emplace_back("Reading", std::move(readings));
923 output.emplace_back("Output", std::move(outputs));
924 }
925 if (inputs)
926 {
927 for (std::string& value : *inputs)
928 {
929 std::ranges::replace(value, '_', ' ');
930 }
931 output.emplace_back("Inputs", std::move(*inputs));
932 }
933 if (negativeHysteresis)
934 {
935 output.emplace_back("NegativeHysteresis", *negativeHysteresis);
936 }
937 if (positiveHysteresis)
938 {
939 output.emplace_back("PositiveHysteresis", *positiveHysteresis);
940 }
941 if (direction)
942 {
943 constexpr const std::array<const char*, 2> allowedDirections = {
944 "Ceiling", "Floor"};
945 if (std::ranges::find(allowedDirections, *direction) ==
946 allowedDirections.end())
947 {
948 messages::propertyValueTypeError(response->res, "Direction",
949 *direction);
950 return CreatePIDRet::fail;
951 }
952 output.emplace_back("Class", *direction);
953 }
954 }
955 else
956 {
957 BMCWEB_LOG_ERROR("Illegal Type {}", type);
958 messages::propertyUnknown(response->res, type);
959 return CreatePIDRet::fail;
960 }
961 return CreatePIDRet::patch;
962 }
963
964 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
965 {
966 struct CompletionValues
967 {
968 std::vector<std::string> supportedProfiles;
969 std::string currentProfile;
970 dbus::utility::MapperGetSubTreeResponse subtree;
971 };
972
GetPIDValuesredfish::GetPIDValues973 explicit GetPIDValues(
974 const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
975 asyncResp(asyncRespIn)
976
977 {}
978
runredfish::GetPIDValues979 void run()
980 {
981 std::shared_ptr<GetPIDValues> self = shared_from_this();
982
983 // get all configurations
984 constexpr std::array<std::string_view, 4> interfaces = {
985 pidConfigurationIface, pidZoneConfigurationIface,
986 objectManagerIface, stepwiseConfigurationIface};
987 dbus::utility::getSubTree(
988 "/", 0, interfaces,
989 [self](
990 const boost::system::error_code& ec,
991 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
992 if (ec)
993 {
994 BMCWEB_LOG_ERROR("{}", ec);
995 messages::internalError(self->asyncResp->res);
996 return;
997 }
998 self->complete.subtree = subtreeLocal;
999 });
1000
1001 // at the same time get the selected profile
1002 constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1003 thermalModeIface};
1004 dbus::utility::getSubTree(
1005 "/", 0, thermalModeIfaces,
1006 [self](
1007 const boost::system::error_code& ec,
1008 const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
1009 if (ec || subtreeLocal.empty())
1010 {
1011 return;
1012 }
1013 if (subtreeLocal[0].second.size() != 1)
1014 {
1015 // invalid mapper response, should never happen
1016 BMCWEB_LOG_ERROR("GetPIDValues: Mapper Error");
1017 messages::internalError(self->asyncResp->res);
1018 return;
1019 }
1020
1021 const std::string& path = subtreeLocal[0].first;
1022 const std::string& owner = subtreeLocal[0].second[0].first;
1023
1024 dbus::utility::getAllProperties(
1025 *crow::connections::systemBus, owner, path,
1026 thermalModeIface,
1027 [path, owner,
1028 self](const boost::system::error_code& ec2,
1029 const dbus::utility::DBusPropertiesMap& resp) {
1030 if (ec2)
1031 {
1032 BMCWEB_LOG_ERROR(
1033 "GetPIDValues: Can't get thermalModeIface {}",
1034 path);
1035 messages::internalError(self->asyncResp->res);
1036 return;
1037 }
1038
1039 const std::string* current = nullptr;
1040 const std::vector<std::string>* supported = nullptr;
1041
1042 const bool success = sdbusplus::unpackPropertiesNoThrow(
1043 dbus_utils::UnpackErrorPrinter(), resp, "Current",
1044 current, "Supported", supported);
1045
1046 if (!success)
1047 {
1048 messages::internalError(self->asyncResp->res);
1049 return;
1050 }
1051
1052 if (current == nullptr || supported == nullptr)
1053 {
1054 BMCWEB_LOG_ERROR(
1055 "GetPIDValues: thermal mode iface invalid {}",
1056 path);
1057 messages::internalError(self->asyncResp->res);
1058 return;
1059 }
1060 self->complete.currentProfile = *current;
1061 self->complete.supportedProfiles = *supported;
1062 });
1063 });
1064 }
1065
processingCompleteredfish::GetPIDValues1066 static void processingComplete(
1067 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1068 const CompletionValues& completion)
1069 {
1070 if (asyncResp->res.result() != boost::beast::http::status::ok)
1071 {
1072 return;
1073 }
1074 // create map of <connection, path to objMgr>>
1075 boost::container::flat_map<
1076 std::string, std::string, std::less<>,
1077 std::vector<std::pair<std::string, std::string>>>
1078 objectMgrPaths;
1079 boost::container::flat_set<std::string, std::less<>,
1080 std::vector<std::string>>
1081 calledConnections;
1082 for (const auto& pathGroup : completion.subtree)
1083 {
1084 for (const auto& connectionGroup : pathGroup.second)
1085 {
1086 auto findConnection =
1087 calledConnections.find(connectionGroup.first);
1088 if (findConnection != calledConnections.end())
1089 {
1090 break;
1091 }
1092 for (const std::string& interface : connectionGroup.second)
1093 {
1094 if (interface == objectManagerIface)
1095 {
1096 objectMgrPaths[connectionGroup.first] = pathGroup.first;
1097 }
1098 // this list is alphabetical, so we
1099 // should have found the objMgr by now
1100 if (interface == pidConfigurationIface ||
1101 interface == pidZoneConfigurationIface ||
1102 interface == stepwiseConfigurationIface)
1103 {
1104 auto findObjMgr =
1105 objectMgrPaths.find(connectionGroup.first);
1106 if (findObjMgr == objectMgrPaths.end())
1107 {
1108 BMCWEB_LOG_DEBUG("{}Has no Object Manager",
1109 connectionGroup.first);
1110 continue;
1111 }
1112
1113 calledConnections.insert(connectionGroup.first);
1114
1115 asyncPopulatePid(findObjMgr->first, findObjMgr->second,
1116 completion.currentProfile,
1117 completion.supportedProfiles,
1118 asyncResp);
1119 break;
1120 }
1121 }
1122 }
1123 }
1124 }
1125
~GetPIDValuesredfish::GetPIDValues1126 ~GetPIDValues()
1127 {
1128 boost::asio::post(getIoContext(),
1129 std::bind_front(&processingComplete, asyncResp,
1130 std::move(complete)));
1131 }
1132
1133 GetPIDValues(const GetPIDValues&) = delete;
1134 GetPIDValues(GetPIDValues&&) = delete;
1135 GetPIDValues& operator=(const GetPIDValues&) = delete;
1136 GetPIDValues& operator=(GetPIDValues&&) = delete;
1137
1138 std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1139 CompletionValues complete;
1140 };
1141
1142 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
1143 {
SetPIDValuesredfish::SetPIDValues1144 SetPIDValues(
1145 const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
1146 std::vector<
1147 std::pair<std::string, std::optional<nlohmann::json::object_t>>>&&
1148 configurationsIn,
1149 std::optional<std::string>& profileIn) :
1150 asyncResp(asyncRespIn), configuration(std::move(configurationsIn)),
1151 profile(std::move(profileIn))
1152 {}
1153
1154 SetPIDValues(const SetPIDValues&) = delete;
1155 SetPIDValues(SetPIDValues&&) = delete;
1156 SetPIDValues& operator=(const SetPIDValues&) = delete;
1157 SetPIDValues& operator=(SetPIDValues&&) = delete;
1158
runredfish::SetPIDValues1159 void run()
1160 {
1161 if (asyncResp->res.result() != boost::beast::http::status::ok)
1162 {
1163 return;
1164 }
1165
1166 std::shared_ptr<SetPIDValues> self = shared_from_this();
1167
1168 // todo(james): might make sense to do a mapper call here if this
1169 // interface gets more traction
1170 sdbusplus::message::object_path objPath(
1171 "/xyz/openbmc_project/inventory");
1172 dbus::utility::getManagedObjects(
1173 "xyz.openbmc_project.EntityManager", objPath,
1174 [self](const boost::system::error_code& ec,
1175 const dbus::utility::ManagedObjectType& mObj) {
1176 if (ec)
1177 {
1178 BMCWEB_LOG_ERROR("Error communicating to Entity Manager");
1179 messages::internalError(self->asyncResp->res);
1180 return;
1181 }
1182 const std::array<const char*, 3> configurations = {
1183 pidConfigurationIface, pidZoneConfigurationIface,
1184 stepwiseConfigurationIface};
1185
1186 for (const auto& [path, object] : mObj)
1187 {
1188 for (const auto& [interface, _] : object)
1189 {
1190 if (std::ranges::find(configurations, interface) !=
1191 configurations.end())
1192 {
1193 self->objectCount++;
1194 break;
1195 }
1196 }
1197 }
1198 self->managedObj = mObj;
1199 });
1200
1201 // at the same time get the profile information
1202 constexpr std::array<std::string_view, 1> thermalModeIfaces = {
1203 thermalModeIface};
1204 dbus::utility::getSubTree(
1205 "/", 0, thermalModeIfaces,
1206 [self](const boost::system::error_code& ec,
1207 const dbus::utility::MapperGetSubTreeResponse& subtree) {
1208 if (ec || subtree.empty())
1209 {
1210 return;
1211 }
1212 if (subtree[0].second.empty())
1213 {
1214 // invalid mapper response, should never happen
1215 BMCWEB_LOG_ERROR("SetPIDValues: Mapper Error");
1216 messages::internalError(self->asyncResp->res);
1217 return;
1218 }
1219
1220 const std::string& path = subtree[0].first;
1221 const std::string& owner = subtree[0].second[0].first;
1222 dbus::utility::getAllProperties(
1223 *crow::connections::systemBus, owner, path,
1224 thermalModeIface,
1225 [self, path,
1226 owner](const boost::system::error_code& ec2,
1227 const dbus::utility::DBusPropertiesMap& r) {
1228 if (ec2)
1229 {
1230 BMCWEB_LOG_ERROR(
1231 "SetPIDValues: Can't get thermalModeIface {}",
1232 path);
1233 messages::internalError(self->asyncResp->res);
1234 return;
1235 }
1236 const std::string* current = nullptr;
1237 const std::vector<std::string>* supported = nullptr;
1238
1239 const bool success = sdbusplus::unpackPropertiesNoThrow(
1240 dbus_utils::UnpackErrorPrinter(), r, "Current",
1241 current, "Supported", supported);
1242
1243 if (!success)
1244 {
1245 messages::internalError(self->asyncResp->res);
1246 return;
1247 }
1248
1249 if (current == nullptr || supported == nullptr)
1250 {
1251 BMCWEB_LOG_ERROR(
1252 "SetPIDValues: thermal mode iface invalid {}",
1253 path);
1254 messages::internalError(self->asyncResp->res);
1255 return;
1256 }
1257 self->currentProfile = *current;
1258 self->supportedProfiles = *supported;
1259 self->profileConnection = owner;
1260 self->profilePath = path;
1261 });
1262 });
1263 }
pidSetDoneredfish::SetPIDValues1264 void pidSetDone()
1265 {
1266 if (asyncResp->res.result() != boost::beast::http::status::ok)
1267 {
1268 return;
1269 }
1270 std::shared_ptr<bmcweb::AsyncResp> response = asyncResp;
1271 if (profile)
1272 {
1273 if (std::ranges::find(supportedProfiles, *profile) ==
1274 supportedProfiles.end())
1275 {
1276 messages::actionParameterUnknown(response->res, "Profile",
1277 *profile);
1278 return;
1279 }
1280 currentProfile = *profile;
1281 sdbusplus::asio::setProperty(
1282 *crow::connections::systemBus, profileConnection, profilePath,
1283 thermalModeIface, "Current", *profile,
1284 [response](const boost::system::error_code& ec) {
1285 if (ec)
1286 {
1287 BMCWEB_LOG_ERROR("Error patching profile{}", ec);
1288 messages::internalError(response->res);
1289 }
1290 });
1291 }
1292
1293 for (auto& containerPair : configuration)
1294 {
1295 auto& container = containerPair.second;
1296 if (!container)
1297 {
1298 continue;
1299 }
1300
1301 const std::string& type = containerPair.first;
1302
1303 for (auto& [name, value] : *container)
1304 {
1305 std::string dbusObjName = name;
1306 std::ranges::replace(dbusObjName, ' ', '_');
1307 BMCWEB_LOG_DEBUG("looking for {}", name);
1308
1309 auto pathItr = std::ranges::find_if(
1310 managedObj, [&dbusObjName](const auto& obj) {
1311 return obj.first.filename() == dbusObjName;
1312 });
1313 dbus::utility::DBusPropertiesMap output;
1314
1315 output.reserve(16); // The pid interface length
1316
1317 // determines if we're patching entity-manager or
1318 // creating a new object
1319 bool createNewObject = (pathItr == managedObj.end());
1320 BMCWEB_LOG_DEBUG("Found = {}", !createNewObject);
1321
1322 std::string iface;
1323 if (!createNewObject)
1324 {
1325 bool findInterface = false;
1326 for (const auto& interface : pathItr->second)
1327 {
1328 if (interface.first == pidConfigurationIface)
1329 {
1330 if (type == "PidControllers" ||
1331 type == "FanControllers")
1332 {
1333 iface = pidConfigurationIface;
1334 findInterface = true;
1335 break;
1336 }
1337 }
1338 else if (interface.first == pidZoneConfigurationIface)
1339 {
1340 if (type == "FanZones")
1341 {
1342 iface = pidZoneConfigurationIface;
1343 findInterface = true;
1344 break;
1345 }
1346 }
1347 else if (interface.first == stepwiseConfigurationIface)
1348 {
1349 if (type == "StepwiseControllers")
1350 {
1351 iface = stepwiseConfigurationIface;
1352 findInterface = true;
1353 break;
1354 }
1355 }
1356 }
1357
1358 // create new object if interface not found
1359 if (!findInterface)
1360 {
1361 createNewObject = true;
1362 }
1363 }
1364
1365 if (createNewObject && value == nullptr)
1366 {
1367 // can't delete a non-existent object
1368 messages::propertyValueNotInList(response->res, value,
1369 name);
1370 continue;
1371 }
1372
1373 std::string path;
1374 if (pathItr != managedObj.end())
1375 {
1376 path = pathItr->first.str;
1377 }
1378
1379 BMCWEB_LOG_DEBUG("Create new = {}", createNewObject);
1380
1381 // arbitrary limit to avoid attacks
1382 constexpr const size_t controllerLimit = 500;
1383 if (createNewObject && objectCount >= controllerLimit)
1384 {
1385 messages::resourceExhaustion(response->res, type);
1386 continue;
1387 }
1388 std::string escaped = name;
1389 std::ranges::replace(escaped, '_', ' ');
1390 output.emplace_back("Name", escaped);
1391
1392 std::string chassis;
1393 CreatePIDRet ret = createPidInterface(
1394 response, type, name, value, path, managedObj,
1395 createNewObject, output, chassis, currentProfile);
1396 if (ret == CreatePIDRet::fail)
1397 {
1398 return;
1399 }
1400 if (ret == CreatePIDRet::del)
1401 {
1402 continue;
1403 }
1404
1405 if (!createNewObject)
1406 {
1407 for (const auto& property : output)
1408 {
1409 dbus::utility::async_method_call(
1410 asyncResp,
1411 [response,
1412 propertyName{std::string(property.first)}](
1413 const boost::system::error_code& ec) {
1414 if (ec)
1415 {
1416 BMCWEB_LOG_ERROR("Error patching {}: {}",
1417 propertyName, ec);
1418 messages::internalError(response->res);
1419 return;
1420 }
1421 messages::success(response->res);
1422 },
1423 "xyz.openbmc_project.EntityManager", path,
1424 "org.freedesktop.DBus.Properties", "Set", iface,
1425 property.first, property.second);
1426 }
1427 }
1428 else
1429 {
1430 if (chassis.empty())
1431 {
1432 BMCWEB_LOG_ERROR("Failed to get chassis from config");
1433 messages::internalError(response->res);
1434 return;
1435 }
1436
1437 bool foundChassis = false;
1438 for (const auto& obj : managedObj)
1439 {
1440 if (obj.first.filename() == chassis)
1441 {
1442 chassis = obj.first.str;
1443 foundChassis = true;
1444 break;
1445 }
1446 }
1447 if (!foundChassis)
1448 {
1449 BMCWEB_LOG_ERROR("Failed to find chassis on dbus");
1450 messages::resourceMissingAtURI(
1451 response->res,
1452 boost::urls::format("/redfish/v1/Chassis/{}",
1453 chassis));
1454 return;
1455 }
1456
1457 dbus::utility::async_method_call(
1458 asyncResp,
1459 [response](const boost::system::error_code& ec) {
1460 if (ec)
1461 {
1462 BMCWEB_LOG_ERROR("Error Adding Pid Object {}",
1463 ec);
1464 messages::internalError(response->res);
1465 return;
1466 }
1467 messages::success(response->res);
1468 },
1469 "xyz.openbmc_project.EntityManager", chassis,
1470 "xyz.openbmc_project.AddObject", "AddObject", output);
1471 }
1472 }
1473 }
1474 }
1475
~SetPIDValuesredfish::SetPIDValues1476 ~SetPIDValues()
1477 {
1478 try
1479 {
1480 pidSetDone();
1481 }
1482 catch (...)
1483 {
1484 BMCWEB_LOG_CRITICAL("pidSetDone threw exception");
1485 }
1486 }
1487
1488 std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1489 std::vector<std::pair<std::string, std::optional<nlohmann::json::object_t>>>
1490 configuration;
1491 std::optional<std::string> profile;
1492 dbus::utility::ManagedObjectType managedObj;
1493 std::vector<std::string> supportedProfiles;
1494 std::string currentProfile;
1495 std::string profileConnection;
1496 std::string profilePath;
1497 size_t objectCount = 0;
1498 };
1499
handleGetManagerOpenBmc(const SubRequest &,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)1500 inline void handleGetManagerOpenBmc(
1501 const SubRequest& /*req*/,
1502 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1503 const std::string& /*managerId*/)
1504 {
1505 // Default OEM data
1506 nlohmann::json& oemOpenbmc = asyncResp->res.jsonValue;
1507 oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager";
1508 oemOpenbmc["@odata.id"] =
1509 boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc",
1510 BMCWEB_REDFISH_MANAGER_URI_NAME);
1511
1512 nlohmann::json::object_t certificates;
1513 certificates["@odata.id"] =
1514 boost::urls::format("/redfish/v1/Managers/{}/Truststore/Certificates",
1515 BMCWEB_REDFISH_MANAGER_URI_NAME);
1516 oemOpenbmc["Certificates"] = std::move(certificates);
1517
1518 if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
1519 {
1520 auto pids = std::make_shared<GetPIDValues>(asyncResp);
1521 pids->run();
1522 }
1523 }
1524
handlePatchManagerOpenBmc(const SubRequest & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)1525 inline void handlePatchManagerOpenBmc(
1526 const SubRequest& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1527 const std::string& /*managerId*/)
1528 {
1529 nlohmann::json::object_t payload = req.payload();
1530
1531 std::optional<nlohmann::json::object_t> pidControllers;
1532 std::optional<nlohmann::json::object_t> fanControllers;
1533 std::optional<nlohmann::json::object_t> fanZones;
1534 std::optional<nlohmann::json::object_t> stepwiseControllers;
1535 std::optional<std::string> profile;
1536
1537 if (!json_util::readJsonObject(
1538 payload, asyncResp->res, "OpenBmc/Fan/PidControllers",
1539 pidControllers, "OpenBmc/Fan/FanControllers", fanControllers,
1540 "OpenBmc/Fan/FanZones", fanZones, "OpenBmc/Fan/StepwiseControllers",
1541 stepwiseControllers, "OpenBmc/Fan/Profile", profile))
1542 {
1543 return;
1544 }
1545
1546 if (pidControllers || fanControllers || fanZones || stepwiseControllers ||
1547 profile)
1548 {
1549 if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
1550 {
1551 std::vector<
1552 std::pair<std::string, std::optional<nlohmann::json::object_t>>>
1553 configuration;
1554
1555 if (pidControllers)
1556 {
1557 configuration.emplace_back("PidControllers",
1558 std::move(pidControllers));
1559 }
1560 if (fanControllers)
1561 {
1562 configuration.emplace_back("FanControllers",
1563 std::move(fanControllers));
1564 }
1565 if (fanZones)
1566 {
1567 configuration.emplace_back("FanZones", std::move(fanZones));
1568 }
1569 if (stepwiseControllers)
1570 {
1571 configuration.emplace_back("StepwiseControllers",
1572 std::move(stepwiseControllers));
1573 }
1574
1575 auto pid = std::make_shared<SetPIDValues>(
1576 asyncResp, std::move(configuration), profile);
1577 pid->run();
1578 }
1579 else
1580 {
1581 messages::propertyUnknown(asyncResp->res, "Oem");
1582 return;
1583 }
1584 }
1585 }
1586
requestRoutesOpenBmcManager(RedfishService & service)1587 inline void requestRoutesOpenBmcManager(RedfishService& service)
1588 {
1589 REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/OpenBmc">(
1590 service, HttpVerb::Get)(handleGetManagerOpenBmc);
1591
1592 REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/OpenBmc">(
1593 service, HttpVerb::Patch)(handlePatchManagerOpenBmc);
1594 }
1595
1596 } // namespace redfish
1597