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