1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4
5 #include "app.hpp"
6 #include "dbus_utility.hpp"
7 #include "generated/enums/resource.hpp"
8 #include "query.hpp"
9 #include "registries/privilege_registry.hpp"
10 #include "utils/chassis_utils.hpp"
11 #include "utils/dbus_utils.hpp"
12 #include "utils/json_utils.hpp"
13 #include "utils/time_utils.hpp"
14
15 #include <boost/system/error_code.hpp>
16 #include <boost/url/format.hpp>
17
18 #include <memory>
19 #include <optional>
20 #include <string>
21
22 namespace redfish
23 {
24
25 static constexpr std::array<std::string_view, 1> powerSupplyInterface = {
26 "xyz.openbmc_project.Inventory.Item.PowerSupply"};
27
updatePowerSupplyList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const dbus::utility::MapperGetSubTreePathsResponse & powerSupplyPaths)28 inline void updatePowerSupplyList(
29 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
30 const std::string& chassisId,
31 const dbus::utility::MapperGetSubTreePathsResponse& powerSupplyPaths)
32 {
33 nlohmann::json& powerSupplyList = asyncResp->res.jsonValue["Members"];
34 for (const std::string& powerSupplyPath : powerSupplyPaths)
35 {
36 std::string powerSupplyName =
37 sdbusplus::message::object_path(powerSupplyPath).filename();
38 if (powerSupplyName.empty())
39 {
40 continue;
41 }
42
43 nlohmann::json item = nlohmann::json::object();
44 item["@odata.id"] = boost::urls::format(
45 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
46 powerSupplyName);
47
48 powerSupplyList.emplace_back(std::move(item));
49 }
50 asyncResp->res.jsonValue["Members@odata.count"] = powerSupplyList.size();
51 }
52
doPowerSupplyCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & subtreePaths)53 inline void doPowerSupplyCollection(
54 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
55 const std::string& chassisId, const boost::system::error_code& ec,
56 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths)
57 {
58 if (ec)
59 {
60 if (ec.value() != EBADR)
61 {
62 BMCWEB_LOG_ERROR("DBUS response error{}", ec.value());
63 messages::internalError(asyncResp->res);
64 }
65 return;
66 }
67 asyncResp->res.addHeader(
68 boost::beast::http::field::link,
69 "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
70 asyncResp->res.jsonValue["@odata.type"] =
71 "#PowerSupplyCollection.PowerSupplyCollection";
72 asyncResp->res.jsonValue["Name"] = "Power Supply Collection";
73 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
74 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies", chassisId);
75 asyncResp->res.jsonValue["Description"] =
76 "The collection of PowerSupply resource instances.";
77 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
78 asyncResp->res.jsonValue["Members@odata.count"] = 0;
79
80 updatePowerSupplyList(asyncResp, chassisId, subtreePaths);
81 }
82
handlePowerSupplyCollectionHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)83 inline void handlePowerSupplyCollectionHead(
84 App& app, const crow::Request& req,
85 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
86 const std::string& chassisId)
87 {
88 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
89 {
90 return;
91 }
92
93 redfish::chassis_utils::getValidChassisPath(
94 asyncResp, chassisId,
95 [asyncResp,
96 chassisId](const std::optional<std::string>& validChassisPath) {
97 if (!validChassisPath)
98 {
99 messages::resourceNotFound(asyncResp->res, "Chassis",
100 chassisId);
101 return;
102 }
103 asyncResp->res.addHeader(
104 boost::beast::http::field::link,
105 "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
106 });
107 }
108
handlePowerSupplyCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)109 inline void handlePowerSupplyCollectionGet(
110 App& app, const crow::Request& req,
111 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
112 const std::string& chassisId)
113 {
114 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
115 {
116 return;
117 }
118
119 constexpr std::array<std::string_view, 2> chasisInterfaces = {
120 "xyz.openbmc_project.Inventory.Item.Board",
121 "xyz.openbmc_project.Inventory.Item.Chassis"};
122 const std::string reqpath = "/xyz/openbmc_project/inventory";
123
124 dbus::utility::getAssociatedSubTreePathsById(
125 chassisId, reqpath, chasisInterfaces, "powered_by",
126 powerSupplyInterface,
127 [asyncResp, chassisId](
128 const boost::system::error_code& ec,
129 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
130 doPowerSupplyCollection(asyncResp, chassisId, ec, subtreePaths);
131 });
132 }
133
requestRoutesPowerSupplyCollection(App & app)134 inline void requestRoutesPowerSupplyCollection(App& app)
135 {
136 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
137 .privileges(redfish::privileges::headPowerSupplyCollection)
138 .methods(boost::beast::http::verb::head)(
139 std::bind_front(handlePowerSupplyCollectionHead, std::ref(app)));
140
141 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
142 .privileges(redfish::privileges::getPowerSupplyCollection)
143 .methods(boost::beast::http::verb::get)(
144 std::bind_front(handlePowerSupplyCollectionGet, std::ref(app)));
145 }
146
afterGetValidPowerSupplyPath(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & powerSupplyId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree,const std::function<void (const std::string & powerSupplyPath,const std::string & service)> & callback)147 inline void afterGetValidPowerSupplyPath(
148 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
149 const std::string& powerSupplyId, const boost::system::error_code& ec,
150 const dbus::utility::MapperGetSubTreeResponse& subtree,
151 const std::function<void(const std::string& powerSupplyPath,
152 const std::string& service)>& callback)
153 {
154 if (ec)
155 {
156 if (ec.value() != EBADR)
157 {
158 BMCWEB_LOG_ERROR("DBUS response error{}", ec.value());
159 messages::internalError(asyncResp->res);
160 }
161 return;
162 }
163 for (const auto& [objectPath, service] : subtree)
164 {
165 sdbusplus::message::object_path path(objectPath);
166 if (path.filename() == powerSupplyId)
167 {
168 callback(path, service.begin()->first);
169 return;
170 }
171 }
172
173 BMCWEB_LOG_WARNING("Power supply not found: {}", powerSupplyId);
174 messages::resourceNotFound(asyncResp->res, "PowerSupplies", powerSupplyId);
175 }
176
getValidPowerSupplyPath(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & powerSupplyId,std::function<void (const std::string & powerSupplyPath,const std::string & service)> && callback)177 inline void getValidPowerSupplyPath(
178 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
179 const std::string& chassisId, const std::string& powerSupplyId,
180 std::function<void(const std::string& powerSupplyPath,
181 const std::string& service)>&& callback)
182 {
183 constexpr std::array<std::string_view, 2> chasisInterfaces = {
184 "xyz.openbmc_project.Inventory.Item.Board",
185 "xyz.openbmc_project.Inventory.Item.Chassis"};
186 const std::string reqpath = "/xyz/openbmc_project/inventory";
187
188 dbus::utility::getAssociatedSubTreeById(
189 chassisId, reqpath, chasisInterfaces, "powered_by",
190 powerSupplyInterface,
191 [asyncResp, chassisId, powerSupplyId, callback{std::move(callback)}](
192 const boost::system::error_code& ec,
193 const dbus::utility::MapperGetSubTreeResponse& subtree) {
194 afterGetValidPowerSupplyPath(asyncResp, powerSupplyId, ec, subtree,
195 callback);
196 });
197 }
198
199 inline void
getPowerSupplyState(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path)200 getPowerSupplyState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
201 const std::string& service, const std::string& path)
202 {
203 dbus::utility::getProperty<bool>(
204 service, path, "xyz.openbmc_project.Inventory.Item", "Present",
205 [asyncResp](const boost::system::error_code& ec, const bool value) {
206 if (ec)
207 {
208 if (ec.value() != EBADR)
209 {
210 BMCWEB_LOG_ERROR("DBUS response error for State {}",
211 ec.value());
212 messages::internalError(asyncResp->res);
213 }
214 return;
215 }
216
217 if (!value)
218 {
219 asyncResp->res.jsonValue["Status"]["State"] =
220 resource::State::Absent;
221 }
222 });
223 }
224
225 inline void
getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path)226 getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
227 const std::string& service, const std::string& path)
228 {
229 dbus::utility::getProperty<bool>(
230 service, path, "xyz.openbmc_project.State.Decorator.OperationalStatus",
231 "Functional",
232 [asyncResp](const boost::system::error_code& ec, const bool value) {
233 if (ec)
234 {
235 if (ec.value() != EBADR)
236 {
237 BMCWEB_LOG_ERROR("DBUS response error for Health {}",
238 ec.value());
239 messages::internalError(asyncResp->res);
240 }
241 return;
242 }
243
244 if (!value)
245 {
246 asyncResp->res.jsonValue["Status"]["Health"] =
247 resource::Health::Critical;
248 }
249 });
250 }
251
252 inline void
getPowerSupplyAsset(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path)253 getPowerSupplyAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
254 const std::string& service, const std::string& path)
255 {
256 dbus::utility::getAllProperties(
257 service, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
258 [asyncResp](const boost::system::error_code& ec,
259 const dbus::utility::DBusPropertiesMap& propertiesList) {
260 if (ec)
261 {
262 if (ec.value() != EBADR)
263 {
264 BMCWEB_LOG_ERROR("DBUS response error for Asset {}",
265 ec.value());
266 messages::internalError(asyncResp->res);
267 }
268 return;
269 }
270
271 const std::string* partNumber = nullptr;
272 const std::string* serialNumber = nullptr;
273 const std::string* manufacturer = nullptr;
274 const std::string* model = nullptr;
275 const std::string* sparePartNumber = nullptr;
276 const std::string* buildDate = nullptr;
277
278 const bool success = sdbusplus::unpackPropertiesNoThrow(
279 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
280 partNumber, "SerialNumber", serialNumber, "Manufacturer",
281 manufacturer, "Model", model, "SparePartNumber",
282 sparePartNumber, "BuildDate", buildDate);
283
284 if (!success)
285 {
286 messages::internalError(asyncResp->res);
287 return;
288 }
289
290 if (partNumber != nullptr)
291 {
292 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
293 }
294
295 if (serialNumber != nullptr)
296 {
297 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
298 }
299
300 if (manufacturer != nullptr)
301 {
302 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
303 }
304
305 if (model != nullptr)
306 {
307 asyncResp->res.jsonValue["Model"] = *model;
308 }
309
310 // SparePartNumber is optional on D-Bus so skip if it is empty
311 if (sparePartNumber != nullptr && !sparePartNumber->empty())
312 {
313 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
314 }
315
316 if (buildDate != nullptr)
317 {
318 time_utils::productionDateReport(asyncResp->res, *buildDate);
319 }
320 });
321 }
322
getPowerSupplyFirmwareVersion(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path)323 inline void getPowerSupplyFirmwareVersion(
324 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
325 const std::string& service, const std::string& path)
326 {
327 dbus::utility::getProperty<std::string>(
328 service, path, "xyz.openbmc_project.Software.Version", "Version",
329 [asyncResp](const boost::system::error_code& ec,
330 const std::string& value) {
331 if (ec)
332 {
333 if (ec.value() != EBADR)
334 {
335 BMCWEB_LOG_ERROR(
336 "DBUS response error for FirmwareVersion {}",
337 ec.value());
338 messages::internalError(asyncResp->res);
339 }
340 return;
341 }
342 asyncResp->res.jsonValue["FirmwareVersion"] = value;
343 });
344 }
345
346 inline void
getPowerSupplyLocation(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const std::string & path)347 getPowerSupplyLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
348 const std::string& service, const std::string& path)
349 {
350 dbus::utility::getProperty<std::string>(
351 service, path, "xyz.openbmc_project.Inventory.Decorator.LocationCode",
352 "LocationCode",
353 [asyncResp](const boost::system::error_code& ec,
354 const std::string& value) {
355 if (ec)
356 {
357 if (ec.value() != EBADR)
358 {
359 BMCWEB_LOG_ERROR("DBUS response error for Location {}",
360 ec.value());
361 messages::internalError(asyncResp->res);
362 }
363 return;
364 }
365 asyncResp->res
366 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = value;
367 });
368 }
369
handleGetEfficiencyResponse(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,uint32_t value)370 inline void handleGetEfficiencyResponse(
371 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
372 const boost::system::error_code& ec, uint32_t value)
373 {
374 if (ec)
375 {
376 if (ec.value() != EBADR)
377 {
378 BMCWEB_LOG_ERROR("DBUS response error for DeratingFactor {}",
379 ec.value());
380 messages::internalError(asyncResp->res);
381 }
382 return;
383 }
384 // The PDI default value is 0, if it hasn't been set leave off
385 if (value == 0)
386 {
387 return;
388 }
389
390 nlohmann::json::array_t efficiencyRatings;
391 nlohmann::json::object_t efficiencyPercent;
392 efficiencyPercent["EfficiencyPercent"] = value;
393 efficiencyRatings.emplace_back(std::move(efficiencyPercent));
394 asyncResp->res.jsonValue["EfficiencyRatings"] =
395 std::move(efficiencyRatings);
396 }
397
handlePowerSupplyAttributesSubTreeResponse(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)398 inline void handlePowerSupplyAttributesSubTreeResponse(
399 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
400 const boost::system::error_code& ec,
401 const dbus::utility::MapperGetSubTreeResponse& subtree)
402 {
403 if (ec)
404 {
405 if (ec.value() != EBADR)
406 {
407 BMCWEB_LOG_ERROR("DBUS response error for EfficiencyPercent {}",
408 ec.value());
409 messages::internalError(asyncResp->res);
410 }
411 return;
412 }
413
414 if (subtree.empty())
415 {
416 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!");
417 return;
418 }
419
420 if (subtree.size() != 1)
421 {
422 BMCWEB_LOG_ERROR(
423 "Unexpected number of paths returned by getSubTree: {}",
424 subtree.size());
425 messages::internalError(asyncResp->res);
426 return;
427 }
428
429 const auto& [path, serviceMap] = *subtree.begin();
430 const auto& [service, interfaces] = *serviceMap.begin();
431 dbus::utility::getProperty<uint32_t>(
432 service, path, "xyz.openbmc_project.Control.PowerSupplyAttributes",
433 "DeratingFactor",
434 [asyncResp](const boost::system::error_code& ec1, uint32_t value) {
435 handleGetEfficiencyResponse(asyncResp, ec1, value);
436 });
437 }
438
439 inline void
getEfficiencyPercent(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)440 getEfficiencyPercent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
441 {
442 constexpr std::array<std::string_view, 1> efficiencyIntf = {
443 "xyz.openbmc_project.Control.PowerSupplyAttributes"};
444
445 dbus::utility::getSubTree(
446 "/xyz/openbmc_project", 0, efficiencyIntf,
447 [asyncResp](const boost::system::error_code& ec,
448 const dbus::utility::MapperGetSubTreeResponse& subtree) {
449 handlePowerSupplyAttributesSubTreeResponse(asyncResp, ec, subtree);
450 });
451 }
452
doPowerSupplyGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & powerSupplyId,const std::string & powerSupplyPath,const std::string & service)453 inline void doPowerSupplyGet(
454 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
455 const std::string& chassisId, const std::string& powerSupplyId,
456 const std::string& powerSupplyPath, const std::string& service)
457 {
458 asyncResp->res.addHeader(
459 boost::beast::http::field::link,
460 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
461 asyncResp->res.jsonValue["@odata.type"] = "#PowerSupply.v1_5_0.PowerSupply";
462 asyncResp->res.jsonValue["Name"] = "Power Supply";
463 asyncResp->res.jsonValue["Id"] = powerSupplyId;
464 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
465 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
466 powerSupplyId);
467
468 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
469 asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
470
471 getPowerSupplyState(asyncResp, service, powerSupplyPath);
472 getPowerSupplyHealth(asyncResp, service, powerSupplyPath);
473 getPowerSupplyAsset(asyncResp, service, powerSupplyPath);
474 getPowerSupplyFirmwareVersion(asyncResp, service, powerSupplyPath);
475 getPowerSupplyLocation(asyncResp, service, powerSupplyPath);
476 getEfficiencyPercent(asyncResp);
477 }
478
handlePowerSupplyHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & powerSupplyId)479 inline void handlePowerSupplyHead(
480 App& app, const crow::Request& req,
481 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
482 const std::string& chassisId, const std::string& powerSupplyId)
483 {
484 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
485 {
486 return;
487 }
488
489 // Get the correct Path and Service that match the input parameters
490 getValidPowerSupplyPath(
491 asyncResp, chassisId, powerSupplyId,
492 [asyncResp](const std::string&, const std::string&) {
493 asyncResp->res.addHeader(
494 boost::beast::http::field::link,
495 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
496 });
497 }
498
handlePowerSupplyGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & powerSupplyId)499 inline void handlePowerSupplyGet(
500 App& app, const crow::Request& req,
501 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
502 const std::string& chassisId, const std::string& powerSupplyId)
503 {
504 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
505 {
506 return;
507 }
508 getValidPowerSupplyPath(
509 asyncResp, chassisId, powerSupplyId,
510 std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
511 }
512
requestRoutesPowerSupply(App & app)513 inline void requestRoutesPowerSupply(App& app)
514 {
515 BMCWEB_ROUTE(
516 app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
517 .privileges(redfish::privileges::headPowerSupply)
518 .methods(boost::beast::http::verb::head)(
519 std::bind_front(handlePowerSupplyHead, std::ref(app)));
520
521 BMCWEB_ROUTE(
522 app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
523 .privileges(redfish::privileges::getPowerSupply)
524 .methods(boost::beast::http::verb::get)(
525 std::bind_front(handlePowerSupplyGet, std::ref(app)));
526 }
527
528 } // namespace redfish
529