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