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