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