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