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