xref: /openbmc/bmcweb/redfish-core/lib/storage.hpp (revision 9d33a47c)
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 <ranges>
40 #include <string_view>
41 
42 namespace redfish
43 {
44 
45 inline void handleSystemsStorageCollectionGet(
46     App& app, const crow::Request& req,
47     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
48     const std::string& systemName)
49 {
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 
67     constexpr std::array<std::string_view, 1> interface{
68         "xyz.openbmc_project.Inventory.Item.Storage"};
69     collection_util::getCollectionMembers(
70         asyncResp, boost::urls::format("/redfish/v1/Systems/system/Storage"),
71         interface, "/xyz/openbmc_project/inventory");
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     collection_util::getCollectionMembers(
89         asyncResp, boost::urls::format("/redfish/v1/Storage"), interface,
90         "/xyz/openbmc_project/inventory");
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::ranges::find_if(
170         subtree,
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::ranges::find_if(
239         subtree,
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::ranges::find_if(
655         subtree,
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::ranges::sort(leafNames, AlphanumLess<std::string>());
804 
805             for (const auto& leafName : leafNames)
806             {
807                 nlohmann::json::object_t member;
808                 member["@odata.id"] = boost::urls::format(
809                     "/redfish/v1/Chassis/{}/Drives/{}", chassisId, leafName);
810                 members.emplace_back(std::move(member));
811                 // navigation links will be registered in next patch set
812             }
813             asyncResp->res.jsonValue["Members@odata.count"] = resp.size();
814         }); // end association lambda
815 
816     }       // end Iterate over all retrieved ObjectPaths
817 }
818 /**
819  * Chassis drives, this URL will show all the DriveCollection
820  * information
821  */
822 inline void chassisDriveCollectionGet(
823     crow::App& app, const crow::Request& req,
824     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
825     const std::string& chassisId)
826 {
827     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
828     {
829         return;
830     }
831 
832     // mapper call lambda
833     constexpr std::array<std::string_view, 2> interfaces = {
834         "xyz.openbmc_project.Inventory.Item.Board",
835         "xyz.openbmc_project.Inventory.Item.Chassis"};
836     dbus::utility::getSubTree(
837         "/xyz/openbmc_project/inventory", 0, interfaces,
838         std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp,
839                         chassisId));
840 }
841 
842 inline void requestRoutesChassisDrive(App& app)
843 {
844     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
845         .privileges(redfish::privileges::getDriveCollection)
846         .methods(boost::beast::http::verb::get)(
847             std::bind_front(chassisDriveCollectionGet, std::ref(app)));
848 }
849 
850 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
851                        const std::string& chassisId,
852                        const std::string& driveName,
853                        const boost::system::error_code& ec,
854                        const dbus::utility::MapperGetSubTreeResponse& subtree)
855 {
856     if (ec)
857     {
858         BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
859         messages::internalError(asyncResp->res);
860         return;
861     }
862 
863     // Iterate over all retrieved ObjectPaths.
864     for (const auto& [path, connectionNames] : subtree)
865     {
866         sdbusplus::message::object_path objPath(path);
867         if (objPath.filename() != driveName)
868         {
869             continue;
870         }
871 
872         if (connectionNames.empty())
873         {
874             BMCWEB_LOG_ERROR("Got 0 Connection names");
875             continue;
876         }
877 
878         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
879             "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName);
880 
881         asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
882         asyncResp->res.jsonValue["Name"] = driveName;
883         asyncResp->res.jsonValue["Id"] = driveName;
884         // default it to Enabled
885         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
886 
887         nlohmann::json::object_t linkChassisNav;
888         linkChassisNav["@odata.id"] =
889             boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
890         asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
891 
892         addAllDriveInfo(asyncResp, connectionNames[0].first, path,
893                         connectionNames[0].second);
894     }
895 }
896 
897 inline void
898     matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
899                       const std::string& chassisId,
900                       const std::string& driveName,
901                       const std::vector<std::string>& resp)
902 {
903     for (const std::string& drivePath : resp)
904     {
905         sdbusplus::message::object_path path(drivePath);
906         std::string leaf = path.filename();
907         if (leaf != driveName)
908         {
909             continue;
910         }
911         //  mapper call drive
912         constexpr std::array<std::string_view, 1> driveInterface = {
913             "xyz.openbmc_project.Inventory.Item.Drive"};
914         dbus::utility::getSubTree(
915             "/xyz/openbmc_project/inventory", 0, driveInterface,
916             [asyncResp, chassisId, driveName](
917                 const boost::system::error_code& ec,
918                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
919             buildDrive(asyncResp, chassisId, driveName, ec, subtree);
920         });
921     }
922 }
923 
924 inline void
925     handleChassisDriveGet(crow::App& app, const crow::Request& req,
926                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
927                           const std::string& chassisId,
928                           const std::string& driveName)
929 {
930     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
931     {
932         return;
933     }
934     constexpr std::array<std::string_view, 2> interfaces = {
935         "xyz.openbmc_project.Inventory.Item.Board",
936         "xyz.openbmc_project.Inventory.Item.Chassis"};
937 
938     // mapper call chassis
939     dbus::utility::getSubTree(
940         "/xyz/openbmc_project/inventory", 0, interfaces,
941         [asyncResp, chassisId,
942          driveName](const boost::system::error_code& ec,
943                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
944         if (ec)
945         {
946             messages::internalError(asyncResp->res);
947             return;
948         }
949 
950         // Iterate over all retrieved ObjectPaths.
951         for (const auto& [path, connectionNames] : subtree)
952         {
953             sdbusplus::message::object_path objPath(path);
954             if (objPath.filename() != chassisId)
955             {
956                 continue;
957             }
958 
959             if (connectionNames.empty())
960             {
961                 BMCWEB_LOG_ERROR("Got 0 Connection names");
962                 continue;
963             }
964 
965             dbus::utility::getAssociationEndPoints(
966                 path + "/drive",
967                 [asyncResp, chassisId,
968                  driveName](const boost::system::error_code& ec3,
969                             const dbus::utility::MapperEndPoints& resp) {
970                 if (ec3)
971                 {
972                     return; // no drives = no failures
973                 }
974                 matchAndFillDrive(asyncResp, chassisId, driveName, resp);
975             });
976             break;
977         }
978     });
979 }
980 
981 /**
982  * This URL will show the drive interface for the specific drive in the chassis
983  */
984 inline void requestRoutesChassisDriveName(App& app)
985 {
986     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
987         .privileges(redfish::privileges::getChassis)
988         .methods(boost::beast::http::verb::get)(
989             std::bind_front(handleChassisDriveGet, std::ref(app)));
990 }
991 
992 inline void getStorageControllerAsset(
993     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
994     const boost::system::error_code& ec,
995     const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
996         propertiesList)
997 {
998     if (ec)
999     {
1000         // this interface isn't necessary
1001         BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset");
1002         return;
1003     }
1004 
1005     const std::string* partNumber = nullptr;
1006     const std::string* serialNumber = nullptr;
1007     const std::string* manufacturer = nullptr;
1008     const std::string* model = nullptr;
1009     if (!sdbusplus::unpackPropertiesNoThrow(
1010             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
1011             partNumber, "SerialNumber", serialNumber, "Manufacturer",
1012             manufacturer, "Model", model))
1013     {
1014         messages::internalError(asyncResp->res);
1015         return;
1016     }
1017 
1018     if (partNumber != nullptr)
1019     {
1020         asyncResp->res.jsonValue["PartNumber"] = *partNumber;
1021     }
1022 
1023     if (serialNumber != nullptr)
1024     {
1025         asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
1026     }
1027 
1028     if (manufacturer != nullptr)
1029     {
1030         asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
1031     }
1032 
1033     if (model != nullptr)
1034     {
1035         asyncResp->res.jsonValue["Model"] = *model;
1036     }
1037 }
1038 
1039 inline void populateStorageController(
1040     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1041     const std::string& controllerId, const std::string& connectionName,
1042     const std::string& path)
1043 {
1044     asyncResp->res.jsonValue["@odata.type"] =
1045         "#StorageController.v1_6_0.StorageController";
1046     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1047         "/redfish/v1/Systems/system/Storage/1/Controllers/{}", controllerId);
1048     asyncResp->res.jsonValue["Name"] = controllerId;
1049     asyncResp->res.jsonValue["Id"] = controllerId;
1050     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
1051 
1052     sdbusplus::asio::getProperty<bool>(
1053         *crow::connections::systemBus, connectionName, path,
1054         "xyz.openbmc_project.Inventory.Item", "Present",
1055         [asyncResp](const boost::system::error_code& ec, bool isPresent) {
1056         // this interface isn't necessary, only check it
1057         // if we get a good return
1058         if (ec)
1059         {
1060             BMCWEB_LOG_DEBUG("Failed to get Present property");
1061             return;
1062         }
1063         if (!isPresent)
1064         {
1065             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
1066         }
1067     });
1068 
1069     sdbusplus::asio::getAllProperties(
1070         *crow::connections::systemBus, connectionName, path,
1071         "xyz.openbmc_project.Inventory.Decorator.Asset",
1072         [asyncResp](const boost::system::error_code& ec,
1073                     const std::vector<
1074                         std::pair<std::string, dbus::utility::DbusVariantType>>&
1075                         propertiesList) {
1076         getStorageControllerAsset(asyncResp, ec, propertiesList);
1077     });
1078 }
1079 
1080 inline void getStorageControllerHandler(
1081     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1082     const std::string& controllerId, const boost::system::error_code& ec,
1083     const dbus::utility::MapperGetSubTreeResponse& subtree)
1084 {
1085     if (ec || subtree.empty())
1086     {
1087         // doesn't have to be there
1088         BMCWEB_LOG_DEBUG("Failed to handle StorageController");
1089         return;
1090     }
1091 
1092     for (const auto& [path, interfaceDict] : subtree)
1093     {
1094         sdbusplus::message::object_path object(path);
1095         std::string id = object.filename();
1096         if (id.empty())
1097         {
1098             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1099             return;
1100         }
1101         if (id != controllerId)
1102         {
1103             continue;
1104         }
1105 
1106         if (interfaceDict.size() != 1)
1107         {
1108             BMCWEB_LOG_ERROR("Connection size {}, greater than 1",
1109                              interfaceDict.size());
1110             messages::internalError(asyncResp->res);
1111             return;
1112         }
1113 
1114         const std::string& connectionName = interfaceDict.front().first;
1115         populateStorageController(asyncResp, controllerId, connectionName,
1116                                   path);
1117     }
1118 }
1119 
1120 inline void populateStorageControllerCollection(
1121     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1122     const boost::system::error_code& ec,
1123     const dbus::utility::MapperGetSubTreePathsResponse& controllerList)
1124 {
1125     nlohmann::json::array_t members;
1126     if (ec || controllerList.empty())
1127     {
1128         asyncResp->res.jsonValue["Members"] = std::move(members);
1129         asyncResp->res.jsonValue["Members@odata.count"] = 0;
1130         BMCWEB_LOG_DEBUG("Failed to find any StorageController");
1131         return;
1132     }
1133 
1134     for (const std::string& path : controllerList)
1135     {
1136         std::string id = sdbusplus::message::object_path(path).filename();
1137         if (id.empty())
1138         {
1139             BMCWEB_LOG_ERROR("Failed to find filename in {}", path);
1140             return;
1141         }
1142         nlohmann::json::object_t member;
1143         member["@odata.id"] = boost::urls::format(
1144             "/redfish/v1/Systems/system/Storage/1/Controllers/{}", id);
1145         members.emplace_back(member);
1146     }
1147     asyncResp->res.jsonValue["Members@odata.count"] = members.size();
1148     asyncResp->res.jsonValue["Members"] = std::move(members);
1149 }
1150 
1151 inline void handleSystemsStorageControllerCollectionGet(
1152     App& app, const crow::Request& req,
1153     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1154     const std::string& systemName)
1155 {
1156     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1157     {
1158         BMCWEB_LOG_DEBUG(
1159             "Failed to setup Redfish Route for StorageController Collection");
1160         return;
1161     }
1162     if (systemName != "system")
1163     {
1164         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1165                                    systemName);
1166         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1167         return;
1168     }
1169 
1170     asyncResp->res.jsonValue["@odata.type"] =
1171         "#StorageControllerCollection.StorageControllerCollection";
1172     asyncResp->res.jsonValue["@odata.id"] =
1173         "/redfish/v1/Systems/system/Storage/1/Controllers";
1174     asyncResp->res.jsonValue["Name"] = "Storage Controller Collection";
1175 
1176     constexpr std::array<std::string_view, 1> interfaces = {
1177         "xyz.openbmc_project.Inventory.Item.StorageController"};
1178     dbus::utility::getSubTreePaths(
1179         "/xyz/openbmc_project/inventory", 0, interfaces,
1180         [asyncResp](const boost::system::error_code& ec,
1181                     const dbus::utility::MapperGetSubTreePathsResponse&
1182                         controllerList) {
1183         populateStorageControllerCollection(asyncResp, ec, controllerList);
1184     });
1185 }
1186 
1187 inline void handleSystemsStorageControllerGet(
1188     App& app, const crow::Request& req,
1189     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1190     const std::string& systemName, const std::string& controllerId)
1191 {
1192     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1193     {
1194         BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController");
1195         return;
1196     }
1197     if (systemName != "system")
1198     {
1199         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1200                                    systemName);
1201         BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName);
1202         return;
1203     }
1204     constexpr std::array<std::string_view, 1> interfaces = {
1205         "xyz.openbmc_project.Inventory.Item.StorageController"};
1206     dbus::utility::getSubTree(
1207         "/xyz/openbmc_project/inventory", 0, interfaces,
1208         [asyncResp,
1209          controllerId](const boost::system::error_code& ec,
1210                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
1211         getStorageControllerHandler(asyncResp, controllerId, ec, subtree);
1212     });
1213 }
1214 
1215 inline void requestRoutesStorageControllerCollection(App& app)
1216 {
1217     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/")
1218         .privileges(redfish::privileges::getStorageControllerCollection)
1219         .methods(boost::beast::http::verb::get)(std::bind_front(
1220             handleSystemsStorageControllerCollectionGet, std::ref(app)));
1221 }
1222 
1223 inline void requestRoutesStorageController(App& app)
1224 {
1225     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>")
1226         .privileges(redfish::privileges::getStorageController)
1227         .methods(boost::beast::http::verb::get)(
1228             std::bind_front(handleSystemsStorageControllerGet, std::ref(app)));
1229 }
1230 
1231 } // namespace redfish
1232