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