xref: /openbmc/bmcweb/redfish-core/lib/storage_chassis.hpp (revision 7bf29ab37ad696aed3f0580ef0f6cb90864c89ee)
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