17fb33566SCarson Labrado #pragma once 27fb33566SCarson Labrado 346a81465SCarson Labrado #include <dbus_utility.hpp> 446a81465SCarson Labrado #include <error_messages.hpp> 57fb33566SCarson Labrado #include <http_client.hpp> 646a81465SCarson Labrado #include <http_connection.hpp> 77fb33566SCarson Labrado 87fb33566SCarson Labrado namespace redfish 97fb33566SCarson Labrado { 107fb33566SCarson Labrado 1105916cefSCarson Labrado enum class Result 1205916cefSCarson Labrado { 1305916cefSCarson Labrado LocalHandle, 1405916cefSCarson Labrado NoLocalHandle 1505916cefSCarson Labrado }; 1605916cefSCarson Labrado 171c0bb5c6SCarson Labrado static void addPrefixToItem(nlohmann::json& item, std::string_view prefix) 181c0bb5c6SCarson Labrado { 191c0bb5c6SCarson Labrado std::string* strValue = item.get_ptr<std::string*>(); 201c0bb5c6SCarson Labrado if (strValue == nullptr) 211c0bb5c6SCarson Labrado { 221c0bb5c6SCarson Labrado BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; 231c0bb5c6SCarson Labrado return; 241c0bb5c6SCarson Labrado } 251c0bb5c6SCarson Labrado // Make sure the value is a properly formatted URI 261c0bb5c6SCarson Labrado auto parsed = boost::urls::parse_relative_ref(*strValue); 271c0bb5c6SCarson Labrado if (!parsed) 281c0bb5c6SCarson Labrado { 291c0bb5c6SCarson Labrado BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << *strValue; 301c0bb5c6SCarson Labrado return; 311c0bb5c6SCarson Labrado } 321c0bb5c6SCarson Labrado 331c0bb5c6SCarson Labrado boost::urls::url_view thisUrl = *parsed; 341c0bb5c6SCarson Labrado 351c0bb5c6SCarson Labrado // We don't need to add prefixes to these URIs since 361c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/ itself is not a collection 371c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory 381c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/SoftwareInventory 391c0bb5c6SCarson Labrado if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 401c0bb5c6SCarson Labrado "UpdateService", "FirmwareInventory") || 411c0bb5c6SCarson Labrado crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 421c0bb5c6SCarson Labrado "UpdateService", "SoftwareInventory")) 431c0bb5c6SCarson Labrado { 441c0bb5c6SCarson Labrado BMCWEB_LOG_DEBUG << "Skipping UpdateService URI prefix fixing"; 451c0bb5c6SCarson Labrado return; 461c0bb5c6SCarson Labrado } 471c0bb5c6SCarson Labrado 481c0bb5c6SCarson Labrado // We also need to aggregate FirmwareInventory and 491c0bb5c6SCarson Labrado // SoftwareInventory so add an extra offset 501c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory/<id> 511c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/SoftwareInventory/<id> 521c0bb5c6SCarson Labrado std::string collectionName; 531c0bb5c6SCarson Labrado std::string softwareItem; 541c0bb5c6SCarson Labrado if (crow::utility::readUrlSegments( 551c0bb5c6SCarson Labrado thisUrl, "redfish", "v1", "UpdateService", std::ref(collectionName), 561c0bb5c6SCarson Labrado std::ref(softwareItem), crow::utility::OrMorePaths())) 571c0bb5c6SCarson Labrado { 581c0bb5c6SCarson Labrado softwareItem.insert(0, "_"); 591c0bb5c6SCarson Labrado softwareItem.insert(0, prefix); 601c0bb5c6SCarson Labrado item = crow::utility::replaceUrlSegment(thisUrl, 4, softwareItem); 611c0bb5c6SCarson Labrado } 621c0bb5c6SCarson Labrado 631c0bb5c6SCarson Labrado // A collection URI that ends with "/" such as 641c0bb5c6SCarson Labrado // "/redfish/v1/Chassis/" will have 4 segments so we need to 651c0bb5c6SCarson Labrado // make sure we don't try to add a prefix to an empty segment 661c0bb5c6SCarson Labrado if (crow::utility::readUrlSegments( 671c0bb5c6SCarson Labrado thisUrl, "redfish", "v1", std::ref(collectionName), 681c0bb5c6SCarson Labrado std::ref(softwareItem), crow::utility::OrMorePaths())) 691c0bb5c6SCarson Labrado { 701c0bb5c6SCarson Labrado softwareItem.insert(0, "_"); 711c0bb5c6SCarson Labrado softwareItem.insert(0, prefix); 721c0bb5c6SCarson Labrado item = crow::utility::replaceUrlSegment(thisUrl, 3, softwareItem); 731c0bb5c6SCarson Labrado } 741c0bb5c6SCarson Labrado } 751c0bb5c6SCarson Labrado 761c0bb5c6SCarson Labrado // We need to attempt to update all URIs under Actions 771c0bb5c6SCarson Labrado static void addPrefixesToActions(nlohmann::json& json, std::string_view prefix) 781c0bb5c6SCarson Labrado { 791c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 801c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 811c0bb5c6SCarson Labrado if (object != nullptr) 821c0bb5c6SCarson Labrado { 831c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 841c0bb5c6SCarson Labrado { 851c0bb5c6SCarson Labrado std::string* strValue = item.second.get_ptr<std::string*>(); 861c0bb5c6SCarson Labrado if (strValue != nullptr) 871c0bb5c6SCarson Labrado { 881c0bb5c6SCarson Labrado addPrefixToItem(item.second, prefix); 891c0bb5c6SCarson Labrado } 901c0bb5c6SCarson Labrado else 911c0bb5c6SCarson Labrado { 921c0bb5c6SCarson Labrado addPrefixesToActions(item.second, prefix); 931c0bb5c6SCarson Labrado } 941c0bb5c6SCarson Labrado } 951c0bb5c6SCarson Labrado } 961c0bb5c6SCarson Labrado } 971c0bb5c6SCarson Labrado 981c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for 991c0bb5c6SCarson Labrado // and aggregated resource. 1001c0bb5c6SCarson Labrado static void addPrefixes(nlohmann::json& json, std::string_view prefix) 1011c0bb5c6SCarson Labrado { 1021c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 1031c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 1041c0bb5c6SCarson Labrado if (object != nullptr) 1051c0bb5c6SCarson Labrado { 1061c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 1071c0bb5c6SCarson Labrado { 1081c0bb5c6SCarson Labrado if (item.first == "Actions") 1091c0bb5c6SCarson Labrado { 1101c0bb5c6SCarson Labrado addPrefixesToActions(item.second, prefix); 1111c0bb5c6SCarson Labrado continue; 1121c0bb5c6SCarson Labrado } 1131c0bb5c6SCarson Labrado 1141c0bb5c6SCarson Labrado if ((item.first == "@odata.id") || (item.first.ends_with("URI"))) 1151c0bb5c6SCarson Labrado { 1161c0bb5c6SCarson Labrado addPrefixToItem(item.second, prefix); 1171c0bb5c6SCarson Labrado } 1181c0bb5c6SCarson Labrado // Recusively parse the rest of the json 1191c0bb5c6SCarson Labrado addPrefixes(item.second, prefix); 1201c0bb5c6SCarson Labrado } 1211c0bb5c6SCarson Labrado return; 1221c0bb5c6SCarson Labrado } 1231c0bb5c6SCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 1241c0bb5c6SCarson Labrado if (array != nullptr) 1251c0bb5c6SCarson Labrado { 1261c0bb5c6SCarson Labrado for (nlohmann::json& item : *array) 1271c0bb5c6SCarson Labrado { 1281c0bb5c6SCarson Labrado addPrefixes(item, prefix); 1291c0bb5c6SCarson Labrado } 1301c0bb5c6SCarson Labrado } 1311c0bb5c6SCarson Labrado } 1321c0bb5c6SCarson Labrado 1337fb33566SCarson Labrado class RedfishAggregator 1347fb33566SCarson Labrado { 1357fb33566SCarson Labrado private: 136a7a80296SCarson Labrado const std::string retryPolicyName = "RedfishAggregation"; 137a7a80296SCarson Labrado const uint32_t retryAttempts = 5; 138a7a80296SCarson Labrado const uint32_t retryTimeoutInterval = 0; 13946a81465SCarson Labrado const std::string id = "Aggregator"; 140a7a80296SCarson Labrado 1417fb33566SCarson Labrado RedfishAggregator() 1427fb33566SCarson Labrado { 1437fb33566SCarson Labrado getSatelliteConfigs(constructorCallback); 144a7a80296SCarson Labrado 145a7a80296SCarson Labrado // Setup the retry policy to be used by Redfish Aggregation 146a7a80296SCarson Labrado crow::HttpClient::getInstance().setRetryConfig( 147a7a80296SCarson Labrado retryAttempts, retryTimeoutInterval, aggregationRetryHandler, 148a7a80296SCarson Labrado retryPolicyName); 1497fb33566SCarson Labrado } 1507fb33566SCarson Labrado 151a7a80296SCarson Labrado static inline boost::system::error_code 152a7a80296SCarson Labrado aggregationRetryHandler(unsigned int respCode) 153a7a80296SCarson Labrado { 154a7a80296SCarson Labrado // As a default, assume 200X is alright. 155a7a80296SCarson Labrado // We don't need to retry on a 404 156a7a80296SCarson Labrado if ((respCode < 200) || ((respCode >= 300) && (respCode != 404))) 157a7a80296SCarson Labrado { 158a7a80296SCarson Labrado return boost::system::errc::make_error_code( 159a7a80296SCarson Labrado boost::system::errc::result_out_of_range); 160a7a80296SCarson Labrado } 161a7a80296SCarson Labrado 162a7a80296SCarson Labrado // Return 0 if the response code is valid 163a7a80296SCarson Labrado return boost::system::errc::make_error_code( 164a7a80296SCarson Labrado boost::system::errc::success); 1659fa6d147SNan Zhou } 166a7a80296SCarson Labrado 1677fb33566SCarson Labrado // Dummy callback used by the Constructor so that it can report the number 1687fb33566SCarson Labrado // of satellite configs when the class is first created 1697fb33566SCarson Labrado static void constructorCallback( 1707fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 1717fb33566SCarson Labrado { 1727fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "There were " 1737fb33566SCarson Labrado << std::to_string(satelliteInfo.size()) 1747fb33566SCarson Labrado << " satellite configs found at startup"; 1757fb33566SCarson Labrado } 1767fb33566SCarson Labrado 1777fb33566SCarson Labrado // Polls D-Bus to get all available satellite config information 1787fb33566SCarson Labrado // Expects a handler which interacts with the returned configs 1797fb33566SCarson Labrado static void getSatelliteConfigs( 1807fb33566SCarson Labrado const std::function<void( 1817fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>&)>& handler) 1827fb33566SCarson Labrado { 1837fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Gathering satellite configs"; 1847fb33566SCarson Labrado crow::connections::systemBus->async_method_call( 1857fb33566SCarson Labrado [handler](const boost::system::error_code ec, 1867fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects) { 1877fb33566SCarson Labrado if (ec) 1887fb33566SCarson Labrado { 189002d39b4SEd Tanous BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", " 190002d39b4SEd Tanous << ec.message(); 1917fb33566SCarson Labrado return; 1927fb33566SCarson Labrado } 1937fb33566SCarson Labrado 1947fb33566SCarson Labrado // Maps a chosen alias representing a satellite BMC to a url 1957fb33566SCarson Labrado // containing the information required to create a http 1967fb33566SCarson Labrado // connection to the satellite 1977fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url> satelliteInfo; 1987fb33566SCarson Labrado 1997fb33566SCarson Labrado findSatelliteConfigs(objects, satelliteInfo); 2007fb33566SCarson Labrado 2017fb33566SCarson Labrado if (!satelliteInfo.empty()) 2027fb33566SCarson Labrado { 2037fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " 2047fb33566SCarson Labrado << std::to_string(satelliteInfo.size()) 2057fb33566SCarson Labrado << " satellite BMCs"; 2067fb33566SCarson Labrado } 2077fb33566SCarson Labrado else 2087fb33566SCarson Labrado { 2097fb33566SCarson Labrado BMCWEB_LOG_DEBUG 2107fb33566SCarson Labrado << "No satellite BMCs detected. Redfish Aggregation not enabled"; 2117fb33566SCarson Labrado } 2127fb33566SCarson Labrado handler(satelliteInfo); 2137fb33566SCarson Labrado }, 2147fb33566SCarson Labrado "xyz.openbmc_project.EntityManager", "/", 2157fb33566SCarson Labrado "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 2167fb33566SCarson Labrado } 2177fb33566SCarson Labrado 2187fb33566SCarson Labrado // Search D-Bus objects for satellite config objects and add their 2197fb33566SCarson Labrado // information if valid 2207fb33566SCarson Labrado static void findSatelliteConfigs( 2217fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects, 2227fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 2237fb33566SCarson Labrado { 2247fb33566SCarson Labrado for (const auto& objectPath : objects) 2257fb33566SCarson Labrado { 2267fb33566SCarson Labrado for (const auto& interface : objectPath.second) 2277fb33566SCarson Labrado { 2287fb33566SCarson Labrado if (interface.first == 2297fb33566SCarson Labrado "xyz.openbmc_project.Configuration.SatelliteController") 2307fb33566SCarson Labrado { 2317fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Found Satellite Controller at " 2327fb33566SCarson Labrado << objectPath.first.str; 2337fb33566SCarson Labrado 23405916cefSCarson Labrado if (!satelliteInfo.empty()) 23505916cefSCarson Labrado { 23605916cefSCarson Labrado BMCWEB_LOG_ERROR 23705916cefSCarson Labrado << "Redfish Aggregation only supports one satellite!"; 23805916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Clearing all satellite data"; 23905916cefSCarson Labrado satelliteInfo.clear(); 24005916cefSCarson Labrado return; 24105916cefSCarson Labrado } 24205916cefSCarson Labrado 24305916cefSCarson Labrado // For now assume there will only be one satellite config. 24405916cefSCarson Labrado // Assign it the name/prefix "5B247A" 24505916cefSCarson Labrado addSatelliteConfig("5B247A", interface.second, 24605916cefSCarson Labrado satelliteInfo); 2477fb33566SCarson Labrado } 2487fb33566SCarson Labrado } 2497fb33566SCarson Labrado } 2507fb33566SCarson Labrado } 2517fb33566SCarson Labrado 2527fb33566SCarson Labrado // Parse the properties of a satellite config object and add the 2537fb33566SCarson Labrado // configuration if the properties are valid 2547fb33566SCarson Labrado static void addSatelliteConfig( 25505916cefSCarson Labrado const std::string& name, 2567fb33566SCarson Labrado const dbus::utility::DBusPropertiesMap& properties, 2577fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 2587fb33566SCarson Labrado { 2597fb33566SCarson Labrado boost::urls::url url; 2607fb33566SCarson Labrado 2617fb33566SCarson Labrado for (const auto& prop : properties) 2627fb33566SCarson Labrado { 26305916cefSCarson Labrado if (prop.first == "Hostname") 2647fb33566SCarson Labrado { 2657fb33566SCarson Labrado const std::string* propVal = 2667fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 2677fb33566SCarson Labrado if (propVal == nullptr) 2687fb33566SCarson Labrado { 2697fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Hostname value"; 2707fb33566SCarson Labrado return; 2717fb33566SCarson Labrado } 2727fb33566SCarson Labrado url.set_host(*propVal); 2737fb33566SCarson Labrado } 2747fb33566SCarson Labrado 2757fb33566SCarson Labrado else if (prop.first == "Port") 2767fb33566SCarson Labrado { 2777fb33566SCarson Labrado const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 2787fb33566SCarson Labrado if (propVal == nullptr) 2797fb33566SCarson Labrado { 2807fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Port value"; 2817fb33566SCarson Labrado return; 2827fb33566SCarson Labrado } 2837fb33566SCarson Labrado 2847fb33566SCarson Labrado if (*propVal > std::numeric_limits<uint16_t>::max()) 2857fb33566SCarson Labrado { 2867fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Port value out of range"; 2877fb33566SCarson Labrado return; 2887fb33566SCarson Labrado } 2897fb33566SCarson Labrado url.set_port(static_cast<uint16_t>(*propVal)); 2907fb33566SCarson Labrado } 2917fb33566SCarson Labrado 2927fb33566SCarson Labrado else if (prop.first == "AuthType") 2937fb33566SCarson Labrado { 2947fb33566SCarson Labrado const std::string* propVal = 2957fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 2967fb33566SCarson Labrado if (propVal == nullptr) 2977fb33566SCarson Labrado { 2987fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid AuthType value"; 2997fb33566SCarson Labrado return; 3007fb33566SCarson Labrado } 3017fb33566SCarson Labrado 3027fb33566SCarson Labrado // For now assume authentication not required to communicate 3037fb33566SCarson Labrado // with the satellite BMC 3047fb33566SCarson Labrado if (*propVal != "None") 3057fb33566SCarson Labrado { 3067fb33566SCarson Labrado BMCWEB_LOG_ERROR 3077fb33566SCarson Labrado << "Unsupported AuthType value: " << *propVal 3087fb33566SCarson Labrado << ", only \"none\" is supported"; 3097fb33566SCarson Labrado return; 3107fb33566SCarson Labrado } 3117fb33566SCarson Labrado url.set_scheme("http"); 3127fb33566SCarson Labrado } 3137fb33566SCarson Labrado } // Finished reading properties 3147fb33566SCarson Labrado 3157fb33566SCarson Labrado // Make sure all required config information was made available 3167fb33566SCarson Labrado if (url.host().empty()) 3177fb33566SCarson Labrado { 3187fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; 3197fb33566SCarson Labrado return; 3207fb33566SCarson Labrado } 3217fb33566SCarson Labrado 3227fb33566SCarson Labrado if (!url.has_port()) 3237fb33566SCarson Labrado { 3247fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; 3257fb33566SCarson Labrado return; 3267fb33566SCarson Labrado } 3277fb33566SCarson Labrado 3287fb33566SCarson Labrado if (!url.has_scheme()) 3297fb33566SCarson Labrado { 3307fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name 3317fb33566SCarson Labrado << " missing AuthType"; 3327fb33566SCarson Labrado return; 3337fb33566SCarson Labrado } 3347fb33566SCarson Labrado 3357fb33566SCarson Labrado std::string resultString; 3367fb33566SCarson Labrado auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 3377fb33566SCarson Labrado if (result.second) 3387fb33566SCarson Labrado { 3397fb33566SCarson Labrado resultString = "Added new satellite config "; 3407fb33566SCarson Labrado } 3417fb33566SCarson Labrado else 3427fb33566SCarson Labrado { 3437fb33566SCarson Labrado resultString = "Updated existing satellite config "; 3447fb33566SCarson Labrado } 3457fb33566SCarson Labrado 3467fb33566SCarson Labrado BMCWEB_LOG_DEBUG << resultString << name << " at " 3477fb33566SCarson Labrado << result.first->second.scheme() << "://" 3487fb33566SCarson Labrado << result.first->second.encoded_host_and_port(); 3497fb33566SCarson Labrado } 3507fb33566SCarson Labrado 35146a81465SCarson Labrado enum AggregationType 35246a81465SCarson Labrado { 35346a81465SCarson Labrado Collection, 35446a81465SCarson Labrado Resource, 35546a81465SCarson Labrado }; 35646a81465SCarson Labrado 35746a81465SCarson Labrado static void 35846a81465SCarson Labrado startAggregation(AggregationType isCollection, 35946a81465SCarson Labrado const crow::Request& thisReq, 36046a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 36146a81465SCarson Labrado { 36246a81465SCarson Labrado // Create a copy of thisReq so we we can still locally process the req 36346a81465SCarson Labrado std::error_code ec; 36446a81465SCarson Labrado auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 36546a81465SCarson Labrado if (ec) 36646a81465SCarson Labrado { 36746a81465SCarson Labrado BMCWEB_LOG_ERROR << "Failed to create copy of request"; 36846a81465SCarson Labrado if (isCollection != AggregationType::Collection) 36946a81465SCarson Labrado { 37046a81465SCarson Labrado messages::internalError(asyncResp->res); 37146a81465SCarson Labrado } 37246a81465SCarson Labrado return; 37346a81465SCarson Labrado } 37446a81465SCarson Labrado 37546a81465SCarson Labrado getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection, 37646a81465SCarson Labrado localReq, asyncResp)); 37746a81465SCarson Labrado } 37846a81465SCarson Labrado 37946a81465SCarson Labrado static void findSatelite( 38046a81465SCarson Labrado const crow::Request& req, 38146a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 38246a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 38346a81465SCarson Labrado std::string_view memberName) 38446a81465SCarson Labrado { 38546a81465SCarson Labrado // Determine if the resource ID begins with a known prefix 38646a81465SCarson Labrado for (const auto& satellite : satelliteInfo) 38746a81465SCarson Labrado { 38846a81465SCarson Labrado std::string targetPrefix = satellite.first; 38946a81465SCarson Labrado targetPrefix += "_"; 39046a81465SCarson Labrado if (memberName.starts_with(targetPrefix)) 39146a81465SCarson Labrado { 39246a81465SCarson Labrado BMCWEB_LOG_DEBUG << "\"" << satellite.first 39346a81465SCarson Labrado << "\" is a known prefix"; 39446a81465SCarson Labrado 39546a81465SCarson Labrado // Remove the known prefix from the request's URI and 39646a81465SCarson Labrado // then forward to the associated satellite BMC 39746a81465SCarson Labrado getInstance().forwardRequest(req, asyncResp, satellite.first, 39846a81465SCarson Labrado satelliteInfo); 39946a81465SCarson Labrado return; 40046a81465SCarson Labrado } 40146a81465SCarson Labrado } 40246a81465SCarson Labrado } 40346a81465SCarson Labrado 40446a81465SCarson Labrado // Intended to handle an incoming request based on if Redfish Aggregation 40546a81465SCarson Labrado // is enabled. Forwards request to satellite BMC if it exists. 40646a81465SCarson Labrado static void aggregateAndHandle( 40746a81465SCarson Labrado AggregationType isCollection, 40846a81465SCarson Labrado const std::shared_ptr<crow::Request>& sharedReq, 40946a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 41046a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 41146a81465SCarson Labrado { 41246a81465SCarson Labrado if (sharedReq == nullptr) 41346a81465SCarson Labrado { 41446a81465SCarson Labrado return; 41546a81465SCarson Labrado } 41646a81465SCarson Labrado const crow::Request& thisReq = *sharedReq; 41746a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " 41846a81465SCarson Labrado << thisReq.target(); 41946a81465SCarson Labrado 42046a81465SCarson Labrado // We previously determined the request is for a collection. No need to 42146a81465SCarson Labrado // check again 42246a81465SCarson Labrado if (isCollection == AggregationType::Collection) 42346a81465SCarson Labrado { 42446a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregating a collection"; 425*4c30e226SCarson Labrado // We need to use a specific response handler and send the 426*4c30e226SCarson Labrado // request to all known satellites 427*4c30e226SCarson Labrado getInstance().forwardCollectionRequests(thisReq, asyncResp, 428*4c30e226SCarson Labrado satelliteInfo); 42946a81465SCarson Labrado return; 43046a81465SCarson Labrado } 43146a81465SCarson Labrado 43246a81465SCarson Labrado std::string updateServiceName; 43346a81465SCarson Labrado std::string memberName; 43446a81465SCarson Labrado if (crow::utility::readUrlSegments( 43546a81465SCarson Labrado thisReq.urlView, "redfish", "v1", "UpdateService", 43646a81465SCarson Labrado std::ref(updateServiceName), std::ref(memberName), 43746a81465SCarson Labrado crow::utility::OrMorePaths())) 43846a81465SCarson Labrado { 43946a81465SCarson Labrado // Must be FirmwareInventory or SoftwareInventory 44046a81465SCarson Labrado findSatelite(thisReq, asyncResp, satelliteInfo, memberName); 44146a81465SCarson Labrado return; 44246a81465SCarson Labrado } 44346a81465SCarson Labrado 44446a81465SCarson Labrado std::string collectionName; 44546a81465SCarson Labrado if (crow::utility::readUrlSegments( 44646a81465SCarson Labrado thisReq.urlView, "redfish", "v1", std::ref(collectionName), 44746a81465SCarson Labrado std::ref(memberName), crow::utility::OrMorePaths())) 44846a81465SCarson Labrado { 44946a81465SCarson Labrado findSatelite(thisReq, asyncResp, satelliteInfo, memberName); 45046a81465SCarson Labrado } 45146a81465SCarson Labrado } 45246a81465SCarson Labrado 45346a81465SCarson Labrado // Attempt to forward a request to the satellite BMC associated with the 45446a81465SCarson Labrado // prefix. 45546a81465SCarson Labrado void forwardRequest( 45646a81465SCarson Labrado const crow::Request& thisReq, 45746a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 45846a81465SCarson Labrado const std::string& prefix, 45946a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 46046a81465SCarson Labrado { 46146a81465SCarson Labrado const auto& sat = satelliteInfo.find(prefix); 46246a81465SCarson Labrado if (sat == satelliteInfo.end()) 46346a81465SCarson Labrado { 46446a81465SCarson Labrado // Realistically this shouldn't get called since we perform an 46546a81465SCarson Labrado // earlier check to make sure the prefix exists 46646a81465SCarson Labrado BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix 46746a81465SCarson Labrado << "\""; 46846a81465SCarson Labrado return; 46946a81465SCarson Labrado } 47046a81465SCarson Labrado 47146a81465SCarson Labrado // We need to strip the prefix from the request's path 47246a81465SCarson Labrado std::string targetURI(thisReq.target()); 47346a81465SCarson Labrado size_t pos = targetURI.find(prefix + "_"); 47446a81465SCarson Labrado if (pos == std::string::npos) 47546a81465SCarson Labrado { 47646a81465SCarson Labrado // If this fails then something went wrong 47746a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix 47846a81465SCarson Labrado << "_\" from request URI"; 47946a81465SCarson Labrado messages::internalError(asyncResp->res); 48046a81465SCarson Labrado return; 48146a81465SCarson Labrado } 48246a81465SCarson Labrado targetURI.erase(pos, prefix.size() + 1); 48346a81465SCarson Labrado 48446a81465SCarson Labrado std::function<void(crow::Response&)> cb = 4851c0bb5c6SCarson Labrado std::bind_front(processResponse, prefix, asyncResp); 48646a81465SCarson Labrado 48746a81465SCarson Labrado std::string data = thisReq.req.body(); 48846a81465SCarson Labrado crow::HttpClient::getInstance().sendDataWithCallback( 48946a81465SCarson Labrado data, id, std::string(sat->second.host()), 49046a81465SCarson Labrado sat->second.port_number(), targetURI, thisReq.fields, 49146a81465SCarson Labrado thisReq.method(), retryPolicyName, cb); 49246a81465SCarson Labrado } 49346a81465SCarson Labrado 494*4c30e226SCarson Labrado // Forward a request for a collection URI to each known satellite BMC 495*4c30e226SCarson Labrado void forwardCollectionRequests( 496*4c30e226SCarson Labrado const crow::Request& thisReq, 497*4c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 498*4c30e226SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 499*4c30e226SCarson Labrado { 500*4c30e226SCarson Labrado for (const auto& sat : satelliteInfo) 501*4c30e226SCarson Labrado { 502*4c30e226SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 503*4c30e226SCarson Labrado processCollectionResponse, sat.first, asyncResp); 504*4c30e226SCarson Labrado 505*4c30e226SCarson Labrado std::string targetURI(thisReq.target()); 506*4c30e226SCarson Labrado std::string data = thisReq.req.body(); 507*4c30e226SCarson Labrado crow::HttpClient::getInstance().sendDataWithCallback( 508*4c30e226SCarson Labrado data, id, std::string(sat.second.host()), 509*4c30e226SCarson Labrado sat.second.port_number(), targetURI, thisReq.fields, 510*4c30e226SCarson Labrado thisReq.method(), retryPolicyName, cb); 511*4c30e226SCarson Labrado } 512*4c30e226SCarson Labrado } 513*4c30e226SCarson Labrado 51446a81465SCarson Labrado // Processes the response returned by a satellite BMC and loads its 51546a81465SCarson Labrado // contents into asyncResp 51646a81465SCarson Labrado static void 5171c0bb5c6SCarson Labrado processResponse(std::string_view prefix, 5181c0bb5c6SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 51946a81465SCarson Labrado crow::Response& resp) 52046a81465SCarson Labrado { 52146a81465SCarson Labrado // No processing needed if the request wasn't successful 52246a81465SCarson Labrado if (resp.resultInt() != 200) 52346a81465SCarson Labrado { 52446a81465SCarson Labrado BMCWEB_LOG_DEBUG << "No need to parse satellite response"; 52546a81465SCarson Labrado asyncResp->res.stringResponse = std::move(resp.stringResponse); 52646a81465SCarson Labrado return; 52746a81465SCarson Labrado } 52846a81465SCarson Labrado 52946a81465SCarson Labrado // The resp will not have a json component 53046a81465SCarson Labrado // We need to create a json from resp's stringResponse 53146a81465SCarson Labrado if (resp.getHeaderValue("Content-Type") == "application/json") 53246a81465SCarson Labrado { 53346a81465SCarson Labrado nlohmann::json jsonVal = 53446a81465SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 53546a81465SCarson Labrado if (jsonVal.is_discarded()) 53646a81465SCarson Labrado { 53746a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 53846a81465SCarson Labrado messages::operationFailed(asyncResp->res); 53946a81465SCarson Labrado return; 54046a81465SCarson Labrado } 54146a81465SCarson Labrado 54246a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 54346a81465SCarson Labrado 54446a81465SCarson Labrado // TODO: For collections we want to add the satellite responses to 54546a81465SCarson Labrado // our response rather than just straight overwriting them if our 54646a81465SCarson Labrado // local handling was successful (i.e. would return a 200). 5471c0bb5c6SCarson Labrado addPrefixes(jsonVal, prefix); 5481c0bb5c6SCarson Labrado 5491c0bb5c6SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 5501c0bb5c6SCarson Labrado 55146a81465SCarson Labrado asyncResp->res.stringResponse.emplace( 55246a81465SCarson Labrado boost::beast::http::response< 55346a81465SCarson Labrado boost::beast::http::string_body>{}); 55446a81465SCarson Labrado asyncResp->res.result(resp.result()); 55546a81465SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 55646a81465SCarson Labrado 55746a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; 55846a81465SCarson Labrado } 55946a81465SCarson Labrado else 56046a81465SCarson Labrado { 56146a81465SCarson Labrado if (!resp.body().empty()) 56246a81465SCarson Labrado { 56346a81465SCarson Labrado // We received a 200 response without the correct Content-Type 56446a81465SCarson Labrado // so return an Operation Failed error 56546a81465SCarson Labrado BMCWEB_LOG_ERROR 56646a81465SCarson Labrado << "Satellite response must be of type \"application/json\""; 56746a81465SCarson Labrado messages::operationFailed(asyncResp->res); 56846a81465SCarson Labrado } 56946a81465SCarson Labrado } 57046a81465SCarson Labrado } 57146a81465SCarson Labrado 572*4c30e226SCarson Labrado // Processes the collection response returned by a satellite BMC and merges 573*4c30e226SCarson Labrado // its "@odata.id" values 574*4c30e226SCarson Labrado static void processCollectionResponse( 575*4c30e226SCarson Labrado const std::string& prefix, 576*4c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 577*4c30e226SCarson Labrado crow::Response& resp) 578*4c30e226SCarson Labrado { 579*4c30e226SCarson Labrado if (resp.resultInt() != 200) 580*4c30e226SCarson Labrado { 581*4c30e226SCarson Labrado BMCWEB_LOG_DEBUG 582*4c30e226SCarson Labrado << "Collection resource does not exist in satellite BMC \"" 583*4c30e226SCarson Labrado << prefix << "\""; 584*4c30e226SCarson Labrado // Return the error if we haven't had any successes 585*4c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 586*4c30e226SCarson Labrado { 587*4c30e226SCarson Labrado asyncResp->res.stringResponse = std::move(resp.stringResponse); 588*4c30e226SCarson Labrado } 589*4c30e226SCarson Labrado return; 590*4c30e226SCarson Labrado } 591*4c30e226SCarson Labrado 592*4c30e226SCarson Labrado // The resp will not have a json component 593*4c30e226SCarson Labrado // We need to create a json from resp's stringResponse 594*4c30e226SCarson Labrado if (resp.getHeaderValue("Content-Type") == "application/json") 595*4c30e226SCarson Labrado { 596*4c30e226SCarson Labrado nlohmann::json jsonVal = 597*4c30e226SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 598*4c30e226SCarson Labrado if (jsonVal.is_discarded()) 599*4c30e226SCarson Labrado { 600*4c30e226SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 601*4c30e226SCarson Labrado 602*4c30e226SCarson Labrado // Notify the user if doing so won't overwrite a valid response 603*4c30e226SCarson Labrado if ((asyncResp->res.resultInt() != 200) && 604*4c30e226SCarson Labrado (asyncResp->res.resultInt() != 502)) 605*4c30e226SCarson Labrado { 606*4c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 607*4c30e226SCarson Labrado } 608*4c30e226SCarson Labrado return; 609*4c30e226SCarson Labrado } 610*4c30e226SCarson Labrado 611*4c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 612*4c30e226SCarson Labrado 613*4c30e226SCarson Labrado // Now we need to add the prefix to the URIs contained in the 614*4c30e226SCarson Labrado // response. 615*4c30e226SCarson Labrado addPrefixes(jsonVal, prefix); 616*4c30e226SCarson Labrado 617*4c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 618*4c30e226SCarson Labrado 619*4c30e226SCarson Labrado // If this resource collection does not exist on the aggregating bmc 620*4c30e226SCarson Labrado // and has not already been added from processing the response from 621*4c30e226SCarson Labrado // a different satellite then we need to completely overwrite 622*4c30e226SCarson Labrado // asyncResp 623*4c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 624*4c30e226SCarson Labrado { 625*4c30e226SCarson Labrado // We only want to aggregate collections that contain a 626*4c30e226SCarson Labrado // "Members" array 627*4c30e226SCarson Labrado if ((!jsonVal.contains("Members")) && 628*4c30e226SCarson Labrado (!jsonVal["Members"].is_array())) 629*4c30e226SCarson Labrado { 630*4c30e226SCarson Labrado BMCWEB_LOG_DEBUG 631*4c30e226SCarson Labrado << "Skipping aggregating unsupported resource"; 632*4c30e226SCarson Labrado return; 633*4c30e226SCarson Labrado } 634*4c30e226SCarson Labrado 635*4c30e226SCarson Labrado BMCWEB_LOG_DEBUG 636*4c30e226SCarson Labrado << "Collection does not exist, overwriting asyncResp"; 637*4c30e226SCarson Labrado asyncResp->res.stringResponse.emplace( 638*4c30e226SCarson Labrado boost::beast::http::response< 639*4c30e226SCarson Labrado boost::beast::http::string_body>{}); 640*4c30e226SCarson Labrado asyncResp->res.result(resp.result()); 641*4c30e226SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 642*4c30e226SCarson Labrado 643*4c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; 644*4c30e226SCarson Labrado } 645*4c30e226SCarson Labrado else 646*4c30e226SCarson Labrado { 647*4c30e226SCarson Labrado // We only want to aggregate collections that contain a 648*4c30e226SCarson Labrado // "Members" array 649*4c30e226SCarson Labrado if ((!asyncResp->res.jsonValue.contains("Members")) && 650*4c30e226SCarson Labrado (!asyncResp->res.jsonValue["Members"].is_array())) 651*4c30e226SCarson Labrado 652*4c30e226SCarson Labrado { 653*4c30e226SCarson Labrado BMCWEB_LOG_DEBUG 654*4c30e226SCarson Labrado << "Skipping aggregating unsupported resource"; 655*4c30e226SCarson Labrado return; 656*4c30e226SCarson Labrado } 657*4c30e226SCarson Labrado 658*4c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" 659*4c30e226SCarson Labrado << prefix << "\" to collection"; 660*4c30e226SCarson Labrado 661*4c30e226SCarson Labrado // TODO: This is a potential race condition with multiple 662*4c30e226SCarson Labrado // satellites and the aggregating bmc attempting to write to 663*4c30e226SCarson Labrado // update this array. May need to cascade calls to the next 664*4c30e226SCarson Labrado // satellite at the end of this function. 665*4c30e226SCarson Labrado // This is presumably not a concern when there is only a single 666*4c30e226SCarson Labrado // satellite since the aggregating bmc should have completed 667*4c30e226SCarson Labrado // before the response is received from the satellite. 668*4c30e226SCarson Labrado 669*4c30e226SCarson Labrado auto& members = asyncResp->res.jsonValue["Members"]; 670*4c30e226SCarson Labrado auto& satMembers = jsonVal["Members"]; 671*4c30e226SCarson Labrado for (auto& satMem : satMembers) 672*4c30e226SCarson Labrado { 673*4c30e226SCarson Labrado members.push_back(std::move(satMem)); 674*4c30e226SCarson Labrado } 675*4c30e226SCarson Labrado asyncResp->res.jsonValue["Members@odata.count"] = 676*4c30e226SCarson Labrado members.size(); 677*4c30e226SCarson Labrado 678*4c30e226SCarson Labrado // TODO: Do we need to sort() after updating the array? 679*4c30e226SCarson Labrado } 680*4c30e226SCarson Labrado } 681*4c30e226SCarson Labrado else 682*4c30e226SCarson Labrado { 683*4c30e226SCarson Labrado BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 684*4c30e226SCarson Labrado << "\""; 685*4c30e226SCarson Labrado // We received as response that was not a json 686*4c30e226SCarson Labrado // Notify the user only if we did not receive any valid responses, 687*4c30e226SCarson Labrado // if the resource collection does not already exist on the 688*4c30e226SCarson Labrado // aggregating BMC, and if we did not already set this warning due 689*4c30e226SCarson Labrado // to a failure from a different satellite 690*4c30e226SCarson Labrado if ((asyncResp->res.resultInt() != 200) && 691*4c30e226SCarson Labrado (asyncResp->res.resultInt() != 502)) 692*4c30e226SCarson Labrado { 693*4c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 694*4c30e226SCarson Labrado } 695*4c30e226SCarson Labrado } 696*4c30e226SCarson Labrado } // End processCollectionResponse() 697*4c30e226SCarson Labrado 6987fb33566SCarson Labrado public: 6997fb33566SCarson Labrado RedfishAggregator(const RedfishAggregator&) = delete; 7007fb33566SCarson Labrado RedfishAggregator& operator=(const RedfishAggregator&) = delete; 7017fb33566SCarson Labrado RedfishAggregator(RedfishAggregator&&) = delete; 7027fb33566SCarson Labrado RedfishAggregator& operator=(RedfishAggregator&&) = delete; 7037fb33566SCarson Labrado ~RedfishAggregator() = default; 7047fb33566SCarson Labrado 7057fb33566SCarson Labrado static RedfishAggregator& getInstance() 7067fb33566SCarson Labrado { 7077fb33566SCarson Labrado static RedfishAggregator handler; 7087fb33566SCarson Labrado return handler; 7097fb33566SCarson Labrado } 71005916cefSCarson Labrado 71105916cefSCarson Labrado // Entry point to Redfish Aggregation 71205916cefSCarson Labrado // Returns Result stating whether or not we still need to locally handle the 71305916cefSCarson Labrado // request 71405916cefSCarson Labrado static Result 71505916cefSCarson Labrado beginAggregation(const crow::Request& thisReq, 71605916cefSCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 71705916cefSCarson Labrado { 71805916cefSCarson Labrado using crow::utility::OrMorePaths; 71905916cefSCarson Labrado using crow::utility::readUrlSegments; 72005916cefSCarson Labrado const boost::urls::url_view& url = thisReq.urlView; 72105916cefSCarson Labrado // UpdateService is the only top level resource that is not a Collection 72205916cefSCarson Labrado if (readUrlSegments(url, "redfish", "v1", "UpdateService")) 72305916cefSCarson Labrado { 72405916cefSCarson Labrado return Result::LocalHandle; 72505916cefSCarson Labrado } 72646a81465SCarson Labrado if (readUrlSegments(url, "redfish", "v1", "UpdateService", 72746a81465SCarson Labrado "SoftwareInventory") || 72846a81465SCarson Labrado readUrlSegments(url, "redfish", "v1", "UpdateService", 72946a81465SCarson Labrado "FirmwareInventory")) 73046a81465SCarson Labrado { 73146a81465SCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 73246a81465SCarson Labrado return Result::LocalHandle; 73346a81465SCarson Labrado } 73405916cefSCarson Labrado 73505916cefSCarson Labrado // Is the request for a resource collection?: 73605916cefSCarson Labrado // /redfish/v1/<resource> 73705916cefSCarson Labrado // e.g. /redfish/v1/Chassis 73805916cefSCarson Labrado std::string collectionName; 73905916cefSCarson Labrado if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName))) 74005916cefSCarson Labrado { 74146a81465SCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 74205916cefSCarson Labrado return Result::LocalHandle; 74305916cefSCarson Labrado } 74405916cefSCarson Labrado 74505916cefSCarson Labrado // We know that the ID of an aggregated resource will begin with 74605916cefSCarson Labrado // "5B247A". For the most part the URI will begin like this: 74705916cefSCarson Labrado // /redfish/v1/<resource>/<resource ID> 74805916cefSCarson Labrado // Note, FirmwareInventory and SoftwareInventory are "special" because 74905916cefSCarson Labrado // they are two levels deep, but still need aggregated 75005916cefSCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID> 75105916cefSCarson Labrado // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID> 75205916cefSCarson Labrado std::string memberName; 75305916cefSCarson Labrado if (readUrlSegments(url, "redfish", "v1", "UpdateService", 75405916cefSCarson Labrado "SoftwareInventory", std::ref(memberName), 75505916cefSCarson Labrado OrMorePaths()) || 75605916cefSCarson Labrado readUrlSegments(url, "redfish", "v1", "UpdateService", 75705916cefSCarson Labrado "FirmwareInventory", std::ref(memberName), 75805916cefSCarson Labrado OrMorePaths()) || 75905916cefSCarson Labrado readUrlSegments(url, "redfish", "v1", std::ref(collectionName), 76005916cefSCarson Labrado std::ref(memberName), OrMorePaths())) 76105916cefSCarson Labrado { 76205916cefSCarson Labrado if (memberName.starts_with("5B247A")) 76305916cefSCarson Labrado { 76405916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Need to forward a request"; 76505916cefSCarson Labrado 76646a81465SCarson Labrado // Extract the prefix from the request's URI, retrieve the 76746a81465SCarson Labrado // associated satellite config information, and then forward the 76846a81465SCarson Labrado // request to that satellite. 76946a81465SCarson Labrado startAggregation(AggregationType::Resource, thisReq, asyncResp); 77005916cefSCarson Labrado return Result::NoLocalHandle; 77105916cefSCarson Labrado } 77205916cefSCarson Labrado return Result::LocalHandle; 77305916cefSCarson Labrado } 77405916cefSCarson Labrado 77505916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation not required"; 77605916cefSCarson Labrado return Result::LocalHandle; 77705916cefSCarson Labrado } 7787fb33566SCarson Labrado }; 7797fb33566SCarson Labrado 7807fb33566SCarson Labrado } // namespace redfish 781