xref: /openbmc/bmcweb/redfish-core/lib/storage.hpp (revision 1b8b02a4)
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/collection.hpp"
31 #include "utils/dbus_utils.hpp"
32 
33 #include <boost/system/error_code.hpp>
34 #include <boost/url/format.hpp>
35 #include <sdbusplus/asio/property.hpp>
36 #include <sdbusplus/unpack_properties.hpp>
37 
38 #include <array>
39 #include <ranges>
40 #include <string_view>
41 
42 namespace redfish
43 {
44 
45 inline void handleSystemsStorageCollectionGet(
46     App& app, const crow::Request& req,
47     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
48     const std::string& systemName)
49 {
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 
67     constexpr std::array<std::string_view, 1> interface {
68         "xyz.openbmc_project.Inventory.Item.Storage"
69     };
70     collection_util::getCollectionMembers(
71         asyncResp, boost::urls::format("/redfish/v1/Systems/system/Storage"),
72         interface);
73 }
74 
75 inline void handleStorageCollectionGet(
76     App& app, const crow::Request& req,
77     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
78 {
79     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
80     {
81         return;
82     }
83     asyncResp->res.jsonValue["@odata.type"] =
84         "#StorageCollection.StorageCollection";
85     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage";
86     asyncResp->res.jsonValue["Name"] = "Storage Collection";
87     constexpr std::array<std::string_view, 1> interface {
88         "xyz.openbmc_project.Inventory.Item.Storage"
89     };
90     collection_util::getCollectionMembers(
91         asyncResp, boost::urls::format("/redfish/v1/Storage"), interface);
92 }
93 
94 inline void requestRoutesStorageCollection(App& app)
95 {
96     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/")
97         .privileges(redfish::privileges::getStorageCollection)
98         .methods(boost::beast::http::verb::get)(
99             std::bind_front(handleSystemsStorageCollectionGet, std::ref(app)));
100     BMCWEB_ROUTE(app, "/redfish/v1/Storage/")
101         .privileges(redfish::privileges::getStorageCollection)
102         .methods(boost::beast::http::verb::get)(
103             std::bind_front(handleStorageCollectionGet, std::ref(app)));
104 }
105 
106 inline void afterChassisDriveCollectionSubtree(
107     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
108     const std::shared_ptr<HealthPopulate>& health,
109     const boost::system::error_code& ec,
110     const dbus::utility::MapperGetSubTreePathsResponse& driveList)
111 {
112     if (ec)
113     {
114         BMCWEB_LOG_ERROR("Drive mapper call error");
115         messages::internalError(asyncResp->res);
116         return;
117     }
118 
119     nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"];
120     driveArray = nlohmann::json::array();
121     auto& count = asyncResp->res.jsonValue["Drives@odata.count"];
122     count = 0;
123 
124     if constexpr (bmcwebEnableHealthPopulate)
125     {
126         health->inventory.insert(health->inventory.end(), driveList.begin(),
127                                  driveList.end());
128     }
129 
130     for (const std::string& drive : driveList)
131     {
132         sdbusplus::message::object_path object(drive);
133         if (object.filename().empty())
134         {
135             BMCWEB_LOG_ERROR("Failed to find filename in {}", drive);
136             return;
137         }
138 
139         nlohmann::json::object_t driveJson;
140         driveJson["@odata.id"] = boost::urls::format(
141             "/redfish/v1/Systems/system/Storage/1/Drives/{}",
142             object.filename());
143         driveArray.emplace_back(std::move(driveJson));
144     }
145 
146     count = driveArray.size();
147 }
148 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
149                       const std::shared_ptr<HealthPopulate>& health)
150 {
151     const std::array<std::string_view, 1> interfaces = {
152         "xyz.openbmc_project.Inventory.Item.Drive"};
153     dbus::utility::getSubTreePaths(
154         "/xyz/openbmc_project/inventory", 0, interfaces,
155         std::bind_front(afterChassisDriveCollectionSubtree, asyncResp, health));
156 }
157 
158 inline void afterSystemsStorageGetSubtree(
159     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
160     const std::string& storageId, const boost::system::error_code& ec,
161     const dbus::utility::MapperGetSubTreeResponse& subtree)
162 {
163     if (ec)
164     {
165         BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error");
166         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
167                                    storageId);
168         return;
169     }
170     auto storage = std::ranges::find_if(
171         subtree,
172         [&storageId](const std::pair<std::string,
173                                      dbus::utility::MapperServiceMap>& object) {
174         return sdbusplus::message::object_path(object.first).filename() ==
175                storageId;
176         });
177     if (storage == subtree.end())
178     {
179         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
180                                    storageId);
181         return;
182     }
183 
184     asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
185     asyncResp->res.jsonValue["@odata.id"] =
186         boost::urls::format("/redfish/v1/Systems/system/Storage/{}", storageId);
187     asyncResp->res.jsonValue["Name"] = "Storage";
188     asyncResp->res.jsonValue["Id"] = storageId;
189     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
190 
191     auto health = std::make_shared<HealthPopulate>(asyncResp);
192     if constexpr (bmcwebEnableHealthPopulate)
193     {
194         health->populate();
195     }
196 
197     getDrives(asyncResp, health);
198     asyncResp->res.jsonValue["Controllers"]["@odata.id"] = boost::urls::format(
199         "/redfish/v1/Systems/system/Storage/{}/Controllers", storageId);
200 }
201 
202 inline void
203     handleSystemsStorageGet(App& app, const crow::Request& req,
204                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
205                             const std::string& systemName,
206                             const std::string& storageId)
207 {
208     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
209     {
210         return;
211     }
212     if constexpr (bmcwebEnableMultiHost)
213     {
214         // Option currently returns no systems.  TBD
215         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
216                                    systemName);
217         return;
218     }
219 
220     constexpr std::array<std::string_view, 1> interfaces = {
221         "xyz.openbmc_project.Inventory.Item.Storage"};
222     dbus::utility::getSubTree(
223         "/xyz/openbmc_project/inventory", 0, interfaces,
224         std::bind_front(afterSystemsStorageGetSubtree, asyncResp, storageId));
225 }
226 
227 inline void afterSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
228                          const std::string& storageId,
229                          const boost::system::error_code& ec,
230                          const dbus::utility::MapperGetSubTreeResponse& subtree)
231 {
232     if (ec)
233     {
234         BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error");
235         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
236                                    storageId);
237         return;
238     }
239     auto storage = std::ranges::find_if(
240         subtree,
241         [&storageId](const std::pair<std::string,
242                                      dbus::utility::MapperServiceMap>& object) {
243         return sdbusplus::message::object_path(object.first).filename() ==
244                storageId;
245         });
246     if (storage == subtree.end())
247     {
248         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
249                                    storageId);
250         return;
251     }
252 
253     asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
254     asyncResp->res.jsonValue["@odata.id"] =
255         boost::urls::format("/redfish/v1/Storage/{}", storageId);
256     asyncResp->res.jsonValue["Name"] = "Storage";
257     asyncResp->res.jsonValue["Id"] = storageId;
258     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
259 
260     // Storage subsystem to Storage link.
261     nlohmann::json::array_t storageServices;
262     nlohmann::json::object_t storageService;
263     storageService["@odata.id"] =
264         boost::urls::format("/redfish/v1/Systems/system/Storage/{}", storageId);
265     storageServices.emplace_back(storageService);
266     asyncResp->res.jsonValue["Links"]["StorageServices"] =
267         std::move(storageServices);
268     asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = 1;
269 }
270 
271 inline void
272     handleStorageGet(App& app, const crow::Request& req,
273                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
274                      const std::string& storageId)
275 {
276     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
277     {
278         BMCWEB_LOG_DEBUG("requestRoutesStorage setUpRedfishRoute failed");
279         return;
280     }
281 
282     constexpr std::array<std::string_view, 1> interfaces = {
283         "xyz.openbmc_project.Inventory.Item.Storage"};
284     dbus::utility::getSubTree(
285         "/xyz/openbmc_project/inventory", 0, interfaces,
286         std::bind_front(afterSubtree, asyncResp, storageId));
287 }
288 
289 inline void requestRoutesStorage(App& app)
290 {
291     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/")
292         .privileges(redfish::privileges::getStorage)
293         .methods(boost::beast::http::verb::get)(
294             std::bind_front(handleSystemsStorageGet, std::ref(app)));
295 
296     BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/")
297         .privileges(redfish::privileges::getStorage)
298         .methods(boost::beast::http::verb::get)(
299             std::bind_front(handleStorageGet, std::ref(app)));
300 }
301 
302 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
303                           const std::string& connectionName,
304                           const std::string& path)
305 {
306     sdbusplus::asio::getAllProperties(
307         *crow::connections::systemBus, connectionName, path,
308         "xyz.openbmc_project.Inventory.Decorator.Asset",
309         [asyncResp](const boost::system::error_code& ec,
310                     const std::vector<
311                         std::pair<std::string, dbus::utility::DbusVariantType>>&
312                         propertiesList) {
313         if (ec)
314         {
315             // this interface isn't necessary
316             return;
317         }
318 
319         const std::string* partNumber = nullptr;
320         const std::string* serialNumber = nullptr;
321         const std::string* manufacturer = nullptr;
322         const std::string* model = nullptr;
323 
324         const bool success = sdbusplus::unpackPropertiesNoThrow(
325             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
326             partNumber, "SerialNumber", serialNumber, "Manufacturer",
327             manufacturer, "Model", model);
328 
329         if (!success)
330         {
331             messages::internalError(asyncResp->res);
332             return;
333         }
334 
335         if (partNumber != nullptr)
336         {
337             asyncResp->res.jsonValue["PartNumber"] = *partNumber;
338         }
339 
340         if (serialNumber != nullptr)
341         {
342             asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
343         }
344 
345         if (manufacturer != nullptr)
346         {
347             asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
348         }
349 
350         if (model != nullptr)
351         {
352             asyncResp->res.jsonValue["Model"] = *model;
353         }
354         });
355 }
356 
357 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
358                             const std::string& connectionName,
359                             const std::string& path)
360 {
361     sdbusplus::asio::getProperty<bool>(
362         *crow::connections::systemBus, connectionName, path,
363         "xyz.openbmc_project.Inventory.Item", "Present",
364         [asyncResp, path](const boost::system::error_code& ec,
365                           const bool isPresent) {
366         // this interface isn't necessary, only check it if
367         // we get a good return
368         if (ec)
369         {
370             return;
371         }
372 
373         if (!isPresent)
374         {
375             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
376         }
377         });
378 }
379 
380 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
381                           const std::string& connectionName,
382                           const std::string& path)
383 {
384     sdbusplus::asio::getProperty<bool>(
385         *crow::connections::systemBus, connectionName, path,
386         "xyz.openbmc_project.State.Drive", "Rebuilding",
387         [asyncResp](const boost::system::error_code& ec, const bool updating) {
388         // this interface isn't necessary, only check it
389         // if we get a good return
390         if (ec)
391         {
392             return;
393         }
394 
395         // updating and disabled in the backend shouldn't be
396         // able to be set at the same time, so we don't need
397         // to check for the race condition of these two
398         // calls
399         if (updating)
400         {
401             asyncResp->res.jsonValue["Status"]["State"] = "Updating";
402         }
403         });
404 }
405 
406 inline std::optional<drive::MediaType> convertDriveType(std::string_view type)
407 {
408     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
409     {
410         return drive::MediaType::HDD;
411     }
412     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
413     {
414         return drive::MediaType::SSD;
415     }
416     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown")
417     {
418         return std::nullopt;
419     }
420 
421     return drive::MediaType::Invalid;
422 }
423 
424 inline std::optional<protocol::Protocol>
425     convertDriveProtocol(std::string_view proto)
426 {
427     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
428     {
429         return protocol::Protocol::SAS;
430     }
431     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
432     {
433         return protocol::Protocol::SATA;
434     }
435     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
436     {
437         return protocol::Protocol::NVMe;
438     }
439     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
440     {
441         return protocol::Protocol::FC;
442     }
443     if (proto ==
444         "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown")
445     {
446         return std::nullopt;
447     }
448 
449     return protocol::Protocol::Invalid;
450 }
451 
452 inline void
453     getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
454                            const std::string& connectionName,
455                            const std::string& path)
456 {
457     sdbusplus::asio::getAllProperties(
458         *crow::connections::systemBus, connectionName, path,
459         "xyz.openbmc_project.Inventory.Item.Drive",
460         [asyncResp](const boost::system::error_code& ec,
461                     const std::vector<
462                         std::pair<std::string, dbus::utility::DbusVariantType>>&
463                         propertiesList) {
464         if (ec)
465         {
466             // this interface isn't required
467             return;
468         }
469         const std::string* encryptionStatus = nullptr;
470         const bool* isLocked = nullptr;
471         for (const std::pair<std::string, dbus::utility::DbusVariantType>&
472                  property : propertiesList)
473         {
474             const std::string& propertyName = property.first;
475             if (propertyName == "Type")
476             {
477                 const std::string* value =
478                     std::get_if<std::string>(&property.second);
479                 if (value == nullptr)
480                 {
481                     // illegal property
482                     BMCWEB_LOG_ERROR("Illegal property: Type");
483                     messages::internalError(asyncResp->res);
484                     return;
485                 }
486 
487                 std::optional<drive::MediaType> mediaType =
488                     convertDriveType(*value);
489                 if (!mediaType)
490                 {
491                     BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}",
492                                        *value);
493                     continue;
494                 }
495                 if (*mediaType == drive::MediaType::Invalid)
496                 {
497                     messages::internalError(asyncResp->res);
498                     return;
499                 }
500 
501                 asyncResp->res.jsonValue["MediaType"] = *mediaType;
502             }
503             else if (propertyName == "Capacity")
504             {
505                 const uint64_t* capacity =
506                     std::get_if<uint64_t>(&property.second);
507                 if (capacity == nullptr)
508                 {
509                     BMCWEB_LOG_ERROR("Illegal property: Capacity");
510                     messages::internalError(asyncResp->res);
511                     return;
512                 }
513                 if (*capacity == 0)
514                 {
515                     // drive capacity not known
516                     continue;
517                 }
518 
519                 asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
520             }
521             else if (propertyName == "Protocol")
522             {
523                 const std::string* value =
524                     std::get_if<std::string>(&property.second);
525                 if (value == nullptr)
526                 {
527                     BMCWEB_LOG_ERROR("Illegal property: Protocol");
528                     messages::internalError(asyncResp->res);
529                     return;
530                 }
531 
532                 std::optional<protocol::Protocol> proto =
533                     convertDriveProtocol(*value);
534                 if (!proto)
535                 {
536                     BMCWEB_LOG_WARNING("Unknown DrivePrototype Interface: {}",
537                                        *value);
538                     continue;
539                 }
540                 if (*proto == protocol::Protocol::Invalid)
541                 {
542                     messages::internalError(asyncResp->res);
543                     return;
544                 }
545                 asyncResp->res.jsonValue["Protocol"] = *proto;
546             }
547             else if (propertyName == "PredictedMediaLifeLeftPercent")
548             {
549                 const uint8_t* lifeLeft =
550                     std::get_if<uint8_t>(&property.second);
551                 if (lifeLeft == nullptr)
552                 {
553                     BMCWEB_LOG_ERROR(
554                         "Illegal property: PredictedMediaLifeLeftPercent");
555                     messages::internalError(asyncResp->res);
556                     return;
557                 }
558                 // 255 means reading the value is not supported
559                 if (*lifeLeft != 255)
560                 {
561                     asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] =
562                         *lifeLeft;
563                 }
564             }
565             else if (propertyName == "EncryptionStatus")
566             {
567                 encryptionStatus = std::get_if<std::string>(&property.second);
568                 if (encryptionStatus == nullptr)
569                 {
570                     BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus");
571                     messages::internalError(asyncResp->res);
572                     return;
573                 }
574             }
575             else if (propertyName == "Locked")
576             {
577                 isLocked = std::get_if<bool>(&property.second);
578                 if (isLocked == nullptr)
579                 {
580                     BMCWEB_LOG_ERROR("Illegal property: Locked");
581                     messages::internalError(asyncResp->res);
582                     return;
583                 }
584             }
585         }
586 
587         if (encryptionStatus == nullptr || isLocked == nullptr ||
588             *encryptionStatus ==
589                 "xyz.openbmc_project.Drive.DriveEncryptionState.Unknown")
590         {
591             return;
592         }
593         if (*encryptionStatus !=
594             "xyz.openbmc_project.Drive.DriveEncryptionState.Encrypted")
595         {
596             //"The drive is not currently encrypted."
597             asyncResp->res.jsonValue["EncryptionStatus"] =
598                 drive::EncryptionStatus::Unencrypted;
599             return;
600         }
601         if (*isLocked)
602         {
603             //"The drive is currently encrypted and the data is not
604             // accessible to the user."
605             asyncResp->res.jsonValue["EncryptionStatus"] =
606                 drive::EncryptionStatus::Locked;
607             return;
608         }
609         // if not locked
610         // "The drive is currently encrypted but the data is accessible
611         // to the user in unencrypted form."
612         asyncResp->res.jsonValue["EncryptionStatus"] =
613             drive::EncryptionStatus::Unlocked;
614         });
615 }
616 
617 static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
618                             const std::string& connectionName,
619                             const std::string& path,
620                             const std::vector<std::string>& interfaces)
621 {
622     for (const std::string& interface : interfaces)
623     {
624         if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
625         {
626             getDriveAsset(asyncResp, connectionName, path);
627         }
628         else if (interface == "xyz.openbmc_project.Inventory.Item")
629         {
630             getDrivePresent(asyncResp, connectionName, path);
631         }
632         else if (interface == "xyz.openbmc_project.State.Drive")
633         {
634             getDriveState(asyncResp, connectionName, path);
635         }
636         else if (interface == "xyz.openbmc_project.Inventory.Item.Drive")
637         {
638             getDriveItemProperties(asyncResp, connectionName, path);
639         }
640     }
641 }
642 
643 inline void afterGetSubtreeSystemsStorageDrive(
644     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
645     const std::string& driveId, const boost::system::error_code& ec,
646     const dbus::utility::MapperGetSubTreeResponse& subtree)
647 {
648     if (ec)
649     {
650         BMCWEB_LOG_ERROR("Drive mapper call error");
651         messages::internalError(asyncResp->res);
652         return;
653     }
654 
655     auto drive = std::ranges::find_if(
656         subtree,
657         [&driveId](const std::pair<std::string,
658                                    dbus::utility::MapperServiceMap>& object) {
659         return sdbusplus::message::object_path(object.first).filename() ==
660                driveId;
661         });
662 
663     if (drive == subtree.end())
664     {
665         messages::resourceNotFound(asyncResp->res, "Drive", driveId);
666         return;
667     }
668 
669     const std::string& path = drive->first;
670     const dbus::utility::MapperServiceMap& connectionNames = drive->second;
671 
672     asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
673     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
674         "/redfish/v1/Systems/system/Storage/1/Drives/{}", driveId);
675     asyncResp->res.jsonValue["Name"] = driveId;
676     asyncResp->res.jsonValue["Id"] = driveId;
677 
678     if (connectionNames.size() != 1)
679     {
680         BMCWEB_LOG_ERROR("Connection size {}, not equal to 1",
681                          connectionNames.size());
682         messages::internalError(asyncResp->res);
683         return;
684     }
685 
686     getMainChassisId(asyncResp,
687                      [](const std::string& chassisId,
688                         const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
689         aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] =
690             boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
691     });
692 
693     // default it to Enabled
694     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
695 
696     if constexpr (bmcwebEnableHealthPopulate)
697     {
698         auto health = std::make_shared<HealthPopulate>(asyncResp);
699         health->inventory.emplace_back(path);
700         health->populate();
701     }
702 
703     addAllDriveInfo(asyncResp, connectionNames[0].first, path,
704                     connectionNames[0].second);
705 }
706 
707 inline void handleSystemsStorageDriveGet(
708     App& app, const crow::Request& req,
709     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
710     const std::string& systemName, const std::string& driveId)
711 {
712     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
713     {
714         return;
715     }
716     if constexpr (bmcwebEnableMultiHost)
717     {
718         // Option currently returns no systems.  TBD
719         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
720                                    systemName);
721         return;
722     }
723 
724     if (systemName != "system")
725     {
726         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
727                                    systemName);
728         return;
729     }
730 
731     constexpr std::array<std::string_view, 1> interfaces = {
732         "xyz.openbmc_project.Inventory.Item.Drive"};
733     dbus::utility::getSubTree(
734         "/xyz/openbmc_project/inventory", 0, interfaces,
735         std::bind_front(afterGetSubtreeSystemsStorageDrive, asyncResp,
736                         driveId));
737 }
738 
739 inline void requestRoutesDrive(App& app)
740 {
741     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/")
742         .privileges(redfish::privileges::getDrive)
743         .methods(boost::beast::http::verb::get)(
744             std::bind_front(handleSystemsStorageDriveGet, std::ref(app)));
745 }
746 
747 inline void afterChassisDriveCollectionSubtreeGet(
748     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
749     const std::string& chassisId, const boost::system::error_code& ec,
750     const dbus::utility::MapperGetSubTreeResponse& subtree)
751 {
752     if (ec)
753     {
754         if (ec == boost::system::errc::host_unreachable)
755         {
756             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
757             return;
758         }
759         messages::internalError(asyncResp->res);
760         return;
761     }
762 
763     // Iterate over all retrieved ObjectPaths.
764     for (const auto& [path, connectionNames] : subtree)
765     {
766         sdbusplus::message::object_path objPath(path);
767         if (objPath.filename() != chassisId)
768         {
769             continue;
770         }
771 
772         if (connectionNames.empty())
773         {
774             BMCWEB_LOG_ERROR("Got 0 Connection names");
775             continue;
776         }
777 
778         asyncResp->res.jsonValue["@odata.type"] =
779             "#DriveCollection.DriveCollection";
780         asyncResp->res.jsonValue["@odata.id"] =
781             boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId);
782         asyncResp->res.jsonValue["Name"] = "Drive Collection";
783 
784         // Association lambda
785         dbus::utility::getAssociationEndPoints(
786             path + "/drive",
787             [asyncResp, chassisId](const boost::system::error_code& ec3,
788                                    const dbus::utility::MapperEndPoints& resp) {
789             if (ec3)
790             {
791                 BMCWEB_LOG_ERROR("Error in chassis Drive association ");
792             }
793             nlohmann::json& members = asyncResp->res.jsonValue["Members"];
794             // important if array is empty
795             members = nlohmann::json::array();
796 
797             std::vector<std::string> leafNames;
798             for (const auto& drive : resp)
799             {
800                 sdbusplus::message::object_path drivePath(drive);
801                 leafNames.push_back(drivePath.filename());
802             }
803 
804             std::ranges::sort(leafNames, AlphanumLess<std::string>());
805 
806             for (const auto& leafName : leafNames)
807             {
808                 nlohmann::json::object_t member;
809                 member["@odata.id"] = boost::urls::format(
810                     "/redfish/v1/Chassis/{}/Drives/{}", chassisId, leafName);
811                 members.emplace_back(std::move(member));
812                 // navigation links will be registered in next patch set
813             }
814             asyncResp->res.jsonValue["Members@odata.count"] = resp.size();
815             }); // end association lambda
816 
817     }           // end Iterate over all retrieved ObjectPaths
818 }
819 /**
820  * Chassis drives, this URL will show all the DriveCollection
821  * information
822  */
823 inline void chassisDriveCollectionGet(
824     crow::App& app, const crow::Request& req,
825     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
826     const std::string& chassisId)
827 {
828     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
829     {
830         return;
831     }
832 
833     // mapper call lambda
834     constexpr std::array<std::string_view, 2> interfaces = {
835         "xyz.openbmc_project.Inventory.Item.Board",
836         "xyz.openbmc_project.Inventory.Item.Chassis"};
837     dbus::utility::getSubTree(
838         "/xyz/openbmc_project/inventory", 0, interfaces,
839         std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp,
840                         chassisId));
841 }
842 
843 inline void requestRoutesChassisDrive(App& app)
844 {
845     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
846         .privileges(redfish::privileges::getDriveCollection)
847         .methods(boost::beast::http::verb::get)(
848             std::bind_front(chassisDriveCollectionGet, std::ref(app)));
849 }
850 
851 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
852                        const std::string& chassisId,
853                        const std::string& driveName,
854                        const boost::system::error_code& ec,
855                        const dbus::utility::MapperGetSubTreeResponse& subtree)
856 {
857     if (ec)
858     {
859         BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
860         messages::internalError(asyncResp->res);
861         return;
862     }
863 
864     // Iterate over all retrieved ObjectPaths.
865     for (const auto& [path, connectionNames] : subtree)
866     {
867         sdbusplus::message::object_path objPath(path);
868         if (objPath.filename() != driveName)
869         {
870             continue;
871         }
872 
873         if (connectionNames.empty())
874         {
875             BMCWEB_LOG_ERROR("Got 0 Connection names");
876             continue;
877         }
878 
879         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
880             "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName);
881 
882         asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
883         asyncResp->res.jsonValue["Name"] = driveName;
884         asyncResp->res.jsonValue["Id"] = driveName;
885         // default it to Enabled
886         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
887 
888         nlohmann::json::object_t linkChassisNav;
889         linkChassisNav["@odata.id"] =
890             boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
891         asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
892 
893         addAllDriveInfo(asyncResp, connectionNames[0].first, path,
894                         connectionNames[0].second);
895     }
896 }
897 
898 inline void
899     matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
900                       const std::string& chassisId,
901                       const std::string& driveName,
902                       const std::vector<std::string>& resp)
903 {
904     for (const std::string& drivePath : resp)
905     {
906         sdbusplus::message::object_path path(drivePath);
907         std::string leaf = path.filename();
908         if (leaf != driveName)
909         {
910             continue;
911         }
912         //  mapper call drive
913         constexpr std::array<std::string_view, 1> driveInterface = {
914             "xyz.openbmc_project.Inventory.Item.Drive"};
915         dbus::utility::getSubTree(
916             "/xyz/openbmc_project/inventory", 0, driveInterface,
917             [asyncResp, chassisId, driveName](
918                 const boost::system::error_code& ec,
919                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
920             buildDrive(asyncResp, chassisId, driveName, ec, subtree);
921             });
922     }
923 }
924 
925 inline void
926     handleChassisDriveGet(crow::App& app, const crow::Request& req,
927                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
928                           const std::string& chassisId,
929                           const std::string& driveName)
930 {
931     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
932     {
933         return;
934     }
935     constexpr std::array<std::string_view, 2> interfaces = {
936         "xyz.openbmc_project.Inventory.Item.Board",
937         "xyz.openbmc_project.Inventory.Item.Chassis"};
938 
939     // mapper call chassis
940     dbus::utility::getSubTree(
941         "/xyz/openbmc_project/inventory", 0, interfaces,
942         [asyncResp, chassisId,
943          driveName](const boost::system::error_code& ec,
944                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
945         if (ec)
946         {
947             messages::internalError(asyncResp->res);
948             return;
949         }
950 
951         // Iterate over all retrieved ObjectPaths.
952         for (const auto& [path, connectionNames] : subtree)
953         {
954             sdbusplus::message::object_path objPath(path);
955             if (objPath.filename() != chassisId)
956             {
957                 continue;
958             }
959 
960             if (connectionNames.empty())
961             {
962                 BMCWEB_LOG_ERROR("Got 0 Connection names");
963                 continue;
964             }
965 
966             dbus::utility::getAssociationEndPoints(
967                 path + "/drive",
968                 [asyncResp, chassisId,
969                  driveName](const boost::system::error_code& ec3,
970                             const dbus::utility::MapperEndPoints& resp) {
971                 if (ec3)
972                 {
973                     return; // no drives = no failures
974                 }
975                 matchAndFillDrive(asyncResp, chassisId, driveName, resp);
976                 });
977             break;
978         }
979         });
980 }
981 
982 /**
983  * This URL will show the drive interface for the specific drive in the chassis
984  */
985 inline void requestRoutesChassisDriveName(App& app)
986 {
987     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
988         .privileges(redfish::privileges::getChassis)
989         .methods(boost::beast::http::verb::get)(
990             std::bind_front(handleChassisDriveGet, std::ref(app)));
991 }
992 
993 inline void getStorageControllerAsset(
994     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
995     const boost::system::error_code& ec,
996     const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
997         propertiesList)
998 {
999     if (ec)
1000     {
1001         // this interface isn't necessary
1002         BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset");
1003         return;
1004     }
1005 
1006     const std::string* partNumber = nullptr;
1007     const std::string* serialNumber = nullptr;
1008     const std::string* manufacturer = nullptr;
1009     const std::string* model = nullptr;
1010     if (!sdbusplus::unpackPropertiesNoThrow(
1011             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
1012             partNumber, "SerialNumber", serialNumber, "Manufacturer",
1013             manufacturer, "Model", model))
1014     {
1015         messages::internalError(asyncResp->res);
1016         return;
1017     }
1018 
1019     if (partNumber != nullptr)
1020     {
1021         asyncResp->res.jsonValue["PartNumber"] = *partNumber;
1022     }
1023 
1024     if (serialNumber != nullptr)
1025     {
1026         asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
1027     }
1028 
1029     if (manufacturer != nullptr)
1030     {
1031         asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
1032     }
1033 
1034     if (model != nullptr)
1035     {
1036         asyncResp->res.jsonValue["Model"] = *model;
1037     }
1038 }
1039 
1040 inline void populateStorageController(
1041     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1042     const std::string& controllerId, const std::string& connectionName,
1043     const std::string& path)
1044 {
1045     asyncResp->res.jsonValue["@odata.type"] =
1046         "#StorageController.v1_6_0.StorageController";
1047     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1048         "/redfish/v1/Systems/system/Storage/1/Controllers/{}", controllerId);
1049     asyncResp->res.jsonValue["Name"] = controllerId;
1050     asyncResp->res.jsonValue["Id"] = controllerId;
1051     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
1052 
1053     sdbusplus::asio::getProperty<bool>(
1054         *crow::connections::systemBus, connectionName, path,
1055         "xyz.openbmc_project.Inventory.Item", "Present",
1056         [asyncResp](const boost::system::error_code& ec, bool isPresent) {
1057         // this interface isn't necessary, only check it
1058         // if we get a good return
1059         if (ec)
1060         {
1061             BMCWEB_LOG_DEBUG("Failed to get Present property");
1062             return;
1063         }
1064         if (!isPresent)
1065         {
1066             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
1067         }
1068         });
1069 
1070     sdbusplus::asio::getAllProperties(
1071         *crow::connections::systemBus, connectionName, path,
1072         "xyz.openbmc_project.Inventory.Decorator.Asset",
1073         [asyncResp](const boost::system::error_code& ec,
1074                     const std::vector<
1075                         std::pair<std::string, dbus::utility::DbusVariantType>>&
1076                         propertiesList) {
1077         getStorageControllerAsset(asyncResp, ec, propertiesList);
1078         });
1079 }
1080 
1081 inline void getStorageControllerHandler(
1082     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1083     const std::string& controllerId, const boost::system::error_code& ec,
1084     const dbus::utility::MapperGetSubTreeResponse& subtree)
1085 {
1086     if (ec || subtree.empty())
1087     {
1088         // doesn't have to be there
1089         BMCWEB_LOG_DEBUG("Failed to handle StorageController");
1090         return;
1091     }
1092 
1093     for (const auto& [path, interfaceDict] : subtree)
1094     {
1095         sdbusplus::message::object_path object(path);
1096         std::string id = object.filename();
1097         if (id.empty())
1098         {
1099             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1100             return;
1101         }
1102         if (id != controllerId)
1103         {
1104             continue;
1105         }
1106 
1107         if (interfaceDict.size() != 1)
1108         {
1109             BMCWEB_LOG_ERROR("Connection size {}, greater than 1",
1110                              interfaceDict.size());
1111             messages::internalError(asyncResp->res);
1112             return;
1113         }
1114 
1115         const std::string& connectionName = interfaceDict.front().first;
1116         populateStorageController(asyncResp, controllerId, connectionName,
1117                                   path);
1118     }
1119 }
1120 
1121 inline void populateStorageControllerCollection(
1122     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1123     const boost::system::error_code& ec,
1124     const dbus::utility::MapperGetSubTreePathsResponse& controllerList)
1125 {
1126     nlohmann::json::array_t members;
1127     if (ec || controllerList.empty())
1128     {
1129         asyncResp->res.jsonValue["Members"] = std::move(members);
1130         asyncResp->res.jsonValue["Members@odata.count"] = 0;
1131         BMCWEB_LOG_DEBUG("Failed to find any StorageController");
1132         return;
1133     }
1134 
1135     for (const std::string& path : controllerList)
1136     {
1137         std::string id = sdbusplus::message::object_path(path).filename();
1138         if (id.empty())
1139         {
1140             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1141             return;
1142         }
1143         nlohmann::json::object_t member;
1144         member["@odata.id"] = boost::urls::format(
1145             "/redfish/v1/Systems/system/Storage/1/Controllers/{}", id);
1146         members.emplace_back(member);
1147     }
1148     asyncResp->res.jsonValue["Members@odata.count"] = members.size();
1149     asyncResp->res.jsonValue["Members"] = std::move(members);
1150 }
1151 
1152 inline void handleSystemsStorageControllerCollectionGet(
1153     App& app, const crow::Request& req,
1154     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1155     const std::string& systemName)
1156 {
1157     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1158     {
1159         BMCWEB_LOG_DEBUG(
1160             "Failed to setup Redfish Route for StorageController Collection");
1161         return;
1162     }
1163     if (systemName != "system")
1164     {
1165         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1166                                    systemName);
1167         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1168         return;
1169     }
1170 
1171     asyncResp->res.jsonValue["@odata.type"] =
1172         "#StorageControllerCollection.StorageControllerCollection";
1173     asyncResp->res.jsonValue["@odata.id"] =
1174         "/redfish/v1/Systems/system/Storage/1/Controllers";
1175     asyncResp->res.jsonValue["Name"] = "Storage Controller Collection";
1176 
1177     constexpr std::array<std::string_view, 1> interfaces = {
1178         "xyz.openbmc_project.Inventory.Item.StorageController"};
1179     dbus::utility::getSubTreePaths(
1180         "/xyz/openbmc_project/inventory", 0, interfaces,
1181         [asyncResp](const boost::system::error_code& ec,
1182                     const dbus::utility::MapperGetSubTreePathsResponse&
1183                         controllerList) {
1184         populateStorageControllerCollection(asyncResp, ec, controllerList);
1185         });
1186 }
1187 
1188 inline void handleSystemsStorageControllerGet(
1189     App& app, const crow::Request& req,
1190     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1191     const std::string& systemName, const std::string& controllerId)
1192 {
1193     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1194     {
1195         BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController");
1196         return;
1197     }
1198     if (systemName != "system")
1199     {
1200         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1201                                    systemName);
1202         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1203         return;
1204     }
1205     constexpr std::array<std::string_view, 1> interfaces = {
1206         "xyz.openbmc_project.Inventory.Item.StorageController"};
1207     dbus::utility::getSubTree(
1208         "/xyz/openbmc_project/inventory", 0, interfaces,
1209         [asyncResp,
1210          controllerId](const boost::system::error_code& ec,
1211                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
1212         getStorageControllerHandler(asyncResp, controllerId, ec, subtree);
1213         });
1214 }
1215 
1216 inline void requestRoutesStorageControllerCollection(App& app)
1217 {
1218     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/")
1219         .privileges(redfish::privileges::getStorageControllerCollection)
1220         .methods(boost::beast::http::verb::get)(std::bind_front(
1221             handleSystemsStorageControllerCollectionGet, std::ref(app)));
1222 }
1223 
1224 inline void requestRoutesStorageController(App& app)
1225 {
1226     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>")
1227         .privileges(redfish::privileges::getStorageController)
1228         .methods(boost::beast::http::verb::get)(
1229             std::bind_front(handleSystemsStorageControllerGet, std::ref(app)));
1230 }
1231 
1232 } // namespace redfish
1233