blob: 41fe09e8f39904aafe3d1075d19fb9646b000379 [file] [log] [blame]
/*
// Copyright (c) 2023 Google LLC
//
// 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 "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "location_utils.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include <format>
#include <array>
#include <span>
#include <string_view>
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
#ifndef GBMCWEB_COMPILATION
#include <bmcweb_plugins/dbus_utils.hpp>
#else
#include "utils/dbus_utils.hpp"
#endif
namespace redfish
{
namespace storage_utils
{
constexpr const char chassisInterface[] =
"xyz.openbmc_project.Inventory.Item.Board";
constexpr const char storageInterface[] =
"xyz.openbmc_project.Inventory.Item.Storage";
constexpr const char driveInterface[] =
"xyz.openbmc_project.Inventory.Item.Drive";
constexpr const char controllerInterface[] =
"xyz.openbmc_project.Inventory.Item.StorageController";
constexpr const char volumeInterface[] =
"xyz.openbmc_project.Inventory.Item.Volume";
// TODO(matt): could move to dbus_utility.hpp
inline std::optional<std::string>
matchServiceName(const dbus::utility::MapperServiceMap& allServices,
const std::string& matchIface)
{
int found = 0;
std::string matchService;
for (const auto& [service, interfaces] : allServices)
{
for (const auto& interface : interfaces)
{
if (interface == matchIface)
{
matchService = service;
found++;
}
}
}
if (found == 1)
{
return matchService;
}
if (found > 1)
{
BMCWEB_LOG_DEBUG << "Failed, multiple service names matched for "
<< matchIface;
}
return {};
}
// Uncached DBus operations with ManageStore
inline void getDbusObject(
const std::string& path, std::span<const std::string_view> interfaces,
const managedStore::ManagedObjectStoreContext& context,
std::function<void(const boost::system::error_code&,
const dbus::utility::MapperGetObject&)>&& callback)
{
// Freshness of data dosent matter in unit tests.
#ifdef UNIT_TEST_BUILD
dbus_utils::getDbusObject(path, interfaces, context, std::move(callback));
#else
std::vector<std::string> interfaces2;
for (const auto& s : interfaces)
{
interfaces2.emplace_back(s);
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
context.GetStrand(),
[callback{std::move(callback)}](
const boost::system::error_code& ec,
const dbus::utility::MapperGetObject& subtree) {
callback(ec, subtree);
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetObject", path, interfaces2);
#endif
}
inline void getAssociatedSubTree(
const sdbusplus::message::object_path& associatedPath,
const sdbusplus::message::object_path& path, int32_t depth,
std::span<const std::string_view> interfaces,
const managedStore::ManagedObjectStoreContext& context,
std::function<void(const boost::system::error_code&,
const dbus::utility::MapperGetSubTreeResponse&)>&&
callback)
{
// Freshness of data dosent matter in unit tests.
#ifdef UNIT_TEST_BUILD
dbus_utils::getAssociatedSubTree(associatedPath, path, depth, interfaces, context, std::move(callback));
#else
std::vector<std::string> interfaces2;
for (const auto& s : interfaces) {
interfaces2.emplace_back(s);
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
context.GetStrand(),
[callback{std::move(callback)}](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
callback(ec, subtree);
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTree",
associatedPath, path, depth, interfaces2);
#endif
}
inline void getAssociatedSubTreePaths(
const sdbusplus::message::object_path& associatedPath,
const sdbusplus::message::object_path& path, int32_t depth,
std::span<const std::string_view> interfaces,
const managedStore::ManagedObjectStoreContext& context,
std::function<void(const boost::system::error_code&,
const dbus::utility::MapperGetSubTreePathsResponse&)>&&
callback)
{
// Freshness of data dosent matter in unit tests.
#ifdef UNIT_TEST_BUILD
dbus_utils::getAssociatedSubTreePaths(associatedPath, path, depth, interfaces, context, std::move(callback));
#else
std::vector<std::string> interfaces2;
for (const auto& s : interfaces) {
interfaces2.emplace_back(s);
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
context.GetStrand(),
[callback{std::move(callback)}](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
callback(ec, subtreePaths);
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTreePaths",
associatedPath, path, depth, interfaces2);
#endif
}
template <typename PropertyType>
inline void getProperty(const std::string& service,
const sdbusplus::message::object_path& path,
const std::string& interface, const std::string& property,
const managedStore::ManagedObjectStoreContext& context,
std::function<void(const boost::system::error_code&,
const PropertyType&)>&& callback)
{
// Freshness of data dosent matter in unit tests.
#ifdef UNIT_TEST_BUILD
dbus_utils::getProperty<PropertyType>(service, path, interface, property, context, std::move(callback));
#else
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
context.GetStrand(),
[handler = std::move(callback)](
boost::system::error_code ec,
std::variant<std::monostate, PropertyType>& ret) mutable {
if (ec)
{
handler(ec, {});
return;
}
if (PropertyType* value = std::get_if<PropertyType>(&ret))
{
handler(ec, std::move(*value));
return;
}
handler(boost::system::errc::make_error_code(
boost::system::errc::invalid_argument),
{});
},
service, path.str, "org.freedesktop.DBus.Properties", "Get", interface,
property);
#endif
}
inline void getProperty(
const std::string& service, const sdbusplus::message::object_path& path,
const std::string& interface, const std::string& property,
const managedStore::ManagedObjectStoreContext& context,
std::function<void(const boost::system::error_code&,
const dbus::utility::DbusVariantType&)>&& callback)
{
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
context.GetStrand(),
[callback{std::move(callback)}](
const boost::system::error_code& ec,
const dbus::utility::DbusVariantType& propertyValue) {
callback(ec, propertyValue);
},
service, path, "org.freedesktop.DBus.Properties", "Get", interface,
property);
}
inline void getAllProperties(
const std::string& service, const sdbusplus::message::object_path& path,
const std::string& interface,
const managedStore::ManagedObjectStoreContext& context,
std::function<void(const boost::system::error_code&,
const dbus::utility::DBusPropertiesMap&)>&& callback)
{
#ifdef UNIT_TEST_BUILD
managedStore::GetManagedObjectStore()->getAllProperties(
service, path, interface, context, std::move(callback));
#else
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
context.GetStrand(),
std::move(callback), service, path, "org.freedesktop.DBus.Properties",
"GetAll", interface);
#endif
}
/**
* @brief Finds a Storage and runs a callback
* @param asyncResp Pointer to object holding response data
* @param storageId ID (D-Bus filename) of the Storage
* @param cb Callback function to call with the Storage path
*
*/
inline void findStorageCallbackWithService(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId,
const std::function<void(const sdbusplus::message::object_path& storagePath,
const std::string& service)>&
cb)
{
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Storage"};
// mapper call chassis
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/inventory", 0, interfaces, requestContext,
[asyncResp, storageId,
cb](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& storageList) {
if (ec && ec.value() != EBADR)
{
BMCWEB_LOG_ERROR << "findStorage DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
auto storage = std::find_if(storageList.begin(), storageList.end(),
[&storageId](auto& entry) {
const std::string& path = entry.first;
return sdbusplus::message::object_path(path).filename() ==
storageId;
});
if (storage == storageList.end())
{
BMCWEB_LOG_DEBUG << "findStorage couldn't find " << storageId;
return;
}
const std::string& storagePath = storage->first;
const auto& serviceMap = storage->second;
if (serviceMap.size() != 1)
{
BMCWEB_LOG_DEBUG << "findStorage multiple services for storage";
messages::resourceNotFound(asyncResp->res,
"#Storage.v1_13_0.Storage", storageId);
}
const std::string& serviceName = serviceMap.front().first;
cb(sdbusplus::message::object_path(storagePath), serviceName);
});
}
inline void findStorage(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId,
const std::function<void(const sdbusplus::message::object_path& storagePath)>& cb)
{
findStorageCallbackWithService(
asyncResp, storageId,
[cb](const sdbusplus::message::object_path& storagePath,
const std::string& service) {
(void)service;
cb(storagePath);
});
}
/**
* @brief Finds the Storage Controllers associated with a Storage path
* @param asyncResp Pointer to object holding response data
* @param storagePath D-Bus path of the Storage
* @param cb Callback function that gets the D-Bus paths of the
* Storage Controllers
*
* Get a list of all Storage Controllers associated with a given Storage. If the
* D-Bus call fails, set an internal error response.
*/
inline void findControllersForStorage(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const sdbusplus::message::object_path& storagePath,
const std::function<
void(const dbus::utility::MapperGetSubTreeResponse& controllerList)>&
cb)
{
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.StorageController"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getAssociatedSubTree(
storagePath / "storage_controller",
sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
interfaces, requestContext,
[asyncResp, storagePath,
cb](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& controllerList) {
if (ec && ec.value() != EBADR)
{
BMCWEB_LOG_ERROR
<< "Failed to get controllers associated with storage "
<< std::string(storagePath) << ": " << ec;
messages::internalError(asyncResp->res);
return;
}
cb(controllerList);
});
}
/**
* @brief Finds a Storage Controller's Storage path
* @param asyncResp Pointer to object holding response data
* @param storageId ID (D-Bus filename) of the Storage
* @param controllerPath D-Bus path to the Storage Controller
* @param cb Callback function that gets the D-Bus path of the
* primary (canonical) Storage for the Storage Controller
*
* Ensure that the Storage Controller is associated with the Storage and call
* cb() with the canonical path for the Controller's Storage, or nullopt if not
* found
*/
inline void findStorageForController(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId,
const sdbusplus::message::object_path& controllerPath,
const std::function<void(
const std::optional<sdbusplus::message::object_path>& canonStoragePath)>&
cb)
{
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Storage"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths(
controllerPath / "storage",
sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
interfaces, requestContext,
[asyncResp, controllerPath, storageId,
cb](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse& storageList) {
if (ec && ec.value() != EBADR)
{
BMCWEB_LOG_ERROR
<< "Failed to get storages associated with controller "
<< std::string(controllerPath) << ": " << ec;
messages::internalError(asyncResp->res);
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())
{
cb(std::nullopt);
}
else
{
std::vector<std::string> paths(storageList);
std::sort(paths.begin(), paths.end());
cb(paths[0]);
}
});
}
/**
* @brief Verifies that a Storage Controller has a specific canonical Storage
* @param asyncResp Pointer to object holding response data
* @param storageId ID (D-Bus filename) of the Storage
* @param controllerPath D-Bus path to the Storage Controller
* @param cb Callback function to run next
*
* Set a "resource not found" error if we can't find the Storage Controller on
* the given Storage or if it shouldn't be accessed through the Storage
*/
inline void verifyStorageForController(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId,
const sdbusplus::message::object_path& controllerPath,
const std::function<void()>& cb)
{
findStorageForController(
asyncResp, storageId, controllerPath,
[asyncResp, storageId, controllerPath,
cb](const std::optional<sdbusplus::message::object_path>&
canonStoragePath) {
if (!canonStoragePath)
{
BMCWEB_LOG_ERROR << "couldn't find Storage Controller "
<< std::string(controllerPath) << " on Storage "
<< storageId;
messages::resourceNotFound(
asyncResp->res, "#StorageController.v1_6_0.StorageController",
controllerPath.filename());
return;
}
if ((*canonStoragePath).filename() != storageId)
{
BMCWEB_LOG_ERROR
<< "Storage Controller " << std::string(controllerPath)
<< " must be accessed through Storage "
<< (*canonStoragePath).filename() << " not through "
<< storageId;
messages::resourceNotFound(
asyncResp->res, "#StorageController.v1_6_0.StorageController",
controllerPath.filename());
return;
}
cb();
});
}
/**
* @brief Finds both a Storage and Storage Controller and makes sure it can be
* accessed through this Storage
* @param asyncResp Pointer to object holding response data
* @param storageId ID (D-Bus filename) of the Storage
* @param controllerId ID (D-Bus filename) of the Storage Controller
* @param cb Callback function to call if the access is valid
*
* Convenience function that finds both a Storage and Storage Controller at the
* same time and passes the Controller's path and interfaces to a callback. Sets
* an error response if the Controller can't be accessed through this Storage
*/
inline void findStorageAndController(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storageId, const std::string& controllerId,
const std::function<void(const sdbusplus::message::object_path& path,
const dbus::utility::MapperServiceMap& ifaces)>&
cb)
{
// TODO: (b/376349303) Potential memory leak
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
auto chooseController =
[asyncResp, storageId, controllerId,
cb](const dbus::utility::MapperGetSubTreeResponse& controllerList) mutable {
auto controller = std::find_if(
controllerList.begin(), controllerList.end(),
[&controllerId](
const std::pair<std::string, dbus::utility::MapperServiceMap>&
object) mutable {
return sdbusplus::message::object_path(object.first).filename() ==
controllerId;
});
if (controller == controllerList.end())
{
BMCWEB_LOG_ERROR << "Couldn't find controller " << controllerId;
messages::resourceNotFound(
asyncResp->res, "#StorageController.v1_6_0.StorageController",
controllerId);
return;
}
verifyStorageForController(
asyncResp, storageId, controller->first,
[first{controller->first}, second{controller->second}, cb] () {
cb(first, second);
});
};
// first find the storage, then find the controllers associated with that
// storage, then choose the correct controller
findStorage(asyncResp, storageId,
[asyncResp, chooseController{std::move(chooseController)}] (const sdbusplus::message::object_path& storagePath) mutable {
findControllersForStorage(asyncResp, storagePath, chooseController);
});
}
// find target system dbus path from system name, return nullopt_t if no system
// interface found
inline void getSystemPathFromName(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName,
std::function<void(std::optional<std::string_view> systemPath)>&& cb)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTreePaths(
"/xyz/openbmc_project/inventory", 0,
std::array<std::string_view, 1>{
{"xyz.openbmc_project.Inventory.Item.System"}},
requestContext,
[asyncResp, systemName, cb{std::move(cb)}](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse& resp) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBus system interface enumerate error";
messages::resourceNotFound(asyncResp->res,
"#ComputerSystem.v1_16_0.ComputerSystem",
systemName);
return;
}
auto itr = std::find_if(resp.begin(), resp.end(),
[systemName](const std::string& pathStr) {
return std::filesystem::path(pathStr).filename() == systemName;
});
if (itr == resp.end())
{
cb({});
return;
}
cb({*itr});
});
}
// find System DBus path from Storage DBus path
inline void getSystemPathFromStorage(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& storagePath,
std::function<void(std::optional<std::string_view> systemPath)>&& cb)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths(
storagePath + "/contained_by", {"/xyz/openbmc_project/inventory"}, 0,
std::array<std::string_view, 1>{
{"xyz.openbmc_project.Inventory.Item.System"}},
requestContext,
[asyncResp, storagePath, cb{std::move(cb)}](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse& resp) {
if (ec)
{
BMCWEB_LOG_DEBUG
<< "Fail to find the associated System for Storage";
messages::internalError(asyncResp->res);
return;
}
// Storage sharing (across System) is not supported
if (resp.size() > 1)
{
BMCWEB_LOG_DEBUG << "Incorrect number of System found for Storage";
messages::internalError(asyncResp->res);
return;
}
if (resp.empty())
{
cb({});
return;
}
cb({resp[0]});
});
}
/**
* @brief: read the LocationType and ServiceLabel from DBus with give path.
*
* @param asyncResp Redfish asyncResp. Error result will be set upon DBus error.
* @param path Target DBus object path
* @param cb callback function to handle the results. The results can be empty
* if no such interface on Dbus.
*/
inline void readLocationFromDbus(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path,
std::function<void(std::optional<const std::string> locationType,
std::optional<const std::string> serviceLabel)>&& cb)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
// Read the Connector interface from Dbus.
// We assume the LocationCode and Connector are provided by the same service
getDbusObject(path, {}, requestContext,
[asyncResp, path, cb{std::move(cb)}](
const boost::system::error_code& ec,
const dbus::utility::MapperGetObject& objects) {
if (ec)
{
BMCWEB_LOG_ERROR << std::format(
"failed to search for interfaces for {}, reason: {}", path,
ec.what());
messages::internalError(asyncResp->res);
cb({}, {});
return;
}
std::optional<std::string> locationType{};
std::optional<std::string> serviceLabelSevice{};
for (const auto& [service, ifaces] : objects)
{
for (const auto& iface : ifaces)
{
/* find location type interface*/
if (!locationType)
{
locationType =
redfish::location_util::getLocationType(iface);
}
else if (redfish::location_util::isConnector(iface))
{
BMCWEB_LOG_ERROR
<< "there are multiple location type interface under path: "
<< path;
messages::internalError(asyncResp->res);
cb({}, {});
return;
}
/* find service label interface */
if (iface ==
"xyz.openbmc_project.Inventory.Decorator.LocationCode")
{
if (serviceLabelSevice)
{
BMCWEB_LOG_ERROR
<< "there are multiple location code interface under path: "
<< path;
messages::internalError(asyncResp->res);
cb({}, {});
return;
}
serviceLabelSevice = service;
}
}
}
if (!serviceLabelSevice)
{
cb(locationType, {});
return;
}
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
getProperty<std::string>(
*serviceLabelSevice, path,
"xyz.openbmc_project.Inventory.Decorator.LocationCode",
"LocationCode", requestContext,
[asyncResp, path, cb{std::move(cb)},
locationType](const boost::system::error_code& ec,
const std::string& property) {
if (ec)
{
BMCWEB_LOG_ERROR << std::format(
"failed to read location code for {}, reason: {}", path,
ec.what());
messages::internalError(asyncResp->res);
cb({}, {});
return;
}
cb(locationType, {property});
});
});
}
/**
* @brief: try to find the topological parent for a storage resource. This
* function can be expanded to support more types of resources.
*
* @param asyncResp redfish async response. Note the function will not set error
* for the asyncResp. Instead, the error will pass to the callback as ec; and
* the callback should modify the response according by itself
* @param interface child's interface type defined by storage_utils.hpp
* @param path child's object path
* @param cb callback function to handle the search result. Empty string will be
* returned if no parent exists. ec will be set on dbus errors. otherwise return
* the parent object.
*/
inline void tryGetParent(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& interface, const std::string& path,
std::function<void(const boost::system::error_code& ec,
const std::string& service, const std::string& path,
const std::string& interface)>&& cb)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
// for everything inside Storage, it will find the parent drive via
// storage
if (interface == controllerInterface || interface == volumeInterface)
{
// volume uses a special backward tag as "contained"
std::string storageTag =
interface == volumeInterface ? "contained" : "storage";
getAssociatedSubTree(
sdbusplus::message::object_path(path) / storageTag,
{"/xyz/openbmc_project/inventory"}, 0, {{storageInterface}},
requestContext,
[asyncResp, cb{std::move(cb)},
interface](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& resp) {
if (ec)
{
cb(ec, {}, {}, {});
return;
}
// Root: the parent is empty.
if (resp.empty())
{
cb({}, {}, {}, {});
return;
}
// this is for a special topology of google ASIC NVMe device, whose
// two controllers share the same chassis
if (interface == controllerInterface && resp.size() == 2 &&
resp[0].second.size() == 1 && resp[1].second.size() == 1 &&
resp[0].second[0].first == resp[1].second[0].first)
{
auto storagePath = std::filesystem::path(resp[0].first);
cb({}, "xyz.openbmc_project.EntityManager",
storagePath.parent_path().c_str(),
storage_utils::chassisInterface);
return;
}
if (resp.size() != 1)
{
cb(boost::system::errc::make_error_code(
boost::system::errc::bad_message),
{}, {}, {});
return;
}
auto storagePath = resp[0].first;
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
getAssociatedSubTree(
sdbusplus::message::object_path(storagePath) / "drive",
{"/xyz/openbmc_project/inventory"}, 0, {{driveInterface}},
requestContext,
[asyncResp, cb{std::move(cb)}](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& resp) {
if (ec)
{
cb(ec, {}, {}, {});
return;
}
// Root: the parent is empty.
if (resp.empty())
{
cb({}, {}, {}, {});
return;
}
// Currently the Devpath cannot support RAID, which
// multiple Drive mapps to a single Storage.
if (resp.size() != 1 || resp[0].second.size() != 1)
{
cb(boost::system::errc::make_error_code(
boost::system::errc::bad_message),
{}, {}, {});
return;
}
auto drivePath = resp[0].first;
auto driveService = resp[0].second[0].first;
cb({}, driveService, drivePath, driveInterface);
});
});
return;
}
// for everthing above (and including) Drive, find its direct parent
std::string parentTag;
std::string parentInterface;
if (interface == chassisInterface)
{
parentTag = "contained_by";
parentInterface = chassisInterface;
}
else if (interface == driveInterface)
{
parentTag = "chassis";
parentInterface = chassisInterface;
}
else
{
cb(boost::system::errc::make_error_code(
boost::system::errc::invalid_argument),
{}, {}, {});
return;
}
getAssociatedSubTree(
sdbusplus::message::object_path(path) / parentTag,
{"/xyz/openbmc_project/inventory"}, 0, {{parentInterface}},
requestContext,
[asyncResp, cb{std::move(cb)},
parentInterface](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& resp) {
if (ec)
{
cb(ec, {}, {}, {});
return;
}
// Root: the parent is empty.
if (resp.empty())
{
cb({}, {}, {}, {});
return;
}
if (resp.size() != 1 || resp[0].second.size() != 1)
{
cb(boost::system::errc::make_error_code(
boost::system::errc::bad_message),
{}, {}, {});
return;
}
auto parentPath = resp[0].first;
auto parentService = resp[0].second[0].first;
cb({}, parentService, parentPath, parentInterface);
});
}
inline std::string composeDevpath(const std::string& parentDevpath,
const std::string& label,
bool embedded = false)
{
std::filesystem::path parent = parentDevpath.empty()
? std::filesystem::path("/phys")
: std::filesystem::path(parentDevpath);
if (embedded)
{
parent.replace_filename(parent.filename().string() +
":device:" + label);
}
else
{
parent /= label;
}
return parent.string();
}
inline void tryGetDevpath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path, const std::string& interface,
std::function<void(const std::string& devpath)>&& cb)
{
readLocationFromDbus(asyncResp, path,
[asyncResp, interface, path, cb{std::move(cb)}](
std::optional<const std::string> locationType,
std::optional<const std::string> serviceLabel) {
auto func = [asyncResp, interface, path, cb{std::move(cb)},
locationType,
serviceLabel](const std::string parentDevpath) {
if (locationType)
{
if (*locationType == "Embedded")
{
std::string nodeName =
serviceLabel
? *serviceLabel
: std::filesystem::path(path).filename().string();
cb(composeDevpath(parentDevpath, nodeName, true));
return;
}
if (serviceLabel)
{
cb(composeDevpath(parentDevpath, *serviceLabel, false));
return;
}
}
cb(parentDevpath);
return;
};
tryGetParent(asyncResp, interface, path,
[asyncResp, currentIfc{interface}, func{std::move(func)}](
const boost::system::error_code& ec,
const std::string& service, const std::string& path,
const std::string& interface) mutable {
if (ec)
{
BMCWEB_LOG_ERROR << "Failed to find parent for " << currentIfc;
messages::internalError(asyncResp->res);
func("/phys");
return;
}
if (service.empty() || path.empty() || interface.empty())
{
func("/phys");
}
else
{
tryGetDevpath(asyncResp, path, interface, std::move(func));
}
});
});
}
inline void tryGetLocation(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& /* service */, const std::string& path,
const std::string& resourceType, const std::vector<std::string>& interfaces,
const nlohmann::json::json_pointer& locationPtr = "/Location"_json_pointer)
{
// Update the location type and service label for devpath2.
// Should be deprecated together with devpath2
readLocationFromDbus(asyncResp, path,
[asyncResp, locationPtr](
std::optional<const std::string> locationType,
std::optional<const std::string> serviceLabel) {
if (locationType)
{
asyncResp->res
.jsonValue[locationPtr]["PartLocation"]["LocationType"] =
*locationType;
}
if (serviceLabel)
{
asyncResp->res
.jsonValue[locationPtr]["PartLocation"]["ServiceLabel"] =
*serviceLabel;
}
});
for (const std::string& interface : interfaces)
{
if (interface == "xyz.openbmc_project.NVMe.MetricStore")
{
if (resourceType == controllerInterface)
{
asyncResp->res.jsonValue[locationPtr]["Oem"]["Google"]
["EmbeddedLocationContext"] =
"controller:" +
std::filesystem::path(path).filename().string();
}
if (resourceType == volumeInterface)
{
asyncResp->res.jsonValue[locationPtr]["Oem"]["Google"]
["EmbeddedLocationContext"] =
"namespace:" +
std::filesystem::path(path).filename().string();
}
}
}
tryGetDevpath(asyncResp, path, resourceType,
[asyncResp, locationPtr](const std::string& devpath) {
asyncResp->res.jsonValue[locationPtr]["Oem"]["Google"]["Devpath"] =
devpath;
// b/416773525: Place holder for PLC version of devpath3 to fail the
// [PLC, SL] lookup table
asyncResp->res.jsonValue[locationPtr]["PartLocationContext"] =
"PlaceHolderAndShouldNotBeUsed";
});
}
} // namespace storage_utils
} // namespace redfish