xref: /openbmc/bmcweb/redfish-core/lib/storage.hpp (revision 45de6126)
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 "generated/enums/resource.hpp"
25 #include "human_sort.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"] = resource::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"] = resource::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"] =
365                 resource::State::Absent;
366         }
367     });
368 }
369 
370 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
371                           const std::string& connectionName,
372                           const std::string& path)
373 {
374     sdbusplus::asio::getProperty<bool>(
375         *crow::connections::systemBus, connectionName, path,
376         "xyz.openbmc_project.State.Drive", "Rebuilding",
377         [asyncResp](const boost::system::error_code& ec, const bool updating) {
378         // this interface isn't necessary, only check it
379         // if we get a good return
380         if (ec)
381         {
382             return;
383         }
384 
385         // updating and disabled in the backend shouldn't be
386         // able to be set at the same time, so we don't need
387         // to check for the race condition of these two
388         // calls
389         if (updating)
390         {
391             asyncResp->res.jsonValue["Status"]["State"] =
392                 resource::State::Updating;
393         }
394     });
395 }
396 
397 inline std::optional<drive::MediaType> convertDriveType(std::string_view type)
398 {
399     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
400     {
401         return drive::MediaType::HDD;
402     }
403     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
404     {
405         return drive::MediaType::SSD;
406     }
407     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown")
408     {
409         return std::nullopt;
410     }
411 
412     return drive::MediaType::Invalid;
413 }
414 
415 inline std::optional<protocol::Protocol>
416     convertDriveProtocol(std::string_view proto)
417 {
418     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
419     {
420         return protocol::Protocol::SAS;
421     }
422     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
423     {
424         return protocol::Protocol::SATA;
425     }
426     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
427     {
428         return protocol::Protocol::NVMe;
429     }
430     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
431     {
432         return protocol::Protocol::FC;
433     }
434     if (proto ==
435         "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown")
436     {
437         return std::nullopt;
438     }
439 
440     return protocol::Protocol::Invalid;
441 }
442 
443 inline void
444     getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
445                            const std::string& connectionName,
446                            const std::string& path)
447 {
448     sdbusplus::asio::getAllProperties(
449         *crow::connections::systemBus, connectionName, path,
450         "xyz.openbmc_project.Inventory.Item.Drive",
451         [asyncResp](const boost::system::error_code& ec,
452                     const std::vector<
453                         std::pair<std::string, dbus::utility::DbusVariantType>>&
454                         propertiesList) {
455         if (ec)
456         {
457             // this interface isn't required
458             return;
459         }
460         const std::string* encryptionStatus = nullptr;
461         const bool* isLocked = nullptr;
462         for (const std::pair<std::string, dbus::utility::DbusVariantType>&
463                  property : propertiesList)
464         {
465             const std::string& propertyName = property.first;
466             if (propertyName == "Type")
467             {
468                 const std::string* value =
469                     std::get_if<std::string>(&property.second);
470                 if (value == nullptr)
471                 {
472                     // illegal property
473                     BMCWEB_LOG_ERROR("Illegal property: Type");
474                     messages::internalError(asyncResp->res);
475                     return;
476                 }
477 
478                 std::optional<drive::MediaType> mediaType =
479                     convertDriveType(*value);
480                 if (!mediaType)
481                 {
482                     BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}",
483                                        *value);
484                     continue;
485                 }
486                 if (*mediaType == drive::MediaType::Invalid)
487                 {
488                     messages::internalError(asyncResp->res);
489                     return;
490                 }
491 
492                 asyncResp->res.jsonValue["MediaType"] = *mediaType;
493             }
494             else if (propertyName == "Capacity")
495             {
496                 const uint64_t* capacity =
497                     std::get_if<uint64_t>(&property.second);
498                 if (capacity == nullptr)
499                 {
500                     BMCWEB_LOG_ERROR("Illegal property: Capacity");
501                     messages::internalError(asyncResp->res);
502                     return;
503                 }
504                 if (*capacity == 0)
505                 {
506                     // drive capacity not known
507                     continue;
508                 }
509 
510                 asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
511             }
512             else if (propertyName == "Protocol")
513             {
514                 const std::string* value =
515                     std::get_if<std::string>(&property.second);
516                 if (value == nullptr)
517                 {
518                     BMCWEB_LOG_ERROR("Illegal property: Protocol");
519                     messages::internalError(asyncResp->res);
520                     return;
521                 }
522 
523                 std::optional<protocol::Protocol> proto =
524                     convertDriveProtocol(*value);
525                 if (!proto)
526                 {
527                     BMCWEB_LOG_WARNING("Unknown DrivePrototype Interface: {}",
528                                        *value);
529                     continue;
530                 }
531                 if (*proto == protocol::Protocol::Invalid)
532                 {
533                     messages::internalError(asyncResp->res);
534                     return;
535                 }
536                 asyncResp->res.jsonValue["Protocol"] = *proto;
537             }
538             else if (propertyName == "PredictedMediaLifeLeftPercent")
539             {
540                 const uint8_t* lifeLeft =
541                     std::get_if<uint8_t>(&property.second);
542                 if (lifeLeft == nullptr)
543                 {
544                     BMCWEB_LOG_ERROR(
545                         "Illegal property: PredictedMediaLifeLeftPercent");
546                     messages::internalError(asyncResp->res);
547                     return;
548                 }
549                 // 255 means reading the value is not supported
550                 if (*lifeLeft != 255)
551                 {
552                     asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] =
553                         *lifeLeft;
554                 }
555             }
556             else if (propertyName == "EncryptionStatus")
557             {
558                 encryptionStatus = std::get_if<std::string>(&property.second);
559                 if (encryptionStatus == nullptr)
560                 {
561                     BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus");
562                     messages::internalError(asyncResp->res);
563                     return;
564                 }
565             }
566             else if (propertyName == "Locked")
567             {
568                 isLocked = std::get_if<bool>(&property.second);
569                 if (isLocked == nullptr)
570                 {
571                     BMCWEB_LOG_ERROR("Illegal property: Locked");
572                     messages::internalError(asyncResp->res);
573                     return;
574                 }
575             }
576         }
577 
578         if (encryptionStatus == nullptr || isLocked == nullptr ||
579             *encryptionStatus ==
580                 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown")
581         {
582             return;
583         }
584         if (*encryptionStatus !=
585             "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted")
586         {
587             //"The drive is not currently encrypted."
588             asyncResp->res.jsonValue["EncryptionStatus"] =
589                 drive::EncryptionStatus::Unencrypted;
590             return;
591         }
592         if (*isLocked)
593         {
594             //"The drive is currently encrypted and the data is not
595             // accessible to the user."
596             asyncResp->res.jsonValue["EncryptionStatus"] =
597                 drive::EncryptionStatus::Locked;
598             return;
599         }
600         // if not locked
601         // "The drive is currently encrypted but the data is accessible
602         // to the user in unencrypted form."
603         asyncResp->res.jsonValue["EncryptionStatus"] =
604             drive::EncryptionStatus::Unlocked;
605     });
606 }
607 
608 static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
609                             const std::string& connectionName,
610                             const std::string& path,
611                             const std::vector<std::string>& interfaces)
612 {
613     for (const std::string& interface : interfaces)
614     {
615         if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
616         {
617             getDriveAsset(asyncResp, connectionName, path);
618         }
619         else if (interface == "xyz.openbmc_project.Inventory.Item")
620         {
621             getDrivePresent(asyncResp, connectionName, path);
622         }
623         else if (interface == "xyz.openbmc_project.State.Drive")
624         {
625             getDriveState(asyncResp, connectionName, path);
626         }
627         else if (interface == "xyz.openbmc_project.Inventory.Item.Drive")
628         {
629             getDriveItemProperties(asyncResp, connectionName, path);
630         }
631     }
632 }
633 
634 inline void afterGetSubtreeSystemsStorageDrive(
635     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
636     const std::string& driveId, const boost::system::error_code& ec,
637     const dbus::utility::MapperGetSubTreeResponse& subtree)
638 {
639     if (ec)
640     {
641         BMCWEB_LOG_ERROR("Drive mapper call error");
642         messages::internalError(asyncResp->res);
643         return;
644     }
645 
646     auto drive = std::ranges::find_if(
647         subtree,
648         [&driveId](const std::pair<std::string,
649                                    dbus::utility::MapperServiceMap>& object) {
650         return sdbusplus::message::object_path(object.first).filename() ==
651                driveId;
652     });
653 
654     if (drive == subtree.end())
655     {
656         messages::resourceNotFound(asyncResp->res, "Drive", driveId);
657         return;
658     }
659 
660     const std::string& path = drive->first;
661     const dbus::utility::MapperServiceMap& connectionNames = drive->second;
662 
663     asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
664     asyncResp->res.jsonValue["@odata.id"] =
665         boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Drives/{}",
666                             BMCWEB_REDFISH_SYSTEM_URI_NAME, driveId);
667     asyncResp->res.jsonValue["Name"] = driveId;
668     asyncResp->res.jsonValue["Id"] = driveId;
669 
670     if (connectionNames.size() != 1)
671     {
672         BMCWEB_LOG_ERROR("Connection size {}, not equal to 1",
673                          connectionNames.size());
674         messages::internalError(asyncResp->res);
675         return;
676     }
677 
678     getMainChassisId(asyncResp,
679                      [](const std::string& chassisId,
680                         const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
681         aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] =
682             boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
683     });
684 
685     // default it to Enabled
686     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
687 
688     addAllDriveInfo(asyncResp, connectionNames[0].first, path,
689                     connectionNames[0].second);
690 }
691 
692 inline void handleSystemsStorageDriveGet(
693     App& app, const crow::Request& req,
694     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
695     const std::string& systemName, const std::string& driveId)
696 {
697     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
698     {
699         return;
700     }
701     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
702     {
703         // Option currently returns no systems.  TBD
704         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
705                                    systemName);
706         return;
707     }
708 
709     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
710     {
711         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
712                                    systemName);
713         return;
714     }
715 
716     constexpr std::array<std::string_view, 1> interfaces = {
717         "xyz.openbmc_project.Inventory.Item.Drive"};
718     dbus::utility::getSubTree(
719         "/xyz/openbmc_project/inventory", 0, interfaces,
720         std::bind_front(afterGetSubtreeSystemsStorageDrive, asyncResp,
721                         driveId));
722 }
723 
724 inline void requestRoutesDrive(App& app)
725 {
726     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/")
727         .privileges(redfish::privileges::getDrive)
728         .methods(boost::beast::http::verb::get)(
729             std::bind_front(handleSystemsStorageDriveGet, std::ref(app)));
730 }
731 
732 inline void afterChassisDriveCollectionSubtreeGet(
733     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
734     const std::string& chassisId, const boost::system::error_code& ec,
735     const dbus::utility::MapperGetSubTreeResponse& subtree)
736 {
737     if (ec)
738     {
739         if (ec == boost::system::errc::host_unreachable)
740         {
741             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
742             return;
743         }
744         messages::internalError(asyncResp->res);
745         return;
746     }
747 
748     // Iterate over all retrieved ObjectPaths.
749     for (const auto& [path, connectionNames] : subtree)
750     {
751         sdbusplus::message::object_path objPath(path);
752         if (objPath.filename() != chassisId)
753         {
754             continue;
755         }
756 
757         if (connectionNames.empty())
758         {
759             BMCWEB_LOG_ERROR("Got 0 Connection names");
760             continue;
761         }
762 
763         asyncResp->res.jsonValue["@odata.type"] =
764             "#DriveCollection.DriveCollection";
765         asyncResp->res.jsonValue["@odata.id"] =
766             boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId);
767         asyncResp->res.jsonValue["Name"] = "Drive Collection";
768 
769         // Association lambda
770         dbus::utility::getAssociationEndPoints(
771             path + "/drive",
772             [asyncResp, chassisId](const boost::system::error_code& ec3,
773                                    const dbus::utility::MapperEndPoints& resp) {
774             if (ec3)
775             {
776                 BMCWEB_LOG_ERROR("Error in chassis Drive association ");
777             }
778             nlohmann::json& members = asyncResp->res.jsonValue["Members"];
779             // important if array is empty
780             members = nlohmann::json::array();
781 
782             std::vector<std::string> leafNames;
783             for (const auto& drive : resp)
784             {
785                 sdbusplus::message::object_path drivePath(drive);
786                 leafNames.push_back(drivePath.filename());
787             }
788 
789             std::ranges::sort(leafNames, AlphanumLess<std::string>());
790 
791             for (const auto& leafName : leafNames)
792             {
793                 nlohmann::json::object_t member;
794                 member["@odata.id"] = boost::urls::format(
795                     "/redfish/v1/Chassis/{}/Drives/{}", chassisId, leafName);
796                 members.emplace_back(std::move(member));
797                 // navigation links will be registered in next patch set
798             }
799             asyncResp->res.jsonValue["Members@odata.count"] = resp.size();
800         }); // end association lambda
801 
802     }       // end Iterate over all retrieved ObjectPaths
803 }
804 /**
805  * Chassis drives, this URL will show all the DriveCollection
806  * information
807  */
808 inline void chassisDriveCollectionGet(
809     crow::App& app, const crow::Request& req,
810     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
811     const std::string& chassisId)
812 {
813     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
814     {
815         return;
816     }
817 
818     // mapper call lambda
819     constexpr std::array<std::string_view, 2> interfaces = {
820         "xyz.openbmc_project.Inventory.Item.Board",
821         "xyz.openbmc_project.Inventory.Item.Chassis"};
822     dbus::utility::getSubTree(
823         "/xyz/openbmc_project/inventory", 0, interfaces,
824         std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp,
825                         chassisId));
826 }
827 
828 inline void requestRoutesChassisDrive(App& app)
829 {
830     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
831         .privileges(redfish::privileges::getDriveCollection)
832         .methods(boost::beast::http::verb::get)(
833             std::bind_front(chassisDriveCollectionGet, std::ref(app)));
834 }
835 
836 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
837                        const std::string& chassisId,
838                        const std::string& driveName,
839                        const boost::system::error_code& ec,
840                        const dbus::utility::MapperGetSubTreeResponse& subtree)
841 {
842     if (ec)
843     {
844         BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
845         messages::internalError(asyncResp->res);
846         return;
847     }
848 
849     // Iterate over all retrieved ObjectPaths.
850     for (const auto& [path, connectionNames] : subtree)
851     {
852         sdbusplus::message::object_path objPath(path);
853         if (objPath.filename() != driveName)
854         {
855             continue;
856         }
857 
858         if (connectionNames.empty())
859         {
860             BMCWEB_LOG_ERROR("Got 0 Connection names");
861             continue;
862         }
863 
864         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
865             "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName);
866 
867         asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
868         asyncResp->res.jsonValue["Name"] = driveName;
869         asyncResp->res.jsonValue["Id"] = driveName;
870         // default it to Enabled
871         asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
872 
873         nlohmann::json::object_t linkChassisNav;
874         linkChassisNav["@odata.id"] =
875             boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
876         asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
877 
878         addAllDriveInfo(asyncResp, connectionNames[0].first, path,
879                         connectionNames[0].second);
880     }
881 }
882 
883 inline void
884     matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
885                       const std::string& chassisId,
886                       const std::string& driveName,
887                       const std::vector<std::string>& resp)
888 {
889     for (const std::string& drivePath : resp)
890     {
891         sdbusplus::message::object_path path(drivePath);
892         std::string leaf = path.filename();
893         if (leaf != driveName)
894         {
895             continue;
896         }
897         //  mapper call drive
898         constexpr std::array<std::string_view, 1> driveInterface = {
899             "xyz.openbmc_project.Inventory.Item.Drive"};
900         dbus::utility::getSubTree(
901             "/xyz/openbmc_project/inventory", 0, driveInterface,
902             [asyncResp, chassisId, driveName](
903                 const boost::system::error_code& ec,
904                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
905             buildDrive(asyncResp, chassisId, driveName, ec, subtree);
906         });
907     }
908 }
909 
910 inline void
911     handleChassisDriveGet(crow::App& app, const crow::Request& req,
912                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
913                           const std::string& chassisId,
914                           const std::string& driveName)
915 {
916     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
917     {
918         return;
919     }
920     constexpr std::array<std::string_view, 2> interfaces = {
921         "xyz.openbmc_project.Inventory.Item.Board",
922         "xyz.openbmc_project.Inventory.Item.Chassis"};
923 
924     // mapper call chassis
925     dbus::utility::getSubTree(
926         "/xyz/openbmc_project/inventory", 0, interfaces,
927         [asyncResp, chassisId,
928          driveName](const boost::system::error_code& ec,
929                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
930         if (ec)
931         {
932             messages::internalError(asyncResp->res);
933             return;
934         }
935 
936         // Iterate over all retrieved ObjectPaths.
937         for (const auto& [path, connectionNames] : subtree)
938         {
939             sdbusplus::message::object_path objPath(path);
940             if (objPath.filename() != chassisId)
941             {
942                 continue;
943             }
944 
945             if (connectionNames.empty())
946             {
947                 BMCWEB_LOG_ERROR("Got 0 Connection names");
948                 continue;
949             }
950 
951             dbus::utility::getAssociationEndPoints(
952                 path + "/drive",
953                 [asyncResp, chassisId,
954                  driveName](const boost::system::error_code& ec3,
955                             const dbus::utility::MapperEndPoints& resp) {
956                 if (ec3)
957                 {
958                     return; // no drives = no failures
959                 }
960                 matchAndFillDrive(asyncResp, chassisId, driveName, resp);
961             });
962             break;
963         }
964     });
965 }
966 
967 /**
968  * This URL will show the drive interface for the specific drive in the chassis
969  */
970 inline void requestRoutesChassisDriveName(App& app)
971 {
972     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
973         .privileges(redfish::privileges::getChassis)
974         .methods(boost::beast::http::verb::get)(
975             std::bind_front(handleChassisDriveGet, std::ref(app)));
976 }
977 
978 inline void getStorageControllerAsset(
979     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
980     const boost::system::error_code& ec,
981     const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
982         propertiesList)
983 {
984     if (ec)
985     {
986         // this interface isn't necessary
987         BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset");
988         return;
989     }
990 
991     const std::string* partNumber = nullptr;
992     const std::string* serialNumber = nullptr;
993     const std::string* manufacturer = nullptr;
994     const std::string* model = nullptr;
995     if (!sdbusplus::unpackPropertiesNoThrow(
996             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
997             partNumber, "SerialNumber", serialNumber, "Manufacturer",
998             manufacturer, "Model", model))
999     {
1000         messages::internalError(asyncResp->res);
1001         return;
1002     }
1003 
1004     if (partNumber != nullptr)
1005     {
1006         asyncResp->res.jsonValue["PartNumber"] = *partNumber;
1007     }
1008 
1009     if (serialNumber != nullptr)
1010     {
1011         asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
1012     }
1013 
1014     if (manufacturer != nullptr)
1015     {
1016         asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
1017     }
1018 
1019     if (model != nullptr)
1020     {
1021         asyncResp->res.jsonValue["Model"] = *model;
1022     }
1023 }
1024 
1025 inline void populateStorageController(
1026     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1027     const std::string& controllerId, const std::string& connectionName,
1028     const std::string& path)
1029 {
1030     asyncResp->res.jsonValue["@odata.type"] =
1031         "#StorageController.v1_6_0.StorageController";
1032     asyncResp->res.jsonValue["@odata.id"] =
1033         boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}",
1034                             BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId);
1035     asyncResp->res.jsonValue["Name"] = controllerId;
1036     asyncResp->res.jsonValue["Id"] = controllerId;
1037     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
1038 
1039     sdbusplus::asio::getProperty<bool>(
1040         *crow::connections::systemBus, connectionName, path,
1041         "xyz.openbmc_project.Inventory.Item", "Present",
1042         [asyncResp](const boost::system::error_code& ec, bool isPresent) {
1043         // this interface isn't necessary, only check it
1044         // if we get a good return
1045         if (ec)
1046         {
1047             BMCWEB_LOG_DEBUG("Failed to get Present property");
1048             return;
1049         }
1050         if (!isPresent)
1051         {
1052             asyncResp->res.jsonValue["Status"]["State"] =
1053                 resource::State::Absent;
1054         }
1055     });
1056 
1057     sdbusplus::asio::getAllProperties(
1058         *crow::connections::systemBus, connectionName, path,
1059         "xyz.openbmc_project.Inventory.Decorator.Asset",
1060         [asyncResp](const boost::system::error_code& ec,
1061                     const std::vector<
1062                         std::pair<std::string, dbus::utility::DbusVariantType>>&
1063                         propertiesList) {
1064         getStorageControllerAsset(asyncResp, ec, propertiesList);
1065     });
1066 }
1067 
1068 inline void getStorageControllerHandler(
1069     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1070     const std::string& controllerId, const boost::system::error_code& ec,
1071     const dbus::utility::MapperGetSubTreeResponse& subtree)
1072 {
1073     if (ec || subtree.empty())
1074     {
1075         // doesn't have to be there
1076         BMCWEB_LOG_DEBUG("Failed to handle StorageController");
1077         return;
1078     }
1079 
1080     for (const auto& [path, interfaceDict] : subtree)
1081     {
1082         sdbusplus::message::object_path object(path);
1083         std::string id = object.filename();
1084         if (id.empty())
1085         {
1086             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1087             return;
1088         }
1089         if (id != controllerId)
1090         {
1091             continue;
1092         }
1093 
1094         if (interfaceDict.size() != 1)
1095         {
1096             BMCWEB_LOG_ERROR("Connection size {}, greater than 1",
1097                              interfaceDict.size());
1098             messages::internalError(asyncResp->res);
1099             return;
1100         }
1101 
1102         const std::string& connectionName = interfaceDict.front().first;
1103         populateStorageController(asyncResp, controllerId, connectionName,
1104                                   path);
1105     }
1106 }
1107 
1108 inline void populateStorageControllerCollection(
1109     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1110     const boost::system::error_code& ec,
1111     const dbus::utility::MapperGetSubTreePathsResponse& controllerList)
1112 {
1113     nlohmann::json::array_t members;
1114     if (ec || controllerList.empty())
1115     {
1116         asyncResp->res.jsonValue["Members"] = std::move(members);
1117         asyncResp->res.jsonValue["Members@odata.count"] = 0;
1118         BMCWEB_LOG_DEBUG("Failed to find any StorageController");
1119         return;
1120     }
1121 
1122     for (const std::string& path : controllerList)
1123     {
1124         std::string id = sdbusplus::message::object_path(path).filename();
1125         if (id.empty())
1126         {
1127             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1128             return;
1129         }
1130         nlohmann::json::object_t member;
1131         member["@odata.id"] = boost::urls::format(
1132             "/redfish/v1/Systems/{}/Storage/1/Controllers/{}",
1133             BMCWEB_REDFISH_SYSTEM_URI_NAME, id);
1134         members.emplace_back(member);
1135     }
1136     asyncResp->res.jsonValue["Members@odata.count"] = members.size();
1137     asyncResp->res.jsonValue["Members"] = std::move(members);
1138 }
1139 
1140 inline void handleSystemsStorageControllerCollectionGet(
1141     App& app, const crow::Request& req,
1142     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1143     const std::string& systemName)
1144 {
1145     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1146     {
1147         BMCWEB_LOG_DEBUG(
1148             "Failed to setup Redfish Route for StorageController Collection");
1149         return;
1150     }
1151     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1152     {
1153         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1154                                    systemName);
1155         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1156         return;
1157     }
1158 
1159     asyncResp->res.jsonValue["@odata.type"] =
1160         "#StorageControllerCollection.StorageControllerCollection";
1161     asyncResp->res.jsonValue["@odata.id"] =
1162         std::format("/redfish/v1/Systems/{}/Storage/1/Controllers",
1163                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
1164     asyncResp->res.jsonValue["Name"] = "Storage Controller Collection";
1165 
1166     constexpr std::array<std::string_view, 1> interfaces = {
1167         "xyz.openbmc_project.Inventory.Item.StorageController"};
1168     dbus::utility::getSubTreePaths(
1169         "/xyz/openbmc_project/inventory", 0, interfaces,
1170         [asyncResp](const boost::system::error_code& ec,
1171                     const dbus::utility::MapperGetSubTreePathsResponse&
1172                         controllerList) {
1173         populateStorageControllerCollection(asyncResp, ec, controllerList);
1174     });
1175 }
1176 
1177 inline void handleSystemsStorageControllerGet(
1178     App& app, const crow::Request& req,
1179     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1180     const std::string& systemName, const std::string& controllerId)
1181 {
1182     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1183     {
1184         BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController");
1185         return;
1186     }
1187     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1188     {
1189         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1190                                    systemName);
1191         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1192         return;
1193     }
1194     constexpr std::array<std::string_view, 1> interfaces = {
1195         "xyz.openbmc_project.Inventory.Item.StorageController"};
1196     dbus::utility::getSubTree(
1197         "/xyz/openbmc_project/inventory", 0, interfaces,
1198         [asyncResp,
1199          controllerId](const boost::system::error_code& ec,
1200                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
1201         getStorageControllerHandler(asyncResp, controllerId, ec, subtree);
1202     });
1203 }
1204 
1205 inline void requestRoutesStorageControllerCollection(App& app)
1206 {
1207     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/")
1208         .privileges(redfish::privileges::getStorageControllerCollection)
1209         .methods(boost::beast::http::verb::get)(std::bind_front(
1210             handleSystemsStorageControllerCollectionGet, std::ref(app)));
1211 }
1212 
1213 inline void requestRoutesStorageController(App& app)
1214 {
1215     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>")
1216         .privileges(redfish::privileges::getStorageController)
1217         .methods(boost::beast::http::verb::get)(
1218             std::bind_front(handleSystemsStorageControllerGet, std::ref(app)));
1219 }
1220 
1221 } // namespace redfish
1222