17fb33566SCarson Labrado #pragma once 27fb33566SCarson Labrado 33ccb3adbSEd Tanous #include "aggregation_utils.hpp" 43ccb3adbSEd Tanous #include "dbus_utility.hpp" 53ccb3adbSEd Tanous #include "error_messages.hpp" 63ccb3adbSEd Tanous #include "http_client.hpp" 73ccb3adbSEd Tanous #include "http_connection.hpp" 83ccb3adbSEd Tanous 9411e6a11SCarson Labrado #include <boost/algorithm/string/predicate.hpp> 107fb33566SCarson Labrado 117e8890c5SCarson Labrado #include <array> 127e8890c5SCarson Labrado 137fb33566SCarson Labrado namespace redfish 147fb33566SCarson Labrado { 157fb33566SCarson Labrado 16d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB 17d14a48ffSCarson Labrado 1805916cefSCarson Labrado enum class Result 1905916cefSCarson Labrado { 2005916cefSCarson Labrado LocalHandle, 2105916cefSCarson Labrado NoLocalHandle 2205916cefSCarson Labrado }; 2305916cefSCarson Labrado 248fd333d6SCarson Labrado enum class SearchType 258fd333d6SCarson Labrado { 268fd333d6SCarson Labrado Collection, 278fd333d6SCarson Labrado CollOrCon, 288fd333d6SCarson Labrado ContainsSubordinate, 298fd333d6SCarson Labrado Resource 308fd333d6SCarson Labrado }; 318fd333d6SCarson Labrado 327e8890c5SCarson Labrado // clang-format off 337e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource 347e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a 357e8890c5SCarson Labrado // case-insensitive form of "uri". That version of the schema is associated 367e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification. Going forward, new URI 377e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as 387e8890c5SCarson Labrado // the spec is updated. NOTE: These have been pre-sorted in order to be 397e8890c5SCarson Labrado // compatible with binary search 407e8890c5SCarson Labrado constexpr std::array nonUriProperties{ 417e8890c5SCarson Labrado "@Redfish.ActionInfo", 427e8890c5SCarson Labrado // "@odata.context", // We can't fix /redfish/v1/$metadata URIs 437e8890c5SCarson Labrado "@odata.id", 447e8890c5SCarson Labrado // "Destination", // Only used by EventService and won't be a Redfish URI 457e8890c5SCarson Labrado // "HostName", // Isn't actually a Redfish URI 467e8890c5SCarson Labrado "Image", 477e8890c5SCarson Labrado "MetricProperty", 4832d7d8ebSCarson Labrado // "OriginOfCondition", // Is URI when in request, but is object in response 497e8890c5SCarson Labrado "TaskMonitor", 507e8890c5SCarson Labrado "target", // normal string, but target URI for POST to invoke an action 517e8890c5SCarson Labrado }; 527e8890c5SCarson Labrado // clang-format on 537e8890c5SCarson Labrado 548fd333d6SCarson Labrado // Search the top collection array to determine if the passed URI is of a 558fd333d6SCarson Labrado // desired type 568fd333d6SCarson Labrado inline bool searchCollectionsArray(std::string_view uri, 578fd333d6SCarson Labrado const SearchType searchType) 588fd333d6SCarson Labrado { 598fd333d6SCarson Labrado constexpr std::string_view serviceRootUri = "/redfish/v1"; 608fd333d6SCarson Labrado 618fd333d6SCarson Labrado // The passed URI must begin with "/redfish/v1", but we have to strip it 628fd333d6SCarson Labrado // from the URI since topCollections does not include it in its URIs 638fd333d6SCarson Labrado if (!uri.starts_with(serviceRootUri)) 648fd333d6SCarson Labrado { 658fd333d6SCarson Labrado return false; 668fd333d6SCarson Labrado } 678fd333d6SCarson Labrado 688fd333d6SCarson Labrado // Catch empty final segments such as "/redfish/v1/Chassis//" 698fd333d6SCarson Labrado if (uri.ends_with("//")) 708fd333d6SCarson Labrado { 718fd333d6SCarson Labrado return false; 728fd333d6SCarson Labrado } 738fd333d6SCarson Labrado 748fd333d6SCarson Labrado std::size_t parseCount = uri.size() - serviceRootUri.size(); 758fd333d6SCarson Labrado // Don't include the trailing "/" if it exists such as in "/redfish/v1/" 768fd333d6SCarson Labrado if (uri.ends_with("/")) 778fd333d6SCarson Labrado { 788fd333d6SCarson Labrado parseCount--; 798fd333d6SCarson Labrado } 808fd333d6SCarson Labrado 818fd333d6SCarson Labrado boost::urls::result<boost::urls::url_view> parsedUrl = 828fd333d6SCarson Labrado boost::urls::parse_relative_ref( 838fd333d6SCarson Labrado uri.substr(serviceRootUri.size(), parseCount)); 848fd333d6SCarson Labrado if (!parsedUrl) 858fd333d6SCarson Labrado { 868fd333d6SCarson Labrado BMCWEB_LOG_ERROR << "Failed to get target URI from " 878fd333d6SCarson Labrado << uri.substr(serviceRootUri.size()); 888fd333d6SCarson Labrado return false; 898fd333d6SCarson Labrado } 908fd333d6SCarson Labrado 918fd333d6SCarson Labrado if (!parsedUrl->segments().is_absolute() && !parsedUrl->segments().empty()) 928fd333d6SCarson Labrado { 938fd333d6SCarson Labrado return false; 948fd333d6SCarson Labrado } 958fd333d6SCarson Labrado 968fd333d6SCarson Labrado // If no segments() then the passed URI was either "/redfish/v1" or 978fd333d6SCarson Labrado // "/redfish/v1/". 988fd333d6SCarson Labrado if (parsedUrl->segments().empty()) 998fd333d6SCarson Labrado { 1008fd333d6SCarson Labrado return (searchType == SearchType::ContainsSubordinate) || 1018fd333d6SCarson Labrado (searchType == SearchType::CollOrCon); 1028fd333d6SCarson Labrado } 1038fd333d6SCarson Labrado 1048fd333d6SCarson Labrado const auto* it = std::lower_bound( 1058fd333d6SCarson Labrado topCollections.begin(), topCollections.end(), parsedUrl->buffer()); 1068fd333d6SCarson Labrado if (it == topCollections.end()) 1078fd333d6SCarson Labrado { 1088fd333d6SCarson Labrado // parsedUrl is alphabetically after the last entry in the array so it 1098fd333d6SCarson Labrado // can't be a top collection or up tree from a top collection 1108fd333d6SCarson Labrado return false; 1118fd333d6SCarson Labrado } 1128fd333d6SCarson Labrado 1138fd333d6SCarson Labrado boost::urls::url collectionUrl(*it); 1148fd333d6SCarson Labrado boost::urls::segments_view collectionSegments = collectionUrl.segments(); 1158fd333d6SCarson Labrado boost::urls::segments_view::iterator itCollection = 1168fd333d6SCarson Labrado collectionSegments.begin(); 1178fd333d6SCarson Labrado const boost::urls::segments_view::const_iterator endCollection = 1188fd333d6SCarson Labrado collectionSegments.end(); 1198fd333d6SCarson Labrado 1208fd333d6SCarson Labrado // Each segment in the passed URI should match the found collection 1218fd333d6SCarson Labrado for (const auto& segment : parsedUrl->segments()) 1228fd333d6SCarson Labrado { 1238fd333d6SCarson Labrado if (itCollection == endCollection) 1248fd333d6SCarson Labrado { 1258fd333d6SCarson Labrado // Leftover segments means the target is for an aggregation 1268fd333d6SCarson Labrado // supported resource 1278fd333d6SCarson Labrado return searchType == SearchType::Resource; 1288fd333d6SCarson Labrado } 1298fd333d6SCarson Labrado 1308fd333d6SCarson Labrado if (segment != (*itCollection)) 1318fd333d6SCarson Labrado { 1328fd333d6SCarson Labrado return false; 1338fd333d6SCarson Labrado } 1348fd333d6SCarson Labrado itCollection++; 1358fd333d6SCarson Labrado } 1368fd333d6SCarson Labrado 1378fd333d6SCarson Labrado // No remaining segments means the passed URI was a top level collection 1388fd333d6SCarson Labrado if (searchType == SearchType::Collection) 1398fd333d6SCarson Labrado { 1408fd333d6SCarson Labrado return itCollection == endCollection; 1418fd333d6SCarson Labrado } 1428fd333d6SCarson Labrado if (searchType == SearchType::ContainsSubordinate) 1438fd333d6SCarson Labrado { 1448fd333d6SCarson Labrado return itCollection != endCollection; 1458fd333d6SCarson Labrado } 1468fd333d6SCarson Labrado 1478fd333d6SCarson Labrado // Return this check instead of "true" in case other SearchTypes get added 1488fd333d6SCarson Labrado return searchType == SearchType::CollOrCon; 1498fd333d6SCarson Labrado } 1508fd333d6SCarson Labrado 1517e8890c5SCarson Labrado // Determines if the passed property contains a URI. Those property names 1527e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically 1537e8890c5SCarson Labrado // defined in the above array. 15426ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName) 1557e8890c5SCarson Labrado { 1567e8890c5SCarson Labrado return boost::iends_with(propertyName, "uri") || 1577e8890c5SCarson Labrado std::binary_search(nonUriProperties.begin(), nonUriProperties.end(), 1587e8890c5SCarson Labrado propertyName); 1597e8890c5SCarson Labrado } 1607e8890c5SCarson Labrado 1610af78d5aSKhang Kieu static inline void addPrefixToStringItem(std::string& strValue, 1620af78d5aSKhang Kieu std::string_view prefix) 1631c0bb5c6SCarson Labrado { 1641c0bb5c6SCarson Labrado // Make sure the value is a properly formatted URI 1650af78d5aSKhang Kieu auto parsed = boost::urls::parse_relative_ref(strValue); 1661c0bb5c6SCarson Labrado if (!parsed) 1671c0bb5c6SCarson Labrado { 1680af78d5aSKhang Kieu BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << strValue; 1691c0bb5c6SCarson Labrado return; 1701c0bb5c6SCarson Labrado } 1711c0bb5c6SCarson Labrado 1721c0bb5c6SCarson Labrado boost::urls::url_view thisUrl = *parsed; 1731c0bb5c6SCarson Labrado 174411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such as 175411e6a11SCarson Labrado // version mismatches between aggregator and satellite BMCs. For now 176411e6a11SCarson Labrado // assume that the aggregator has all the schemas and versions that the 177411e6a11SCarson Labrado // aggregated server has. 178411e6a11SCarson Labrado if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas", 179411e6a11SCarson Labrado crow::utility::OrMorePaths())) 180411e6a11SCarson Labrado { 181411e6a11SCarson Labrado BMCWEB_LOG_DEBUG << "Skipping JsonSchemas URI prefix fixing"; 182411e6a11SCarson Labrado return; 183411e6a11SCarson Labrado } 184411e6a11SCarson Labrado 18511987af6SCarson Labrado // The first two segments should be "/redfish/v1". We need to check that 18611987af6SCarson Labrado // before we can search topCollections 18711987af6SCarson Labrado if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 18811987af6SCarson Labrado crow::utility::OrMorePaths())) 1891c0bb5c6SCarson Labrado { 1901c0bb5c6SCarson Labrado return; 1911c0bb5c6SCarson Labrado } 1921c0bb5c6SCarson Labrado 19311987af6SCarson Labrado // Check array adding a segment each time until collection is identified 19411987af6SCarson Labrado // Add prefix to segment after the collection 19511987af6SCarson Labrado const boost::urls::segments_view urlSegments = thisUrl.segments(); 19611987af6SCarson Labrado bool addedPrefix = false; 19711987af6SCarson Labrado boost::urls::url url("/"); 19811987af6SCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 19911987af6SCarson Labrado const boost::urls::segments_view::const_iterator end = urlSegments.end(); 20011987af6SCarson Labrado 20111987af6SCarson Labrado // Skip past the leading "/redfish/v1" 20211987af6SCarson Labrado it++; 20311987af6SCarson Labrado it++; 20411987af6SCarson Labrado for (; it != end; it++) 2051c0bb5c6SCarson Labrado { 20611987af6SCarson Labrado // Trailing "/" will result in an empty segment. In that case we need 20711987af6SCarson Labrado // to return so we don't apply a prefix to top level collections such 20811987af6SCarson Labrado // as "/redfish/v1/Chassis/" 20911987af6SCarson Labrado if ((*it).empty()) 21011987af6SCarson Labrado { 211411e6a11SCarson Labrado return; 2121c0bb5c6SCarson Labrado } 2131c0bb5c6SCarson Labrado 21411987af6SCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 21511987af6SCarson Labrado url.buffer())) 2161c0bb5c6SCarson Labrado { 21711987af6SCarson Labrado std::string collectionItem(prefix); 21811987af6SCarson Labrado collectionItem += "_" + (*it); 21911987af6SCarson Labrado url.segments().push_back(collectionItem); 22011987af6SCarson Labrado it++; 22111987af6SCarson Labrado addedPrefix = true; 22211987af6SCarson Labrado break; 22311987af6SCarson Labrado } 22411987af6SCarson Labrado 22511987af6SCarson Labrado url.segments().push_back(*it); 22611987af6SCarson Labrado } 22711987af6SCarson Labrado 22811987af6SCarson Labrado // Finish constructing the URL here (if needed) to avoid additional checks 22911987af6SCarson Labrado for (; it != end; it++) 23011987af6SCarson Labrado { 23111987af6SCarson Labrado url.segments().push_back(*it); 23211987af6SCarson Labrado } 23311987af6SCarson Labrado 23411987af6SCarson Labrado if (addedPrefix) 23511987af6SCarson Labrado { 23611987af6SCarson Labrado url.segments().insert(url.segments().begin(), {"redfish", "v1"}); 2370af78d5aSKhang Kieu strValue = url.buffer(); 2381c0bb5c6SCarson Labrado } 2391c0bb5c6SCarson Labrado } 2401c0bb5c6SCarson Labrado 2410af78d5aSKhang Kieu static inline void addPrefixToItem(nlohmann::json& item, 2420af78d5aSKhang Kieu std::string_view prefix) 2430af78d5aSKhang Kieu { 2440af78d5aSKhang Kieu std::string* strValue = item.get_ptr<std::string*>(); 2450af78d5aSKhang Kieu if (strValue == nullptr) 2460af78d5aSKhang Kieu { 2470af78d5aSKhang Kieu BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; 2480af78d5aSKhang Kieu return; 2490af78d5aSKhang Kieu } 2500af78d5aSKhang Kieu addPrefixToStringItem(*strValue, prefix); 2510af78d5aSKhang Kieu item = *strValue; 2520af78d5aSKhang Kieu } 2530af78d5aSKhang Kieu 2540af78d5aSKhang Kieu static inline void addAggregatedHeaders(crow::Response& asyncResp, 25524dadc88SCarson Labrado const crow::Response& resp, 2560af78d5aSKhang Kieu std::string_view prefix) 2570af78d5aSKhang Kieu { 2580af78d5aSKhang Kieu if (!resp.getHeaderValue("Content-Type").empty()) 2590af78d5aSKhang Kieu { 2600af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::content_type, 2610af78d5aSKhang Kieu resp.getHeaderValue("Content-Type")); 2620af78d5aSKhang Kieu } 2630af78d5aSKhang Kieu if (!resp.getHeaderValue("Allow").empty()) 2640af78d5aSKhang Kieu { 2650af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::allow, 2660af78d5aSKhang Kieu resp.getHeaderValue("Allow")); 2670af78d5aSKhang Kieu } 2680af78d5aSKhang Kieu std::string_view header = resp.getHeaderValue("Location"); 2690af78d5aSKhang Kieu if (!header.empty()) 2700af78d5aSKhang Kieu { 2710af78d5aSKhang Kieu std::string location(header); 2720af78d5aSKhang Kieu addPrefixToStringItem(location, prefix); 2730af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::location, location); 2740af78d5aSKhang Kieu } 2750af78d5aSKhang Kieu if (!resp.getHeaderValue("Retry-After").empty()) 2760af78d5aSKhang Kieu { 2770af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::retry_after, 2780af78d5aSKhang Kieu resp.getHeaderValue("Retry-After")); 2790af78d5aSKhang Kieu } 2800af78d5aSKhang Kieu // TODO: we need special handling for Link Header Value 2810af78d5aSKhang Kieu } 2820af78d5aSKhang Kieu 283b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others 284b27e1cbeSCarson Labrado static inline void addPrefixToHeadersInResp(nlohmann::json& json, 285b27e1cbeSCarson Labrado std::string_view prefix) 286b27e1cbeSCarson Labrado { 287b27e1cbeSCarson Labrado // The passed in "HttpHeaders" should be an array of headers 288b27e1cbeSCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 289b27e1cbeSCarson Labrado if (array == nullptr) 290b27e1cbeSCarson Labrado { 291b27e1cbeSCarson Labrado BMCWEB_LOG_ERROR << "Field wasn't an array_t????"; 292b27e1cbeSCarson Labrado return; 293b27e1cbeSCarson Labrado } 294b27e1cbeSCarson Labrado 295b27e1cbeSCarson Labrado for (nlohmann::json& item : *array) 296b27e1cbeSCarson Labrado { 297b27e1cbeSCarson Labrado // Each header is a single string with the form "<Field>: <Value>" 298b27e1cbeSCarson Labrado std::string* strHeader = item.get_ptr<std::string*>(); 299b27e1cbeSCarson Labrado if (strHeader == nullptr) 300b27e1cbeSCarson Labrado { 301b27e1cbeSCarson Labrado BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; 302b27e1cbeSCarson Labrado continue; 303b27e1cbeSCarson Labrado } 304b27e1cbeSCarson Labrado 305b27e1cbeSCarson Labrado constexpr std::string_view location = "Location: "; 306b27e1cbeSCarson Labrado if (strHeader->starts_with(location)) 307b27e1cbeSCarson Labrado { 308b27e1cbeSCarson Labrado std::string header = strHeader->substr(location.size()); 309b27e1cbeSCarson Labrado addPrefixToStringItem(header, prefix); 310b27e1cbeSCarson Labrado *strHeader = std::string(location) + header; 311b27e1cbeSCarson Labrado } 312b27e1cbeSCarson Labrado } 313b27e1cbeSCarson Labrado } 314b27e1cbeSCarson Labrado 3151c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for 3167e8890c5SCarson Labrado // an aggregated resource. 3170af78d5aSKhang Kieu static inline void addPrefixes(nlohmann::json& json, std::string_view prefix) 3181c0bb5c6SCarson Labrado { 3191c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 3201c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 3211c0bb5c6SCarson Labrado if (object != nullptr) 3221c0bb5c6SCarson Labrado { 3231c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 3241c0bb5c6SCarson Labrado { 3257e8890c5SCarson Labrado if (isPropertyUri(item.first)) 3261c0bb5c6SCarson Labrado { 3277e8890c5SCarson Labrado addPrefixToItem(item.second, prefix); 3281c0bb5c6SCarson Labrado continue; 3291c0bb5c6SCarson Labrado } 3301c0bb5c6SCarson Labrado 331b27e1cbeSCarson Labrado // "HttpHeaders" contains HTTP headers. Among those we need to 332b27e1cbeSCarson Labrado // attempt to fix the "Location" header 333b27e1cbeSCarson Labrado if (item.first == "HttpHeaders") 334b27e1cbeSCarson Labrado { 335b27e1cbeSCarson Labrado addPrefixToHeadersInResp(item.second, prefix); 336b27e1cbeSCarson Labrado continue; 337b27e1cbeSCarson Labrado } 338b27e1cbeSCarson Labrado 3391c0bb5c6SCarson Labrado // Recusively parse the rest of the json 3401c0bb5c6SCarson Labrado addPrefixes(item.second, prefix); 3411c0bb5c6SCarson Labrado } 3421c0bb5c6SCarson Labrado return; 3431c0bb5c6SCarson Labrado } 3441c0bb5c6SCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 3451c0bb5c6SCarson Labrado if (array != nullptr) 3461c0bb5c6SCarson Labrado { 3471c0bb5c6SCarson Labrado for (nlohmann::json& item : *array) 3481c0bb5c6SCarson Labrado { 3491c0bb5c6SCarson Labrado addPrefixes(item, prefix); 3501c0bb5c6SCarson Labrado } 3511c0bb5c6SCarson Labrado } 3521c0bb5c6SCarson Labrado } 3531c0bb5c6SCarson Labrado 354d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode) 355a7a80296SCarson Labrado { 35632d7d8ebSCarson Labrado // Allow all response codes because we want to surface any satellite 35732d7d8ebSCarson Labrado // issue to the client 358d14a48ffSCarson Labrado BMCWEB_LOG_DEBUG << "Received " << respCode << " response from satellite"; 359d14a48ffSCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 360d14a48ffSCarson Labrado } 361d14a48ffSCarson Labrado 362d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy() 363d14a48ffSCarson Labrado { 364d14a48ffSCarson Labrado return {.maxRetryAttempts = 1, 365d14a48ffSCarson Labrado .requestByteLimit = aggregatorReadBodyLimit, 366d14a48ffSCarson Labrado .maxConnections = 20, 367d14a48ffSCarson Labrado .retryPolicyAction = "TerminateAfterRetries", 368d14a48ffSCarson Labrado .retryIntervalSecs = std::chrono::seconds(0), 369d14a48ffSCarson Labrado .invalidResp = aggregationRetryHandler}; 370d14a48ffSCarson Labrado } 371d14a48ffSCarson Labrado 372d14a48ffSCarson Labrado class RedfishAggregator 373d14a48ffSCarson Labrado { 374d14a48ffSCarson Labrado private: 375d14a48ffSCarson Labrado crow::HttpClient client; 376d14a48ffSCarson Labrado 377d14a48ffSCarson Labrado RedfishAggregator() : 378d14a48ffSCarson Labrado client(std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy())) 379d14a48ffSCarson Labrado { 380d14a48ffSCarson Labrado getSatelliteConfigs(constructorCallback); 3819fa6d147SNan Zhou } 382a7a80296SCarson Labrado 3837fb33566SCarson Labrado // Dummy callback used by the Constructor so that it can report the number 3847fb33566SCarson Labrado // of satellite configs when the class is first created 3857fb33566SCarson Labrado static void constructorCallback( 3868b2521a5SCarson Labrado const boost::system::error_code& ec, 3877fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 3887fb33566SCarson Labrado { 3897fb33566SCarson Labrado if (ec) 3907fb33566SCarson Labrado { 3918b2521a5SCarson Labrado BMCWEB_LOG_ERROR << "Something went wrong while querying dbus!"; 3927fb33566SCarson Labrado return; 3937fb33566SCarson Labrado } 3947fb33566SCarson Labrado 3958b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "There were " 3967fb33566SCarson Labrado << std::to_string(satelliteInfo.size()) 3978b2521a5SCarson Labrado << " satellite configs found at startup"; 3987fb33566SCarson Labrado } 3997fb33566SCarson Labrado 4007fb33566SCarson Labrado // Search D-Bus objects for satellite config objects and add their 4017fb33566SCarson Labrado // information if valid 4027fb33566SCarson Labrado static void findSatelliteConfigs( 4037fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects, 4047fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 4057fb33566SCarson Labrado { 4067fb33566SCarson Labrado for (const auto& objectPath : objects) 4077fb33566SCarson Labrado { 4087fb33566SCarson Labrado for (const auto& interface : objectPath.second) 4097fb33566SCarson Labrado { 4107fb33566SCarson Labrado if (interface.first == 4117fb33566SCarson Labrado "xyz.openbmc_project.Configuration.SatelliteController") 4127fb33566SCarson Labrado { 4137fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Found Satellite Controller at " 4147fb33566SCarson Labrado << objectPath.first.str; 4157fb33566SCarson Labrado 41605916cefSCarson Labrado if (!satelliteInfo.empty()) 41705916cefSCarson Labrado { 41805916cefSCarson Labrado BMCWEB_LOG_ERROR 41905916cefSCarson Labrado << "Redfish Aggregation only supports one satellite!"; 42005916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Clearing all satellite data"; 42105916cefSCarson Labrado satelliteInfo.clear(); 42205916cefSCarson Labrado return; 42305916cefSCarson Labrado } 42405916cefSCarson Labrado 42505916cefSCarson Labrado // For now assume there will only be one satellite config. 42605916cefSCarson Labrado // Assign it the name/prefix "5B247A" 42705916cefSCarson Labrado addSatelliteConfig("5B247A", interface.second, 42805916cefSCarson Labrado satelliteInfo); 4297fb33566SCarson Labrado } 4307fb33566SCarson Labrado } 4317fb33566SCarson Labrado } 4327fb33566SCarson Labrado } 4337fb33566SCarson Labrado 4347fb33566SCarson Labrado // Parse the properties of a satellite config object and add the 4357fb33566SCarson Labrado // configuration if the properties are valid 4367fb33566SCarson Labrado static void addSatelliteConfig( 43705916cefSCarson Labrado const std::string& name, 4387fb33566SCarson Labrado const dbus::utility::DBusPropertiesMap& properties, 4397fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 4407fb33566SCarson Labrado { 4417fb33566SCarson Labrado boost::urls::url url; 4427fb33566SCarson Labrado 4437fb33566SCarson Labrado for (const auto& prop : properties) 4447fb33566SCarson Labrado { 44505916cefSCarson Labrado if (prop.first == "Hostname") 4467fb33566SCarson Labrado { 4477fb33566SCarson Labrado const std::string* propVal = 4487fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 4497fb33566SCarson Labrado if (propVal == nullptr) 4507fb33566SCarson Labrado { 4517fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Hostname value"; 4527fb33566SCarson Labrado return; 4537fb33566SCarson Labrado } 4547fb33566SCarson Labrado url.set_host(*propVal); 4557fb33566SCarson Labrado } 4567fb33566SCarson Labrado 4577fb33566SCarson Labrado else if (prop.first == "Port") 4587fb33566SCarson Labrado { 4597fb33566SCarson Labrado const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 4607fb33566SCarson Labrado if (propVal == nullptr) 4617fb33566SCarson Labrado { 4627fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Port value"; 4637fb33566SCarson Labrado return; 4647fb33566SCarson Labrado } 4657fb33566SCarson Labrado 4667fb33566SCarson Labrado if (*propVal > std::numeric_limits<uint16_t>::max()) 4677fb33566SCarson Labrado { 4687fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Port value out of range"; 4697fb33566SCarson Labrado return; 4707fb33566SCarson Labrado } 471079360aeSEd Tanous url.set_port(std::to_string(static_cast<uint16_t>(*propVal))); 4727fb33566SCarson Labrado } 4737fb33566SCarson Labrado 4747fb33566SCarson Labrado else if (prop.first == "AuthType") 4757fb33566SCarson Labrado { 4767fb33566SCarson Labrado const std::string* propVal = 4777fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 4787fb33566SCarson Labrado if (propVal == nullptr) 4797fb33566SCarson Labrado { 4807fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid AuthType value"; 4817fb33566SCarson Labrado return; 4827fb33566SCarson Labrado } 4837fb33566SCarson Labrado 4847fb33566SCarson Labrado // For now assume authentication not required to communicate 4857fb33566SCarson Labrado // with the satellite BMC 4867fb33566SCarson Labrado if (*propVal != "None") 4877fb33566SCarson Labrado { 4887fb33566SCarson Labrado BMCWEB_LOG_ERROR 4897fb33566SCarson Labrado << "Unsupported AuthType value: " << *propVal 4907fb33566SCarson Labrado << ", only \"none\" is supported"; 4917fb33566SCarson Labrado return; 4927fb33566SCarson Labrado } 4937fb33566SCarson Labrado url.set_scheme("http"); 4947fb33566SCarson Labrado } 4957fb33566SCarson Labrado } // Finished reading properties 4967fb33566SCarson Labrado 4977fb33566SCarson Labrado // Make sure all required config information was made available 4987fb33566SCarson Labrado if (url.host().empty()) 4997fb33566SCarson Labrado { 5007fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; 5017fb33566SCarson Labrado return; 5027fb33566SCarson Labrado } 5037fb33566SCarson Labrado 5047fb33566SCarson Labrado if (!url.has_port()) 5057fb33566SCarson Labrado { 5067fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; 5077fb33566SCarson Labrado return; 5087fb33566SCarson Labrado } 5097fb33566SCarson Labrado 5107fb33566SCarson Labrado if (!url.has_scheme()) 5117fb33566SCarson Labrado { 5127fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name 5137fb33566SCarson Labrado << " missing AuthType"; 5147fb33566SCarson Labrado return; 5157fb33566SCarson Labrado } 5167fb33566SCarson Labrado 5177fb33566SCarson Labrado std::string resultString; 5187fb33566SCarson Labrado auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 5197fb33566SCarson Labrado if (result.second) 5207fb33566SCarson Labrado { 5217fb33566SCarson Labrado resultString = "Added new satellite config "; 5227fb33566SCarson Labrado } 5237fb33566SCarson Labrado else 5247fb33566SCarson Labrado { 5257fb33566SCarson Labrado resultString = "Updated existing satellite config "; 5267fb33566SCarson Labrado } 5277fb33566SCarson Labrado 5287fb33566SCarson Labrado BMCWEB_LOG_DEBUG << resultString << name << " at " 5297fb33566SCarson Labrado << result.first->second.scheme() << "://" 5307fb33566SCarson Labrado << result.first->second.encoded_host_and_port(); 5317fb33566SCarson Labrado } 5327fb33566SCarson Labrado 53346a81465SCarson Labrado enum AggregationType 53446a81465SCarson Labrado { 53546a81465SCarson Labrado Collection, 53646a81465SCarson Labrado Resource, 53746a81465SCarson Labrado }; 53846a81465SCarson Labrado 53946a81465SCarson Labrado static void 54046a81465SCarson Labrado startAggregation(AggregationType isCollection, 54146a81465SCarson Labrado const crow::Request& thisReq, 54246a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 54346a81465SCarson Labrado { 544db18fc98SCarson Labrado if ((isCollection == AggregationType::Collection) && 545db18fc98SCarson Labrado (thisReq.method() != boost::beast::http::verb::get)) 546db18fc98SCarson Labrado { 547db18fc98SCarson Labrado BMCWEB_LOG_DEBUG 548db18fc98SCarson Labrado << "Only aggregate GET requests to top level collections"; 549db18fc98SCarson Labrado return; 550db18fc98SCarson Labrado } 551db18fc98SCarson Labrado 55246a81465SCarson Labrado // Create a copy of thisReq so we we can still locally process the req 55346a81465SCarson Labrado std::error_code ec; 55446a81465SCarson Labrado auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 55546a81465SCarson Labrado if (ec) 55646a81465SCarson Labrado { 55746a81465SCarson Labrado BMCWEB_LOG_ERROR << "Failed to create copy of request"; 55846a81465SCarson Labrado if (isCollection != AggregationType::Collection) 55946a81465SCarson Labrado { 56046a81465SCarson Labrado messages::internalError(asyncResp->res); 56146a81465SCarson Labrado } 56246a81465SCarson Labrado return; 56346a81465SCarson Labrado } 56446a81465SCarson Labrado 56546a81465SCarson Labrado getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection, 56646a81465SCarson Labrado localReq, asyncResp)); 56746a81465SCarson Labrado } 56846a81465SCarson Labrado 569db18fc98SCarson Labrado static void findSatellite( 57046a81465SCarson Labrado const crow::Request& req, 57146a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 57246a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 57346a81465SCarson Labrado std::string_view memberName) 57446a81465SCarson Labrado { 57546a81465SCarson Labrado // Determine if the resource ID begins with a known prefix 57646a81465SCarson Labrado for (const auto& satellite : satelliteInfo) 57746a81465SCarson Labrado { 57846a81465SCarson Labrado std::string targetPrefix = satellite.first; 57946a81465SCarson Labrado targetPrefix += "_"; 58046a81465SCarson Labrado if (memberName.starts_with(targetPrefix)) 58146a81465SCarson Labrado { 58246a81465SCarson Labrado BMCWEB_LOG_DEBUG << "\"" << satellite.first 58346a81465SCarson Labrado << "\" is a known prefix"; 58446a81465SCarson Labrado 58546a81465SCarson Labrado // Remove the known prefix from the request's URI and 58646a81465SCarson Labrado // then forward to the associated satellite BMC 58746a81465SCarson Labrado getInstance().forwardRequest(req, asyncResp, satellite.first, 58846a81465SCarson Labrado satelliteInfo); 58946a81465SCarson Labrado return; 59046a81465SCarson Labrado } 59146a81465SCarson Labrado } 592db18fc98SCarson Labrado 593db18fc98SCarson Labrado // We didn't recognize the prefix and need to return a 404 59439662a3bSEd Tanous std::string nameStr = req.url().segments().back(); 595db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 59646a81465SCarson Labrado } 59746a81465SCarson Labrado 59846a81465SCarson Labrado // Intended to handle an incoming request based on if Redfish Aggregation 59946a81465SCarson Labrado // is enabled. Forwards request to satellite BMC if it exists. 60046a81465SCarson Labrado static void aggregateAndHandle( 60146a81465SCarson Labrado AggregationType isCollection, 60246a81465SCarson Labrado const std::shared_ptr<crow::Request>& sharedReq, 60346a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 6048b2521a5SCarson Labrado const boost::system::error_code& ec, 60546a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 60646a81465SCarson Labrado { 60746a81465SCarson Labrado if (sharedReq == nullptr) 60846a81465SCarson Labrado { 60946a81465SCarson Labrado return; 61046a81465SCarson Labrado } 6118b2521a5SCarson Labrado // Something went wrong while querying dbus 6128b2521a5SCarson Labrado if (ec) 6138b2521a5SCarson Labrado { 6148b2521a5SCarson Labrado messages::internalError(asyncResp->res); 6158b2521a5SCarson Labrado return; 6168b2521a5SCarson Labrado } 617db18fc98SCarson Labrado 618db18fc98SCarson Labrado // No satellite configs means we don't need to keep attempting to 619db18fc98SCarson Labrado // aggregate 620db18fc98SCarson Labrado if (satelliteInfo.empty()) 621db18fc98SCarson Labrado { 622db18fc98SCarson Labrado // For collections we'll also handle the request locally so we 623db18fc98SCarson Labrado // don't need to write an error code 624db18fc98SCarson Labrado if (isCollection == AggregationType::Resource) 625db18fc98SCarson Labrado { 62639662a3bSEd Tanous std::string nameStr = sharedReq->url().segments().back(); 627db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 628db18fc98SCarson Labrado } 629db18fc98SCarson Labrado return; 630db18fc98SCarson Labrado } 631db18fc98SCarson Labrado 63246a81465SCarson Labrado const crow::Request& thisReq = *sharedReq; 63346a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " 63446a81465SCarson Labrado << thisReq.target(); 63546a81465SCarson Labrado 63646a81465SCarson Labrado // We previously determined the request is for a collection. No need to 63746a81465SCarson Labrado // check again 63846a81465SCarson Labrado if (isCollection == AggregationType::Collection) 63946a81465SCarson Labrado { 64046a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregating a collection"; 6414c30e226SCarson Labrado // We need to use a specific response handler and send the 6424c30e226SCarson Labrado // request to all known satellites 6434c30e226SCarson Labrado getInstance().forwardCollectionRequests(thisReq, asyncResp, 6444c30e226SCarson Labrado satelliteInfo); 64546a81465SCarson Labrado return; 64646a81465SCarson Labrado } 64746a81465SCarson Labrado 64839662a3bSEd Tanous const boost::urls::segments_view urlSegments = thisReq.url().segments(); 6497c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 6507c4c52cbSCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 6517c4c52cbSCarson Labrado const boost::urls::segments_view::const_iterator end = 6527c4c52cbSCarson Labrado urlSegments.end(); 6537c4c52cbSCarson Labrado 6547c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 6557c4c52cbSCarson Labrado it++; 6567c4c52cbSCarson Labrado it++; 6577c4c52cbSCarson Labrado for (; it != end; it++) 65846a81465SCarson Labrado { 6597c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 6607c4c52cbSCarson Labrado currentUrl.buffer())) 6617c4c52cbSCarson Labrado { 6627c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 6637c4c52cbSCarson Labrado // must contain an aggregation prefix 6647c4c52cbSCarson Labrado findSatellite(thisReq, asyncResp, satelliteInfo, *it); 66546a81465SCarson Labrado return; 66646a81465SCarson Labrado } 66746a81465SCarson Labrado 6687c4c52cbSCarson Labrado currentUrl.segments().push_back(*it); 66946a81465SCarson Labrado } 670db18fc98SCarson Labrado 671db18fc98SCarson Labrado // We shouldn't reach this point since we should've hit one of the 672db18fc98SCarson Labrado // previous exits 673db18fc98SCarson Labrado messages::internalError(asyncResp->res); 67446a81465SCarson Labrado } 67546a81465SCarson Labrado 67646a81465SCarson Labrado // Attempt to forward a request to the satellite BMC associated with the 67746a81465SCarson Labrado // prefix. 67846a81465SCarson Labrado void forwardRequest( 67946a81465SCarson Labrado const crow::Request& thisReq, 68046a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 68146a81465SCarson Labrado const std::string& prefix, 68246a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 68346a81465SCarson Labrado { 68446a81465SCarson Labrado const auto& sat = satelliteInfo.find(prefix); 68546a81465SCarson Labrado if (sat == satelliteInfo.end()) 68646a81465SCarson Labrado { 68746a81465SCarson Labrado // Realistically this shouldn't get called since we perform an 68846a81465SCarson Labrado // earlier check to make sure the prefix exists 68946a81465SCarson Labrado BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix 69046a81465SCarson Labrado << "\""; 69146a81465SCarson Labrado return; 69246a81465SCarson Labrado } 69346a81465SCarson Labrado 69446a81465SCarson Labrado // We need to strip the prefix from the request's path 69546a81465SCarson Labrado std::string targetURI(thisReq.target()); 69646a81465SCarson Labrado size_t pos = targetURI.find(prefix + "_"); 69746a81465SCarson Labrado if (pos == std::string::npos) 69846a81465SCarson Labrado { 69946a81465SCarson Labrado // If this fails then something went wrong 70046a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix 70146a81465SCarson Labrado << "_\" from request URI"; 70246a81465SCarson Labrado messages::internalError(asyncResp->res); 70346a81465SCarson Labrado return; 70446a81465SCarson Labrado } 70546a81465SCarson Labrado targetURI.erase(pos, prefix.size() + 1); 70646a81465SCarson Labrado 70746a81465SCarson Labrado std::function<void(crow::Response&)> cb = 7081c0bb5c6SCarson Labrado std::bind_front(processResponse, prefix, asyncResp); 70946a81465SCarson Labrado 71046a81465SCarson Labrado std::string data = thisReq.req.body(); 711d14a48ffSCarson Labrado client.sendDataWithCallback(data, std::string(sat->second.host()), 712d14a48ffSCarson Labrado sat->second.port_number(), targetURI, 713d14a48ffSCarson Labrado false /*useSSL*/, thisReq.fields(), 714d14a48ffSCarson Labrado thisReq.method(), cb); 71546a81465SCarson Labrado } 71646a81465SCarson Labrado 7174c30e226SCarson Labrado // Forward a request for a collection URI to each known satellite BMC 7184c30e226SCarson Labrado void forwardCollectionRequests( 7194c30e226SCarson Labrado const crow::Request& thisReq, 7204c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 7214c30e226SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 7224c30e226SCarson Labrado { 7234c30e226SCarson Labrado for (const auto& sat : satelliteInfo) 7244c30e226SCarson Labrado { 7254c30e226SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 7264c30e226SCarson Labrado processCollectionResponse, sat.first, asyncResp); 7274c30e226SCarson Labrado 7284c30e226SCarson Labrado std::string targetURI(thisReq.target()); 7294c30e226SCarson Labrado std::string data = thisReq.req.body(); 730d14a48ffSCarson Labrado client.sendDataWithCallback(data, std::string(sat.second.host()), 731d14a48ffSCarson Labrado sat.second.port_number(), targetURI, 732d14a48ffSCarson Labrado false /*useSSL*/, thisReq.fields(), 733d14a48ffSCarson Labrado thisReq.method(), cb); 7344c30e226SCarson Labrado } 7354c30e226SCarson Labrado } 7364c30e226SCarson Labrado 73732d7d8ebSCarson Labrado public: 73832d7d8ebSCarson Labrado RedfishAggregator(const RedfishAggregator&) = delete; 73932d7d8ebSCarson Labrado RedfishAggregator& operator=(const RedfishAggregator&) = delete; 74032d7d8ebSCarson Labrado RedfishAggregator(RedfishAggregator&&) = delete; 74132d7d8ebSCarson Labrado RedfishAggregator& operator=(RedfishAggregator&&) = delete; 74232d7d8ebSCarson Labrado ~RedfishAggregator() = default; 74332d7d8ebSCarson Labrado 74432d7d8ebSCarson Labrado static RedfishAggregator& getInstance() 74532d7d8ebSCarson Labrado { 74632d7d8ebSCarson Labrado static RedfishAggregator handler; 74732d7d8ebSCarson Labrado return handler; 74832d7d8ebSCarson Labrado } 74932d7d8ebSCarson Labrado 7508b2521a5SCarson Labrado // Polls D-Bus to get all available satellite config information 7518b2521a5SCarson Labrado // Expects a handler which interacts with the returned configs 7528b2521a5SCarson Labrado static void getSatelliteConfigs( 7538b2521a5SCarson Labrado std::function< 7548b2521a5SCarson Labrado void(const boost::system::error_code&, 7558b2521a5SCarson Labrado const std::unordered_map<std::string, boost::urls::url>&)> 7568b2521a5SCarson Labrado handler) 7578b2521a5SCarson Labrado { 7588b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "Gathering satellite configs"; 7598b2521a5SCarson Labrado crow::connections::systemBus->async_method_call( 7608b2521a5SCarson Labrado [handler{std::move(handler)}]( 7618b2521a5SCarson Labrado const boost::system::error_code& ec, 7628b2521a5SCarson Labrado const dbus::utility::ManagedObjectType& objects) { 7638b2521a5SCarson Labrado std::unordered_map<std::string, boost::urls::url> satelliteInfo; 7648b2521a5SCarson Labrado if (ec) 7658b2521a5SCarson Labrado { 7668b2521a5SCarson Labrado BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", " 7678b2521a5SCarson Labrado << ec.message(); 7688b2521a5SCarson Labrado handler(ec, satelliteInfo); 7698b2521a5SCarson Labrado return; 7708b2521a5SCarson Labrado } 7718b2521a5SCarson Labrado 7728b2521a5SCarson Labrado // Maps a chosen alias representing a satellite BMC to a url 7738b2521a5SCarson Labrado // containing the information required to create a http 7748b2521a5SCarson Labrado // connection to the satellite 7758b2521a5SCarson Labrado findSatelliteConfigs(objects, satelliteInfo); 7768b2521a5SCarson Labrado 7778b2521a5SCarson Labrado if (!satelliteInfo.empty()) 7788b2521a5SCarson Labrado { 7798b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " 7808b2521a5SCarson Labrado << std::to_string(satelliteInfo.size()) 7818b2521a5SCarson Labrado << " satellite BMCs"; 7828b2521a5SCarson Labrado } 7838b2521a5SCarson Labrado else 7848b2521a5SCarson Labrado { 7858b2521a5SCarson Labrado BMCWEB_LOG_DEBUG 7868b2521a5SCarson Labrado << "No satellite BMCs detected. Redfish Aggregation not enabled"; 7878b2521a5SCarson Labrado } 7888b2521a5SCarson Labrado handler(ec, satelliteInfo); 7898b2521a5SCarson Labrado }, 7908b2521a5SCarson Labrado "xyz.openbmc_project.EntityManager", 7918b2521a5SCarson Labrado "/xyz/openbmc_project/inventory", 7928b2521a5SCarson Labrado "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 7938b2521a5SCarson Labrado } 7948b2521a5SCarson Labrado 79546a81465SCarson Labrado // Processes the response returned by a satellite BMC and loads its 79646a81465SCarson Labrado // contents into asyncResp 79746a81465SCarson Labrado static void 7981c0bb5c6SCarson Labrado processResponse(std::string_view prefix, 7991c0bb5c6SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 80046a81465SCarson Labrado crow::Response& resp) 80146a81465SCarson Labrado { 80243e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 80343e14d38SCarson Labrado // overwrite the response headers in that case 804*46b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 805*46b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 80643e14d38SCarson Labrado { 80743e14d38SCarson Labrado asyncResp->res.result(resp.result()); 80843e14d38SCarson Labrado return; 80943e14d38SCarson Labrado } 81043e14d38SCarson Labrado 81132d7d8ebSCarson Labrado // We want to attempt prefix fixing regardless of response code 81246a81465SCarson Labrado // The resp will not have a json component 81346a81465SCarson Labrado // We need to create a json from resp's stringResponse 814*46b30283SCarson Labrado std::string_view contentType = resp.getHeaderValue("Content-Type"); 815*46b30283SCarson Labrado if (boost::iequals(contentType, "application/json") || 816*46b30283SCarson Labrado boost::iequals(contentType, "application/json; charset=utf-8")) 81746a81465SCarson Labrado { 81846a81465SCarson Labrado nlohmann::json jsonVal = 81946a81465SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 82046a81465SCarson Labrado if (jsonVal.is_discarded()) 82146a81465SCarson Labrado { 82246a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 82346a81465SCarson Labrado messages::operationFailed(asyncResp->res); 82446a81465SCarson Labrado return; 82546a81465SCarson Labrado } 82646a81465SCarson Labrado 82746a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 82846a81465SCarson Labrado 8291c0bb5c6SCarson Labrado addPrefixes(jsonVal, prefix); 8301c0bb5c6SCarson Labrado 8311c0bb5c6SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 8321c0bb5c6SCarson Labrado 83346a81465SCarson Labrado asyncResp->res.result(resp.result()); 83446a81465SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 83546a81465SCarson Labrado 83646a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; 83746a81465SCarson Labrado } 83846a81465SCarson Labrado else 83946a81465SCarson Labrado { 8400af78d5aSKhang Kieu // We allow any Content-Type that is not "application/json" now 8410af78d5aSKhang Kieu asyncResp->res.result(resp.result()); 8420af78d5aSKhang Kieu asyncResp->res.write(resp.body()); 84346a81465SCarson Labrado } 8440af78d5aSKhang Kieu addAggregatedHeaders(asyncResp->res, resp, prefix); 84546a81465SCarson Labrado } 84646a81465SCarson Labrado 8474c30e226SCarson Labrado // Processes the collection response returned by a satellite BMC and merges 8484c30e226SCarson Labrado // its "@odata.id" values 8494c30e226SCarson Labrado static void processCollectionResponse( 8504c30e226SCarson Labrado const std::string& prefix, 8514c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 8524c30e226SCarson Labrado crow::Response& resp) 8534c30e226SCarson Labrado { 85443e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 85543e14d38SCarson Labrado // overwrite the response headers in that case 856*46b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 857*46b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 85843e14d38SCarson Labrado { 85943e14d38SCarson Labrado return; 86043e14d38SCarson Labrado } 86143e14d38SCarson Labrado 8624c30e226SCarson Labrado if (resp.resultInt() != 200) 8634c30e226SCarson Labrado { 8644c30e226SCarson Labrado BMCWEB_LOG_DEBUG 8654c30e226SCarson Labrado << "Collection resource does not exist in satellite BMC \"" 8664c30e226SCarson Labrado << prefix << "\""; 8674c30e226SCarson Labrado // Return the error if we haven't had any successes 8684c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 8694c30e226SCarson Labrado { 870*46b30283SCarson Labrado asyncResp->res.result(resp.result()); 871*46b30283SCarson Labrado asyncResp->res.write(resp.body()); 8724c30e226SCarson Labrado } 8734c30e226SCarson Labrado return; 8744c30e226SCarson Labrado } 8754c30e226SCarson Labrado 8764c30e226SCarson Labrado // The resp will not have a json component 8774c30e226SCarson Labrado // We need to create a json from resp's stringResponse 878*46b30283SCarson Labrado std::string_view contentType = resp.getHeaderValue("Content-Type"); 879*46b30283SCarson Labrado if (boost::iequals(contentType, "application/json") || 880*46b30283SCarson Labrado boost::iequals(contentType, "application/json; charset=utf-8")) 8814c30e226SCarson Labrado { 8824c30e226SCarson Labrado nlohmann::json jsonVal = 8834c30e226SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 8844c30e226SCarson Labrado if (jsonVal.is_discarded()) 8854c30e226SCarson Labrado { 8864c30e226SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 8874c30e226SCarson Labrado 8884c30e226SCarson Labrado // Notify the user if doing so won't overwrite a valid response 889*46b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 8904c30e226SCarson Labrado { 8914c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 8924c30e226SCarson Labrado } 8934c30e226SCarson Labrado return; 8944c30e226SCarson Labrado } 8954c30e226SCarson Labrado 8964c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 8974c30e226SCarson Labrado 8984c30e226SCarson Labrado // Now we need to add the prefix to the URIs contained in the 8994c30e226SCarson Labrado // response. 9004c30e226SCarson Labrado addPrefixes(jsonVal, prefix); 9014c30e226SCarson Labrado 9024c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 9034c30e226SCarson Labrado 9044c30e226SCarson Labrado // If this resource collection does not exist on the aggregating bmc 9054c30e226SCarson Labrado // and has not already been added from processing the response from 9064c30e226SCarson Labrado // a different satellite then we need to completely overwrite 9074c30e226SCarson Labrado // asyncResp 9084c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 9094c30e226SCarson Labrado { 9104c30e226SCarson Labrado // We only want to aggregate collections that contain a 9114c30e226SCarson Labrado // "Members" array 9124c30e226SCarson Labrado if ((!jsonVal.contains("Members")) && 9134c30e226SCarson Labrado (!jsonVal["Members"].is_array())) 9144c30e226SCarson Labrado { 9154c30e226SCarson Labrado BMCWEB_LOG_DEBUG 9164c30e226SCarson Labrado << "Skipping aggregating unsupported resource"; 9174c30e226SCarson Labrado return; 9184c30e226SCarson Labrado } 9194c30e226SCarson Labrado 9204c30e226SCarson Labrado BMCWEB_LOG_DEBUG 9214c30e226SCarson Labrado << "Collection does not exist, overwriting asyncResp"; 9224c30e226SCarson Labrado asyncResp->res.result(resp.result()); 9234c30e226SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 92443e14d38SCarson Labrado asyncResp->res.addHeader("Content-Type", "application/json"); 9254c30e226SCarson Labrado 9264c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; 9274c30e226SCarson Labrado } 9284c30e226SCarson Labrado else 9294c30e226SCarson Labrado { 9304c30e226SCarson Labrado // We only want to aggregate collections that contain a 9314c30e226SCarson Labrado // "Members" array 9324c30e226SCarson Labrado if ((!asyncResp->res.jsonValue.contains("Members")) && 9334c30e226SCarson Labrado (!asyncResp->res.jsonValue["Members"].is_array())) 9344c30e226SCarson Labrado 9354c30e226SCarson Labrado { 9364c30e226SCarson Labrado BMCWEB_LOG_DEBUG 9374c30e226SCarson Labrado << "Skipping aggregating unsupported resource"; 9384c30e226SCarson Labrado return; 9394c30e226SCarson Labrado } 9404c30e226SCarson Labrado 9414c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" 9424c30e226SCarson Labrado << prefix << "\" to collection"; 9434c30e226SCarson Labrado 9444c30e226SCarson Labrado // TODO: This is a potential race condition with multiple 9454c30e226SCarson Labrado // satellites and the aggregating bmc attempting to write to 9464c30e226SCarson Labrado // update this array. May need to cascade calls to the next 9474c30e226SCarson Labrado // satellite at the end of this function. 9484c30e226SCarson Labrado // This is presumably not a concern when there is only a single 9494c30e226SCarson Labrado // satellite since the aggregating bmc should have completed 9504c30e226SCarson Labrado // before the response is received from the satellite. 9514c30e226SCarson Labrado 9524c30e226SCarson Labrado auto& members = asyncResp->res.jsonValue["Members"]; 9534c30e226SCarson Labrado auto& satMembers = jsonVal["Members"]; 9544c30e226SCarson Labrado for (auto& satMem : satMembers) 9554c30e226SCarson Labrado { 9564c30e226SCarson Labrado members.push_back(std::move(satMem)); 9574c30e226SCarson Labrado } 9584c30e226SCarson Labrado asyncResp->res.jsonValue["Members@odata.count"] = 9594c30e226SCarson Labrado members.size(); 9604c30e226SCarson Labrado 9614c30e226SCarson Labrado // TODO: Do we need to sort() after updating the array? 9624c30e226SCarson Labrado } 9634c30e226SCarson Labrado } 9644c30e226SCarson Labrado else 9654c30e226SCarson Labrado { 9664c30e226SCarson Labrado BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 9674c30e226SCarson Labrado << "\""; 96843e14d38SCarson Labrado // We received a response that was not a json. 969*46b30283SCarson Labrado // Notify the user only if we did not receive any valid responses 970*46b30283SCarson Labrado // and if the resource collection does not already exist on the 971*46b30283SCarson Labrado // aggregating BMC 972*46b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 9734c30e226SCarson Labrado { 9744c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 9754c30e226SCarson Labrado } 9764c30e226SCarson Labrado } 9774c30e226SCarson Labrado } // End processCollectionResponse() 9784c30e226SCarson Labrado 979*46b30283SCarson Labrado // Processes the response returned by a satellite BMC and merges any 980*46b30283SCarson Labrado // properties whose "@odata.id" value is the URI or either a top level 981*46b30283SCarson Labrado // collection or is uptree from a top level collection 982*46b30283SCarson Labrado static void processContainsSubordinateResponse( 983*46b30283SCarson Labrado const std::string& prefix, 984*46b30283SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 985*46b30283SCarson Labrado crow::Response& resp) 986*46b30283SCarson Labrado { 987*46b30283SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 988*46b30283SCarson Labrado // overwrite the response headers in that case 989*46b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 990*46b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 991*46b30283SCarson Labrado { 992*46b30283SCarson Labrado return; 993*46b30283SCarson Labrado } 994*46b30283SCarson Labrado 995*46b30283SCarson Labrado if (resp.resultInt() != 200) 996*46b30283SCarson Labrado { 997*46b30283SCarson Labrado BMCWEB_LOG_DEBUG 998*46b30283SCarson Labrado << "Resource uptree from Collection does not exist in " 999*46b30283SCarson Labrado << "satellite BMC \"" << prefix << "\""; 1000*46b30283SCarson Labrado // Return the error if we haven't had any successes 1001*46b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 1002*46b30283SCarson Labrado { 1003*46b30283SCarson Labrado asyncResp->res.result(resp.result()); 1004*46b30283SCarson Labrado asyncResp->res.write(resp.body()); 1005*46b30283SCarson Labrado } 1006*46b30283SCarson Labrado return; 1007*46b30283SCarson Labrado } 1008*46b30283SCarson Labrado 1009*46b30283SCarson Labrado // The resp will not have a json component 1010*46b30283SCarson Labrado // We need to create a json from resp's stringResponse 1011*46b30283SCarson Labrado std::string_view contentType = resp.getHeaderValue("Content-Type"); 1012*46b30283SCarson Labrado if (boost::iequals(contentType, "application/json") || 1013*46b30283SCarson Labrado boost::iequals(contentType, "application/json; charset=utf-8")) 1014*46b30283SCarson Labrado { 1015*46b30283SCarson Labrado bool addedLinks = false; 1016*46b30283SCarson Labrado nlohmann::json jsonVal = 1017*46b30283SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 1018*46b30283SCarson Labrado if (jsonVal.is_discarded()) 1019*46b30283SCarson Labrado { 1020*46b30283SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 1021*46b30283SCarson Labrado 1022*46b30283SCarson Labrado // Notify the user if doing so won't overwrite a valid response 1023*46b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 1024*46b30283SCarson Labrado { 1025*46b30283SCarson Labrado messages::operationFailed(asyncResp->res); 1026*46b30283SCarson Labrado } 1027*46b30283SCarson Labrado return; 1028*46b30283SCarson Labrado } 1029*46b30283SCarson Labrado 1030*46b30283SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 1031*46b30283SCarson Labrado 1032*46b30283SCarson Labrado // Parse response and add properties missing from the AsyncResp 1033*46b30283SCarson Labrado // Valid properties will be of the form <property>.@odata.id and 1034*46b30283SCarson Labrado // @odata.id is a <URI>. In other words, the json should contain 1035*46b30283SCarson Labrado // multiple properties such that 1036*46b30283SCarson Labrado // {"<property>":{"@odata.id": "<URI>"}} 1037*46b30283SCarson Labrado nlohmann::json::object_t* object = 1038*46b30283SCarson Labrado jsonVal.get_ptr<nlohmann::json::object_t*>(); 1039*46b30283SCarson Labrado if (object == nullptr) 1040*46b30283SCarson Labrado { 1041*46b30283SCarson Labrado BMCWEB_LOG_ERROR << "Parsed JSON was not an object?"; 1042*46b30283SCarson Labrado return; 1043*46b30283SCarson Labrado } 1044*46b30283SCarson Labrado 1045*46b30283SCarson Labrado for (std::pair<const std::string, nlohmann::json>& prop : *object) 1046*46b30283SCarson Labrado { 1047*46b30283SCarson Labrado if (!prop.second.contains("@odata.id")) 1048*46b30283SCarson Labrado { 1049*46b30283SCarson Labrado continue; 1050*46b30283SCarson Labrado } 1051*46b30283SCarson Labrado 1052*46b30283SCarson Labrado std::string* strValue = 1053*46b30283SCarson Labrado prop.second["@odata.id"].get_ptr<std::string*>(); 1054*46b30283SCarson Labrado if (strValue == nullptr) 1055*46b30283SCarson Labrado { 1056*46b30283SCarson Labrado BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; 1057*46b30283SCarson Labrado continue; 1058*46b30283SCarson Labrado } 1059*46b30283SCarson Labrado if (!searchCollectionsArray(*strValue, SearchType::CollOrCon)) 1060*46b30283SCarson Labrado { 1061*46b30283SCarson Labrado continue; 1062*46b30283SCarson Labrado } 1063*46b30283SCarson Labrado 1064*46b30283SCarson Labrado BMCWEB_LOG_DEBUG << "Adding link for " << *strValue 1065*46b30283SCarson Labrado << " from BMC " << prefix; 1066*46b30283SCarson Labrado addedLinks = true; 1067*46b30283SCarson Labrado if (!asyncResp->res.jsonValue.contains(prop.first)) 1068*46b30283SCarson Labrado { 1069*46b30283SCarson Labrado // Only add the property if it did not already exist 1070*46b30283SCarson Labrado asyncResp->res.jsonValue[prop.first]["@odata.id"] = 1071*46b30283SCarson Labrado *strValue; 1072*46b30283SCarson Labrado continue; 1073*46b30283SCarson Labrado } 1074*46b30283SCarson Labrado } 1075*46b30283SCarson Labrado 1076*46b30283SCarson Labrado // If we added links to a previously unsuccessful (non-200) response 1077*46b30283SCarson Labrado // then we need to make sure the response contains the bare minimum 1078*46b30283SCarson Labrado // amount of additional information that we'd expect to have been 1079*46b30283SCarson Labrado // populated. 1080*46b30283SCarson Labrado if (addedLinks && (asyncResp->res.resultInt() != 200)) 1081*46b30283SCarson Labrado { 1082*46b30283SCarson Labrado // This resource didn't locally exist or an error 1083*46b30283SCarson Labrado // occurred while generating the response. Remove any 1084*46b30283SCarson Labrado // error messages and update the error code. 1085*46b30283SCarson Labrado asyncResp->res.jsonValue.erase( 1086*46b30283SCarson Labrado asyncResp->res.jsonValue.find("error")); 1087*46b30283SCarson Labrado asyncResp->res.result(resp.result()); 1088*46b30283SCarson Labrado 1089*46b30283SCarson Labrado const auto& it1 = object->find("@odata.id"); 1090*46b30283SCarson Labrado if (it1 != object->end()) 1091*46b30283SCarson Labrado { 1092*46b30283SCarson Labrado asyncResp->res.jsonValue["@odata.id"] = (it1->second); 1093*46b30283SCarson Labrado } 1094*46b30283SCarson Labrado const auto& it2 = object->find("@odata.type"); 1095*46b30283SCarson Labrado if (it2 != object->end()) 1096*46b30283SCarson Labrado { 1097*46b30283SCarson Labrado asyncResp->res.jsonValue["@odata.type"] = (it2->second); 1098*46b30283SCarson Labrado } 1099*46b30283SCarson Labrado const auto& it3 = object->find("Id"); 1100*46b30283SCarson Labrado if (it3 != object->end()) 1101*46b30283SCarson Labrado { 1102*46b30283SCarson Labrado asyncResp->res.jsonValue["Id"] = (it3->second); 1103*46b30283SCarson Labrado } 1104*46b30283SCarson Labrado const auto& it4 = object->find("Name"); 1105*46b30283SCarson Labrado if (it4 != object->end()) 1106*46b30283SCarson Labrado { 1107*46b30283SCarson Labrado asyncResp->res.jsonValue["Name"] = (it4->second); 1108*46b30283SCarson Labrado } 1109*46b30283SCarson Labrado } 1110*46b30283SCarson Labrado } 1111*46b30283SCarson Labrado else 1112*46b30283SCarson Labrado { 1113*46b30283SCarson Labrado BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 1114*46b30283SCarson Labrado << "\""; 1115*46b30283SCarson Labrado // We received as response that was not a json 1116*46b30283SCarson Labrado // Notify the user only if we did not receive any valid responses, 1117*46b30283SCarson Labrado // and if the resource does not already exist on the aggregating BMC 1118*46b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 1119*46b30283SCarson Labrado { 1120*46b30283SCarson Labrado messages::operationFailed(asyncResp->res); 1121*46b30283SCarson Labrado } 1122*46b30283SCarson Labrado } 1123*46b30283SCarson Labrado } 1124*46b30283SCarson Labrado 112505916cefSCarson Labrado // Entry point to Redfish Aggregation 112605916cefSCarson Labrado // Returns Result stating whether or not we still need to locally handle the 112705916cefSCarson Labrado // request 112805916cefSCarson Labrado static Result 112905916cefSCarson Labrado beginAggregation(const crow::Request& thisReq, 113005916cefSCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 113105916cefSCarson Labrado { 113205916cefSCarson Labrado using crow::utility::OrMorePaths; 113305916cefSCarson Labrado using crow::utility::readUrlSegments; 113439662a3bSEd Tanous const boost::urls::url_view url = thisReq.url(); 1135411e6a11SCarson Labrado 1136411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such 1137411e6a11SCarson Labrado // as version mismatches between aggregator and satellite BMCs. For 1138411e6a11SCarson Labrado // now assume that the aggregator has all the schemas and versions that 1139411e6a11SCarson Labrado // the aggregated server has. 1140411e6a11SCarson Labrado if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", 1141411e6a11SCarson Labrado crow::utility::OrMorePaths())) 1142411e6a11SCarson Labrado { 1143411e6a11SCarson Labrado return Result::LocalHandle; 1144411e6a11SCarson Labrado } 1145411e6a11SCarson Labrado 11467c4c52cbSCarson Labrado // The first two segments should be "/redfish/v1". We need to check 11477c4c52cbSCarson Labrado // that before we can search topCollections 11487c4c52cbSCarson Labrado if (!crow::utility::readUrlSegments(url, "redfish", "v1", 11497c4c52cbSCarson Labrado crow::utility::OrMorePaths())) 115046a81465SCarson Labrado { 115146a81465SCarson Labrado return Result::LocalHandle; 115246a81465SCarson Labrado } 115305916cefSCarson Labrado 11547c4c52cbSCarson Labrado // Parse the URI to see if it begins with a known top level collection 11557c4c52cbSCarson Labrado // such as: 11567c4c52cbSCarson Labrado // /redfish/v1/Chassis 11577c4c52cbSCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory 11587c4c52cbSCarson Labrado const boost::urls::segments_view urlSegments = url.segments(); 11597c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 11607c4c52cbSCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 11617c4c52cbSCarson Labrado const boost::urls::segments_view::const_iterator end = 11627c4c52cbSCarson Labrado urlSegments.end(); 116305916cefSCarson Labrado 11647c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 11657c4c52cbSCarson Labrado it++; 11667c4c52cbSCarson Labrado it++; 11677c4c52cbSCarson Labrado for (; it != end; it++) 116805916cefSCarson Labrado { 1169d4413c5bSGeorge Liu const std::string& collectionItem = *it; 11707c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 11717c4c52cbSCarson Labrado currentUrl.buffer())) 11727c4c52cbSCarson Labrado { 11737c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 11747c4c52cbSCarson Labrado // might contain an aggregation prefix 11758b2521a5SCarson Labrado // TODO: This needs to be rethought when we can support multiple 11768b2521a5SCarson Labrado // satellites due to 11778b2521a5SCarson Labrado // /redfish/v1/AggregationService/AggregationSources/5B247A 11788b2521a5SCarson Labrado // being a local resource describing the satellite 11798b2521a5SCarson Labrado if (collectionItem.starts_with("5B247A_")) 118005916cefSCarson Labrado { 118105916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Need to forward a request"; 118205916cefSCarson Labrado 118346a81465SCarson Labrado // Extract the prefix from the request's URI, retrieve the 11847c4c52cbSCarson Labrado // associated satellite config information, and then forward 11857c4c52cbSCarson Labrado // the request to that satellite. 11867c4c52cbSCarson Labrado startAggregation(AggregationType::Resource, thisReq, 11877c4c52cbSCarson Labrado asyncResp); 118805916cefSCarson Labrado return Result::NoLocalHandle; 118905916cefSCarson Labrado } 11907c4c52cbSCarson Labrado 11917c4c52cbSCarson Labrado // Handle collection URI with a trailing backslash 11927c4c52cbSCarson Labrado // e.g. /redfish/v1/Chassis/ 11937c4c52cbSCarson Labrado it++; 11947c4c52cbSCarson Labrado if ((it == end) && collectionItem.empty()) 11957c4c52cbSCarson Labrado { 11967c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, 11977c4c52cbSCarson Labrado asyncResp); 11987c4c52cbSCarson Labrado } 11997c4c52cbSCarson Labrado 12007c4c52cbSCarson Labrado // We didn't recognize the prefix or it's a collection with a 12017c4c52cbSCarson Labrado // trailing "/". In both cases we still want to locally handle 12027c4c52cbSCarson Labrado // the request 12037c4c52cbSCarson Labrado return Result::LocalHandle; 12047c4c52cbSCarson Labrado } 12057c4c52cbSCarson Labrado 12067c4c52cbSCarson Labrado currentUrl.segments().push_back(collectionItem); 12077c4c52cbSCarson Labrado } 12087c4c52cbSCarson Labrado 12097c4c52cbSCarson Labrado // If we made it here then currentUrl could contain a top level 12107c4c52cbSCarson Labrado // collection URI without a trailing "/", e.g. /redfish/v1/Chassis 12117c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 12127c4c52cbSCarson Labrado currentUrl.buffer())) 12137c4c52cbSCarson Labrado { 12147c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 121505916cefSCarson Labrado return Result::LocalHandle; 121605916cefSCarson Labrado } 121705916cefSCarson Labrado 121805916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation not required"; 121905916cefSCarson Labrado return Result::LocalHandle; 122005916cefSCarson Labrado } 12217fb33566SCarson Labrado }; 12227fb33566SCarson Labrado 12237fb33566SCarson Labrado } // namespace redfish 1224