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