| /* |
| // 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. |
| */ |
| |
| /** |
| * The following requirments on Dbus should be satisfied for storage stack |
| * working properly: |
| * * system name and the storage name are globally unique |
| * * On Single System system, the default system DBus object can be omitted. |
| * * On Multi System system, the system DBus object and the association |
| * (containing/contained_by) to Storage are manditory. |
| */ |
| #pragma once |
| |
| #include "app.hpp" |
| #include "dbus_utility.hpp" |
| #include "generated/enums/drive.hpp" |
| #include "health.hpp" |
| #include "human_sort.hpp" |
| #include "managed_store.hpp" |
| #include "managed_store_types.hpp" |
| #include "openbmc_dbus_rest.hpp" |
| #include "query.hpp" |
| #include "redfish_util.hpp" |
| #include "registries/privilege_registry.hpp" |
| #include "task.hpp" |
| #include "utils/dbus_utils.hpp" |
| #include "utils/hex_utils.hpp" |
| #include "utils/nvme_metric_utils.hpp" |
| #include "utils/storage_utils.hpp" |
| |
| #include <boost/system/error_code.hpp> |
| #include <sdbusplus/asio/property.hpp> |
| #include <sdbusplus/unpack_properties.hpp> |
| #include <utils/location_utils.hpp> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstdlib> |
| #include <filesystem> |
| #include <string_view> |
| #include <unordered_set> |
| |
| #ifdef UNIT_TEST_BUILD |
| #include "test/g3/mock_managed_store.hpp" // NOLINT |
| #endif |
| |
| namespace redfish |
| { |
| |
| /* Converts a NVMe dbus error to a redfish equivalent, adds to the response */ |
| inline void storageAddDbusError(crow::Response& res, std::string_view func, |
| const std::string& storageId, |
| std::string_view errorName, |
| std::string_view errorDesc) |
| { |
| (void)storageId; |
| |
| crow::Response err; |
| |
| BMCWEB_LOG_DEBUG << func << " " << errorName << ", " << errorDesc; |
| if (errorName == "xyz.openbmc_project.Common.Error.TooManyResources") |
| { |
| messages::createLimitReachedForResource(err); |
| } |
| else if (errorName == "xyz.openbmc_project.Common.Error.InvalidArgument") |
| { |
| messages::propertyValueError(err, ""); |
| } |
| else if (errorName == |
| "xyz.openbmc_project.Common.Error.DeviceOperationFailed" || |
| errorName == "xyz.openbmc_project.Common.Error.UnsupportedRequest") |
| { |
| messages::operationFailed(err); |
| } |
| else |
| { |
| messages::internalError(err); |
| } |
| |
| // Some messages have "error" toplevel, others have "@Message.ExtendedInfo" |
| // (addMessageToErrorJson() versus addMessageToJson()). Choose which. |
| nlohmann::json extInfo; |
| if (err.jsonValue.contains("error")) |
| { |
| extInfo = err.jsonValue["error"][messages::messageAnnotation][0]; |
| } |
| else |
| { |
| extInfo = err.jsonValue[messages::messageAnnotation][0]; |
| } |
| |
| // Keep the specific error message provided from the NVMe software. |
| extInfo["Message"] = errorDesc; |
| |
| messages::moveErrorsToErrorJson(res.jsonValue, extInfo); |
| res.result(boost::beast::http::status::bad_request); |
| } |
| |
| /** |
| * @brief Makes sure the target Storage can be accessed through given System |
| * @param asyncResp Pointer to object holding response data |
| * @param systemName ID (D-Bus filename) of the System |
| * @param storageId ID (D-Bus filename) of the Storage |
| // * @param cb Callback function to call if the access is valid |
| * |
| * Sets an error response if the Storage can't be accessed through this System |
| */ |
| inline void |
| checkSystemAndStorage(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, |
| const std::string& storageId) |
| { |
| storage_utils::findStorage( |
| asyncResp, storageId, |
| [asyncResp, systemName, |
| storageId](const sdbusplus::message::object_path& storagePath) { |
| storage_utils::getSystemPathFromStorage( |
| asyncResp, storagePath, |
| [asyncResp, systemName, |
| storageId](std::optional<std::string_view> systemPath) { |
| if ((!systemPath && |
| systemName != "system") // default single system system |
| || |
| (systemPath && |
| std::filesystem::path(*systemPath).filename() != systemName)) |
| { |
| BMCWEB_LOG_ERROR |
| << "No association between System and Storage: " |
| << systemName << ", " << storageId; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| // cb(); |
| }); |
| }); |
| } |
| |
| inline void handleGetSystemStorageCollection( |
| App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#StorageCollection.StorageCollection"; |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", systemName, "Storage"); |
| asyncResp->res.jsonValue["Name"] = "Storage Collection"; |
| |
| storage_utils::getSystemPathFromName( |
| asyncResp, systemName, |
| [asyncResp, systemName](std::optional<std::string_view> systemPath) { |
| if (!systemPath) // fall back to single-system system |
| { |
| |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| collection_util::getCollectionMembers( |
| asyncResp, |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", |
| systemName, "Storage"), |
| std::array<std::string_view, 1>{ |
| {"xyz.openbmc_project.Inventory.Item.Storage"}}); |
| return; |
| } |
| collection_util::getAssociatedCollectionMembers( |
| asyncResp, |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName, |
| "Storage"), |
| std::array<std::string_view, 1>{ |
| {"xyz.openbmc_project.Inventory.Item.Storage"}}, |
| (std::string(*systemPath) + "/containing").c_str()); |
| }); |
| } |
| |
| inline void handleGetStorageCollection( |
| App& 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 requestRoutesStorageCollection(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/") |
| .privileges(redfish::privileges::getStorageCollection) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleGetSystemStorageCollection, std::ref(app))); |
| |
| BMCWEB_ROUTE(app, "/redfish/v1/Storage/") |
| .privileges(redfish::privileges::getStorageCollection) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleGetStorageCollection, std::ref(app))); |
| } |
| |
| 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"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( |
| storagePath / "drive", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, |
| interfaces, requestContext, |
| [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.emplace_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." + |
| boost::algorithm::to_lower_copy(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; |
| } |
| |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| asyncResp->res.jsonValue["Links"]["Oem"]["Google"][std::string(customSSD)] = |
| nlohmann::json::object_t(); |
| dbus_utils::getProperty<bool>( |
| connection, path, customSSDDbusInterface, "PwrseqPgood", context, |
| [asyncResp, path](const boost::system::error_code& ec, |
| bool pwrseqPgood) { |
| // this interface isn't necessary, only check it if |
| // we get a good return |
| if (ec) |
| { |
| return; |
| } |
| |
| asyncResp->res.jsonValue["Links"]["Oem"]["Google"] |
| [std::string(customSSD)]["PwrseqPgood"] = |
| pwrseqPgood; |
| }); |
| managedStore::GetManagedObjectStore()->getAllProperties( |
| connection, path, customSSDDbusInterface, context, |
| [asyncResp, connection, path, context]( |
| 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* 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, "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& customSSDJsonObj = |
| asyncResp->res |
| .jsonValue["Links"]["Oem"]["Google"][std::string(customSSD)]; |
| customSSDJsonObj["@odata.type"] = customSSDOdataType; |
| // Write Only and will always read as false. |
| customSSDJsonObj["CpldReset"] = false; |
| |
| if (manufacturingMode != nullptr) |
| { |
| customSSDJsonObj["ManufacturingMode"] = *manufacturingMode; |
| } |
| 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"; |
| }); |
| |
| managedStore::GetManagedObjectStore()->getAllProperties( |
| connection, path, "xyz.openbmc_project.Inventory.Decorator.Asset", |
| context, |
| [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"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( |
| storagePath / "chassis", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, |
| interfaces, requestContext, |
| [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.empty()) |
| { |
| BMCWEB_LOG_DEBUG << "Can not find the Chassis containing the drive"; |
| return; |
| } |
| |
| if (chassisList.size() != 1) |
| { |
| BMCWEB_LOG_ERROR |
| << "Storage is not associated with only one chassis"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| const 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 |
| handleGetSystemStorage(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)) |
| { |
| return; |
| } |
| |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Storage"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTreePaths( |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, |
| [asyncResp, systemName, storageId]( |
| const boost::system::error_code ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& 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::string& path) { |
| return sdbusplus::message::object_path(path).filename() == |
| storageId; |
| }); |
| if (storage == subtree.end()) |
| { |
| messages::resourceNotFound(asyncResp->res, |
| "#Storage.v1_13_0.Storage", storageId); |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", systemName.c_str(), "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); |
| asyncResp->res.jsonValue["Controllers"]["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", |
| systemName.c_str(), "Storage", |
| storageId, "Controllers"); |
| asyncResp->res.jsonValue["Volumes"]["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", |
| systemName.c_str(), "Storage", |
| storageId, "Volumes"); |
| }); |
| } |
| |
| inline void |
| handleGetStorage(App& 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"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTreePaths( |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, |
| [asyncResp, storageId]( |
| const boost::system::error_code ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& 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::string& storage) { |
| return sdbusplus::message::object_path(storage).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. |
| storage_utils::getSystemPathFromStorage( |
| asyncResp, *storage, |
| [asyncResp, storageId](std::optional<std::string_view> systemPath) { |
| nlohmann::json::array_t storageServices; |
| nlohmann::json::object_t storageService; |
| |
| std::string systemName = |
| systemPath ? std::filesystem::path(*systemPath).filename() |
| : "system"; |
| storageService["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", systemName, "Storage", storageId); |
| storageServices.emplace_back(storageService); |
| asyncResp->res.jsonValue["Links"]["StorageServices"] = |
| std::move(storageServices); |
| asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = |
| 1; |
| }); |
| }); |
| } |
| |
| inline void requestRoutesStorage(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/") |
| .privileges(redfish::privileges::getStorage) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleGetSystemStorage, std::ref(app))); |
| |
| BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/") |
| .privileges(redfish::privileges::getStorage) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleGetStorage, std::ref(app))); |
| } |
| |
| inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& connectionName, |
| const std::string& path) |
| { |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| managedStore::GetManagedObjectStore()->getAllProperties( |
| connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset", |
| context, |
| [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) |
| { |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| dbus_utils::getProperty<bool>( |
| connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present", |
| requestContext, |
| [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) |
| { |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| dbus_utils::getProperty<bool>( |
| connectionName, path, "xyz.openbmc_project.State.Drive", "Rebuilding", |
| requestContext, |
| [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"); |
| } |
| |
| 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"; |
| } |
| if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.eMMC") |
| { |
| return "eMMC"; |
| } |
| |
| 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) |
| { |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| managedStore::GetManagedObjectStore()->getAllProperties( |
| connectionName, path, "xyz.openbmc_project.Inventory.Item.Drive", |
| context, |
| [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; |
| } |
| const std::string* encryptionStatus = nullptr; |
| const bool* isLocked = nullptr; |
| 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); |
| } |
| } |
| else if (propertyName == "EncryptionStatus") |
| { |
| encryptionStatus = std::get_if<std::string>(&property.second); |
| if (encryptionStatus == nullptr) |
| { |
| BMCWEB_LOG_ERROR << "Illegal property: EncryptionStatus"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| } |
| else if (propertyName == "Locked") |
| { |
| isLocked = std::get_if<bool>(&property.second); |
| if (isLocked == nullptr) |
| { |
| BMCWEB_LOG_ERROR << "Illegal property: Locked"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| } |
| } |
| |
| if (encryptionStatus == nullptr || isLocked == nullptr || |
| *encryptionStatus == |
| "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown") |
| { |
| return; |
| } |
| if (*encryptionStatus != |
| "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted") |
| { |
| //"The drive is not currently encrypted." |
| asyncResp->res.jsonValue["EncryptionStatus"] = |
| drive::EncryptionStatus::Unencrypted; |
| return; |
| } |
| if (*isLocked) |
| { |
| //"The drive is currently encrypted and the data is not |
| // accessible to the user." |
| asyncResp->res.jsonValue["EncryptionStatus"] = |
| drive::EncryptionStatus::Locked; |
| return; |
| } |
| // if not locked |
| // "The drive is currently encrypted but the data is accessible |
| // to the user in unencrypted form." |
| asyncResp->res.jsonValue["EncryptionStatus"] = |
| drive::EncryptionStatus::Unlocked; |
| }); |
| } |
| |
| inline void getDriveErase(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& chassisId, |
| const std::string& driveName) |
| { |
| auto eraseUrl = crow::utility::urlFromPieces( |
| "redfish", "v1", "Chassis", chassisId, "Drives", driveName, "Actions", |
| "Drive.SecureErase"); |
| asyncResp->res.jsonValue["Actions"]["#Drive.SecureErase"]["target"] = |
| eraseUrl; |
| } |
| |
| 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::string& chassisId) |
| { |
| 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; |
| } |
| else if (interface == "xyz.openbmc_project.Inventory.Item.DriveErase") |
| { |
| getDriveErase(asyncResp, chassisId, driveId); |
| } |
| } |
| |
| if (driveInterface) |
| { |
| getDriveItemProperties(asyncResp, driveId, chassisId, connectionName, |
| path, driveStateInterface); |
| storage_utils::tryGetLocation(asyncResp, connectionName, path, |
| storage_utils::driveInterface, interfaces, |
| "/PhysicalLocation"_json_pointer); |
| } |
| } |
| |
| /** |
| * 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"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, |
| [asyncResp, chassisId, requestContext]( |
| 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_utils::getAssociationEndPoints( |
| path + "/drive", requestContext, |
| [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.emplace_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.emplace_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 storageVolumes( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const sdbusplus::message::object_path& storagePath, |
| std::function<void(const boost::system::error_code& ec, |
| const std::vector<std::string>& volPaths)>&& cb) |
| { |
| // Get list of attached volumes |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Volume"}; |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getAssociatedSubTreePaths( |
| storagePath / "containing", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, |
| interfaces, context, |
| [cb](const boost::system::error_code& ec, |
| const std::vector<std::string>& volPaths) { cb(ec, volPaths); }); |
| } |
| |
| 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; |
| |
| // mapper call chassis |
| storage_utils::findStorage( |
| asyncResp, driveName, |
| [asyncResp, |
| driveName](const sdbusplus::message::object_path& storagePath) { |
| storage_utils::getSystemPathFromStorage( |
| asyncResp, storagePath, |
| [asyncResp, storagePath, |
| driveName](std::optional<std::string_view> systemPath) { |
| std::string systemName = |
| systemPath ? std::filesystem::path(*systemPath).filename() |
| : "system"; |
| storageVolumes( |
| asyncResp, storagePath, |
| [asyncResp, systemName, driveName]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& |
| volumeList) { |
| nlohmann::json::array_t volumes; |
| if (ec || volumeList.empty()) |
| { |
| // No association found, just return. |
| return; |
| } |
| |
| for (const std::string& path : volumeList) |
| { |
| std::string id = |
| sdbusplus::message::object_path(path).filename(); |
| if (id.empty()) |
| { |
| // Empty list, just return. |
| return; |
| } |
| nlohmann::json::object_t volume; |
| volume["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", systemName, "Storage", |
| driveName, "Volumes", id); |
| volumes.emplace_back(volume); |
| } |
| asyncResp->res.jsonValue["Links"]["Volumes@odata.count"] = |
| volumes.size(); |
| asyncResp->res.jsonValue["Links"]["Volumes"] = |
| std::move(volumes); |
| }); |
| }); |
| }); |
| |
| 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"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTree( |
| "/xyz/openbmc_project/inventory", 0, driveInterface, requestContext, |
| [asyncResp, chassisId, driveName]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| buildDrive(asyncResp, chassisId, driveName, ec, subtree); |
| }); |
| return; |
| } |
| messages::resourceNotFound(asyncResp->res, "#Drive.v1_7_0.Drive", |
| driveName); |
| } |
| |
| struct EraseParams |
| { |
| |
| enum Action : std::uint8_t |
| { |
| CryptoErase, |
| BlockErase, |
| Overwrite, |
| } action; |
| |
| static std::optional<EraseParams> |
| parse(const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) |
| { |
| // Redfish allows sanitizationType to be defaulted, though we don't |
| // know a good default at present, leave it mandatory. |
| std::string sanitizationType; |
| |
| if (!json_util::readJsonAction(req, asyncResp->res, "SanitizationType", |
| sanitizationType)) |
| { |
| BMCWEB_LOG_DEBUG << "Missing request json parameters"; |
| return std::nullopt; |
| } |
| |
| Action action = Action::CryptoErase; |
| if (sanitizationType == "BlockErase") |
| { |
| action = Action::BlockErase; |
| } |
| else if (sanitizationType == "CryptographicErase") |
| { |
| action = Action::CryptoErase; |
| } |
| else if (sanitizationType == "Overwrite") |
| { |
| // Redfish defines an optional "OverwritePasses" parameter, we |
| // don't handle that at the moment. If the client passes it, the |
| // readJsonAction will fail it. |
| action = Action::Overwrite; |
| } |
| else |
| { |
| messages::actionParameterValueNotInList( |
| asyncResp->res, sanitizationType, "SanitizationType", |
| "Drive.SecureErase"); |
| return std::nullopt; |
| } |
| |
| return EraseParams{.action = action}; |
| } |
| |
| std::string actionName() const |
| { |
| switch (action) |
| { |
| case Action::CryptoErase: |
| return "xyz.openbmc_project.Inventory.Item.DriveErase.EraseAction.CryptoErase"; |
| case Action::BlockErase: |
| return "xyz.openbmc_project.Inventory.Item.DriveErase.EraseAction.BlockErase"; |
| case Action::Overwrite: |
| return "xyz.openbmc_project.Inventory.Item.DriveErase.EraseAction.Overwrite"; |
| } |
| return "unreachable"; |
| } |
| }; |
| |
| inline void eraseTaskUpdate(bool eraseInProgress, |
| const std::shared_ptr<task::TaskData>& taskData, |
| const std::string& connectionName, |
| const std::string& drivePath) |
| { |
| if (eraseInProgress) |
| { |
| // nothing to do |
| return; |
| } |
| |
| // has finished, either success or failure |
| taskData->stopMonitor(); |
| |
| // Have to create a new Resp here as we cannot use the original resp or it never goes out of scope |
| // This resp will not have a strand associated with it which is okay because this will never be |
| // multithreaded |
| auto resp = std::make_shared<bmcweb::AsyncResp>(nullptr); |
| managedStore::ManagedObjectStoreContext context(resp); |
| redfish::storage_utils::getAllProperties( |
| connectionName, drivePath, |
| "xyz.openbmc_project.Inventory.Item.DriveErase", context, |
| [taskData](const boost::system::error_code& ec, |
| const std::vector<std::pair< |
| std::string, dbus::utility::DbusVariantType>>& props) { |
| if (ec) |
| { |
| taskData->messages.emplace_back(messages::internalError()); |
| taskData->state = "Exception"; |
| taskData->complete( |
| nlohmann::json(), |
| boost::beast::http::status::internal_server_error); |
| return; |
| } |
| |
| std::string errorName; |
| std::string errorDescription; |
| |
| const bool success = sdbusplus::unpackPropertiesNoThrow( |
| dbus_utils::UnpackErrorPrinter(), props, "ErrorName", errorName, |
| "ErrorDescription", errorDescription); |
| |
| if (!success) |
| { |
| taskData->messages.emplace_back(messages::internalError()); |
| taskData->state = "Exception"; |
| taskData->complete( |
| nlohmann::json(), |
| boost::beast::http::status::internal_server_error); |
| return; |
| } |
| |
| if (errorName.empty()) |
| { |
| // Erase Success |
| taskData->state = "Completed"; |
| taskData->percentComplete = 100; |
| taskData->messages.emplace_back(messages::success()); |
| taskData->complete(); |
| } |
| else |
| { |
| // Erase Failed |
| bmcweb::AsyncResp resp(nullptr); |
| storageAddDbusError(resp.res, "eraseTaskUpdate", "", errorName, |
| errorDescription); |
| for (auto& m : |
| resp.res.jsonValue["error"][messages::messageAnnotation]) |
| { |
| taskData->messages.emplace_back(m); |
| } |
| taskData->state = "Exception"; |
| taskData->complete(std::move(resp.res.jsonValue), |
| resp.res.result()); |
| } |
| }); |
| } |
| |
| inline bool eraseTaskHandler(sdbusplus::message_t& msg, |
| const std::shared_ptr<task::TaskData>& taskData, |
| const std::string& connectionName, |
| const std::string& drivePath) |
| { |
| dbus::utility::DBusPropertiesMap props; |
| std::string iface; |
| msg.read(iface, props); |
| |
| if (iface != "xyz.openbmc_project.Inventory.Item.DriveErase") |
| { |
| BMCWEB_LOG_DEBUG << "eraseTaskHandler wrong interface"; |
| return !task::completed; |
| } |
| |
| std::optional<bool> inProgress; |
| std::optional<double> erasePercentage; |
| sdbusplus::unpackPropertiesNoThrow(dbus_utils::UnpackErrorPrinter(), props, |
| "EraseInProgress", inProgress, |
| "ErasePercentage", erasePercentage); |
| |
| if (erasePercentage) |
| { |
| BMCWEB_LOG_DEBUG << "eraseTaskHandler update erasePercentage " |
| << *erasePercentage; |
| taskData->percentComplete = static_cast<int>(*erasePercentage); |
| } |
| |
| if (inProgress) |
| { |
| BMCWEB_LOG_DEBUG << "eraseTaskHandler update iniProgress " |
| << *inProgress; |
| eraseTaskUpdate(*inProgress, taskData, connectionName, drivePath); |
| } |
| |
| // completion is handled asynchronously so always return !completed |
| return !task::completed; |
| } |
| |
| // Clang believes that req is unused even though its captured in lambda |
| inline void eraseDrive(const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& connectionName, |
| const std::string& drivePath, const EraseParams& params) |
| { |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [req, asyncResp, connectionName, drivePath |
| ](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 != nullptr) |
| { |
| storageAddDbusError(asyncResp->res, "Drive Erase", "", sd_err->name, |
| sd_err->message); |
| return; |
| } |
| |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "Erase dbus error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // success, create the async task |
| BMCWEB_LOG_DEBUG << "erase started"; |
| std::shared_ptr<task::TaskData> task = task::TaskData::createTask( |
| [connectionName, |
| drivePath](const boost::system::error_code& err, |
| sdbusplus::message_t& taskMsg, |
| const std::shared_ptr<task::TaskData>& taskData) { |
| if (err) |
| { |
| // Internal error in property signal callback? |
| BMCWEB_LOG_ERROR << drivePath << ": Error in task"; |
| taskData->messages.emplace_back(messages::internalError()); |
| taskData->state = "Cancelled"; |
| return task::completed; |
| } |
| |
| return eraseTaskHandler(taskMsg, taskData, connectionName, |
| drivePath); |
| }, |
| "type='signal',interface='org.freedesktop.DBus.Properties'," |
| "member='PropertiesChanged',arg0='xyz.openbmc_project.Inventory.Item.DriveErase'," |
| "path='" + |
| drivePath + "'"); |
| |
| task->startTimer(std::chrono::minutes(180)); |
| task->populateResp(asyncResp->res); |
| task->payload.emplace(req); |
| |
| // Erase may have completed prior to Task watching for signals, so poll |
| // once. |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getProperty<bool>( |
| connectionName, drivePath, |
| "xyz.openbmc_project.Inventory.Item.DriveErase", "EraseInProgress", context, |
| [task, connectionName, |
| drivePath](const boost::system::error_code& ec2, bool inProgress) { |
| if (ec2) |
| { |
| BMCWEB_LOG_DEBUG << "erase poll error: " << ec2; |
| return; |
| } |
| |
| eraseTaskUpdate(inProgress, task, connectionName, drivePath); |
| }); |
| }, |
| connectionName, drivePath, |
| "xyz.openbmc_project.Inventory.Item.DriveErase", "Erase", |
| params.actionName()); |
| } |
| |
| inline void |
| matchAndEraseDrive(const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::vector<std::string>& drivePaths, |
| const std::string& driveName, const EraseParams& params) |
| { |
| // Match the driveName |
| int found = 0; |
| std::string drivePath; |
| for (const std::string& d : drivePaths) |
| { |
| sdbusplus::message::object_path path(d); |
| std::string leaf = path.filename(); |
| if (leaf == driveName) |
| { |
| found++; |
| drivePath = d; |
| } |
| } |
| |
| if (found > 1) |
| { |
| // Sanity check |
| BMCWEB_LOG_DEBUG << "Multiple drives match name " << driveName; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| if (found == 0) |
| { |
| messages::resourceNotFound(asyncResp->res, "#Drive.v1_7_0.Drive", |
| driveName); |
| return; |
| } |
| |
| // Find the connection |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.DriveErase"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getDbusObject( |
| drivePath, interfaces, requestContext, |
| [req, asyncResp, params, |
| drivePath](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetObject& services) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| if (services.size() != 1) |
| { |
| BMCWEB_LOG_DEBUG << "multiple serviceInterfaces entries"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| auto connectionName = services.front().first; |
| |
| // Perform the erase |
| eraseDrive(req, asyncResp, connectionName, drivePath, params); |
| }); |
| } |
| |
| // Find Chassis with chassisId and the Drives associated to it. |
| inline 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 |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, |
| [asyncResp, chassisId, cb, requestContext]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Iterate over all retrieved ObjectPaths. |
| int found = 0; |
| std::string chassisPath; |
| 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; |
| } |
| found++; |
| chassisPath = path; |
| } |
| if (found > 1) |
| { |
| BMCWEB_LOG_ERROR << "Multiple chassis match"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| if (found == 0) |
| { |
| messages::resourceNotFound(asyncResp->res, |
| "#Chassis.v1_14_0.Chassis", chassisId); |
| return; |
| } |
| dbus_utils::getAssociationEndPoints(chassisPath + "/drive", |
| requestContext, cb); |
| }); |
| } |
| |
| 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); |
| }); |
| } |
| |
| inline void handleDriveSecureErase(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; |
| } |
| |
| auto p = EraseParams::parse(req, asyncResp); |
| if (!p) |
| { |
| return; |
| } |
| EraseParams params = *p; |
| |
| // Find paths of drives associated with the ChassisId |
| findChassisDrive(asyncResp, chassisId, |
| [req, asyncResp, chassisId, driveName, |
| params](const boost::system::error_code ec, |
| const std::vector<std::string>& drivePaths) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| matchAndEraseDrive(req, asyncResp, drivePaths, driveName, params); |
| }); |
| } |
| |
| /** |
| * 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))); |
| |
| BMCWEB_ROUTE( |
| app, "/redfish/v1/Chassis/<str>/Drives/<str>/Actions/Drive.SecureErase") |
| .privileges(redfish::privileges::postDrive) |
| .methods(boost::beast::http::verb::post)( |
| std::bind_front(handleDriveSecureErase, 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; |
| } |
| |
| managedStore::GetManagedObjectStore()->setProperty( |
| connectionNames[0].first, path, "xyz.openbmc_project.State.Drive", |
| "RequestedDriveTransition", action, |
| [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"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, |
| [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, |
| const 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"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, |
| [asyncResp, driveId, resetType, drivesMap, requestContext]( |
| 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; |
| } |
| |
| dbus_utils::getProperty<bool>( |
| driveConnections[0].first, drivePath, |
| "xyz.openbmc_project.Inventory.Item.Drive", "Resettable", |
| requestContext, |
| [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); |
| }); |
| }); |
| } |
| |
| inline void handlePostDriveResetAction( |
| App& 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); |
| }); |
| } |
| |
| /** |
| * 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)( |
| std::bind_front(handlePostDriveResetAction, std::ref(app))); |
| } |
| |
| 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"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTree( |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, |
| [asyncResp, chassisId, driveId, drivesMap, requestContext]( |
| 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; |
| } |
| |
| dbus_utils::getProperty<bool>( |
| driveConnections[0].first, drivePath, |
| "xyz.openbmc_project.Inventory.Item.Drive", "Resettable", |
| requestContext, |
| [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); |
| }); |
| }); |
| } |
| |
| inline void handleGetDriveResetActionInfo( |
| App& 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; |
| } |
| 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); |
| }); |
| } |
| |
| /** |
| * 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)( |
| std::bind_front(handleGetDriveResetActionInfo, std::ref(app))); |
| } |
| |
| 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 tryPopulateControllerNvme( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& path, const dbus::utility::MapperServiceMap& ifaces) |
| { |
| if (storage_utils::matchServiceName(ifaces, |
| "xyz.openbmc_project.NVMe.NVMeAdmin")) |
| { |
| auto& nvprop = asyncResp->res.jsonValue["NVMeControllerProperties"]; |
| // TODO(matt) fetch other properties, don't use hardcoded values |
| nvprop["ControllerType"] = "IO"; |
| nvprop["NVMeVersion"] = "1.4"; |
| } |
| |
| if (auto service = storage_utils::matchServiceName( |
| ifaces, "xyz.openbmc_project.Software.ExtendedVersion")) |
| { |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getProperty<std::string>( |
| *service, path, "xyz.openbmc_project.Software.ExtendedVersion", |
| "ExtendedVersion", context, |
| [asyncResp](const boost::system::error_code& ec, |
| const std::string& extVers) { |
| if (ec) |
| { |
| return; |
| } |
| |
| std::string v( |
| "xyz.openbmc_project.NVMe.ControllerFirmwareVersion:"); |
| if (extVers.starts_with(v)) |
| { |
| asyncResp->res.jsonValue["FirmwareVersion"] = |
| extVers.substr(v.size()); |
| } |
| }); |
| } |
| } |
| |
| inline void tryPopulateControllerSecurity( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const boost::urls::url& controllerUrl, |
| const dbus::utility::MapperServiceMap& ifaces) |
| { |
| if (!storage_utils::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 storageCtrlAttachedVolumes( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const sdbusplus::message::object_path& controllerPath, |
| std::function<void(const boost::system::error_code& ec, |
| const std::vector<std::string>& volPaths)>&& |
| cb) |
| { |
| // Get list of attached volumes |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Volume"}; |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getAssociatedSubTreePaths( |
| controllerPath / "attaching", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, |
| interfaces, context, |
| [cb](const boost::system::error_code& ec, |
| const std::vector<std::string>& volPaths) { cb(ec, volPaths); }); |
| } |
| |
| inline void populateStorageControllerAttached( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& path) |
| { |
| storageCtrlAttachedVolumes( |
| asyncResp, path, [asyncResp, systemName, |
| storageId](const boost::system::error_code& ec, |
| const std::vector<std::string>& attachedVolumeList) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "populating attached volumes failed"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| std::vector<boost::urls::url> redfishVolumes; |
| for (const std::string& volumePath : attachedVolumeList) |
| { |
| std::string id = |
| sdbusplus::message::object_path(volumePath).filename(); |
| if (id.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Failed to find filename in " << volumePath; |
| return; |
| } |
| boost::urls::url volumeUrl = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", systemName, "Storage", storageId, |
| "Volumes", id); |
| redfishVolumes.emplace_back(volumeUrl); |
| } |
| asyncResp->res.jsonValue["Links"]["AttachedVolumes"] = redfishVolumes; |
| }); |
| } |
| |
| inline void populateStorageController( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& controllerId, const std::string& path, |
| const dbus::utility::MapperServiceMap& ifaces) |
| { |
| if (ifaces.size() != 1) |
| { |
| BMCWEB_LOG_ERROR << "Connection size " << ifaces.size() |
| << ", greater than 1"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| const std::string& connectionName = ifaces.front().first; |
| const std::vector<std::string>& interfaces = ifaces.front().second; |
| |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#StorageController.v1_7_0.StorageController"; |
| auto url = crow::utility::urlFromPieces("redfish", "v1", "Systems", |
| systemName, "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"; |
| |
| storage_utils::tryGetLocation(asyncResp, connectionName, path, |
| storage_utils::controllerInterface, |
| interfaces); |
| populateStorageControllerAttached(asyncResp, systemName, storageId, path); |
| tryPopulateControllerNvme(asyncResp, path, ifaces); |
| tryPopulateMetricCollection(asyncResp, path, ifaces, url); |
| tryPopulateControllerSecurity(asyncResp, url, ifaces); |
| if (enableCustomSSD) |
| { |
| populateCustomSSDInfo(asyncResp, ifaces, path); |
| } |
| |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| dbus_utils::getProperty<bool>( |
| connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present", |
| context, |
| [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"; |
| } |
| }); |
| |
| managedStore::GetManagedObjectStore()->getAllProperties( |
| connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset", |
| context, |
| [asyncResp](const boost::system::error_code& ec, |
| const std::vector< |
| std::pair<std::string, dbus::utility::DbusVariantType>>& |
| propertiesList) { |
| getStorageControllerAsset(asyncResp, ec, propertiesList); |
| }); |
| } |
| |
| 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 |
| uint8_t* buffer = reinterpret_cast<uint8_t*>(dataString.data()); // NOLINT |
| std::vector<uint8_t> data(buffer, buffer + dataString.size()); // NOLINT |
| |
| auto service = storage_utils::matchServiceName( |
| ifaces, "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity"); |
| if (!service) |
| { |
| BMCWEB_LOG_DEBUG << "No servicename"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [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 != nullptr) |
| { |
| messages::generalError(asyncResp->res); |
| BMCWEB_LOG_DEBUG << "SecuritySend NVMe error"; |
| if (sd_err->message != nullptr) |
| { |
| 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 |
| asyncResp->res.result(boost::beast::http::status::no_content); |
| }, |
| *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 = storage_utils::matchServiceName( |
| ifaces, "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity"); |
| if (!service) |
| { |
| BMCWEB_LOG_DEBUG << "No servicename"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [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 != nullptr) |
| { |
| messages::generalError(asyncResp->res); |
| BMCWEB_LOG_DEBUG << "SecurityReceive NVMe error"; |
| if (sd_err->message != nullptr) |
| { |
| 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())); // NOLINT |
| }, |
| *service, path, |
| "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity", |
| "SecurityReceive", proto, protoSpecific, transferLength); |
| } |
| |
| 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." + |
| boost::algorithm::to_lower_copy(std::string(customSSD)); |
| |
| managedStore::GetManagedObjectStore()->setProperty( |
| "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, |
| const std::string& value) |
| { |
| std::string customSSDDbusInterface = |
| "com.google.gbmc.ssd." + |
| boost::algorithm::to_lower_copy(std::string(customSSD)); |
| |
| managedStore::GetManagedObjectStore()->setProperty( |
| "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 storagePatchCustomSSDOem( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId, const std::string& controllerId, |
| nlohmann::json& customSSDOem) |
| { |
| storage_utils::findStorageAndController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, storageId, controllerId, customSSDOem]( |
| const std::string& path, const dbus::utility::MapperServiceMap&) { |
| if (customSSDOem.contains("MorristownOtpWriteEnable")) |
| { |
| setCustomSSDOemGpio(asyncResp, path, "MorristownOtpWriteEnable", |
| customSSDOem["MorristownOtpWriteEnable"]); |
| } |
| 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"]); |
| } |
| }); |
| } |
| |
| // Performs storage attach and detach operations. |
| // Will be called pseudo-recursively (asio dbus callbacks) to perform |
| // the operations. |
| inline void storageApplyAttachDetach( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& connectionName, const std::string& controllerPath, |
| const std::shared_ptr<std::vector<std::string>>& attaches, |
| const std::shared_ptr<std::vector<std::string>>& detaches) |
| { |
| if (!detaches->empty()) |
| { |
| sdbusplus::message::object_path v = detaches->back(); |
| detaches->pop_back(); |
| BMCWEB_LOG_DEBUG << "detaching " << v.str << " from " << controllerPath |
| << "\n"; |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [asyncResp, connectionName, controllerPath, attaches, |
| detaches](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 != nullptr) |
| { |
| // TODO remove "" argument |
| storageAddDbusError(asyncResp->res, "detach volume NVMe", "", |
| sd_err->name, sd_err->message); |
| return; |
| } |
| |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "detach volume dbus error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // "recurse" |
| storageApplyAttachDetach(asyncResp, connectionName, controllerPath, |
| attaches, detaches); |
| }, |
| connectionName, controllerPath, |
| "xyz.openbmc_project.Inventory.Item.StorageController", |
| "DetachVolume", v); |
| return; |
| } |
| |
| if (!attaches->empty()) |
| { |
| sdbusplus::message::object_path v = attaches->back(); |
| attaches->pop_back(); |
| BMCWEB_LOG_DEBUG << "attaching " << v.str << " to " << controllerPath |
| << "\n"; |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [asyncResp, connectionName, controllerPath, attaches, |
| detaches](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 != nullptr) |
| { |
| // TODO remove "" argument |
| storageAddDbusError(asyncResp->res, "attach volume NVMe", "", |
| sd_err->name, sd_err->message); |
| return; |
| } |
| |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "attach volume dbus error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // "recurse" |
| storageApplyAttachDetach(asyncResp, connectionName, controllerPath, |
| attaches, detaches); |
| }, |
| connectionName, controllerPath, |
| "xyz.openbmc_project.Inventory.Item.StorageController", |
| "AttachVolume", v); |
| return; |
| } |
| |
| // both lists are complete, return success with the controller. |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.StorageController"}; |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| managedStore::GetManagedObjectStore()->getDbusObject( |
| controllerPath, interfaces, context, |
| [asyncResp, connectionName, |
| controllerPath](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetObject& interfaceDict) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "attach volume get controller dbus error " |
| << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| if (interfaceDict.size() != 1) |
| { |
| BMCWEB_LOG_DEBUG << "attachdetach extra services"; |
| for (const auto& x : interfaceDict) |
| { |
| BMCWEB_LOG_DEBUG << "if " << x.first; |
| } |
| messages::internalError(asyncResp->res); |
| } |
| |
| auto c = sdbusplus::message::object_path(controllerPath); |
| auto storagePath = c.parent_path().parent_path(); |
| std::string storageId = storagePath.filename(); |
| std::string controllerId = c.filename(); |
| storage_utils::getSystemPathFromStorage( |
| asyncResp, storagePath, |
| [asyncResp, storageId, controllerId, controllerPath, |
| interfaceDict](std::optional<std::string_view> systemPath) { |
| std::string systemName = |
| (systemPath) ? std::filesystem::path(*systemPath).filename() |
| : "system"; |
| populateStorageController(asyncResp, systemName, storageId, |
| controllerId, controllerPath, |
| interfaceDict); |
| }); |
| }); |
| } |
| |
| inline void storagePatchAttachedVolumes( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& controllerId, std::vector<std::string>& updateVolumeURIs) |
| { |
| // vector of {parsed storageId, URI} |
| std::vector<std::pair<std::string, std::string>> updateVolIDs; |
| for (auto& u : updateVolumeURIs) |
| { |
| auto parsedUrl = boost::urls::parse_relative_ref(u); |
| if (!parsedUrl) |
| { |
| BMCWEB_LOG_DEBUG << "bad attached volume URI " << u; |
| messages::invalidURI(asyncResp->res, u); |
| return; |
| } |
| std::string urlStorageId; |
| std::string volumeId; |
| if (!crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", |
| "Systems", systemName.c_str(), |
| "Storage", std::ref(urlStorageId), |
| "Volumes", std::ref(volumeId))) |
| { |
| BMCWEB_LOG_DEBUG << "bad attached volume URI " << u; |
| messages::invalidURI(asyncResp->res, u); |
| return; |
| } |
| |
| if (urlStorageId != storageId) |
| { |
| BMCWEB_LOG_DEBUG << "bad attached volume URI " << u; |
| messages::invalidURI(asyncResp->res, u); |
| return; |
| } |
| |
| updateVolIDs.emplace_back(volumeId, u); |
| } |
| |
| storage_utils::findStorageAndController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, |
| updateVolIDs](const std::string& controllerPath, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| const auto& connectionName = ifaces.front().first; |
| |
| // Create dbus paths to update. Elements are {dbus_path, URI} |
| std::vector<std::pair<std::string, std::string>> updateVolumes; |
| auto storagePath = sdbusplus::message::object_path(controllerPath) |
| .parent_path() |
| .parent_path(); |
| updateVolumes.reserve(updateVolIDs.size()); |
| for (const auto& [u, uri] : updateVolIDs) |
| { |
| updateVolumes.emplace_back((storagePath / "volumes" / u).str, uri); |
| } |
| std::sort(updateVolumes.begin(), updateVolumes.end()); |
| |
| // Get list of available volumes |
| storageVolumes( |
| asyncResp, storagePath, |
| [asyncResp, updateVolumes, connectionName, |
| controllerPath](const boost::system::error_code& ec, |
| const std::vector<std::string>& volPaths) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG |
| << "patch attached volumes list volumes failed"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| for (const auto& a : volPaths) |
| { |
| BMCWEB_LOG_DEBUG << "vol is " << a; |
| } |
| |
| std::vector<std::string> updatePaths; |
| // Early check for bad volume paths |
| for (const auto& [u, uri] : updateVolumes) |
| { |
| if (std::find(volPaths.begin(), volPaths.end(), u) == |
| volPaths.end()) |
| { |
| BMCWEB_LOG_DEBUG << "patch volume not found " << uri; |
| messages::invalidURI(asyncResp->res, uri); |
| return; |
| } |
| updatePaths.emplace_back(u); |
| } |
| |
| // Fetch currently attached volumes |
| storageCtrlAttachedVolumes( |
| asyncResp, controllerPath, |
| [asyncResp, updatePaths, connectionName, |
| controllerPath](const boost::system::error_code& ec2, |
| const std::vector<std::string>& ex) { |
| if (ec2) |
| { |
| BMCWEB_LOG_DEBUG |
| << "patch attached volumes list attached failed"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Find changes |
| auto attaches = std::make_shared<std::vector<std::string>>(); |
| auto detaches = std::make_shared<std::vector<std::string>>(); |
| std::vector<std::string> existing(ex); |
| std::sort(existing.begin(), existing.end()); |
| std::set_difference(updatePaths.begin(), updatePaths.end(), |
| existing.begin(), existing.end(), |
| std::back_inserter(*attaches)); |
| |
| std::set_difference(existing.begin(), existing.end(), |
| updatePaths.begin(), updatePaths.end(), |
| std::back_inserter(*detaches)); |
| |
| // Apply |
| storageApplyAttachDetach(asyncResp, connectionName, |
| controllerPath, attaches, detaches); |
| }); |
| }); |
| }); |
| } |
| |
| inline void |
| storagePatchController(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)) |
| { |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| if (!enableCustomSSD) |
| { |
| return; |
| } |
| |
| std::optional<nlohmann::json> customSSDOem; |
| std::optional<std::vector<std::string>> attachedVolumes; |
| if (!json_util::readJsonPatch( |
| req, asyncResp->res, "Links/AttachedVolumes", attachedVolumes, |
| "Links/Oem/Google/" + std::string(customSSD), customSSDOem)) |
| { |
| BMCWEB_LOG_DEBUG << "Bad controller patch input"; |
| return; |
| } |
| |
| if (customSSDOem && attachedVolumes) |
| { |
| BMCWEB_LOG_DEBUG << "Multiple values to controller patch"; |
| messages::generalError(asyncResp->res); |
| asyncResp->res.jsonValue["error"]["message"] = |
| "PATCH may only alter one resource type"; |
| return; |
| } |
| |
| if (!(customSSDOem || attachedVolumes)) |
| { |
| BMCWEB_LOG_DEBUG << "No values to controller patch"; |
| messages::noOperation(asyncResp->res); |
| return; |
| } |
| |
| if (customSSDOem) |
| { |
| storagePatchCustomSSDOem(asyncResp, storageId, controllerId, |
| *customSSDOem); |
| } |
| |
| if (attachedVolumes) |
| { |
| storagePatchAttachedVolumes(asyncResp, systemName, storageId, |
| controllerId, *attachedVolumes); |
| } |
| |
| // TODO: we should setCompleteRequestHandler to return the modified |
| // StorageController on completion, rather than handling in attachedVolumes. |
| } |
| |
| inline void handlePostStorageControllerSecuritySend( |
| 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)) |
| { |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| uint8_t proto = 0; |
| uint16_t protoSpecific = 0; |
| std::string dataBase64; |
| |
| if (!json_util::readJsonAction(req, asyncResp->res, "SecurityProtocol", |
| proto, "SecurityProtocolSpecific", |
| protoSpecific, "Data", dataBase64)) |
| { |
| BMCWEB_LOG_DEBUG << "Missing request json parameters"; |
| return; |
| } |
| |
| storage_utils::findStorageAndController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, proto, protoSpecific, |
| dataBase64](const sdbusplus::message::object_path& path, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| securitySendAction(asyncResp, path, ifaces, proto, protoSpecific, |
| dataBase64); |
| }); |
| } |
| |
| inline void handlePostStorageControllerSecurityReceive( |
| 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)) |
| { |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| uint8_t proto = 0; |
| uint16_t protoSpecific = 0; |
| uint32_t transferLength = 0; |
| |
| if (!json_util::readJsonAction(req, asyncResp->res, "SecurityProtocol", |
| proto, "SecurityProtocolSpecific", |
| protoSpecific, "AllocationLength", |
| transferLength)) |
| { |
| BMCWEB_LOG_DEBUG << "Missing request json parameters"; |
| return; |
| } |
| |
| storage_utils::findStorageAndController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, proto, protoSpecific, |
| transferLength](const sdbusplus::message::object_path& path, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| securityReceiveAction(asyncResp, path, ifaces, proto, protoSpecific, |
| transferLength); |
| }); |
| } |
| |
| 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)(std::bind_front( |
| handlePostStorageControllerSecuritySend, std::ref(app))); |
| |
| BMCWEB_ROUTE( |
| app, |
| "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/Actions/StorageController.SecurityReceive") |
| .privileges(redfish::privileges::postStorageController) |
| .methods(boost::beast::http::verb::post)(std::bind_front( |
| handlePostStorageControllerSecurityReceive, std::ref(app))); |
| |
| 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) { |
| storagePatchController(app, req, asyncResp, systemName, storageId, |
| controllerId); |
| }); |
| } |
| |
| inline void populateStorageControllerCollection( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const dbus::utility::MapperGetSubTreeResponse& controllerList) |
| { |
| if (!asyncResp->res.jsonValue["Members"].is_array()) |
| { |
| asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); |
| } |
| asyncResp->res.jsonValue["Members@odata.count"] = |
| asyncResp->res.jsonValue["Members"].size(); |
| |
| for (const auto& controller : controllerList) |
| { |
| const sdbusplus::message::object_path controllerPath = controller.first; |
| storage_utils::findStorageForController( |
| asyncResp, storageId, controllerPath, |
| [asyncResp, systemName, controllerPath]( |
| const std::optional<sdbusplus::message::object_path>& |
| canonStoragePath) { |
| if (!canonStoragePath) |
| { |
| BMCWEB_LOG_DEBUG << "No storage associated with controller " |
| << std::string(controllerPath); |
| return; |
| } |
| nlohmann::json::object_t member; |
| std::string canonStorageId = (*canonStoragePath).filename(); |
| std::string controllerId = controllerPath.filename(); |
| member["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", systemName, "Storage", |
| canonStorageId, "Controllers", controllerId); |
| asyncResp->res.jsonValue["Members"].emplace_back(member); |
| asyncResp->res.jsonValue["Members@odata.count"] = |
| asyncResp->res.jsonValue["Members"].size(); |
| }); |
| } |
| } |
| |
| 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; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| storage_utils::findStorage( |
| asyncResp, storageId, |
| [asyncResp, systemName, |
| 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", systemName, |
| "Storage", storageId, "Controllers"); |
| asyncResp->res.jsonValue["Name"] = "Storage Controller Collection"; |
| |
| auto& cap = asyncResp->res.jsonValue["@Redfish.CollectionCapabilities"]; |
| cap["@odata.type"] = |
| "#CollectionCapabilities.v1_3_0.CollectionCapabilities"; |
| auto& cs = cap["Capabilities"]; |
| if (!cs.is_array()) |
| { |
| cs = nlohmann::json::array_t(); |
| } |
| auto& c = cs.emplace_back(nlohmann::json::object_t()); |
| c["CapabilitiesObject"]["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", systemName, "Storage", storageId, |
| "Volumes", "Capabilities"); |
| c["Links"]["TargetCollection"]["@odata.id"] = |
| asyncResp->res.jsonValue["@odata.id"]; |
| |
| storage_utils::findControllersForStorage( |
| asyncResp, storagePath, |
| std::bind_front(populateStorageControllerCollection, asyncResp, |
| systemName, storageId)); |
| }); |
| } |
| |
| inline void |
| tryPopulateVolumeNvme(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& connectionName, |
| const std::string& path, |
| const dbus::utility::MapperServiceMap& ifaces, |
| const std::string& volumeId, size_t blockSize) |
| { |
| if (!storage_utils::matchServiceName(ifaces, |
| "xyz.openbmc_project.Nvme.Volume")) |
| { |
| return; |
| } |
| |
| asyncResp->res.jsonValue["Name"] = std::string("Namespace ") + volumeId; |
| |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getAllProperties( |
| connectionName, path, "xyz.openbmc_project.Nvme.Volume", context, |
| [asyncResp, blockSize]( |
| const boost::system::error_code& ec, |
| const std::vector<std::pair< |
| std::string, dbus::utility::DbusVariantType>>& propertiesList) { |
| if (ec) |
| { |
| std::cerr << "error fetching nvme volume " << ec << '\n'; |
| // this interface isn't necessary |
| return; |
| } |
| |
| const uint32_t* namespaceId = nullptr; |
| const size_t* lbaFormat = nullptr; |
| |
| const bool success = sdbusplus::unpackPropertiesNoThrow( |
| dbus_utils::UnpackErrorPrinter(), propertiesList, "NamespaceId", |
| namespaceId, "LBAFormat", lbaFormat); |
| |
| if (!success) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| auto& nvprop = asyncResp->res.jsonValue["NVMeNamespaceProperties"]; |
| if (namespaceId != nullptr) |
| { |
| nvprop["NamespaceId"] = |
| std::string("0x") + intToHexString(*namespaceId, 8); |
| } |
| if (lbaFormat != nullptr) |
| { |
| auto& lbafprop = nvprop["LBAFormat"]; |
| lbafprop["LBAFormatType"] = |
| std::string("LBAFormat") + std::to_string(*lbaFormat); |
| lbafprop["LBADataSizeBytes"] = blockSize; |
| // TODO: populate other lbaformat attributes, and metadata_at_end |
| } |
| }); |
| } |
| |
| inline void populateStorageVolume( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& volumeId, const std::string& connectionName, |
| const std::string& path, const dbus::utility::MapperServiceMap& ifaces) |
| { |
| asyncResp->res.jsonValue["@odata.type"] = "#Volume.v1_9_0.Volume"; |
| auto url = |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName, |
| "Storage", storageId, "Volumes", volumeId); |
| asyncResp->res.jsonValue["@odata.id"] = url; |
| // May be overridden by nvme |
| asyncResp->res.jsonValue["Name"] = std::string("Volume ") + volumeId; |
| asyncResp->res.jsonValue["Id"] = volumeId; |
| |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getAllProperties( |
| connectionName, path, "xyz.openbmc_project.Inventory.Item.Volume", |
| context, |
| [asyncResp, connectionName, path, ifaces, volumeId]( |
| const boost::system::error_code& ec, |
| const std::vector<std::pair< |
| std::string, dbus::utility::DbusVariantType>>& propertiesList) { |
| size_t volBlockSize = 0; |
| // this interface isn't necessary |
| if (!ec) |
| { |
| const uint64_t* size = nullptr; |
| const size_t* blockSize = nullptr; |
| |
| const bool success = sdbusplus::unpackPropertiesNoThrow( |
| dbus_utils::UnpackErrorPrinter(), propertiesList, "Size", size, |
| "BlockSize", blockSize); |
| |
| if (!success) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| auto& cap = asyncResp->res.jsonValue["Capacity"]; |
| auto& capdata = cap["Data"]; |
| if (size != nullptr) |
| { |
| capdata["ProvisionedBytes"] = *size; |
| } |
| // Capacity.Metadata or provisioned/allocated is not currently |
| // handled by OpenBMC |
| if (blockSize != nullptr) |
| { |
| asyncResp->res.jsonValue["BlockSizeBytes"] = *blockSize; |
| volBlockSize = *blockSize; |
| } |
| tryPopulateVolumeNvme(asyncResp, connectionName, path, ifaces, |
| volumeId, volBlockSize); |
| } |
| }); |
| |
| tryPopulateMetricCollection(asyncResp, path, ifaces, url); |
| |
| auto findIfaces = std::find_if( |
| ifaces.begin(), ifaces.end(), |
| [connectionName]( |
| const std::pair<std::string, std::vector<std::string>>& pair) { |
| return pair.first == connectionName; |
| }); |
| if (findIfaces != ifaces.end()) |
| { |
| storage_utils::tryGetLocation(asyncResp, connectionName, path, |
| storage_utils::volumeInterface, |
| findIfaces->second); |
| } |
| } |
| |
| inline void |
| deleteStorageVolume(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId, |
| const std::string& connectionName, |
| const std::string& path) |
| { |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [asyncResp, storageId](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 != nullptr) |
| { |
| storageAddDbusError(asyncResp->res, "delete Volume NVMe", storageId, |
| sd_err->name, sd_err->message); |
| return; |
| } |
| |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "delete Volume dbus error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // success |
| asyncResp->res.result(boost::beast::http::status::no_content); |
| }, |
| connectionName, path, "xyz.openbmc_project.Object.Delete", "Delete"); |
| } |
| |
| inline void findStorageVolume( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId, const std::string& volumeId, |
| const std::function< |
| void(const std::string& storagePath, const std::string& volumePath, |
| const std::string& connectionName, |
| const dbus::utility::MapperServiceMap& ifaces)>& cb) |
| { |
| storage_utils::findStorage( |
| asyncResp, storageId, |
| [asyncResp, storageId, volumeId, |
| cb](const sdbusplus::message::object_path& storagePath) { |
| constexpr std::array<std::string_view, 1> interfaces = { |
| "xyz.openbmc_project.Inventory.Item.Volume"}; |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getAssociatedSubTree( |
| storagePath / "containing", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), |
| 0, interfaces, context, |
| [asyncResp, storageId, volumeId, storagePath, |
| cb](const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (ec || subtree.empty()) |
| { |
| BMCWEB_LOG_DEBUG << "findStorageVolume error" << ec; |
| messages::resourceNotFound(asyncResp->res, |
| "#Volume.v1_9_0.Volume", volumeId); |
| 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; |
| messages::resourceNotFound( |
| asyncResp->res, "#Volume.v1_9_0.Volume", volumeId); |
| return; |
| } |
| if (id != volumeId) |
| { |
| 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; |
| cb(storagePath, path, connectionName, interfaceDict); |
| return; |
| } |
| BMCWEB_LOG_DEBUG << "findStorageVolume not found"; |
| messages::resourceNotFound(asyncResp->res, "#Volume.v1_9_0.Volume", |
| volumeId); |
| }); |
| }); |
| } |
| |
| inline void createVolumeSuccess(const std::shared_ptr<task::TaskData>& taskData, |
| const std::string& service, |
| const std::string& storageId, |
| const std::string& progressPath) |
| { |
| taskData->stopMonitor(); |
| |
| // Have to create a new Resp here as we cannot use the original resp or it never goes out of scope |
| // This resp will not have a strand associated with it which is okay because this will never be |
| // multithreaded |
| auto resp = std::make_shared<bmcweb::AsyncResp>(nullptr); |
| managedStore::ManagedObjectStoreContext context(resp); |
| redfish::storage_utils::getProperty<sdbusplus::message::object_path>( |
| service, progressPath, |
| "xyz.openbmc_project.Nvme.CreateVolumeProgressSuccess", "VolumePath", context, |
| [taskData, storageId, |
| progressPath](const boost::system::error_code& ec, |
| const sdbusplus::message::object_path& volumePath) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "createVolumeSuccess volumepath error " << ec; |
| taskData->messages.emplace_back(messages::internalError()); |
| taskData->state = "Exception"; |
| taskData->complete( |
| nlohmann::json(), |
| boost::beast::http::status::internal_server_error); |
| return; |
| } |
| |
| auto resp = std::make_shared<bmcweb::AsyncResp>(nullptr); |
| resp->res.setCompleteRequestHandler([taskData](crow::Response& res) { |
| if (res.result() == boost::beast::http::status::ok) |
| { |
| taskData->messages.emplace_back(messages::created()); |
| taskData->state = "Completed"; |
| taskData->complete(std::move(res.jsonValue), |
| boost::beast::http::status::created); |
| } |
| else |
| { |
| BMCWEB_LOG_DEBUG << "createVolumeSuccess error populating: " |
| << res.result(); |
| BMCWEB_LOG_DEBUG << res.jsonValue; |
| taskData->messages.emplace_back(messages::internalError()); |
| taskData->state = "Exception"; |
| taskData->complete( |
| nlohmann::json(), |
| boost::beast::http::status::internal_server_error); |
| } |
| }); |
| |
| auto volumeId = volumePath.filename(); |
| |
| findStorageVolume( |
| resp, storageId, volumeId, |
| [resp, storageId, |
| volumeId](const std::string& sPath, const std::string& vPath, |
| const std::string& connectionName, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| BMCWEB_LOG_DEBUG << "createVolumeSuccess connectionName is " |
| << connectionName; |
| storage_utils::getSystemPathFromStorage( |
| resp, sPath, |
| [resp, storageId, volumeId, connectionName, vPath, |
| ifaces](std::optional<std::string_view> systemPath) { |
| std::string systemName = |
| systemPath ? std::filesystem::path(*systemPath).filename() |
| : "system"; |
| populateStorageVolume(resp, systemName, storageId, volumeId, |
| connectionName, vPath, ifaces); |
| }); |
| // on completion completeRequestHandler above will copy the response |
| // to taskData |
| }); |
| }); |
| } |
| |
| inline void createVolumeFailure(const std::shared_ptr<task::TaskData>& taskData, |
| const std::string& service, |
| const std::string& storageId, |
| const std::string& progressPath) |
| { |
| taskData->stopMonitor(); |
| |
| // Have to create a new Resp here as we cannot use the original resp or it never goes out of scope |
| // This resp will not have a strand associated with it which is okay because this will never be |
| // multithreaded |
| auto resp = std::make_shared<bmcweb::AsyncResp>(nullptr); |
| managedStore::ManagedObjectStoreContext context(resp); |
| redfish::storage_utils::getAllProperties( |
| service, progressPath, |
| "xyz.openbmc_project.Nvme.CreateVolumeProgressFailure", context, |
| [taskData, |
| storageId](const boost::system::error_code& ec, |
| const std::vector<std::pair< |
| std::string, dbus::utility::DbusVariantType>>& props) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "createVolumeSuccess volumepath error " << ec; |
| taskData->messages.emplace_back(messages::internalError()); |
| taskData->state = "Exception"; |
| taskData->complete( |
| nlohmann::json(), |
| boost::beast::http::status::internal_server_error); |
| return; |
| } |
| |
| std::string errorName; |
| std::string errorDesc; |
| sdbusplus::unpackPropertiesNoThrow(dbus_utils::UnpackErrorPrinter(), |
| props, "ErrorName", errorName, |
| "ErrorDescription", errorDesc); |
| bmcweb::AsyncResp resp(nullptr); |
| storageAddDbusError(resp.res, "createVolumeFailure", storageId, |
| errorName, errorDesc); |
| for (auto& m : resp.res.jsonValue["error"][messages::messageAnnotation]) |
| { |
| taskData->messages.emplace_back(m); |
| } |
| |
| taskData->state = "Exception"; |
| taskData->complete(std::move(resp.res.jsonValue), resp.res.result()); |
| }); |
| } |
| |
| // Handles the Status property of Common.Progress interface |
| inline void createVolumeTaskUpdate(const std::string& status, |
| const std::shared_ptr<task::TaskData>& taskData, |
| const std::string& service, |
| const std::string& storageId, |
| const std::string& progressPath) |
| { |
| if (status == |
| "xyz.openbmc_project.Common.Progress.OperationStatus.InProgress") |
| { |
| // nothing to do |
| } |
| else if (status == |
| "xyz.openbmc_project.Common.Progress.OperationStatus.Completed") |
| { |
| createVolumeSuccess(taskData, service, storageId, progressPath); |
| } |
| else if (status == |
| "xyz.openbmc_project.Common.Progress.OperationStatus.Failed" || |
| status == |
| "xyz.openbmc_project.Common.Progress.OperationStatus.Aborted") |
| { |
| createVolumeFailure(taskData, service, storageId, progressPath); |
| } |
| else |
| { |
| BMCWEB_LOG_DEBUG << "updateCreateVolumeTask unexpected state " |
| << status; |
| } |
| } |
| |
| // Handler called by TaskData on Commmon.Progress property change |
| inline bool createVolumeTaskHandler(sdbusplus::message_t& msg, |
| const std::shared_ptr<task::TaskData>& taskData, |
| const std::string& service, |
| const std::string& storageId, |
| const std::string& progressPath) |
| { |
| dbus::utility::DBusPropertiesMap props; |
| std::string iface; |
| msg.read(iface, props); |
| |
| if (iface != "xyz.openbmc_project.Common.Progress") |
| { |
| BMCWEB_LOG_DEBUG << "updateCreateVolumeTask wrong interface"; |
| return !task::completed; |
| } |
| |
| std::optional<std::string> status; |
| sdbusplus::unpackPropertiesNoThrow(dbus_utils::UnpackErrorPrinter(), props, |
| "Status", status); |
| if (!status) |
| { |
| BMCWEB_LOG_DEBUG << "updateCreateVolumeTask not status update"; |
| return !task::completed; |
| } |
| |
| createVolumeTaskUpdate(*status, taskData, service, storageId, progressPath); |
| // completion is handled asynchronously so always return !completed |
| return !task::completed; |
| } |
| |
| inline void |
| createStorageVolume(const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storagePath, |
| const std::string& storageService, uint64_t size, |
| size_t lbaIndex, bool metadataAtEnd) |
| { |
| auto storageId = sdbusplus::message::object_path(storagePath).filename(); |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [req, asyncResp, storageId, storageService]( |
| const boost::system::error_code ec, const sdbusplus::message_t& msg, |
| const sdbusplus::message::object_path& progressPath) { |
| const ::sd_bus_error* sd_err = msg.get_error(); |
| if (sd_err != nullptr) |
| { |
| storageAddDbusError(asyncResp->res, "create Volume NVMe", storageId, |
| sd_err->name, sd_err->message); |
| return; |
| } |
| |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "create Volume dbus error " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // success |
| BMCWEB_LOG_DEBUG << "create volume success, progress path " |
| << progressPath.str; |
| std::shared_ptr<task::TaskData> task = task::TaskData::createTask( |
| [storageService, storageId, |
| progressPath](const boost::system::error_code& err, |
| sdbusplus::message_t& taskMsg, |
| const std::shared_ptr<task::TaskData>& taskData) { |
| if (err) |
| { |
| // Internal error in property signal callback? |
| BMCWEB_LOG_ERROR << progressPath.str << ": Error in task"; |
| taskData->messages.emplace_back(messages::internalError()); |
| taskData->state = "Cancelled"; |
| return task::completed; |
| } |
| |
| return createVolumeTaskHandler(taskMsg, taskData, storageService, |
| storageId, progressPath); |
| }, |
| "type='signal',interface='org.freedesktop.DBus.Properties'," |
| "member='PropertiesChanged',arg0='xyz.openbmc_project.Common.Progress'," |
| "path='" + |
| progressPath.str + "'"); |
| |
| task->startTimer(std::chrono::minutes(60)); |
| task->populateResp(asyncResp->res); |
| task->payload.emplace(req); |
| |
| // Progress may have completed prior to Task watching for signals, so |
| // poll Status once. |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getProperty<std::string>( |
| storageService, progressPath, "xyz.openbmc_project.Common.Progress", |
| "Status", context, |
| [task, storageService, storageId, |
| progressPath](const boost::system::error_code& ec2, |
| const std::string& status) { |
| if (ec2) |
| { |
| BMCWEB_LOG_ERROR << "createVolume poll error: " |
| << ec2.message(); |
| return; |
| } |
| |
| createVolumeTaskUpdate(status, task, storageService, storageId, |
| progressPath); |
| }); |
| }, |
| storageService, storagePath, "xyz.openbmc_project.Nvme.Storage", |
| "CreateVolume", size, lbaIndex, metadataAtEnd); |
| } |
| |
| inline void populateStorageVolumeCollection( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const boost::system::error_code& ec, const std::string& systemName, |
| const std::string& storageId, |
| const dbus::utility::MapperGetSubTreePathsResponse& volumeList) |
| { |
| nlohmann::json::array_t members; |
| if (ec || volumeList.empty()) |
| { |
| asyncResp->res.jsonValue["Members"] = std::move(members); |
| asyncResp->res.jsonValue["Members@odata.count"] = 0; |
| BMCWEB_LOG_DEBUG << "Failed to find any storage Volumes"; |
| return; |
| } |
| |
| for (const std::string& path : volumeList) |
| { |
| 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", systemName, |
| "Storage", storageId, "Volumes", id); |
| members.emplace_back(member); |
| } |
| asyncResp->res.jsonValue["Members@odata.count"] = members.size(); |
| asyncResp->res.jsonValue["Members"] = std::move(members); |
| } |
| |
| inline void |
| storageVolumeHandler(App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, |
| const std::string& storageId, |
| const std::string& volumeId) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| BMCWEB_LOG_DEBUG << "Failed to setup Redfish Route for StorageVolume"; |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| findStorageVolume( |
| asyncResp, storageId, volumeId, |
| [asyncResp, systemName, storageId, |
| volumeId](const std::string& /* sPath */, const std::string& vPath, |
| const std::string& connectionName, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| populateStorageVolume(asyncResp, systemName, storageId, volumeId, |
| connectionName, vPath, ifaces); |
| }); |
| } |
| |
| inline std::optional<size_t> parseLbaFormatType(std::string_view ty) |
| { |
| // expects LBAFormat0, LBAFormat1 etc |
| if (!ty.starts_with("LBAFormat")) |
| { |
| BMCWEB_LOG_DEBUG << "wrong start"; |
| return std::nullopt; |
| } |
| |
| ty.remove_prefix(std::min(ty.size(), strlen("LBAFormat"))); |
| |
| size_t v = 0; |
| auto e = std::from_chars(ty.data(), ty.data() + ty.size(), v); |
| if (e.ptr != ty.data() + ty.size() || e.ec != std::errc()) |
| { |
| return std::nullopt; |
| } |
| return v; |
| } |
| |
| inline void storageVolumeCreateHandler( |
| 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 StorageVolume"; |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| uint64_t size = 0; |
| std::string lbaFormat; |
| // allow to default, non-metadata formats ignore the parameter |
| std::optional<bool> metadataAtEnd = false; |
| std::optional<std::string> name; |
| |
| if (!json_util::readJsonAction( |
| req, asyncResp->res, "Name", name, "Capacity/Data/ProvisionedBytes", |
| size, "NVMeNamespaceProperties/LBAFormat/LBAFormatType", lbaFormat, |
| "NVMeNamespaceProperties/LBAFormat/MetadataTransferredAtEndOfDataLBA", |
| metadataAtEnd)) |
| { |
| BMCWEB_LOG_DEBUG << "create volume json input failed"; |
| return; |
| } |
| |
| std::optional<size_t> lbaIndex = parseLbaFormatType(lbaFormat); |
| if (!lbaIndex) |
| { |
| BMCWEB_LOG_DEBUG << "Bad parsing lbaFormatType"; |
| messages::propertyValueNotInList( |
| asyncResp->res, lbaFormat, |
| "NVMeNamespaceProperties.LBAFormat.LBAFormatType"); |
| return; |
| } |
| |
| storage_utils::findStorageCallbackWithService( |
| asyncResp, storageId, |
| [req, asyncResp, size, lbaIndex, |
| metadataAtEnd](const sdbusplus::message::object_path& storagePath, |
| const std::string& storageService) { |
| createStorageVolume(req, asyncResp, storagePath, storageService, size, |
| *lbaIndex, *metadataAtEnd); |
| }); |
| } |
| |
| inline void storageVolumeDeleteHandler( |
| App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::string& storageId, |
| const std::string& volumeId) |
| { |
| BMCWEB_LOG_DEBUG << "delete handler vol " << volumeId; |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| BMCWEB_LOG_DEBUG << "Failed to setup Redfish Route for StorageVolume"; |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| findStorageVolume( |
| asyncResp, storageId, volumeId, |
| [asyncResp, storageId, |
| volumeId](const std::string& /* sPath */, const std::string& vPath, |
| const std::string& connectionName, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| (void)ifaces; |
| deleteStorageVolume(asyncResp, storageId, connectionName, vPath); |
| }); |
| } |
| |
| inline void storageVolumeCollectionHandler( |
| 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 StorageVolume Collection"; |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| storage_utils::findStorage( |
| asyncResp, storageId, |
| [asyncResp, systemName, |
| storageId](const sdbusplus::message::object_path& storagePath) { |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#VolumeCollection.VolumeCollection"; |
| asyncResp->res.jsonValue["@odata.id"] = |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName, |
| "Storage", storageId, "Volumes"); |
| asyncResp->res.jsonValue["Name"] = "Storage Volume Collection"; |
| |
| storageVolumes(asyncResp, storagePath, |
| [asyncResp, systemName, storageId]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& |
| volumeList) { |
| populateStorageVolumeCollection(asyncResp, ec, systemName, |
| storageId, volumeList); |
| }); |
| }); |
| } |
| |
| inline std::string lookupRelativePerformance(const std::string& rp) |
| { |
| if (rp == "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Best") |
| { |
| return "Best"; |
| } |
| if (rp == |
| "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Better") |
| { |
| return "Better"; |
| } |
| if (rp == "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Good") |
| { |
| return "Good"; |
| } |
| return "Degraded"; |
| } |
| |
| inline void storageVolumeCapabilitiesHandler( |
| 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 StorageVolume Capabilities"; |
| return; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| storage_utils::findStorageCallbackWithService( |
| asyncResp, storageId, |
| [asyncResp, systemName, |
| storageId](const sdbusplus::message::object_path& storagePath, |
| const std::string& service) { |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::storage_utils::getProperty< |
| std::vector<std::tuple<size_t, size_t, size_t, std::string>>>( |
| service, storagePath, "xyz.openbmc_project.Nvme.Storage", |
| "SupportedFormats", context, |
| [asyncResp, systemName, storageId]( |
| const boost::system::error_code& ec, |
| const std::vector< |
| std::tuple<size_t, size_t, size_t, std::string>>& formats) { |
| asyncResp->res.jsonValue["@odata.type"] = "#Volume.v1_9_0.Volume"; |
| auto url = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", systemName, "Storage", storageId, |
| "Volumes", "Capabilities"); |
| asyncResp->res.jsonValue["@odata.id"] = url; |
| asyncResp->res.jsonValue["Id"] = "Capabilities"; |
| asyncResp->res.jsonValue["Name"] = "Capabilities for Volumes"; |
| auto& nv = asyncResp->res.jsonValue["NVMeNamespaceProperties"]; |
| auto& allowable = |
| nv["LBAFormatsSupported@Redfish.AllowableValues"] = |
| nlohmann::json::array_t(); |
| auto& formatDesc = nv["LBAFormats"] = nlohmann::json::array_t(); |
| |
| if (ec) |
| { |
| BMCWEB_LOG_WARNING |
| << "Failed to get SupportedFormats property for " |
| << storageId << " : " << ec.message(); |
| return; |
| } |
| |
| for (const auto& [index, blockSize, metadataSize, relPerf] : formats) |
| { |
| auto name = std::string("LBAFormat") + std::to_string(index); |
| allowable.emplace_back(name); |
| auto& f = formatDesc.emplace_back(nlohmann::json::object_t()); |
| auto rp = lookupRelativePerformance(relPerf); |
| f["LBAFormatType"] = name; |
| f["RelativePerformance"] = rp; |
| f["LBADataSizeBytes"] = blockSize; |
| f["LBAMetadataSizeBytes"] = metadataSize; |
| } |
| }); |
| }); |
| } |
| 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; |
| } |
| |
| checkSystemAndStorage(asyncResp, systemName, storageId); |
| |
| storage_utils::findStorageAndController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, storageId, systemName, |
| controllerId](const sdbusplus::message::object_path& path, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| populateStorageController(asyncResp, systemName, storageId, |
| controllerId, path, ifaces); |
| }); |
| } |
| |
| 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))); |
| } |
| |
| inline void requestRoutesStorageVolumeCollection(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/") |
| .privileges(redfish::privileges::getVolumeCollection) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(storageVolumeCollectionHandler, std::ref(app))); |
| |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/") |
| .privileges(redfish::privileges::postVolumeCollection) |
| .methods(boost::beast::http::verb::post)( |
| std::bind_front(storageVolumeCreateHandler, std::ref(app))); |
| |
| BMCWEB_ROUTE(app, |
| "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/Capabilities") |
| .privileges(redfish::privileges::getVolumeCollection) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(storageVolumeCapabilitiesHandler, std::ref(app))); |
| } |
| |
| inline void requestRoutesStorageVolume(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/<str>") |
| .privileges(redfish::privileges::getVolume) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(storageVolumeHandler, std::ref(app))); |
| |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/<str>") |
| .privileges(redfish::privileges::deleteVolume) |
| .methods(boost::beast::http::verb::delete_)( |
| std::bind_front(storageVolumeDeleteHandler, std::ref(app))); |
| } |
| |
| inline void requestRoutesStorageControllerMetric(App& app) |
| { |
| BMCWEB_ROUTE( |
| app, |
| "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/Oem/Google/Metrics/<str>") |
| .privileges(redfish::privileges::privilegeSetLogin) |
| .methods(boost::beast::http::verb::get)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& system, const std::string& storage, |
| const std::string& cntrl, const std::string& metric) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", system, "Storage", storage, |
| "Controllers", cntrl, "Oem", "Google", "Metrics", metric); |
| |
| asyncResp->res.jsonValue["Name"] = metric; |
| |
| // TODO: verify system and storage relation |
| |
| nvmeControllerMetricFetcher(asyncResp, storage, cntrl, metric); |
| }); |
| } |
| |
| void handleStorageVolumeMetricGet( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& system, const std::string& storage, |
| const std::string& volume, const std::string& metric) |
| { |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Systems", system, "Storage", storage, |
| "Volumes", volume, "Oem", "Google", "Metrics", metric); |
| |
| asyncResp->res.jsonValue["Name"] = metric; |
| |
| checkSystemAndStorage(asyncResp, system, storage); |
| |
| findStorageVolume( |
| asyncResp, storage, volume, |
| [asyncResp, metric](const std::string& /* sPath */, |
| const std::string& vPath, |
| const std::string& /* connectionName */, |
| const dbus::utility::MapperServiceMap& ifaces) { |
| nvmeMetricFetcher(asyncResp, vPath, ifaces, metric, {}); |
| }); |
| } |
| |
| inline void requestRoutesStorageVolumeMetric(App& app) |
| { |
| BMCWEB_ROUTE( |
| app, |
| "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/<str>/Oem/Google/Metrics/<str>") |
| .privileges(redfish::privileges::privilegeSetLogin) |
| .methods(boost::beast::http::verb::get)( |
| [&app](const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& system, const std::string& storage, |
| const std::string& volume, const std::string& metric) { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| handleStorageVolumeMetricGet(asyncResp, system, storage, volume, |
| metric); |
| }); |
| } |
| |
| } // namespace redfish |