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