| /* |
| // Copyright (c) 2019 Intel Corporation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| */ |
| #pragma once |
| |
| #include "app.hpp" |
| #include "dbus_utility.hpp" |
| #include "health.hpp" |
| #include "human_sort.hpp" |
| #include "openbmc_dbus_rest.hpp" |
| #include "query.hpp" |
| #include "redfish_util.hpp" |
| #include "registries/privilege_registry.hpp" |
| #include "utils/dbus_utils.hpp" |
| |
| #include <boost/system/error_code.hpp> |
| #include <sdbusplus/asio/property.hpp> |
| #include <sdbusplus/unpack_properties.hpp> |
| #include <utils/location_utils.hpp> |
| |
| #include <array> |
| #include <string_view> |
| #include <unordered_set> |
| |
| namespace redfish |
| { |
| inline void requestRoutesStorageCollection(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/") |
| .privileges(redfish::privileges::getStorageCollection) |
| .methods(boost::beast::http::verb::get)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#StorageCollection.StorageCollection"; |
| asyncResp->res.jsonValue["@odata.id"] = |
| "/redfish/v1/Systems/system/Storage"; |
| asyncResp->res.jsonValue["Name"] = "Storage Collection"; |
| |
| constexpr std::array<std::string_view, 1> interface { |
| "xyz.openbmc_project.Inventory.Item.Storage" |
| }; |
| collection_util::getCollectionMembers( |
| asyncResp, |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", "system", |
| "Storage"), |
| interface); |
| }); |
| |
| BMCWEB_ROUTE(app, "/redfish/v1/Storage/") |
| .privileges(redfish::privileges::getStorageCollection) |
| .methods(boost::beast::http::verb::get)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#StorageCollection.StorageCollection"; |
| asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage"; |
| asyncResp->res.jsonValue["Name"] = "Storage Collection"; |
| constexpr std::array<std::string_view, 1> interface { |
| "xyz.openbmc_project.Inventory.Item.Storage" |
| }; |
| collection_util::getCollectionMembers( |
| asyncResp, crow::utility::urlFromPieces("redfish", "v1", "Storage"), |
| interface); |
| }); |
| } |
| |
| inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::shared_ptr<HealthPopulate>& health, |
| const sdbusplus::message::object_path& storagePath, |
| const std::string& chassisId) |
| { |
| const std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Drive"}; |
| dbus::utility::getAssociatedSubTreePaths( |
| storagePath / "drive", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, |
| interfaces, |
| [asyncResp, health, chassisId]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& driveList) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "Drive mapper call error"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"]; |
| driveArray = nlohmann::json::array(); |
| auto& count = asyncResp->res.jsonValue["Drives@odata.count"]; |
| count = 0; |
| |
| #ifdef HEALTH_POPULATE |
| health->inventory.insert(health->inventory.end(), driveList.begin(), |
| driveList.end()); |
| #endif |
| |
| for (const std::string& drive : driveList) |
| { |
| sdbusplus::message::object_path object(drive); |
| if (object.filename().empty()) |
| { |
| BMCWEB_LOG_ERROR << "Failed to find filename in " << drive; |
| return; |
| } |
| |
| nlohmann::json::object_t driveJson; |
| driveJson["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Chassis", chassisId, "Drives", |
| object.filename()); |
| driveArray.push_back(std::move(driveJson)); |
| } |
| |
| count = driveArray.size(); |
| }); |
| } |
| |
| inline void |
| populateCustomSSDInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const dbus::utility::MapperServiceMap& ifaces, |
| const std::string& path) |
| { |
| std::string connection = ""; |
| std::string customSSDDbusInterface = |
| "com.google.gbmc.ssd." + std::string(customSSD); |
| |
| for (const auto& x : ifaces) |
| { |
| for (const std::string& y : x.second) |
| { |
| if (y == customSSDDbusInterface) |
| { |
| connection = x.first; |
| break; |
| } |
| } |
| if (!connection.empty()) |
| { |
| break; |
| } |
| } |
| if (connection.empty()) |
| { |
| return; |
| } |
| |
| // CustomSSD GPIO |
| sdbusplus::asio::getAllProperties( |
| *crow::connections::systemBus, connection, path, customSSDDbusInterface, |
| [asyncResp, connection, path]( |
| const boost::system::error_code ec2, |
| const std::vector<std::pair< |
| std::string, dbus::utility::DbusVariantType>>& propertiesList) { |
| if (ec2) |
| { |
| // this interface isn't necessary |
| return; |
| } |
| |
| const bool* manufacturingMode = nullptr; |
| const bool* pwrseqPgood = nullptr; |
| const bool* watchdogTriggered = nullptr; |
| const bool* fruEepromWriteProtect = nullptr; |
| const bool* controllerOtpWriteProtect = nullptr; |
| const bool* triggerPowerCycle = nullptr; |
| const bool* triggerReset = nullptr; |
| const bool* disableWatchdog = nullptr; |
| const bool* debugMode = nullptr; |
| const bool* controllerOtpWriteEnable = nullptr; |
| const uint64_t* spiImgSelect = nullptr; |
| const uint64_t* bootFailureCount = nullptr; |
| const std::string* pwrseqState = nullptr; |
| const uint64_t* uptimeInSeconds = nullptr; |
| const uint64_t* uptimeInMinutes = nullptr; |
| const bool* pgoodVdd12v0Ssd = nullptr; |
| const bool* pgoodVddPcMor = nullptr; |
| const bool* pgoodVdd3v3Pcie = nullptr; |
| const bool* pgoodVdd0v83Mor = nullptr; |
| const bool* pgoodVttVrefca = nullptr; |
| const bool* pgoodVddFlashVcc = nullptr; |
| const bool* pgood12vFlashVpp = nullptr; |
| const std::string* cpldVersion = nullptr; |
| |
| std::string otpWriteProtectProperty = |
| customSSDController + std::string("OtpWriteProtect"); |
| std::string otpWriteEnableProperty = |
| customSSDController + std::string("OtpWriteEnable"); |
| |
| const bool success = sdbusplus::unpackPropertiesNoThrow( |
| dbus_utils::UnpackErrorPrinter(), propertiesList, |
| "ManufacturingMode", manufacturingMode, "WatchdogTriggered", |
| watchdogTriggered, "PwrseqPgood", pwrseqPgood, |
| "FruEepromWriteProtect", fruEepromWriteProtect, |
| otpWriteProtectProperty, controllerOtpWriteProtect, |
| "TriggerPowerCycle", triggerPowerCycle, "TriggerReset", |
| triggerReset, "DisableWatchdog", disableWatchdog, "DebugMode", |
| debugMode, otpWriteEnableProperty, controllerOtpWriteEnable, |
| "SpiImgSelect", spiImgSelect, "BootFailureCount", bootFailureCount, |
| "PwrseqState", pwrseqState, "UptimeInSeconds", uptimeInSeconds, |
| "UptimeInMinutes", uptimeInMinutes, "PgoodVdd12v0Ssd", |
| pgoodVdd12v0Ssd, "PgoodVddPcMor", pgoodVddPcMor, "PgoodVdd3v3Pcie", |
| pgoodVdd3v3Pcie, "PgoodVdd0v83Mor", pgoodVdd0v83Mor, |
| "PgoodVttVrefca", pgoodVttVrefca, "PgoodVddFlashVcc", |
| pgoodVddFlashVcc, "Pgood12vFlashVpp", pgood12vFlashVpp, |
| "CpldVersion", cpldVersion); |
| |
| if (!success) |
| { |
| BMCWEB_LOG_CRITICAL << "Failed to parse Arguments for " |
| << std::string(customSSD); |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| nlohmann::json::object_t customSSDJsonObj; |
| customSSDJsonObj["@odata.type"] = customSSDOdataType; |
| // Write Only and will always read as false. |
| customSSDJsonObj["CpldReset"] = false; |
| |
| if (manufacturingMode != nullptr) |
| { |
| customSSDJsonObj["ManufacturingMode"] = *manufacturingMode; |
| } |
| if (pwrseqPgood != nullptr) |
| { |
| customSSDJsonObj["PwrseqPgood"] = *pwrseqPgood; |
| } |
| if (watchdogTriggered != nullptr) |
| { |
| customSSDJsonObj["WatchdogTriggered"] = *watchdogTriggered; |
| } |
| if (fruEepromWriteProtect != nullptr) |
| { |
| customSSDJsonObj["FruEepromWriteProtect"] = *fruEepromWriteProtect; |
| } |
| if (controllerOtpWriteProtect != nullptr) |
| { |
| customSSDJsonObj[otpWriteProtectProperty] = |
| *controllerOtpWriteProtect; |
| } |
| if (triggerPowerCycle != nullptr) |
| { |
| customSSDJsonObj["TriggerPowerCycle"] = *triggerPowerCycle; |
| } |
| if (triggerReset != nullptr) |
| { |
| customSSDJsonObj["TriggerReset"] = *triggerReset; |
| } |
| if (disableWatchdog != nullptr) |
| { |
| customSSDJsonObj["DisableWatchdog"] = *disableWatchdog; |
| } |
| if (debugMode != nullptr) |
| { |
| customSSDJsonObj["DebugMode"] = *debugMode; |
| } |
| if (controllerOtpWriteEnable != nullptr) |
| { |
| customSSDJsonObj[otpWriteEnableProperty] = |
| *controllerOtpWriteEnable; |
| } |
| if (spiImgSelect != nullptr) |
| { |
| customSSDJsonObj["SpiImgSelect"] = *spiImgSelect; |
| } |
| if (bootFailureCount != nullptr) |
| { |
| customSSDJsonObj["BootFailureCount"] = *bootFailureCount; |
| } |
| if (pwrseqState != nullptr) |
| { |
| customSSDJsonObj["PwrseqState"] = *pwrseqState; |
| } |
| if (uptimeInSeconds != nullptr) |
| { |
| customSSDJsonObj["UptimeInSeconds"] = *uptimeInSeconds; |
| } |
| if (uptimeInMinutes != nullptr) |
| { |
| customSSDJsonObj["UptimeInMinutes"] = *uptimeInMinutes; |
| } |
| if (pgoodVdd12v0Ssd != nullptr) |
| { |
| customSSDJsonObj["PgoodVdd12v0Ssd"] = *pgoodVdd12v0Ssd; |
| } |
| if (pgoodVddPcMor != nullptr) |
| { |
| customSSDJsonObj["PgoodVddPcMor"] = *pgoodVddPcMor; |
| } |
| if (pgoodVdd3v3Pcie != nullptr) |
| { |
| customSSDJsonObj["PgoodVdd3v3Pcie"] = *pgoodVdd3v3Pcie; |
| } |
| if (pgoodVdd0v83Mor != nullptr) |
| { |
| customSSDJsonObj["PgoodVdd0v83Mor"] = *pgoodVdd0v83Mor; |
| } |
| if (pgoodVttVrefca != nullptr) |
| { |
| customSSDJsonObj["PgoodVttVrefca"] = *pgoodVttVrefca; |
| } |
| if (pgoodVddFlashVcc != nullptr) |
| { |
| customSSDJsonObj["PgoodVddFlashVcc"] = *pgoodVddFlashVcc; |
| } |
| if (pgood12vFlashVpp != nullptr) |
| { |
| customSSDJsonObj["Pgood12vFlashVpp"] = *pgood12vFlashVpp; |
| } |
| if (cpldVersion != nullptr) |
| { |
| customSSDJsonObj["CpldVersion"] = *cpldVersion; |
| } |
| |
| customSSDJsonObj["Name"] = std::string(customSSD) + " GPIO Action Info"; |
| asyncResp->res |
| .jsonValue["Links"]["Oem"]["Google"][std::string(customSSD)] = |
| std::move(customSSDJsonObj); |
| |
| sdbusplus::asio::getAllProperties( |
| *crow::connections::systemBus, connection, path, |
| "xyz.openbmc_project.Inventory.Decorator.Asset", |
| [asyncResp]( |
| const boost::system::error_code ec3, |
| const std::vector<std::pair< |
| std::string, dbus::utility::DbusVariantType>>& asset) { |
| if (ec3) |
| { |
| // this interface isn't necessary |
| return; |
| } |
| nlohmann::json::object_t customSSDFruEeprom; |
| |
| const std::string* partNumber = nullptr; |
| const std::string* serialNumber = nullptr; |
| const std::string* manufacturer = nullptr; |
| const std::string* model = nullptr; |
| const std::string* manufactureDate = nullptr; |
| |
| const bool assetSuccess = sdbusplus::unpackPropertiesNoThrow( |
| dbus_utils::UnpackErrorPrinter(), asset, "PartNumber", |
| partNumber, "SerialNumber", serialNumber, "Manufacturer", |
| manufacturer, "Model", model, "ManufactureDate", |
| manufactureDate); |
| if (!assetSuccess) |
| { |
| BMCWEB_LOG_CRITICAL << "Failed to parse Arguments for " |
| << std::string(customSSD); |
| return; |
| } |
| customSSDFruEeprom["DeviceName"] = std::string(customSSD); |
| // If we get to this point, then it is enabled. |
| customSSDFruEeprom["Validity"] = "Enabled"; |
| if (partNumber != nullptr) |
| { |
| customSSDFruEeprom["BrdPartNumber"] = *partNumber; |
| } |
| if (serialNumber != nullptr) |
| { |
| customSSDFruEeprom["BrdSerialNumber"] = *serialNumber; |
| } |
| if (manufacturer != nullptr) |
| { |
| customSSDFruEeprom["BrdMfgName"] = *manufacturer; |
| } |
| if (model != nullptr) |
| { |
| customSSDFruEeprom["BrdProductName"] = *model; |
| } |
| if (manufactureDate != nullptr) |
| { |
| customSSDFruEeprom["BrdMfgTime"] = *manufactureDate; |
| } |
| asyncResp->res.jsonValue["Links"]["Oem"]["Google"] |
| [std::string(customSSD)]["FruEeprom"] = |
| std::move(customSSDFruEeprom); |
| }); |
| }); |
| } |
| |
| inline void |
| getDriveFromChassis(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::shared_ptr<HealthPopulate>& health, |
| const sdbusplus::message::object_path& storagePath) |
| { |
| const std::array<std::string_view, 2> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Board", |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; |
| dbus::utility::getAssociatedSubTreePaths( |
| storagePath / "chassis", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, |
| interfaces, |
| [asyncResp, health, storagePath]( |
| const boost::system::error_code ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& chassisList) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "Chassis mapper call error"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| if (chassisList.size() != 1) |
| { |
| BMCWEB_LOG_ERROR |
| << "Storage is not associated with only one chassis"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| std::string chassisPath = chassisList.front(); |
| std::string chassisId = |
| sdbusplus::message::object_path(chassisPath).filename(); |
| if (chassisId.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Failed to find filename in " << chassisPath; |
| return; |
| } |
| getDrives(asyncResp, health, storagePath, chassisId); |
| }); |
| } |
| |
| inline void requestRoutesStorage(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/") |
| .privileges(redfish::privileges::getStorage) |
| .methods(boost::beast::http::verb::get)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, |
| const std::string& storageId) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Storage"}; |
| dbus::utility::getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, |
| [asyncResp, storageId]( |
| const boost::system::error_code ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "requestRoutesStorage DBUS response error"; |
| messages::resourceNotFound( |
| asyncResp->res, "#Storage.v1_13_0.Storage", storageId); |
| return; |
| } |
| auto storage = std::find_if( |
| subtree.begin(), subtree.end(), |
| [&storageId]( |
| const std::pair<std::string, |
| dbus::utility::MapperServiceMap>& object) { |
| return sdbusplus::message::object_path(object.first) |
| .filename() == storageId; |
| }); |
| if (storage == subtree.end()) |
| { |
| messages::resourceNotFound( |
| asyncResp->res, "#Storage.v1_13_0.Storage", storageId); |
| return; |
| } |
| |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#Storage.v1_13_0.Storage"; |
| asyncResp->res.jsonValue["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", |
| "system", "Storage", storageId); |
| asyncResp->res.jsonValue["Name"] = "Storage"; |
| asyncResp->res.jsonValue["Id"] = storageId; |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; |
| |
| auto health = std::make_shared<HealthPopulate>(asyncResp); |
| #ifdef HEALTH_POPULATE |
| health->populate(); |
| #endif |
| |
| getDriveFromChassis(asyncResp, health, storage->first); |
| asyncResp->res.jsonValue["Controllers"]["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", |
| "system", "Storage", storageId, |
| "Controllers"); |
| }); |
| }); |
| |
| BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/") |
| .privileges(redfish::privileges::getStorage) |
| .methods(boost::beast::http::verb::get)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| BMCWEB_LOG_DEBUG << "requestRoutesStorage setUpRedfishRoute failed"; |
| return; |
| } |
| |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Storage"}; |
| dbus::utility::getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, |
| [asyncResp, storageId]( |
| const boost::system::error_code ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "requestRoutesStorage DBUS response error"; |
| messages::resourceNotFound( |
| asyncResp->res, "#Storage.v1_13_0.Storage", storageId); |
| return; |
| } |
| auto storage = std::find_if( |
| subtree.begin(), subtree.end(), |
| [&storageId]( |
| const std::pair<std::string, |
| dbus::utility::MapperServiceMap>& object) { |
| return sdbusplus::message::object_path(object.first) |
| .filename() == storageId; |
| }); |
| if (storage == subtree.end()) |
| { |
| messages::resourceNotFound( |
| asyncResp->res, "#Storage.v1_13_0.Storage", storageId); |
| return; |
| } |
| |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#Storage.v1_13_0.Storage"; |
| asyncResp->res.jsonValue["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Storage", |
| storageId); |
| asyncResp->res.jsonValue["Name"] = "Storage"; |
| asyncResp->res.jsonValue["Id"] = storageId; |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; |
| |
| // Storage subsystem to Stroage link. |
| nlohmann::json::array_t storageServices; |
| nlohmann::json::object_t storageService; |
| storageService["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", "system", "Storage", storageId); |
| storageServices.emplace_back(storageService); |
| asyncResp->res.jsonValue["Links"]["StorageServices"] = |
| std::move(storageServices); |
| asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = |
| 1; |
| }); |
| }); |
| } |
| |
| inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& connectionName, |
| const std::string& path) |
| { |
| sdbusplus::asio::getAllProperties( |
| *crow::connections::systemBus, connectionName, path, |
| "xyz.openbmc_project.Inventory.Decorator.Asset", |
| [asyncResp](const boost::system::error_code& ec, |
| const std::vector< |
| std::pair<std::string, dbus::utility::DbusVariantType>>& |
| propertiesList) { |
| if (ec) |
| { |
| // this interface isn't necessary |
| return; |
| } |
| |
| const std::string* partNumber = nullptr; |
| const std::string* serialNumber = nullptr; |
| const std::string* manufacturer = nullptr; |
| const std::string* model = nullptr; |
| |
| const bool success = sdbusplus::unpackPropertiesNoThrow( |
| dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", |
| partNumber, "SerialNumber", serialNumber, "Manufacturer", |
| manufacturer, "Model", model); |
| |
| if (!success) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| if (partNumber != nullptr) |
| { |
| asyncResp->res.jsonValue["PartNumber"] = *partNumber; |
| } |
| |
| if (serialNumber != nullptr) |
| { |
| asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; |
| } |
| |
| if (manufacturer != nullptr) |
| { |
| asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; |
| } |
| |
| if (model != nullptr) |
| { |
| asyncResp->res.jsonValue["Model"] = *model; |
| } |
| }); |
| } |
| |
| inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& connectionName, |
| const std::string& path) |
| { |
| sdbusplus::asio::getProperty<bool>( |
| *crow::connections::systemBus, connectionName, path, |
| "xyz.openbmc_project.Inventory.Item", "Present", |
| [asyncResp, path](const boost::system::error_code& ec, |
| const bool isPresent) { |
| // this interface isn't necessary, only check it if |
| // we get a good return |
| if (ec) |
| { |
| return; |
| } |
| |
| if (!isPresent) |
| { |
| asyncResp->res.jsonValue["Status"]["State"] = "Absent"; |
| } |
| }); |
| } |
| |
| inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& connectionName, |
| const std::string& path) |
| { |
| sdbusplus::asio::getProperty<bool>( |
| *crow::connections::systemBus, connectionName, path, |
| "xyz.openbmc_project.State.Drive", "Rebuilding", |
| [asyncResp](const boost::system::error_code& ec, const bool updating) { |
| // this interface isn't necessary, only check it |
| // if we get a good return |
| if (ec) |
| { |
| return; |
| } |
| |
| // updating and disabled in the backend shouldn't be |
| // able to be set at the same time, so we don't need |
| // to check for the race condition of these two |
| // calls |
| if (updating) |
| { |
| asyncResp->res.jsonValue["Status"]["State"] = "Updating"; |
| } |
| }); |
| } |
| |
| inline std::optional<std::string> convertDriveType(const std::string& type) |
| { |
| if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") |
| { |
| return "HDD"; |
| } |
| if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") |
| { |
| return "SSD"; |
| } |
| |
| return std::nullopt; |
| } |
| |
| inline void addResetLinks(nlohmann::json& driveReset, |
| const std::string& driveId, |
| const std::string& chassisId) |
| { |
| driveReset["target"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Chassis", chassisId, "Drives", driveId, "Actions", |
| "Drive.Reset"); |
| driveReset["@Redfish.ActionInfo"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId, |
| "Drives", driveId, "ResetActionInfo"); |
| return; |
| } |
| |
| inline std::optional<std::string> convertDriveProtocol(const std::string& proto) |
| { |
| if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") |
| { |
| return "SAS"; |
| } |
| if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") |
| { |
| return "SATA"; |
| } |
| if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") |
| { |
| return "NVMe"; |
| } |
| if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") |
| { |
| return "FC"; |
| } |
| |
| return std::nullopt; |
| } |
| |
| inline void |
| getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& driveId, |
| const std::optional<std::string>& chassisId, |
| const std::string& connectionName, |
| const std::string& path, bool hasDriveState) |
| { |
| sdbusplus::asio::getAllProperties( |
| *crow::connections::systemBus, connectionName, path, |
| "xyz.openbmc_project.Inventory.Item.Drive", |
| [asyncResp, driveId, chassisId, hasDriveState]( |
| const boost::system::error_code& ec, |
| const std::vector<std::pair< |
| std::string, dbus::utility::DbusVariantType>>& propertiesList) { |
| if (ec) |
| { |
| // this interface isn't required |
| return; |
| } |
| for (const std::pair<std::string, dbus::utility::DbusVariantType>& |
| property : propertiesList) |
| { |
| const std::string& propertyName = property.first; |
| if (propertyName == "Type") |
| { |
| const std::string* value = |
| std::get_if<std::string>(&property.second); |
| if (value == nullptr) |
| { |
| // illegal property |
| BMCWEB_LOG_ERROR << "Illegal property: Type"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| std::optional<std::string> mediaType = convertDriveType(*value); |
| if (!mediaType) |
| { |
| BMCWEB_LOG_ERROR << "Unsupported DriveType Interface: " |
| << *value; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| asyncResp->res.jsonValue["MediaType"] = *mediaType; |
| } |
| else if (propertyName == "Capacity") |
| { |
| const uint64_t* capacity = |
| std::get_if<uint64_t>(&property.second); |
| if (capacity == nullptr) |
| { |
| BMCWEB_LOG_ERROR << "Illegal property: Capacity"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| if (*capacity == 0) |
| { |
| // drive capacity not known |
| continue; |
| } |
| |
| asyncResp->res.jsonValue["CapacityBytes"] = *capacity; |
| } |
| else if (propertyName == "Protocol") |
| { |
| const std::string* value = |
| std::get_if<std::string>(&property.second); |
| if (value == nullptr) |
| { |
| BMCWEB_LOG_ERROR << "Illegal property: Protocol"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| std::optional<std::string> proto = convertDriveProtocol(*value); |
| if (!proto) |
| { |
| BMCWEB_LOG_ERROR << "Unsupported DrivePrototype Interface: " |
| << *value; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| asyncResp->res.jsonValue["Protocol"] = *proto; |
| } |
| else if (propertyName == "PredictedMediaLifeLeftPercent") |
| { |
| const uint8_t* lifeLeft = |
| std::get_if<uint8_t>(&property.second); |
| if (lifeLeft == nullptr) |
| { |
| BMCWEB_LOG_ERROR |
| << "Illegal property: PredictedMediaLifeLeftPercent"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| // 255 means reading the value is not supported |
| if (*lifeLeft != 255) |
| { |
| asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] = |
| *lifeLeft; |
| } |
| } |
| else if (propertyName == "Resettable" && hasDriveState) |
| { |
| const bool* value = std::get_if<bool>(&property.second); |
| // If Resettable flag is not present, its not considered a |
| // failure. |
| if (value != nullptr && *value && chassisId.has_value()) |
| { |
| addResetLinks( |
| asyncResp->res.jsonValue["Actions"]["#Drive.Reset"], |
| driveId, *chassisId); |
| } |
| } |
| } |
| }); |
| } |
| |
| static void |
| addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& driveId, |
| const std::string& connectionName, const std::string& path, |
| const std::vector<std::string>& interfaces, |
| const std::optional<std::string> chassisId = std::nullopt) |
| { |
| bool driveInterface = false; |
| bool driveStateInterface = false; |
| for (const std::string& interface : interfaces) |
| { |
| if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") |
| { |
| getDriveAsset(asyncResp, connectionName, path); |
| } |
| else if (interface == "xyz.openbmc_project.Inventory.Item") |
| { |
| getDrivePresent(asyncResp, connectionName, path); |
| } |
| else if (interface == "xyz.openbmc_project.State.Drive") |
| { |
| driveStateInterface = true; |
| getDriveState(asyncResp, connectionName, path); |
| } |
| else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") |
| { |
| driveInterface = true; |
| location_util::getPartLocationContext( |
| asyncResp, "/PhysicalLocation"_json_pointer, path + "/chassis"); |
| } |
| else if (interface == |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode") |
| { |
| location_util::getLocationCode(asyncResp, connectionName, path, |
| "/PhysicalLocation"_json_pointer); |
| } |
| else |
| { |
| std::optional<std::string> locationType = |
| location_util::getLocationType(interface); |
| if (!locationType) |
| { |
| BMCWEB_LOG_DEBUG << "getLocationType for Drive failed for " |
| << interface; |
| continue; |
| } |
| asyncResp->res |
| .jsonValue["PhysicalLocation"]["PartLocation"]["LocationType"] = |
| *locationType; |
| } |
| } |
| |
| if (driveInterface) |
| { |
| getDriveItemProperties(asyncResp, driveId, chassisId, connectionName, |
| path, driveStateInterface); |
| } |
| } |
| |
| /** |
| * Chassis drives, this URL will show all the DriveCollection |
| * information |
| */ |
| inline void chassisDriveCollectionGet( |
| crow::App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| |
| // mapper call lambda |
| constexpr std::array<std::string_view, 2> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Board", |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; |
| dbus::utility::getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, |
| [asyncResp, |
| chassisId](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| if (ec == boost::system::errc::host_unreachable) |
| { |
| messages::resourceNotFound(asyncResp->res, "Chassis", |
| chassisId); |
| return; |
| } |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Iterate over all retrieved ObjectPaths. |
| for (const auto& [path, connectionNames] : subtree) |
| { |
| sdbusplus::message::object_path objPath(path); |
| if (objPath.filename() != chassisId) |
| { |
| continue; |
| } |
| |
| if (connectionNames.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Got 0 Connection names"; |
| continue; |
| } |
| |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#DriveCollection.DriveCollection"; |
| asyncResp->res.jsonValue["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", |
| chassisId, "Drives"); |
| asyncResp->res.jsonValue["Name"] = "Drive Collection"; |
| |
| // Association lambda |
| dbus::utility::getAssociationEndPoints( |
| path + "/drive", |
| [asyncResp, |
| chassisId](const boost::system::error_code& ec3, |
| const dbus::utility::MapperEndPoints& resp) { |
| if (ec3) |
| { |
| BMCWEB_LOG_ERROR << "Error in chassis Drive association "; |
| } |
| nlohmann::json& members = asyncResp->res.jsonValue["Members"]; |
| // important if array is empty |
| members = nlohmann::json::array(); |
| |
| std::vector<std::string> leafNames; |
| for (const auto& drive : resp) |
| { |
| sdbusplus::message::object_path drivePath(drive); |
| leafNames.push_back(drivePath.filename()); |
| } |
| |
| std::sort(leafNames.begin(), leafNames.end(), |
| AlphanumLess<std::string>()); |
| |
| for (const auto& leafName : leafNames) |
| { |
| nlohmann::json::object_t member; |
| member["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Chassis", chassisId, "Drives", |
| leafName); |
| members.push_back(std::move(member)); |
| // navigation links will be registered in next patch set |
| } |
| asyncResp->res.jsonValue["Members@odata.count"] = resp.size(); |
| }); // end association lambda |
| |
| } // end Iterate over all retrieved ObjectPaths |
| }); |
| } |
| |
| inline void requestRoutesChassisDrive(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") |
| .privileges(redfish::privileges::getDriveCollection) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(chassisDriveCollectionGet, std::ref(app))); |
| } |
| |
| inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId, |
| const std::string& driveName, |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) |
| { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Iterate over all retrieved ObjectPaths. |
| for (const auto& [path, connectionNames] : subtree) |
| { |
| sdbusplus::message::object_path objPath(path); |
| if (objPath.filename() != driveName) |
| { |
| continue; |
| } |
| |
| if (connectionNames.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Got 0 Connection names"; |
| continue; |
| } |
| |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Chassis", chassisId, "Drives", driveName); |
| |
| asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; |
| asyncResp->res.jsonValue["Name"] = driveName; |
| asyncResp->res.jsonValue["Id"] = driveName; |
| // default it to Enabled |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; |
| |
| nlohmann::json::object_t linkChassisNav; |
| linkChassisNav["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId); |
| asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; |
| |
| addAllDriveInfo(asyncResp, driveName, connectionNames[0].first, path, |
| connectionNames[0].second, chassisId); |
| } |
| } |
| |
| inline void |
| matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId, |
| const std::string& driveName, |
| const std::vector<std::string>& resp) |
| { |
| for (const std::string& drivePath : resp) |
| { |
| sdbusplus::message::object_path path(drivePath); |
| std::string leaf = path.filename(); |
| if (leaf != driveName) |
| { |
| continue; |
| } |
| // mapper call drive |
| constexpr std::array<std::string_view, 1> driveInterface = { |
| "xyz.openbmc_project.Inventory.Item.Drive"}; |
| dbus::utility::getSubTree( |
| "/xyz/openbmc_project/inventory", 0, driveInterface, |
| [asyncResp, chassisId, driveName]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| buildDrive(asyncResp, chassisId, driveName, ec, subtree); |
| }); |
| return; |
| } |
| messages::internalError(asyncResp->res); |
| } |
| |
| // Find Chassis with chassisId and the Drives associated to it. |
| void findChassisDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId, |
| std::function<void(const boost::system::error_code ec3, |
| const std::vector<std::string>& resp)> |
| cb) |
| { |
| constexpr std::array<std::string_view, 2> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Board", |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; |
| // mapper call chassis |
| dbus::utility::getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, |
| [asyncResp, chassisId, |
| cb](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Iterate over all retrieved ObjectPaths. |
| for (const auto& [path, connectionNames] : subtree) |
| { |
| sdbusplus::message::object_path objPath(path); |
| if (objPath.filename() != chassisId) |
| { |
| continue; |
| } |
| |
| if (connectionNames.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Got 0 Connection names"; |
| continue; |
| } |
| |
| dbus::utility::getAssociationEndPoints(path + "/drive", cb); |
| break; |
| } |
| }); |
| } |
| |
| inline void |
| handleChassisDriveGet(crow::App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId, |
| const std::string& driveName) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| findChassisDrive(asyncResp, chassisId, |
| [asyncResp, chassisId, |
| driveName](const boost::system::error_code ec, |
| const std::vector<std::string>& resp) { |
| if (ec) |
| { |
| return; // no drives = no failures |
| } |
| matchAndFillDrive(asyncResp, chassisId, driveName, resp); |
| }); |
| } |
| |
| /** |
| * This URL will show the drive interface for the specific drive in the chassis |
| */ |
| inline void requestRoutesChassisDriveName(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") |
| .privileges(redfish::privileges::getChassis) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleChassisDriveGet, std::ref(app))); |
| } |
| |
| inline void setResetType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& driveId, const std::string& action, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) |
| { |
| auto driveState = |
| std::find_if(subtree.begin(), subtree.end(), [&driveId](auto& object) { |
| const sdbusplus::message::object_path path(object.first); |
| return path.filename() == driveId; |
| }); |
| |
| if (driveState == subtree.end()) |
| { |
| messages::resourceNotFound(asyncResp->res, "Drive Action", driveId); |
| return; |
| } |
| |
| const std::string& path = driveState->first; |
| const std::vector<std::pair<std::string, std::vector<std::string>>>& |
| connectionNames = driveState->second; |
| |
| if (connectionNames.size() != 1) |
| { |
| BMCWEB_LOG_ERROR << "Connection size " << connectionNames.size() |
| << ", not equal to 1"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| sdbusplus::asio::setProperty<std::string>( |
| *crow::connections::systemBus, connectionNames[0].first, path, |
| "xyz.openbmc_project.State.Drive", "RequestedDriveTransition", |
| action.c_str(), |
| [asyncResp, action](const boost::system::error_code ec) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error for " << action |
| << " : " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| messages::success(asyncResp->res); |
| }); |
| } |
| |
| /** |
| * Performs drive reset action. |
| * |
| * @param[in] asyncResp - Shared pointer for completing asynchronous calls |
| * @param[in] driveId - D-bus filename to identify the Drive |
| * @param[in] resetType - Reset type for the Drive |
| */ |
| inline void |
| performDriveReset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& driveId, |
| std::optional<std::string> resetType) |
| { |
| std::string action; |
| if (!resetType || *resetType == "PowerCycle") |
| { |
| action = "xyz.openbmc_project.State.Drive.Transition.Powercycle"; |
| } |
| else if (*resetType == "ForceReset") |
| { |
| action = "xyz.openbmc_project.State.Drive.Transition.Reboot"; |
| } |
| else |
| { |
| BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: " |
| << *resetType; |
| messages::actionParameterNotSupported(asyncResp->res, *resetType, |
| "ResetType"); |
| return; |
| } |
| |
| BMCWEB_LOG_DEBUG << "Reset Drive with " << action; |
| |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.State.Drive"}; |
| dbus::utility::getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, |
| [asyncResp, driveId, |
| action](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "DBUS response error"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| setResetType(asyncResp, driveId, action, subtree); |
| }); |
| } |
| |
| inline void |
| handleChassisDriveReset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& driveId, |
| std::optional<std::string> resetType, |
| const std::vector<std::string>& drives) |
| { |
| std::unordered_set<std::string> drivesMap(drives.begin(), drives.end()); |
| constexpr std::array<std::string_view, 2> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Drive", |
| "xyz.openbmc_project.State.Drive"}; |
| dbus::utility::getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, |
| [asyncResp, driveId, resetType, |
| drivesMap](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "Drive mapper call error "; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| auto drive = std::find_if( |
| subtree.begin(), subtree.end(), |
| [&driveId, &drivesMap]( |
| const std::pair<std::string, dbus::utility::MapperServiceMap>& |
| object) { |
| return sdbusplus::message::object_path(object.first).filename() == |
| driveId && |
| drivesMap.contains(object.first); |
| }); |
| |
| if (drive == subtree.end()) |
| { |
| messages::resourceNotFound(asyncResp->res, "Drive Action Reset", |
| driveId); |
| return; |
| } |
| |
| const std::string& drivePath = drive->first; |
| const dbus::utility::MapperServiceMap& driveConnections = drive->second; |
| if (driveConnections.size() != 1) |
| { |
| BMCWEB_LOG_ERROR << "Connection size " << driveConnections.size() |
| << ", not equal to 1"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| bool driveInterface = false; |
| bool driveStateInterface = false; |
| for (const std::string& interface : driveConnections[0].second) |
| { |
| if (interface == "xyz.openbmc_project.Inventory.Item.Drive") |
| { |
| driveInterface = true; |
| } |
| if (interface == "xyz.openbmc_project.State.Drive") |
| { |
| driveStateInterface = true; |
| } |
| } |
| if (!driveInterface || !driveStateInterface) |
| { |
| BMCWEB_LOG_ERROR << "Drive does not have the required interfaces "; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| sdbusplus::asio::getProperty<bool>( |
| *crow::connections::systemBus, driveConnections[0].first, drivePath, |
| "xyz.openbmc_project.Inventory.Item.Drive", "Resettable", |
| [asyncResp, driveId, resetType]( |
| const boost::system::error_code propEc, bool resettable) { |
| if (propEc) |
| { |
| BMCWEB_LOG_ERROR << "Failed to get resettable property "; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| if (!resettable) |
| { |
| messages::actionNotSupported( |
| asyncResp->res, "The drive does not support resets."); |
| return; |
| } |
| performDriveReset(asyncResp, driveId, resetType); |
| }); |
| }); |
| } |
| |
| /** |
| * DriveResetAction class supports the POST method for the Reset (reboot) |
| * action. |
| */ |
| inline void requestDriveResetAction(App& app) |
| { |
| BMCWEB_ROUTE(app, |
| "/redfish/v1/Chassis/<str>/Drives/<str>/Actions/Drive.Reset/") |
| .privileges(redfish::privileges::postDrive) |
| .methods(boost::beast::http::verb::post)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId, const std::string& driveId) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| BMCWEB_LOG_DEBUG << "Post Drive Reset."; |
| |
| nlohmann::json jsonRequest; |
| std::optional<std::string> resetType; |
| if (json_util::processJsonFromRequest(asyncResp->res, req, |
| jsonRequest) && |
| !jsonRequest["ResetType"].empty()) |
| { |
| resetType = jsonRequest["ResetType"]; |
| } |
| |
| findChassisDrive(asyncResp, chassisId, |
| [asyncResp, driveId, |
| resetType](const boost::system::error_code ec, |
| const std::vector<std::string>& drives) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "failed to find drives"; |
| messages::internalError(asyncResp->res); |
| return; // no drives = no failures |
| } |
| handleChassisDriveReset(asyncResp, driveId, resetType, drives); |
| }); |
| }); |
| } |
| |
| inline void handleChassisDriveResetActionInfo( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId, const std::string& driveId, |
| const std::vector<std::string>& drives) |
| { |
| std::unordered_set<std::string> drivesMap(drives.begin(), drives.end()); |
| |
| constexpr std::array<std::string_view, 2> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Drive", |
| "xyz.openbmc_project.State.Drive"}; |
| dbus::utility::getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, |
| [asyncResp, chassisId, driveId, |
| drivesMap](const boost::system::error_code ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "Drive mapper call error"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| auto drive = std::find_if( |
| subtree.begin(), subtree.end(), |
| [&driveId, &drivesMap]( |
| const std::pair<std::string, |
| std::vector<std::pair< |
| std::string, std::vector<std::string>>>>& |
| object) { |
| return sdbusplus::message::object_path(object.first).filename() == |
| driveId && |
| drivesMap.contains(object.first); |
| }); |
| |
| if (drive == subtree.end()) |
| { |
| messages::resourceNotFound(asyncResp->res, "Drive ResetActionInfo", |
| driveId); |
| return; |
| } |
| |
| const std::string& drivePath = drive->first; |
| const dbus::utility::MapperServiceMap& driveConnections = drive->second; |
| |
| if (driveConnections.size() != 1) |
| { |
| BMCWEB_LOG_ERROR << "Connection size " << driveConnections.size() |
| << ", not equal to 1"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| bool driveInterface = false; |
| bool driveStateInterface = false; |
| for (const std::string& interface : driveConnections[0].second) |
| { |
| if (interface == "xyz.openbmc_project.Inventory.Item.Drive") |
| { |
| driveInterface = true; |
| } |
| if (interface == "xyz.openbmc_project.State.Drive") |
| { |
| driveStateInterface = true; |
| } |
| } |
| if (!driveInterface || !driveStateInterface) |
| { |
| BMCWEB_LOG_ERROR << "Drive does not have the required interfaces "; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| sdbusplus::asio::getProperty<bool>( |
| *crow::connections::systemBus, driveConnections[0].first, drivePath, |
| "xyz.openbmc_project.Inventory.Item.Drive", "Resettable", |
| [asyncResp, chassisId, |
| driveId](const boost::system::error_code propEc, bool resettable) { |
| if (propEc) |
| { |
| BMCWEB_LOG_ERROR << "Failed to get resettable property "; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| if (!resettable) |
| { |
| messages::actionNotSupported( |
| asyncResp->res, "The drive does not support resets."); |
| return; |
| } |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#ActionInfo.v1_1_2.ActionInfo"; |
| asyncResp->res.jsonValue["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", |
| chassisId, "Drives", driveId, |
| "ResetActionInfo"); |
| asyncResp->res.jsonValue["Name"] = "Reset Action Info"; |
| asyncResp->res.jsonValue["Id"] = "ResetActionInfo"; |
| nlohmann::json::array_t parameters; |
| nlohmann::json::object_t parameter; |
| parameter["Name"] = "ResetType"; |
| parameter["Required"] = true; |
| parameter["DataType"] = "String"; |
| nlohmann::json::array_t allowableValues; |
| allowableValues.emplace_back("PowerCycle"); |
| allowableValues.emplace_back("ForceRestart"); |
| parameter["AllowableValues"] = std::move(allowableValues); |
| parameters.emplace_back(parameter); |
| asyncResp->res.jsonValue["Parameters"] = std::move(parameters); |
| }); |
| }); |
| } |
| |
| /** |
| * DriveResetActionInfo derived class for delivering Drive |
| * ResetType AllowableValues using ResetInfo schema. |
| */ |
| inline void requestRoutesDriveResetActionInfo(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/ResetActionInfo/") |
| .privileges(redfish::privileges::getActionInfo) |
| .methods(boost::beast::http::verb::get)( |
| [](const crow::Request&, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId, const std::string& driveId) { |
| findChassisDrive(asyncResp, chassisId, |
| [asyncResp, chassisId, |
| driveId](const boost::system::error_code ec, |
| const std::vector<std::string>& drives) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "failed to find drives"; |
| messages::internalError(asyncResp->res); |
| return; // no drives = no failures |
| } |
| handleChassisDriveResetActionInfo(asyncResp, chassisId, driveId, |
| drives); |
| }); |
| }); |
| } |
| |
| inline void getStorageControllerAsset( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const boost::system::error_code& ec, |
| const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& |
| propertiesList) |
| { |
| if (ec) |
| { |
| // this interface isn't necessary |
| BMCWEB_LOG_DEBUG << "Failed to get StorageControllerAsset"; |
| return; |
| } |
| |
| const std::string* partNumber = nullptr; |
| const std::string* serialNumber = nullptr; |
| const std::string* manufacturer = nullptr; |
| const std::string* model = nullptr; |
| if (!sdbusplus::unpackPropertiesNoThrow( |
| dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", |
| partNumber, "SerialNumber", serialNumber, "Manufacturer", |
| manufacturer, "Model", model)) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| if (partNumber != nullptr) |
| { |
| asyncResp->res.jsonValue["PartNumber"] = *partNumber; |
| } |
| |
| if (serialNumber != nullptr) |
| { |
| asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; |
| } |
| |
| if (manufacturer != nullptr) |
| { |
| asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; |
| } |
| |
| if (model != nullptr) |
| { |
| asyncResp->res.jsonValue["Model"] = *model; |
| } |
| } |
| |
| inline void getStorageControllerLocation( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& service, const std::string& path, |
| const std::vector<std::string>& interfaces) |
| { |
| nlohmann::json::json_pointer locationPtr = "/Location"_json_pointer; |
| for (const std::string& interface : interfaces) |
| { |
| if (interface == "xyz.openbmc_project.Inventory.Decorator.LocationCode") |
| { |
| location_util::getLocationCode(asyncResp, service, path, |
| locationPtr); |
| } |
| if (location_util::isConnector(interface)) |
| { |
| std::optional<std::string> locationType = |
| location_util::getLocationType(interface); |
| if (!locationType) |
| { |
| BMCWEB_LOG_DEBUG |
| << "getLocationType for StorageController failed for " |
| << interface; |
| continue; |
| } |
| asyncResp->res |
| .jsonValue[locationPtr]["PartLocation"]["LocationType"] = |
| *locationType; |
| } |
| } |
| } |
| |
| // TODO(matt): could move to dbus_utility.hpp |
| inline std::optional<std::string> |
| matchServiceName(const dbus::utility::MapperServiceMap& allServices, |
| const std::string& matchIface) |
| { |
| // TODO(matt): assumes only a single service matches |
| for (const auto& [service, interfaces] : allServices) |
| { |
| for (const auto& interface : interfaces) |
| { |
| if (interface == matchIface) |
| { |
| return service; |
| } |
| } |
| } |
| return {}; |
| } |
| |
| inline void tryPopulateControllerNvme( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& path, const dbus::utility::MapperServiceMap& ifaces) |
| { |
| if (!matchServiceName(ifaces, "xyz.openbmc_project.NVMe.NVMeAdmin")) |
| { |
| return; |
| } |
| |
| auto& nvprop = asyncResp->res.jsonValue["NVMeControllerProperties"]; |
| // TODO(matt) fetch other properties, don't use hardcoded values |
| nvprop["ControllerType"] = "IO"; |
| nvprop["NVMeVersion"] = "1.4"; |
| (void)path; |
| } |
| |
| inline void tryPopulateControllerSecurity( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const boost::urls::url& controllerUrl, |
| const dbus::utility::MapperServiceMap& ifaces) |
| { |
| if (!matchServiceName( |
| ifaces, |
| "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity")) |
| { |
| return; |
| } |
| |
| boost::urls::url sendUrl(controllerUrl); |
| crow::utility::appendUrlPieces(sendUrl, "Actions", |
| "StorageController.SecuritySend"); |
| boost::urls::url receiveUrl(controllerUrl); |
| crow::utility::appendUrlPieces(receiveUrl, "Actions", |
| "StorageController.SecurityReceive"); |
| |
| auto& actions = asyncResp->res.jsonValue["Actions"]; |
| actions["#StorageController.SecuritySend"]["target"] = sendUrl; |
| actions["#StorageController.SecurityReceive"]["target"] = receiveUrl; |
| } |
| |
| inline void securitySendAction( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& path, const dbus::utility::MapperServiceMap& ifaces, |
| uint8_t proto, uint16_t protoSpecific, const std::string& dataBase64) |
| { |
| std::string dataString; |
| if (!crow::utility::base64Decode(dataBase64, dataString)) |
| { |
| BMCWEB_LOG_DEBUG << "base data base64decode"; |
| messages::actionParameterValueFormatError( |
| asyncResp->res, "<data>", "Data", "StorageController.SecuritySend"); |
| return; |
| } |
| |
| // base64Decode outputs a string not bytes |
| std::span<const uint8_t> data( |
| reinterpret_cast<const uint8_t*>(dataString.data()), dataString.size()); |
| |
| auto service = matchServiceName( |
| ifaces, "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity"); |
| if (!service) |
| { |
| BMCWEB_LOG_DEBUG << "No servicename"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| crow::connections::systemBus->async_method_call( |
| [asyncResp](const boost::system::error_code ec, |
| const sdbusplus::message_t& msg) { |
| // Failure returned from NVMe |
| const ::sd_bus_error* sd_err = msg.get_error(); |
| if (sd_err) |
| { |
| messages::generalError(asyncResp->res); |
| BMCWEB_LOG_DEBUG << "SecuritySend NVMe error"; |
| if (sd_err->message) |
| { |
| BMCWEB_LOG_DEBUG << "Error: " << sd_err->name << " message " |
| << sd_err->message; |
| asyncResp->res.jsonValue["error"]["message"] = sd_err->message; |
| } |
| return; |
| } |
| |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "SecuritySend dbus error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // success |
| }, |
| *service, path, |
| "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity", |
| "SecuritySend", proto, protoSpecific, data); |
| } |
| |
| inline void securityReceiveAction( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& path, const dbus::utility::MapperServiceMap& ifaces, |
| uint8_t proto, uint16_t protoSpecific, uint32_t transferLength) |
| { |
| auto service = matchServiceName( |
| ifaces, "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity"); |
| if (!service) |
| { |
| BMCWEB_LOG_DEBUG << "No servicename"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| crow::connections::systemBus->async_method_call( |
| [asyncResp](const boost::system::error_code ec, |
| const sdbusplus::message_t& msg, |
| const std::vector<uint8_t>& data) { |
| // Failure returned from NVMe |
| const ::sd_bus_error* sd_err = msg.get_error(); |
| if (sd_err) |
| { |
| messages::generalError(asyncResp->res); |
| BMCWEB_LOG_DEBUG << "SecurityReceive NVMe error"; |
| if (sd_err->message) |
| { |
| BMCWEB_LOG_DEBUG << "Error: " << sd_err->name << " message " |
| << sd_err->message; |
| asyncResp->res.jsonValue["error"]["message"] = sd_err->message; |
| } |
| return; |
| } |
| |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "SecurityReceive dbus error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Success |
| asyncResp->res.jsonValue["Data"] = |
| crow::utility::base64encode(std::string_view( |
| reinterpret_cast<const char*>(data.data()), data.size())); |
| }, |
| *service, path, |
| "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity", |
| "SecurityReceive", proto, protoSpecific, transferLength); |
| } |
| |
| // Finds a controller and runs a callback |
| inline void findStorageController( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId, const std::string& controllerId, |
| const std::function<void(const std::string& path, |
| const dbus::utility::MapperServiceMap& ifaces)>& |
| cb) |
| { |
| // Find storage |
| crow::connections::systemBus->async_method_call( |
| [asyncResp, storageId, controllerId, |
| cb](const boost::system::error_code ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG |
| << "requestRoutesStorageController DBUS response error"; |
| messages::resourceNotFound( |
| asyncResp->res, "#StorageController.v1_6_0.StorageController", |
| controllerId); |
| return; |
| } |
| |
| auto storage = std::find_if( |
| subtree.begin(), subtree.end(), |
| [&storageId]( |
| const std::pair<std::string, dbus::utility::MapperServiceMap>& |
| object) { |
| return sdbusplus::message::object_path(object.first).filename() == |
| storageId; |
| }); |
| if (storage == subtree.end()) |
| { |
| messages::resourceNotFound(asyncResp->res, |
| "#Storage.v1_9_1.Storage", storageId); |
| return; |
| } |
| |
| // Find controller below the storagePath |
| crow::connections::systemBus->async_method_call( |
| [asyncResp, storageId, controllerId, |
| cb](const boost::system::error_code ec2, |
| const dbus::utility::MapperGetSubTreeResponse& subtree2) { |
| if (ec2) |
| { |
| BMCWEB_LOG_DEBUG |
| << "requestRoutesStorageController DBUS response error" |
| << ec2; |
| messages::resourceNotFound( |
| asyncResp->res, |
| "#StorageController.v1_6_0.StorageController", |
| controllerId); |
| return; |
| } |
| |
| auto ctrl = std::find_if( |
| subtree2.begin(), subtree2.end(), |
| [&controllerId]( |
| const std::pair<std::string, |
| dbus::utility::MapperServiceMap>& object) { |
| return sdbusplus::message::object_path(object.first) |
| .filename() == controllerId; |
| }); |
| if (ctrl == subtree2.end()) |
| { |
| messages::resourceNotFound( |
| asyncResp->res, |
| "#StorageController.v1_6_0.StorageController", |
| controllerId); |
| return; |
| } |
| |
| cb(ctrl->first, ctrl->second); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", storage->first, 0, |
| std::array<const char*, 1>{ |
| "xyz.openbmc_project.Inventory.Item.StorageController"}); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| "/xyz/openbmc_project/inventory", 0, |
| std::array<std::string, 1>{ |
| "xyz.openbmc_project.Inventory.Item.Storage"}); |
| } |
| |
| inline static void |
| setCustomSSDOemGpio(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& path, const std::string& property, |
| bool value) |
| { |
| |
| std::string customSSDDbusInterface = |
| "com.google.gbmc.ssd." + std::string(customSSD); |
| sdbusplus::asio::setProperty( |
| *crow::connections::systemBus, "com.google.gbmc.ssd", path, |
| customSSDDbusInterface, property, value, |
| [asyncResp](const boost::system::error_code ec) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "setCustomSSDOemGpio D-Bus responses error: " |
| << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| messages::success(asyncResp->res); |
| }); |
| } |
| |
| inline static void |
| setCustomSSDSpiImage(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& path, const std::string& property, |
| std::string value) |
| { |
| std::string customSSDDbusInterface = |
| "com.google.gbmc.ssd." + std::string(customSSD); |
| sdbusplus::asio::setProperty( |
| *crow::connections::systemBus, "com.google.gbmc.ssd", path, |
| customSSDDbusInterface, property, value, |
| [asyncResp](const boost::system::error_code ec) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "setCustomSSDSpiImage D-Bus responses error: " |
| << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| messages::success(asyncResp->res); |
| }); |
| } |
| |
| inline void requestRoutesStorageControllerActions(App& app) |
| { |
| BMCWEB_ROUTE( |
| app, |
| "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/Actions/StorageController.SecuritySend") |
| .privileges(redfish::privileges::postStorageController) |
| .methods(boost::beast::http::verb::post)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& controllerId) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| |
| uint8_t proto; |
| uint16_t protoSpecific; |
| std::string dataBase64; |
| |
| if (!json_util::readJsonAction(req, asyncResp->res, "SecurityProtocol", |
| proto, "SecurityProtocolSpecific", |
| protoSpecific, "Data", dataBase64)) |
| { |
| BMCWEB_LOG_DEBUG << "Missing request json parameters"; |
| return; |
| } |
| |
| findStorageController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, proto, protoSpecific, |
| dataBase64](const std::string& path, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| securitySendAction(asyncResp, path, ifaces, proto, protoSpecific, |
| dataBase64); |
| }); |
| }); |
| |
| BMCWEB_ROUTE( |
| app, |
| "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/Actions/StorageController.SecurityReceive") |
| .privileges(redfish::privileges::postStorageController) |
| .methods(boost::beast::http::verb::post)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& controllerId) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| |
| uint8_t proto; |
| uint16_t protoSpecific; |
| uint32_t transferLength; |
| |
| if (!json_util::readJsonAction(req, asyncResp->res, "SecurityProtocol", |
| proto, "SecurityProtocolSpecific", |
| protoSpecific, "AllocationLength", |
| transferLength)) |
| { |
| BMCWEB_LOG_DEBUG << "Missing request json parameters"; |
| return; |
| } |
| |
| findStorageController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, proto, protoSpecific, |
| transferLength](const std::string& path, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| securityReceiveAction(asyncResp, path, ifaces, proto, protoSpecific, |
| transferLength); |
| }); |
| }); |
| |
| BMCWEB_ROUTE(app, |
| "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>") |
| .privileges(redfish::privileges::patchStorageController) |
| .methods(boost::beast::http::verb::patch)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& controllerId) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| |
| if (!enableCustomSSD) |
| return; |
| |
| nlohmann::json customSSDOem; |
| if (!json_util::readJsonPatch( |
| req, asyncResp->res, |
| "Links/Oem/Google/" + std::string(customSSD), customSSDOem)) |
| { |
| BMCWEB_LOG_DEBUG |
| << std::string(customSSD) + " OEM is not in the patch input"; |
| return; |
| } |
| |
| findStorageController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, storageId, controllerId, |
| customSSDOem](const std::string& path, |
| const dbus::utility::MapperServiceMap&) { |
| std::string otpWriteEnableProperty = |
| customSSDController + std::string("OtpWriteEnable"); |
| if (customSSDOem.contains(otpWriteEnableProperty)) |
| { |
| setCustomSSDOemGpio(asyncResp, path, otpWriteEnableProperty, |
| customSSDOem[otpWriteEnableProperty]); |
| } |
| if (customSSDOem.contains("TriggerPowerCycle")) |
| { |
| setCustomSSDOemGpio(asyncResp, path, "TriggerPowerCycle", |
| customSSDOem["TriggerPowerCycle"]); |
| } |
| if (customSSDOem.contains("DisableWatchdog")) |
| { |
| setCustomSSDOemGpio(asyncResp, path, "DisableWatchdog", |
| customSSDOem["DisableWatchdog"]); |
| } |
| if (customSSDOem.contains("TriggerReset")) |
| { |
| setCustomSSDOemGpio(asyncResp, path, "TriggerReset", |
| customSSDOem["TriggerReset"]); |
| } |
| if (customSSDOem.contains("CpldReset")) |
| { |
| setCustomSSDOemGpio(asyncResp, path, "CpldReset", |
| customSSDOem["CpldReset"]); |
| } |
| if (customSSDOem.contains("SpiImgSelect")) |
| { |
| setCustomSSDSpiImage(asyncResp, path, "SpiImgSelect", |
| customSSDOem["SpiImgSelect"]); |
| } |
| }); |
| }); |
| } |
| |
| inline void populateStorageController( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId, const std::string& controllerId, |
| const std::string& connectionName, const std::string& path, |
| const dbus::utility::MapperServiceMap& ifaces, |
| const std::vector<std::string>& interfaces) |
| { |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#StorageController.v1_7_0.StorageController"; |
| auto url = crow::utility::urlFromPieces("redfish", "v1", "Systems", |
| "system", "Storage", storageId, |
| "Controllers", controllerId); |
| asyncResp->res.jsonValue["@odata.id"] = url; |
| asyncResp->res.jsonValue["Name"] = controllerId; |
| asyncResp->res.jsonValue["Id"] = controllerId; |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["LocationType"] = |
| "Embedded"; |
| getStorageControllerLocation(asyncResp, connectionName, path, interfaces); |
| tryPopulateControllerNvme(asyncResp, path, ifaces); |
| tryPopulateControllerSecurity(asyncResp, url, ifaces); |
| if (enableCustomSSD) |
| { |
| populateCustomSSDInfo(asyncResp, ifaces, path); |
| } |
| |
| sdbusplus::asio::getProperty<bool>( |
| *crow::connections::systemBus, connectionName, path, |
| "xyz.openbmc_project.Inventory.Item", "Present", |
| [asyncResp](const boost::system::error_code& ec, bool isPresent) { |
| // this interface isn't necessary, only check it |
| // if we get a good return |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "Failed to get Present property"; |
| return; |
| } |
| if (!isPresent) |
| { |
| asyncResp->res.jsonValue["Status"]["State"] = "Absent"; |
| } |
| }); |
| |
| sdbusplus::asio::getAllProperties( |
| *crow::connections::systemBus, connectionName, path, |
| "xyz.openbmc_project.Inventory.Decorator.Asset", |
| [asyncResp](const boost::system::error_code& ec, |
| const std::vector< |
| std::pair<std::string, dbus::utility::DbusVariantType>>& |
| propertiesList) { |
| getStorageControllerAsset(asyncResp, ec, propertiesList); |
| }); |
| } |
| |
| inline void getStorageControllerHandler( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId, const std::string& controllerId, |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) |
| { |
| if (ec || subtree.empty()) |
| { |
| // doesn't have to be there |
| BMCWEB_LOG_DEBUG << "Failed to handle StorageController"; |
| return; |
| } |
| |
| for (const auto& [path, interfaceDict] : subtree) |
| { |
| sdbusplus::message::object_path object(path); |
| std::string id = object.filename(); |
| if (id.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Failed to find filename in " << path; |
| return; |
| } |
| if (id != controllerId) |
| { |
| continue; |
| } |
| |
| if (interfaceDict.size() != 1) |
| { |
| BMCWEB_LOG_ERROR << "Connection size " << interfaceDict.size() |
| << ", greater than 1"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| const std::string& connectionName = interfaceDict.front().first; |
| populateStorageController(asyncResp, storageId, controllerId, |
| connectionName, path, interfaceDict, |
| interfaceDict.front().second); |
| } |
| } |
| |
| inline void populateStorageControllerCollection( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const boost::system::error_code& ec, const std::string& storageId, |
| const dbus::utility::MapperGetSubTreePathsResponse& controllerList) |
| { |
| nlohmann::json::array_t members; |
| if (ec || controllerList.empty()) |
| { |
| asyncResp->res.jsonValue["Members"] = std::move(members); |
| asyncResp->res.jsonValue["Members@odata.count"] = 0; |
| BMCWEB_LOG_DEBUG << "Failed to find any StorageController"; |
| return; |
| } |
| |
| for (const std::string& path : controllerList) |
| { |
| std::string id = sdbusplus::message::object_path(path).filename(); |
| if (id.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Failed to find filename in " << path; |
| return; |
| } |
| nlohmann::json::object_t member; |
| member["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", "system", "Storage", storageId, |
| "Controllers", id); |
| members.emplace_back(member); |
| } |
| asyncResp->res.jsonValue["Members@odata.count"] = members.size(); |
| asyncResp->res.jsonValue["Members"] = std::move(members); |
| } |
| |
| void findStorage( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId, |
| std::function<void(const sdbusplus::message::object_path& storagePath)> cb) |
| { |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Storage"}; |
| // mapper call chassis |
| dbus::utility::getSubTreePaths( |
| "/xyz/openbmc_project/inventory", 0, interfaces, |
| [asyncResp, storageId, |
| cb](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& storageList) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "findStorageController DBUS response error"; |
| messages::resourceNotFound(asyncResp->res, |
| "#Storage.v1_13_0.Storage", storageId); |
| return; |
| } |
| |
| auto storage = std::find_if(storageList.begin(), storageList.end(), |
| [&storageId](const std::string& path) { |
| return sdbusplus::message::object_path(path).filename() == |
| storageId; |
| }); |
| if (storage == storageList.end()) |
| { |
| BMCWEB_LOG_DEBUG << "findStorageController couldn't find " |
| << storageId; |
| messages::resourceNotFound(asyncResp->res, |
| "#Storage.v1_13_0.Storage", storageId); |
| return; |
| } |
| |
| cb(sdbusplus::message::object_path(*storage)); |
| }); |
| } |
| |
| inline void storageControllerCollectionHandler( |
| App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| BMCWEB_LOG_DEBUG |
| << "Failed to setup Redfish Route for StorageController Collection"; |
| return; |
| } |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| BMCWEB_LOG_DEBUG << "Failed to find ComputerSystem of " << systemName; |
| return; |
| } |
| |
| findStorage(asyncResp, storageId, |
| [asyncResp, storageId]( |
| const sdbusplus::message::object_path& storagePath) { |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#StorageControllerCollection.StorageControllerCollection"; |
| asyncResp->res.jsonValue["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", "system", |
| "Storage", storageId, "Controllers"); |
| asyncResp->res.jsonValue["Name"] = "Storage Controller Collection"; |
| |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.StorageController"}; |
| dbus::utility::getAssociatedSubTreePaths( |
| storagePath / "storage_controller", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), |
| 0, interfaces, |
| [asyncResp, |
| storageId](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& |
| controllerList) { |
| populateStorageControllerCollection(asyncResp, ec, storageId, |
| controllerList); |
| }); |
| }); |
| } |
| |
| inline void storageControllerHandler( |
| App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& controllerId) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| BMCWEB_LOG_DEBUG |
| << "Failed to setup Redfish Route for StorageController"; |
| return; |
| } |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| BMCWEB_LOG_DEBUG << "Failed to find ComputerSystem of " << systemName; |
| return; |
| } |
| findStorage(asyncResp, storageId, |
| [asyncResp, storageId, controllerId]( |
| const sdbusplus::message::object_path& storagePath) { |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.StorageController"}; |
| dbus::utility::getAssociatedSubTree( |
| storagePath / "storage_controller", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), |
| 0, interfaces, |
| [asyncResp, storageId, controllerId]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| getStorageControllerHandler(asyncResp, storageId, controllerId, ec, |
| subtree); |
| }); |
| }); |
| } |
| |
| inline void requestRoutesStorageControllerCollection(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/") |
| .privileges(redfish::privileges::getStorageControllerCollection) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(storageControllerCollectionHandler, std::ref(app))); |
| } |
| |
| inline void requestRoutesStorageController(App& app) |
| { |
| BMCWEB_ROUTE(app, |
| "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>") |
| .privileges(redfish::privileges::getStorageController) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(storageControllerHandler, std::ref(app))); |
| } |
| |
| } // namespace redfish |