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