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