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