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 25 namespace redfish 26 { 27 inline void requestRoutesStorageCollection(App& app) 28 { 29 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/") 30 .privileges(redfish::privileges::getStorageCollection) 31 .methods(boost::beast::http::verb::get)( 32 [](const crow::Request&, 33 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 34 asyncResp->res.jsonValue["@odata.type"] = 35 "#StorageCollection.StorageCollection"; 36 asyncResp->res.jsonValue["@odata.id"] = 37 "/redfish/v1/Systems/system/Storage"; 38 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 39 asyncResp->res.jsonValue["Members"] = { 40 {{"@odata.id", "/redfish/v1/Systems/system/Storage/1"}}}; 41 asyncResp->res.jsonValue["Members@odata.count"] = 1; 42 }); 43 } 44 45 inline void requestRoutesStorage(App& app) 46 { 47 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/") 48 .privileges(redfish::privileges::getStorage) 49 .methods( 50 boost::beast::http::verb:: 51 get)([](const crow::Request&, 52 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 53 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_7_1.Storage"; 54 asyncResp->res.jsonValue["@odata.id"] = 55 "/redfish/v1/Systems/system/Storage/1"; 56 asyncResp->res.jsonValue["Name"] = "Storage"; 57 asyncResp->res.jsonValue["Id"] = "1"; 58 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 59 60 auto health = std::make_shared<HealthPopulate>(asyncResp); 61 health->populate(); 62 63 crow::connections::systemBus->async_method_call( 64 [asyncResp, 65 health](const boost::system::error_code ec, 66 const std::vector<std::string>& storageList) { 67 nlohmann::json& storageArray = 68 asyncResp->res.jsonValue["Drives"]; 69 storageArray = nlohmann::json::array(); 70 auto& count = 71 asyncResp->res.jsonValue["Drives@odata.count"]; 72 count = 0; 73 74 if (ec) 75 { 76 BMCWEB_LOG_ERROR << "Drive mapper call error"; 77 messages::internalError(asyncResp->res); 78 return; 79 } 80 81 health->inventory.insert(health->inventory.end(), 82 storageList.begin(), 83 storageList.end()); 84 85 for (const std::string& objpath : storageList) 86 { 87 std::size_t lastPos = objpath.rfind('/'); 88 if (lastPos == std::string::npos || 89 (objpath.size() <= lastPos + 1)) 90 { 91 BMCWEB_LOG_ERROR << "Failed to find '/' in " 92 << objpath; 93 continue; 94 } 95 96 storageArray.push_back( 97 {{"@odata.id", 98 "/redfish/v1/Systems/system/Storage/1/Drives/" + 99 objpath.substr(lastPos + 1)}}); 100 } 101 102 count = storageArray.size(); 103 }, 104 "xyz.openbmc_project.ObjectMapper", 105 "/xyz/openbmc_project/object_mapper", 106 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 107 "/xyz/openbmc_project/inventory", int32_t(0), 108 std::array<const char*, 1>{ 109 "xyz.openbmc_project.Inventory.Item.Drive"}); 110 111 crow::connections::systemBus->async_method_call( 112 [asyncResp, 113 health](const boost::system::error_code ec, 114 const crow::openbmc_mapper::GetSubTreeType& subtree) { 115 if (ec || !subtree.size()) 116 { 117 // doesn't have to be there 118 return; 119 } 120 121 nlohmann::json& root = 122 asyncResp->res.jsonValue["StorageControllers"]; 123 root = nlohmann::json::array(); 124 for (const auto& [path, interfaceDict] : subtree) 125 { 126 std::size_t lastPos = path.rfind('/'); 127 if (lastPos == std::string::npos || 128 (path.size() <= lastPos + 1)) 129 { 130 BMCWEB_LOG_ERROR << "Failed to find '/' in " 131 << path; 132 return; 133 } 134 135 if (interfaceDict.size() != 1) 136 { 137 BMCWEB_LOG_ERROR << "Connection size " 138 << interfaceDict.size() 139 << ", greater than 1"; 140 messages::internalError(asyncResp->res); 141 return; 142 } 143 144 const std::string& connectionName = 145 interfaceDict.front().first; 146 147 size_t index = root.size(); 148 nlohmann::json& storageController = 149 root.emplace_back(nlohmann::json::object()); 150 151 std::string id = path.substr(lastPos + 1); 152 153 storageController["@odata.type"] = 154 "#Storage.v1_7_0.StorageController"; 155 storageController["@odata.id"] = 156 "/redfish/v1/Systems/system/Storage/1#/StorageControllers/" + 157 std::to_string(index); 158 storageController["Name"] = id; 159 storageController["MemberId"] = id; 160 storageController["Status"]["State"] = "Enabled"; 161 162 crow::connections::systemBus->async_method_call( 163 [asyncResp, index]( 164 const boost::system::error_code ec2, 165 const dbus::utility::DbusVariantType present) { 166 // this interface isn't necessary, only check it 167 // if we get a good return 168 if (ec2) 169 { 170 return; 171 } 172 const bool* enabled = 173 std::get_if<bool>(&present); 174 if (enabled == nullptr) 175 { 176 BMCWEB_LOG_DEBUG 177 << "Illegal property present"; 178 messages::internalError(asyncResp->res); 179 return; 180 } 181 if (!(*enabled)) 182 { 183 asyncResp->res 184 .jsonValue["StorageControllers"][index] 185 ["Status"]["State"] = 186 "Disabled"; 187 } 188 }, 189 connectionName, path, 190 "org.freedesktop.DBus.Properties", "Get", 191 "xyz.openbmc_project.Inventory.Item", "Present"); 192 193 crow::connections::systemBus->async_method_call( 194 [asyncResp, index]( 195 const boost::system::error_code ec2, 196 const std::vector< 197 std::pair<std::string, 198 dbus::utility::DbusVariantType>>& 199 propertiesList) { 200 if (ec2) 201 { 202 // this interface isn't necessary 203 return; 204 } 205 for (const std::pair< 206 std::string, 207 dbus::utility::DbusVariantType>& 208 property : propertiesList) 209 { 210 // Store DBus properties that are also 211 // Redfish properties with same name and a 212 // string value 213 const std::string& propertyName = 214 property.first; 215 nlohmann::json& object = 216 asyncResp->res 217 .jsonValue["StorageControllers"] 218 [index]; 219 if ((propertyName == "PartNumber") || 220 (propertyName == "SerialNumber") || 221 (propertyName == "Manufacturer") || 222 (propertyName == "Model")) 223 { 224 const std::string* value = 225 std::get_if<std::string>( 226 &property.second); 227 if (value == nullptr) 228 { 229 // illegal property 230 messages::internalError( 231 asyncResp->res); 232 return; 233 } 234 object[propertyName] = *value; 235 } 236 } 237 }, 238 connectionName, path, 239 "org.freedesktop.DBus.Properties", "GetAll", 240 "xyz.openbmc_project.Inventory.Decorator.Asset"); 241 } 242 243 // this is done after we know the json array will no longer 244 // be resized, as json::array uses vector underneath and we 245 // need references to its members that won't change 246 size_t count = 0; 247 for (const auto& [path, interfaceDict] : subtree) 248 { 249 auto subHealth = std::make_shared<HealthPopulate>( 250 asyncResp, root[count]["Status"]); 251 subHealth->inventory.emplace_back(path); 252 health->inventory.emplace_back(path); 253 health->children.emplace_back(subHealth); 254 count++; 255 } 256 }, 257 "xyz.openbmc_project.ObjectMapper", 258 "/xyz/openbmc_project/object_mapper", 259 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 260 "/xyz/openbmc_project/inventory", int32_t(0), 261 std::array<const char*, 1>{ 262 "xyz.openbmc_project.Inventory.Item.StorageController"}); 263 }); 264 } 265 266 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 267 const std::string& connectionName, 268 const std::string& path) 269 { 270 crow::connections::systemBus->async_method_call( 271 [asyncResp](const boost::system::error_code ec, 272 const std::vector< 273 std::pair<std::string, dbus::utility::DbusVariantType>>& 274 propertiesList) { 275 if (ec) 276 { 277 // this interface isn't necessary 278 return; 279 } 280 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 281 property : propertiesList) 282 { 283 // Store DBus properties that are also 284 // Redfish properties with same name and a 285 // string value 286 const std::string& propertyName = property.first; 287 if ((propertyName == "PartNumber") || 288 (propertyName == "SerialNumber") || 289 (propertyName == "Manufacturer") || 290 (propertyName == "Model")) 291 { 292 const std::string* value = 293 std::get_if<std::string>(&property.second); 294 if (value == nullptr) 295 { 296 // illegal property 297 messages::internalError(asyncResp->res); 298 return; 299 } 300 asyncResp->res.jsonValue[propertyName] = *value; 301 } 302 } 303 }, 304 connectionName, path, "org.freedesktop.DBus.Properties", "GetAll", 305 "xyz.openbmc_project.Inventory.Decorator.Asset"); 306 } 307 308 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 309 const std::string& connectionName, 310 const std::string& path) 311 { 312 crow::connections::systemBus->async_method_call( 313 [asyncResp, path](const boost::system::error_code ec, 314 const dbus::utility::DbusVariantType present) { 315 // this interface isn't necessary, only check it if 316 // we get a good return 317 if (ec) 318 { 319 return; 320 } 321 322 const bool* enabled = std::get_if<bool>(&present); 323 if (enabled == nullptr) 324 { 325 BMCWEB_LOG_DEBUG << "Illegal property present"; 326 messages::internalError(asyncResp->res); 327 return; 328 } 329 if (!(*enabled)) 330 { 331 asyncResp->res.jsonValue["Status"]["State"] = "Disabled"; 332 } 333 }, 334 connectionName, path, "org.freedesktop.DBus.Properties", "Get", 335 "xyz.openbmc_project.Inventory.Item", "Present"); 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 crow::connections::systemBus->async_method_call( 343 [asyncResp](const boost::system::error_code ec, 344 const dbus::utility::DbusVariantType rebuilding) { 345 // this interface isn't necessary, only check it 346 // if we get a good return 347 if (ec) 348 { 349 return; 350 } 351 352 const bool* updating = std::get_if<bool>(&rebuilding); 353 if (updating == nullptr) 354 { 355 BMCWEB_LOG_DEBUG << "Illegal property present"; 356 messages::internalError(asyncResp->res); 357 return; 358 } 359 360 // updating and disabled in the backend shouldn't be 361 // able to be set at the same time, so we don't need 362 // to check for the race condition of these two 363 // calls 364 if (*updating) 365 { 366 asyncResp->res.jsonValue["Status"]["State"] = "Updating"; 367 } 368 }, 369 connectionName, path, "org.freedesktop.DBus.Properties", "Get", 370 "xyz.openbmc_project.State.Drive", "Rebuilding"); 371 } 372 373 inline void requestRoutesDrive(App& app) 374 { 375 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/Drives/<str>/") 376 .privileges(redfish::privileges::getDrive) 377 .methods( 378 boost::beast::http::verb::get)([](const crow::Request&, 379 const std::shared_ptr< 380 bmcweb::AsyncResp>& asyncResp, 381 const std::string& driveId) { 382 crow::connections::systemBus->async_method_call( 383 [asyncResp, 384 driveId](const boost::system::error_code ec, 385 const crow::openbmc_mapper::GetSubTreeType& subtree) { 386 if (ec) 387 { 388 BMCWEB_LOG_ERROR << "Drive mapper call error"; 389 messages::internalError(asyncResp->res); 390 return; 391 } 392 393 auto drive = std::find_if( 394 subtree.begin(), subtree.end(), 395 [&driveId](const std::pair< 396 std::string, 397 std::vector<std::pair< 398 std::string, std::vector<std::string>>>>& 399 object) { 400 return sdbusplus::message::object_path(object.first) 401 .filename() == driveId; 402 }); 403 404 if (drive == subtree.end()) 405 { 406 messages::resourceNotFound(asyncResp->res, "Drive", 407 driveId); 408 return; 409 } 410 411 const std::string& path = drive->first; 412 const std::vector< 413 std::pair<std::string, std::vector<std::string>>>& 414 connectionNames = drive->second; 415 416 asyncResp->res.jsonValue["@odata.type"] = 417 "#Drive.v1_7_0.Drive"; 418 asyncResp->res.jsonValue["@odata.id"] = 419 "/redfish/v1/Systems/system/Storage/1/Drives/" + 420 driveId; 421 asyncResp->res.jsonValue["Name"] = driveId; 422 asyncResp->res.jsonValue["Id"] = driveId; 423 424 if (connectionNames.size() != 1) 425 { 426 BMCWEB_LOG_ERROR << "Connection size " 427 << connectionNames.size() 428 << ", not equal to 1"; 429 messages::internalError(asyncResp->res); 430 return; 431 } 432 433 getMainChassisId( 434 asyncResp, 435 [](const std::string& chassisId, 436 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 437 aRsp->res.jsonValue["Links"]["Chassis"] = { 438 {"@odata.id", 439 "/redfish/v1/Chassis/" + chassisId}}; 440 }); 441 442 // default it to Enabled 443 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 444 445 auto health = std::make_shared<HealthPopulate>(asyncResp); 446 health->inventory.emplace_back(path); 447 health->populate(); 448 449 const std::string& connectionName = 450 connectionNames[0].first; 451 452 getDriveAsset(asyncResp, connectionName, path); 453 getDrivePresent(asyncResp, connectionName, path); 454 getDriveState(asyncResp, connectionName, path); 455 }, 456 "xyz.openbmc_project.ObjectMapper", 457 "/xyz/openbmc_project/object_mapper", 458 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 459 "/xyz/openbmc_project/inventory", int32_t(0), 460 std::array<const char*, 1>{ 461 "xyz.openbmc_project.Inventory.Item.Drive"}); 462 }); 463 } 464 } // namespace redfish 465