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 for (const auto& [path, interfaceDict] : subtree) 252 { 253 auto subHealth = std::make_shared<HealthPopulate>( 254 asyncResp, root[count]["Status"]); 255 subHealth->inventory.emplace_back(path); 256 health->inventory.emplace_back(path); 257 health->children.emplace_back(subHealth); 258 count++; 259 } 260 }, 261 "xyz.openbmc_project.ObjectMapper", 262 "/xyz/openbmc_project/object_mapper", 263 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 264 "/xyz/openbmc_project/inventory", int32_t(0), 265 std::array<const char*, 1>{ 266 "xyz.openbmc_project.Inventory.Item.StorageController"}); 267 }); 268 } 269 270 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 271 const std::string& connectionName, 272 const std::string& path) 273 { 274 crow::connections::systemBus->async_method_call( 275 [asyncResp](const boost::system::error_code ec, 276 const std::vector< 277 std::pair<std::string, dbus::utility::DbusVariantType>>& 278 propertiesList) { 279 if (ec) 280 { 281 // this interface isn't necessary 282 return; 283 } 284 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 285 property : propertiesList) 286 { 287 // Store DBus properties that are also 288 // Redfish properties with same name and a 289 // string value 290 const std::string& propertyName = property.first; 291 if ((propertyName == "PartNumber") || 292 (propertyName == "SerialNumber") || 293 (propertyName == "Manufacturer") || 294 (propertyName == "Model")) 295 { 296 const std::string* value = 297 std::get_if<std::string>(&property.second); 298 if (value == nullptr) 299 { 300 // illegal property 301 messages::internalError(asyncResp->res); 302 return; 303 } 304 asyncResp->res.jsonValue[propertyName] = *value; 305 } 306 } 307 }, 308 connectionName, path, "org.freedesktop.DBus.Properties", "GetAll", 309 "xyz.openbmc_project.Inventory.Decorator.Asset"); 310 } 311 312 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 313 const std::string& connectionName, 314 const std::string& path) 315 { 316 sdbusplus::asio::getProperty<bool>( 317 *crow::connections::systemBus, connectionName, path, 318 "xyz.openbmc_project.Inventory.Item", "Present", 319 [asyncResp, path](const boost::system::error_code ec, 320 const bool enabled) { 321 // this interface isn't necessary, only check it if 322 // we get a good return 323 if (ec) 324 { 325 return; 326 } 327 328 if (!enabled) 329 { 330 asyncResp->res.jsonValue["Status"]["State"] = "Disabled"; 331 } 332 }); 333 } 334 335 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 336 const std::string& connectionName, 337 const std::string& path) 338 { 339 sdbusplus::asio::getProperty<bool>( 340 *crow::connections::systemBus, connectionName, path, 341 "xyz.openbmc_project.State.Drive", "Rebuilding", 342 [asyncResp](const boost::system::error_code ec, const bool updating) { 343 // this interface isn't necessary, only check it 344 // if we get a good return 345 if (ec) 346 { 347 return; 348 } 349 350 // updating and disabled in the backend shouldn't be 351 // able to be set at the same time, so we don't need 352 // to check for the race condition of these two 353 // calls 354 if (updating) 355 { 356 asyncResp->res.jsonValue["Status"]["State"] = "Updating"; 357 } 358 }); 359 } 360 361 inline std::optional<std::string> convertDriveType(const std::string& type) 362 { 363 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 364 { 365 return "HDD"; 366 } 367 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 368 { 369 return "SSD"; 370 } 371 372 return std::nullopt; 373 } 374 375 inline std::optional<std::string> convertDriveProtocol(const std::string& proto) 376 { 377 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 378 { 379 return "SAS"; 380 } 381 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 382 { 383 return "SATA"; 384 } 385 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 386 { 387 return "NVMe"; 388 } 389 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 390 { 391 return "FC"; 392 } 393 394 return std::nullopt; 395 } 396 397 inline void 398 getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 399 const std::string& connectionName, 400 const std::string& path) 401 { 402 sdbusplus::asio::getAllProperties( 403 *crow::connections::systemBus, connectionName, path, 404 "xyz.openbmc_project.Inventory.Item.Drive", 405 [asyncResp](const boost::system::error_code ec, 406 const std::vector< 407 std::pair<std::string, dbus::utility::DbusVariantType>>& 408 propertiesList) { 409 if (ec) 410 { 411 // this interface isn't required 412 return; 413 } 414 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 415 property : propertiesList) 416 { 417 const std::string& propertyName = property.first; 418 if (propertyName == "Type") 419 { 420 const std::string* value = 421 std::get_if<std::string>(&property.second); 422 if (value == nullptr) 423 { 424 // illegal property 425 BMCWEB_LOG_ERROR << "Illegal property: Type"; 426 messages::internalError(asyncResp->res); 427 return; 428 } 429 430 std::optional<std::string> mediaType = 431 convertDriveType(*value); 432 if (!mediaType) 433 { 434 BMCWEB_LOG_ERROR << "Unsupported DriveType Interface: " 435 << *value; 436 messages::internalError(asyncResp->res); 437 return; 438 } 439 440 asyncResp->res.jsonValue["MediaType"] = *mediaType; 441 } 442 else if (propertyName == "Capacity") 443 { 444 const uint64_t* capacity = 445 std::get_if<uint64_t>(&property.second); 446 if (capacity == nullptr) 447 { 448 BMCWEB_LOG_ERROR << "Illegal property: Capacity"; 449 messages::internalError(asyncResp->res); 450 return; 451 } 452 if (*capacity == 0) 453 { 454 // drive capacity not known 455 continue; 456 } 457 458 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 459 } 460 else if (propertyName == "Protocol") 461 { 462 const std::string* value = 463 std::get_if<std::string>(&property.second); 464 if (value == nullptr) 465 { 466 BMCWEB_LOG_ERROR << "Illegal property: Protocol"; 467 messages::internalError(asyncResp->res); 468 return; 469 } 470 471 std::optional<std::string> proto = 472 convertDriveProtocol(*value); 473 if (!proto) 474 { 475 BMCWEB_LOG_ERROR 476 << "Unsupported DrivePrototype Interface: " 477 << *value; 478 messages::internalError(asyncResp->res); 479 return; 480 } 481 asyncResp->res.jsonValue["Protocol"] = *proto; 482 } 483 } 484 }); 485 } 486 487 inline void requestRoutesDrive(App& app) 488 { 489 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/Drives/<str>/") 490 .privileges(redfish::privileges::getDrive) 491 .methods( 492 boost::beast::http::verb:: 493 get)([&app](const crow::Request& req, 494 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 495 const std::string& driveId) { 496 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 497 { 498 return; 499 } 500 crow::connections::systemBus->async_method_call( 501 [asyncResp, driveId]( 502 const boost::system::error_code ec, 503 const dbus::utility::MapperGetSubTreeResponse& subtree) { 504 if (ec) 505 { 506 BMCWEB_LOG_ERROR << "Drive mapper call error"; 507 messages::internalError(asyncResp->res); 508 return; 509 } 510 511 auto drive = std::find_if( 512 subtree.begin(), subtree.end(), 513 [&driveId](const std::pair< 514 std::string, 515 std::vector<std::pair< 516 std::string, std::vector<std::string>>>>& 517 object) { 518 return sdbusplus::message::object_path(object.first) 519 .filename() == driveId; 520 }); 521 522 if (drive == subtree.end()) 523 { 524 messages::resourceNotFound(asyncResp->res, "Drive", 525 driveId); 526 return; 527 } 528 529 const std::string& path = drive->first; 530 const std::vector< 531 std::pair<std::string, std::vector<std::string>>>& 532 connectionNames = drive->second; 533 534 asyncResp->res.jsonValue["@odata.type"] = 535 "#Drive.v1_7_0.Drive"; 536 asyncResp->res.jsonValue["@odata.id"] = 537 "/redfish/v1/Systems/system/Storage/1/Drives/" + 538 driveId; 539 asyncResp->res.jsonValue["Name"] = driveId; 540 asyncResp->res.jsonValue["Id"] = driveId; 541 542 if (connectionNames.size() != 1) 543 { 544 BMCWEB_LOG_ERROR << "Connection size " 545 << connectionNames.size() 546 << ", not equal to 1"; 547 messages::internalError(asyncResp->res); 548 return; 549 } 550 551 getMainChassisId( 552 asyncResp, 553 [](const std::string& chassisId, 554 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 555 aRsp->res 556 .jsonValue["Links"]["Chassis"]["@odata.id"] = 557 "/redfish/v1/Chassis/" + chassisId; 558 }); 559 560 // default it to Enabled 561 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 562 563 auto health = std::make_shared<HealthPopulate>(asyncResp); 564 health->inventory.emplace_back(path); 565 health->populate(); 566 567 const std::string& connectionName = 568 connectionNames[0].first; 569 570 for (const std::string& interface : 571 connectionNames[0].second) 572 { 573 if (interface == 574 "xyz.openbmc_project.Inventory.Decorator.Asset") 575 { 576 getDriveAsset(asyncResp, connectionName, path); 577 } 578 else if (interface == 579 "xyz.openbmc_project.Inventory.Item") 580 { 581 getDrivePresent(asyncResp, connectionName, path); 582 } 583 else if (interface == "xyz.openbmc_project.State.Drive") 584 { 585 getDriveState(asyncResp, connectionName, path); 586 } 587 else if (interface == 588 "xyz.openbmc_project.Inventory.Item.Drive") 589 { 590 getDriveItemProperties(asyncResp, connectionName, 591 path); 592 } 593 } 594 }, 595 "xyz.openbmc_project.ObjectMapper", 596 "/xyz/openbmc_project/object_mapper", 597 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 598 "/xyz/openbmc_project/inventory", int32_t(0), 599 std::array<const char*, 1>{ 600 "xyz.openbmc_project.Inventory.Item.Drive"}); 601 }); 602 } 603 } // namespace redfish 604