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