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