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