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