xref: /openbmc/bmcweb/features/redfish/lib/storage.hpp (revision 45ca1b868e47978a4d2e8ebb680cb384e804c97e)
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 "health.hpp"
19 #include "openbmc_dbus_rest.hpp"
20 
21 #include <app.hpp>
22 #include <dbus_utility.hpp>
23 #include <query.hpp>
24 #include <registries/privilege_registry.hpp>
25 #include <sdbusplus/asio/property.hpp>
26 
27 namespace redfish
28 {
29 inline void requestRoutesStorageCollection(App& app)
30 {
31     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/")
32         .privileges(redfish::privileges::getStorageCollection)
33         .methods(boost::beast::http::verb::get)(
34             [&app](const crow::Request& req,
35                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
36                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
37                 {
38                     return;
39                 }
40                 asyncResp->res.jsonValue["@odata.type"] =
41                     "#StorageCollection.StorageCollection";
42                 asyncResp->res.jsonValue["@odata.id"] =
43                     "/redfish/v1/Systems/system/Storage";
44                 asyncResp->res.jsonValue["Name"] = "Storage Collection";
45                 asyncResp->res.jsonValue["Members"] = {
46                     {{"@odata.id", "/redfish/v1/Systems/system/Storage/1"}}};
47                 asyncResp->res.jsonValue["Members@odata.count"] = 1;
48             });
49 }
50 
51 inline void requestRoutesStorage(App& app)
52 {
53     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/")
54         .privileges(redfish::privileges::getStorage)
55         .methods(boost::beast::http::verb::get)([&app](const crow::Request& req,
56                                                        const std::shared_ptr<
57                                                            bmcweb::AsyncResp>&
58                                                            asyncResp) {
59             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
60             {
61                 return;
62             }
63             asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_7_1.Storage";
64             asyncResp->res.jsonValue["@odata.id"] =
65                 "/redfish/v1/Systems/system/Storage/1";
66             asyncResp->res.jsonValue["Name"] = "Storage";
67             asyncResp->res.jsonValue["Id"] = "1";
68             asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
69 
70             auto health = std::make_shared<HealthPopulate>(asyncResp);
71             health->populate();
72 
73             crow::connections::systemBus->async_method_call(
74                 [asyncResp,
75                  health](const boost::system::error_code ec,
76                          const dbus::utility::MapperGetSubTreePathsResponse&
77                              storageList) {
78                     nlohmann::json& storageArray =
79                         asyncResp->res.jsonValue["Drives"];
80                     storageArray = nlohmann::json::array();
81                     auto& count =
82                         asyncResp->res.jsonValue["Drives@odata.count"];
83                     count = 0;
84 
85                     if (ec)
86                     {
87                         BMCWEB_LOG_ERROR << "Drive mapper call error";
88                         messages::internalError(asyncResp->res);
89                         return;
90                     }
91 
92                     health->inventory.insert(health->inventory.end(),
93                                              storageList.begin(),
94                                              storageList.end());
95 
96                     for (const std::string& objpath : storageList)
97                     {
98                         std::size_t lastPos = objpath.rfind('/');
99                         if (lastPos == std::string::npos ||
100                             (objpath.size() <= lastPos + 1))
101                         {
102                             BMCWEB_LOG_ERROR << "Failed to find '/' in "
103                                              << objpath;
104                             continue;
105                         }
106 
107                         storageArray.push_back(
108                             {{"@odata.id",
109                               "/redfish/v1/Systems/system/Storage/1/Drives/" +
110                                   objpath.substr(lastPos + 1)}});
111                     }
112 
113                     count = storageArray.size();
114                 },
115                 "xyz.openbmc_project.ObjectMapper",
116                 "/xyz/openbmc_project/object_mapper",
117                 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
118                 "/xyz/openbmc_project/inventory", int32_t(0),
119                 std::array<const char*, 1>{
120                     "xyz.openbmc_project.Inventory.Item.Drive"});
121 
122             crow::connections::systemBus->async_method_call(
123                 [asyncResp, health](
124                     const boost::system::error_code ec,
125                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
126                     if (ec || subtree.empty())
127                     {
128                         // doesn't have to be there
129                         return;
130                     }
131 
132                     nlohmann::json& root =
133                         asyncResp->res.jsonValue["StorageControllers"];
134                     root = nlohmann::json::array();
135                     for (const auto& [path, interfaceDict] : subtree)
136                     {
137                         std::size_t lastPos = path.rfind('/');
138                         if (lastPos == std::string::npos ||
139                             (path.size() <= lastPos + 1))
140                         {
141                             BMCWEB_LOG_ERROR << "Failed to find '/' in "
142                                              << path;
143                             return;
144                         }
145 
146                         if (interfaceDict.size() != 1)
147                         {
148                             BMCWEB_LOG_ERROR << "Connection size "
149                                              << interfaceDict.size()
150                                              << ", greater than 1";
151                             messages::internalError(asyncResp->res);
152                             return;
153                         }
154 
155                         const std::string& connectionName =
156                             interfaceDict.front().first;
157 
158                         size_t index = root.size();
159                         nlohmann::json& storageController =
160                             root.emplace_back(nlohmann::json::object());
161 
162                         std::string id = path.substr(lastPos + 1);
163 
164                         storageController["@odata.type"] =
165                             "#Storage.v1_7_0.StorageController";
166                         storageController["@odata.id"] =
167                             "/redfish/v1/Systems/system/Storage/1#/StorageControllers/" +
168                             std::to_string(index);
169                         storageController["Name"] = id;
170                         storageController["MemberId"] = id;
171                         storageController["Status"]["State"] = "Enabled";
172 
173                         sdbusplus::asio::getProperty<bool>(
174                             *crow::connections::systemBus, connectionName, path,
175                             "xyz.openbmc_project.Inventory.Item", "Present",
176                             [asyncResp,
177                              index](const boost::system::error_code ec2,
178                                     bool enabled) {
179                                 // this interface isn't necessary, only check it
180                                 // if we get a good return
181                                 if (ec2)
182                                 {
183                                     return;
184                                 }
185                                 if (!enabled)
186                                 {
187                                     asyncResp->res
188                                         .jsonValue["StorageControllers"][index]
189                                                   ["Status"]["State"] =
190                                         "Disabled";
191                                 }
192                             });
193 
194                         crow::connections::systemBus->async_method_call(
195                             [asyncResp, index](
196                                 const boost::system::error_code ec2,
197                                 const std::vector<
198                                     std::pair<std::string,
199                                               dbus::utility::DbusVariantType>>&
200                                     propertiesList) {
201                                 if (ec2)
202                                 {
203                                     // this interface isn't necessary
204                                     return;
205                                 }
206                                 for (const std::pair<
207                                          std::string,
208                                          dbus::utility::DbusVariantType>&
209                                          property : propertiesList)
210                                 {
211                                     // Store DBus properties that are also
212                                     // Redfish properties with same name and a
213                                     // string value
214                                     const std::string& propertyName =
215                                         property.first;
216                                     nlohmann::json& object =
217                                         asyncResp->res
218                                             .jsonValue["StorageControllers"]
219                                                       [index];
220                                     if ((propertyName == "PartNumber") ||
221                                         (propertyName == "SerialNumber") ||
222                                         (propertyName == "Manufacturer") ||
223                                         (propertyName == "Model"))
224                                     {
225                                         const std::string* value =
226                                             std::get_if<std::string>(
227                                                 &property.second);
228                                         if (value == nullptr)
229                                         {
230                                             // illegal property
231                                             messages::internalError(
232                                                 asyncResp->res);
233                                             return;
234                                         }
235                                         object[propertyName] = *value;
236                                     }
237                                 }
238                             },
239                             connectionName, path,
240                             "org.freedesktop.DBus.Properties", "GetAll",
241                             "xyz.openbmc_project.Inventory.Decorator.Asset");
242                     }
243 
244                     // this is done after we know the json array will no longer
245                     // be resized, as json::array uses vector underneath and we
246                     // need references to its members that won't change
247                     size_t count = 0;
248                     for (const auto& [path, interfaceDict] : subtree)
249                     {
250                         auto subHealth = std::make_shared<HealthPopulate>(
251                             asyncResp, root[count]["Status"]);
252                         subHealth->inventory.emplace_back(path);
253                         health->inventory.emplace_back(path);
254                         health->children.emplace_back(subHealth);
255                         count++;
256                     }
257                 },
258                 "xyz.openbmc_project.ObjectMapper",
259                 "/xyz/openbmc_project/object_mapper",
260                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
261                 "/xyz/openbmc_project/inventory", int32_t(0),
262                 std::array<const char*, 1>{
263                     "xyz.openbmc_project.Inventory.Item.StorageController"});
264         });
265 }
266 
267 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
268                           const std::string& connectionName,
269                           const std::string& path)
270 {
271     crow::connections::systemBus->async_method_call(
272         [asyncResp](const boost::system::error_code ec,
273                     const std::vector<
274                         std::pair<std::string, dbus::utility::DbusVariantType>>&
275                         propertiesList) {
276             if (ec)
277             {
278                 // this interface isn't necessary
279                 return;
280             }
281             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
282                      property : propertiesList)
283             {
284                 // Store DBus properties that are also
285                 // Redfish properties with same name and a
286                 // string value
287                 const std::string& propertyName = property.first;
288                 if ((propertyName == "PartNumber") ||
289                     (propertyName == "SerialNumber") ||
290                     (propertyName == "Manufacturer") ||
291                     (propertyName == "Model"))
292                 {
293                     const std::string* value =
294                         std::get_if<std::string>(&property.second);
295                     if (value == nullptr)
296                     {
297                         // illegal property
298                         messages::internalError(asyncResp->res);
299                         return;
300                     }
301                     asyncResp->res.jsonValue[propertyName] = *value;
302                 }
303             }
304         },
305         connectionName, path, "org.freedesktop.DBus.Properties", "GetAll",
306         "xyz.openbmc_project.Inventory.Decorator.Asset");
307 }
308 
309 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
310                             const std::string& connectionName,
311                             const std::string& path)
312 {
313     sdbusplus::asio::getProperty<bool>(
314         *crow::connections::systemBus, connectionName, path,
315         "xyz.openbmc_project.Inventory.Item", "Present",
316         [asyncResp, path](const boost::system::error_code ec,
317                           const bool enabled) {
318             // this interface isn't necessary, only check it if
319             // we get a good return
320             if (ec)
321             {
322                 return;
323             }
324 
325             if (!enabled)
326             {
327                 asyncResp->res.jsonValue["Status"]["State"] = "Disabled";
328             }
329         });
330 }
331 
332 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
333                           const std::string& connectionName,
334                           const std::string& path)
335 {
336     sdbusplus::asio::getProperty<bool>(
337         *crow::connections::systemBus, connectionName, path,
338         "xyz.openbmc_project.State.Drive", "Rebuilding",
339         [asyncResp](const boost::system::error_code ec, const bool updating) {
340             // this interface isn't necessary, only check it
341             // if we get a good return
342             if (ec)
343             {
344                 return;
345             }
346 
347             // updating and disabled in the backend shouldn't be
348             // able to be set at the same time, so we don't need
349             // to check for the race condition of these two
350             // calls
351             if (updating)
352             {
353                 asyncResp->res.jsonValue["Status"]["State"] = "Updating";
354             }
355         });
356 }
357 
358 inline std::optional<std::string> convertDriveType(const std::string& type)
359 {
360     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
361     {
362         return "HDD";
363     }
364     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
365     {
366         return "SSD";
367     }
368 
369     return std::nullopt;
370 }
371 
372 inline std::optional<std::string> convertDriveProtocol(const std::string& proto)
373 {
374     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
375     {
376         return "SAS";
377     }
378     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
379     {
380         return "SATA";
381     }
382     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
383     {
384         return "NVMe";
385     }
386     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
387     {
388         return "FC";
389     }
390 
391     return std::nullopt;
392 }
393 
394 inline void
395     getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
396                            const std::string& connectionName,
397                            const std::string& path)
398 {
399     sdbusplus::asio::getAllProperties(
400         *crow::connections::systemBus, connectionName, path,
401         "xyz.openbmc_project.Inventory.Item.Drive",
402         [asyncResp](const boost::system::error_code ec,
403                     const std::vector<
404                         std::pair<std::string, dbus::utility::DbusVariantType>>&
405                         propertiesList) {
406             if (ec)
407             {
408                 // this interface isn't required
409                 return;
410             }
411             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
412                      property : propertiesList)
413             {
414                 const std::string& propertyName = property.first;
415                 if (propertyName == "Type")
416                 {
417                     const std::string* value =
418                         std::get_if<std::string>(&property.second);
419                     if (value == nullptr)
420                     {
421                         // illegal property
422                         BMCWEB_LOG_ERROR << "Illegal property: Type";
423                         messages::internalError(asyncResp->res);
424                         return;
425                     }
426 
427                     std::optional<std::string> mediaType =
428                         convertDriveType(*value);
429                     if (!mediaType)
430                     {
431                         BMCWEB_LOG_ERROR << "Unsupported DriveType Interface: "
432                                          << *value;
433                         messages::internalError(asyncResp->res);
434                         return;
435                     }
436 
437                     asyncResp->res.jsonValue["MediaType"] = *mediaType;
438                 }
439                 else if (propertyName == "Capacity")
440                 {
441                     const uint64_t* capacity =
442                         std::get_if<uint64_t>(&property.second);
443                     if (capacity == nullptr)
444                     {
445                         BMCWEB_LOG_ERROR << "Illegal property: Capacity";
446                         messages::internalError(asyncResp->res);
447                         return;
448                     }
449                     if (*capacity == 0)
450                     {
451                         // drive capacity not known
452                         continue;
453                     }
454 
455                     asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
456                 }
457                 else if (propertyName == "Protocol")
458                 {
459                     const std::string* value =
460                         std::get_if<std::string>(&property.second);
461                     if (value == nullptr)
462                     {
463                         BMCWEB_LOG_ERROR << "Illegal property: Protocol";
464                         messages::internalError(asyncResp->res);
465                         return;
466                     }
467 
468                     std::optional<std::string> proto =
469                         convertDriveProtocol(*value);
470                     if (!proto)
471                     {
472                         BMCWEB_LOG_ERROR
473                             << "Unsupported DrivePrototype Interface: "
474                             << *value;
475                         messages::internalError(asyncResp->res);
476                         return;
477                     }
478                     asyncResp->res.jsonValue["Protocol"] = *proto;
479                 }
480             }
481         });
482 }
483 
484 inline void requestRoutesDrive(App& app)
485 {
486     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/Drives/<str>/")
487         .privileges(redfish::privileges::getDrive)
488         .methods(
489             boost::beast::http::verb::
490                 get)([&app](const crow::Request& req,
491                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
492                             const std::string& driveId) {
493             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
494             {
495                 return;
496             }
497             crow::connections::systemBus->async_method_call(
498                 [asyncResp, driveId](
499                     const boost::system::error_code ec,
500                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
501                     if (ec)
502                     {
503                         BMCWEB_LOG_ERROR << "Drive mapper call error";
504                         messages::internalError(asyncResp->res);
505                         return;
506                     }
507 
508                     auto drive = std::find_if(
509                         subtree.begin(), subtree.end(),
510                         [&driveId](const std::pair<
511                                    std::string,
512                                    std::vector<std::pair<
513                                        std::string, std::vector<std::string>>>>&
514                                        object) {
515                             return sdbusplus::message::object_path(object.first)
516                                        .filename() == driveId;
517                         });
518 
519                     if (drive == subtree.end())
520                     {
521                         messages::resourceNotFound(asyncResp->res, "Drive",
522                                                    driveId);
523                         return;
524                     }
525 
526                     const std::string& path = drive->first;
527                     const std::vector<
528                         std::pair<std::string, std::vector<std::string>>>&
529                         connectionNames = drive->second;
530 
531                     asyncResp->res.jsonValue["@odata.type"] =
532                         "#Drive.v1_7_0.Drive";
533                     asyncResp->res.jsonValue["@odata.id"] =
534                         "/redfish/v1/Systems/system/Storage/1/Drives/" +
535                         driveId;
536                     asyncResp->res.jsonValue["Name"] = driveId;
537                     asyncResp->res.jsonValue["Id"] = driveId;
538 
539                     if (connectionNames.size() != 1)
540                     {
541                         BMCWEB_LOG_ERROR << "Connection size "
542                                          << connectionNames.size()
543                                          << ", not equal to 1";
544                         messages::internalError(asyncResp->res);
545                         return;
546                     }
547 
548                     getMainChassisId(
549                         asyncResp,
550                         [](const std::string& chassisId,
551                            const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
552                             aRsp->res.jsonValue["Links"]["Chassis"] = {
553                                 {"@odata.id",
554                                  "/redfish/v1/Chassis/" + chassisId}};
555                         });
556 
557                     // default it to Enabled
558                     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
559 
560                     auto health = std::make_shared<HealthPopulate>(asyncResp);
561                     health->inventory.emplace_back(path);
562                     health->populate();
563 
564                     const std::string& connectionName =
565                         connectionNames[0].first;
566 
567                     getDriveAsset(asyncResp, connectionName, path);
568                     getDrivePresent(asyncResp, connectionName, path);
569                     getDriveState(asyncResp, connectionName, path);
570                     getDriveItemProperties(asyncResp, connectionName, path);
571                 },
572                 "xyz.openbmc_project.ObjectMapper",
573                 "/xyz/openbmc_project/object_mapper",
574                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
575                 "/xyz/openbmc_project/inventory", int32_t(0),
576                 std::array<const char*, 1>{
577                     "xyz.openbmc_project.Inventory.Item.Drive"});
578         });
579 }
580 } // namespace redfish
581