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