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