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