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