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