| /* |
| // 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 |