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