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