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