| #ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_STORAGE_H_ | |
| #define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_STORAGE_H_ | |
| /* | |
| // 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. | |
| */ | |
| #include <systemd/sd-bus.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <cerrno> | |
| #include <charconv> | |
| #include <chrono> // NOLINT | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <filesystem> // NOLINT | |
| #include <functional> | |
| #include <iostream> | |
| #include <iterator> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <system_error> // NOLINT | |
| #include <tuple> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <variant> | |
| #include <vector> | |
| #include "absl/functional/any_invocable.h" // IWYU pragma: keep | |
| #include "absl/strings/numbers.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/strip.h" | |
| #include "boost/system/error_code.hpp" // NOLINT | |
| #include "bmcweb_config.h" | |
| #include "app.hpp" | |
| #include "http_request.hpp" | |
| #include "http_response.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "human_sort.hpp" | |
| #include "error_messages.hpp" | |
| #include "generated/enums/drive.hpp" | |
| #include "query.hpp" | |
| #include "registries/privilege_registry.hpp" | |
| #include "collection.hpp" | |
| #include "dbus_utils.hpp" | |
| #include "hex_utils.hpp" | |
| #include "json_utils.hpp" | |
| #include "nvme_metric_utils.hpp" | |
| #include "storage_utils.hpp" | |
| #include "system_utils.hpp" // NOLINT | |
| #include "system_utils.hpp" | |
| #include "health.hpp" | |
| #include "task.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "sdbusplus/message.hpp" | |
| #include "sdbusplus/message/native_types.hpp" | |
| #include "sdbusplus/unpack_properties.hpp" | |
| 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 if (errorName == "xyz.openbmc_project.Common.Error.NotAllowed") { | |
| messages::operationNotAllowed(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(err.result()); | |
| } | |
| /** | |
| * @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, | |
| std::function<void(const sdbusplus::message::object_path& storagePath, | |
| const std::string& service)>&& cb) { | |
| storage_utils::findStorageCallbackWithService( | |
| asyncResp, storageId, | |
| [asyncResp, systemName, storageId, cb{std::move(cb)}]( | |
| const sdbusplus::message::object_path& storagePath, | |
| const std::string& service) { | |
| if (systemName.empty()) { | |
| cb(storagePath, service); | |
| return; | |
| } | |
| storage_utils::getSystemPathFromStorage( | |
| asyncResp, storagePath, | |
| [asyncResp, systemName, storageId, storagePath, service, | |
| cb](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(storagePath, service); | |
| }); | |
| }); | |
| } | |
| inline void checkSystemAndStorage( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName, const std::string& storageId) { | |
| checkSystemAndStorage(asyncResp, systemName, storageId, | |
| [](const auto&, const auto&) {}); | |
| } | |
| inline void handleGetSystemStorageCollection( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName) { | |
| 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)( | |
| [&app](const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| redfish::system_utils::getSystemInformation( | |
| asyncResp, systemName, | |
| [systemName]( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemPath) { | |
| handleGetSystemStorageCollection(asyncResp, systemName); | |
| (void)systemPath; | |
| }); | |
| }); | |
| 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()->getSubTree( | |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, | |
| [asyncResp, systemName, storageId]( | |
| const boost::system::error_code ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "requestRoutesStorage DBUS response error"; | |
| messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", | |
| storageId); | |
| return; | |
| } | |
| auto storage = std::find_if( | |
| subtree.begin(), subtree.end(), [&storageId](const auto& entry) { | |
| return sdbusplus::message::object_path(entry.first).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->first); | |
| 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"); | |
| if (storage_utils::matchServiceName( | |
| storage->second, "xyz.openbmc_project.Nvme.Storage")) { | |
| asyncResp->res | |
| .jsonValue["Actions"]["Oem"]["#GoogleStorage.Format"]["target"] = | |
| crow::utility::urlFromPieces( | |
| "redfish", "v1", "Systems", systemName, "Storage", storageId, | |
| "Actions", "Oem", "GoogleStorage.Format"); | |
| } | |
| }); | |
| } | |
| 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()->getSubTree( | |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, | |
| [asyncResp, storageId]( | |
| const boost::system::error_code ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "requestRoutesStorage DBUS response error"; | |
| messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", | |
| storageId); | |
| return; | |
| } | |
| auto storage = std::find_if( | |
| subtree.begin(), subtree.end(), [&storageId](const auto& entry) { | |
| return sdbusplus::message::object_path(entry.first).filename() == | |
| storageId; | |
| }); | |
| if (storage == subtree.end()) { | |
| messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", | |
| storageId); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Storage", storageId); | |
| asyncResp->res.jsonValue["Name"] = "Storage"; | |
| asyncResp->res.jsonValue["Id"] = storageId; | |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; | |
| // Storage subsystem to Stroage link. | |
| storage_utils::getSystemPathFromStorage( | |
| asyncResp, storage->first, | |
| [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.push_back(std::move(storageService)); | |
| asyncResp->res.jsonValue["Links"]["StorageServices"] = | |
| std::move(storageServices); | |
| asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = | |
| 1; | |
| }); | |
| if (storage_utils::matchServiceName( | |
| storage->second, "xyz.openbmc_project.Nvme.Storage")) { | |
| asyncResp->res | |
| .jsonValue["Actions"]["Oem"]["#GoogleStorage.Format"]["target"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Storage", | |
| storageId, "Actions", "Oem", | |
| "GoogleStorage.Format"); | |
| } | |
| }); | |
| } | |
| enum class SecureEraseSetting { | |
| kNone = 0, | |
| kCryptoErase, | |
| }; | |
| enum class DataProtectionType { | |
| kNone = 0, | |
| kType1, | |
| kType2, | |
| kType3, | |
| }; | |
| inline std::optional<SecureEraseSetting> parseSecureEraseSetting( | |
| std::string_view str) { | |
| if (str == "None") { | |
| return SecureEraseSetting::kNone; | |
| } | |
| if (str == "CryptoErase" || str == "CryptographicErase") { | |
| return SecureEraseSetting::kCryptoErase; | |
| } | |
| return std::nullopt; | |
| } | |
| // Note the capitalization: the Format PDI is defined in | |
| // phosphor-dbus-interfaces as yaml/xyz/openbmc_project/NVMe/Format, so its | |
| // generated enum strings use the all-caps "NVMe", while the interfaces hosting | |
| // the Format method are "xyz.openbmc_project.Nvme.{Storage,Controller}". This | |
| // asymmetry is intentional; sdbusplus rejects the wrong spelling with | |
| // InvalidEnumString. | |
| inline std::string_view toDbusString(SecureEraseSetting setting) { | |
| switch (setting) { | |
| case SecureEraseSetting::kNone: | |
| return "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.None"; | |
| case SecureEraseSetting::kCryptoErase: | |
| return "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.CryptoErase"; | |
| } | |
| return "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.None"; | |
| } | |
| inline std::optional<DataProtectionType> parseDataProtectionType( | |
| std::string_view str) { | |
| if (str == "None") { | |
| return DataProtectionType::kNone; | |
| } | |
| if (str == "Type1") { | |
| return DataProtectionType::kType1; | |
| } | |
| if (str == "Type2") { | |
| return DataProtectionType::kType2; | |
| } | |
| if (str == "Type3") { | |
| return DataProtectionType::kType3; | |
| } | |
| return std::nullopt; | |
| } | |
| inline std::string_view toDbusString(DataProtectionType type) { | |
| switch (type) { | |
| case DataProtectionType::kNone: | |
| return "xyz.openbmc_project.NVMe.Format.DataProtectionType.None"; | |
| case DataProtectionType::kType1: | |
| return "xyz.openbmc_project.NVMe.Format.DataProtectionType.Type1"; | |
| case DataProtectionType::kType2: | |
| return "xyz.openbmc_project.NVMe.Format.DataProtectionType.Type2"; | |
| case DataProtectionType::kType3: | |
| return "xyz.openbmc_project.NVMe.Format.DataProtectionType.Type3"; | |
| } | |
| return "xyz.openbmc_project.NVMe.Format.DataProtectionType.None"; | |
| } | |
| struct FormatParameters { | |
| uint8_t lbaFormat = 0; | |
| SecureEraseSetting secureEraseSetting = SecureEraseSetting::kNone; | |
| DataProtectionType dataProtectionType = DataProtectionType::kNone; | |
| }; | |
| inline std::optional<FormatParameters> parseFormatParameters( | |
| const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| std::string lbaFormatStr; | |
| std::string secureEraseSettingStr; | |
| std::string dataProtectionTypeStr; | |
| if (!json_util::readJsonAction(req, asyncResp->res, "LBAFormat", lbaFormatStr, | |
| "SecureEraseSetting", secureEraseSettingStr, | |
| "DataProtectionType", dataProtectionTypeStr)) { | |
| return std::nullopt; | |
| } | |
| std::string_view lbaFormatView(lbaFormatStr); | |
| if (!absl::ConsumePrefix(&lbaFormatView, "LBAFormat")) { | |
| messages::propertyValueFormatError(asyncResp->res, lbaFormatStr, | |
| "LBAFormat"); | |
| return std::nullopt; | |
| } | |
| constexpr int kMaxLbaFormatIndex = 15; | |
| int val = 0; | |
| if (!absl::SimpleAtoi(lbaFormatView, &val) || val < 0 || | |
| val > kMaxLbaFormatIndex) { | |
| messages::propertyValueFormatError(asyncResp->res, lbaFormatStr, | |
| "LBAFormat"); | |
| return std::nullopt; | |
| } | |
| uint8_t lbaFormat = static_cast<uint8_t>(val); | |
| std::optional<SecureEraseSetting> secureEraseSetting = | |
| parseSecureEraseSetting(secureEraseSettingStr); | |
| if (!secureEraseSetting) { | |
| messages::propertyValueNotInList(asyncResp->res, secureEraseSettingStr, | |
| "SecureEraseSetting"); | |
| return std::nullopt; | |
| } | |
| std::optional<DataProtectionType> dataProtectionType = | |
| parseDataProtectionType(dataProtectionTypeStr); | |
| if (!dataProtectionType) { | |
| messages::propertyValueNotInList(asyncResp->res, dataProtectionTypeStr, | |
| "DataProtectionType"); | |
| return std::nullopt; | |
| } | |
| FormatParameters params; | |
| params.lbaFormat = lbaFormat; | |
| params.secureEraseSetting = *secureEraseSetting; | |
| params.dataProtectionType = *dataProtectionType; | |
| return params; | |
| } | |
| inline void handleFormatAction( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& path, const std::string& interface, | |
| const std::string& service, const FormatParameters& params) { | |
| const std::string method = "Format"; | |
| const std::string secureEraseSetting(toDbusString(params.secureEraseSetting)); | |
| const std::string dataProtectionType(toDbusString(params.dataProtectionType)); | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const sdbusplus::message_t&)>( | |
| [asyncResp](const boost::system::error_code& ec, | |
| const sdbusplus::message_t& msg) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Format error: " << ec; | |
| const ::sd_bus_error* sd_err = msg.get_error(); | |
| if (sd_err != nullptr) { | |
| storageAddDbusError(asyncResp->res, "Format", "", sd_err->name, | |
| sd_err->message); | |
| } else { | |
| messages::internalError(asyncResp->res); | |
| } | |
| return; | |
| } | |
| messages::success(asyncResp->res); | |
| }), | |
| service, path, interface, method, params.lbaFormat, secureEraseSetting, | |
| dataProtectionType); | |
| } | |
| inline void setUpFormatAction( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName, const std::string& storageId, | |
| std::function<void(const sdbusplus::message::object_path& storagePath, | |
| const std::string& service, | |
| const FormatParameters& params)>&& cb) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| std::optional<FormatParameters> params = | |
| parseFormatParameters(req, asyncResp); | |
| if (!params) { | |
| // parseFormatParameters populates the appropriate error in asyncResp->res | |
| // before returning std::nullopt. | |
| return; | |
| } | |
| checkSystemAndStorage( | |
| asyncResp, systemName, storageId, | |
| [params = std::move(*params), cb{std::move(cb)}]( | |
| const sdbusplus::message::object_path& storagePath, | |
| const std::string& service) { cb(storagePath, service, params); }); | |
| } | |
| inline void handlePostStorageFormat( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName, const std::string& storageId) { | |
| setUpFormatAction( | |
| app, req, asyncResp, systemName, storageId, | |
| [asyncResp](const sdbusplus::message::object_path& storagePath, | |
| const std::string& service, const FormatParameters& params) { | |
| handleFormatAction(asyncResp, storagePath, | |
| "xyz.openbmc_project.Nvme.Storage", service, params); | |
| }); | |
| } | |
| inline void handlePostStorageControllerFormat( | |
| 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) { | |
| setUpFormatAction( | |
| app, req, asyncResp, systemName, storageId, | |
| [asyncResp, storageId, controllerId]( | |
| const sdbusplus::message::object_path& /*storagePath*/, | |
| const std::string& /*service*/, const FormatParameters& params) { | |
| storage_utils::findStorageAndController( | |
| asyncResp, storageId, controllerId, | |
| [asyncResp, params](const sdbusplus::message::object_path& path, | |
| const dbus::utility::MapperServiceMap& ifaces) { | |
| std::optional<std::string> service = | |
| storage_utils::matchServiceName( | |
| ifaces, "xyz.openbmc_project.Nvme.Controller"); | |
| if (!service) { | |
| BMCWEB_LOG_ERROR | |
| << "Failed to find Nvme.Controller interface for " | |
| << path.str; | |
| messages::actionNotSupported(asyncResp->res, | |
| "#GoogleStorageController.Format"); | |
| return; | |
| } | |
| handleFormatAction(asyncResp, path, | |
| "xyz.openbmc_project.Nvme.Controller", | |
| *service, params); | |
| }); | |
| }); | |
| } | |
| 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))); | |
| BMCWEB_ROUTE(app, | |
| "/redfish/v1/Systems/<str>/Storage/<str>/Actions/Oem/" | |
| "GoogleStorage.Format") | |
| .privileges(redfish::privileges::postStorage) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handlePostStorageFormat, std::ref(app))); | |
| BMCWEB_ROUTE(app, | |
| "/redfish/v1/Storage/<str>/Actions/Oem/" | |
| "GoogleStorage.Format") | |
| .privileges(redfish::privileges::postStorage) | |
| .methods(boost::beast::http::verb::post)( | |
| [&app](const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& storageId) { | |
| handlePostStorageFormat(app, req, asyncResp, "", storageId); | |
| }); | |
| } | |
| 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. | |
| bool found = false; | |
| 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; | |
| } | |
| found = true; | |
| 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); | |
| } | |
| if (!found) { | |
| messages::resourceNotFound(asyncResp->res, "#Drive.v1_7_0.Drive", | |
| driveName); | |
| } | |
| } | |
| 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_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const sdbusplus::message_t&)>( | |
| [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{std::move(cb)}](const boost::system::error_code& ec2, | |
| const dbus::utility::MapperEndPoints& resp) { | |
| if (ec2) { | |
| if (ec2.value() == EBADR || ec2.value() == ENOENT) { | |
| // No drive association endpoints found on this chassis. | |
| cb(boost::system::error_code{}, {}); | |
| return; | |
| } | |
| } | |
| cb(ec2, resp); | |
| }); | |
| }); | |
| } | |
| 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) { | |
| BMCWEB_LOG_ERROR << "DBUS response error " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| 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: " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| 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: " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| 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"; | |
| if (storage_utils::matchServiceName( | |
| ifaces, "xyz.openbmc_project.NVMe.FeatureStore")) { | |
| asyncResp->res.jsonValue["Oem"]["Google"]["Features"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName, | |
| "Storage", storageId, "Controllers", | |
| controllerId, "Oem", "Google", "Features"); | |
| } | |
| 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); | |
| } | |
| if (storage_utils::matchServiceName(ifaces, | |
| "xyz.openbmc_project.Nvme.Controller")) { | |
| asyncResp->res.jsonValue["Actions"]["Oem"] | |
| ["#GoogleStorageController.Format"]["target"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName, | |
| "Storage", storageId, "Controllers", | |
| controllerId, "Actions", "Oem", | |
| "GoogleStorageController.Format"); | |
| } | |
| 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_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const sdbusplus::message_t&)>( | |
| [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_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const sdbusplus::message_t&, | |
| const std::vector<uint8_t>&)>( | |
| [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_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const sdbusplus::message_t&)>( | |
| [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_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const sdbusplus::message_t&)>( | |
| [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 std::string_view lookupRelativePerformance(std::string_view 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 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 | |
| } | |
| }); | |
| sdbusplus::message::object_path volumeObjPath(path); | |
| std::string storagePath = volumeObjPath.parent_path().parent_path().str; | |
| managedStore::ManagedObjectStoreContext ctxFormat(asyncResp); | |
| dbus_utils::getProperty< | |
| std::vector<std::tuple<size_t, size_t, size_t, std::string>>>( | |
| connectionName, storagePath, "xyz.openbmc_project.Nvme.Storage", | |
| "SupportedFormats", ctxFormat, | |
| [asyncResp]( | |
| const boost::system::error_code& ec, | |
| const std::vector< | |
| std::tuple<size_t, size_t, size_t, std::string>>& formats) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "Failed to get SupportedFormats: " << ec; | |
| return; | |
| } | |
| nlohmann::json::array_t formatArray; | |
| formatArray.reserve(formats.size()); | |
| for (const auto& [index, blockSize, metadataSize, relPerf] : formats) { | |
| nlohmann::json::object_t formatObj; | |
| formatObj["Format"] = absl::StrCat("LBAFormat", index); | |
| formatObj["BlockSize"] = blockSize; | |
| formatObj["MetadataSize"] = metadataSize; | |
| formatObj["RelativePerformance"] = lookupRelativePerformance(relPerf); | |
| formatArray.push_back(std::move(formatObj)); | |
| } | |
| auto& nvprop = asyncResp->res.jsonValue["NVMeNamespaceProperties"]; | |
| nvprop["SupportedLBAFormats"] = std::move(formatArray); | |
| }); | |
| } | |
| 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; | |
| if (storage_utils::matchServiceName( | |
| ifaces, "xyz.openbmc_project.NVMe.FeatureStore")) { | |
| asyncResp->res.jsonValue["Oem"]["Google"]["Features"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName, | |
| "Storage", storageId, "Volumes", volumeId, | |
| "Oem", "Google", "Features"); | |
| } | |
| 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_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const sdbusplus::message_t&)>( | |
| [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_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const sdbusplus::message_t&, | |
| const sdbusplus::message::object_path&)>( | |
| [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 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); | |
| }); | |
| } | |
| // This will be a dummy handler so plugins can be appended to the route. | |
| inline void rootStorageControllerCollectionHandler( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| [[maybe_unused]] const std::string& storageId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| BMCWEB_LOG_DEBUG << "Failed to setup Redfish Route for StorageController"; | |
| return; | |
| } | |
| } | |
| 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))); | |
| BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/Controllers/") | |
| .privileges(redfish::privileges::getStorageControllerCollection) | |
| .methods(boost::beast::http::verb::get)(std::bind_front( | |
| rootStorageControllerCollectionHandler, std::ref(app))); | |
| } | |
| // This will be a dummy handler so plugins can be appended to the route. | |
| inline void rootStorageControllerHandler( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| [[maybe_unused]] const std::string& storageId, | |
| [[maybe_unused]] const std::string& controllerId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| BMCWEB_LOG_DEBUG << "Failed to setup Redfish Route for StorageController"; | |
| return; | |
| } | |
| } | |
| 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))); | |
| BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/Controllers/<str>/") | |
| .privileges(redfish::privileges::getStorageController) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(rootStorageControllerHandler, std::ref(app))); | |
| BMCWEB_ROUTE(app, | |
| "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/" | |
| "Actions/Oem/GoogleStorageController.Format") | |
| .privileges(redfish::privileges::postStorageController) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handlePostStorageControllerFormat, 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; | |
| asyncResp->res | |
| .jsonValue["Actions"]["#GoogleNvmeMetric.RefreshMetric"] | |
| ["target"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Systems", system, "Storage", storage, | |
| "Controllers", cntrl, "Oem", "Google", "Metrics", metric, | |
| "Actions", "GoogleNvmeMetric.RefreshMetric"); | |
| // TODO: verify system and storage relation | |
| nvmeControllerMetricFetcher(asyncResp, storage, cntrl, metric); | |
| }); | |
| BMCWEB_ROUTE(app, | |
| "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/Oem/" | |
| "Google/Metrics/<str>/Actions/GoogleNvmeMetric.RefreshMetric") | |
| .privileges(redfish::privileges::postStorageController) | |
| .methods(boost::beast::http::verb::post)( | |
| [&app](const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& 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; | |
| nvmeControllerMetricRefresher(asyncResp, storage, cntrl, metric); | |
| }); | |
| } | |
| inline 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 | |
| #endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_STORAGE_H_ |