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