xref: /openbmc/bmcweb/redfish-core/lib/storage.hpp (revision 141d9431)
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 void requestRoutesDrive(App& app)
349 {
350     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/Drives/<str>/")
351         .privileges(redfish::privileges::getDrive)
352         .methods(
353             boost::beast::http::verb::get)([](const crow::Request&,
354                                               const std::shared_ptr<
355                                                   bmcweb::AsyncResp>& asyncResp,
356                                               const std::string& driveId) {
357             crow::connections::systemBus->async_method_call(
358                 [asyncResp,
359                  driveId](const boost::system::error_code ec,
360                           const crow::openbmc_mapper::GetSubTreeType& subtree) {
361                     if (ec)
362                     {
363                         BMCWEB_LOG_ERROR << "Drive mapper call error";
364                         messages::internalError(asyncResp->res);
365                         return;
366                     }
367 
368                     auto drive = std::find_if(
369                         subtree.begin(), subtree.end(),
370                         [&driveId](const std::pair<
371                                    std::string,
372                                    std::vector<std::pair<
373                                        std::string, std::vector<std::string>>>>&
374                                        object) {
375                             return sdbusplus::message::object_path(object.first)
376                                        .filename() == driveId;
377                         });
378 
379                     if (drive == subtree.end())
380                     {
381                         messages::resourceNotFound(asyncResp->res, "Drive",
382                                                    driveId);
383                         return;
384                     }
385 
386                     const std::string& path = drive->first;
387                     const std::vector<
388                         std::pair<std::string, std::vector<std::string>>>&
389                         connectionNames = drive->second;
390 
391                     asyncResp->res.jsonValue["@odata.type"] =
392                         "#Drive.v1_7_0.Drive";
393                     asyncResp->res.jsonValue["@odata.id"] =
394                         "/redfish/v1/Systems/system/Storage/1/Drives/" +
395                         driveId;
396                     asyncResp->res.jsonValue["Name"] = driveId;
397                     asyncResp->res.jsonValue["Id"] = driveId;
398 
399                     if (connectionNames.size() != 1)
400                     {
401                         BMCWEB_LOG_ERROR << "Connection size "
402                                          << connectionNames.size()
403                                          << ", not equal to 1";
404                         messages::internalError(asyncResp->res);
405                         return;
406                     }
407 
408                     getMainChassisId(
409                         asyncResp,
410                         [](const std::string& chassisId,
411                            const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
412                             aRsp->res.jsonValue["Links"]["Chassis"] = {
413                                 {"@odata.id",
414                                  "/redfish/v1/Chassis/" + chassisId}};
415                         });
416 
417                     // default it to Enabled
418                     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
419 
420                     auto health = std::make_shared<HealthPopulate>(asyncResp);
421                     health->inventory.emplace_back(path);
422                     health->populate();
423 
424                     const std::string& connectionName =
425                         connectionNames[0].first;
426 
427                     getDriveAsset(asyncResp, connectionName, path);
428                     getDrivePresent(asyncResp, connectionName, path);
429                     getDriveState(asyncResp, connectionName, path);
430                 },
431                 "xyz.openbmc_project.ObjectMapper",
432                 "/xyz/openbmc_project/object_mapper",
433                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
434                 "/xyz/openbmc_project/inventory", int32_t(0),
435                 std::array<const char*, 1>{
436                     "xyz.openbmc_project.Inventory.Item.Drive"});
437         });
438 }
439 } // namespace redfish
440