xref: /openbmc/bmcweb/redfish-core/lib/storage.hpp (revision 5a60c56f08c8c65a611e559b4c1ef6886933501a)
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 "app.hpp"
19 #include "dbus_utility.hpp"
20 #include "health.hpp"
21 #include "human_sort.hpp"
22 #include "openbmc_dbus_rest.hpp"
23 #include "query.hpp"
24 #include "redfish_util.hpp"
25 #include "registries/privilege_registry.hpp"
26 #include "utils/dbus_utils.hpp"
27 
28 #include <boost/system/error_code.hpp>
29 #include <boost/url/format.hpp>
30 #include <sdbusplus/asio/property.hpp>
31 #include <sdbusplus/unpack_properties.hpp>
32 
33 #include <array>
34 #include <string_view>
35 
36 namespace redfish
37 {
38 inline void requestRoutesStorageCollection(App& app)
39 {
40     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/")
41         .privileges(redfish::privileges::getStorageCollection)
42         .methods(boost::beast::http::verb::get)(
43             [&app](const crow::Request& req,
44                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
45                    const std::string& systemName) {
46         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
47         {
48             return;
49         }
50         if (systemName != "system")
51         {
52             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
53                                        systemName);
54             return;
55         }
56 
57         asyncResp->res.jsonValue["@odata.type"] =
58             "#StorageCollection.StorageCollection";
59         asyncResp->res.jsonValue["@odata.id"] =
60             "/redfish/v1/Systems/system/Storage";
61         asyncResp->res.jsonValue["Name"] = "Storage Collection";
62         nlohmann::json::array_t members;
63         nlohmann::json::object_t member;
64         member["@odata.id"] = "/redfish/v1/Systems/system/Storage/1";
65         members.emplace_back(member);
66         asyncResp->res.jsonValue["Members"] = std::move(members);
67         asyncResp->res.jsonValue["Members@odata.count"] = 1;
68         });
69 }
70 
71 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
72                       const std::shared_ptr<HealthPopulate>& health)
73 {
74     const std::array<std::string_view, 1> interfaces = {
75         "xyz.openbmc_project.Inventory.Item.Drive"};
76     dbus::utility::getSubTreePaths(
77         "/xyz/openbmc_project/inventory", 0, interfaces,
78         [asyncResp, health](
79             const boost::system::error_code& ec,
80             const dbus::utility::MapperGetSubTreePathsResponse& driveList) {
81         if (ec)
82         {
83             BMCWEB_LOG_ERROR << "Drive mapper call error";
84             messages::internalError(asyncResp->res);
85             return;
86         }
87 
88         nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"];
89         driveArray = nlohmann::json::array();
90         auto& count = asyncResp->res.jsonValue["Drives@odata.count"];
91         count = 0;
92 
93         health->inventory.insert(health->inventory.end(), driveList.begin(),
94                                  driveList.end());
95 
96         for (const std::string& drive : driveList)
97         {
98             sdbusplus::message::object_path object(drive);
99             if (object.filename().empty())
100             {
101                 BMCWEB_LOG_ERROR << "Failed to find filename in " << drive;
102                 return;
103             }
104 
105             nlohmann::json::object_t driveJson;
106             driveJson["@odata.id"] = boost::urls::format(
107                 "/redfish/v1/Systems/system/Storage/1/Drives/{}",
108                 object.filename());
109             driveArray.emplace_back(std::move(driveJson));
110         }
111 
112         count = driveArray.size();
113         });
114 }
115 
116 inline void
117     getStorageControllers(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
118                           const std::shared_ptr<HealthPopulate>& health)
119 {
120     constexpr std::array<std::string_view, 1> interfaces = {
121         "xyz.openbmc_project.Inventory.Item.StorageController"};
122     dbus::utility::getSubTree(
123         "/xyz/openbmc_project/inventory", 0, interfaces,
124         [asyncResp,
125          health](const boost::system::error_code& ec,
126                  const dbus::utility::MapperGetSubTreeResponse& subtree) {
127         if (ec || subtree.empty())
128         {
129             // doesn't have to be there
130             return;
131         }
132 
133         nlohmann::json& root = asyncResp->res.jsonValue["StorageControllers"];
134         root = nlohmann::json::array();
135         for (const auto& [path, interfaceDict] : subtree)
136         {
137             sdbusplus::message::object_path object(path);
138             std::string id = object.filename();
139             if (id.empty())
140             {
141                 BMCWEB_LOG_ERROR << "Failed to find filename in " << path;
142                 return;
143             }
144 
145             if (interfaceDict.size() != 1)
146             {
147                 BMCWEB_LOG_ERROR << "Connection size " << interfaceDict.size()
148                                  << ", greater than 1";
149                 messages::internalError(asyncResp->res);
150                 return;
151             }
152 
153             const std::string& connectionName = interfaceDict.front().first;
154 
155             size_t index = root.size();
156             nlohmann::json& storageController =
157                 root.emplace_back(nlohmann::json::object());
158 
159             storageController["@odata.type"] =
160                 "#Storage.v1_7_0.StorageController";
161             storageController["@odata.id"] = boost::urls::format(
162                 "/redfish/v1/Systems/system/Storage/1#{}",
163                 ("/StorageControllers"_json_pointer / index).to_string());
164             storageController["Name"] = id;
165             storageController["MemberId"] = id;
166             storageController["Status"]["State"] = "Enabled";
167 
168             sdbusplus::asio::getProperty<bool>(
169                 *crow::connections::systemBus, connectionName, path,
170                 "xyz.openbmc_project.Inventory.Item", "Present",
171                 [asyncResp, index](const boost::system::error_code& ec2,
172                                    bool isPresent) {
173                 // this interface isn't necessary, only check it
174                 // if we get a good return
175                 if (ec2)
176                 {
177                     return;
178                 }
179                 if (!isPresent)
180                 {
181                     asyncResp->res.jsonValue["StorageControllers"][index]
182                                             ["Status"]["State"] = "Absent";
183                 }
184                 });
185 
186             sdbusplus::asio::getAllProperties(
187                 *crow::connections::systemBus, connectionName, path,
188                 "xyz.openbmc_project.Inventory.Decorator.Asset",
189                 [asyncResp, index](
190                     const boost::system::error_code& ec2,
191                     const std::vector<
192                         std::pair<std::string, dbus::utility::DbusVariantType>>&
193                         propertiesList) {
194                 if (ec2)
195                 {
196                     // this interface isn't necessary
197                     return;
198                 }
199 
200                 const std::string* partNumber = nullptr;
201                 const std::string* serialNumber = nullptr;
202                 const std::string* manufacturer = nullptr;
203                 const std::string* model = nullptr;
204 
205                 const bool success = sdbusplus::unpackPropertiesNoThrow(
206                     dbus_utils::UnpackErrorPrinter(), propertiesList,
207                     "PartNumber", partNumber, "SerialNumber", serialNumber,
208                     "Manufacturer", manufacturer, "Model", model);
209 
210                 if (!success)
211                 {
212                     messages::internalError(asyncResp->res);
213                     return;
214                 }
215 
216                 nlohmann::json& controller =
217                     asyncResp->res.jsonValue["StorageControllers"][index];
218 
219                 if (partNumber != nullptr)
220                 {
221                     controller["PartNumber"] = *partNumber;
222                 }
223 
224                 if (serialNumber != nullptr)
225                 {
226                     controller["SerialNumber"] = *serialNumber;
227                 }
228 
229                 if (manufacturer != nullptr)
230                 {
231                     controller["Manufacturer"] = *manufacturer;
232                 }
233 
234                 if (model != nullptr)
235                 {
236                     controller["Model"] = *model;
237                 }
238                 });
239         }
240 
241         // this is done after we know the json array will no longer
242         // be resized, as json::array uses vector underneath and we
243         // need references to its members that won't change
244         size_t count = 0;
245         // Pointer based on |asyncResp->res.jsonValue|
246         nlohmann::json::json_pointer rootPtr =
247             "/StorageControllers"_json_pointer;
248         for (const auto& [path, interfaceDict] : subtree)
249         {
250             auto subHealth = std::make_shared<HealthPopulate>(
251                 asyncResp, rootPtr / count / "Status");
252             subHealth->inventory.emplace_back(path);
253             health->inventory.emplace_back(path);
254             health->children.emplace_back(subHealth);
255             count++;
256         }
257         });
258 }
259 
260 inline void requestRoutesStorage(App& app)
261 {
262     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/")
263         .privileges(redfish::privileges::getStorage)
264         .methods(boost::beast::http::verb::get)(
265             [&app](const crow::Request& req,
266                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
267         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
268         {
269             return;
270         }
271         asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_7_1.Storage";
272         asyncResp->res.jsonValue["@odata.id"] =
273             "/redfish/v1/Systems/system/Storage/1";
274         asyncResp->res.jsonValue["Name"] = "Storage";
275         asyncResp->res.jsonValue["Id"] = "1";
276         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
277 
278         auto health = std::make_shared<HealthPopulate>(asyncResp);
279         health->populate();
280 
281         getDrives(asyncResp, health);
282         getStorageControllers(asyncResp, health);
283         });
284 }
285 
286 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
287                           const std::string& connectionName,
288                           const std::string& path)
289 {
290     sdbusplus::asio::getAllProperties(
291         *crow::connections::systemBus, connectionName, path,
292         "xyz.openbmc_project.Inventory.Decorator.Asset",
293         [asyncResp](const boost::system::error_code& ec,
294                     const std::vector<
295                         std::pair<std::string, dbus::utility::DbusVariantType>>&
296                         propertiesList) {
297         if (ec)
298         {
299             // this interface isn't necessary
300             return;
301         }
302 
303         const std::string* partNumber = nullptr;
304         const std::string* serialNumber = nullptr;
305         const std::string* manufacturer = nullptr;
306         const std::string* model = nullptr;
307 
308         const bool success = sdbusplus::unpackPropertiesNoThrow(
309             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
310             partNumber, "SerialNumber", serialNumber, "Manufacturer",
311             manufacturer, "Model", model);
312 
313         if (!success)
314         {
315             messages::internalError(asyncResp->res);
316             return;
317         }
318 
319         if (partNumber != nullptr)
320         {
321             asyncResp->res.jsonValue["PartNumber"] = *partNumber;
322         }
323 
324         if (serialNumber != nullptr)
325         {
326             asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
327         }
328 
329         if (manufacturer != nullptr)
330         {
331             asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
332         }
333 
334         if (model != nullptr)
335         {
336             asyncResp->res.jsonValue["Model"] = *model;
337         }
338         });
339 }
340 
341 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
342                             const std::string& connectionName,
343                             const std::string& path)
344 {
345     sdbusplus::asio::getProperty<bool>(
346         *crow::connections::systemBus, connectionName, path,
347         "xyz.openbmc_project.Inventory.Item", "Present",
348         [asyncResp, path](const boost::system::error_code& ec,
349                           const bool isPresent) {
350         // this interface isn't necessary, only check it if
351         // we get a good return
352         if (ec)
353         {
354             return;
355         }
356 
357         if (!isPresent)
358         {
359             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
360         }
361         });
362 }
363 
364 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
365                           const std::string& connectionName,
366                           const std::string& path)
367 {
368     sdbusplus::asio::getProperty<bool>(
369         *crow::connections::systemBus, connectionName, path,
370         "xyz.openbmc_project.State.Drive", "Rebuilding",
371         [asyncResp](const boost::system::error_code& ec, const bool updating) {
372         // this interface isn't necessary, only check it
373         // if we get a good return
374         if (ec)
375         {
376             return;
377         }
378 
379         // updating and disabled in the backend shouldn't be
380         // able to be set at the same time, so we don't need
381         // to check for the race condition of these two
382         // calls
383         if (updating)
384         {
385             asyncResp->res.jsonValue["Status"]["State"] = "Updating";
386         }
387         });
388 }
389 
390 inline std::optional<std::string> convertDriveType(const std::string& type)
391 {
392     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
393     {
394         return "HDD";
395     }
396     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
397     {
398         return "SSD";
399     }
400 
401     return std::nullopt;
402 }
403 
404 inline std::optional<std::string> convertDriveProtocol(const std::string& proto)
405 {
406     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
407     {
408         return "SAS";
409     }
410     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
411     {
412         return "SATA";
413     }
414     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
415     {
416         return "NVMe";
417     }
418     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
419     {
420         return "FC";
421     }
422 
423     return std::nullopt;
424 }
425 
426 inline void
427     getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
428                            const std::string& connectionName,
429                            const std::string& path)
430 {
431     sdbusplus::asio::getAllProperties(
432         *crow::connections::systemBus, connectionName, path,
433         "xyz.openbmc_project.Inventory.Item.Drive",
434         [asyncResp](const boost::system::error_code& ec,
435                     const std::vector<
436                         std::pair<std::string, dbus::utility::DbusVariantType>>&
437                         propertiesList) {
438         if (ec)
439         {
440             // this interface isn't required
441             return;
442         }
443         for (const std::pair<std::string, dbus::utility::DbusVariantType>&
444                  property : propertiesList)
445         {
446             const std::string& propertyName = property.first;
447             if (propertyName == "Type")
448             {
449                 const std::string* value =
450                     std::get_if<std::string>(&property.second);
451                 if (value == nullptr)
452                 {
453                     // illegal property
454                     BMCWEB_LOG_ERROR << "Illegal property: Type";
455                     messages::internalError(asyncResp->res);
456                     return;
457                 }
458 
459                 std::optional<std::string> mediaType = convertDriveType(*value);
460                 if (!mediaType)
461                 {
462                     BMCWEB_LOG_ERROR << "Unsupported DriveType Interface: "
463                                      << *value;
464                     messages::internalError(asyncResp->res);
465                     return;
466                 }
467 
468                 asyncResp->res.jsonValue["MediaType"] = *mediaType;
469             }
470             else if (propertyName == "Capacity")
471             {
472                 const uint64_t* capacity =
473                     std::get_if<uint64_t>(&property.second);
474                 if (capacity == nullptr)
475                 {
476                     BMCWEB_LOG_ERROR << "Illegal property: Capacity";
477                     messages::internalError(asyncResp->res);
478                     return;
479                 }
480                 if (*capacity == 0)
481                 {
482                     // drive capacity not known
483                     continue;
484                 }
485 
486                 asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
487             }
488             else if (propertyName == "Protocol")
489             {
490                 const std::string* value =
491                     std::get_if<std::string>(&property.second);
492                 if (value == nullptr)
493                 {
494                     BMCWEB_LOG_ERROR << "Illegal property: Protocol";
495                     messages::internalError(asyncResp->res);
496                     return;
497                 }
498 
499                 std::optional<std::string> proto = convertDriveProtocol(*value);
500                 if (!proto)
501                 {
502                     BMCWEB_LOG_ERROR << "Unsupported DrivePrototype Interface: "
503                                      << *value;
504                     messages::internalError(asyncResp->res);
505                     return;
506                 }
507                 asyncResp->res.jsonValue["Protocol"] = *proto;
508             }
509             else if (propertyName == "PredictedMediaLifeLeftPercent")
510             {
511                 const uint8_t* lifeLeft =
512                     std::get_if<uint8_t>(&property.second);
513                 if (lifeLeft == nullptr)
514                 {
515                     BMCWEB_LOG_ERROR
516                         << "Illegal property: PredictedMediaLifeLeftPercent";
517                     messages::internalError(asyncResp->res);
518                     return;
519                 }
520                 // 255 means reading the value is not supported
521                 if (*lifeLeft != 255)
522                 {
523                     asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] =
524                         *lifeLeft;
525                 }
526             }
527         }
528         });
529 }
530 
531 static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
532                             const std::string& connectionName,
533                             const std::string& path,
534                             const std::vector<std::string>& interfaces)
535 {
536     for (const std::string& interface : interfaces)
537     {
538         if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
539         {
540             getDriveAsset(asyncResp, connectionName, path);
541         }
542         else if (interface == "xyz.openbmc_project.Inventory.Item")
543         {
544             getDrivePresent(asyncResp, connectionName, path);
545         }
546         else if (interface == "xyz.openbmc_project.State.Drive")
547         {
548             getDriveState(asyncResp, connectionName, path);
549         }
550         else if (interface == "xyz.openbmc_project.Inventory.Item.Drive")
551         {
552             getDriveItemProperties(asyncResp, connectionName, path);
553         }
554     }
555 }
556 
557 inline void requestRoutesDrive(App& app)
558 {
559     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/")
560         .privileges(redfish::privileges::getDrive)
561         .methods(boost::beast::http::verb::get)(
562             [&app](const crow::Request& req,
563                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
564                    const std::string& systemName, const std::string& driveId) {
565         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
566         {
567             return;
568         }
569         if (systemName != "system")
570         {
571             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
572                                        systemName);
573             return;
574         }
575 
576         constexpr std::array<std::string_view, 1> interfaces = {
577             "xyz.openbmc_project.Inventory.Item.Drive"};
578         dbus::utility::getSubTree(
579             "/xyz/openbmc_project/inventory", 0, interfaces,
580             [asyncResp,
581              driveId](const boost::system::error_code& ec,
582                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
583             if (ec)
584             {
585                 BMCWEB_LOG_ERROR << "Drive mapper call error";
586                 messages::internalError(asyncResp->res);
587                 return;
588             }
589 
590             auto drive = std::find_if(
591                 subtree.begin(), subtree.end(),
592                 [&driveId](
593                     const std::pair<std::string,
594                                     dbus::utility::MapperServiceMap>& object) {
595                 return sdbusplus::message::object_path(object.first)
596                            .filename() == driveId;
597                 });
598 
599             if (drive == subtree.end())
600             {
601                 messages::resourceNotFound(asyncResp->res, "Drive", driveId);
602                 return;
603             }
604 
605             const std::string& path = drive->first;
606             const dbus::utility::MapperServiceMap& connectionNames =
607                 drive->second;
608 
609             asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
610             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
611                 "/redfish/v1/Systems/system/Storage/1/Drives/{}", driveId);
612             asyncResp->res.jsonValue["Name"] = driveId;
613             asyncResp->res.jsonValue["Id"] = driveId;
614 
615             if (connectionNames.size() != 1)
616             {
617                 BMCWEB_LOG_ERROR << "Connection size " << connectionNames.size()
618                                  << ", not equal to 1";
619                 messages::internalError(asyncResp->res);
620                 return;
621             }
622 
623             getMainChassisId(
624                 asyncResp,
625                 [](const std::string& chassisId,
626                    const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
627                 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] =
628                     boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
629                 });
630 
631             // default it to Enabled
632             asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
633 
634             auto health = std::make_shared<HealthPopulate>(asyncResp);
635             health->inventory.emplace_back(path);
636             health->populate();
637 
638             addAllDriveInfo(asyncResp, connectionNames[0].first, path,
639                             connectionNames[0].second);
640             });
641         });
642 }
643 
644 /**
645  * Chassis drives, this URL will show all the DriveCollection
646  * information
647  */
648 inline void chassisDriveCollectionGet(
649     crow::App& app, const crow::Request& req,
650     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
651     const std::string& chassisId)
652 {
653     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
654     {
655         return;
656     }
657 
658     // mapper call lambda
659     constexpr std::array<std::string_view, 2> interfaces = {
660         "xyz.openbmc_project.Inventory.Item.Board",
661         "xyz.openbmc_project.Inventory.Item.Chassis"};
662     dbus::utility::getSubTree(
663         "/xyz/openbmc_project/inventory", 0, interfaces,
664         [asyncResp,
665          chassisId](const boost::system::error_code& ec,
666                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
667         if (ec)
668         {
669             if (ec == boost::system::errc::host_unreachable)
670             {
671                 messages::resourceNotFound(asyncResp->res, "Chassis",
672                                            chassisId);
673                 return;
674             }
675             messages::internalError(asyncResp->res);
676             return;
677         }
678 
679         // Iterate over all retrieved ObjectPaths.
680         for (const auto& [path, connectionNames] : subtree)
681         {
682             sdbusplus::message::object_path objPath(path);
683             if (objPath.filename() != chassisId)
684             {
685                 continue;
686             }
687 
688             if (connectionNames.empty())
689             {
690                 BMCWEB_LOG_ERROR << "Got 0 Connection names";
691                 continue;
692             }
693 
694             asyncResp->res.jsonValue["@odata.type"] =
695                 "#DriveCollection.DriveCollection";
696             asyncResp->res.jsonValue["@odata.id"] =
697                 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId);
698             asyncResp->res.jsonValue["Name"] = "Drive Collection";
699 
700             // Association lambda
701             dbus::utility::getAssociationEndPoints(
702                 path + "/drive",
703                 [asyncResp,
704                  chassisId](const boost::system::error_code& ec3,
705                             const dbus::utility::MapperEndPoints& resp) {
706                 if (ec3)
707                 {
708                     BMCWEB_LOG_ERROR << "Error in chassis Drive association ";
709                 }
710                 nlohmann::json& members = asyncResp->res.jsonValue["Members"];
711                 // important if array is empty
712                 members = nlohmann::json::array();
713 
714                 std::vector<std::string> leafNames;
715                 for (const auto& drive : resp)
716                 {
717                     sdbusplus::message::object_path drivePath(drive);
718                     leafNames.push_back(drivePath.filename());
719                 }
720 
721                 std::sort(leafNames.begin(), leafNames.end(),
722                           AlphanumLess<std::string>());
723 
724                 for (const auto& leafName : leafNames)
725                 {
726                     nlohmann::json::object_t member;
727                     member["@odata.id"] =
728                         boost::urls::format("/redfish/v1/Chassis/{}/Drives/{}",
729                                             chassisId, leafName);
730                     members.emplace_back(std::move(member));
731                     // navigation links will be registered in next patch set
732                 }
733                 asyncResp->res.jsonValue["Members@odata.count"] = resp.size();
734                 }); // end association lambda
735 
736         }           // end Iterate over all retrieved ObjectPaths
737         });
738 }
739 
740 inline void requestRoutesChassisDrive(App& app)
741 {
742     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
743         .privileges(redfish::privileges::getDriveCollection)
744         .methods(boost::beast::http::verb::get)(
745             std::bind_front(chassisDriveCollectionGet, std::ref(app)));
746 }
747 
748 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
749                        const std::string& chassisId,
750                        const std::string& driveName,
751                        const boost::system::error_code& ec,
752                        const dbus::utility::MapperGetSubTreeResponse& subtree)
753 {
754     if (ec)
755     {
756         BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
757         messages::internalError(asyncResp->res);
758         return;
759     }
760 
761     // Iterate over all retrieved ObjectPaths.
762     for (const auto& [path, connectionNames] : subtree)
763     {
764         sdbusplus::message::object_path objPath(path);
765         if (objPath.filename() != driveName)
766         {
767             continue;
768         }
769 
770         if (connectionNames.empty())
771         {
772             BMCWEB_LOG_ERROR << "Got 0 Connection names";
773             continue;
774         }
775 
776         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
777             "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName);
778 
779         asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
780         asyncResp->res.jsonValue["Name"] = driveName;
781         asyncResp->res.jsonValue["Id"] = driveName;
782         // default it to Enabled
783         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
784 
785         nlohmann::json::object_t linkChassisNav;
786         linkChassisNav["@odata.id"] =
787             boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
788         asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
789 
790         addAllDriveInfo(asyncResp, connectionNames[0].first, path,
791                         connectionNames[0].second);
792     }
793 }
794 
795 inline void
796     matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
797                       const std::string& chassisId,
798                       const std::string& driveName,
799                       const std::vector<std::string>& resp)
800 {
801     for (const std::string& drivePath : resp)
802     {
803         sdbusplus::message::object_path path(drivePath);
804         std::string leaf = path.filename();
805         if (leaf != driveName)
806         {
807             continue;
808         }
809         //  mapper call drive
810         constexpr std::array<std::string_view, 1> driveInterface = {
811             "xyz.openbmc_project.Inventory.Item.Drive"};
812         dbus::utility::getSubTree(
813             "/xyz/openbmc_project/inventory", 0, driveInterface,
814             [asyncResp, chassisId, driveName](
815                 const boost::system::error_code& ec,
816                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
817             buildDrive(asyncResp, chassisId, driveName, ec, subtree);
818             });
819     }
820 }
821 
822 inline void
823     handleChassisDriveGet(crow::App& app, const crow::Request& req,
824                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
825                           const std::string& chassisId,
826                           const std::string& driveName)
827 {
828     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
829     {
830         return;
831     }
832     constexpr std::array<std::string_view, 2> interfaces = {
833         "xyz.openbmc_project.Inventory.Item.Board",
834         "xyz.openbmc_project.Inventory.Item.Chassis"};
835 
836     // mapper call chassis
837     dbus::utility::getSubTree(
838         "/xyz/openbmc_project/inventory", 0, interfaces,
839         [asyncResp, chassisId,
840          driveName](const boost::system::error_code& ec,
841                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
842         if (ec)
843         {
844             messages::internalError(asyncResp->res);
845             return;
846         }
847 
848         // Iterate over all retrieved ObjectPaths.
849         for (const auto& [path, connectionNames] : subtree)
850         {
851             sdbusplus::message::object_path objPath(path);
852             if (objPath.filename() != chassisId)
853             {
854                 continue;
855             }
856 
857             if (connectionNames.empty())
858             {
859                 BMCWEB_LOG_ERROR << "Got 0 Connection names";
860                 continue;
861             }
862 
863             dbus::utility::getAssociationEndPoints(
864                 path + "/drive",
865                 [asyncResp, chassisId,
866                  driveName](const boost::system::error_code& ec3,
867                             const dbus::utility::MapperEndPoints& resp) {
868                 if (ec3)
869                 {
870                     return; // no drives = no failures
871                 }
872                 matchAndFillDrive(asyncResp, chassisId, driveName, resp);
873                 });
874             break;
875         }
876         });
877 }
878 
879 /**
880  * This URL will show the drive interface for the specific drive in the chassis
881  */
882 inline void requestRoutesChassisDriveName(App& app)
883 {
884     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
885         .privileges(redfish::privileges::getChassis)
886         .methods(boost::beast::http::verb::get)(
887             std::bind_front(handleChassisDriveGet, std::ref(app)));
888 }
889 
890 } // namespace redfish
891