|  | #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 = ""); | 
|  | /** | 
|  | * @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 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 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](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) { | 
|  | updateLocationContext(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)); | 
|  | }); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @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)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @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 |