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