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