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