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