| #pragma once |
| |
| #include "async_resp.hpp" |
| #include "chassis_utils.hpp" |
| #include "dbus_utility.hpp" |
| #include "dbus_utils.hpp" |
| #include "error_messages.hpp" |
| #include "managed_store_types.hpp" |
| |
| #include <sdbusplus/asio/property.hpp> |
| |
| #include <string> |
| #include <unordered_set> |
| |
| namespace redfish |
| { |
| namespace location_util |
| { |
| |
| /** |
| * @brief Check if the interface is a supported connector |
| * |
| * @param[in] interface Location type interface. |
| * |
| * @return true if the interface is a supported connector |
| */ |
| inline bool isConnector(const std::string& interface) |
| { |
| return interface == "xyz.openbmc_project.Inventory.Connector.Backplane" || |
| interface == "xyz.openbmc_project.Inventory.Connector.Bay" || |
| interface == "xyz.openbmc_project.Inventory.Connector.Connector" || |
| interface == "xyz.openbmc_project.Inventory.Connector.Embedded" || |
| interface == "xyz.openbmc_project.Inventory.Connector.Slot" || |
| interface == "xyz.openbmc_project.Inventory.Connector.Socket"; |
| } |
| |
| /** |
| * @brief Fill out location info of a resource by requesting data from the |
| * given D-Bus object. |
| * |
| * @param[in,out] asyncResp Async HTTP response. |
| * @param[in] interface Location type interface. |
| * |
| * @return location if interface is supported Connector, otherwise, return |
| * std::nullopt. |
| */ |
| inline std::optional<std::string> getLocationType(const std::string& interface) |
| { |
| if (!isConnector(interface)) |
| { |
| return std::nullopt; |
| } |
| return interface.substr(interface.find_last_of('.') + 1); |
| } |
| |
| void getPartLocationContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& associationPath, |
| const std::string& locationCode = ""); |
| |
| void getOemDevpathLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& associationPath, |
| const std::string& locationCode = ""); |
| |
| /** |
| * |
| * @brief Fill out location code of a resource by requesting data from the |
| * given D-Bus object. |
| * |
| * @param[in,out] asyncResp Async HTTP response. |
| * @param[in] service D-Bus service to query. |
| * @param[in] objPath D-Bus object to query. |
| * @param[in] location Json path of where to find the location |
| * property. |
| */ |
| inline void getLocationCode(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& service, |
| const std::string& objPath, |
| const nlohmann::json::json_pointer& location, |
| const std::optional<std::string>& contextPath = std::nullopt) |
| { |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| dbus_utils::getProperty<std::string>( |
| service, objPath, |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", |
| requestContext, |
| [asyncResp, location, contextPath](const boost::system::error_code ec, |
| const std::string& property) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error for Location"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| asyncResp->res.jsonValue[location]["PartLocation"]["ServiceLabel"] = |
| property; |
| if (contextPath != std::nullopt) |
| { |
| getPartLocationContext(asyncResp, location, contextPath.value(), |
| property); |
| } |
| }); |
| } |
| |
| /** |
| * @brief Fill out PartLocationContext of a redfish resource. |
| * |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] jsonPtr Json path of where to put the location context |
| * @param[in] label Service label to update the location context with |
| */ |
| inline void |
| updateLocationContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& label) |
| { |
| BMCWEB_LOG_DEBUG << "Add service label " << label |
| << " to PartLocationContext"; |
| nlohmann::json& propertyJson = |
| asyncResp->res.jsonValue[jsonPtr]["PartLocationContext"]; |
| const std::string* val = propertyJson.get_ptr<const std::string*>(); |
| if (val != nullptr && !val->empty()) |
| { |
| propertyJson = label + "/" + *val; |
| } |
| else |
| { |
| propertyJson = label; |
| } |
| } |
| |
| /** |
| * @brief Fill out Oem devpath of a redfish resource. |
| * |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] jsonPtr Json path of where to put the Oem Devpath |
| * @param[in] label Service label to update the Oem Devpath with |
| */ |
| inline void updateLocationOemDevpath( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, const std::string& label) |
| { |
| BMCWEB_LOG_DEBUG << "update devpath" << label << "to Oem/Google/Devpath"; |
| const std::string prefix = "/phys/"; |
| nlohmann::json& propertyJson = |
| asyncResp->res.jsonValue[jsonPtr]["Oem"]["Google"]["Devpath"]; |
| std::string* val = propertyJson.get_ptr<std::string*>(); |
| if (val != nullptr && !val->empty()) |
| { |
| if (val->rfind(prefix, 0) == 0) |
| { |
| val->erase(0, prefix.length()); |
| propertyJson = prefix + label + "/" + *val; |
| } |
| else |
| { |
| BMCWEB_LOG_ERROR << "Oem devpath " << *val << " missing /phys/"; |
| return; |
| } |
| } |
| else |
| { |
| propertyJson = prefix + label; |
| } |
| } |
| |
| /** |
| * @brief Parses given chassis subtree for object path and connection to retrive |
| * chassis location code used to fill PartLocationContext. |
| * |
| * @param[in] upstreamChassisPaths Set of chassis object paths queried. |
| * @param[in] jsonPtr Json location to fill PartLocationContext |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] subtree Subtree associated with chassis endpoint |
| */ |
| inline void getAssociatedChassisSubtreeCallback( |
| std::unordered_set<std::string> upstreamChassisPaths, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& currentLocationCode, const bool isOemDevpath, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) |
| { |
| // Base case as we recurse to get upstream chassis association. |
| if (subtree.empty()) |
| { |
| BMCWEB_LOG_DEBUG << "Chassis association not found." |
| "Must be root chassis or link is broken"; |
| return; |
| } |
| |
| if (subtree.size() > 1) |
| { |
| BMCWEB_LOG_ERROR << "Found multiple upstream chassis."; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| auto chassisPathToConnMap = subtree.begin(); |
| |
| // Upstream chassis. |
| const std::string& chassisPath = chassisPathToConnMap->first; |
| if (!upstreamChassisPaths.emplace(chassisPath).second) |
| { |
| BMCWEB_LOG_ERROR << "Loop detected in upstream chassis associations."; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| const dbus::utility::MapperServiceMap& serviceMap = |
| chassisPathToConnMap->second; |
| if (serviceMap.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Associated chassis obj path " << chassisPath |
| << " has no service mapping."; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| if (serviceMap.size() > 1) |
| { |
| BMCWEB_LOG_ERROR << "Associated chassis path " << chassisPath |
| << " found in multiple connections"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Get service name to retrieve upstream chassis location code property. |
| const std::string& service = serviceMap.begin()->first; |
| |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| dbus_utils::getProperty<std::string>( |
| service, chassisPath, |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", |
| requestContext, |
| [asyncResp, jsonPtr, chassisPath, currentLocationCode, |
| upstreamChassisPaths, |
| isOemDevpath](const boost::system::error_code ec2, |
| const std::string& locationCode) { |
| if (ec2) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error for Location, ec: " |
| << ec2.message(); |
| return; |
| } |
| |
| std::string nextLocationCode; |
| if (currentLocationCode != locationCode) |
| { |
| if (!isOemDevpath) |
| { |
| updateLocationContext(asyncResp, jsonPtr, locationCode); |
| } |
| else |
| { |
| updateLocationOemDevpath(asyncResp, jsonPtr, locationCode); |
| } |
| } |
| else |
| { |
| // Continue checking to see if the locationCode matches |
| nextLocationCode = currentLocationCode; |
| } |
| |
| chassis_utils::getAssociatedChassisSubtree( |
| asyncResp, chassisPath + "/contained_by", |
| std::bind_front(getAssociatedChassisSubtreeCallback, |
| upstreamChassisPaths, jsonPtr, nextLocationCode, |
| isOemDevpath)); |
| }); |
| } |
| |
| /** |
| * @brief Recursively gets LocationCode for upstream chassis to populate |
| * PartLocationContext. |
| * |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] jsonPtr Json location to fill PartLocationContext |
| * @param[in] associationPath Path used to associate with upstream chassis |
| */ |
| inline void |
| getPartLocationContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& associationPath, const std::string& locationCode) |
| { |
| BMCWEB_LOG_DEBUG << "Get chassis endpoints associated with " |
| << associationPath; |
| // Set of chassis object paths used to detect cycle as we resolve usptream |
| // chassis associations to get to root chassis to create PartLocationContext |
| std::unordered_set<std::string> upstreamChassisPaths; |
| chassis_utils::getAssociatedChassisSubtree( |
| asyncResp, associationPath, |
| std::bind_front(getAssociatedChassisSubtreeCallback, |
| std::move(upstreamChassisPaths), jsonPtr, locationCode, |
| false)); |
| } |
| |
| /** |
| * @brief Recursively gets LocationCode for upstream chassis to populate oem |
| * devpath. This translates "/phys/A/SL" to "/phys/.../DC_DC_A/DC_A/A/SL" |
| * |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] jsonPtr Json location to fill PartLocationContext |
| * @param[in] associationPath Path used to associate with upstream chassis |
| */ |
| inline void |
| getOemDevpathLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& associationPath, |
| const std::string& locationCode) |
| { |
| BMCWEB_LOG_DEBUG << "Get chassis endpoints associated with " |
| << associationPath << " for oem devpath"; |
| // Set of chassis object paths used to detect cycle as we resolve usptream |
| // chassis associations to get to root chassis to create PartLocationContext |
| std::unordered_set<std::string> upstreamChassisPaths; |
| chassis_utils::getAssociatedChassisSubtree( |
| asyncResp, associationPath, |
| std::bind_front(getAssociatedChassisSubtreeCallback, |
| std::move(upstreamChassisPaths), jsonPtr, locationCode, |
| true)); |
| } |
| |
| /** |
| * @brief Fill out location code of a resource from given managed objects. |
| * |
| * @param[in,out] aResp Async HTTP response. |
| * @param[in] objPath D-Bus object to query. |
| * @param[in] interface D-Bus interface to query. |
| * @param[in] jsonPtr Json pointer to populate. |
| * @param[in] managedObjects Managed objects. |
| */ |
| inline void getLocationCodeFromManagedObject( |
| const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& objPath, |
| const std::string& interface, const nlohmann::json::json_pointer& jsonPtr, |
| const dbus::utility::ManagedObjectType& managedObjects) |
| { |
| dbus::utility::DBusPropertiesMap properties; |
| dbus_utils::getPropertiesFromManagedObjects(managedObjects, objPath, |
| interface, properties); |
| |
| const std::string* locationProperty = nullptr; |
| |
| const bool success = sdbusplus::unpackPropertiesNoThrow( |
| dbus_utils::UnpackErrorPrinter(), properties, "LocationCode", |
| locationProperty); |
| |
| if (!success) |
| { |
| messages::internalError(aResp->res); |
| return; |
| } |
| |
| if (locationProperty != nullptr) |
| { |
| aResp->res.jsonValue[jsonPtr]["PartLocation"]["ServiceLabel"] = |
| *locationProperty; |
| } |
| } |
| |
| /** |
| * @brief Validate the association subtree. |
| * |
| * @param[in] upstreamChassisPaths Set of chassis object paths queried. |
| * @param[in] subtree Subtree associated with chassis endpoint |
| */ |
| |
| inline bool |
| validateSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) |
| { |
| if (subtree.empty()) |
| { |
| BMCWEB_LOG_DEBUG << "Chassis association not found." |
| "Must be root chassis or link is broken"; |
| return false; |
| } |
| if (subtree.size() > 1) |
| { |
| BMCWEB_LOG_ERROR << "Found multiple upstream chassis."; |
| messages::internalError(asyncResp->res); |
| return false; |
| } |
| auto chassisPathToConnMap = subtree.begin(); |
| // Upstream chassis. |
| const std::string& chassisPath = chassisPathToConnMap->first; |
| const dbus::utility::MapperServiceMap& serviceMap = |
| chassisPathToConnMap->second; |
| if (serviceMap.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Associated chassis obj path " << chassisPath |
| << " has no service mapping."; |
| messages::internalError(asyncResp->res); |
| return false; |
| } |
| if (serviceMap.size() > 1) |
| { |
| BMCWEB_LOG_ERROR << "Associated chassis path " << chassisPath |
| << " found in multiple connections"; |
| messages::internalError(asyncResp->res); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * @brief Fill out Oem.LocationContext.ServiceLabel of a redfish resource. |
| * |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] jsonPtr Json path of where to put the location context |
| * @param[in] label Service label to update the location context with |
| */ |
| inline void |
| updateOemServiceLabel(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& label) |
| { |
| asyncResp->res |
| .jsonValue[jsonPtr]["Google"]["LocationContext"]["ServiceLabel"] = |
| label; |
| } |
| |
| /** |
| * @brief Parses given chassis subtree for object path and connection to retrive |
| * chassis location code used to fill Oem ServiceLabel. |
| * |
| * @param[in] jsonPtr Json location to fill PartLocationContext |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] subtree Subtree associated with chassis endpoint |
| */ |
| inline void getOemServiceLabelCallback( |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) |
| { |
| if (!validateSubtree(asyncResp, subtree)) |
| { |
| BMCWEB_LOG_DEBUG << "Not a valid subree"; |
| return; |
| } |
| auto chassisPathToConnMap = subtree.begin(); |
| |
| // Upstream chassis. |
| const std::string& chassisPath = chassisPathToConnMap->first; |
| const dbus::utility::MapperServiceMap& serviceMap = |
| chassisPathToConnMap->second; |
| // Get service name to retrieve upstream chassis location code property. |
| const std::string& service = serviceMap.begin()->first; |
| |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| dbus_utils::getProperty<std::string>( |
| service, chassisPath, |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", |
| requestContext, |
| [asyncResp, jsonPtr, chassisPath](const boost::system::error_code ec2, |
| const std::string& locationCode) { |
| if (ec2) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error for Location, ec: " |
| << ec2.message(); |
| return; |
| } |
| updateOemServiceLabel(asyncResp, jsonPtr, locationCode); |
| }); |
| } |
| |
| /** |
| * @brief gets LocationCode for upstream chassis to populate |
| * Oem ServiceLabel. |
| * |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] jsonPtr Json location to fill ServiceLabel |
| * @param[in] associationPath Path used to associate with upstream chassis |
| */ |
| inline void |
| getOemServiceLabel(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& associationPath) |
| { |
| // Oem.Google.LocationContext.ServiceLabel is ServiceLabel inherited from |
| // parent replaceable resource. |
| chassis_utils::getAssociatedChassisSubtree( |
| asyncResp, associationPath, |
| std::bind_front(getOemServiceLabelCallback, jsonPtr)); |
| } |
| |
| /** |
| * @brief Fill out Oem PartLocationContext of a redfish resource. |
| * |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] jsonPtr Json path of where to put the location context |
| * @param[in] label Service label to update the location context with |
| */ |
| inline void updateOemPartLocationContext( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, const std::string& label) |
| { |
| nlohmann::json& propertyJson = |
| asyncResp->res.jsonValue[jsonPtr]["Google"]["LocationContext"] |
| ["PartLocationContext"]; |
| const std::string* val = propertyJson.get_ptr<const std::string*>(); |
| if (val != nullptr && !val->empty()) |
| { |
| propertyJson = label + "/" + *val; |
| } |
| else |
| { |
| propertyJson = label; |
| } |
| } |
| |
| /** |
| * @brief Parses given chassis subtree for object path and connection to retrive |
| * chassis location code used to fill Oem PartLocationContext. |
| * |
| * @param[in] upstreamChassisPaths Set of chassis object paths queried. |
| * @param[in] jsonPtr Json location to fill PartLocationContext |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] subtree Subtree associated with chassis endpoint |
| */ |
| inline void getOemPartLocationContextCallback( |
| std::unordered_set<std::string> upstreamChassisPaths, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) |
| { |
| if (!validateSubtree(asyncResp, subtree)) |
| { |
| BMCWEB_LOG_DEBUG << "Not a valid subree"; |
| return; |
| } |
| |
| auto chassisPathToConnMap = subtree.begin(); |
| // Upstream chassis. |
| const std::string& chassisPath = chassisPathToConnMap->first; |
| if (!upstreamChassisPaths.emplace(chassisPath).second) |
| { |
| BMCWEB_LOG_ERROR << "Loop detected in upstream chassis associations."; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| const dbus::utility::MapperServiceMap& serviceMap = |
| chassisPathToConnMap->second; |
| |
| // Get service name to retrieve upstream chassis location code property. |
| const std::string& service = serviceMap.begin()->first; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| dbus_utils::getProperty<std::string>( |
| service, chassisPath, |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", |
| requestContext, |
| [asyncResp, jsonPtr, chassisPath, |
| upstreamChassisPaths](const boost::system::error_code ec2, |
| const std::string& locationCode) { |
| if (ec2) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error for Location, ec: " |
| << ec2.message(); |
| return; |
| } |
| |
| updateOemPartLocationContext(asyncResp, jsonPtr, locationCode); |
| |
| chassis_utils::getAssociatedChassisSubtree( |
| asyncResp, chassisPath + "/contained_by", |
| std::bind_front(getOemPartLocationContextCallback, |
| upstreamChassisPaths, jsonPtr)); |
| }); |
| } |
| |
| /** |
| * @brief Recursively gets LocationCode for upstream chassis to populate |
| * Oem PartLocationContext. |
| * |
| * @param[in,out] asyncResp Async HTTP response |
| * @param[in] jsonPtr Json location to fill PartLocationContext |
| * @param[in] associationPath Path used to associate with upstream chassis |
| */ |
| inline void getOemPartLocationContext( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const nlohmann::json::json_pointer& jsonPtr, |
| const std::string& associationPath) |
| { |
| // Oem.Google.LocationContext.PartLocationContext is PartLocationContext |
| // inherited from parent replaceable resource. |
| asyncResp->res.jsonValue[jsonPtr]["Google"]["LocationContext"] |
| ["PartLocationContext"] = ""; |
| std::unordered_set<std::string> upstreamChassisPaths; |
| chassis_utils::getAssociatedChassisSubtree( |
| asyncResp, associationPath, |
| [jsonPtr, upstreamChassisPaths]( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| if (!validateSubtree(asyncResp, subtree)) |
| { |
| BMCWEB_LOG_DEBUG << "Not a valid subree"; |
| return; |
| } |
| // Upstream chassis. |
| const std::string& chassisPath = subtree.begin()->first; |
| |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| chassis_utils::getAssociatedChassisSubtree( |
| asyncResp, chassisPath + "/contained_by", |
| std::bind_front(getOemPartLocationContextCallback, |
| upstreamChassisPaths, jsonPtr)); |
| }); |
| } |
| |
| } // namespace location_util |
| } // namespace redfish |