1 /*
2 Copyright (c) 2019 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17
18 #include "bmcweb_config.h"
19
20 #include "app.hpp"
21 #include "dbus_utility.hpp"
22 #include "generated/enums/drive.hpp"
23 #include "generated/enums/protocol.hpp"
24 #include "generated/enums/resource.hpp"
25 #include "human_sort.hpp"
26 #include "query.hpp"
27 #include "redfish_util.hpp"
28 #include "registries/privilege_registry.hpp"
29 #include "utils/collection.hpp"
30 #include "utils/dbus_utils.hpp"
31
32 #include <boost/system/error_code.hpp>
33 #include <boost/url/format.hpp>
34 #include <sdbusplus/asio/property.hpp>
35 #include <sdbusplus/unpack_properties.hpp>
36
37 #include <array>
38 #include <ranges>
39 #include <string_view>
40
41 namespace redfish
42 {
43
handleSystemsStorageCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)44 inline void handleSystemsStorageCollectionGet(
45 App& app, const crow::Request& req,
46 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
47 const std::string& systemName)
48 {
49 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
50 {
51 return;
52 }
53 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
54 {
55 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
56 systemName);
57 return;
58 }
59
60 asyncResp->res.jsonValue["@odata.type"] =
61 "#StorageCollection.StorageCollection";
62 asyncResp->res.jsonValue["@odata.id"] = std::format(
63 "/redfish/v1/Systems/{}/Storage", BMCWEB_REDFISH_SYSTEM_URI_NAME);
64 asyncResp->res.jsonValue["Name"] = "Storage Collection";
65
66 constexpr std::array<std::string_view, 1> interface{
67 "xyz.openbmc_project.Inventory.Item.Storage"};
68 collection_util::getCollectionMembers(
69 asyncResp,
70 boost::urls::format("/redfish/v1/Systems/{}/Storage",
71 BMCWEB_REDFISH_SYSTEM_URI_NAME),
72 interface, "/xyz/openbmc_project/inventory");
73 }
74
handleStorageCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)75 inline void handleStorageCollectionGet(
76 App& app, const crow::Request& req,
77 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
78 {
79 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
80 {
81 return;
82 }
83 asyncResp->res.jsonValue["@odata.type"] =
84 "#StorageCollection.StorageCollection";
85 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage";
86 asyncResp->res.jsonValue["Name"] = "Storage Collection";
87 constexpr std::array<std::string_view, 1> interface{
88 "xyz.openbmc_project.Inventory.Item.Storage"};
89 collection_util::getCollectionMembers(
90 asyncResp, boost::urls::format("/redfish/v1/Storage"), interface,
91 "/xyz/openbmc_project/inventory");
92 }
93
requestRoutesStorageCollection(App & app)94 inline void requestRoutesStorageCollection(App& app)
95 {
96 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/")
97 .privileges(redfish::privileges::getStorageCollection)
98 .methods(boost::beast::http::verb::get)(
99 std::bind_front(handleSystemsStorageCollectionGet, std::ref(app)));
100 BMCWEB_ROUTE(app, "/redfish/v1/Storage/")
101 .privileges(redfish::privileges::getStorageCollection)
102 .methods(boost::beast::http::verb::get)(
103 std::bind_front(handleStorageCollectionGet, std::ref(app)));
104 }
105
afterChassisDriveCollectionSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & driveList)106 inline void afterChassisDriveCollectionSubtree(
107 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
108 const boost::system::error_code& ec,
109 const dbus::utility::MapperGetSubTreePathsResponse& driveList)
110 {
111 if (ec)
112 {
113 BMCWEB_LOG_ERROR("Drive mapper call error");
114 messages::internalError(asyncResp->res);
115 return;
116 }
117
118 nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"];
119 driveArray = nlohmann::json::array();
120 auto& count = asyncResp->res.jsonValue["Drives@odata.count"];
121 count = 0;
122
123 for (const std::string& drive : driveList)
124 {
125 sdbusplus::message::object_path object(drive);
126 if (object.filename().empty())
127 {
128 BMCWEB_LOG_ERROR("Failed to find filename in {}", drive);
129 return;
130 }
131
132 nlohmann::json::object_t driveJson;
133 driveJson["@odata.id"] = boost::urls::format(
134 "/redfish/v1/Systems/{}/Storage/1/Drives/{}",
135 BMCWEB_REDFISH_SYSTEM_URI_NAME, object.filename());
136 driveArray.emplace_back(std::move(driveJson));
137 }
138
139 count = driveArray.size();
140 }
getDrives(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)141 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
142 {
143 const std::array<std::string_view, 1> interfaces = {
144 "xyz.openbmc_project.Inventory.Item.Drive"};
145 dbus::utility::getSubTreePaths(
146 "/xyz/openbmc_project/inventory", 0, interfaces,
147 std::bind_front(afterChassisDriveCollectionSubtree, asyncResp));
148 }
149
afterSystemsStorageGetSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & storageId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)150 inline void afterSystemsStorageGetSubtree(
151 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
152 const std::string& storageId, const boost::system::error_code& ec,
153 const dbus::utility::MapperGetSubTreeResponse& subtree)
154 {
155 if (ec)
156 {
157 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error");
158 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
159 storageId);
160 return;
161 }
162 auto storage = std::ranges::find_if(
163 subtree,
164 [&storageId](const std::pair<std::string,
165 dbus::utility::MapperServiceMap>& object) {
166 return sdbusplus::message::object_path(object.first).filename() ==
167 storageId;
168 });
169 if (storage == subtree.end())
170 {
171 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
172 storageId);
173 return;
174 }
175
176 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
177 asyncResp->res.jsonValue["@odata.id"] =
178 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}",
179 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
180 asyncResp->res.jsonValue["Name"] = "Storage";
181 asyncResp->res.jsonValue["Id"] = storageId;
182 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
183
184 getDrives(asyncResp);
185 asyncResp->res.jsonValue["Controllers"]["@odata.id"] =
186 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}/Controllers",
187 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
188 }
189
handleSystemsStorageGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & storageId)190 inline void handleSystemsStorageGet(
191 App& app, const crow::Request& req,
192 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
193 const std::string& systemName, const std::string& storageId)
194 {
195 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
196 {
197 return;
198 }
199 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
200 {
201 // Option currently returns no systems. TBD
202 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
203 systemName);
204 return;
205 }
206
207 constexpr std::array<std::string_view, 1> interfaces = {
208 "xyz.openbmc_project.Inventory.Item.Storage"};
209 dbus::utility::getSubTree(
210 "/xyz/openbmc_project/inventory", 0, interfaces,
211 std::bind_front(afterSystemsStorageGetSubtree, asyncResp, storageId));
212 }
213
afterSubtree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & storageId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)214 inline void afterSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
215 const std::string& storageId,
216 const boost::system::error_code& ec,
217 const dbus::utility::MapperGetSubTreeResponse& subtree)
218 {
219 if (ec)
220 {
221 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error");
222 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
223 storageId);
224 return;
225 }
226 auto storage = std::ranges::find_if(
227 subtree,
228 [&storageId](const std::pair<std::string,
229 dbus::utility::MapperServiceMap>& object) {
230 return sdbusplus::message::object_path(object.first).filename() ==
231 storageId;
232 });
233 if (storage == subtree.end())
234 {
235 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage",
236 storageId);
237 return;
238 }
239
240 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
241 asyncResp->res.jsonValue["@odata.id"] =
242 boost::urls::format("/redfish/v1/Storage/{}", storageId);
243 asyncResp->res.jsonValue["Name"] = "Storage";
244 asyncResp->res.jsonValue["Id"] = storageId;
245 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
246
247 // Storage subsystem to Storage link.
248 nlohmann::json::array_t storageServices;
249 nlohmann::json::object_t storageService;
250 storageService["@odata.id"] =
251 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}",
252 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId);
253 storageServices.emplace_back(storageService);
254 asyncResp->res.jsonValue["Links"]["StorageServices"] =
255 std::move(storageServices);
256 asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = 1;
257 }
258
259 inline void
handleStorageGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & storageId)260 handleStorageGet(App& app, const crow::Request& req,
261 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
262 const std::string& storageId)
263 {
264 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
265 {
266 BMCWEB_LOG_DEBUG("requestRoutesStorage setUpRedfishRoute failed");
267 return;
268 }
269
270 constexpr std::array<std::string_view, 1> interfaces = {
271 "xyz.openbmc_project.Inventory.Item.Storage"};
272 dbus::utility::getSubTree(
273 "/xyz/openbmc_project/inventory", 0, interfaces,
274 std::bind_front(afterSubtree, asyncResp, storageId));
275 }
276
requestRoutesStorage(App & app)277 inline void requestRoutesStorage(App& app)
278 {
279 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/")
280 .privileges(redfish::privileges::getStorage)
281 .methods(boost::beast::http::verb::get)(
282 std::bind_front(handleSystemsStorageGet, std::ref(app)));
283
284 BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/")
285 .privileges(redfish::privileges::getStorage)
286 .methods(boost::beast::http::verb::get)(
287 std::bind_front(handleStorageGet, std::ref(app)));
288 }
289
getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)290 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
291 const std::string& connectionName,
292 const std::string& path)
293 {
294 sdbusplus::asio::getAllProperties(
295 *crow::connections::systemBus, connectionName, path,
296 "xyz.openbmc_project.Inventory.Decorator.Asset",
297 [asyncResp](const boost::system::error_code& ec,
298 const std::vector<
299 std::pair<std::string, dbus::utility::DbusVariantType>>&
300 propertiesList) {
301 if (ec)
302 {
303 // this interface isn't necessary
304 return;
305 }
306
307 const std::string* partNumber = nullptr;
308 const std::string* serialNumber = nullptr;
309 const std::string* manufacturer = nullptr;
310 const std::string* model = nullptr;
311
312 const bool success = sdbusplus::unpackPropertiesNoThrow(
313 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
314 partNumber, "SerialNumber", serialNumber, "Manufacturer",
315 manufacturer, "Model", model);
316
317 if (!success)
318 {
319 messages::internalError(asyncResp->res);
320 return;
321 }
322
323 if (partNumber != nullptr)
324 {
325 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
326 }
327
328 if (serialNumber != nullptr)
329 {
330 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
331 }
332
333 if (manufacturer != nullptr)
334 {
335 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
336 }
337
338 if (model != nullptr)
339 {
340 asyncResp->res.jsonValue["Model"] = *model;
341 }
342 });
343 }
344
getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)345 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
346 const std::string& connectionName,
347 const std::string& path)
348 {
349 sdbusplus::asio::getProperty<bool>(
350 *crow::connections::systemBus, connectionName, path,
351 "xyz.openbmc_project.Inventory.Item", "Present",
352 [asyncResp,
353 path](const boost::system::error_code& ec, const bool isPresent) {
354 // this interface isn't necessary, only check it if
355 // we get a good return
356 if (ec)
357 {
358 return;
359 }
360
361 if (!isPresent)
362 {
363 asyncResp->res.jsonValue["Status"]["State"] =
364 resource::State::Absent;
365 }
366 });
367 }
368
getDriveState(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & connectionName,const std::string & path)369 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
370 const std::string& connectionName,
371 const std::string& path)
372 {
373 sdbusplus::asio::getProperty<bool>(
374 *crow::connections::systemBus, connectionName, path,
375 "xyz.openbmc_project.State.Drive", "Rebuilding",
376 [asyncResp](const boost::system::error_code& ec, const bool updating) {
377 // this interface isn't necessary, only check it
378 // if we get a good return
379 if (ec)
380 {
381 return;
382 }
383
384 // updating and disabled in the backend shouldn't be
385 // able to be set at the same time, so we don't need
386 // to check for the race condition of these two
387 // calls
388 if (updating)
389 {
390 asyncResp->res.jsonValue["Status"]["State"] =
391 resource::State::Updating;
392 }
393 });
394 }
395
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
414 inline std::optional<protocol::Protocol>
convertDriveProtocol(std::string_view proto)415 convertDriveProtocol(std::string_view proto)
416 {
417 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
418 {
419 return protocol::Protocol::SAS;
420 }
421 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
422 {
423 return protocol::Protocol::SATA;
424 }
425 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
426 {
427 return protocol::Protocol::NVMe;
428 }
429 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
430 {
431 return protocol::Protocol::FC;
432 }
433 if (proto ==
434 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown")
435 {
436 return std::nullopt;
437 }
438
439 return protocol::Protocol::Invalid;
440 }
441
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 sdbusplus::asio::getAllProperties(
447 *crow::connections::systemBus, connectionName, path,
448 "xyz.openbmc_project.Inventory.Item.Drive",
449 [asyncResp](const boost::system::error_code& ec,
450 const std::vector<
451 std::pair<std::string, dbus::utility::DbusVariantType>>&
452 propertiesList) {
453 if (ec)
454 {
455 // this interface isn't required
456 return;
457 }
458 const std::string* encryptionStatus = nullptr;
459 const bool* isLocked = nullptr;
460 for (const std::pair<std::string, dbus::utility::DbusVariantType>&
461 property : propertiesList)
462 {
463 const std::string& propertyName = property.first;
464 if (propertyName == "Type")
465 {
466 const std::string* value =
467 std::get_if<std::string>(&property.second);
468 if (value == nullptr)
469 {
470 // illegal property
471 BMCWEB_LOG_ERROR("Illegal property: Type");
472 messages::internalError(asyncResp->res);
473 return;
474 }
475
476 std::optional<drive::MediaType> mediaType =
477 convertDriveType(*value);
478 if (!mediaType)
479 {
480 BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}",
481 *value);
482 continue;
483 }
484 if (*mediaType == drive::MediaType::Invalid)
485 {
486 messages::internalError(asyncResp->res);
487 return;
488 }
489
490 asyncResp->res.jsonValue["MediaType"] = *mediaType;
491 }
492 else if (propertyName == "Capacity")
493 {
494 const uint64_t* capacity =
495 std::get_if<uint64_t>(&property.second);
496 if (capacity == nullptr)
497 {
498 BMCWEB_LOG_ERROR("Illegal property: Capacity");
499 messages::internalError(asyncResp->res);
500 return;
501 }
502 if (*capacity == 0)
503 {
504 // drive capacity not known
505 continue;
506 }
507
508 asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
509 }
510 else if (propertyName == "Protocol")
511 {
512 const std::string* value =
513 std::get_if<std::string>(&property.second);
514 if (value == nullptr)
515 {
516 BMCWEB_LOG_ERROR("Illegal property: Protocol");
517 messages::internalError(asyncResp->res);
518 return;
519 }
520
521 std::optional<protocol::Protocol> proto =
522 convertDriveProtocol(*value);
523 if (!proto)
524 {
525 BMCWEB_LOG_WARNING(
526 "Unknown DrivePrototype Interface: {}", *value);
527 continue;
528 }
529 if (*proto == protocol::Protocol::Invalid)
530 {
531 messages::internalError(asyncResp->res);
532 return;
533 }
534 asyncResp->res.jsonValue["Protocol"] = *proto;
535 }
536 else if (propertyName == "PredictedMediaLifeLeftPercent")
537 {
538 const uint8_t* lifeLeft =
539 std::get_if<uint8_t>(&property.second);
540 if (lifeLeft == nullptr)
541 {
542 BMCWEB_LOG_ERROR(
543 "Illegal property: PredictedMediaLifeLeftPercent");
544 messages::internalError(asyncResp->res);
545 return;
546 }
547 // 255 means reading the value is not supported
548 if (*lifeLeft != 255)
549 {
550 asyncResp->res
551 .jsonValue["PredictedMediaLifeLeftPercent"] =
552 *lifeLeft;
553 }
554 }
555 else if (propertyName == "EncryptionStatus")
556 {
557 encryptionStatus =
558 std::get_if<std::string>(&property.second);
559 if (encryptionStatus == nullptr)
560 {
561 BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus");
562 messages::internalError(asyncResp->res);
563 return;
564 }
565 }
566 else if (propertyName == "Locked")
567 {
568 isLocked = std::get_if<bool>(&property.second);
569 if (isLocked == nullptr)
570 {
571 BMCWEB_LOG_ERROR("Illegal property: Locked");
572 messages::internalError(asyncResp->res);
573 return;
574 }
575 }
576 }
577
578 if (encryptionStatus == nullptr || isLocked == nullptr ||
579 *encryptionStatus ==
580 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown")
581 {
582 return;
583 }
584 if (*encryptionStatus !=
585 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted")
586 {
587 //"The drive is not currently encrypted."
588 asyncResp->res.jsonValue["EncryptionStatus"] =
589 drive::EncryptionStatus::Unencrypted;
590 return;
591 }
592 if (*isLocked)
593 {
594 //"The drive is currently encrypted and the data is not
595 // accessible to the user."
596 asyncResp->res.jsonValue["EncryptionStatus"] =
597 drive::EncryptionStatus::Locked;
598 return;
599 }
600 // if not locked
601 // "The drive is currently encrypted but the data is accessible
602 // to the user in unencrypted form."
603 asyncResp->res.jsonValue["EncryptionStatus"] =
604 drive::EncryptionStatus::Unlocked;
605 });
606 }
607
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 constexpr std::array<std::string_view, 2> interfaces = {
821 "xyz.openbmc_project.Inventory.Item.Board",
822 "xyz.openbmc_project.Inventory.Item.Chassis"};
823 dbus::utility::getSubTree(
824 "/xyz/openbmc_project/inventory", 0, interfaces,
825 std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp,
826 chassisId));
827 }
828
requestRoutesChassisDrive(App & app)829 inline void requestRoutesChassisDrive(App& app)
830 {
831 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
832 .privileges(redfish::privileges::getDriveCollection)
833 .methods(boost::beast::http::verb::get)(
834 std::bind_front(chassisDriveCollectionGet, std::ref(app)));
835 }
836
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)837 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
838 const std::string& chassisId,
839 const std::string& driveName,
840 const boost::system::error_code& ec,
841 const dbus::utility::MapperGetSubTreeResponse& subtree)
842 {
843 if (ec)
844 {
845 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
846 messages::internalError(asyncResp->res);
847 return;
848 }
849
850 // Iterate over all retrieved ObjectPaths.
851 for (const auto& [path, connectionNames] : subtree)
852 {
853 sdbusplus::message::object_path objPath(path);
854 if (objPath.filename() != driveName)
855 {
856 continue;
857 }
858
859 if (connectionNames.empty())
860 {
861 BMCWEB_LOG_ERROR("Got 0 Connection names");
862 continue;
863 }
864
865 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
866 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName);
867
868 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
869 asyncResp->res.jsonValue["Name"] = driveName;
870 asyncResp->res.jsonValue["Id"] = driveName;
871 // default it to Enabled
872 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
873
874 nlohmann::json::object_t linkChassisNav;
875 linkChassisNav["@odata.id"] =
876 boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
877 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
878
879 addAllDriveInfo(asyncResp, connectionNames[0].first, path,
880 connectionNames[0].second);
881 }
882 }
883
matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & driveName,const std::vector<std::string> & resp)884 inline void matchAndFillDrive(
885 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
886 const std::string& chassisId, const std::string& driveName,
887 const std::vector<std::string>& resp)
888 {
889 for (const std::string& drivePath : resp)
890 {
891 sdbusplus::message::object_path path(drivePath);
892 std::string leaf = path.filename();
893 if (leaf != driveName)
894 {
895 continue;
896 }
897 // mapper call drive
898 constexpr std::array<std::string_view, 1> driveInterface = {
899 "xyz.openbmc_project.Inventory.Item.Drive"};
900 dbus::utility::getSubTree(
901 "/xyz/openbmc_project/inventory", 0, driveInterface,
902 [asyncResp, chassisId, driveName](
903 const boost::system::error_code& ec,
904 const dbus::utility::MapperGetSubTreeResponse& subtree) {
905 buildDrive(asyncResp, chassisId, driveName, ec, subtree);
906 });
907 }
908 }
909
handleChassisDriveGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & driveName)910 inline void handleChassisDriveGet(
911 crow::App& app, const crow::Request& req,
912 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
913 const std::string& chassisId, const std::string& driveName)
914 {
915 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
916 {
917 return;
918 }
919 constexpr std::array<std::string_view, 2> interfaces = {
920 "xyz.openbmc_project.Inventory.Item.Board",
921 "xyz.openbmc_project.Inventory.Item.Chassis"};
922
923 // mapper call chassis
924 dbus::utility::getSubTree(
925 "/xyz/openbmc_project/inventory", 0, interfaces,
926 [asyncResp, chassisId,
927 driveName](const boost::system::error_code& ec,
928 const dbus::utility::MapperGetSubTreeResponse& subtree) {
929 if (ec)
930 {
931 messages::internalError(asyncResp->res);
932 return;
933 }
934
935 // Iterate over all retrieved ObjectPaths.
936 for (const auto& [path, connectionNames] : subtree)
937 {
938 sdbusplus::message::object_path objPath(path);
939 if (objPath.filename() != chassisId)
940 {
941 continue;
942 }
943
944 if (connectionNames.empty())
945 {
946 BMCWEB_LOG_ERROR("Got 0 Connection names");
947 continue;
948 }
949
950 dbus::utility::getAssociationEndPoints(
951 path + "/drive",
952 [asyncResp, chassisId,
953 driveName](const boost::system::error_code& ec3,
954 const dbus::utility::MapperEndPoints& resp) {
955 if (ec3)
956 {
957 return; // no drives = no failures
958 }
959 matchAndFillDrive(asyncResp, chassisId, driveName,
960 resp);
961 });
962 break;
963 }
964 });
965 }
966
967 /**
968 * This URL will show the drive interface for the specific drive in the chassis
969 */
requestRoutesChassisDriveName(App & app)970 inline void requestRoutesChassisDriveName(App& app)
971 {
972 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
973 .privileges(redfish::privileges::getChassis)
974 .methods(boost::beast::http::verb::get)(
975 std::bind_front(handleChassisDriveGet, std::ref(app)));
976 }
977
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)978 inline void getStorageControllerAsset(
979 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
980 const boost::system::error_code& ec,
981 const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
982 propertiesList)
983 {
984 if (ec)
985 {
986 // this interface isn't necessary
987 BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset");
988 return;
989 }
990
991 const std::string* partNumber = nullptr;
992 const std::string* serialNumber = nullptr;
993 const std::string* manufacturer = nullptr;
994 const std::string* model = nullptr;
995 if (!sdbusplus::unpackPropertiesNoThrow(
996 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
997 partNumber, "SerialNumber", serialNumber, "Manufacturer",
998 manufacturer, "Model", model))
999 {
1000 messages::internalError(asyncResp->res);
1001 return;
1002 }
1003
1004 if (partNumber != nullptr)
1005 {
1006 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
1007 }
1008
1009 if (serialNumber != nullptr)
1010 {
1011 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
1012 }
1013
1014 if (manufacturer != nullptr)
1015 {
1016 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
1017 }
1018
1019 if (model != nullptr)
1020 {
1021 asyncResp->res.jsonValue["Model"] = *model;
1022 }
1023 }
1024
populateStorageController(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & controllerId,const std::string & connectionName,const std::string & path)1025 inline void populateStorageController(
1026 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1027 const std::string& controllerId, const std::string& connectionName,
1028 const std::string& path)
1029 {
1030 asyncResp->res.jsonValue["@odata.type"] =
1031 "#StorageController.v1_6_0.StorageController";
1032 asyncResp->res.jsonValue["@odata.id"] =
1033 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}",
1034 BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId);
1035 asyncResp->res.jsonValue["Name"] = controllerId;
1036 asyncResp->res.jsonValue["Id"] = controllerId;
1037 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
1038
1039 sdbusplus::asio::getProperty<bool>(
1040 *crow::connections::systemBus, connectionName, path,
1041 "xyz.openbmc_project.Inventory.Item", "Present",
1042 [asyncResp](const boost::system::error_code& ec, bool isPresent) {
1043 // this interface isn't necessary, only check it
1044 // if we get a good return
1045 if (ec)
1046 {
1047 BMCWEB_LOG_DEBUG("Failed to get Present property");
1048 return;
1049 }
1050 if (!isPresent)
1051 {
1052 asyncResp->res.jsonValue["Status"]["State"] =
1053 resource::State::Absent;
1054 }
1055 });
1056
1057 sdbusplus::asio::getAllProperties(
1058 *crow::connections::systemBus, connectionName, path,
1059 "xyz.openbmc_project.Inventory.Decorator.Asset",
1060 [asyncResp](const boost::system::error_code& ec,
1061 const std::vector<
1062 std::pair<std::string, dbus::utility::DbusVariantType>>&
1063 propertiesList) {
1064 getStorageControllerAsset(asyncResp, ec, propertiesList);
1065 });
1066 }
1067
getStorageControllerHandler(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & controllerId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)1068 inline void getStorageControllerHandler(
1069 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1070 const std::string& controllerId, const boost::system::error_code& ec,
1071 const dbus::utility::MapperGetSubTreeResponse& subtree)
1072 {
1073 if (ec || subtree.empty())
1074 {
1075 // doesn't have to be there
1076 BMCWEB_LOG_DEBUG("Failed to handle StorageController");
1077 return;
1078 }
1079
1080 for (const auto& [path, interfaceDict] : subtree)
1081 {
1082 sdbusplus::message::object_path object(path);
1083 std::string id = object.filename();
1084 if (id.empty())
1085 {
1086 BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1087 return;
1088 }
1089 if (id != controllerId)
1090 {
1091 continue;
1092 }
1093
1094 if (interfaceDict.size() != 1)
1095 {
1096 BMCWEB_LOG_ERROR("Connection size {}, greater than 1",
1097 interfaceDict.size());
1098 messages::internalError(asyncResp->res);
1099 return;
1100 }
1101
1102 const std::string& connectionName = interfaceDict.front().first;
1103 populateStorageController(asyncResp, controllerId, connectionName,
1104 path);
1105 }
1106 }
1107
populateStorageControllerCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & controllerList)1108 inline void populateStorageControllerCollection(
1109 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1110 const boost::system::error_code& ec,
1111 const dbus::utility::MapperGetSubTreePathsResponse& controllerList)
1112 {
1113 nlohmann::json::array_t members;
1114 if (ec || controllerList.empty())
1115 {
1116 asyncResp->res.jsonValue["Members"] = std::move(members);
1117 asyncResp->res.jsonValue["Members@odata.count"] = 0;
1118 BMCWEB_LOG_DEBUG("Failed to find any StorageController");
1119 return;
1120 }
1121
1122 for (const std::string& path : controllerList)
1123 {
1124 std::string id = sdbusplus::message::object_path(path).filename();
1125 if (id.empty())
1126 {
1127 BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1128 return;
1129 }
1130 nlohmann::json::object_t member;
1131 member["@odata.id"] = boost::urls::format(
1132 "/redfish/v1/Systems/{}/Storage/1/Controllers/{}",
1133 BMCWEB_REDFISH_SYSTEM_URI_NAME, id);
1134 members.emplace_back(member);
1135 }
1136 asyncResp->res.jsonValue["Members@odata.count"] = members.size();
1137 asyncResp->res.jsonValue["Members"] = std::move(members);
1138 }
1139
handleSystemsStorageControllerCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)1140 inline void handleSystemsStorageControllerCollectionGet(
1141 App& app, const crow::Request& req,
1142 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1143 const std::string& systemName)
1144 {
1145 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1146 {
1147 BMCWEB_LOG_DEBUG(
1148 "Failed to setup Redfish Route for StorageController Collection");
1149 return;
1150 }
1151 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1152 {
1153 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1154 systemName);
1155 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1156 return;
1157 }
1158
1159 asyncResp->res.jsonValue["@odata.type"] =
1160 "#StorageControllerCollection.StorageControllerCollection";
1161 asyncResp->res.jsonValue["@odata.id"] =
1162 std::format("/redfish/v1/Systems/{}/Storage/1/Controllers",
1163 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1164 asyncResp->res.jsonValue["Name"] = "Storage Controller Collection";
1165
1166 constexpr std::array<std::string_view, 1> interfaces = {
1167 "xyz.openbmc_project.Inventory.Item.StorageController"};
1168 dbus::utility::getSubTreePaths(
1169 "/xyz/openbmc_project/inventory", 0, interfaces,
1170 [asyncResp](const boost::system::error_code& ec,
1171 const dbus::utility::MapperGetSubTreePathsResponse&
1172 controllerList) {
1173 populateStorageControllerCollection(asyncResp, ec, controllerList);
1174 });
1175 }
1176
handleSystemsStorageControllerGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & controllerId)1177 inline void handleSystemsStorageControllerGet(
1178 App& app, const crow::Request& req,
1179 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1180 const std::string& systemName, const std::string& controllerId)
1181 {
1182 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1183 {
1184 BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController");
1185 return;
1186 }
1187 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1188 {
1189 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1190 systemName);
1191 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1192 return;
1193 }
1194 constexpr std::array<std::string_view, 1> interfaces = {
1195 "xyz.openbmc_project.Inventory.Item.StorageController"};
1196 dbus::utility::getSubTree(
1197 "/xyz/openbmc_project/inventory", 0, interfaces,
1198 [asyncResp,
1199 controllerId](const boost::system::error_code& ec,
1200 const dbus::utility::MapperGetSubTreeResponse& subtree) {
1201 getStorageControllerHandler(asyncResp, controllerId, ec, subtree);
1202 });
1203 }
1204
requestRoutesStorageControllerCollection(App & app)1205 inline void requestRoutesStorageControllerCollection(App& app)
1206 {
1207 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/")
1208 .privileges(redfish::privileges::getStorageControllerCollection)
1209 .methods(boost::beast::http::verb::get)(std::bind_front(
1210 handleSystemsStorageControllerCollectionGet, std::ref(app)));
1211 }
1212
requestRoutesStorageController(App & app)1213 inline void requestRoutesStorageController(App& app)
1214 {
1215 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>")
1216 .privileges(redfish::privileges::getStorageController)
1217 .methods(boost::beast::http::verb::get)(
1218 std::bind_front(handleSystemsStorageControllerGet, std::ref(app)));
1219 }
1220
1221 } // namespace redfish
1222