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