xref: /openbmc/bmcweb/redfish-core/lib/storage.hpp (revision d8f53b82dc6fe14a4bd2ff2b020e54321a22e8f4)
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/chassis_utils.hpp"
22 #include "utils/collection.hpp"
23 #include "utils/dbus_utils.hpp"
24 
25 #include <boost/beast/http/verb.hpp>
26 #include <boost/system/error_code.hpp>
27 #include <boost/url/format.hpp>
28 #include <sdbusplus/message/native_types.hpp>
29 #include <sdbusplus/unpack_properties.hpp>
30 
31 #include <algorithm>
32 #include <array>
33 #include <cstdint>
34 #include <format>
35 #include <functional>
36 #include <memory>
37 #include <optional>
38 #include <ranges>
39 #include <string>
40 #include <string_view>
41 #include <utility>
42 #include <variant>
43 #include <vector>
44 
45 namespace redfish
46 {
47 
handleSystemsStorageCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)48 inline void handleSystemsStorageCollectionGet(
49     App& app, const crow::Request& req,
50     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
51     const std::string& systemName)
52 {
53     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
54     {
55         return;
56     }
57     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
58     {
59         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
60                                    systemName);
61         return;
62     }
63 
64     asyncResp->res.jsonValue["@odata.type"] =
65         "#StorageCollection.StorageCollection";
66     asyncResp->res.jsonValue["@odata.id"] = std::format(
67         "/redfish/v1/Systems/{}/Storage", BMCWEB_REDFISH_SYSTEM_URI_NAME);
68     asyncResp->res.jsonValue["Name"] = "Storage Collection";
69 
70     constexpr std::array<std::string_view, 1> interface{
71         "xyz.openbmc_project.Inventory.Item.Storage"};
72     collection_util::getCollectionMembers(
73         asyncResp,
74         boost::urls::format("/redfish/v1/Systems/{}/Storage",
75                             BMCWEB_REDFISH_SYSTEM_URI_NAME),
76         interface, "/xyz/openbmc_project/inventory");
77 }
78 
handleStorageCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)79 inline void handleStorageCollectionGet(
80     App& app, const crow::Request& req,
81     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
82 {
83     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
84     {
85         return;
86     }
87     asyncResp->res.jsonValue["@odata.type"] =
88         "#StorageCollection.StorageCollection";
89     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage";
90     asyncResp->res.jsonValue["Name"] = "Storage Collection";
91     constexpr std::array<std::string_view, 1> interface{
92         "xyz.openbmc_project.Inventory.Item.Storage"};
93     collection_util::getCollectionMembers(
94         asyncResp, boost::urls::format("/redfish/v1/Storage"), interface,
95         "/xyz/openbmc_project/inventory");
96 }
97 
requestRoutesStorageCollection(App & app)98 inline void requestRoutesStorageCollection(App& app)
99 {
100     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/")
101         .privileges(redfish::privileges::getStorageCollection)
102         .methods(boost::beast::http::verb::get)(
103             std::bind_front(handleSystemsStorageCollectionGet, std::ref(app)));
104     BMCWEB_ROUTE(app, "/redfish/v1/Storage/")
105         .privileges(redfish::privileges::getStorageCollection)
106         .methods(boost::beast::http::verb::get)(
107             std::bind_front(handleStorageCollectionGet, std::ref(app)));
108 }
109 
afterChassisDriveCollectionSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & driveList)110 inline void afterChassisDriveCollectionSubtree(
111     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
112     const boost::system::error_code& ec,
113     const dbus::utility::MapperGetSubTreePathsResponse& driveList)
114 {
115     if (ec)
116     {
117         BMCWEB_LOG_ERROR("Drive mapper call error");
118         messages::internalError(asyncResp->res);
119         return;
120     }
121 
122     nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"];
123     driveArray = nlohmann::json::array();
124     auto& count = asyncResp->res.jsonValue["Drives@odata.count"];
125     count = 0;
126 
127     for (const std::string& drive : driveList)
128     {
129         sdbusplus::message::object_path object(drive);
130         if (object.filename().empty())
131         {
132             BMCWEB_LOG_ERROR("Failed to find filename in {}", drive);
133             return;
134         }
135 
136         nlohmann::json::object_t driveJson;
137         driveJson["@odata.id"] = boost::urls::format(
138             "/redfish/v1/Systems/{}/Storage/1/Drives/{}",
139             BMCWEB_REDFISH_SYSTEM_URI_NAME, object.filename());
140         driveArray.emplace_back(std::move(driveJson));
141     }
142 
143     count = driveArray.size();
144 }
getDrives(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)145 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
146 {
147     const std::array<std::string_view, 1> interfaces = {
148         "xyz.openbmc_project.Inventory.Item.Drive"};
149     dbus::utility::getSubTreePaths(
150         "/xyz/openbmc_project/inventory", 0, interfaces,
151         std::bind_front(afterChassisDriveCollectionSubtree, asyncResp));
152 }
153 
afterSystemsStorageGetSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & storageId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)154 inline void afterSystemsStorageGetSubtree(
155     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
156     const std::string& storageId, const boost::system::error_code& ec,
157     const dbus::utility::MapperGetSubTreeResponse& subtree)
158 {
159     if (ec)
160     {
161         BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error");
162         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
163                                    storageId);
164         return;
165     }
166     auto storage = std::ranges::find_if(
167         subtree,
168         [&storageId](const std::pair<std::string,
169                                      dbus::utility::MapperServiceMap>& object) {
170             return sdbusplus::message::object_path(object.first).filename() ==
171                    storageId;
172         });
173     if (storage == subtree.end())
174     {
175         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
176                                    storageId);
177         return;
178     }
179 
180     asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
181     asyncResp->res.jsonValue["@odata.id"] =
182         boost::urls::format("/redfish/v1/Systems/{}/Storage/{}",
183                             BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
184     asyncResp->res.jsonValue["Name"] = "Storage";
185     asyncResp->res.jsonValue["Id"] = storageId;
186     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
187 
188     getDrives(asyncResp);
189     asyncResp->res.jsonValue["Controllers"]["@odata.id"] =
190         boost::urls::format("/redfish/v1/Systems/{}/Storage/{}/Controllers",
191                             BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
192 }
193 
handleSystemsStorageGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & storageId)194 inline void handleSystemsStorageGet(
195     App& app, const crow::Request& req,
196     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
197     const std::string& systemName, const std::string& storageId)
198 {
199     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
200     {
201         return;
202     }
203     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
204     {
205         // Option currently returns no systems.  TBD
206         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
207                                    systemName);
208         return;
209     }
210 
211     constexpr std::array<std::string_view, 1> interfaces = {
212         "xyz.openbmc_project.Inventory.Item.Storage"};
213     dbus::utility::getSubTree(
214         "/xyz/openbmc_project/inventory", 0, interfaces,
215         std::bind_front(afterSystemsStorageGetSubtree, asyncResp, storageId));
216 }
217 
afterSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & storageId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)218 inline void afterSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
219                          const std::string& storageId,
220                          const boost::system::error_code& ec,
221                          const dbus::utility::MapperGetSubTreeResponse& subtree)
222 {
223     if (ec)
224     {
225         BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error");
226         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
227                                    storageId);
228         return;
229     }
230     auto storage = std::ranges::find_if(
231         subtree,
232         [&storageId](const std::pair<std::string,
233                                      dbus::utility::MapperServiceMap>& object) {
234             return sdbusplus::message::object_path(object.first).filename() ==
235                    storageId;
236         });
237     if (storage == subtree.end())
238     {
239         messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
240                                    storageId);
241         return;
242     }
243 
244     asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
245     asyncResp->res.jsonValue["@odata.id"] =
246         boost::urls::format("/redfish/v1/Storage/{}", storageId);
247     asyncResp->res.jsonValue["Name"] = "Storage";
248     asyncResp->res.jsonValue["Id"] = storageId;
249     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
250 
251     // Storage subsystem to Storage link.
252     nlohmann::json::array_t storageServices;
253     nlohmann::json::object_t storageService;
254     storageService["@odata.id"] =
255         boost::urls::format("/redfish/v1/Systems/{}/Storage/{}",
256                             BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
257     storageServices.emplace_back(storageService);
258     asyncResp->res.jsonValue["Links"]["StorageServices"] =
259         std::move(storageServices);
260     asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = 1;
261 }
262 
handleStorageGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & storageId)263 inline void handleStorageGet(
264     App& app, const crow::Request& req,
265     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
266     const std::string& storageId)
267 {
268     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
269     {
270         BMCWEB_LOG_DEBUG("requestRoutesStorage setUpRedfishRoute failed");
271         return;
272     }
273 
274     constexpr std::array<std::string_view, 1> interfaces = {
275         "xyz.openbmc_project.Inventory.Item.Storage"};
276     dbus::utility::getSubTree(
277         "/xyz/openbmc_project/inventory", 0, interfaces,
278         std::bind_front(afterSubtree, asyncResp, storageId));
279 }
280 
requestRoutesStorage(App & app)281 inline void requestRoutesStorage(App& app)
282 {
283     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/")
284         .privileges(redfish::privileges::getStorage)
285         .methods(boost::beast::http::verb::get)(
286             std::bind_front(handleSystemsStorageGet, std::ref(app)));
287 
288     BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/")
289         .privileges(redfish::privileges::getStorage)
290         .methods(boost::beast::http::verb::get)(
291             std::bind_front(handleStorageGet, std::ref(app)));
292 }
293 
getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)294 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
295                           const std::string& connectionName,
296                           const std::string& path)
297 {
298     dbus::utility::getAllProperties(
299         connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
300         [asyncResp](const boost::system::error_code& ec,
301                     const std::vector<
302                         std::pair<std::string, dbus::utility::DbusVariantType>>&
303                         propertiesList) {
304             if (ec)
305             {
306                 // this interface isn't necessary
307                 return;
308             }
309 
310             const std::string* partNumber = nullptr;
311             const std::string* serialNumber = nullptr;
312             const std::string* manufacturer = nullptr;
313             const std::string* model = nullptr;
314 
315             const bool success = sdbusplus::unpackPropertiesNoThrow(
316                 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
317                 partNumber, "SerialNumber", serialNumber, "Manufacturer",
318                 manufacturer, "Model", model);
319 
320             if (!success)
321             {
322                 messages::internalError(asyncResp->res);
323                 return;
324             }
325 
326             if (partNumber != nullptr)
327             {
328                 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
329             }
330 
331             if (serialNumber != nullptr)
332             {
333                 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
334             }
335 
336             if (manufacturer != nullptr)
337             {
338                 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
339             }
340 
341             if (model != nullptr)
342             {
343                 asyncResp->res.jsonValue["Model"] = *model;
344             }
345         });
346 }
347 
getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)348 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
349                             const std::string& connectionName,
350                             const std::string& path)
351 {
352     dbus::utility::getProperty<bool>(
353         connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present",
354         [asyncResp,
355          path](const boost::system::error_code& ec, const bool isPresent) {
356             // this interface isn't necessary, only check it if
357             // we get a good return
358             if (ec)
359             {
360                 return;
361             }
362 
363             if (!isPresent)
364             {
365                 asyncResp->res.jsonValue["Status"]["State"] =
366                     resource::State::Absent;
367             }
368         });
369 }
370 
getDriveState(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)371 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
372                           const std::string& connectionName,
373                           const std::string& path)
374 {
375     dbus::utility::getProperty<bool>(
376         connectionName, path, "xyz.openbmc_project.State.Drive", "Rebuilding",
377         [asyncResp](const boost::system::error_code& ec, const bool updating) {
378             // this interface isn't necessary, only check it
379             // if we get a good return
380             if (ec)
381             {
382                 return;
383             }
384 
385             // updating and disabled in the backend shouldn't be
386             // able to be set at the same time, so we don't need
387             // to check for the race condition of these two
388             // calls
389             if (updating)
390             {
391                 asyncResp->res.jsonValue["Status"]["State"] =
392                     resource::State::Updating;
393             }
394         });
395 }
396 
convertDriveType(std::string_view type)397 inline std::optional<drive::MediaType> convertDriveType(std::string_view type)
398 {
399     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
400     {
401         return drive::MediaType::HDD;
402     }
403     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
404     {
405         return drive::MediaType::SSD;
406     }
407     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown")
408     {
409         return std::nullopt;
410     }
411 
412     return drive::MediaType::Invalid;
413 }
414 
convertDriveProtocol(std::string_view proto)415 inline std::optional<protocol::Protocol> convertDriveProtocol(
416     std::string_view proto)
417 {
418     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
419     {
420         return protocol::Protocol::SAS;
421     }
422     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
423     {
424         return protocol::Protocol::SATA;
425     }
426     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
427     {
428         return protocol::Protocol::NVMe;
429     }
430     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
431     {
432         return protocol::Protocol::FC;
433     }
434     if (proto ==
435         "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown")
436     {
437         return std::nullopt;
438     }
439 
440     return protocol::Protocol::Invalid;
441 }
442 
getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)443 inline void getDriveItemProperties(
444     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
445     const std::string& connectionName, const std::string& path)
446 {
447     dbus::utility::getAllProperties(
448         connectionName, path, "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 
addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path,const std::vector<std::string> & interfaces)608 inline 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 
afterGetSubtreeSystemsStorageDrive(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & driveId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)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 
handleSystemsStorageDriveGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & driveId)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 
requestRoutesDrive(App & app)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 
afterChassisDriveCollectionSubtreeGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)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  */
chassisDriveCollectionGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)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     dbus::utility::getSubTree(
821         "/xyz/openbmc_project/inventory", 0, chassisInterfaces,
822         std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp,
823                         chassisId));
824 }
825 
requestRoutesChassisDrive(App & app)826 inline void requestRoutesChassisDrive(App& app)
827 {
828     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
829         .privileges(redfish::privileges::getDriveCollection)
830         .methods(boost::beast::http::verb::get)(
831             std::bind_front(chassisDriveCollectionGet, std::ref(app)));
832 }
833 
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)834 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
835                        const std::string& chassisId,
836                        const std::string& driveName,
837                        const boost::system::error_code& ec,
838                        const dbus::utility::MapperGetSubTreeResponse& subtree)
839 {
840     if (ec)
841     {
842         BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
843         messages::internalError(asyncResp->res);
844         return;
845     }
846 
847     // Iterate over all retrieved ObjectPaths.
848     for (const auto& [path, connectionNames] : subtree)
849     {
850         sdbusplus::message::object_path objPath(path);
851         if (objPath.filename() != driveName)
852         {
853             continue;
854         }
855 
856         if (connectionNames.empty())
857         {
858             BMCWEB_LOG_ERROR("Got 0 Connection names");
859             continue;
860         }
861 
862         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
863             "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName);
864 
865         asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
866         asyncResp->res.jsonValue["Name"] = driveName;
867         asyncResp->res.jsonValue["Id"] = driveName;
868         // default it to Enabled
869         asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
870 
871         nlohmann::json::object_t linkChassisNav;
872         linkChassisNav["@odata.id"] =
873             boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
874         asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
875 
876         addAllDriveInfo(asyncResp, connectionNames[0].first, path,
877                         connectionNames[0].second);
878     }
879 }
880 
matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & driveName,const std::vector<std::string> & resp)881 inline void matchAndFillDrive(
882     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
883     const std::string& chassisId, const std::string& driveName,
884     const std::vector<std::string>& resp)
885 {
886     for (const std::string& drivePath : resp)
887     {
888         sdbusplus::message::object_path path(drivePath);
889         std::string leaf = path.filename();
890         if (leaf != driveName)
891         {
892             continue;
893         }
894         //  mapper call drive
895         constexpr std::array<std::string_view, 1> driveInterface = {
896             "xyz.openbmc_project.Inventory.Item.Drive"};
897         dbus::utility::getSubTree(
898             "/xyz/openbmc_project/inventory", 0, driveInterface,
899             [asyncResp, chassisId, driveName](
900                 const boost::system::error_code& ec,
901                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
902                 buildDrive(asyncResp, chassisId, driveName, ec, subtree);
903             });
904     }
905 }
906 
handleChassisDriveGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & driveName)907 inline void handleChassisDriveGet(
908     crow::App& app, const crow::Request& req,
909     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
910     const std::string& chassisId, const std::string& driveName)
911 {
912     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
913     {
914         return;
915     }
916 
917     // mapper call chassis
918     dbus::utility::getSubTree(
919         "/xyz/openbmc_project/inventory", 0, chassisInterfaces,
920         [asyncResp, chassisId,
921          driveName](const boost::system::error_code& ec,
922                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
923             if (ec)
924             {
925                 messages::internalError(asyncResp->res);
926                 return;
927             }
928 
929             // Iterate over all retrieved ObjectPaths.
930             for (const auto& [path, connectionNames] : subtree)
931             {
932                 sdbusplus::message::object_path objPath(path);
933                 if (objPath.filename() != chassisId)
934                 {
935                     continue;
936                 }
937 
938                 if (connectionNames.empty())
939                 {
940                     BMCWEB_LOG_ERROR("Got 0 Connection names");
941                     continue;
942                 }
943 
944                 dbus::utility::getAssociationEndPoints(
945                     path + "/drive",
946                     [asyncResp, chassisId,
947                      driveName](const boost::system::error_code& ec3,
948                                 const dbus::utility::MapperEndPoints& resp) {
949                         if (ec3)
950                         {
951                             return; // no drives = no failures
952                         }
953                         matchAndFillDrive(asyncResp, chassisId, driveName,
954                                           resp);
955                     });
956                 return;
957             }
958             // Couldn't find an object with that name.  return an error
959             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
960         });
961 }
962 
963 /**
964  * This URL will show the drive interface for the specific drive in the chassis
965  */
requestRoutesChassisDriveName(App & app)966 inline void requestRoutesChassisDriveName(App& app)
967 {
968     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
969         .privileges(redfish::privileges::getChassis)
970         .methods(boost::beast::http::verb::get)(
971             std::bind_front(handleChassisDriveGet, std::ref(app)));
972 }
973 
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)974 inline void getStorageControllerAsset(
975     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
976     const boost::system::error_code& ec,
977     const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
978         propertiesList)
979 {
980     if (ec)
981     {
982         // this interface isn't necessary
983         BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset");
984         return;
985     }
986 
987     const std::string* partNumber = nullptr;
988     const std::string* serialNumber = nullptr;
989     const std::string* manufacturer = nullptr;
990     const std::string* model = nullptr;
991     if (!sdbusplus::unpackPropertiesNoThrow(
992             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
993             partNumber, "SerialNumber", serialNumber, "Manufacturer",
994             manufacturer, "Model", model))
995     {
996         messages::internalError(asyncResp->res);
997         return;
998     }
999 
1000     if (partNumber != nullptr)
1001     {
1002         asyncResp->res.jsonValue["PartNumber"] = *partNumber;
1003     }
1004 
1005     if (serialNumber != nullptr)
1006     {
1007         asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
1008     }
1009 
1010     if (manufacturer != nullptr)
1011     {
1012         asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
1013     }
1014 
1015     if (model != nullptr)
1016     {
1017         asyncResp->res.jsonValue["Model"] = *model;
1018     }
1019 }
1020 
populateStorageController(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & controllerId,const std::string & connectionName,const std::string & path)1021 inline void populateStorageController(
1022     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1023     const std::string& controllerId, const std::string& connectionName,
1024     const std::string& path)
1025 {
1026     asyncResp->res.jsonValue["@odata.type"] =
1027         "#StorageController.v1_6_0.StorageController";
1028     asyncResp->res.jsonValue["@odata.id"] =
1029         boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}",
1030                             BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId);
1031     asyncResp->res.jsonValue["Name"] = controllerId;
1032     asyncResp->res.jsonValue["Id"] = controllerId;
1033     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
1034 
1035     dbus::utility::getProperty<bool>(
1036         connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present",
1037         [asyncResp](const boost::system::error_code& ec, bool isPresent) {
1038             // this interface isn't necessary, only check it
1039             // if we get a good return
1040             if (ec)
1041             {
1042                 BMCWEB_LOG_DEBUG("Failed to get Present property");
1043                 return;
1044             }
1045             if (!isPresent)
1046             {
1047                 asyncResp->res.jsonValue["Status"]["State"] =
1048                     resource::State::Absent;
1049             }
1050         });
1051 
1052     dbus::utility::getAllProperties(
1053         connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
1054         [asyncResp](const boost::system::error_code& ec,
1055                     const std::vector<
1056                         std::pair<std::string, dbus::utility::DbusVariantType>>&
1057                         propertiesList) {
1058             getStorageControllerAsset(asyncResp, ec, propertiesList);
1059         });
1060 }
1061 
getStorageControllerHandler(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & controllerId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)1062 inline void getStorageControllerHandler(
1063     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1064     const std::string& controllerId, const boost::system::error_code& ec,
1065     const dbus::utility::MapperGetSubTreeResponse& subtree)
1066 {
1067     if (ec || subtree.empty())
1068     {
1069         // doesn't have to be there
1070         BMCWEB_LOG_DEBUG("Failed to handle StorageController");
1071         return;
1072     }
1073 
1074     for (const auto& [path, interfaceDict] : subtree)
1075     {
1076         sdbusplus::message::object_path object(path);
1077         std::string id = object.filename();
1078         if (id.empty())
1079         {
1080             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1081             return;
1082         }
1083         if (id != controllerId)
1084         {
1085             continue;
1086         }
1087 
1088         if (interfaceDict.size() != 1)
1089         {
1090             BMCWEB_LOG_ERROR("Connection size {}, greater than 1",
1091                              interfaceDict.size());
1092             messages::internalError(asyncResp->res);
1093             return;
1094         }
1095 
1096         const std::string& connectionName = interfaceDict.front().first;
1097         populateStorageController(asyncResp, controllerId, connectionName,
1098                                   path);
1099     }
1100 }
1101 
populateStorageControllerCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & controllerList)1102 inline void populateStorageControllerCollection(
1103     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1104     const boost::system::error_code& ec,
1105     const dbus::utility::MapperGetSubTreePathsResponse& controllerList)
1106 {
1107     nlohmann::json::array_t members;
1108     if (ec || controllerList.empty())
1109     {
1110         asyncResp->res.jsonValue["Members"] = std::move(members);
1111         asyncResp->res.jsonValue["Members@odata.count"] = 0;
1112         BMCWEB_LOG_DEBUG("Failed to find any StorageController");
1113         return;
1114     }
1115 
1116     for (const std::string& path : controllerList)
1117     {
1118         std::string id = sdbusplus::message::object_path(path).filename();
1119         if (id.empty())
1120         {
1121             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1122             return;
1123         }
1124         nlohmann::json::object_t member;
1125         member["@odata.id"] = boost::urls::format(
1126             "/redfish/v1/Systems/{}/Storage/1/Controllers/{}",
1127             BMCWEB_REDFISH_SYSTEM_URI_NAME, id);
1128         members.emplace_back(member);
1129     }
1130     asyncResp->res.jsonValue["Members@odata.count"] = members.size();
1131     asyncResp->res.jsonValue["Members"] = std::move(members);
1132 }
1133 
handleSystemsStorageControllerCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)1134 inline void handleSystemsStorageControllerCollectionGet(
1135     App& app, const crow::Request& req,
1136     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1137     const std::string& systemName)
1138 {
1139     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1140     {
1141         BMCWEB_LOG_DEBUG(
1142             "Failed to setup Redfish Route for StorageController Collection");
1143         return;
1144     }
1145     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1146     {
1147         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1148                                    systemName);
1149         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1150         return;
1151     }
1152 
1153     asyncResp->res.jsonValue["@odata.type"] =
1154         "#StorageControllerCollection.StorageControllerCollection";
1155     asyncResp->res.jsonValue["@odata.id"] =
1156         std::format("/redfish/v1/Systems/{}/Storage/1/Controllers",
1157                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
1158     asyncResp->res.jsonValue["Name"] = "Storage Controller Collection";
1159 
1160     constexpr std::array<std::string_view, 1> interfaces = {
1161         "xyz.openbmc_project.Inventory.Item.StorageController"};
1162     dbus::utility::getSubTreePaths(
1163         "/xyz/openbmc_project/inventory", 0, interfaces,
1164         [asyncResp](const boost::system::error_code& ec,
1165                     const dbus::utility::MapperGetSubTreePathsResponse&
1166                         controllerList) {
1167             populateStorageControllerCollection(asyncResp, ec, controllerList);
1168         });
1169 }
1170 
handleSystemsStorageControllerGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & controllerId)1171 inline void handleSystemsStorageControllerGet(
1172     App& app, const crow::Request& req,
1173     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1174     const std::string& systemName, const std::string& controllerId)
1175 {
1176     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1177     {
1178         BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController");
1179         return;
1180     }
1181     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1182     {
1183         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1184                                    systemName);
1185         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1186         return;
1187     }
1188     constexpr std::array<std::string_view, 1> interfaces = {
1189         "xyz.openbmc_project.Inventory.Item.StorageController"};
1190     dbus::utility::getSubTree(
1191         "/xyz/openbmc_project/inventory", 0, interfaces,
1192         [asyncResp,
1193          controllerId](const boost::system::error_code& ec,
1194                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
1195             getStorageControllerHandler(asyncResp, controllerId, ec, subtree);
1196         });
1197 }
1198 
requestRoutesStorageControllerCollection(App & app)1199 inline void requestRoutesStorageControllerCollection(App& app)
1200 {
1201     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/")
1202         .privileges(redfish::privileges::getStorageControllerCollection)
1203         .methods(boost::beast::http::verb::get)(std::bind_front(
1204             handleSystemsStorageControllerCollectionGet, std::ref(app)));
1205 }
1206 
requestRoutesStorageController(App & app)1207 inline void requestRoutesStorageController(App& app)
1208 {
1209     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>")
1210         .privileges(redfish::privileges::getStorageController)
1211         .methods(boost::beast::http::verb::get)(
1212             std::bind_front(handleSystemsStorageControllerGet, std::ref(app)));
1213 }
1214 
1215 } // namespace redfish
1216