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#/StorageControllers/" + 156 std::to_string(index); 157 storageController["Name"] = id; 158 storageController["MemberId"] = id; 159 storageController["Status"]["State"] = "Enabled"; 160 161 crow::connections::systemBus->async_method_call( 162 [asyncResp, 163 index](const boost::system::error_code ec2, 164 const std::variant<bool> present) { 165 // this interface isn't necessary, only check it 166 // if we get a good return 167 if (ec2) 168 { 169 return; 170 } 171 const bool* enabled = 172 std::get_if<bool>(&present); 173 if (enabled == nullptr) 174 { 175 BMCWEB_LOG_DEBUG 176 << "Illegal property present"; 177 messages::internalError(asyncResp->res); 178 return; 179 } 180 if (!(*enabled)) 181 { 182 asyncResp->res 183 .jsonValue["StorageControllers"][index] 184 ["Status"]["State"] = 185 "Disabled"; 186 } 187 }, 188 connectionName, path, 189 "org.freedesktop.DBus.Properties", "Get", 190 "xyz.openbmc_project.Inventory.Item", "Present"); 191 192 crow::connections::systemBus->async_method_call( 193 [asyncResp, index]( 194 const boost::system::error_code ec2, 195 const std::vector<std::pair< 196 std::string, 197 std::variant<bool, std::string, uint64_t>>>& 198 propertiesList) { 199 if (ec2) 200 { 201 // this interface isn't necessary 202 return; 203 } 204 for (const std::pair< 205 std::string, 206 std::variant<bool, std::string, 207 uint64_t>>& property : 208 propertiesList) 209 { 210 // Store DBus properties that are also 211 // Redfish properties with same name and a 212 // string value 213 const std::string& propertyName = 214 property.first; 215 nlohmann::json& object = 216 asyncResp->res 217 .jsonValue["StorageControllers"] 218 [index]; 219 if ((propertyName == "PartNumber") || 220 (propertyName == "SerialNumber") || 221 (propertyName == "Manufacturer") || 222 (propertyName == "Model")) 223 { 224 const std::string* value = 225 std::get_if<std::string>( 226 &property.second); 227 if (value == nullptr) 228 { 229 // illegal property 230 messages::internalError( 231 asyncResp->res); 232 return; 233 } 234 object[propertyName] = *value; 235 } 236 } 237 }, 238 connectionName, path, 239 "org.freedesktop.DBus.Properties", "GetAll", 240 "xyz.openbmc_project.Inventory.Decorator.Asset"); 241 } 242 243 // this is done after we know the json array will no longer 244 // be resized, as json::array uses vector underneath and we 245 // need references to its members that won't change 246 size_t count = 0; 247 for (const auto& [path, interfaceDict] : subtree) 248 { 249 auto subHealth = std::make_shared<HealthPopulate>( 250 asyncResp, root[count]["Status"]); 251 subHealth->inventory.emplace_back(path); 252 health->inventory.emplace_back(path); 253 health->children.emplace_back(subHealth); 254 count++; 255 } 256 }, 257 "xyz.openbmc_project.ObjectMapper", 258 "/xyz/openbmc_project/object_mapper", 259 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 260 "/xyz/openbmc_project/inventory", int32_t(0), 261 std::array<const char*, 1>{ 262 "xyz.openbmc_project.Inventory.Item.StorageController"}); 263 }); 264 } 265 266 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 267 const std::string& connectionName, 268 const std::string& path) 269 { 270 crow::connections::systemBus->async_method_call( 271 [asyncResp]( 272 const boost::system::error_code ec, 273 const std::vector<std::pair< 274 std::string, std::variant<bool, std::string, uint64_t>>>& 275 propertiesList) { 276 if (ec) 277 { 278 // this interface isn't necessary 279 return; 280 } 281 for (const std::pair<std::string, 282 std::variant<bool, std::string, uint64_t>>& 283 property : propertiesList) 284 { 285 // Store DBus properties that are also 286 // Redfish properties with same name and a 287 // string value 288 const std::string& propertyName = property.first; 289 if ((propertyName == "PartNumber") || 290 (propertyName == "SerialNumber") || 291 (propertyName == "Manufacturer") || 292 (propertyName == "Model")) 293 { 294 const std::string* value = 295 std::get_if<std::string>(&property.second); 296 if (value == nullptr) 297 { 298 // illegal property 299 messages::internalError(asyncResp->res); 300 return; 301 } 302 asyncResp->res.jsonValue[propertyName] = *value; 303 } 304 } 305 }, 306 connectionName, path, "org.freedesktop.DBus.Properties", "GetAll", 307 "xyz.openbmc_project.Inventory.Decorator.Asset"); 308 } 309 310 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 311 const std::string& connectionName, 312 const std::string& path) 313 { 314 crow::connections::systemBus->async_method_call( 315 [asyncResp, path](const boost::system::error_code ec, 316 const std::variant<bool> present) { 317 // this interface isn't necessary, only check it if 318 // we get a good return 319 if (ec) 320 { 321 return; 322 } 323 324 const bool* enabled = std::get_if<bool>(&present); 325 if (enabled == nullptr) 326 { 327 BMCWEB_LOG_DEBUG << "Illegal property present"; 328 messages::internalError(asyncResp->res); 329 return; 330 } 331 if (!(*enabled)) 332 { 333 asyncResp->res.jsonValue["Status"]["State"] = "Disabled"; 334 } 335 }, 336 connectionName, path, "org.freedesktop.DBus.Properties", "Get", 337 "xyz.openbmc_project.Inventory.Item", "Present"); 338 } 339 340 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 341 const std::string& connectionName, 342 const std::string& path) 343 { 344 crow::connections::systemBus->async_method_call( 345 [asyncResp](const boost::system::error_code ec, 346 const std::variant<bool> rebuilding) { 347 // this interface isn't necessary, only check it 348 // if we get a good return 349 if (ec) 350 { 351 return; 352 } 353 354 const bool* updating = std::get_if<bool>(&rebuilding); 355 if (updating == nullptr) 356 { 357 BMCWEB_LOG_DEBUG << "Illegal property present"; 358 messages::internalError(asyncResp->res); 359 return; 360 } 361 362 // updating and disabled in the backend shouldn't be 363 // able to be set at the same time, so we don't need 364 // to check for the race condition of these two 365 // calls 366 if (*updating) 367 { 368 asyncResp->res.jsonValue["Status"]["State"] = "Updating"; 369 } 370 }, 371 connectionName, path, "org.freedesktop.DBus.Properties", "Get", 372 "xyz.openbmc_project.State.Drive", "Rebuilding"); 373 } 374 375 inline void requestRoutesDrive(App& app) 376 { 377 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/Drives/<str>/") 378 .privileges(redfish::privileges::getDrive) 379 .methods( 380 boost::beast::http::verb::get)([](const crow::Request&, 381 const std::shared_ptr< 382 bmcweb::AsyncResp>& asyncResp, 383 const std::string& driveId) { 384 crow::connections::systemBus->async_method_call( 385 [asyncResp, 386 driveId](const boost::system::error_code ec, 387 const crow::openbmc_mapper::GetSubTreeType& subtree) { 388 if (ec) 389 { 390 BMCWEB_LOG_ERROR << "Drive mapper call error"; 391 messages::internalError(asyncResp->res); 392 return; 393 } 394 395 auto drive = std::find_if( 396 subtree.begin(), subtree.end(), 397 [&driveId](const std::pair< 398 std::string, 399 std::vector<std::pair< 400 std::string, std::vector<std::string>>>>& 401 object) { 402 return sdbusplus::message::object_path(object.first) 403 .filename() == driveId; 404 }); 405 406 if (drive == subtree.end()) 407 { 408 messages::resourceNotFound(asyncResp->res, "Drive", 409 driveId); 410 return; 411 } 412 413 const std::string& path = drive->first; 414 const std::vector< 415 std::pair<std::string, std::vector<std::string>>>& 416 connectionNames = drive->second; 417 418 asyncResp->res.jsonValue["@odata.type"] = 419 "#Drive.v1_7_0.Drive"; 420 asyncResp->res.jsonValue["@odata.id"] = 421 "/redfish/v1/Systems/system/Storage/1/Drives/" + 422 driveId; 423 asyncResp->res.jsonValue["Name"] = driveId; 424 asyncResp->res.jsonValue["Id"] = driveId; 425 426 if (connectionNames.size() != 1) 427 { 428 BMCWEB_LOG_ERROR << "Connection size " 429 << connectionNames.size() 430 << ", not equal to 1"; 431 messages::internalError(asyncResp->res); 432 return; 433 } 434 435 getMainChassisId( 436 asyncResp, 437 [](const std::string& chassisId, 438 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 439 aRsp->res.jsonValue["Links"]["Chassis"] = { 440 {"@odata.id", 441 "/redfish/v1/Chassis/" + chassisId}}; 442 }); 443 444 // default it to Enabled 445 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 446 447 auto health = std::make_shared<HealthPopulate>(asyncResp); 448 health->inventory.emplace_back(path); 449 health->populate(); 450 451 const std::string& connectionName = 452 connectionNames[0].first; 453 454 getDriveAsset(asyncResp, connectionName, path); 455 getDrivePresent(asyncResp, connectionName, path); 456 getDriveState(asyncResp, connectionName, path); 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