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