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