blob: 1edad9c6634851ef0c09c318f5dc950c4e0e663c [file] [log] [blame]
/*
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
/**
* The following requirments on Dbus should be satisfied for storage stack
* working properly:
* * system name and the storage name are globally unique
* * On Single System system, the default system DBus object can be omitted.
* * On Multi System system, the system DBus object and the association
* (containing/contained_by) to Storage are manditory.
*/
#pragma once
#include "app.hpp"
#include "dbus_utility.hpp"
#include "generated/enums/drive.hpp"
#include "health.hpp"
#include "human_sort.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "openbmc_dbus_rest.hpp"
#include "query.hpp"
#include "redfish_util.hpp"
#include "registries/privilege_registry.hpp"
#include "task.hpp"
#include "utils/dbus_utils.hpp"
#include "utils/hex_utils.hpp"
#include "utils/nvme_metric_utils.hpp"
#include "utils/storage_utils.hpp"
#include <boost/system/error_code.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/unpack_properties.hpp>
#include <utils/location_utils.hpp>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <filesystem>
#include <string_view>
#include <unordered_set>
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish
{
/* Converts a NVMe dbus error to a redfish equivalent, adds to the response */
inline void storageAddDbusError(crow::Response& res, std::string_view func,
const std::string& storageId,
std::string_view errorName,
std::string_view errorDesc)
{
(void)storageId;
crow::Response err;
BMCWEB_LOG_DEBUG << func << " " << errorName << ", " << errorDesc;
if (errorName == "xyz.openbmc_project.Common.Error.TooManyResources")
{
messages::createLimitReachedForResource(err);
}
else if (errorName == "xyz.openbmc_project.Common.Error.InvalidArgument")
{
messages::propertyValueError(err, "");
}
else if (errorName ==
"xyz.openbmc_project.Common.Error.DeviceOperationFailed" ||
errorName == "xyz.openbmc_project.Common.Error.UnsupportedRequest")
{
messages::operationFailed(err);
}
else
{
messages::internalError(err);
}
// Some messages have "error" toplevel, others have "@Message.ExtendedInfo"
// (addMessageToErrorJson() versus addMessageToJson()). Choose which.
nlohmann::json extInfo;
if (err.jsonValue.contains("error"))
{
extInfo = err.jsonValue["error"][messages::messageAnnotation][0];
}
else
{
extInfo = err.jsonValue[messages::messageAnnotation][0];
}
// Keep the specific error message provided from the NVMe software.
extInfo["Message"] = errorDesc;
messages::moveErrorsToErrorJson(res.jsonValue, extInfo);
res.result(boost::beast::http::status::bad_request);
}
/**
* @brief Makes sure the target Storage can be accessed through given System
* @param asyncResp Pointer to object holding response data
* @param systemName ID (D-Bus filename) of the System
* @param storageId ID (D-Bus filename) of the Storage
// * @param cb Callback function to call if the access is valid
*
* Sets an error response if the Storage can't be accessed through this System
*/
inline void
checkSystemAndStorage(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName,
const std::string& storageId)
{
storage_utils::findStorage(
asyncResp, storageId,
[asyncResp, systemName,
storageId](const sdbusplus::message::object_path& storagePath) {
storage_utils::getSystemPathFromStorage(
asyncResp, storagePath,
[asyncResp, systemName,
storageId](std::optional<std::string_view> systemPath) {
if ((!systemPath &&
systemName != "system") // default single system system
||
(systemPath &&
std::filesystem::path(*systemPath).filename() != systemName))
{
BMCWEB_LOG_ERROR
<< "No association between System and Storage: "
<< systemName << ", " << storageId;
messages::internalError(asyncResp->res);
return;
}
// cb();
});
});
}
inline void handleGetSystemStorageCollection(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#StorageCollection.StorageCollection";
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName, "Storage");
asyncResp->res.jsonValue["Name"] = "Storage Collection";
storage_utils::getSystemPathFromName(
asyncResp, systemName,
[asyncResp, systemName](std::optional<std::string_view> systemPath) {
if (!systemPath) // fall back to single-system system
{
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
collection_util::getCollectionMembers(
asyncResp,
crow::utility::urlFromPieces("redfish", "v1", "Systems",
systemName, "Storage"),
std::array<std::string_view, 1>{
{"xyz.openbmc_project.Inventory.Item.Storage"}});
return;
}
collection_util::getAssociatedCollectionMembers(
asyncResp,
crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName,
"Storage"),
std::array<std::string_view, 1>{
{"xyz.openbmc_project.Inventory.Item.Storage"}},
(std::string(*systemPath) + "/containing").c_str());
});
}
inline void handleGetStorageCollection(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#StorageCollection.StorageCollection";
asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage";
asyncResp->res.jsonValue["Name"] = "Storage Collection";
constexpr std::array<std::string_view, 1> interface{
"xyz.openbmc_project.Inventory.Item.Storage"};
collection_util::getCollectionMembers(
asyncResp, crow::utility::urlFromPieces("redfish", "v1", "Storage"),
interface);
}
inline void requestRoutesStorageCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/")
.privileges(redfish::privileges::getStorageCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetSystemStorageCollection, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/Storage/")
.privileges(redfish::privileges::getStorageCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetStorageCollection, std::ref(app)));
}
inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::shared_ptr<HealthPopulate>& health,
const sdbusplus::message::object_path& storagePath,
const std::string& chassisId)
{
const std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Drive"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths(
storagePath / "drive",
sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
interfaces, requestContext,
[asyncResp, health, chassisId](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse& driveList) {
if (ec)
{
BMCWEB_LOG_ERROR << "Drive mapper call error";
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"];
driveArray = nlohmann::json::array();
auto& count = asyncResp->res.jsonValue["Drives@odata.count"];
count = 0;
#ifdef HEALTH_POPULATE
health->inventory.insert(health->inventory.end(), driveList.begin(),
driveList.end());
#endif
for (const std::string& drive : driveList)
{
sdbusplus::message::object_path object(drive);
if (object.filename().empty())
{
BMCWEB_LOG_ERROR << "Failed to find filename in " << drive;
return;
}
nlohmann::json::object_t driveJson;
driveJson["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassisId, "Drives",
object.filename());
driveArray.emplace_back(std::move(driveJson));
}
count = driveArray.size();
});
}
inline void
populateCustomSSDInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const dbus::utility::MapperServiceMap& ifaces,
const std::string& path)
{
std::string connection;
std::string customSSDDbusInterface =
"com.google.gbmc.ssd." +
boost::algorithm::to_lower_copy(std::string(customSSD));
for (const auto& x : ifaces)
{
for (const std::string& y : x.second)
{
if (y == customSSDDbusInterface)
{
connection = x.first;
break;
}
}
if (!connection.empty())
{
break;
}
}
if (connection.empty())
{
return;
}
managedStore::ManagedObjectStoreContext context(asyncResp);
asyncResp->res.jsonValue["Links"]["Oem"]["Google"][std::string(customSSD)] =
nlohmann::json::object_t();
dbus_utils::getProperty<bool>(
connection, path, customSSDDbusInterface, "PwrseqPgood", context,
[asyncResp, path](const boost::system::error_code& ec,
bool pwrseqPgood) {
// this interface isn't necessary, only check it if
// we get a good return
if (ec)
{
return;
}
asyncResp->res.jsonValue["Links"]["Oem"]["Google"]
[std::string(customSSD)]["PwrseqPgood"] =
pwrseqPgood;
});
managedStore::GetManagedObjectStore()->getAllProperties(
connection, path, customSSDDbusInterface, context,
[asyncResp, connection, path, context](
const boost::system::error_code ec2,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& propertiesList) {
if (ec2)
{
// this interface isn't necessary
return;
}
const bool* manufacturingMode = nullptr;
const bool* watchdogTriggered = nullptr;
const bool* fruEepromWriteProtect = nullptr;
const bool* controllerOtpWriteProtect = nullptr;
const bool* triggerPowerCycle = nullptr;
const bool* triggerReset = nullptr;
const bool* disableWatchdog = nullptr;
const bool* debugMode = nullptr;
const bool* controllerOtpWriteEnable = nullptr;
const uint64_t* spiImgSelect = nullptr;
const uint64_t* bootFailureCount = nullptr;
const std::string* pwrseqState = nullptr;
const uint64_t* uptimeInSeconds = nullptr;
const uint64_t* uptimeInMinutes = nullptr;
const bool* pgoodVdd12v0Ssd = nullptr;
const bool* pgoodVddPcMor = nullptr;
const bool* pgoodVdd3v3Pcie = nullptr;
const bool* pgoodVdd0v83Mor = nullptr;
const bool* pgoodVttVrefca = nullptr;
const bool* pgoodVddFlashVcc = nullptr;
const bool* pgood12vFlashVpp = nullptr;
const std::string* cpldVersion = nullptr;
std::string otpWriteProtectProperty =
customSSDController + std::string("OtpWriteProtect");
std::string otpWriteEnableProperty =
customSSDController + std::string("OtpWriteEnable");
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList,
"ManufacturingMode", manufacturingMode, "WatchdogTriggered",
watchdogTriggered, "FruEepromWriteProtect", fruEepromWriteProtect,
otpWriteProtectProperty, controllerOtpWriteProtect,
"TriggerPowerCycle", triggerPowerCycle, "TriggerReset",
triggerReset, "DisableWatchdog", disableWatchdog, "DebugMode",
debugMode, otpWriteEnableProperty, controllerOtpWriteEnable,
"SpiImgSelect", spiImgSelect, "BootFailureCount", bootFailureCount,
"PwrseqState", pwrseqState, "UptimeInSeconds", uptimeInSeconds,
"UptimeInMinutes", uptimeInMinutes, "PgoodVdd12v0Ssd",
pgoodVdd12v0Ssd, "PgoodVddPcMor", pgoodVddPcMor, "PgoodVdd3v3Pcie",
pgoodVdd3v3Pcie, "PgoodVdd0v83Mor", pgoodVdd0v83Mor,
"PgoodVttVrefca", pgoodVttVrefca, "PgoodVddFlashVcc",
pgoodVddFlashVcc, "Pgood12vFlashVpp", pgood12vFlashVpp,
"CpldVersion", cpldVersion);
if (!success)
{
BMCWEB_LOG_CRITICAL << "Failed to parse Arguments for "
<< std::string(customSSD);
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& customSSDJsonObj =
asyncResp->res
.jsonValue["Links"]["Oem"]["Google"][std::string(customSSD)];
customSSDJsonObj["@odata.type"] = customSSDOdataType;
// Write Only and will always read as false.
customSSDJsonObj["CpldReset"] = false;
if (manufacturingMode != nullptr)
{
customSSDJsonObj["ManufacturingMode"] = *manufacturingMode;
}
if (watchdogTriggered != nullptr)
{
customSSDJsonObj["WatchdogTriggered"] = *watchdogTriggered;
}
if (fruEepromWriteProtect != nullptr)
{
customSSDJsonObj["FruEepromWriteProtect"] = *fruEepromWriteProtect;
}
if (controllerOtpWriteProtect != nullptr)
{
customSSDJsonObj[otpWriteProtectProperty] =
*controllerOtpWriteProtect;
}
if (triggerPowerCycle != nullptr)
{
customSSDJsonObj["TriggerPowerCycle"] = *triggerPowerCycle;
}
if (triggerReset != nullptr)
{
customSSDJsonObj["TriggerReset"] = *triggerReset;
}
if (disableWatchdog != nullptr)
{
customSSDJsonObj["DisableWatchdog"] = *disableWatchdog;
}
if (debugMode != nullptr)
{
customSSDJsonObj["DebugMode"] = *debugMode;
}
if (controllerOtpWriteEnable != nullptr)
{
customSSDJsonObj[otpWriteEnableProperty] =
*controllerOtpWriteEnable;
}
if (spiImgSelect != nullptr)
{
customSSDJsonObj["SpiImgSelect"] = *spiImgSelect;
}
if (bootFailureCount != nullptr)
{
customSSDJsonObj["BootFailureCount"] = *bootFailureCount;
}
if (pwrseqState != nullptr)
{
customSSDJsonObj["PwrseqState"] = *pwrseqState;
}
if (uptimeInSeconds != nullptr)
{
customSSDJsonObj["UptimeInSeconds"] = *uptimeInSeconds;
}
if (uptimeInMinutes != nullptr)
{
customSSDJsonObj["UptimeInMinutes"] = *uptimeInMinutes;
}
if (pgoodVdd12v0Ssd != nullptr)
{
customSSDJsonObj["PgoodVdd12v0Ssd"] = *pgoodVdd12v0Ssd;
}
if (pgoodVddPcMor != nullptr)
{
customSSDJsonObj["PgoodVddPcMor"] = *pgoodVddPcMor;
}
if (pgoodVdd3v3Pcie != nullptr)
{
customSSDJsonObj["PgoodVdd3v3Pcie"] = *pgoodVdd3v3Pcie;
}
if (pgoodVdd0v83Mor != nullptr)
{
customSSDJsonObj["PgoodVdd0v83Mor"] = *pgoodVdd0v83Mor;
}
if (pgoodVttVrefca != nullptr)
{
customSSDJsonObj["PgoodVttVrefca"] = *pgoodVttVrefca;
}
if (pgoodVddFlashVcc != nullptr)
{
customSSDJsonObj["PgoodVddFlashVcc"] = *pgoodVddFlashVcc;
}
if (pgood12vFlashVpp != nullptr)
{
customSSDJsonObj["Pgood12vFlashVpp"] = *pgood12vFlashVpp;
}
if (cpldVersion != nullptr)
{
customSSDJsonObj["CpldVersion"] = *cpldVersion;
}
customSSDJsonObj["Name"] = std::string(customSSD) + " GPIO Action Info";
});
managedStore::GetManagedObjectStore()->getAllProperties(
connection, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
context,
[asyncResp](const boost::system::error_code ec3,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& asset) {
if (ec3)
{
// this interface isn't necessary
return;
}
nlohmann::json::object_t customSSDFruEeprom;
const std::string* partNumber = nullptr;
const std::string* serialNumber = nullptr;
const std::string* manufacturer = nullptr;
const std::string* model = nullptr;
const std::string* manufactureDate = nullptr;
const bool assetSuccess = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), asset, "PartNumber", partNumber,
"SerialNumber", serialNumber, "Manufacturer", manufacturer, "Model",
model, "ManufactureDate", manufactureDate);
if (!assetSuccess)
{
BMCWEB_LOG_CRITICAL << "Failed to parse Arguments for "
<< std::string(customSSD);
return;
}
customSSDFruEeprom["DeviceName"] = std::string(customSSD);
// If we get to this point, then it is enabled.
customSSDFruEeprom["Validity"] = "Enabled";
if (partNumber != nullptr)
{
customSSDFruEeprom["BrdPartNumber"] = *partNumber;
}
if (serialNumber != nullptr)
{
customSSDFruEeprom["BrdSerialNumber"] = *serialNumber;
}
if (manufacturer != nullptr)
{
customSSDFruEeprom["BrdMfgName"] = *manufacturer;
}
if (model != nullptr)
{
customSSDFruEeprom["BrdProductName"] = *model;
}
if (manufactureDate != nullptr)
{
customSSDFruEeprom["BrdMfgTime"] = *manufactureDate;
}
asyncResp->res.jsonValue["Links"]["Oem"]["Google"]
[std::string(customSSD)]["FruEeprom"] =
std::move(customSSDFruEeprom);
});
}
inline void
getDriveFromChassis(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::shared_ptr<HealthPopulate>& health,
const sdbusplus::message::object_path& storagePath)
{
const std::array<std::string_view, 2> interfaces = {
"xyz.openbmc_project.Inventory.Item.Board",
"xyz.openbmc_project.Inventory.Item.Chassis"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths(
storagePath / "chassis",
sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
interfaces, requestContext,
[asyncResp, health, storagePath](
const boost::system::error_code ec,
const dbus::utility::MapperGetSubTreePathsResponse& chassisList) {
if (ec)
{
BMCWEB_LOG_ERROR << "Chassis mapper call error";
messages::internalError(asyncResp->res);
return;
}
if (chassisList.empty())
{
BMCWEB_LOG_DEBUG << "Can not find the Chassis containing the drive";
return;
}
if (chassisList.size() != 1)
{
BMCWEB_LOG_ERROR
<< "Storage is not associated with only one chassis";
messages::internalError(asyncResp->res);
return;
}
const std::string& chassisPath = chassisList.front();
std::string chassisId =
sdbusplus::message::object_path(chassisPath).filename();
if (chassisId.empty())
{
BMCWEB_LOG_ERROR << "Failed to find filename in " << chassisPath;
return;
}
getDrives(asyncResp, health, storagePath, chassisId);
});
}
inline void
handleGetSystemStorage(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName,
const std::string& storageId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Storage"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTreePaths(
"/xyz/openbmc_project/inventory", 0, interfaces, requestContext,
[asyncResp, systemName, storageId](
const boost::system::error_code ec,
const dbus::utility::MapperGetSubTreePathsResponse& subtree) {
if (ec)
{
BMCWEB_LOG_DEBUG << "requestRoutesStorage DBUS response error";
messages::resourceNotFound(asyncResp->res,
"#Storage.v1_13_0.Storage", storageId);
return;
}
auto storage = std::find_if(subtree.begin(), subtree.end(),
[&storageId](const std::string& path) {
return sdbusplus::message::object_path(path).filename() ==
storageId;
});
if (storage == subtree.end())
{
messages::resourceNotFound(asyncResp->res,
"#Storage.v1_13_0.Storage", storageId);
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName.c_str(), "Storage",
storageId);
asyncResp->res.jsonValue["Name"] = "Storage";
asyncResp->res.jsonValue["Id"] = storageId;
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
auto health = std::make_shared<HealthPopulate>(asyncResp);
#ifdef HEALTH_POPULATE
health->populate();
#endif
getDriveFromChassis(asyncResp, health, *storage);
asyncResp->res.jsonValue["Controllers"]["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Systems",
systemName.c_str(), "Storage",
storageId, "Controllers");
asyncResp->res.jsonValue["Volumes"]["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Systems",
systemName.c_str(), "Storage",
storageId, "Volumes");
});
}
inline void
handleGetStorage(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
BMCWEB_LOG_DEBUG << "requestRoutesStorage setUpRedfishRoute failed";
return;
}
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Storage"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTreePaths(
"/xyz/openbmc_project/inventory", 0, interfaces, requestContext,
[asyncResp, storageId](
const boost::system::error_code ec,
const dbus::utility::MapperGetSubTreePathsResponse& subtree) {
if (ec)
{
BMCWEB_LOG_DEBUG << "requestRoutesStorage DBUS response error";
messages::resourceNotFound(asyncResp->res,
"#Storage.v1_13_0.Storage", storageId);
return;
}
auto storage = std::find_if(subtree.begin(), subtree.end(),
[&storageId](const std::string& storage) {
return sdbusplus::message::object_path(storage).filename() ==
storageId;
});
if (storage == subtree.end())
{
messages::resourceNotFound(asyncResp->res,
"#Storage.v1_13_0.Storage", storageId);
return;
}
asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage";
asyncResp->res.jsonValue["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Storage", storageId);
asyncResp->res.jsonValue["Name"] = "Storage";
asyncResp->res.jsonValue["Id"] = storageId;
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
// Storage subsystem to Stroage link.
storage_utils::getSystemPathFromStorage(
asyncResp, *storage,
[asyncResp, storageId](std::optional<std::string_view> systemPath) {
nlohmann::json::array_t storageServices;
nlohmann::json::object_t storageService;
std::string systemName =
systemPath ? std::filesystem::path(*systemPath).filename()
: "system";
storageService["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName, "Storage", storageId);
storageServices.emplace_back(storageService);
asyncResp->res.jsonValue["Links"]["StorageServices"] =
std::move(storageServices);
asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] =
1;
});
});
}
inline void requestRoutesStorage(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/")
.privileges(redfish::privileges::getStorage)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetSystemStorage, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/")
.privileges(redfish::privileges::getStorage)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetStorage, std::ref(app)));
}
inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& connectionName,
const std::string& path)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
context,
[asyncResp](const boost::system::error_code& ec,
const std::vector<
std::pair<std::string, dbus::utility::DbusVariantType>>&
propertiesList) {
if (ec)
{
// this interface isn't necessary
return;
}
const std::string* partNumber = nullptr;
const std::string* serialNumber = nullptr;
const std::string* manufacturer = nullptr;
const std::string* model = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
partNumber, "SerialNumber", serialNumber, "Manufacturer",
manufacturer, "Model", model);
if (!success)
{
messages::internalError(asyncResp->res);
return;
}
if (partNumber != nullptr)
{
asyncResp->res.jsonValue["PartNumber"] = *partNumber;
}
if (serialNumber != nullptr)
{
asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
}
if (manufacturer != nullptr)
{
asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
}
if (model != nullptr)
{
asyncResp->res.jsonValue["Model"] = *model;
}
});
}
inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& connectionName,
const std::string& path)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
dbus_utils::getProperty<bool>(
connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present",
requestContext,
[asyncResp, path](const boost::system::error_code& ec,
const bool isPresent) {
// this interface isn't necessary, only check it if
// we get a good return
if (ec)
{
return;
}
if (!isPresent)
{
asyncResp->res.jsonValue["Status"]["State"] = "Absent";
}
});
}
inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& connectionName,
const std::string& path)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
dbus_utils::getProperty<bool>(
connectionName, path, "xyz.openbmc_project.State.Drive", "Rebuilding",
requestContext,
[asyncResp](const boost::system::error_code& ec, const bool updating) {
// this interface isn't necessary, only check it
// if we get a good return
if (ec)
{
return;
}
// updating and disabled in the backend shouldn't be
// able to be set at the same time, so we don't need
// to check for the race condition of these two
// calls
if (updating)
{
asyncResp->res.jsonValue["Status"]["State"] = "Updating";
}
});
}
inline std::optional<std::string> convertDriveType(const std::string& type)
{
if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD")
{
return "HDD";
}
if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD")
{
return "SSD";
}
return std::nullopt;
}
inline void addResetLinks(nlohmann::json& driveReset,
const std::string& driveId,
const std::string& chassisId)
{
driveReset["target"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassisId, "Drives", driveId, "Actions",
"Drive.Reset");
driveReset["@Redfish.ActionInfo"] =
crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId,
"Drives", driveId, "ResetActionInfo");
}
inline std::optional<std::string> convertDriveProtocol(const std::string& proto)
{
if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS")
{
return "SAS";
}
if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA")
{
return "SATA";
}
if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe")
{
return "NVMe";
}
if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC")
{
return "FC";
}
if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.eMMC")
{
return "eMMC";
}
return std::nullopt;
}
inline void
getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& driveId,
const std::optional<std::string>& chassisId,
const std::string& connectionName,
const std::string& path, bool hasDriveState)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
connectionName, path, "xyz.openbmc_project.Inventory.Item.Drive",
context,
[asyncResp, driveId, chassisId, hasDriveState](
const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& propertiesList) {
if (ec)
{
// this interface isn't required
return;
}
const std::string* encryptionStatus = nullptr;
const bool* isLocked = nullptr;
for (const std::pair<std::string, dbus::utility::DbusVariantType>&
property : propertiesList)
{
const std::string& propertyName = property.first;
if (propertyName == "Type")
{
const std::string* value =
std::get_if<std::string>(&property.second);
if (value == nullptr)
{
// illegal property
BMCWEB_LOG_ERROR << "Illegal property: Type";
messages::internalError(asyncResp->res);
return;
}
std::optional<std::string> mediaType = convertDriveType(*value);
if (!mediaType)
{
BMCWEB_LOG_ERROR << "Unsupported DriveType Interface: "
<< *value;
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue["MediaType"] = *mediaType;
}
else if (propertyName == "Capacity")
{
const uint64_t* capacity =
std::get_if<uint64_t>(&property.second);
if (capacity == nullptr)
{
BMCWEB_LOG_ERROR << "Illegal property: Capacity";
messages::internalError(asyncResp->res);
return;
}
if (*capacity == 0)
{
// drive capacity not known
continue;
}
asyncResp->res.jsonValue["CapacityBytes"] = *capacity;
}
else if (propertyName == "Protocol")
{
const std::string* value =
std::get_if<std::string>(&property.second);
if (value == nullptr)
{
BMCWEB_LOG_ERROR << "Illegal property: Protocol";
messages::internalError(asyncResp->res);
return;
}
std::optional<std::string> proto = convertDriveProtocol(*value);
if (!proto)
{
BMCWEB_LOG_ERROR << "Unsupported DrivePrototype Interface: "
<< *value;
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue["Protocol"] = *proto;
}
else if (propertyName == "PredictedMediaLifeLeftPercent")
{
const uint8_t* lifeLeft =
std::get_if<uint8_t>(&property.second);
if (lifeLeft == nullptr)
{
BMCWEB_LOG_ERROR
<< "Illegal property: PredictedMediaLifeLeftPercent";
messages::internalError(asyncResp->res);
return;
}
// 255 means reading the value is not supported
if (*lifeLeft != 255)
{
asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] =
*lifeLeft;
}
}
else if (propertyName == "Resettable" && hasDriveState)
{
const bool* value = std::get_if<bool>(&property.second);
// If Resettable flag is not present, its not considered a
// failure.
if (value != nullptr && *value && chassisId.has_value())
{
addResetLinks(
asyncResp->res.jsonValue["Actions"]["#Drive.Reset"],
driveId, *chassisId);
}
}
else if (propertyName == "EncryptionStatus")
{
encryptionStatus = std::get_if<std::string>(&property.second);
if (encryptionStatus == nullptr)
{
BMCWEB_LOG_ERROR << "Illegal property: EncryptionStatus";
messages::internalError(asyncResp->res);
return;
}
}
else if (propertyName == "Locked")
{
isLocked = std::get_if<bool>(&property.second);
if (isLocked == nullptr)
{
BMCWEB_LOG_ERROR << "Illegal property: Locked";
messages::internalError(asyncResp->res);
return;
}
}
}
if (encryptionStatus == nullptr || isLocked == nullptr ||
*encryptionStatus ==
"xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown")
{
return;
}
if (*encryptionStatus !=
"xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted")
{
//"The drive is not currently encrypted."
asyncResp->res.jsonValue["EncryptionStatus"] =
drive::EncryptionStatus::Unencrypted;
return;
}
if (*isLocked)
{
//"The drive is currently encrypted and the data is not
// accessible to the user."
asyncResp->res.jsonValue["EncryptionStatus"] =
drive::EncryptionStatus::Locked;
return;
}
// if not locked
// "The drive is currently encrypted but the data is accessible
// to the user in unencrypted form."
asyncResp->res.jsonValue["EncryptionStatus"] =
drive::EncryptionStatus::Unlocked;
});
}
inline void getDriveErase(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId,
const std::string& driveName)
{
auto eraseUrl = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassisId, "Drives", driveName, "Actions",
"Drive.SecureErase");
asyncResp->res.jsonValue["Actions"]["#Drive.SecureErase"]["target"] =
eraseUrl;
}
static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& driveId,
const std::string& connectionName,
const std::string& path,
const std::vector<std::string>& interfaces,
const std::string& chassisId)
{
bool driveInterface = false;
bool driveStateInterface = false;
for (const std::string& interface : interfaces)
{
if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
{
getDriveAsset(asyncResp, connectionName, path);
}
else if (interface == "xyz.openbmc_project.Inventory.Item")
{
getDrivePresent(asyncResp, connectionName, path);
}
else if (interface == "xyz.openbmc_project.State.Drive")
{
driveStateInterface = true;
getDriveState(asyncResp, connectionName, path);
}
else if (interface == "xyz.openbmc_project.Inventory.Item.Drive")
{
driveInterface = true;
}
else if (interface == "xyz.openbmc_project.Inventory.Item.DriveErase")
{
getDriveErase(asyncResp, chassisId, driveId);
}
}
if (driveInterface)
{
getDriveItemProperties(asyncResp, driveId, chassisId, connectionName,
path, driveStateInterface);
storage_utils::tryGetLocation(asyncResp, connectionName, path,
storage_utils::driveInterface, interfaces,
"/PhysicalLocation"_json_pointer);
}
}
/**
* Chassis drives, this URL will show all the DriveCollection
* information
*/
inline void chassisDriveCollectionGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
// mapper call lambda
constexpr std::array<std::string_view, 2> interfaces = {
"xyz.openbmc_project.Inventory.Item.Board",
"xyz.openbmc_project.Inventory.Item.Chassis"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/inventory", 0, interfaces, requestContext,
[asyncResp, chassisId, requestContext](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
if (ec == boost::system::errc::host_unreachable)
{
messages::resourceNotFound(asyncResp->res, "Chassis",
chassisId);
return;
}
messages::internalError(asyncResp->res);
return;
}
// Iterate over all retrieved ObjectPaths.
for (const auto& [path, connectionNames] : subtree)
{
sdbusplus::message::object_path objPath(path);
if (objPath.filename() != chassisId)
{
continue;
}
if (connectionNames.empty())
{
BMCWEB_LOG_ERROR << "Got 0 Connection names";
continue;
}
asyncResp->res.jsonValue["@odata.type"] =
"#DriveCollection.DriveCollection";
asyncResp->res.jsonValue["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Chassis",
chassisId, "Drives");
asyncResp->res.jsonValue["Name"] = "Drive Collection";
// Association lambda
dbus_utils::getAssociationEndPoints(
path + "/drive", requestContext,
[asyncResp,
chassisId](const boost::system::error_code& ec3,
const dbus::utility::MapperEndPoints& resp) {
if (ec3)
{
BMCWEB_LOG_ERROR << "Error in chassis Drive association ";
}
nlohmann::json& members = asyncResp->res.jsonValue["Members"];
// important if array is empty
members = nlohmann::json::array();
std::vector<std::string> leafNames;
for (const auto& drive : resp)
{
sdbusplus::message::object_path drivePath(drive);
leafNames.emplace_back(drivePath.filename());
}
std::sort(leafNames.begin(), leafNames.end(),
AlphanumLess<std::string>());
for (const auto& leafName : leafNames)
{
nlohmann::json::object_t member;
member["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassisId, "Drives",
leafName);
members.emplace_back(std::move(member));
// navigation links will be registered in next patch set
}
asyncResp->res.jsonValue["Members@odata.count"] = resp.size();
}); // end association lambda
} // end Iterate over all retrieved ObjectPaths
});
}
inline void requestRoutesChassisDrive(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
.privileges(redfish::privileges::getDriveCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(chassisDriveCollectionGet, std::ref(app)));
}
inline void storageVolumes(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const sdbusplus::message::object_path& storagePath,
std::function<void(const boost::system::error_code& ec,
const std::vector<std::string>& volPaths)>&& cb)
{
// Get list of attached volumes
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Volume"};
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getAssociatedSubTreePaths(
storagePath / "containing",
sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
interfaces, context,
[cb](const boost::system::error_code& ec,
const std::vector<std::string>& volPaths) { cb(ec, volPaths); });
}
inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId,
const std::string& driveName,
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree)
{
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
// Iterate over all retrieved ObjectPaths.
for (const auto& [path, connectionNames] : subtree)
{
sdbusplus::message::object_path objPath(path);
if (objPath.filename() != driveName)
{
continue;
}
if (connectionNames.empty())
{
BMCWEB_LOG_ERROR << "Got 0 Connection names";
continue;
}
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassisId, "Drives", driveName);
asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
asyncResp->res.jsonValue["Name"] = driveName;
asyncResp->res.jsonValue["Id"] = driveName;
// default it to Enabled
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
nlohmann::json::object_t linkChassisNav;
linkChassisNav["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId);
asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav;
// mapper call chassis
storage_utils::findStorage(
asyncResp, driveName,
[asyncResp,
driveName](const sdbusplus::message::object_path& storagePath) {
storage_utils::getSystemPathFromStorage(
asyncResp, storagePath,
[asyncResp, storagePath,
driveName](std::optional<std::string_view> systemPath) {
std::string systemName =
systemPath ? std::filesystem::path(*systemPath).filename()
: "system";
storageVolumes(
asyncResp, storagePath,
[asyncResp, systemName, driveName](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse&
volumeList) {
nlohmann::json::array_t volumes;
if (ec || volumeList.empty())
{
// No association found, just return.
return;
}
for (const std::string& path : volumeList)
{
std::string id =
sdbusplus::message::object_path(path).filename();
if (id.empty())
{
// Empty list, just return.
return;
}
nlohmann::json::object_t volume;
volume["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName, "Storage",
driveName, "Volumes", id);
volumes.emplace_back(volume);
}
asyncResp->res.jsonValue["Links"]["Volumes@odata.count"] =
volumes.size();
asyncResp->res.jsonValue["Links"]["Volumes"] =
std::move(volumes);
});
});
});
addAllDriveInfo(asyncResp, driveName, connectionNames[0].first, path,
connectionNames[0].second, chassisId);
}
}
inline void
matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId,
const std::string& driveName,
const std::vector<std::string>& resp)
{
for (const std::string& drivePath : resp)
{
sdbusplus::message::object_path path(drivePath);
std::string leaf = path.filename();
if (leaf != driveName)
{
continue;
}
// mapper call drive
constexpr std::array<std::string_view, 1> driveInterface = {
"xyz.openbmc_project.Inventory.Item.Drive"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/inventory", 0, driveInterface, requestContext,
[asyncResp, chassisId, driveName](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
buildDrive(asyncResp, chassisId, driveName, ec, subtree);
});
return;
}
messages::resourceNotFound(asyncResp->res, "#Drive.v1_7_0.Drive",
driveName);
}
struct EraseParams
{
enum Action : std::uint8_t
{
CryptoErase,
BlockErase,
Overwrite,
} action;
static std::optional<EraseParams>
parse(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
// Redfish allows sanitizationType to be defaulted, though we don't
// know a good default at present, leave it mandatory.
std::string sanitizationType;
if (!json_util::readJsonAction(req, asyncResp->res, "SanitizationType",
sanitizationType))
{
BMCWEB_LOG_DEBUG << "Missing request json parameters";
return std::nullopt;
}
Action action = Action::CryptoErase;
if (sanitizationType == "BlockErase")
{
action = Action::BlockErase;
}
else if (sanitizationType == "CryptographicErase")
{
action = Action::CryptoErase;
}
else if (sanitizationType == "Overwrite")
{
// Redfish defines an optional "OverwritePasses" parameter, we
// don't handle that at the moment. If the client passes it, the
// readJsonAction will fail it.
action = Action::Overwrite;
}
else
{
messages::actionParameterValueNotInList(
asyncResp->res, sanitizationType, "SanitizationType",
"Drive.SecureErase");
return std::nullopt;
}
return EraseParams{.action = action};
}
std::string actionName() const
{
switch (action)
{
case Action::CryptoErase:
return "xyz.openbmc_project.Inventory.Item.DriveErase.EraseAction.CryptoErase";
case Action::BlockErase:
return "xyz.openbmc_project.Inventory.Item.DriveErase.EraseAction.BlockErase";
case Action::Overwrite:
return "xyz.openbmc_project.Inventory.Item.DriveErase.EraseAction.Overwrite";
}
return "unreachable";
}
};
inline void eraseTaskUpdate(bool eraseInProgress,
const std::shared_ptr<task::TaskData>& taskData,
const std::string& connectionName,
const std::string& drivePath)
{
if (eraseInProgress)
{
// nothing to do
return;
}
// has finished, either success or failure
taskData->stopMonitor();
// Have to create a new Resp here as we cannot use the original resp or it never goes out of scope
// This resp will not have a strand associated with it which is okay because this will never be
// multithreaded
auto resp = std::make_shared<bmcweb::AsyncResp>(nullptr);
managedStore::ManagedObjectStoreContext context(resp);
redfish::storage_utils::getAllProperties(
connectionName, drivePath,
"xyz.openbmc_project.Inventory.Item.DriveErase", context,
[taskData](const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& props) {
if (ec)
{
taskData->messages.emplace_back(messages::internalError());
taskData->state = "Exception";
taskData->complete(
nlohmann::json(),
boost::beast::http::status::internal_server_error);
return;
}
std::string errorName;
std::string errorDescription;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), props, "ErrorName", errorName,
"ErrorDescription", errorDescription);
if (!success)
{
taskData->messages.emplace_back(messages::internalError());
taskData->state = "Exception";
taskData->complete(
nlohmann::json(),
boost::beast::http::status::internal_server_error);
return;
}
if (errorName.empty())
{
// Erase Success
taskData->state = "Completed";
taskData->percentComplete = 100;
taskData->messages.emplace_back(messages::success());
taskData->complete();
}
else
{
// Erase Failed
bmcweb::AsyncResp resp(nullptr);
storageAddDbusError(resp.res, "eraseTaskUpdate", "", errorName,
errorDescription);
for (auto& m :
resp.res.jsonValue["error"][messages::messageAnnotation])
{
taskData->messages.emplace_back(m);
}
taskData->state = "Exception";
taskData->complete(std::move(resp.res.jsonValue),
resp.res.result());
}
});
}
inline bool eraseTaskHandler(sdbusplus::message_t& msg,
const std::shared_ptr<task::TaskData>& taskData,
const std::string& connectionName,
const std::string& drivePath)
{
dbus::utility::DBusPropertiesMap props;
std::string iface;
msg.read(iface, props);
if (iface != "xyz.openbmc_project.Inventory.Item.DriveErase")
{
BMCWEB_LOG_DEBUG << "eraseTaskHandler wrong interface";
return !task::completed;
}
std::optional<bool> inProgress;
std::optional<double> erasePercentage;
sdbusplus::unpackPropertiesNoThrow(dbus_utils::UnpackErrorPrinter(), props,
"EraseInProgress", inProgress,
"ErasePercentage", erasePercentage);
if (erasePercentage)
{
BMCWEB_LOG_DEBUG << "eraseTaskHandler update erasePercentage "
<< *erasePercentage;
taskData->percentComplete = static_cast<int>(*erasePercentage);
}
if (inProgress)
{
BMCWEB_LOG_DEBUG << "eraseTaskHandler update iniProgress "
<< *inProgress;
eraseTaskUpdate(*inProgress, taskData, connectionName, drivePath);
}
// completion is handled asynchronously so always return !completed
return !task::completed;
}
// Clang believes that req is unused even though its captured in lambda
inline void eraseDrive(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& connectionName,
const std::string& drivePath, const EraseParams& params)
{
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[req, asyncResp, connectionName, drivePath
](const boost::system::error_code ec,
const sdbusplus::message_t& msg) {
// Failure returned from NVMe
const ::sd_bus_error* sd_err = msg.get_error();
if (sd_err != nullptr)
{
storageAddDbusError(asyncResp->res, "Drive Erase", "", sd_err->name,
sd_err->message);
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "Erase dbus error " << ec;
messages::internalError(asyncResp->res);
return;
}
// success, create the async task
BMCWEB_LOG_DEBUG << "erase started";
std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
[connectionName,
drivePath](const boost::system::error_code& err,
sdbusplus::message_t& taskMsg,
const std::shared_ptr<task::TaskData>& taskData) {
if (err)
{
// Internal error in property signal callback?
BMCWEB_LOG_ERROR << drivePath << ": Error in task";
taskData->messages.emplace_back(messages::internalError());
taskData->state = "Cancelled";
return task::completed;
}
return eraseTaskHandler(taskMsg, taskData, connectionName,
drivePath);
},
"type='signal',interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',arg0='xyz.openbmc_project.Inventory.Item.DriveErase',"
"path='" +
drivePath + "'");
task->startTimer(std::chrono::minutes(180));
task->populateResp(asyncResp->res);
task->payload.emplace(req);
// Erase may have completed prior to Task watching for signals, so poll
// once.
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getProperty<bool>(
connectionName, drivePath,
"xyz.openbmc_project.Inventory.Item.DriveErase", "EraseInProgress", context,
[task, connectionName,
drivePath](const boost::system::error_code& ec2, bool inProgress) {
if (ec2)
{
BMCWEB_LOG_DEBUG << "erase poll error: " << ec2;
return;
}
eraseTaskUpdate(inProgress, task, connectionName, drivePath);
});
},
connectionName, drivePath,
"xyz.openbmc_project.Inventory.Item.DriveErase", "Erase",
params.actionName());
}
inline void
matchAndEraseDrive(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::vector<std::string>& drivePaths,
const std::string& driveName, const EraseParams& params)
{
// Match the driveName
int found = 0;
std::string drivePath;
for (const std::string& d : drivePaths)
{
sdbusplus::message::object_path path(d);
std::string leaf = path.filename();
if (leaf == driveName)
{
found++;
drivePath = d;
}
}
if (found > 1)
{
// Sanity check
BMCWEB_LOG_DEBUG << "Multiple drives match name " << driveName;
messages::internalError(asyncResp->res);
return;
}
if (found == 0)
{
messages::resourceNotFound(asyncResp->res, "#Drive.v1_7_0.Drive",
driveName);
return;
}
// Find the connection
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.DriveErase"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getDbusObject(
drivePath, interfaces, requestContext,
[req, asyncResp, params,
drivePath](const boost::system::error_code& ec,
const dbus::utility::MapperGetObject& services) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
if (services.size() != 1)
{
BMCWEB_LOG_DEBUG << "multiple serviceInterfaces entries";
messages::internalError(asyncResp->res);
return;
}
auto connectionName = services.front().first;
// Perform the erase
eraseDrive(req, asyncResp, connectionName, drivePath, params);
});
}
// Find Chassis with chassisId and the Drives associated to it.
inline void findChassisDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId,
std::function<void(const boost::system::error_code ec3,
const std::vector<std::string>& resp)>&&
cb)
{
constexpr std::array<std::string_view, 2> interfaces = {
"xyz.openbmc_project.Inventory.Item.Board",
"xyz.openbmc_project.Inventory.Item.Chassis"};
// mapper call chassis
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/inventory", 0, interfaces, requestContext,
[asyncResp, chassisId, cb, requestContext](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
messages::internalError(asyncResp->res);
return;
}
// Iterate over all retrieved ObjectPaths.
int found = 0;
std::string chassisPath;
for (const auto& [path, connectionNames] : subtree)
{
sdbusplus::message::object_path objPath(path);
if (objPath.filename() != chassisId)
{
continue;
}
if (connectionNames.empty())
{
BMCWEB_LOG_ERROR << "Got 0 Connection names";
continue;
}
found++;
chassisPath = path;
}
if (found > 1)
{
BMCWEB_LOG_ERROR << "Multiple chassis match";
messages::internalError(asyncResp->res);
return;
}
if (found == 0)
{
messages::resourceNotFound(asyncResp->res,
"#Chassis.v1_14_0.Chassis", chassisId);
return;
}
dbus_utils::getAssociationEndPoints(chassisPath + "/drive",
requestContext, cb);
});
}
inline void
handleChassisDriveGet(crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId,
const std::string& driveName)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
findChassisDrive(asyncResp, chassisId,
[asyncResp, chassisId,
driveName](const boost::system::error_code ec,
const std::vector<std::string>& resp) {
if (ec)
{
return; // no drives = no failures
}
matchAndFillDrive(asyncResp, chassisId, driveName, resp);
});
}
inline void handleDriveSecureErase(crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId,
const std::string& driveName)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
auto p = EraseParams::parse(req, asyncResp);
if (!p)
{
return;
}
EraseParams params = *p;
// Find paths of drives associated with the ChassisId
findChassisDrive(asyncResp, chassisId,
[req, asyncResp, chassisId, driveName,
params](const boost::system::error_code ec,
const std::vector<std::string>& drivePaths) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
matchAndEraseDrive(req, asyncResp, drivePaths, driveName, params);
});
}
/**
* This URL will show the drive interface for the specific drive in the chassis
*/
inline void requestRoutesChassisDriveName(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/")
.privileges(redfish::privileges::getChassis)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleChassisDriveGet, std::ref(app)));
BMCWEB_ROUTE(
app, "/redfish/v1/Chassis/<str>/Drives/<str>/Actions/Drive.SecureErase")
.privileges(redfish::privileges::postDrive)
.methods(boost::beast::http::verb::post)(
std::bind_front(handleDriveSecureErase, std::ref(app)));
}
inline void setResetType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& driveId, const std::string& action,
const dbus::utility::MapperGetSubTreeResponse& subtree)
{
auto driveState =
std::find_if(subtree.begin(), subtree.end(), [&driveId](auto& object) {
const sdbusplus::message::object_path path(object.first);
return path.filename() == driveId;
});
if (driveState == subtree.end())
{
messages::resourceNotFound(asyncResp->res, "Drive Action", driveId);
return;
}
const std::string& path = driveState->first;
const std::vector<std::pair<std::string, std::vector<std::string>>>&
connectionNames = driveState->second;
if (connectionNames.size() != 1)
{
BMCWEB_LOG_ERROR << "Connection size " << connectionNames.size()
<< ", not equal to 1";
messages::internalError(asyncResp->res);
return;
}
managedStore::GetManagedObjectStore()->setProperty(
connectionNames[0].first, path, "xyz.openbmc_project.State.Drive",
"RequestedDriveTransition", action,
[asyncResp, action](const boost::system::error_code ec) {
if (ec)
{
BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error for " << action
<< " : " << ec;
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
});
}
/**
* Performs drive reset action.
*
* @param[in] asyncResp - Shared pointer for completing asynchronous calls
* @param[in] driveId - D-bus filename to identify the Drive
* @param[in] resetType - Reset type for the Drive
*/
inline void
performDriveReset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& driveId,
std::optional<std::string> resetType)
{
std::string action;
if (!resetType || *resetType == "PowerCycle")
{
action = "xyz.openbmc_project.State.Drive.Transition.Powercycle";
}
else if (*resetType == "ForceReset")
{
action = "xyz.openbmc_project.State.Drive.Transition.Reboot";
}
else
{
BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: "
<< *resetType;
messages::actionParameterNotSupported(asyncResp->res, *resetType,
"ResetType");
return;
}
BMCWEB_LOG_DEBUG << "Reset Drive with " << action;
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.State.Drive"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/inventory", 0, interfaces, requestContext,
[asyncResp, driveId,
action](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBUS response error";
messages::internalError(asyncResp->res);
return;
}
setResetType(asyncResp, driveId, action, subtree);
});
}
inline void
handleChassisDriveReset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& driveId,
const std::optional<std::string>& resetType,
const std::vector<std::string>& drives)
{
std::unordered_set<std::string> drivesMap(drives.begin(), drives.end());
constexpr std::array<std::string_view, 2> interfaces = {
"xyz.openbmc_project.Inventory.Item.Drive",
"xyz.openbmc_project.State.Drive"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/inventory", 0, interfaces, requestContext,
[asyncResp, driveId, resetType, drivesMap, requestContext](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
BMCWEB_LOG_ERROR << "Drive mapper call error ";
messages::internalError(asyncResp->res);
return;
}
auto drive = std::find_if(
subtree.begin(), subtree.end(),
[&driveId, &drivesMap](
const std::pair<std::string, dbus::utility::MapperServiceMap>&
object) {
return sdbusplus::message::object_path(object.first).filename() ==
driveId &&
drivesMap.contains(object.first);
});
if (drive == subtree.end())
{
messages::resourceNotFound(asyncResp->res, "Drive Action Reset",
driveId);
return;
}
const std::string& drivePath = drive->first;
const dbus::utility::MapperServiceMap& driveConnections = drive->second;
if (driveConnections.size() != 1)
{
BMCWEB_LOG_ERROR << "Connection size " << driveConnections.size()
<< ", not equal to 1";
messages::internalError(asyncResp->res);
return;
}
bool driveInterface = false;
bool driveStateInterface = false;
for (const std::string& interface : driveConnections[0].second)
{
if (interface == "xyz.openbmc_project.Inventory.Item.Drive")
{
driveInterface = true;
}
if (interface == "xyz.openbmc_project.State.Drive")
{
driveStateInterface = true;
}
}
if (!driveInterface || !driveStateInterface)
{
BMCWEB_LOG_ERROR << "Drive does not have the required interfaces ";
messages::internalError(asyncResp->res);
return;
}
dbus_utils::getProperty<bool>(
driveConnections[0].first, drivePath,
"xyz.openbmc_project.Inventory.Item.Drive", "Resettable",
requestContext,
[asyncResp, driveId, resetType](
const boost::system::error_code propEc, bool resettable) {
if (propEc)
{
BMCWEB_LOG_ERROR << "Failed to get resettable property ";
messages::internalError(asyncResp->res);
return;
}
if (!resettable)
{
messages::actionNotSupported(
asyncResp->res, "The drive does not support resets.");
return;
}
performDriveReset(asyncResp, driveId, resetType);
});
});
}
inline void handlePostDriveResetAction(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId, const std::string& driveId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
BMCWEB_LOG_DEBUG << "Post Drive Reset.";
nlohmann::json jsonRequest;
std::optional<std::string> resetType;
if (json_util::processJsonFromRequest(asyncResp->res, req, jsonRequest) &&
!jsonRequest["ResetType"].empty())
{
resetType = jsonRequest["ResetType"];
}
findChassisDrive(asyncResp, chassisId,
[asyncResp, driveId,
resetType](const boost::system::error_code ec,
const std::vector<std::string>& drives) {
if (ec)
{
BMCWEB_LOG_ERROR << "failed to find drives";
messages::internalError(asyncResp->res);
return; // no drives = no failures
}
handleChassisDriveReset(asyncResp, driveId, resetType, drives);
});
}
/**
* DriveResetAction class supports the POST method for the Reset (reboot)
* action.
*/
inline void requestDriveResetAction(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Chassis/<str>/Drives/<str>/Actions/Drive.Reset/")
.privileges(redfish::privileges::postDrive)
.methods(boost::beast::http::verb::post)(
std::bind_front(handlePostDriveResetAction, std::ref(app)));
}
inline void handleChassisDriveResetActionInfo(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId, const std::string& driveId,
const std::vector<std::string>& drives)
{
std::unordered_set<std::string> drivesMap(drives.begin(), drives.end());
constexpr std::array<std::string_view, 2> interfaces = {
"xyz.openbmc_project.Inventory.Item.Drive",
"xyz.openbmc_project.State.Drive"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/inventory", 0, interfaces, requestContext,
[asyncResp, chassisId, driveId, drivesMap, requestContext](
const boost::system::error_code ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
BMCWEB_LOG_ERROR << "Drive mapper call error";
messages::internalError(asyncResp->res);
return;
}
auto drive = std::find_if(
subtree.begin(), subtree.end(),
[&driveId, &drivesMap](
const std::pair<std::string,
std::vector<std::pair<
std::string, std::vector<std::string>>>>&
object) {
return sdbusplus::message::object_path(object.first).filename() ==
driveId &&
drivesMap.contains(object.first);
});
if (drive == subtree.end())
{
messages::resourceNotFound(asyncResp->res, "Drive ResetActionInfo",
driveId);
return;
}
const std::string& drivePath = drive->first;
const dbus::utility::MapperServiceMap& driveConnections = drive->second;
if (driveConnections.size() != 1)
{
BMCWEB_LOG_ERROR << "Connection size " << driveConnections.size()
<< ", not equal to 1";
messages::internalError(asyncResp->res);
return;
}
bool driveInterface = false;
bool driveStateInterface = false;
for (const std::string& interface : driveConnections[0].second)
{
if (interface == "xyz.openbmc_project.Inventory.Item.Drive")
{
driveInterface = true;
}
if (interface == "xyz.openbmc_project.State.Drive")
{
driveStateInterface = true;
}
}
if (!driveInterface || !driveStateInterface)
{
BMCWEB_LOG_ERROR << "Drive does not have the required interfaces ";
messages::internalError(asyncResp->res);
return;
}
dbus_utils::getProperty<bool>(
driveConnections[0].first, drivePath,
"xyz.openbmc_project.Inventory.Item.Drive", "Resettable",
requestContext,
[asyncResp, chassisId,
driveId](const boost::system::error_code propEc, bool resettable) {
if (propEc)
{
BMCWEB_LOG_ERROR << "Failed to get resettable property ";
messages::internalError(asyncResp->res);
return;
}
if (!resettable)
{
messages::actionNotSupported(
asyncResp->res, "The drive does not support resets.");
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#ActionInfo.v1_1_2.ActionInfo";
asyncResp->res.jsonValue["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Chassis",
chassisId, "Drives", driveId,
"ResetActionInfo");
asyncResp->res.jsonValue["Name"] = "Reset Action Info";
asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
nlohmann::json::array_t parameters;
nlohmann::json::object_t parameter;
parameter["Name"] = "ResetType";
parameter["Required"] = true;
parameter["DataType"] = "String";
nlohmann::json::array_t allowableValues;
allowableValues.emplace_back("PowerCycle");
allowableValues.emplace_back("ForceRestart");
parameter["AllowableValues"] = std::move(allowableValues);
parameters.emplace_back(parameter);
asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
});
});
}
inline void handleGetDriveResetActionInfo(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId, const std::string& driveId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
findChassisDrive(asyncResp, chassisId,
[asyncResp, chassisId,
driveId](const boost::system::error_code ec,
const std::vector<std::string>& drives) {
if (ec)
{
BMCWEB_LOG_ERROR << "failed to find drives";
messages::internalError(asyncResp->res);
return; // no drives = no failures
}
handleChassisDriveResetActionInfo(asyncResp, chassisId, driveId,
drives);
});
}
/**
* DriveResetActionInfo derived class for delivering Drive
* ResetType AllowableValues using ResetInfo schema.
*/
inline void requestRoutesDriveResetActionInfo(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/ResetActionInfo/")
.privileges(redfish::privileges::getActionInfo)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetDriveResetActionInfo, std::ref(app)));
}
inline void getStorageControllerAsset(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const boost::system::error_code& ec,
const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
propertiesList)
{
if (ec)
{
// this interface isn't necessary
BMCWEB_LOG_DEBUG << "Failed to get StorageControllerAsset";
return;
}
const std::string* partNumber = nullptr;
const std::string* serialNumber = nullptr;
const std::string* manufacturer = nullptr;
const std::string* model = nullptr;
if (!sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
partNumber, "SerialNumber", serialNumber, "Manufacturer",
manufacturer, "Model", model))
{
messages::internalError(asyncResp->res);
return;
}
if (partNumber != nullptr)
{
asyncResp->res.jsonValue["PartNumber"] = *partNumber;
}
if (serialNumber != nullptr)
{
asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
}
if (manufacturer != nullptr)
{
asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
}
if (model != nullptr)
{
asyncResp->res.jsonValue["Model"] = *model;
}
}
inline void tryPopulateControllerNvme(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path, const dbus::utility::MapperServiceMap& ifaces)
{
if (storage_utils::matchServiceName(ifaces,
"xyz.openbmc_project.NVMe.NVMeAdmin"))
{
auto& nvprop = asyncResp->res.jsonValue["NVMeControllerProperties"];
// TODO(matt) fetch other properties, don't use hardcoded values
nvprop["ControllerType"] = "IO";
nvprop["NVMeVersion"] = "1.4";
}
if (auto service = storage_utils::matchServiceName(
ifaces, "xyz.openbmc_project.Software.ExtendedVersion"))
{
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getProperty<std::string>(
*service, path, "xyz.openbmc_project.Software.ExtendedVersion",
"ExtendedVersion", context,
[asyncResp](const boost::system::error_code& ec,
const std::string& extVers) {
if (ec)
{
return;
}
std::string v(
"xyz.openbmc_project.NVMe.ControllerFirmwareVersion:");
if (extVers.starts_with(v))
{
asyncResp->res.jsonValue["FirmwareVersion"] =
extVers.substr(v.size());
}
});
}
}
inline void tryPopulateControllerSecurity(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const boost::urls::url& controllerUrl,
const dbus::utility::MapperServiceMap& ifaces)
{
if (!storage_utils::matchServiceName(
ifaces,
"xyz.openbmc_project.Inventory.Item.StorageControllerSecurity"))
{
return;
}
boost::urls::url sendUrl(controllerUrl);
crow::utility::appendUrlPieces(sendUrl, "Actions",
"StorageController.SecuritySend");
boost::urls::url receiveUrl(controllerUrl);
crow::utility::appendUrlPieces(receiveUrl, "Actions",
"StorageController.SecurityReceive");
auto& actions = asyncResp->res.jsonValue["Actions"];
actions["#StorageController.SecuritySend"]["target"] = sendUrl;
actions["#StorageController.SecurityReceive"]["target"] = receiveUrl;
}
inline void storageCtrlAttachedVolumes(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const sdbusplus::message::object_path& controllerPath,
std::function<void(const boost::system::error_code& ec,
const std::vector<std::string>& volPaths)>&&
cb)
{
// Get list of attached volumes
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Volume"};
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getAssociatedSubTreePaths(
controllerPath / "attaching",
sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
interfaces, context,
[cb](const boost::system::error_code& ec,
const std::vector<std::string>& volPaths) { cb(ec, volPaths); });
}
inline void populateStorageControllerAttached(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& path)
{
storageCtrlAttachedVolumes(
asyncResp, path, [asyncResp, systemName,
storageId](const boost::system::error_code& ec,
const std::vector<std::string>& attachedVolumeList) {
if (ec)
{
BMCWEB_LOG_DEBUG << "populating attached volumes failed";
messages::internalError(asyncResp->res);
return;
}
std::vector<boost::urls::url> redfishVolumes;
for (const std::string& volumePath : attachedVolumeList)
{
std::string id =
sdbusplus::message::object_path(volumePath).filename();
if (id.empty())
{
BMCWEB_LOG_ERROR << "Failed to find filename in " << volumePath;
return;
}
boost::urls::url volumeUrl = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName, "Storage", storageId,
"Volumes", id);
redfishVolumes.emplace_back(volumeUrl);
}
asyncResp->res.jsonValue["Links"]["AttachedVolumes"] = redfishVolumes;
});
}
inline void populateStorageController(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& controllerId, const std::string& path,
const dbus::utility::MapperServiceMap& ifaces)
{
if (ifaces.size() != 1)
{
BMCWEB_LOG_ERROR << "Connection size " << ifaces.size()
<< ", greater than 1";
messages::internalError(asyncResp->res);
return;
}
const std::string& connectionName = ifaces.front().first;
const std::vector<std::string>& interfaces = ifaces.front().second;
asyncResp->res.jsonValue["@odata.type"] =
"#StorageController.v1_7_0.StorageController";
auto url = crow::utility::urlFromPieces("redfish", "v1", "Systems",
systemName, "Storage", storageId,
"Controllers", controllerId);
asyncResp->res.jsonValue["@odata.id"] = url;
asyncResp->res.jsonValue["Name"] = controllerId;
asyncResp->res.jsonValue["Id"] = controllerId;
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
storage_utils::tryGetLocation(asyncResp, connectionName, path,
storage_utils::controllerInterface,
interfaces);
populateStorageControllerAttached(asyncResp, systemName, storageId, path);
tryPopulateControllerNvme(asyncResp, path, ifaces);
tryPopulateMetricCollection(asyncResp, path, ifaces, url);
tryPopulateControllerSecurity(asyncResp, url, ifaces);
if (enableCustomSSD)
{
populateCustomSSDInfo(asyncResp, ifaces, path);
}
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<bool>(
connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present",
context,
[asyncResp](const boost::system::error_code& ec, bool isPresent) {
// this interface isn't necessary, only check it
// if we get a good return
if (ec)
{
BMCWEB_LOG_DEBUG << "Failed to get Present property";
return;
}
if (!isPresent)
{
asyncResp->res.jsonValue["Status"]["State"] = "Absent";
}
});
managedStore::GetManagedObjectStore()->getAllProperties(
connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
context,
[asyncResp](const boost::system::error_code& ec,
const std::vector<
std::pair<std::string, dbus::utility::DbusVariantType>>&
propertiesList) {
getStorageControllerAsset(asyncResp, ec, propertiesList);
});
}
inline void securitySendAction(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path, const dbus::utility::MapperServiceMap& ifaces,
uint8_t proto, uint16_t protoSpecific, const std::string& dataBase64)
{
std::string dataString;
if (!crow::utility::base64Decode(dataBase64, dataString))
{
BMCWEB_LOG_DEBUG << "base data base64decode";
messages::actionParameterValueFormatError(
asyncResp->res, "<data>", "Data", "StorageController.SecuritySend");
return;
}
// base64Decode outputs a string not bytes
uint8_t* buffer = reinterpret_cast<uint8_t*>(dataString.data()); // NOLINT
std::vector<uint8_t> data(buffer, buffer + dataString.size()); // NOLINT
auto service = storage_utils::matchServiceName(
ifaces, "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity");
if (!service)
{
BMCWEB_LOG_DEBUG << "No servicename";
messages::internalError(asyncResp->res);
return;
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code ec,
const sdbusplus::message_t& msg) {
// Failure returned from NVMe
const ::sd_bus_error* sd_err = msg.get_error();
if (sd_err != nullptr)
{
messages::generalError(asyncResp->res);
BMCWEB_LOG_DEBUG << "SecuritySend NVMe error";
if (sd_err->message != nullptr)
{
BMCWEB_LOG_DEBUG << "Error: " << sd_err->name << " message "
<< sd_err->message;
asyncResp->res.jsonValue["error"]["message"] = sd_err->message;
}
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "SecuritySend dbus error " << ec;
messages::internalError(asyncResp->res);
return;
}
// success
asyncResp->res.result(boost::beast::http::status::no_content);
},
*service, path,
"xyz.openbmc_project.Inventory.Item.StorageControllerSecurity",
"SecuritySend", proto, protoSpecific, data);
}
inline void securityReceiveAction(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path, const dbus::utility::MapperServiceMap& ifaces,
uint8_t proto, uint16_t protoSpecific, uint32_t transferLength)
{
auto service = storage_utils::matchServiceName(
ifaces, "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity");
if (!service)
{
BMCWEB_LOG_DEBUG << "No servicename";
messages::internalError(asyncResp->res);
return;
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code ec,
const sdbusplus::message_t& msg,
const std::vector<uint8_t>& data) {
// Failure returned from NVMe
const ::sd_bus_error* sd_err = msg.get_error();
if (sd_err != nullptr)
{
messages::generalError(asyncResp->res);
BMCWEB_LOG_DEBUG << "SecurityReceive NVMe error";
if (sd_err->message != nullptr)
{
BMCWEB_LOG_DEBUG << "Error: " << sd_err->name << " message "
<< sd_err->message;
asyncResp->res.jsonValue["error"]["message"] = sd_err->message;
}
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "SecurityReceive dbus error " << ec;
messages::internalError(asyncResp->res);
return;
}
// Success
asyncResp->res.jsonValue["Data"] =
crow::utility::base64encode(std::string_view(
reinterpret_cast<const char*>(data.data()), data.size())); // NOLINT
},
*service, path,
"xyz.openbmc_project.Inventory.Item.StorageControllerSecurity",
"SecurityReceive", proto, protoSpecific, transferLength);
}
inline static void
setCustomSSDOemGpio(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path, const std::string& property,
bool value)
{
std::string customSSDDbusInterface =
"com.google.gbmc.ssd." +
boost::algorithm::to_lower_copy(std::string(customSSD));
managedStore::GetManagedObjectStore()->setProperty(
"com.google.gbmc.ssd", path,
customSSDDbusInterface, property, value,
[asyncResp](const boost::system::error_code ec) {
if (ec)
{
BMCWEB_LOG_ERROR << "setCustomSSDOemGpio D-Bus responses error: "
<< ec;
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
});
}
inline static void
setCustomSSDSpiImage(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path, const std::string& property,
const std::string& value)
{
std::string customSSDDbusInterface =
"com.google.gbmc.ssd." +
boost::algorithm::to_lower_copy(std::string(customSSD));
managedStore::GetManagedObjectStore()->setProperty(
"com.google.gbmc.ssd", path,
customSSDDbusInterface, property, value,
[asyncResp](const boost::system::error_code ec) {
if (ec)
{
BMCWEB_LOG_ERROR << "setCustomSSDSpiImage D-Bus responses error: "
<< ec;
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
});
}
inline void storagePatchCustomSSDOem(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId, const std::string& controllerId,
nlohmann::json& customSSDOem)
{
storage_utils::findStorageAndController(
asyncResp, storageId, controllerId,
[asyncResp, storageId, controllerId, customSSDOem](
const std::string& path, const dbus::utility::MapperServiceMap&) {
if (customSSDOem.contains("MorristownOtpWriteEnable"))
{
setCustomSSDOemGpio(asyncResp, path, "MorristownOtpWriteEnable",
customSSDOem["MorristownOtpWriteEnable"]);
}
if (customSSDOem.contains("TriggerPowerCycle"))
{
setCustomSSDOemGpio(asyncResp, path, "TriggerPowerCycle",
customSSDOem["TriggerPowerCycle"]);
}
if (customSSDOem.contains("DisableWatchdog"))
{
setCustomSSDOemGpio(asyncResp, path, "DisableWatchdog",
customSSDOem["DisableWatchdog"]);
}
if (customSSDOem.contains("TriggerReset"))
{
setCustomSSDOemGpio(asyncResp, path, "TriggerReset",
customSSDOem["TriggerReset"]);
}
if (customSSDOem.contains("CpldReset"))
{
setCustomSSDOemGpio(asyncResp, path, "CpldReset",
customSSDOem["CpldReset"]);
}
if (customSSDOem.contains("SpiImgSelect"))
{
setCustomSSDSpiImage(asyncResp, path, "SpiImgSelect",
customSSDOem["SpiImgSelect"]);
}
});
}
// Performs storage attach and detach operations.
// Will be called pseudo-recursively (asio dbus callbacks) to perform
// the operations.
inline void storageApplyAttachDetach(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& connectionName, const std::string& controllerPath,
const std::shared_ptr<std::vector<std::string>>& attaches,
const std::shared_ptr<std::vector<std::string>>& detaches)
{
if (!detaches->empty())
{
sdbusplus::message::object_path v = detaches->back();
detaches->pop_back();
BMCWEB_LOG_DEBUG << "detaching " << v.str << " from " << controllerPath
<< "\n";
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, connectionName, controllerPath, attaches,
detaches](const boost::system::error_code ec,
const sdbusplus::message_t& msg) {
// Failure returned from NVMe
const ::sd_bus_error* sd_err = msg.get_error();
if (sd_err != nullptr)
{
// TODO remove "" argument
storageAddDbusError(asyncResp->res, "detach volume NVMe", "",
sd_err->name, sd_err->message);
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "detach volume dbus error " << ec;
messages::internalError(asyncResp->res);
return;
}
// "recurse"
storageApplyAttachDetach(asyncResp, connectionName, controllerPath,
attaches, detaches);
},
connectionName, controllerPath,
"xyz.openbmc_project.Inventory.Item.StorageController",
"DetachVolume", v);
return;
}
if (!attaches->empty())
{
sdbusplus::message::object_path v = attaches->back();
attaches->pop_back();
BMCWEB_LOG_DEBUG << "attaching " << v.str << " to " << controllerPath
<< "\n";
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, connectionName, controllerPath, attaches,
detaches](const boost::system::error_code ec,
const sdbusplus::message_t& msg) {
// Failure returned from NVMe
const ::sd_bus_error* sd_err = msg.get_error();
if (sd_err != nullptr)
{
// TODO remove "" argument
storageAddDbusError(asyncResp->res, "attach volume NVMe", "",
sd_err->name, sd_err->message);
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "attach volume dbus error " << ec;
messages::internalError(asyncResp->res);
return;
}
// "recurse"
storageApplyAttachDetach(asyncResp, connectionName, controllerPath,
attaches, detaches);
},
connectionName, controllerPath,
"xyz.openbmc_project.Inventory.Item.StorageController",
"AttachVolume", v);
return;
}
// both lists are complete, return success with the controller.
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.StorageController"};
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getDbusObject(
controllerPath, interfaces, context,
[asyncResp, connectionName,
controllerPath](const boost::system::error_code& ec,
const dbus::utility::MapperGetObject& interfaceDict) {
if (ec)
{
BMCWEB_LOG_DEBUG << "attach volume get controller dbus error "
<< ec;
messages::internalError(asyncResp->res);
return;
}
if (interfaceDict.size() != 1)
{
BMCWEB_LOG_DEBUG << "attachdetach extra services";
for (const auto& x : interfaceDict)
{
BMCWEB_LOG_DEBUG << "if " << x.first;
}
messages::internalError(asyncResp->res);
}
auto c = sdbusplus::message::object_path(controllerPath);
auto storagePath = c.parent_path().parent_path();
std::string storageId = storagePath.filename();
std::string controllerId = c.filename();
storage_utils::getSystemPathFromStorage(
asyncResp, storagePath,
[asyncResp, storageId, controllerId, controllerPath,
interfaceDict](std::optional<std::string_view> systemPath) {
std::string systemName =
(systemPath) ? std::filesystem::path(*systemPath).filename()
: "system";
populateStorageController(asyncResp, systemName, storageId,
controllerId, controllerPath,
interfaceDict);
});
});
}
inline void storagePatchAttachedVolumes(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& controllerId, std::vector<std::string>& updateVolumeURIs)
{
// vector of {parsed storageId, URI}
std::vector<std::pair<std::string, std::string>> updateVolIDs;
for (auto& u : updateVolumeURIs)
{
auto parsedUrl = boost::urls::parse_relative_ref(u);
if (!parsedUrl)
{
BMCWEB_LOG_DEBUG << "bad attached volume URI " << u;
messages::invalidURI(asyncResp->res, u);
return;
}
std::string urlStorageId;
std::string volumeId;
if (!crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
"Systems", systemName.c_str(),
"Storage", std::ref(urlStorageId),
"Volumes", std::ref(volumeId)))
{
BMCWEB_LOG_DEBUG << "bad attached volume URI " << u;
messages::invalidURI(asyncResp->res, u);
return;
}
if (urlStorageId != storageId)
{
BMCWEB_LOG_DEBUG << "bad attached volume URI " << u;
messages::invalidURI(asyncResp->res, u);
return;
}
updateVolIDs.emplace_back(volumeId, u);
}
storage_utils::findStorageAndController(
asyncResp, storageId, controllerId,
[asyncResp,
updateVolIDs](const std::string& controllerPath,
const dbus::utility::MapperServiceMap& ifaces) {
const auto& connectionName = ifaces.front().first;
// Create dbus paths to update. Elements are {dbus_path, URI}
std::vector<std::pair<std::string, std::string>> updateVolumes;
auto storagePath = sdbusplus::message::object_path(controllerPath)
.parent_path()
.parent_path();
updateVolumes.reserve(updateVolIDs.size());
for (const auto& [u, uri] : updateVolIDs)
{
updateVolumes.emplace_back((storagePath / "volumes" / u).str, uri);
}
std::sort(updateVolumes.begin(), updateVolumes.end());
// Get list of available volumes
storageVolumes(
asyncResp, storagePath,
[asyncResp, updateVolumes, connectionName,
controllerPath](const boost::system::error_code& ec,
const std::vector<std::string>& volPaths) {
if (ec)
{
BMCWEB_LOG_DEBUG
<< "patch attached volumes list volumes failed";
messages::internalError(asyncResp->res);
return;
}
for (const auto& a : volPaths)
{
BMCWEB_LOG_DEBUG << "vol is " << a;
}
std::vector<std::string> updatePaths;
// Early check for bad volume paths
for (const auto& [u, uri] : updateVolumes)
{
if (std::find(volPaths.begin(), volPaths.end(), u) ==
volPaths.end())
{
BMCWEB_LOG_DEBUG << "patch volume not found " << uri;
messages::invalidURI(asyncResp->res, uri);
return;
}
updatePaths.emplace_back(u);
}
// Fetch currently attached volumes
storageCtrlAttachedVolumes(
asyncResp, controllerPath,
[asyncResp, updatePaths, connectionName,
controllerPath](const boost::system::error_code& ec2,
const std::vector<std::string>& ex) {
if (ec2)
{
BMCWEB_LOG_DEBUG
<< "patch attached volumes list attached failed";
messages::internalError(asyncResp->res);
return;
}
// Find changes
auto attaches = std::make_shared<std::vector<std::string>>();
auto detaches = std::make_shared<std::vector<std::string>>();
std::vector<std::string> existing(ex);
std::sort(existing.begin(), existing.end());
std::set_difference(updatePaths.begin(), updatePaths.end(),
existing.begin(), existing.end(),
std::back_inserter(*attaches));
std::set_difference(existing.begin(), existing.end(),
updatePaths.begin(), updatePaths.end(),
std::back_inserter(*detaches));
// Apply
storageApplyAttachDetach(asyncResp, connectionName,
controllerPath, attaches, detaches);
});
});
});
}
inline void
storagePatchController(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName,
const std::string& storageId,
const std::string& controllerId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
if (!enableCustomSSD)
{
return;
}
std::optional<nlohmann::json> customSSDOem;
std::optional<std::vector<std::string>> attachedVolumes;
if (!json_util::readJsonPatch(
req, asyncResp->res, "Links/AttachedVolumes", attachedVolumes,
"Links/Oem/Google/" + std::string(customSSD), customSSDOem))
{
BMCWEB_LOG_DEBUG << "Bad controller patch input";
return;
}
if (customSSDOem && attachedVolumes)
{
BMCWEB_LOG_DEBUG << "Multiple values to controller patch";
messages::generalError(asyncResp->res);
asyncResp->res.jsonValue["error"]["message"] =
"PATCH may only alter one resource type";
return;
}
if (!(customSSDOem || attachedVolumes))
{
BMCWEB_LOG_DEBUG << "No values to controller patch";
messages::noOperation(asyncResp->res);
return;
}
if (customSSDOem)
{
storagePatchCustomSSDOem(asyncResp, storageId, controllerId,
*customSSDOem);
}
if (attachedVolumes)
{
storagePatchAttachedVolumes(asyncResp, systemName, storageId,
controllerId, *attachedVolumes);
}
// TODO: we should setCompleteRequestHandler to return the modified
// StorageController on completion, rather than handling in attachedVolumes.
}
inline void handlePostStorageControllerSecuritySend(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& controllerId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
uint8_t proto = 0;
uint16_t protoSpecific = 0;
std::string dataBase64;
if (!json_util::readJsonAction(req, asyncResp->res, "SecurityProtocol",
proto, "SecurityProtocolSpecific",
protoSpecific, "Data", dataBase64))
{
BMCWEB_LOG_DEBUG << "Missing request json parameters";
return;
}
storage_utils::findStorageAndController(
asyncResp, storageId, controllerId,
[asyncResp, proto, protoSpecific,
dataBase64](const sdbusplus::message::object_path& path,
const dbus::utility::MapperServiceMap& ifaces) {
securitySendAction(asyncResp, path, ifaces, proto, protoSpecific,
dataBase64);
});
}
inline void handlePostStorageControllerSecurityReceive(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& controllerId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
uint8_t proto = 0;
uint16_t protoSpecific = 0;
uint32_t transferLength = 0;
if (!json_util::readJsonAction(req, asyncResp->res, "SecurityProtocol",
proto, "SecurityProtocolSpecific",
protoSpecific, "AllocationLength",
transferLength))
{
BMCWEB_LOG_DEBUG << "Missing request json parameters";
return;
}
storage_utils::findStorageAndController(
asyncResp, storageId, controllerId,
[asyncResp, proto, protoSpecific,
transferLength](const sdbusplus::message::object_path& path,
const dbus::utility::MapperServiceMap& ifaces) {
securityReceiveAction(asyncResp, path, ifaces, proto, protoSpecific,
transferLength);
});
}
inline void requestRoutesStorageControllerActions(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/Actions/StorageController.SecuritySend")
.privileges(redfish::privileges::postStorageController)
.methods(boost::beast::http::verb::post)(std::bind_front(
handlePostStorageControllerSecuritySend, std::ref(app)));
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/Actions/StorageController.SecurityReceive")
.privileges(redfish::privileges::postStorageController)
.methods(boost::beast::http::verb::post)(std::bind_front(
handlePostStorageControllerSecurityReceive, std::ref(app)));
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>")
.privileges(redfish::privileges::patchStorageController)
.methods(boost::beast::http::verb::patch)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& controllerId) {
storagePatchController(app, req, asyncResp, systemName, storageId,
controllerId);
});
}
inline void populateStorageControllerCollection(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const dbus::utility::MapperGetSubTreeResponse& controllerList)
{
if (!asyncResp->res.jsonValue["Members"].is_array())
{
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
}
asyncResp->res.jsonValue["Members@odata.count"] =
asyncResp->res.jsonValue["Members"].size();
for (const auto& controller : controllerList)
{
const sdbusplus::message::object_path controllerPath = controller.first;
storage_utils::findStorageForController(
asyncResp, storageId, controllerPath,
[asyncResp, systemName, controllerPath](
const std::optional<sdbusplus::message::object_path>&
canonStoragePath) {
if (!canonStoragePath)
{
BMCWEB_LOG_DEBUG << "No storage associated with controller "
<< std::string(controllerPath);
return;
}
nlohmann::json::object_t member;
std::string canonStorageId = (*canonStoragePath).filename();
std::string controllerId = controllerPath.filename();
member["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName, "Storage",
canonStorageId, "Controllers", controllerId);
asyncResp->res.jsonValue["Members"].emplace_back(member);
asyncResp->res.jsonValue["Members@odata.count"] =
asyncResp->res.jsonValue["Members"].size();
});
}
}
inline void storageControllerCollectionHandler(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
BMCWEB_LOG_DEBUG
<< "Failed to setup Redfish Route for StorageController Collection";
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
storage_utils::findStorage(
asyncResp, storageId,
[asyncResp, systemName,
storageId](const sdbusplus::message::object_path& storagePath) {
asyncResp->res.jsonValue["@odata.type"] =
"#StorageControllerCollection.StorageControllerCollection";
asyncResp->res.jsonValue["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName,
"Storage", storageId, "Controllers");
asyncResp->res.jsonValue["Name"] = "Storage Controller Collection";
auto& cap = asyncResp->res.jsonValue["@Redfish.CollectionCapabilities"];
cap["@odata.type"] =
"#CollectionCapabilities.v1_3_0.CollectionCapabilities";
auto& cs = cap["Capabilities"];
if (!cs.is_array())
{
cs = nlohmann::json::array_t();
}
auto& c = cs.emplace_back(nlohmann::json::object_t());
c["CapabilitiesObject"]["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName, "Storage", storageId,
"Volumes", "Capabilities");
c["Links"]["TargetCollection"]["@odata.id"] =
asyncResp->res.jsonValue["@odata.id"];
storage_utils::findControllersForStorage(
asyncResp, storagePath,
std::bind_front(populateStorageControllerCollection, asyncResp,
systemName, storageId));
});
}
inline void
tryPopulateVolumeNvme(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& connectionName,
const std::string& path,
const dbus::utility::MapperServiceMap& ifaces,
const std::string& volumeId, size_t blockSize)
{
if (!storage_utils::matchServiceName(ifaces,
"xyz.openbmc_project.Nvme.Volume"))
{
return;
}
asyncResp->res.jsonValue["Name"] = std::string("Namespace ") + volumeId;
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getAllProperties(
connectionName, path, "xyz.openbmc_project.Nvme.Volume", context,
[asyncResp, blockSize](
const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& propertiesList) {
if (ec)
{
std::cerr << "error fetching nvme volume " << ec << '\n';
// this interface isn't necessary
return;
}
const uint32_t* namespaceId = nullptr;
const size_t* lbaFormat = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList, "NamespaceId",
namespaceId, "LBAFormat", lbaFormat);
if (!success)
{
messages::internalError(asyncResp->res);
return;
}
auto& nvprop = asyncResp->res.jsonValue["NVMeNamespaceProperties"];
if (namespaceId != nullptr)
{
nvprop["NamespaceId"] =
std::string("0x") + intToHexString(*namespaceId, 8);
}
if (lbaFormat != nullptr)
{
auto& lbafprop = nvprop["LBAFormat"];
lbafprop["LBAFormatType"] =
std::string("LBAFormat") + std::to_string(*lbaFormat);
lbafprop["LBADataSizeBytes"] = blockSize;
// TODO: populate other lbaformat attributes, and metadata_at_end
}
});
}
inline void populateStorageVolume(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& volumeId, const std::string& connectionName,
const std::string& path, const dbus::utility::MapperServiceMap& ifaces)
{
asyncResp->res.jsonValue["@odata.type"] = "#Volume.v1_9_0.Volume";
auto url =
crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName,
"Storage", storageId, "Volumes", volumeId);
asyncResp->res.jsonValue["@odata.id"] = url;
// May be overridden by nvme
asyncResp->res.jsonValue["Name"] = std::string("Volume ") + volumeId;
asyncResp->res.jsonValue["Id"] = volumeId;
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getAllProperties(
connectionName, path, "xyz.openbmc_project.Inventory.Item.Volume",
context,
[asyncResp, connectionName, path, ifaces, volumeId](
const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& propertiesList) {
size_t volBlockSize = 0;
// this interface isn't necessary
if (!ec)
{
const uint64_t* size = nullptr;
const size_t* blockSize = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList, "Size", size,
"BlockSize", blockSize);
if (!success)
{
messages::internalError(asyncResp->res);
return;
}
auto& cap = asyncResp->res.jsonValue["Capacity"];
auto& capdata = cap["Data"];
if (size != nullptr)
{
capdata["ProvisionedBytes"] = *size;
}
// Capacity.Metadata or provisioned/allocated is not currently
// handled by OpenBMC
if (blockSize != nullptr)
{
asyncResp->res.jsonValue["BlockSizeBytes"] = *blockSize;
volBlockSize = *blockSize;
}
tryPopulateVolumeNvme(asyncResp, connectionName, path, ifaces,
volumeId, volBlockSize);
}
});
tryPopulateMetricCollection(asyncResp, path, ifaces, url);
auto findIfaces = std::find_if(
ifaces.begin(), ifaces.end(),
[connectionName](
const std::pair<std::string, std::vector<std::string>>& pair) {
return pair.first == connectionName;
});
if (findIfaces != ifaces.end())
{
storage_utils::tryGetLocation(asyncResp, connectionName, path,
storage_utils::volumeInterface,
findIfaces->second);
}
}
inline void
deleteStorageVolume(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId,
const std::string& connectionName,
const std::string& path)
{
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, storageId](const boost::system::error_code ec,
const sdbusplus::message_t& msg) {
// Failure returned from NVMe
const ::sd_bus_error* sd_err = msg.get_error();
if (sd_err != nullptr)
{
storageAddDbusError(asyncResp->res, "delete Volume NVMe", storageId,
sd_err->name, sd_err->message);
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "delete Volume dbus error " << ec;
messages::internalError(asyncResp->res);
return;
}
// success
asyncResp->res.result(boost::beast::http::status::no_content);
},
connectionName, path, "xyz.openbmc_project.Object.Delete", "Delete");
}
inline void findStorageVolume(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId, const std::string& volumeId,
const std::function<
void(const std::string& storagePath, const std::string& volumePath,
const std::string& connectionName,
const dbus::utility::MapperServiceMap& ifaces)>& cb)
{
storage_utils::findStorage(
asyncResp, storageId,
[asyncResp, storageId, volumeId,
cb](const sdbusplus::message::object_path& storagePath) {
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Volume"};
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getAssociatedSubTree(
storagePath / "containing",
sdbusplus::message::object_path("/xyz/openbmc_project/inventory"),
0, interfaces, context,
[asyncResp, storageId, volumeId, storagePath,
cb](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec || subtree.empty())
{
BMCWEB_LOG_DEBUG << "findStorageVolume error" << ec;
messages::resourceNotFound(asyncResp->res,
"#Volume.v1_9_0.Volume", volumeId);
return;
}
for (const auto& [path, interfaceDict] : subtree)
{
sdbusplus::message::object_path object(path);
std::string id = object.filename();
if (id.empty())
{
BMCWEB_LOG_ERROR << "Failed to find filename in " << path;
messages::resourceNotFound(
asyncResp->res, "#Volume.v1_9_0.Volume", volumeId);
return;
}
if (id != volumeId)
{
continue;
}
if (interfaceDict.size() != 1)
{
BMCWEB_LOG_ERROR << "Connection size "
<< interfaceDict.size()
<< ", greater than 1";
messages::internalError(asyncResp->res);
return;
}
const std::string& connectionName = interfaceDict.front().first;
cb(storagePath, path, connectionName, interfaceDict);
return;
}
BMCWEB_LOG_DEBUG << "findStorageVolume not found";
messages::resourceNotFound(asyncResp->res, "#Volume.v1_9_0.Volume",
volumeId);
});
});
}
inline void createVolumeSuccess(const std::shared_ptr<task::TaskData>& taskData,
const std::string& service,
const std::string& storageId,
const std::string& progressPath)
{
taskData->stopMonitor();
// Have to create a new Resp here as we cannot use the original resp or it never goes out of scope
// This resp will not have a strand associated with it which is okay because this will never be
// multithreaded
auto resp = std::make_shared<bmcweb::AsyncResp>(nullptr);
managedStore::ManagedObjectStoreContext context(resp);
redfish::storage_utils::getProperty<sdbusplus::message::object_path>(
service, progressPath,
"xyz.openbmc_project.Nvme.CreateVolumeProgressSuccess", "VolumePath", context,
[taskData, storageId,
progressPath](const boost::system::error_code& ec,
const sdbusplus::message::object_path& volumePath) {
if (ec)
{
BMCWEB_LOG_DEBUG << "createVolumeSuccess volumepath error " << ec;
taskData->messages.emplace_back(messages::internalError());
taskData->state = "Exception";
taskData->complete(
nlohmann::json(),
boost::beast::http::status::internal_server_error);
return;
}
auto resp = std::make_shared<bmcweb::AsyncResp>(nullptr);
resp->res.setCompleteRequestHandler([taskData](crow::Response& res) {
if (res.result() == boost::beast::http::status::ok)
{
taskData->messages.emplace_back(messages::created());
taskData->state = "Completed";
taskData->complete(std::move(res.jsonValue),
boost::beast::http::status::created);
}
else
{
BMCWEB_LOG_DEBUG << "createVolumeSuccess error populating: "
<< res.result();
BMCWEB_LOG_DEBUG << res.jsonValue;
taskData->messages.emplace_back(messages::internalError());
taskData->state = "Exception";
taskData->complete(
nlohmann::json(),
boost::beast::http::status::internal_server_error);
}
});
auto volumeId = volumePath.filename();
findStorageVolume(
resp, storageId, volumeId,
[resp, storageId,
volumeId](const std::string& sPath, const std::string& vPath,
const std::string& connectionName,
const dbus::utility::MapperServiceMap& ifaces) {
BMCWEB_LOG_DEBUG << "createVolumeSuccess connectionName is "
<< connectionName;
storage_utils::getSystemPathFromStorage(
resp, sPath,
[resp, storageId, volumeId, connectionName, vPath,
ifaces](std::optional<std::string_view> systemPath) {
std::string systemName =
systemPath ? std::filesystem::path(*systemPath).filename()
: "system";
populateStorageVolume(resp, systemName, storageId, volumeId,
connectionName, vPath, ifaces);
});
// on completion completeRequestHandler above will copy the response
// to taskData
});
});
}
inline void createVolumeFailure(const std::shared_ptr<task::TaskData>& taskData,
const std::string& service,
const std::string& storageId,
const std::string& progressPath)
{
taskData->stopMonitor();
// Have to create a new Resp here as we cannot use the original resp or it never goes out of scope
// This resp will not have a strand associated with it which is okay because this will never be
// multithreaded
auto resp = std::make_shared<bmcweb::AsyncResp>(nullptr);
managedStore::ManagedObjectStoreContext context(resp);
redfish::storage_utils::getAllProperties(
service, progressPath,
"xyz.openbmc_project.Nvme.CreateVolumeProgressFailure", context,
[taskData,
storageId](const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& props) {
if (ec)
{
BMCWEB_LOG_DEBUG << "createVolumeSuccess volumepath error " << ec;
taskData->messages.emplace_back(messages::internalError());
taskData->state = "Exception";
taskData->complete(
nlohmann::json(),
boost::beast::http::status::internal_server_error);
return;
}
std::string errorName;
std::string errorDesc;
sdbusplus::unpackPropertiesNoThrow(dbus_utils::UnpackErrorPrinter(),
props, "ErrorName", errorName,
"ErrorDescription", errorDesc);
bmcweb::AsyncResp resp(nullptr);
storageAddDbusError(resp.res, "createVolumeFailure", storageId,
errorName, errorDesc);
for (auto& m : resp.res.jsonValue["error"][messages::messageAnnotation])
{
taskData->messages.emplace_back(m);
}
taskData->state = "Exception";
taskData->complete(std::move(resp.res.jsonValue), resp.res.result());
});
}
// Handles the Status property of Common.Progress interface
inline void createVolumeTaskUpdate(const std::string& status,
const std::shared_ptr<task::TaskData>& taskData,
const std::string& service,
const std::string& storageId,
const std::string& progressPath)
{
if (status ==
"xyz.openbmc_project.Common.Progress.OperationStatus.InProgress")
{
// nothing to do
}
else if (status ==
"xyz.openbmc_project.Common.Progress.OperationStatus.Completed")
{
createVolumeSuccess(taskData, service, storageId, progressPath);
}
else if (status ==
"xyz.openbmc_project.Common.Progress.OperationStatus.Failed" ||
status ==
"xyz.openbmc_project.Common.Progress.OperationStatus.Aborted")
{
createVolumeFailure(taskData, service, storageId, progressPath);
}
else
{
BMCWEB_LOG_DEBUG << "updateCreateVolumeTask unexpected state "
<< status;
}
}
// Handler called by TaskData on Commmon.Progress property change
inline bool createVolumeTaskHandler(sdbusplus::message_t& msg,
const std::shared_ptr<task::TaskData>& taskData,
const std::string& service,
const std::string& storageId,
const std::string& progressPath)
{
dbus::utility::DBusPropertiesMap props;
std::string iface;
msg.read(iface, props);
if (iface != "xyz.openbmc_project.Common.Progress")
{
BMCWEB_LOG_DEBUG << "updateCreateVolumeTask wrong interface";
return !task::completed;
}
std::optional<std::string> status;
sdbusplus::unpackPropertiesNoThrow(dbus_utils::UnpackErrorPrinter(), props,
"Status", status);
if (!status)
{
BMCWEB_LOG_DEBUG << "updateCreateVolumeTask not status update";
return !task::completed;
}
createVolumeTaskUpdate(*status, taskData, service, storageId, progressPath);
// completion is handled asynchronously so always return !completed
return !task::completed;
}
inline void
createStorageVolume(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storagePath,
const std::string& storageService, uint64_t size,
size_t lbaIndex, bool metadataAtEnd)
{
auto storageId = sdbusplus::message::object_path(storagePath).filename();
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[req, asyncResp, storageId, storageService](
const boost::system::error_code ec, const sdbusplus::message_t& msg,
const sdbusplus::message::object_path& progressPath) {
const ::sd_bus_error* sd_err = msg.get_error();
if (sd_err != nullptr)
{
storageAddDbusError(asyncResp->res, "create Volume NVMe", storageId,
sd_err->name, sd_err->message);
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "create Volume dbus error " << ec;
messages::internalError(asyncResp->res);
return;
}
// success
BMCWEB_LOG_DEBUG << "create volume success, progress path "
<< progressPath.str;
std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
[storageService, storageId,
progressPath](const boost::system::error_code& err,
sdbusplus::message_t& taskMsg,
const std::shared_ptr<task::TaskData>& taskData) {
if (err)
{
// Internal error in property signal callback?
BMCWEB_LOG_ERROR << progressPath.str << ": Error in task";
taskData->messages.emplace_back(messages::internalError());
taskData->state = "Cancelled";
return task::completed;
}
return createVolumeTaskHandler(taskMsg, taskData, storageService,
storageId, progressPath);
},
"type='signal',interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',arg0='xyz.openbmc_project.Common.Progress',"
"path='" +
progressPath.str + "'");
task->startTimer(std::chrono::minutes(60));
task->populateResp(asyncResp->res);
task->payload.emplace(req);
// Progress may have completed prior to Task watching for signals, so
// poll Status once.
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getProperty<std::string>(
storageService, progressPath, "xyz.openbmc_project.Common.Progress",
"Status", context,
[task, storageService, storageId,
progressPath](const boost::system::error_code& ec2,
const std::string& status) {
if (ec2)
{
BMCWEB_LOG_ERROR << "createVolume poll error: "
<< ec2.message();
return;
}
createVolumeTaskUpdate(status, task, storageService, storageId,
progressPath);
});
},
storageService, storagePath, "xyz.openbmc_project.Nvme.Storage",
"CreateVolume", size, lbaIndex, metadataAtEnd);
}
inline void populateStorageVolumeCollection(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const boost::system::error_code& ec, const std::string& systemName,
const std::string& storageId,
const dbus::utility::MapperGetSubTreePathsResponse& volumeList)
{
nlohmann::json::array_t members;
if (ec || volumeList.empty())
{
asyncResp->res.jsonValue["Members"] = std::move(members);
asyncResp->res.jsonValue["Members@odata.count"] = 0;
BMCWEB_LOG_DEBUG << "Failed to find any storage Volumes";
return;
}
for (const std::string& path : volumeList)
{
std::string id = sdbusplus::message::object_path(path).filename();
if (id.empty())
{
BMCWEB_LOG_ERROR << "Failed to find filename in " << path;
return;
}
nlohmann::json::object_t member;
member["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName,
"Storage", storageId, "Volumes", id);
members.emplace_back(member);
}
asyncResp->res.jsonValue["Members@odata.count"] = members.size();
asyncResp->res.jsonValue["Members"] = std::move(members);
}
inline void
storageVolumeHandler(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName,
const std::string& storageId,
const std::string& volumeId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
BMCWEB_LOG_DEBUG << "Failed to setup Redfish Route for StorageVolume";
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
findStorageVolume(
asyncResp, storageId, volumeId,
[asyncResp, systemName, storageId,
volumeId](const std::string& /* sPath */, const std::string& vPath,
const std::string& connectionName,
const dbus::utility::MapperServiceMap& ifaces) {
populateStorageVolume(asyncResp, systemName, storageId, volumeId,
connectionName, vPath, ifaces);
});
}
inline std::optional<size_t> parseLbaFormatType(std::string_view ty)
{
// expects LBAFormat0, LBAFormat1 etc
if (!ty.starts_with("LBAFormat"))
{
BMCWEB_LOG_DEBUG << "wrong start";
return std::nullopt;
}
ty.remove_prefix(std::min(ty.size(), strlen("LBAFormat")));
size_t v = 0;
auto e = std::from_chars(ty.data(), ty.data() + ty.size(), v);
if (e.ptr != ty.data() + ty.size() || e.ec != std::errc())
{
return std::nullopt;
}
return v;
}
inline void storageVolumeCreateHandler(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
BMCWEB_LOG_DEBUG << "Failed to setup Redfish Route for StorageVolume";
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
uint64_t size = 0;
std::string lbaFormat;
// allow to default, non-metadata formats ignore the parameter
std::optional<bool> metadataAtEnd = false;
std::optional<std::string> name;
if (!json_util::readJsonAction(
req, asyncResp->res, "Name", name, "Capacity/Data/ProvisionedBytes",
size, "NVMeNamespaceProperties/LBAFormat/LBAFormatType", lbaFormat,
"NVMeNamespaceProperties/LBAFormat/MetadataTransferredAtEndOfDataLBA",
metadataAtEnd))
{
BMCWEB_LOG_DEBUG << "create volume json input failed";
return;
}
std::optional<size_t> lbaIndex = parseLbaFormatType(lbaFormat);
if (!lbaIndex)
{
BMCWEB_LOG_DEBUG << "Bad parsing lbaFormatType";
messages::propertyValueNotInList(
asyncResp->res, lbaFormat,
"NVMeNamespaceProperties.LBAFormat.LBAFormatType");
return;
}
storage_utils::findStorageCallbackWithService(
asyncResp, storageId,
[req, asyncResp, size, lbaIndex,
metadataAtEnd](const sdbusplus::message::object_path& storagePath,
const std::string& storageService) {
createStorageVolume(req, asyncResp, storagePath, storageService, size,
*lbaIndex, *metadataAtEnd);
});
}
inline void storageVolumeDeleteHandler(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& volumeId)
{
BMCWEB_LOG_DEBUG << "delete handler vol " << volumeId;
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
BMCWEB_LOG_DEBUG << "Failed to setup Redfish Route for StorageVolume";
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
findStorageVolume(
asyncResp, storageId, volumeId,
[asyncResp, storageId,
volumeId](const std::string& /* sPath */, const std::string& vPath,
const std::string& connectionName,
const dbus::utility::MapperServiceMap& ifaces) {
(void)ifaces;
deleteStorageVolume(asyncResp, storageId, connectionName, vPath);
});
}
inline void storageVolumeCollectionHandler(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
BMCWEB_LOG_DEBUG
<< "Failed to setup Redfish Route for StorageVolume Collection";
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
storage_utils::findStorage(
asyncResp, storageId,
[asyncResp, systemName,
storageId](const sdbusplus::message::object_path& storagePath) {
asyncResp->res.jsonValue["@odata.type"] =
"#VolumeCollection.VolumeCollection";
asyncResp->res.jsonValue["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName,
"Storage", storageId, "Volumes");
asyncResp->res.jsonValue["Name"] = "Storage Volume Collection";
storageVolumes(asyncResp, storagePath,
[asyncResp, systemName, storageId](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse&
volumeList) {
populateStorageVolumeCollection(asyncResp, ec, systemName,
storageId, volumeList);
});
});
}
inline std::string lookupRelativePerformance(const std::string& rp)
{
if (rp == "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Best")
{
return "Best";
}
if (rp ==
"xyz.openbmc_project.Nvme.Storage.RelativePerformance.Better")
{
return "Better";
}
if (rp == "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Good")
{
return "Good";
}
return "Degraded";
}
inline void storageVolumeCapabilitiesHandler(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
BMCWEB_LOG_DEBUG
<< "Failed to setup Redfish Route for StorageVolume Capabilities";
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
storage_utils::findStorageCallbackWithService(
asyncResp, storageId,
[asyncResp, systemName,
storageId](const sdbusplus::message::object_path& storagePath,
const std::string& service) {
managedStore::ManagedObjectStoreContext context(asyncResp);
redfish::storage_utils::getProperty<
std::vector<std::tuple<size_t, size_t, size_t, std::string>>>(
service, storagePath, "xyz.openbmc_project.Nvme.Storage",
"SupportedFormats", context,
[asyncResp, systemName, storageId](
const boost::system::error_code& ec,
const std::vector<
std::tuple<size_t, size_t, size_t, std::string>>& formats) {
asyncResp->res.jsonValue["@odata.type"] = "#Volume.v1_9_0.Volume";
auto url = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName, "Storage", storageId,
"Volumes", "Capabilities");
asyncResp->res.jsonValue["@odata.id"] = url;
asyncResp->res.jsonValue["Id"] = "Capabilities";
asyncResp->res.jsonValue["Name"] = "Capabilities for Volumes";
auto& nv = asyncResp->res.jsonValue["NVMeNamespaceProperties"];
auto& allowable =
nv["LBAFormatsSupported@Redfish.AllowableValues"] =
nlohmann::json::array_t();
auto& formatDesc = nv["LBAFormats"] = nlohmann::json::array_t();
if (ec)
{
BMCWEB_LOG_WARNING
<< "Failed to get SupportedFormats property for "
<< storageId << " : " << ec.message();
return;
}
for (const auto& [index, blockSize, metadataSize, relPerf] : formats)
{
auto name = std::string("LBAFormat") + std::to_string(index);
allowable.emplace_back(name);
auto& f = formatDesc.emplace_back(nlohmann::json::object_t());
auto rp = lookupRelativePerformance(relPerf);
f["LBAFormatType"] = name;
f["RelativePerformance"] = rp;
f["LBADataSizeBytes"] = blockSize;
f["LBAMetadataSizeBytes"] = metadataSize;
}
});
});
}
inline void storageControllerHandler(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& storageId,
const std::string& controllerId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
BMCWEB_LOG_DEBUG
<< "Failed to setup Redfish Route for StorageController";
return;
}
checkSystemAndStorage(asyncResp, systemName, storageId);
storage_utils::findStorageAndController(
asyncResp, storageId, controllerId,
[asyncResp, storageId, systemName,
controllerId](const sdbusplus::message::object_path& path,
const dbus::utility::MapperServiceMap& ifaces) {
populateStorageController(asyncResp, systemName, storageId,
controllerId, path, ifaces);
});
}
inline void requestRoutesStorageControllerCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Controllers/")
.privileges(redfish::privileges::getStorageControllerCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(storageControllerCollectionHandler, std::ref(app)));
}
inline void requestRoutesStorageController(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>")
.privileges(redfish::privileges::getStorageController)
.methods(boost::beast::http::verb::get)(
std::bind_front(storageControllerHandler, std::ref(app)));
}
inline void requestRoutesStorageVolumeCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/")
.privileges(redfish::privileges::getVolumeCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(storageVolumeCollectionHandler, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/")
.privileges(redfish::privileges::postVolumeCollection)
.methods(boost::beast::http::verb::post)(
std::bind_front(storageVolumeCreateHandler, std::ref(app)));
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/Storage/<str>/Volumes/Capabilities")
.privileges(redfish::privileges::getVolumeCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(storageVolumeCapabilitiesHandler, std::ref(app)));
}
inline void requestRoutesStorageVolume(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/<str>")
.privileges(redfish::privileges::getVolume)
.methods(boost::beast::http::verb::get)(
std::bind_front(storageVolumeHandler, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/<str>")
.privileges(redfish::privileges::deleteVolume)
.methods(boost::beast::http::verb::delete_)(
std::bind_front(storageVolumeDeleteHandler, std::ref(app)));
}
inline void requestRoutesStorageControllerMetric(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/Storage/<str>/Controllers/<str>/Oem/Google/Metrics/<str>")
.privileges(redfish::privileges::privilegeSetLogin)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& system, const std::string& storage,
const std::string& cntrl, const std::string& metric) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", system, "Storage", storage,
"Controllers", cntrl, "Oem", "Google", "Metrics", metric);
asyncResp->res.jsonValue["Name"] = metric;
// TODO: verify system and storage relation
nvmeControllerMetricFetcher(asyncResp, storage, cntrl, metric);
});
}
void handleStorageVolumeMetricGet(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& system, const std::string& storage,
const std::string& volume, const std::string& metric)
{
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", system, "Storage", storage,
"Volumes", volume, "Oem", "Google", "Metrics", metric);
asyncResp->res.jsonValue["Name"] = metric;
checkSystemAndStorage(asyncResp, system, storage);
findStorageVolume(
asyncResp, storage, volume,
[asyncResp, metric](const std::string& /* sPath */,
const std::string& vPath,
const std::string& /* connectionName */,
const dbus::utility::MapperServiceMap& ifaces) {
nvmeMetricFetcher(asyncResp, vPath, ifaces, metric, {});
});
}
inline void requestRoutesStorageVolumeMetric(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/Storage/<str>/Volumes/<str>/Oem/Google/Metrics/<str>")
.privileges(redfish::privileges::privilegeSetLogin)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& system, const std::string& storage,
const std::string& volume, const std::string& metric) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
handleStorageVolumeMetricGet(asyncResp, system, storage, volume,
metric);
});
}
} // namespace redfish