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