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