blob: 328d5ced158cd7ea32661af05504d19ed4f64085 [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 "managed_store.hpp"
#include "managed_store_types.hpp"
#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
{
// Uncached DBus operations with ManageStore
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)
{
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
context.GetStrand(),
std::move(callback), service, path, "org.freedesktop.DBus.Properties",
"GetAll", interface);
}
/**
* @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]});
});
}
} // namespace storage_utils
} // namespace redfish