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