xref: /openbmc/bmcweb/redfish-core/lib/storage.hpp (revision 4cee35e7)
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                     for (const auto& [path, interfaceDict] : subtree)
252                     {
253                         auto subHealth = std::make_shared<HealthPopulate>(
254                             asyncResp, root[count]["Status"]);
255                         subHealth->inventory.emplace_back(path);
256                         health->inventory.emplace_back(path);
257                         health->children.emplace_back(subHealth);
258                         count++;
259                     }
260                 },
261                 "xyz.openbmc_project.ObjectMapper",
262                 "/xyz/openbmc_project/object_mapper",
263                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
264                 "/xyz/openbmc_project/inventory", int32_t(0),
265                 std::array<const char*, 1>{
266                     "xyz.openbmc_project.Inventory.Item.StorageController"});
267         });
268 }
269 
270 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
271                           const std::string& connectionName,
272                           const std::string& path)
273 {
274     crow::connections::systemBus->async_method_call(
275         [asyncResp](const boost::system::error_code ec,
276                     const std::vector<
277                         std::pair<std::string, dbus::utility::DbusVariantType>>&
278                         propertiesList) {
279             if (ec)
280             {
281                 // this interface isn't necessary
282                 return;
283             }
284             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
285                      property : propertiesList)
286             {
287                 // Store DBus properties that are also
288                 // Redfish properties with same name and a
289                 // string value
290                 const std::string& propertyName = property.first;
291                 if ((propertyName == "PartNumber") ||
292                     (propertyName == "SerialNumber") ||
293                     (propertyName == "Manufacturer") ||
294                     (propertyName == "Model"))
295                 {
296                     const std::string* value =
297                         std::get_if<std::string>(&property.second);
298                     if (value == nullptr)
299                     {
300                         // illegal property
301                         messages::internalError(asyncResp->res);
302                         return;
303                     }
304                     asyncResp->res.jsonValue[propertyName] = *value;
305                 }
306             }
307         },
308         connectionName, path, "org.freedesktop.DBus.Properties", "GetAll",
309         "xyz.openbmc_project.Inventory.Decorator.Asset");
310 }
311 
312 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
313                             const std::string& connectionName,
314                             const std::string& path)
315 {
316     sdbusplus::asio::getProperty<bool>(
317         *crow::connections::systemBus, connectionName, path,
318         "xyz.openbmc_project.Inventory.Item", "Present",
319         [asyncResp, path](const boost::system::error_code ec,
320                           const bool enabled) {
321             // this interface isn't necessary, only check it if
322             // we get a good return
323             if (ec)
324             {
325                 return;
326             }
327 
328             if (!enabled)
329             {
330                 asyncResp->res.jsonValue["Status"]["State"] = "Disabled";
331             }
332         });
333 }
334 
335 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
336                           const std::string& connectionName,
337                           const std::string& path)
338 {
339     sdbusplus::asio::getProperty<bool>(
340         *crow::connections::systemBus, connectionName, path,
341         "xyz.openbmc_project.State.Drive", "Rebuilding",
342         [asyncResp](const boost::system::error_code ec, const bool updating) {
343             // this interface isn't necessary, only check it
344             // if we get a good return
345             if (ec)
346             {
347                 return;
348             }
349 
350             // updating and disabled in the backend shouldn't be
351             // able to be set at the same time, so we don't need
352             // to check for the race condition of these two
353             // calls
354             if (updating)
355             {
356                 asyncResp->res.jsonValue["Status"]["State"] = "Updating";
357             }
358         });
359 }
360 
361 inline std::optional<std::string> convertDriveType(const std::string& type)
362 {
363     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
364     {
365         return "HDD";
366     }
367     if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
368     {
369         return "SSD";
370     }
371 
372     return std::nullopt;
373 }
374 
375 inline std::optional<std::string> convertDriveProtocol(const std::string& proto)
376 {
377     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
378     {
379         return "SAS";
380     }
381     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
382     {
383         return "SATA";
384     }
385     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
386     {
387         return "NVMe";
388     }
389     if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
390     {
391         return "FC";
392     }
393 
394     return std::nullopt;
395 }
396 
397 inline void
398     getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
399                            const std::string& connectionName,
400                            const std::string& path)
401 {
402     sdbusplus::asio::getAllProperties(
403         *crow::connections::systemBus, connectionName, path,
404         "xyz.openbmc_project.Inventory.Item.Drive",
405         [asyncResp](const boost::system::error_code ec,
406                     const std::vector<
407                         std::pair<std::string, dbus::utility::DbusVariantType>>&
408                         propertiesList) {
409             if (ec)
410             {
411                 // this interface isn't required
412                 return;
413             }
414             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
415                      property : propertiesList)
416             {
417                 const std::string& propertyName = property.first;
418                 if (propertyName == "Type")
419                 {
420                     const std::string* value =
421                         std::get_if<std::string>(&property.second);
422                     if (value == nullptr)
423                     {
424                         // illegal property
425                         BMCWEB_LOG_ERROR << "Illegal property: Type";
426                         messages::internalError(asyncResp->res);
427                         return;
428                     }
429 
430                     std::optional<std::string> mediaType =
431                         convertDriveType(*value);
432                     if (!mediaType)
433                     {
434                         BMCWEB_LOG_ERROR << "Unsupported DriveType Interface: "
435                                          << *value;
436                         messages::internalError(asyncResp->res);
437                         return;
438                     }
439 
440                     asyncResp->res.jsonValue["MediaType"] = *mediaType;
441                 }
442                 else if (propertyName == "Capacity")
443                 {
444                     const uint64_t* capacity =
445                         std::get_if<uint64_t>(&property.second);
446                     if (capacity == nullptr)
447                     {
448                         BMCWEB_LOG_ERROR << "Illegal property: Capacity";
449                         messages::internalError(asyncResp->res);
450                         return;
451                     }
452                     if (*capacity == 0)
453                     {
454                         // drive capacity not known
455                         continue;
456                     }
457 
458                     asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
459                 }
460                 else if (propertyName == "Protocol")
461                 {
462                     const std::string* value =
463                         std::get_if<std::string>(&property.second);
464                     if (value == nullptr)
465                     {
466                         BMCWEB_LOG_ERROR << "Illegal property: Protocol";
467                         messages::internalError(asyncResp->res);
468                         return;
469                     }
470 
471                     std::optional<std::string> proto =
472                         convertDriveProtocol(*value);
473                     if (!proto)
474                     {
475                         BMCWEB_LOG_ERROR
476                             << "Unsupported DrivePrototype Interface: "
477                             << *value;
478                         messages::internalError(asyncResp->res);
479                         return;
480                     }
481                     asyncResp->res.jsonValue["Protocol"] = *proto;
482                 }
483             }
484         });
485 }
486 
487 inline void requestRoutesDrive(App& app)
488 {
489     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/Drives/<str>/")
490         .privileges(redfish::privileges::getDrive)
491         .methods(
492             boost::beast::http::verb::
493                 get)([&app](const crow::Request& req,
494                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
495                             const std::string& driveId) {
496             if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
497             {
498                 return;
499             }
500             crow::connections::systemBus->async_method_call(
501                 [asyncResp, driveId](
502                     const boost::system::error_code ec,
503                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
504                     if (ec)
505                     {
506                         BMCWEB_LOG_ERROR << "Drive mapper call error";
507                         messages::internalError(asyncResp->res);
508                         return;
509                     }
510 
511                     auto drive = std::find_if(
512                         subtree.begin(), subtree.end(),
513                         [&driveId](const std::pair<
514                                    std::string,
515                                    std::vector<std::pair<
516                                        std::string, std::vector<std::string>>>>&
517                                        object) {
518                             return sdbusplus::message::object_path(object.first)
519                                        .filename() == driveId;
520                         });
521 
522                     if (drive == subtree.end())
523                     {
524                         messages::resourceNotFound(asyncResp->res, "Drive",
525                                                    driveId);
526                         return;
527                     }
528 
529                     const std::string& path = drive->first;
530                     const std::vector<
531                         std::pair<std::string, std::vector<std::string>>>&
532                         connectionNames = drive->second;
533 
534                     asyncResp->res.jsonValue["@odata.type"] =
535                         "#Drive.v1_7_0.Drive";
536                     asyncResp->res.jsonValue["@odata.id"] =
537                         "/redfish/v1/Systems/system/Storage/1/Drives/" +
538                         driveId;
539                     asyncResp->res.jsonValue["Name"] = driveId;
540                     asyncResp->res.jsonValue["Id"] = driveId;
541 
542                     if (connectionNames.size() != 1)
543                     {
544                         BMCWEB_LOG_ERROR << "Connection size "
545                                          << connectionNames.size()
546                                          << ", not equal to 1";
547                         messages::internalError(asyncResp->res);
548                         return;
549                     }
550 
551                     getMainChassisId(
552                         asyncResp,
553                         [](const std::string& chassisId,
554                            const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
555                             aRsp->res
556                                 .jsonValue["Links"]["Chassis"]["@odata.id"] =
557                                 "/redfish/v1/Chassis/" + chassisId;
558                         });
559 
560                     // default it to Enabled
561                     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
562 
563                     auto health = std::make_shared<HealthPopulate>(asyncResp);
564                     health->inventory.emplace_back(path);
565                     health->populate();
566 
567                     const std::string& connectionName =
568                         connectionNames[0].first;
569 
570                     for (const std::string& interface :
571                          connectionNames[0].second)
572                     {
573                         if (interface ==
574                             "xyz.openbmc_project.Inventory.Decorator.Asset")
575                         {
576                             getDriveAsset(asyncResp, connectionName, path);
577                         }
578                         else if (interface ==
579                                  "xyz.openbmc_project.Inventory.Item")
580                         {
581                             getDrivePresent(asyncResp, connectionName, path);
582                         }
583                         else if (interface == "xyz.openbmc_project.State.Drive")
584                         {
585                             getDriveState(asyncResp, connectionName, path);
586                         }
587                         else if (interface ==
588                                  "xyz.openbmc_project.Inventory.Item.Drive")
589                         {
590                             getDriveItemProperties(asyncResp, connectionName,
591                                                    path);
592                         }
593                     }
594                 },
595                 "xyz.openbmc_project.ObjectMapper",
596                 "/xyz/openbmc_project/object_mapper",
597                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
598                 "/xyz/openbmc_project/inventory", int32_t(0),
599                 std::array<const char*, 1>{
600                     "xyz.openbmc_project.Inventory.Item.Drive"});
601         });
602 }
603 } // namespace redfish
604