1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2019 Intel Corporation
4 #pragma once
5
6 #include "app.hpp"
7 #include "async_resp.hpp"
8 #include "error_messages.hpp"
9 #include "generated/enums/drive.hpp"
10 #include "generated/enums/protocol.hpp"
11 #include "generated/enums/resource.hpp"
12 #include "http_request.hpp"
13 #include "query.hpp"
14 #include "redfish_util.hpp"
15 #include "registries/privilege_registry.hpp"
16
17 namespace redfish
18 {
19
getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)20 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
21 const std::string& connectionName,
22 const std::string& path)
23 {
24 dbus::utility::getProperty<bool>(
25 connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present",
26 [asyncResp,
27 path](const boost::system::error_code& ec, const bool isPresent) {
28 // this interface isn't necessary, only check it if
29 // we get a good return
30 if (ec)
31 {
32 return;
33 }
34
35 if (!isPresent)
36 {
37 asyncResp->res.jsonValue["Status"]["State"] =
38 resource::State::Absent;
39 }
40 });
41 }
42
getDriveState(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)43 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
44 const std::string& connectionName,
45 const std::string& path)
46 {
47 dbus::utility::getProperty<bool>(
48 connectionName, path, "xyz.openbmc_project.State.Drive", "Rebuilding",
49 [asyncResp](const boost::system::error_code& ec, const bool updating) {
50 // this interface isn't necessary, only check it
51 // if we get a good return
52 if (ec)
53 {
54 return;
55 }
56
57 // updating and disabled in the backend shouldn't be
58 // able to be set at the same time, so we don't need
59 // to check for the race condition of these two
60 // calls
61 if (updating)
62 {
63 asyncResp->res.jsonValue["Status"]["State"] =
64 resource::State::Updating;
65 }
66 });
67 }
68
convertDriveType(std::string_view type)69 inline std::optional<drive::MediaType> convertDriveType(std::string_view type)
70 {
71 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
72 {
73 return drive::MediaType::HDD;
74 }
75 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
76 {
77 return drive::MediaType::SSD;
78 }
79 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown")
80 {
81 return std::nullopt;
82 }
83
84 return drive::MediaType::Invalid;
85 }
86
convertDriveProtocol(std::string_view proto)87 inline std::optional<protocol::Protocol> convertDriveProtocol(
88 std::string_view proto)
89 {
90 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
91 {
92 return protocol::Protocol::SAS;
93 }
94 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
95 {
96 return protocol::Protocol::SATA;
97 }
98 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
99 {
100 return protocol::Protocol::NVMe;
101 }
102 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
103 {
104 return protocol::Protocol::FC;
105 }
106 if (proto ==
107 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown")
108 {
109 return std::nullopt;
110 }
111
112 return protocol::Protocol::Invalid;
113 }
114
getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)115 inline void getDriveItemProperties(
116 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
117 const std::string& connectionName, const std::string& path)
118 {
119 dbus::utility::getAllProperties(
120 connectionName, path, "xyz.openbmc_project.Inventory.Item.Drive",
121 [asyncResp](const boost::system::error_code& ec,
122 const std::vector<
123 std::pair<std::string, dbus::utility::DbusVariantType>>&
124 propertiesList) {
125 if (ec)
126 {
127 // this interface isn't required
128 return;
129 }
130 const std::string* encryptionStatus = nullptr;
131 const bool* isLocked = nullptr;
132 for (const std::pair<std::string, dbus::utility::DbusVariantType>&
133 property : propertiesList)
134 {
135 const std::string& propertyName = property.first;
136 if (propertyName == "Type")
137 {
138 const std::string* value =
139 std::get_if<std::string>(&property.second);
140 if (value == nullptr)
141 {
142 // illegal property
143 BMCWEB_LOG_ERROR("Illegal property: Type");
144 messages::internalError(asyncResp->res);
145 return;
146 }
147
148 std::optional<drive::MediaType> mediaType =
149 convertDriveType(*value);
150 if (!mediaType)
151 {
152 BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}",
153 *value);
154 continue;
155 }
156 if (*mediaType == drive::MediaType::Invalid)
157 {
158 messages::internalError(asyncResp->res);
159 return;
160 }
161
162 asyncResp->res.jsonValue["MediaType"] = *mediaType;
163 }
164 else if (propertyName == "Capacity")
165 {
166 const uint64_t* capacity =
167 std::get_if<uint64_t>(&property.second);
168 if (capacity == nullptr)
169 {
170 BMCWEB_LOG_ERROR("Illegal property: Capacity");
171 messages::internalError(asyncResp->res);
172 return;
173 }
174 if (*capacity == 0)
175 {
176 // drive capacity not known
177 continue;
178 }
179
180 asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
181 }
182 else if (propertyName == "Protocol")
183 {
184 const std::string* value =
185 std::get_if<std::string>(&property.second);
186 if (value == nullptr)
187 {
188 BMCWEB_LOG_ERROR("Illegal property: Protocol");
189 messages::internalError(asyncResp->res);
190 return;
191 }
192
193 std::optional<protocol::Protocol> proto =
194 convertDriveProtocol(*value);
195 if (!proto)
196 {
197 BMCWEB_LOG_WARNING(
198 "Unknown DrivePrototype Interface: {}", *value);
199 continue;
200 }
201 if (*proto == protocol::Protocol::Invalid)
202 {
203 messages::internalError(asyncResp->res);
204 return;
205 }
206 asyncResp->res.jsonValue["Protocol"] = *proto;
207 }
208 else if (propertyName == "PredictedMediaLifeLeftPercent")
209 {
210 const uint8_t* lifeLeft =
211 std::get_if<uint8_t>(&property.second);
212 if (lifeLeft == nullptr)
213 {
214 BMCWEB_LOG_ERROR(
215 "Illegal property: PredictedMediaLifeLeftPercent");
216 messages::internalError(asyncResp->res);
217 return;
218 }
219 // 255 means reading the value is not supported
220 if (*lifeLeft != 255)
221 {
222 asyncResp->res
223 .jsonValue["PredictedMediaLifeLeftPercent"] =
224 *lifeLeft;
225 }
226 }
227 else if (propertyName == "EncryptionStatus")
228 {
229 encryptionStatus =
230 std::get_if<std::string>(&property.second);
231 if (encryptionStatus == nullptr)
232 {
233 BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus");
234 messages::internalError(asyncResp->res);
235 return;
236 }
237 }
238 else if (propertyName == "Locked")
239 {
240 isLocked = std::get_if<bool>(&property.second);
241 if (isLocked == nullptr)
242 {
243 BMCWEB_LOG_ERROR("Illegal property: Locked");
244 messages::internalError(asyncResp->res);
245 return;
246 }
247 }
248 }
249
250 if (encryptionStatus == nullptr || isLocked == nullptr ||
251 *encryptionStatus ==
252 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown")
253 {
254 return;
255 }
256 if (*encryptionStatus !=
257 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted")
258 {
259 //"The drive is not currently encrypted."
260 asyncResp->res.jsonValue["EncryptionStatus"] =
261 drive::EncryptionStatus::Unencrypted;
262 return;
263 }
264 if (*isLocked)
265 {
266 //"The drive is currently encrypted and the data is not
267 // accessible to the user."
268 asyncResp->res.jsonValue["EncryptionStatus"] =
269 drive::EncryptionStatus::Locked;
270 return;
271 }
272 // if not locked
273 // "The drive is currently encrypted but the data is accessible
274 // to the user in unencrypted form."
275 asyncResp->res.jsonValue["EncryptionStatus"] =
276 drive::EncryptionStatus::Unlocked;
277 });
278 }
279
addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path,const std::vector<std::string> & interfaces)280 inline void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
281 const std::string& connectionName,
282 const std::string& path,
283 const std::vector<std::string>& interfaces)
284 {
285 for (const std::string& interface : interfaces)
286 {
287 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
288 {
289 asset_utils::getAssetInfo(asyncResp, connectionName, path,
290 ""_json_pointer, false);
291 }
292 else if (interface == "xyz.openbmc_project.Inventory.Item")
293 {
294 getDrivePresent(asyncResp, connectionName, path);
295 }
296 else if (interface == "xyz.openbmc_project.State.Drive")
297 {
298 getDriveState(asyncResp, connectionName, path);
299 }
300 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive")
301 {
302 getDriveItemProperties(asyncResp, connectionName, path);
303 }
304 }
305 }
306
afterGetSubtreeSystemsStorageDrive(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & driveId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)307 inline void afterGetSubtreeSystemsStorageDrive(
308 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
309 const std::string& driveId, const boost::system::error_code& ec,
310 const dbus::utility::MapperGetSubTreeResponse& subtree)
311 {
312 if (ec)
313 {
314 BMCWEB_LOG_ERROR("Drive mapper call error");
315 messages::internalError(asyncResp->res);
316 return;
317 }
318
319 auto drive = std::ranges::find_if(
320 subtree,
321 [&driveId](const std::pair<std::string,
322 dbus::utility::MapperServiceMap>& object) {
323 return sdbusplus::message::object_path(object.first).filename() ==
324 driveId;
325 });
326
327 if (drive == subtree.end())
328 {
329 messages::resourceNotFound(asyncResp->res, "Drive", driveId);
330 return;
331 }
332
333 const std::string& path = drive->first;
334 const dbus::utility::MapperServiceMap& connectionNames = drive->second;
335
336 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
337 asyncResp->res.jsonValue["@odata.id"] =
338 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Drives/{}",
339 BMCWEB_REDFISH_SYSTEM_URI_NAME, driveId);
340 asyncResp->res.jsonValue["Name"] = driveId;
341 asyncResp->res.jsonValue["Id"] = driveId;
342
343 if (connectionNames.size() != 1)
344 {
345 BMCWEB_LOG_ERROR("Connection size {}, not equal to 1",
346 connectionNames.size());
347 messages::internalError(asyncResp->res);
348 return;
349 }
350
351 getMainChassisId(
352 asyncResp, [](const std::string& chassisId,
353 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
354 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] =
355 boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
356 });
357
358 // default it to Enabled
359 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
360
361 addAllDriveInfo(asyncResp, connectionNames[0].first, path,
362 connectionNames[0].second);
363 }
364
afterChassisDriveCollectionSubtreeGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)365 inline void afterChassisDriveCollectionSubtreeGet(
366 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
367 const std::string& chassisId, const boost::system::error_code& ec,
368 const dbus::utility::MapperGetSubTreeResponse& subtree)
369 {
370 if (ec)
371 {
372 if (ec == boost::system::errc::host_unreachable)
373 {
374 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
375 return;
376 }
377 messages::internalError(asyncResp->res);
378 return;
379 }
380
381 // Iterate over all retrieved ObjectPaths.
382 for (const auto& [path, connectionNames] : subtree)
383 {
384 sdbusplus::message::object_path objPath(path);
385 if (objPath.filename() != chassisId)
386 {
387 continue;
388 }
389
390 if (connectionNames.empty())
391 {
392 BMCWEB_LOG_ERROR("Got 0 Connection names");
393 continue;
394 }
395
396 asyncResp->res.jsonValue["@odata.type"] =
397 "#DriveCollection.DriveCollection";
398 asyncResp->res.jsonValue["@odata.id"] =
399 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId);
400 asyncResp->res.jsonValue["Name"] = "Drive Collection";
401
402 // Association lambda
403 dbus::utility::getAssociationEndPoints(
404 path + "/drive",
405 [asyncResp, chassisId](const boost::system::error_code& ec3,
406 const dbus::utility::MapperEndPoints& resp) {
407 if (ec3)
408 {
409 BMCWEB_LOG_ERROR("Error in chassis Drive association ");
410 }
411 nlohmann::json& members = asyncResp->res.jsonValue["Members"];
412 // important if array is empty
413 members = nlohmann::json::array();
414
415 std::vector<std::string> leafNames;
416 for (const auto& drive : resp)
417 {
418 sdbusplus::message::object_path drivePath(drive);
419 leafNames.push_back(drivePath.filename());
420 }
421
422 std::ranges::sort(leafNames, AlphanumLess<std::string>());
423
424 for (const auto& leafName : leafNames)
425 {
426 nlohmann::json::object_t member;
427 member["@odata.id"] =
428 boost::urls::format("/redfish/v1/Chassis/{}/Drives/{}",
429 chassisId, leafName);
430 members.emplace_back(std::move(member));
431 // navigation links will be registered in next patch set
432 }
433 asyncResp->res.jsonValue["Members@odata.count"] = resp.size();
434 }); // end association lambda
435
436 } // end Iterate over all retrieved ObjectPaths
437 }
438
439 /**
440 * Chassis drives, this URL will show all the DriveCollection
441 * information
442 */
chassisDriveCollectionGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)443 inline void chassisDriveCollectionGet(
444 crow::App& app, const crow::Request& req,
445 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
446 const std::string& chassisId)
447 {
448 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
449 {
450 return;
451 }
452
453 // mapper call lambda
454 dbus::utility::getSubTree(
455 "/xyz/openbmc_project/inventory", 0, chassisInterfaces,
456 std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp,
457 chassisId));
458 }
459
buildDrive(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & driveName,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)460 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
461 const std::string& chassisId,
462 const std::string& driveName,
463 const boost::system::error_code& ec,
464 const dbus::utility::MapperGetSubTreeResponse& subtree)
465 {
466 if (ec)
467 {
468 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
469 messages::internalError(asyncResp->res);
470 return;
471 }
472
473 // Iterate over all retrieved ObjectPaths.
474 for (const auto& [path, connectionNames] : subtree)
475 {
476 sdbusplus::message::object_path objPath(path);
477 if (objPath.filename() != driveName)
478 {
479 continue;
480 }
481
482 if (connectionNames.empty())
483 {
484 BMCWEB_LOG_ERROR("Got 0 Connection names");
485 continue;
486 }
487
488 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
489 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName);
490
491 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
492 asyncResp->res.jsonValue["Name"] = driveName;
493 asyncResp->res.jsonValue["Id"] = driveName;
494 // default it to Enabled
495 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
496
497 nlohmann::json::object_t linkChassisNav;
498 linkChassisNav["@odata.id"] =
499 boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
500 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
501
502 addAllDriveInfo(asyncResp, connectionNames[0].first, path,
503 connectionNames[0].second);
504 }
505 }
506
matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & driveName,const std::vector<std::string> & resp)507 inline void matchAndFillDrive(
508 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
509 const std::string& chassisId, const std::string& driveName,
510 const std::vector<std::string>& resp)
511 {
512 for (const std::string& drivePath : resp)
513 {
514 sdbusplus::message::object_path path(drivePath);
515 std::string leaf = path.filename();
516 if (leaf != driveName)
517 {
518 continue;
519 }
520 // mapper call drive
521 constexpr std::array<std::string_view, 1> driveInterface = {
522 "xyz.openbmc_project.Inventory.Item.Drive"};
523 dbus::utility::getSubTree(
524 "/xyz/openbmc_project/inventory", 0, driveInterface,
525 [asyncResp, chassisId, driveName](
526 const boost::system::error_code& ec,
527 const dbus::utility::MapperGetSubTreeResponse& subtree) {
528 buildDrive(asyncResp, chassisId, driveName, ec, subtree);
529 });
530 }
531 }
532
handleChassisDriveGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & driveName)533 inline void handleChassisDriveGet(
534 crow::App& app, const crow::Request& req,
535 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
536 const std::string& chassisId, const std::string& driveName)
537 {
538 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
539 {
540 return;
541 }
542
543 // mapper call chassis
544 dbus::utility::getSubTree(
545 "/xyz/openbmc_project/inventory", 0, chassisInterfaces,
546 [asyncResp, chassisId,
547 driveName](const boost::system::error_code& ec,
548 const dbus::utility::MapperGetSubTreeResponse& subtree) {
549 if (ec)
550 {
551 messages::internalError(asyncResp->res);
552 return;
553 }
554
555 // Iterate over all retrieved ObjectPaths.
556 for (const auto& [path, connectionNames] : subtree)
557 {
558 sdbusplus::message::object_path objPath(path);
559 if (objPath.filename() != chassisId)
560 {
561 continue;
562 }
563
564 if (connectionNames.empty())
565 {
566 BMCWEB_LOG_ERROR("Got 0 Connection names");
567 continue;
568 }
569
570 dbus::utility::getAssociationEndPoints(
571 path + "/drive",
572 [asyncResp, chassisId,
573 driveName](const boost::system::error_code& ec3,
574 const dbus::utility::MapperEndPoints& resp) {
575 if (ec3)
576 {
577 return; // no drives = no failures
578 }
579 matchAndFillDrive(asyncResp, chassisId, driveName,
580 resp);
581 });
582 return;
583 }
584 // Couldn't find an object with that name. return an error
585 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
586 });
587 }
588
589 /**
590 * This URL will show the drive interface for the specific drive in the chassis
591 */
requestRoutesChassisDrive(App & app)592 inline void requestRoutesChassisDrive(App& app)
593 {
594 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
595 .privileges(redfish::privileges::getDriveCollection)
596 .methods(boost::beast::http::verb::get)(
597 std::bind_front(chassisDriveCollectionGet, std::ref(app)));
598
599 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
600 .privileges(redfish::privileges::getChassis)
601 .methods(boost::beast::http::verb::get)(
602 std::bind_front(handleChassisDriveGet, std::ref(app)));
603 }
604
605 } // namespace redfish
606