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