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