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