xref: /openbmc/bmcweb/redfish-core/lib/storage.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
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 "bmcweb_config.h"
7 
8 #include "app.hpp"
9 #include "dbus_utility.hpp"
10 #include "generated/enums/drive.hpp"
11 #include "generated/enums/protocol.hpp"
12 #include "generated/enums/resource.hpp"
13 #include "human_sort.hpp"
14 #include "query.hpp"
15 #include "redfish_util.hpp"
16 #include "registries/privilege_registry.hpp"
17 #include "utils/collection.hpp"
18 #include "utils/dbus_utils.hpp"
19 
20 #include <boost/system/error_code.hpp>
21 #include <boost/url/format.hpp>
22 #include <sdbusplus/asio/property.hpp>
23 #include <sdbusplus/unpack_properties.hpp>
24 
25 #include <array>
26 #include <ranges>
27 #include <string_view>
28 
29 namespace redfish
30 {
31 
32 inline void handleSystemsStorageCollectionGet(
33     App& app, const crow::Request& req,
34     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
35     const std::string& systemName)
36 {
37     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
38     {
39         return;
40     }
41     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
42     {
43         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
44                                    systemName);
45         return;
46     }
47 
48     asyncResp->res.jsonValue["@odata.type"] =
49         "#StorageCollection.StorageCollection";
50     asyncResp->res.jsonValue["@odata.id"] = std::format(
51         "/redfish/v1/Systems/{}/Storage", BMCWEB_REDFISH_SYSTEM_URI_NAME);
52     asyncResp->res.jsonValue["Name"] = "Storage Collection";
53 
54     constexpr std::array<std::string_view, 1> interface{
55         "xyz.openbmc_project.Inventory.Item.Storage"};
56     collection_util::getCollectionMembers(
57         asyncResp,
58         boost::urls::format("/redfish/v1/Systems/{}/Storage",
59                             BMCWEB_REDFISH_SYSTEM_URI_NAME),
60         interface, "/xyz/openbmc_project/inventory");
61 }
62 
63 inline void handleStorageCollectionGet(
64     App& app, const crow::Request& req,
65     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
66 {
67     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
68     {
69         return;
70     }
71     asyncResp->res.jsonValue["@odata.type"] =
72         "#StorageCollection.StorageCollection";
73     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage";
74     asyncResp->res.jsonValue["Name"] = "Storage Collection";
75     constexpr std::array<std::string_view, 1> interface{
76         "xyz.openbmc_project.Inventory.Item.Storage"};
77     collection_util::getCollectionMembers(
78         asyncResp, boost::urls::format("/redfish/v1/Storage"), interface,
79         "/xyz/openbmc_project/inventory");
80 }
81 
82 inline void requestRoutesStorageCollection(App& app)
83 {
84     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/")
85         .privileges(redfish::privileges::getStorageCollection)
86         .methods(boost::beast::http::verb::get)(
87             std::bind_front(handleSystemsStorageCollectionGet, std::ref(app)));
88     BMCWEB_ROUTE(app, "/redfish/v1/Storage/")
89         .privileges(redfish::privileges::getStorageCollection)
90         .methods(boost::beast::http::verb::get)(
91             std::bind_front(handleStorageCollectionGet, std::ref(app)));
92 }
93 
94 inline void afterChassisDriveCollectionSubtree(
95     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
96     const boost::system::error_code& ec,
97     const dbus::utility::MapperGetSubTreePathsResponse& driveList)
98 {
99     if (ec)
100     {
101         BMCWEB_LOG_ERROR("Drive mapper call error");
102         messages::internalError(asyncResp->res);
103         return;
104     }
105 
106     nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"];
107     driveArray = nlohmann::json::array();
108     auto& count = asyncResp->res.jsonValue["Drives@odata.count"];
109     count = 0;
110 
111     for (const std::string& drive : driveList)
112     {
113         sdbusplus::message::object_path object(drive);
114         if (object.filename().empty())
115         {
116             BMCWEB_LOG_ERROR("Failed to find filename in {}", drive);
117             return;
118         }
119 
120         nlohmann::json::object_t driveJson;
121         driveJson["@odata.id"] = boost::urls::format(
122             "/redfish/v1/Systems/{}/Storage/1/Drives/{}",
123             BMCWEB_REDFISH_SYSTEM_URI_NAME, object.filename());
124         driveArray.emplace_back(std::move(driveJson));
125     }
126 
127     count = driveArray.size();
128 }
129 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
130 {
131     const std::array<std::string_view, 1> interfaces = {
132         "xyz.openbmc_project.Inventory.Item.Drive"};
133     dbus::utility::getSubTreePaths(
134         "/xyz/openbmc_project/inventory", 0, interfaces,
135         std::bind_front(afterChassisDriveCollectionSubtree, asyncResp));
136 }
137 
138 inline void afterSystemsStorageGetSubtree(
139     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
140     const std::string& storageId, const boost::system::error_code& ec,
141     const dbus::utility::MapperGetSubTreeResponse& subtree)
142 {
143     if (ec)
144     {
145         BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error");
146         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
147                                    storageId);
148         return;
149     }
150     auto storage = std::ranges::find_if(
151         subtree,
152         [&storageId](const std::pair<std::string,
153                                      dbus::utility::MapperServiceMap>& object) {
154             return sdbusplus::message::object_path(object.first).filename() ==
155                    storageId;
156         });
157     if (storage == subtree.end())
158     {
159         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
160                                    storageId);
161         return;
162     }
163 
164     asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
165     asyncResp->res.jsonValue["@odata.id"] =
166         boost::urls::format("/redfish/v1/Systems/{}/Storage/{}",
167                             BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
168     asyncResp->res.jsonValue["Name"] = "Storage";
169     asyncResp->res.jsonValue["Id"] = storageId;
170     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
171 
172     getDrives(asyncResp);
173     asyncResp->res.jsonValue["Controllers"]["@odata.id"] =
174         boost::urls::format("/redfish/v1/Systems/{}/Storage/{}/Controllers",
175                             BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
176 }
177 
178 inline void handleSystemsStorageGet(
179     App& app, const crow::Request& req,
180     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
181     const std::string& systemName, const std::string& storageId)
182 {
183     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
184     {
185         return;
186     }
187     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
188     {
189         // Option currently returns no systems.  TBD
190         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
191                                    systemName);
192         return;
193     }
194 
195     constexpr std::array<std::string_view, 1> interfaces = {
196         "xyz.openbmc_project.Inventory.Item.Storage"};
197     dbus::utility::getSubTree(
198         "/xyz/openbmc_project/inventory", 0, interfaces,
199         std::bind_front(afterSystemsStorageGetSubtree, asyncResp, storageId));
200 }
201 
202 inline void afterSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
203                          const std::string& storageId,
204                          const boost::system::error_code& ec,
205                          const dbus::utility::MapperGetSubTreeResponse& subtree)
206 {
207     if (ec)
208     {
209         BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error");
210         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
211                                    storageId);
212         return;
213     }
214     auto storage = std::ranges::find_if(
215         subtree,
216         [&storageId](const std::pair<std::string,
217                                      dbus::utility::MapperServiceMap>& object) {
218             return sdbusplus::message::object_path(object.first).filename() ==
219                    storageId;
220         });
221     if (storage == subtree.end())
222     {
223         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
224                                    storageId);
225         return;
226     }
227 
228     asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
229     asyncResp->res.jsonValue["@odata.id"] =
230         boost::urls::format("/redfish/v1/Storage/{}", storageId);
231     asyncResp->res.jsonValue["Name"] = "Storage";
232     asyncResp->res.jsonValue["Id"] = storageId;
233     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
234 
235     // Storage subsystem to Storage link.
236     nlohmann::json::array_t storageServices;
237     nlohmann::json::object_t storageService;
238     storageService["@odata.id"] =
239         boost::urls::format("/redfish/v1/Systems/{}/Storage/{}",
240                             BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
241     storageServices.emplace_back(storageService);
242     asyncResp->res.jsonValue["Links"]["StorageServices"] =
243         std::move(storageServices);
244     asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = 1;
245 }
246 
247 inline void
248     handleStorageGet(App& app, const crow::Request& req,
249                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
250                      const std::string& storageId)
251 {
252     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
253     {
254         BMCWEB_LOG_DEBUG("requestRoutesStorage setUpRedfishRoute failed");
255         return;
256     }
257 
258     constexpr std::array<std::string_view, 1> interfaces = {
259         "xyz.openbmc_project.Inventory.Item.Storage"};
260     dbus::utility::getSubTree(
261         "/xyz/openbmc_project/inventory", 0, interfaces,
262         std::bind_front(afterSubtree, asyncResp, storageId));
263 }
264 
265 inline void requestRoutesStorage(App& app)
266 {
267     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/")
268         .privileges(redfish::privileges::getStorage)
269         .methods(boost::beast::http::verb::get)(
270             std::bind_front(handleSystemsStorageGet, std::ref(app)));
271 
272     BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/")
273         .privileges(redfish::privileges::getStorage)
274         .methods(boost::beast::http::verb::get)(
275             std::bind_front(handleStorageGet, std::ref(app)));
276 }
277 
278 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
279                           const std::string& connectionName,
280                           const std::string& path)
281 {
282     dbus::utility::getAllProperties(
283         connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
284         [asyncResp](const boost::system::error_code& ec,
285                     const std::vector<
286                         std::pair<std::string, dbus::utility::DbusVariantType>>&
287                         propertiesList) {
288             if (ec)
289             {
290                 // this interface isn't necessary
291                 return;
292             }
293 
294             const std::string* partNumber = nullptr;
295             const std::string* serialNumber = nullptr;
296             const std::string* manufacturer = nullptr;
297             const std::string* model = nullptr;
298 
299             const bool success = sdbusplus::unpackPropertiesNoThrow(
300                 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
301                 partNumber, "SerialNumber", serialNumber, "Manufacturer",
302                 manufacturer, "Model", model);
303 
304             if (!success)
305             {
306                 messages::internalError(asyncResp->res);
307                 return;
308             }
309 
310             if (partNumber != nullptr)
311             {
312                 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
313             }
314 
315             if (serialNumber != nullptr)
316             {
317                 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
318             }
319 
320             if (manufacturer != nullptr)
321             {
322                 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
323             }
324 
325             if (model != nullptr)
326             {
327                 asyncResp->res.jsonValue["Model"] = *model;
328             }
329         });
330 }
331 
332 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
333                             const std::string& connectionName,
334                             const std::string& path)
335 {
336     dbus::utility::getProperty<bool>(
337         connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present",
338         [asyncResp,
339          path](const boost::system::error_code& ec, const bool isPresent) {
340             // this interface isn't necessary, only check it if
341             // we get a good return
342             if (ec)
343             {
344                 return;
345             }
346 
347             if (!isPresent)
348             {
349                 asyncResp->res.jsonValue["Status"]["State"] =
350                     resource::State::Absent;
351             }
352         });
353 }
354 
355 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
356                           const std::string& connectionName,
357                           const std::string& path)
358 {
359     dbus::utility::getProperty<bool>(
360         connectionName, path, "xyz.openbmc_project.State.Drive", "Rebuilding",
361         [asyncResp](const boost::system::error_code& ec, const bool updating) {
362             // this interface isn't necessary, only check it
363             // if we get a good return
364             if (ec)
365             {
366                 return;
367             }
368 
369             // updating and disabled in the backend shouldn't be
370             // able to be set at the same time, so we don't need
371             // to check for the race condition of these two
372             // calls
373             if (updating)
374             {
375                 asyncResp->res.jsonValue["Status"]["State"] =
376                     resource::State::Updating;
377             }
378         });
379 }
380 
381 inline std::optional<drive::MediaType> convertDriveType(std::string_view type)
382 {
383     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
384     {
385         return drive::MediaType::HDD;
386     }
387     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
388     {
389         return drive::MediaType::SSD;
390     }
391     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown")
392     {
393         return std::nullopt;
394     }
395 
396     return drive::MediaType::Invalid;
397 }
398 
399 inline std::optional<protocol::Protocol>
400     convertDriveProtocol(std::string_view proto)
401 {
402     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
403     {
404         return protocol::Protocol::SAS;
405     }
406     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
407     {
408         return protocol::Protocol::SATA;
409     }
410     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
411     {
412         return protocol::Protocol::NVMe;
413     }
414     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
415     {
416         return protocol::Protocol::FC;
417     }
418     if (proto ==
419         "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown")
420     {
421         return std::nullopt;
422     }
423 
424     return protocol::Protocol::Invalid;
425 }
426 
427 inline void getDriveItemProperties(
428     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
429     const std::string& connectionName, const std::string& path)
430 {
431     dbus::utility::getAllProperties(
432         connectionName, path, "xyz.openbmc_project.Inventory.Item.Drive",
433         [asyncResp](const boost::system::error_code& ec,
434                     const std::vector<
435                         std::pair<std::string, dbus::utility::DbusVariantType>>&
436                         propertiesList) {
437             if (ec)
438             {
439                 // this interface isn't required
440                 return;
441             }
442             const std::string* encryptionStatus = nullptr;
443             const bool* isLocked = nullptr;
444             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
445                      property : propertiesList)
446             {
447                 const std::string& propertyName = property.first;
448                 if (propertyName == "Type")
449                 {
450                     const std::string* value =
451                         std::get_if<std::string>(&property.second);
452                     if (value == nullptr)
453                     {
454                         // illegal property
455                         BMCWEB_LOG_ERROR("Illegal property: Type");
456                         messages::internalError(asyncResp->res);
457                         return;
458                     }
459 
460                     std::optional<drive::MediaType> mediaType =
461                         convertDriveType(*value);
462                     if (!mediaType)
463                     {
464                         BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}",
465                                            *value);
466                         continue;
467                     }
468                     if (*mediaType == drive::MediaType::Invalid)
469                     {
470                         messages::internalError(asyncResp->res);
471                         return;
472                     }
473 
474                     asyncResp->res.jsonValue["MediaType"] = *mediaType;
475                 }
476                 else if (propertyName == "Capacity")
477                 {
478                     const uint64_t* capacity =
479                         std::get_if<uint64_t>(&property.second);
480                     if (capacity == nullptr)
481                     {
482                         BMCWEB_LOG_ERROR("Illegal property: Capacity");
483                         messages::internalError(asyncResp->res);
484                         return;
485                     }
486                     if (*capacity == 0)
487                     {
488                         // drive capacity not known
489                         continue;
490                     }
491 
492                     asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
493                 }
494                 else if (propertyName == "Protocol")
495                 {
496                     const std::string* value =
497                         std::get_if<std::string>(&property.second);
498                     if (value == nullptr)
499                     {
500                         BMCWEB_LOG_ERROR("Illegal property: Protocol");
501                         messages::internalError(asyncResp->res);
502                         return;
503                     }
504 
505                     std::optional<protocol::Protocol> proto =
506                         convertDriveProtocol(*value);
507                     if (!proto)
508                     {
509                         BMCWEB_LOG_WARNING(
510                             "Unknown DrivePrototype Interface: {}", *value);
511                         continue;
512                     }
513                     if (*proto == protocol::Protocol::Invalid)
514                     {
515                         messages::internalError(asyncResp->res);
516                         return;
517                     }
518                     asyncResp->res.jsonValue["Protocol"] = *proto;
519                 }
520                 else if (propertyName == "PredictedMediaLifeLeftPercent")
521                 {
522                     const uint8_t* lifeLeft =
523                         std::get_if<uint8_t>(&property.second);
524                     if (lifeLeft == nullptr)
525                     {
526                         BMCWEB_LOG_ERROR(
527                             "Illegal property: PredictedMediaLifeLeftPercent");
528                         messages::internalError(asyncResp->res);
529                         return;
530                     }
531                     // 255 means reading the value is not supported
532                     if (*lifeLeft != 255)
533                     {
534                         asyncResp->res
535                             .jsonValue["PredictedMediaLifeLeftPercent"] =
536                             *lifeLeft;
537                     }
538                 }
539                 else if (propertyName == "EncryptionStatus")
540                 {
541                     encryptionStatus =
542                         std::get_if<std::string>(&property.second);
543                     if (encryptionStatus == nullptr)
544                     {
545                         BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus");
546                         messages::internalError(asyncResp->res);
547                         return;
548                     }
549                 }
550                 else if (propertyName == "Locked")
551                 {
552                     isLocked = std::get_if<bool>(&property.second);
553                     if (isLocked == nullptr)
554                     {
555                         BMCWEB_LOG_ERROR("Illegal property: Locked");
556                         messages::internalError(asyncResp->res);
557                         return;
558                     }
559                 }
560             }
561 
562             if (encryptionStatus == nullptr || isLocked == nullptr ||
563                 *encryptionStatus ==
564                     "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown")
565             {
566                 return;
567             }
568             if (*encryptionStatus !=
569                 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted")
570             {
571                 //"The drive is not currently encrypted."
572                 asyncResp->res.jsonValue["EncryptionStatus"] =
573                     drive::EncryptionStatus::Unencrypted;
574                 return;
575             }
576             if (*isLocked)
577             {
578                 //"The drive is currently encrypted and the data is not
579                 // accessible to the user."
580                 asyncResp->res.jsonValue["EncryptionStatus"] =
581                     drive::EncryptionStatus::Locked;
582                 return;
583             }
584             // if not locked
585             // "The drive is currently encrypted but the data is accessible
586             // to the user in unencrypted form."
587             asyncResp->res.jsonValue["EncryptionStatus"] =
588                 drive::EncryptionStatus::Unlocked;
589         });
590 }
591 
592 inline void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
593                             const std::string& connectionName,
594                             const std::string& path,
595                             const std::vector<std::string>& interfaces)
596 {
597     for (const std::string& interface : interfaces)
598     {
599         if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
600         {
601             getDriveAsset(asyncResp, connectionName, path);
602         }
603         else if (interface == "xyz.openbmc_project.Inventory.Item")
604         {
605             getDrivePresent(asyncResp, connectionName, path);
606         }
607         else if (interface == "xyz.openbmc_project.State.Drive")
608         {
609             getDriveState(asyncResp, connectionName, path);
610         }
611         else if (interface == "xyz.openbmc_project.Inventory.Item.Drive")
612         {
613             getDriveItemProperties(asyncResp, connectionName, path);
614         }
615     }
616 }
617 
618 inline void afterGetSubtreeSystemsStorageDrive(
619     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
620     const std::string& driveId, const boost::system::error_code& ec,
621     const dbus::utility::MapperGetSubTreeResponse& subtree)
622 {
623     if (ec)
624     {
625         BMCWEB_LOG_ERROR("Drive mapper call error");
626         messages::internalError(asyncResp->res);
627         return;
628     }
629 
630     auto drive = std::ranges::find_if(
631         subtree,
632         [&driveId](const std::pair<std::string,
633                                    dbus::utility::MapperServiceMap>& object) {
634             return sdbusplus::message::object_path(object.first).filename() ==
635                    driveId;
636         });
637 
638     if (drive == subtree.end())
639     {
640         messages::resourceNotFound(asyncResp->res, "Drive", driveId);
641         return;
642     }
643 
644     const std::string& path = drive->first;
645     const dbus::utility::MapperServiceMap& connectionNames = drive->second;
646 
647     asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
648     asyncResp->res.jsonValue["@odata.id"] =
649         boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Drives/{}",
650                             BMCWEB_REDFISH_SYSTEM_URI_NAME, driveId);
651     asyncResp->res.jsonValue["Name"] = driveId;
652     asyncResp->res.jsonValue["Id"] = driveId;
653 
654     if (connectionNames.size() != 1)
655     {
656         BMCWEB_LOG_ERROR("Connection size {}, not equal to 1",
657                          connectionNames.size());
658         messages::internalError(asyncResp->res);
659         return;
660     }
661 
662     getMainChassisId(
663         asyncResp, [](const std::string& chassisId,
664                       const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
665             aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] =
666                 boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
667         });
668 
669     // default it to Enabled
670     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
671 
672     addAllDriveInfo(asyncResp, connectionNames[0].first, path,
673                     connectionNames[0].second);
674 }
675 
676 inline void handleSystemsStorageDriveGet(
677     App& app, const crow::Request& req,
678     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
679     const std::string& systemName, const std::string& driveId)
680 {
681     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
682     {
683         return;
684     }
685     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
686     {
687         // Option currently returns no systems.  TBD
688         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
689                                    systemName);
690         return;
691     }
692 
693     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
694     {
695         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
696                                    systemName);
697         return;
698     }
699 
700     constexpr std::array<std::string_view, 1> interfaces = {
701         "xyz.openbmc_project.Inventory.Item.Drive"};
702     dbus::utility::getSubTree(
703         "/xyz/openbmc_project/inventory", 0, interfaces,
704         std::bind_front(afterGetSubtreeSystemsStorageDrive, asyncResp,
705                         driveId));
706 }
707 
708 inline void requestRoutesDrive(App& app)
709 {
710     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/")
711         .privileges(redfish::privileges::getDrive)
712         .methods(boost::beast::http::verb::get)(
713             std::bind_front(handleSystemsStorageDriveGet, std::ref(app)));
714 }
715 
716 inline void afterChassisDriveCollectionSubtreeGet(
717     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
718     const std::string& chassisId, const boost::system::error_code& ec,
719     const dbus::utility::MapperGetSubTreeResponse& subtree)
720 {
721     if (ec)
722     {
723         if (ec == boost::system::errc::host_unreachable)
724         {
725             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
726             return;
727         }
728         messages::internalError(asyncResp->res);
729         return;
730     }
731 
732     // Iterate over all retrieved ObjectPaths.
733     for (const auto& [path, connectionNames] : subtree)
734     {
735         sdbusplus::message::object_path objPath(path);
736         if (objPath.filename() != chassisId)
737         {
738             continue;
739         }
740 
741         if (connectionNames.empty())
742         {
743             BMCWEB_LOG_ERROR("Got 0 Connection names");
744             continue;
745         }
746 
747         asyncResp->res.jsonValue["@odata.type"] =
748             "#DriveCollection.DriveCollection";
749         asyncResp->res.jsonValue["@odata.id"] =
750             boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId);
751         asyncResp->res.jsonValue["Name"] = "Drive Collection";
752 
753         // Association lambda
754         dbus::utility::getAssociationEndPoints(
755             path + "/drive",
756             [asyncResp, chassisId](const boost::system::error_code& ec3,
757                                    const dbus::utility::MapperEndPoints& resp) {
758                 if (ec3)
759                 {
760                     BMCWEB_LOG_ERROR("Error in chassis Drive association ");
761                 }
762                 nlohmann::json& members = asyncResp->res.jsonValue["Members"];
763                 // important if array is empty
764                 members = nlohmann::json::array();
765 
766                 std::vector<std::string> leafNames;
767                 for (const auto& drive : resp)
768                 {
769                     sdbusplus::message::object_path drivePath(drive);
770                     leafNames.push_back(drivePath.filename());
771                 }
772 
773                 std::ranges::sort(leafNames, AlphanumLess<std::string>());
774 
775                 for (const auto& leafName : leafNames)
776                 {
777                     nlohmann::json::object_t member;
778                     member["@odata.id"] =
779                         boost::urls::format("/redfish/v1/Chassis/{}/Drives/{}",
780                                             chassisId, leafName);
781                     members.emplace_back(std::move(member));
782                     // navigation links will be registered in next patch set
783                 }
784                 asyncResp->res.jsonValue["Members@odata.count"] = resp.size();
785             }); // end association lambda
786 
787     } // end Iterate over all retrieved ObjectPaths
788 }
789 /**
790  * Chassis drives, this URL will show all the DriveCollection
791  * information
792  */
793 inline void chassisDriveCollectionGet(
794     crow::App& app, const crow::Request& req,
795     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
796     const std::string& chassisId)
797 {
798     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
799     {
800         return;
801     }
802 
803     // mapper call lambda
804     constexpr std::array<std::string_view, 2> interfaces = {
805         "xyz.openbmc_project.Inventory.Item.Board",
806         "xyz.openbmc_project.Inventory.Item.Chassis"};
807     dbus::utility::getSubTree(
808         "/xyz/openbmc_project/inventory", 0, interfaces,
809         std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp,
810                         chassisId));
811 }
812 
813 inline void requestRoutesChassisDrive(App& app)
814 {
815     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
816         .privileges(redfish::privileges::getDriveCollection)
817         .methods(boost::beast::http::verb::get)(
818             std::bind_front(chassisDriveCollectionGet, std::ref(app)));
819 }
820 
821 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
822                        const std::string& chassisId,
823                        const std::string& driveName,
824                        const boost::system::error_code& ec,
825                        const dbus::utility::MapperGetSubTreeResponse& subtree)
826 {
827     if (ec)
828     {
829         BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
830         messages::internalError(asyncResp->res);
831         return;
832     }
833 
834     // Iterate over all retrieved ObjectPaths.
835     for (const auto& [path, connectionNames] : subtree)
836     {
837         sdbusplus::message::object_path objPath(path);
838         if (objPath.filename() != driveName)
839         {
840             continue;
841         }
842 
843         if (connectionNames.empty())
844         {
845             BMCWEB_LOG_ERROR("Got 0 Connection names");
846             continue;
847         }
848 
849         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
850             "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName);
851 
852         asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
853         asyncResp->res.jsonValue["Name"] = driveName;
854         asyncResp->res.jsonValue["Id"] = driveName;
855         // default it to Enabled
856         asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
857 
858         nlohmann::json::object_t linkChassisNav;
859         linkChassisNav["@odata.id"] =
860             boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
861         asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
862 
863         addAllDriveInfo(asyncResp, connectionNames[0].first, path,
864                         connectionNames[0].second);
865     }
866 }
867 
868 inline void matchAndFillDrive(
869     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
870     const std::string& chassisId, const std::string& driveName,
871     const std::vector<std::string>& resp)
872 {
873     for (const std::string& drivePath : resp)
874     {
875         sdbusplus::message::object_path path(drivePath);
876         std::string leaf = path.filename();
877         if (leaf != driveName)
878         {
879             continue;
880         }
881         //  mapper call drive
882         constexpr std::array<std::string_view, 1> driveInterface = {
883             "xyz.openbmc_project.Inventory.Item.Drive"};
884         dbus::utility::getSubTree(
885             "/xyz/openbmc_project/inventory", 0, driveInterface,
886             [asyncResp, chassisId, driveName](
887                 const boost::system::error_code& ec,
888                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
889                 buildDrive(asyncResp, chassisId, driveName, ec, subtree);
890             });
891     }
892 }
893 
894 inline void handleChassisDriveGet(
895     crow::App& app, const crow::Request& req,
896     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
897     const std::string& chassisId, const std::string& driveName)
898 {
899     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
900     {
901         return;
902     }
903     constexpr std::array<std::string_view, 2> interfaces = {
904         "xyz.openbmc_project.Inventory.Item.Board",
905         "xyz.openbmc_project.Inventory.Item.Chassis"};
906 
907     // mapper call chassis
908     dbus::utility::getSubTree(
909         "/xyz/openbmc_project/inventory", 0, interfaces,
910         [asyncResp, chassisId,
911          driveName](const boost::system::error_code& ec,
912                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
913             if (ec)
914             {
915                 messages::internalError(asyncResp->res);
916                 return;
917             }
918 
919             // Iterate over all retrieved ObjectPaths.
920             for (const auto& [path, connectionNames] : subtree)
921             {
922                 sdbusplus::message::object_path objPath(path);
923                 if (objPath.filename() != chassisId)
924                 {
925                     continue;
926                 }
927 
928                 if (connectionNames.empty())
929                 {
930                     BMCWEB_LOG_ERROR("Got 0 Connection names");
931                     continue;
932                 }
933 
934                 dbus::utility::getAssociationEndPoints(
935                     path + "/drive",
936                     [asyncResp, chassisId,
937                      driveName](const boost::system::error_code& ec3,
938                                 const dbus::utility::MapperEndPoints& resp) {
939                         if (ec3)
940                         {
941                             return; // no drives = no failures
942                         }
943                         matchAndFillDrive(asyncResp, chassisId, driveName,
944                                           resp);
945                     });
946                 break;
947             }
948         });
949 }
950 
951 /**
952  * This URL will show the drive interface for the specific drive in the chassis
953  */
954 inline void requestRoutesChassisDriveName(App& app)
955 {
956     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
957         .privileges(redfish::privileges::getChassis)
958         .methods(boost::beast::http::verb::get)(
959             std::bind_front(handleChassisDriveGet, std::ref(app)));
960 }
961 
962 inline void getStorageControllerAsset(
963     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
964     const boost::system::error_code& ec,
965     const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
966         propertiesList)
967 {
968     if (ec)
969     {
970         // this interface isn't necessary
971         BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset");
972         return;
973     }
974 
975     const std::string* partNumber = nullptr;
976     const std::string* serialNumber = nullptr;
977     const std::string* manufacturer = nullptr;
978     const std::string* model = nullptr;
979     if (!sdbusplus::unpackPropertiesNoThrow(
980             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
981             partNumber, "SerialNumber", serialNumber, "Manufacturer",
982             manufacturer, "Model", model))
983     {
984         messages::internalError(asyncResp->res);
985         return;
986     }
987 
988     if (partNumber != nullptr)
989     {
990         asyncResp->res.jsonValue["PartNumber"] = *partNumber;
991     }
992 
993     if (serialNumber != nullptr)
994     {
995         asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
996     }
997 
998     if (manufacturer != nullptr)
999     {
1000         asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
1001     }
1002 
1003     if (model != nullptr)
1004     {
1005         asyncResp->res.jsonValue["Model"] = *model;
1006     }
1007 }
1008 
1009 inline void populateStorageController(
1010     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1011     const std::string& controllerId, const std::string& connectionName,
1012     const std::string& path)
1013 {
1014     asyncResp->res.jsonValue["@odata.type"] =
1015         "#StorageController.v1_6_0.StorageController";
1016     asyncResp->res.jsonValue["@odata.id"] =
1017         boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}",
1018                             BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId);
1019     asyncResp->res.jsonValue["Name"] = controllerId;
1020     asyncResp->res.jsonValue["Id"] = controllerId;
1021     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
1022 
1023     dbus::utility::getProperty<bool>(
1024         connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present",
1025         [asyncResp](const boost::system::error_code& ec, bool isPresent) {
1026             // this interface isn't necessary, only check it
1027             // if we get a good return
1028             if (ec)
1029             {
1030                 BMCWEB_LOG_DEBUG("Failed to get Present property");
1031                 return;
1032             }
1033             if (!isPresent)
1034             {
1035                 asyncResp->res.jsonValue["Status"]["State"] =
1036                     resource::State::Absent;
1037             }
1038         });
1039 
1040     dbus::utility::getAllProperties(
1041         connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
1042         [asyncResp](const boost::system::error_code& ec,
1043                     const std::vector<
1044                         std::pair<std::string, dbus::utility::DbusVariantType>>&
1045                         propertiesList) {
1046             getStorageControllerAsset(asyncResp, ec, propertiesList);
1047         });
1048 }
1049 
1050 inline void getStorageControllerHandler(
1051     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1052     const std::string& controllerId, const boost::system::error_code& ec,
1053     const dbus::utility::MapperGetSubTreeResponse& subtree)
1054 {
1055     if (ec || subtree.empty())
1056     {
1057         // doesn't have to be there
1058         BMCWEB_LOG_DEBUG("Failed to handle StorageController");
1059         return;
1060     }
1061 
1062     for (const auto& [path, interfaceDict] : subtree)
1063     {
1064         sdbusplus::message::object_path object(path);
1065         std::string id = object.filename();
1066         if (id.empty())
1067         {
1068             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1069             return;
1070         }
1071         if (id != controllerId)
1072         {
1073             continue;
1074         }
1075 
1076         if (interfaceDict.size() != 1)
1077         {
1078             BMCWEB_LOG_ERROR("Connection size {}, greater than 1",
1079                              interfaceDict.size());
1080             messages::internalError(asyncResp->res);
1081             return;
1082         }
1083 
1084         const std::string& connectionName = interfaceDict.front().first;
1085         populateStorageController(asyncResp, controllerId, connectionName,
1086                                   path);
1087     }
1088 }
1089 
1090 inline void populateStorageControllerCollection(
1091     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1092     const boost::system::error_code& ec,
1093     const dbus::utility::MapperGetSubTreePathsResponse& controllerList)
1094 {
1095     nlohmann::json::array_t members;
1096     if (ec || controllerList.empty())
1097     {
1098         asyncResp->res.jsonValue["Members"] = std::move(members);
1099         asyncResp->res.jsonValue["Members@odata.count"] = 0;
1100         BMCWEB_LOG_DEBUG("Failed to find any StorageController");
1101         return;
1102     }
1103 
1104     for (const std::string& path : controllerList)
1105     {
1106         std::string id = sdbusplus::message::object_path(path).filename();
1107         if (id.empty())
1108         {
1109             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1110             return;
1111         }
1112         nlohmann::json::object_t member;
1113         member["@odata.id"] = boost::urls::format(
1114             "/redfish/v1/Systems/{}/Storage/1/Controllers/{}",
1115             BMCWEB_REDFISH_SYSTEM_URI_NAME, id);
1116         members.emplace_back(member);
1117     }
1118     asyncResp->res.jsonValue["Members@odata.count"] = members.size();
1119     asyncResp->res.jsonValue["Members"] = std::move(members);
1120 }
1121 
1122 inline void handleSystemsStorageControllerCollectionGet(
1123     App& app, const crow::Request& req,
1124     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1125     const std::string& systemName)
1126 {
1127     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1128     {
1129         BMCWEB_LOG_DEBUG(
1130             "Failed to setup Redfish Route for StorageController Collection");
1131         return;
1132     }
1133     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1134     {
1135         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1136                                    systemName);
1137         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1138         return;
1139     }
1140 
1141     asyncResp->res.jsonValue["@odata.type"] =
1142         "#StorageControllerCollection.StorageControllerCollection";
1143     asyncResp->res.jsonValue["@odata.id"] =
1144         std::format("/redfish/v1/Systems/{}/Storage/1/Controllers",
1145                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
1146     asyncResp->res.jsonValue["Name"] = "Storage Controller Collection";
1147 
1148     constexpr std::array<std::string_view, 1> interfaces = {
1149         "xyz.openbmc_project.Inventory.Item.StorageController"};
1150     dbus::utility::getSubTreePaths(
1151         "/xyz/openbmc_project/inventory", 0, interfaces,
1152         [asyncResp](const boost::system::error_code& ec,
1153                     const dbus::utility::MapperGetSubTreePathsResponse&
1154                         controllerList) {
1155             populateStorageControllerCollection(asyncResp, ec, controllerList);
1156         });
1157 }
1158 
1159 inline void handleSystemsStorageControllerGet(
1160     App& app, const crow::Request& req,
1161     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1162     const std::string& systemName, const std::string& controllerId)
1163 {
1164     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1165     {
1166         BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController");
1167         return;
1168     }
1169     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1170     {
1171         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1172                                    systemName);
1173         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1174         return;
1175     }
1176     constexpr std::array<std::string_view, 1> interfaces = {
1177         "xyz.openbmc_project.Inventory.Item.StorageController"};
1178     dbus::utility::getSubTree(
1179         "/xyz/openbmc_project/inventory", 0, interfaces,
1180         [asyncResp,
1181          controllerId](const boost::system::error_code& ec,
1182                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
1183             getStorageControllerHandler(asyncResp, controllerId, ec, subtree);
1184         });
1185 }
1186 
1187 inline void requestRoutesStorageControllerCollection(App& app)
1188 {
1189     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/")
1190         .privileges(redfish::privileges::getStorageControllerCollection)
1191         .methods(boost::beast::http::verb::get)(std::bind_front(
1192             handleSystemsStorageControllerCollectionGet, std::ref(app)));
1193 }
1194 
1195 inline void requestRoutesStorageController(App& app)
1196 {
1197     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>")
1198         .privileges(redfish::privileges::getStorageController)
1199         .methods(boost::beast::http::verb::get)(
1200             std::bind_front(handleSystemsStorageControllerGet, std::ref(app)));
1201 }
1202 
1203 } // namespace redfish
1204