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