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