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> 123544d2a7SEd Tanous #include <ranges> 133544d2a7SEd Tanous #include <string_view> 147e8890c5SCarson Labrado 157fb33566SCarson Labrado namespace redfish 167fb33566SCarson Labrado { 177fb33566SCarson Labrado 18d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB 19d14a48ffSCarson Labrado 2005916cefSCarson Labrado enum class Result 2105916cefSCarson Labrado { 2205916cefSCarson Labrado LocalHandle, 2305916cefSCarson Labrado NoLocalHandle 2405916cefSCarson Labrado }; 2505916cefSCarson Labrado 268fd333d6SCarson Labrado enum class SearchType 278fd333d6SCarson Labrado { 288fd333d6SCarson Labrado Collection, 298fd333d6SCarson Labrado CollOrCon, 308fd333d6SCarson Labrado ContainsSubordinate, 318fd333d6SCarson Labrado Resource 328fd333d6SCarson Labrado }; 338fd333d6SCarson Labrado 347e8890c5SCarson Labrado // clang-format off 357e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource 367e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a 377e8890c5SCarson Labrado // case-insensitive form of "uri". That version of the schema is associated 387e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification. Going forward, new URI 397e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as 407e8890c5SCarson Labrado // the spec is updated. NOTE: These have been pre-sorted in order to be 417e8890c5SCarson Labrado // compatible with binary search 427e8890c5SCarson Labrado constexpr std::array nonUriProperties{ 437e8890c5SCarson Labrado "@Redfish.ActionInfo", 447e8890c5SCarson Labrado // "@odata.context", // We can't fix /redfish/v1/$metadata URIs 457e8890c5SCarson Labrado "@odata.id", 467e8890c5SCarson Labrado // "Destination", // Only used by EventService and won't be a Redfish URI 477e8890c5SCarson Labrado // "HostName", // Isn't actually a Redfish URI 487e8890c5SCarson Labrado "Image", 497e8890c5SCarson Labrado "MetricProperty", 5032d7d8ebSCarson Labrado // "OriginOfCondition", // Is URI when in request, but is object in response 517e8890c5SCarson Labrado "TaskMonitor", 527e8890c5SCarson Labrado "target", // normal string, but target URI for POST to invoke an action 537e8890c5SCarson Labrado }; 547e8890c5SCarson Labrado // clang-format on 557e8890c5SCarson Labrado 568fd333d6SCarson Labrado // Search the top collection array to determine if the passed URI is of a 578fd333d6SCarson Labrado // desired type 588fd333d6SCarson Labrado inline bool searchCollectionsArray(std::string_view uri, 598fd333d6SCarson Labrado const SearchType searchType) 608fd333d6SCarson Labrado { 618fd333d6SCarson Labrado constexpr std::string_view serviceRootUri = "/redfish/v1"; 628fd333d6SCarson Labrado 638fd333d6SCarson Labrado // The passed URI must begin with "/redfish/v1", but we have to strip it 648fd333d6SCarson Labrado // from the URI since topCollections does not include it in its URIs 658fd333d6SCarson Labrado if (!uri.starts_with(serviceRootUri)) 668fd333d6SCarson Labrado { 678fd333d6SCarson Labrado return false; 688fd333d6SCarson Labrado } 698fd333d6SCarson Labrado 708fd333d6SCarson Labrado // Catch empty final segments such as "/redfish/v1/Chassis//" 718fd333d6SCarson Labrado if (uri.ends_with("//")) 728fd333d6SCarson Labrado { 738fd333d6SCarson Labrado return false; 748fd333d6SCarson Labrado } 758fd333d6SCarson Labrado 768fd333d6SCarson Labrado std::size_t parseCount = uri.size() - serviceRootUri.size(); 778fd333d6SCarson Labrado // Don't include the trailing "/" if it exists such as in "/redfish/v1/" 788fd333d6SCarson Labrado if (uri.ends_with("/")) 798fd333d6SCarson Labrado { 808fd333d6SCarson Labrado parseCount--; 818fd333d6SCarson Labrado } 828fd333d6SCarson Labrado 83*6fd29553SEd Tanous boost::system::result<boost::urls::url_view> parsedUrl = 848fd333d6SCarson Labrado boost::urls::parse_relative_ref( 858fd333d6SCarson Labrado uri.substr(serviceRootUri.size(), parseCount)); 868fd333d6SCarson Labrado if (!parsedUrl) 878fd333d6SCarson Labrado { 8862598e31SEd Tanous BMCWEB_LOG_ERROR("Failed to get target URI from {}", 8962598e31SEd Tanous uri.substr(serviceRootUri.size())); 908fd333d6SCarson Labrado return false; 918fd333d6SCarson Labrado } 928fd333d6SCarson Labrado 938fd333d6SCarson Labrado if (!parsedUrl->segments().is_absolute() && !parsedUrl->segments().empty()) 948fd333d6SCarson Labrado { 958fd333d6SCarson Labrado return false; 968fd333d6SCarson Labrado } 978fd333d6SCarson Labrado 988fd333d6SCarson Labrado // If no segments() then the passed URI was either "/redfish/v1" or 998fd333d6SCarson Labrado // "/redfish/v1/". 1008fd333d6SCarson Labrado if (parsedUrl->segments().empty()) 1018fd333d6SCarson Labrado { 1028fd333d6SCarson Labrado return (searchType == SearchType::ContainsSubordinate) || 1038fd333d6SCarson Labrado (searchType == SearchType::CollOrCon); 1048fd333d6SCarson Labrado } 1053544d2a7SEd Tanous std::string_view url = parsedUrl->buffer(); 1063544d2a7SEd Tanous const auto* it = std::ranges::lower_bound(topCollections, url); 1078fd333d6SCarson Labrado if (it == topCollections.end()) 1088fd333d6SCarson Labrado { 1098fd333d6SCarson Labrado // parsedUrl is alphabetically after the last entry in the array so it 1108fd333d6SCarson Labrado // can't be a top collection or up tree from a top collection 1118fd333d6SCarson Labrado return false; 1128fd333d6SCarson Labrado } 1138fd333d6SCarson Labrado 1148fd333d6SCarson Labrado boost::urls::url collectionUrl(*it); 1158fd333d6SCarson Labrado boost::urls::segments_view collectionSegments = collectionUrl.segments(); 1168fd333d6SCarson Labrado boost::urls::segments_view::iterator itCollection = 1178fd333d6SCarson Labrado collectionSegments.begin(); 1188fd333d6SCarson Labrado const boost::urls::segments_view::const_iterator endCollection = 1198fd333d6SCarson Labrado collectionSegments.end(); 1208fd333d6SCarson Labrado 1218fd333d6SCarson Labrado // Each segment in the passed URI should match the found collection 1228fd333d6SCarson Labrado for (const auto& segment : parsedUrl->segments()) 1238fd333d6SCarson Labrado { 1248fd333d6SCarson Labrado if (itCollection == endCollection) 1258fd333d6SCarson Labrado { 1268fd333d6SCarson Labrado // Leftover segments means the target is for an aggregation 1278fd333d6SCarson Labrado // supported resource 1288fd333d6SCarson Labrado return searchType == SearchType::Resource; 1298fd333d6SCarson Labrado } 1308fd333d6SCarson Labrado 1318fd333d6SCarson Labrado if (segment != (*itCollection)) 1328fd333d6SCarson Labrado { 1338fd333d6SCarson Labrado return false; 1348fd333d6SCarson Labrado } 1358fd333d6SCarson Labrado itCollection++; 1368fd333d6SCarson Labrado } 1378fd333d6SCarson Labrado 1388fd333d6SCarson Labrado // No remaining segments means the passed URI was a top level collection 1398fd333d6SCarson Labrado if (searchType == SearchType::Collection) 1408fd333d6SCarson Labrado { 1418fd333d6SCarson Labrado return itCollection == endCollection; 1428fd333d6SCarson Labrado } 1438fd333d6SCarson Labrado if (searchType == SearchType::ContainsSubordinate) 1448fd333d6SCarson Labrado { 1458fd333d6SCarson Labrado return itCollection != endCollection; 1468fd333d6SCarson Labrado } 1478fd333d6SCarson Labrado 1488fd333d6SCarson Labrado // Return this check instead of "true" in case other SearchTypes get added 1498fd333d6SCarson Labrado return searchType == SearchType::CollOrCon; 1508fd333d6SCarson Labrado } 1518fd333d6SCarson Labrado 1527e8890c5SCarson Labrado // Determines if the passed property contains a URI. Those property names 1537e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically 1547e8890c5SCarson Labrado // defined in the above array. 15526ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName) 1567e8890c5SCarson Labrado { 1577e8890c5SCarson Labrado return boost::iends_with(propertyName, "uri") || 1587e8890c5SCarson Labrado std::binary_search(nonUriProperties.begin(), nonUriProperties.end(), 1597e8890c5SCarson Labrado propertyName); 1607e8890c5SCarson Labrado } 1617e8890c5SCarson Labrado 1620af78d5aSKhang Kieu static inline void addPrefixToStringItem(std::string& strValue, 1630af78d5aSKhang Kieu std::string_view prefix) 1641c0bb5c6SCarson Labrado { 1651c0bb5c6SCarson Labrado // Make sure the value is a properly formatted URI 1660af78d5aSKhang Kieu auto parsed = boost::urls::parse_relative_ref(strValue); 1671c0bb5c6SCarson Labrado if (!parsed) 1681c0bb5c6SCarson Labrado { 169bf2ddedeSCarson Labrado // Note that DMTF URIs such as 170bf2ddedeSCarson Labrado // https://redfish.dmtf.org/registries/Base.1.15.0.json will fail this 171bf2ddedeSCarson Labrado // check and that's okay 172bf2ddedeSCarson Labrado BMCWEB_LOG_DEBUG("Couldn't parse URI from resource {}", strValue); 1731c0bb5c6SCarson Labrado return; 1741c0bb5c6SCarson Labrado } 1751c0bb5c6SCarson Labrado 1761c0bb5c6SCarson Labrado boost::urls::url_view thisUrl = *parsed; 1771c0bb5c6SCarson Labrado 178411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such as 179411e6a11SCarson Labrado // version mismatches between aggregator and satellite BMCs. For now 180411e6a11SCarson Labrado // assume that the aggregator has all the schemas and versions that the 181411e6a11SCarson Labrado // aggregated server has. 182411e6a11SCarson Labrado if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas", 183411e6a11SCarson Labrado crow::utility::OrMorePaths())) 184411e6a11SCarson Labrado { 18562598e31SEd Tanous BMCWEB_LOG_DEBUG("Skipping JsonSchemas URI prefix fixing"); 186411e6a11SCarson Labrado return; 187411e6a11SCarson Labrado } 188411e6a11SCarson Labrado 18911987af6SCarson Labrado // The first two segments should be "/redfish/v1". We need to check that 19011987af6SCarson Labrado // before we can search topCollections 19111987af6SCarson Labrado if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 19211987af6SCarson Labrado crow::utility::OrMorePaths())) 1931c0bb5c6SCarson Labrado { 1941c0bb5c6SCarson Labrado return; 1951c0bb5c6SCarson Labrado } 1961c0bb5c6SCarson Labrado 19711987af6SCarson Labrado // Check array adding a segment each time until collection is identified 19811987af6SCarson Labrado // Add prefix to segment after the collection 19911987af6SCarson Labrado const boost::urls::segments_view urlSegments = thisUrl.segments(); 20011987af6SCarson Labrado bool addedPrefix = false; 20111987af6SCarson Labrado boost::urls::url url("/"); 20211987af6SCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 20311987af6SCarson Labrado const boost::urls::segments_view::const_iterator end = urlSegments.end(); 20411987af6SCarson Labrado 20511987af6SCarson Labrado // Skip past the leading "/redfish/v1" 20611987af6SCarson Labrado it++; 20711987af6SCarson Labrado it++; 20811987af6SCarson Labrado for (; it != end; it++) 2091c0bb5c6SCarson Labrado { 21011987af6SCarson Labrado // Trailing "/" will result in an empty segment. In that case we need 21111987af6SCarson Labrado // to return so we don't apply a prefix to top level collections such 21211987af6SCarson Labrado // as "/redfish/v1/Chassis/" 21311987af6SCarson Labrado if ((*it).empty()) 21411987af6SCarson Labrado { 215411e6a11SCarson Labrado return; 2161c0bb5c6SCarson Labrado } 2171c0bb5c6SCarson Labrado 21811987af6SCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 21911987af6SCarson Labrado url.buffer())) 2201c0bb5c6SCarson Labrado { 22111987af6SCarson Labrado std::string collectionItem(prefix); 22211987af6SCarson Labrado collectionItem += "_" + (*it); 22311987af6SCarson Labrado url.segments().push_back(collectionItem); 22411987af6SCarson Labrado it++; 22511987af6SCarson Labrado addedPrefix = true; 22611987af6SCarson Labrado break; 22711987af6SCarson Labrado } 22811987af6SCarson Labrado 22911987af6SCarson Labrado url.segments().push_back(*it); 23011987af6SCarson Labrado } 23111987af6SCarson Labrado 23211987af6SCarson Labrado // Finish constructing the URL here (if needed) to avoid additional checks 23311987af6SCarson Labrado for (; it != end; it++) 23411987af6SCarson Labrado { 23511987af6SCarson Labrado url.segments().push_back(*it); 23611987af6SCarson Labrado } 23711987af6SCarson Labrado 23811987af6SCarson Labrado if (addedPrefix) 23911987af6SCarson Labrado { 24011987af6SCarson Labrado url.segments().insert(url.segments().begin(), {"redfish", "v1"}); 2410af78d5aSKhang Kieu strValue = url.buffer(); 2421c0bb5c6SCarson Labrado } 2431c0bb5c6SCarson Labrado } 2441c0bb5c6SCarson Labrado 2450af78d5aSKhang Kieu static inline void addPrefixToItem(nlohmann::json& item, 2460af78d5aSKhang Kieu std::string_view prefix) 2470af78d5aSKhang Kieu { 2480af78d5aSKhang Kieu std::string* strValue = item.get_ptr<std::string*>(); 2490af78d5aSKhang Kieu if (strValue == nullptr) 2500af78d5aSKhang Kieu { 251bf2ddedeSCarson Labrado // Values for properties like "InvalidURI" and "ResourceMissingAtURI" 252bf2ddedeSCarson Labrado // from within the Base Registry are objects instead of strings and will 253bf2ddedeSCarson Labrado // fall into this check 254bf2ddedeSCarson Labrado BMCWEB_LOG_DEBUG("Field was not a string"); 2550af78d5aSKhang Kieu return; 2560af78d5aSKhang Kieu } 2570af78d5aSKhang Kieu addPrefixToStringItem(*strValue, prefix); 2580af78d5aSKhang Kieu item = *strValue; 2590af78d5aSKhang Kieu } 2600af78d5aSKhang Kieu 2610af78d5aSKhang Kieu static inline void addAggregatedHeaders(crow::Response& asyncResp, 26224dadc88SCarson Labrado const crow::Response& resp, 2630af78d5aSKhang Kieu std::string_view prefix) 2640af78d5aSKhang Kieu { 2650af78d5aSKhang Kieu if (!resp.getHeaderValue("Content-Type").empty()) 2660af78d5aSKhang Kieu { 2670af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::content_type, 2680af78d5aSKhang Kieu resp.getHeaderValue("Content-Type")); 2690af78d5aSKhang Kieu } 2700af78d5aSKhang Kieu if (!resp.getHeaderValue("Allow").empty()) 2710af78d5aSKhang Kieu { 2720af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::allow, 2730af78d5aSKhang Kieu resp.getHeaderValue("Allow")); 2740af78d5aSKhang Kieu } 2750af78d5aSKhang Kieu std::string_view header = resp.getHeaderValue("Location"); 2760af78d5aSKhang Kieu if (!header.empty()) 2770af78d5aSKhang Kieu { 2780af78d5aSKhang Kieu std::string location(header); 2790af78d5aSKhang Kieu addPrefixToStringItem(location, prefix); 2800af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::location, location); 2810af78d5aSKhang Kieu } 2820af78d5aSKhang Kieu if (!resp.getHeaderValue("Retry-After").empty()) 2830af78d5aSKhang Kieu { 2840af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::retry_after, 2850af78d5aSKhang Kieu resp.getHeaderValue("Retry-After")); 2860af78d5aSKhang Kieu } 2870af78d5aSKhang Kieu // TODO: we need special handling for Link Header Value 2880af78d5aSKhang Kieu } 2890af78d5aSKhang Kieu 290b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others 291b27e1cbeSCarson Labrado static inline void addPrefixToHeadersInResp(nlohmann::json& json, 292b27e1cbeSCarson Labrado std::string_view prefix) 293b27e1cbeSCarson Labrado { 294b27e1cbeSCarson Labrado // The passed in "HttpHeaders" should be an array of headers 295b27e1cbeSCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 296b27e1cbeSCarson Labrado if (array == nullptr) 297b27e1cbeSCarson Labrado { 29862598e31SEd Tanous BMCWEB_LOG_ERROR("Field wasn't an array_t????"); 299b27e1cbeSCarson Labrado return; 300b27e1cbeSCarson Labrado } 301b27e1cbeSCarson Labrado 302b27e1cbeSCarson Labrado for (nlohmann::json& item : *array) 303b27e1cbeSCarson Labrado { 304b27e1cbeSCarson Labrado // Each header is a single string with the form "<Field>: <Value>" 305b27e1cbeSCarson Labrado std::string* strHeader = item.get_ptr<std::string*>(); 306b27e1cbeSCarson Labrado if (strHeader == nullptr) 307b27e1cbeSCarson Labrado { 30862598e31SEd Tanous BMCWEB_LOG_CRITICAL("Field wasn't a string????"); 309b27e1cbeSCarson Labrado continue; 310b27e1cbeSCarson Labrado } 311b27e1cbeSCarson Labrado 312b27e1cbeSCarson Labrado constexpr std::string_view location = "Location: "; 313b27e1cbeSCarson Labrado if (strHeader->starts_with(location)) 314b27e1cbeSCarson Labrado { 315b27e1cbeSCarson Labrado std::string header = strHeader->substr(location.size()); 316b27e1cbeSCarson Labrado addPrefixToStringItem(header, prefix); 317b27e1cbeSCarson Labrado *strHeader = std::string(location) + header; 318b27e1cbeSCarson Labrado } 319b27e1cbeSCarson Labrado } 320b27e1cbeSCarson Labrado } 321b27e1cbeSCarson Labrado 3221c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for 3237e8890c5SCarson Labrado // an aggregated resource. 3240af78d5aSKhang Kieu static inline void addPrefixes(nlohmann::json& json, std::string_view prefix) 3251c0bb5c6SCarson Labrado { 3261c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 3271c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 3281c0bb5c6SCarson Labrado if (object != nullptr) 3291c0bb5c6SCarson Labrado { 3301c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 3311c0bb5c6SCarson Labrado { 3327e8890c5SCarson Labrado if (isPropertyUri(item.first)) 3331c0bb5c6SCarson Labrado { 3347e8890c5SCarson Labrado addPrefixToItem(item.second, prefix); 3351c0bb5c6SCarson Labrado continue; 3361c0bb5c6SCarson Labrado } 3371c0bb5c6SCarson Labrado 338b27e1cbeSCarson Labrado // "HttpHeaders" contains HTTP headers. Among those we need to 339b27e1cbeSCarson Labrado // attempt to fix the "Location" header 340b27e1cbeSCarson Labrado if (item.first == "HttpHeaders") 341b27e1cbeSCarson Labrado { 342b27e1cbeSCarson Labrado addPrefixToHeadersInResp(item.second, prefix); 343b27e1cbeSCarson Labrado continue; 344b27e1cbeSCarson Labrado } 345b27e1cbeSCarson Labrado 3461c0bb5c6SCarson Labrado // Recusively parse the rest of the json 3471c0bb5c6SCarson Labrado addPrefixes(item.second, prefix); 3481c0bb5c6SCarson Labrado } 3491c0bb5c6SCarson Labrado return; 3501c0bb5c6SCarson Labrado } 3511c0bb5c6SCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 3521c0bb5c6SCarson Labrado if (array != nullptr) 3531c0bb5c6SCarson Labrado { 3541c0bb5c6SCarson Labrado for (nlohmann::json& item : *array) 3551c0bb5c6SCarson Labrado { 3561c0bb5c6SCarson Labrado addPrefixes(item, prefix); 3571c0bb5c6SCarson Labrado } 3581c0bb5c6SCarson Labrado } 3591c0bb5c6SCarson Labrado } 3601c0bb5c6SCarson Labrado 361d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode) 362a7a80296SCarson Labrado { 36332d7d8ebSCarson Labrado // Allow all response codes because we want to surface any satellite 36432d7d8ebSCarson Labrado // issue to the client 36562598e31SEd Tanous BMCWEB_LOG_DEBUG("Received {} response from satellite", respCode); 366d14a48ffSCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 367d14a48ffSCarson Labrado } 368d14a48ffSCarson Labrado 369d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy() 370d14a48ffSCarson Labrado { 371d14a48ffSCarson Labrado return {.maxRetryAttempts = 1, 372d14a48ffSCarson Labrado .requestByteLimit = aggregatorReadBodyLimit, 373d14a48ffSCarson Labrado .maxConnections = 20, 374d14a48ffSCarson Labrado .retryPolicyAction = "TerminateAfterRetries", 375d14a48ffSCarson Labrado .retryIntervalSecs = std::chrono::seconds(0), 376d14a48ffSCarson Labrado .invalidResp = aggregationRetryHandler}; 377d14a48ffSCarson Labrado } 378d14a48ffSCarson Labrado 379d14a48ffSCarson Labrado class RedfishAggregator 380d14a48ffSCarson Labrado { 381d14a48ffSCarson Labrado private: 382d14a48ffSCarson Labrado crow::HttpClient client; 383d14a48ffSCarson Labrado 3847fb33566SCarson Labrado // Dummy callback used by the Constructor so that it can report the number 3857fb33566SCarson Labrado // of satellite configs when the class is first created 3867fb33566SCarson Labrado static void constructorCallback( 3878b2521a5SCarson Labrado const boost::system::error_code& ec, 3887fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 3897fb33566SCarson Labrado { 3907fb33566SCarson Labrado if (ec) 3917fb33566SCarson Labrado { 39262598e31SEd Tanous BMCWEB_LOG_ERROR("Something went wrong while querying dbus!"); 3937fb33566SCarson Labrado return; 3947fb33566SCarson Labrado } 3957fb33566SCarson Labrado 39662598e31SEd Tanous BMCWEB_LOG_DEBUG("There were {} satellite configs found at startup", 39762598e31SEd Tanous std::to_string(satelliteInfo.size())); 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 { 41362598e31SEd Tanous BMCWEB_LOG_DEBUG("Found Satellite Controller at {}", 41462598e31SEd Tanous objectPath.first.str); 4157fb33566SCarson Labrado 41605916cefSCarson Labrado if (!satelliteInfo.empty()) 41705916cefSCarson Labrado { 41862598e31SEd Tanous BMCWEB_LOG_ERROR( 41962598e31SEd Tanous "Redfish Aggregation only supports one satellite!"); 42062598e31SEd Tanous 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 { 45162598e31SEd Tanous 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 { 46262598e31SEd Tanous 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 { 46862598e31SEd Tanous 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 { 48062598e31SEd Tanous 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 { 48862598e31SEd Tanous BMCWEB_LOG_ERROR( 48962598e31SEd Tanous "Unsupported AuthType value: {}, only \"none\" is supported", 49062598e31SEd Tanous *propVal); 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 { 50062598e31SEd Tanous BMCWEB_LOG_ERROR("Satellite config {} missing Host", name); 5017fb33566SCarson Labrado return; 5027fb33566SCarson Labrado } 5037fb33566SCarson Labrado 5047fb33566SCarson Labrado if (!url.has_port()) 5057fb33566SCarson Labrado { 50662598e31SEd Tanous BMCWEB_LOG_ERROR("Satellite config {} missing Port", name); 5077fb33566SCarson Labrado return; 5087fb33566SCarson Labrado } 5097fb33566SCarson Labrado 5107fb33566SCarson Labrado if (!url.has_scheme()) 5117fb33566SCarson Labrado { 51262598e31SEd Tanous BMCWEB_LOG_ERROR("Satellite config {} missing AuthType", name); 5137fb33566SCarson Labrado return; 5147fb33566SCarson Labrado } 5157fb33566SCarson Labrado 5167fb33566SCarson Labrado std::string resultString; 5177fb33566SCarson Labrado auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 5187fb33566SCarson Labrado if (result.second) 5197fb33566SCarson Labrado { 5207fb33566SCarson Labrado resultString = "Added new satellite config "; 5217fb33566SCarson Labrado } 5227fb33566SCarson Labrado else 5237fb33566SCarson Labrado { 5247fb33566SCarson Labrado resultString = "Updated existing satellite config "; 5257fb33566SCarson Labrado } 5267fb33566SCarson Labrado 52762598e31SEd Tanous BMCWEB_LOG_DEBUG("{}{} at {}://{}", resultString, name, 52862598e31SEd Tanous result.first->second.scheme(), 52962598e31SEd Tanous result.first->second.encoded_host_and_port()); 5307fb33566SCarson Labrado } 5317fb33566SCarson Labrado 53246a81465SCarson Labrado enum AggregationType 53346a81465SCarson Labrado { 53446a81465SCarson Labrado Collection, 535e002dbc0SCarson Labrado ContainsSubordinate, 53646a81465SCarson Labrado Resource, 53746a81465SCarson Labrado }; 53846a81465SCarson Labrado 53946a81465SCarson Labrado static void 540e002dbc0SCarson Labrado startAggregation(AggregationType aggType, const crow::Request& thisReq, 54146a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 54246a81465SCarson Labrado { 543e002dbc0SCarson Labrado if (thisReq.method() != boost::beast::http::verb::get) 544e002dbc0SCarson Labrado { 545e002dbc0SCarson Labrado if (aggType == AggregationType::Collection) 546db18fc98SCarson Labrado { 54762598e31SEd Tanous BMCWEB_LOG_DEBUG( 54862598e31SEd Tanous "Only aggregate GET requests to top level collections"); 549db18fc98SCarson Labrado return; 550db18fc98SCarson Labrado } 551db18fc98SCarson Labrado 552e002dbc0SCarson Labrado if (aggType == AggregationType::ContainsSubordinate) 553e002dbc0SCarson Labrado { 55462598e31SEd Tanous BMCWEB_LOG_DEBUG( 55562598e31SEd Tanous "Only aggregate GET requests when uptree of a top level collection"); 556e002dbc0SCarson Labrado return; 557e002dbc0SCarson Labrado } 558e002dbc0SCarson Labrado } 559e002dbc0SCarson Labrado 56046a81465SCarson Labrado // Create a copy of thisReq so we we can still locally process the req 56146a81465SCarson Labrado std::error_code ec; 56246a81465SCarson Labrado auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 56346a81465SCarson Labrado if (ec) 56446a81465SCarson Labrado { 56562598e31SEd Tanous BMCWEB_LOG_ERROR("Failed to create copy of request"); 566e002dbc0SCarson Labrado if (aggType == AggregationType::Resource) 56746a81465SCarson Labrado { 56846a81465SCarson Labrado messages::internalError(asyncResp->res); 56946a81465SCarson Labrado } 57046a81465SCarson Labrado return; 57146a81465SCarson Labrado } 57246a81465SCarson Labrado 573e002dbc0SCarson Labrado getSatelliteConfigs( 574e002dbc0SCarson Labrado std::bind_front(aggregateAndHandle, aggType, localReq, asyncResp)); 57546a81465SCarson Labrado } 57646a81465SCarson Labrado 577db18fc98SCarson Labrado static void findSatellite( 57846a81465SCarson Labrado const crow::Request& req, 57946a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 58046a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 58146a81465SCarson Labrado std::string_view memberName) 58246a81465SCarson Labrado { 58346a81465SCarson Labrado // Determine if the resource ID begins with a known prefix 58446a81465SCarson Labrado for (const auto& satellite : satelliteInfo) 58546a81465SCarson Labrado { 58646a81465SCarson Labrado std::string targetPrefix = satellite.first; 58746a81465SCarson Labrado targetPrefix += "_"; 58846a81465SCarson Labrado if (memberName.starts_with(targetPrefix)) 58946a81465SCarson Labrado { 59062598e31SEd Tanous BMCWEB_LOG_DEBUG("\"{}\" is a known prefix", satellite.first); 59146a81465SCarson Labrado 59246a81465SCarson Labrado // Remove the known prefix from the request's URI and 59346a81465SCarson Labrado // then forward to the associated satellite BMC 59446a81465SCarson Labrado getInstance().forwardRequest(req, asyncResp, satellite.first, 59546a81465SCarson Labrado satelliteInfo); 59646a81465SCarson Labrado return; 59746a81465SCarson Labrado } 59846a81465SCarson Labrado } 599db18fc98SCarson Labrado 600db18fc98SCarson Labrado // We didn't recognize the prefix and need to return a 404 60139662a3bSEd Tanous std::string nameStr = req.url().segments().back(); 602db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 60346a81465SCarson Labrado } 60446a81465SCarson Labrado 60546a81465SCarson Labrado // Intended to handle an incoming request based on if Redfish Aggregation 60646a81465SCarson Labrado // is enabled. Forwards request to satellite BMC if it exists. 60746a81465SCarson Labrado static void aggregateAndHandle( 608e002dbc0SCarson Labrado AggregationType aggType, 60946a81465SCarson Labrado const std::shared_ptr<crow::Request>& sharedReq, 61046a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 6118b2521a5SCarson Labrado const boost::system::error_code& ec, 61246a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 61346a81465SCarson Labrado { 61446a81465SCarson Labrado if (sharedReq == nullptr) 61546a81465SCarson Labrado { 61646a81465SCarson Labrado return; 61746a81465SCarson Labrado } 6188b2521a5SCarson Labrado // Something went wrong while querying dbus 6198b2521a5SCarson Labrado if (ec) 6208b2521a5SCarson Labrado { 6218b2521a5SCarson Labrado messages::internalError(asyncResp->res); 6228b2521a5SCarson Labrado return; 6238b2521a5SCarson Labrado } 624db18fc98SCarson Labrado 625db18fc98SCarson Labrado // No satellite configs means we don't need to keep attempting to 626db18fc98SCarson Labrado // aggregate 627db18fc98SCarson Labrado if (satelliteInfo.empty()) 628db18fc98SCarson Labrado { 629e002dbc0SCarson Labrado // For collections or resources that can contain a subordinate 630e002dbc0SCarson Labrado // top level collection we'll also handle the request locally so we 631db18fc98SCarson Labrado // don't need to write an error code 632e002dbc0SCarson Labrado if (aggType == AggregationType::Resource) 633db18fc98SCarson Labrado { 63439662a3bSEd Tanous std::string nameStr = sharedReq->url().segments().back(); 635db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 636db18fc98SCarson Labrado } 637db18fc98SCarson Labrado return; 638db18fc98SCarson Labrado } 639db18fc98SCarson Labrado 64046a81465SCarson Labrado const crow::Request& thisReq = *sharedReq; 64162598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregation is enabled, begin processing of {}", 64262598e31SEd Tanous thisReq.target()); 64346a81465SCarson Labrado 64446a81465SCarson Labrado // We previously determined the request is for a collection. No need to 64546a81465SCarson Labrado // check again 646e002dbc0SCarson Labrado if (aggType == AggregationType::Collection) 64746a81465SCarson Labrado { 64862598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregating a collection"); 6494c30e226SCarson Labrado // We need to use a specific response handler and send the 6504c30e226SCarson Labrado // request to all known satellites 6514c30e226SCarson Labrado getInstance().forwardCollectionRequests(thisReq, asyncResp, 6524c30e226SCarson Labrado satelliteInfo); 65346a81465SCarson Labrado return; 65446a81465SCarson Labrado } 65546a81465SCarson Labrado 656e002dbc0SCarson Labrado // We previously determined the request may contain a subordinate 657e002dbc0SCarson Labrado // collection. No need to check again 658e002dbc0SCarson Labrado if (aggType == AggregationType::ContainsSubordinate) 659e002dbc0SCarson Labrado { 66062598e31SEd Tanous BMCWEB_LOG_DEBUG( 66162598e31SEd Tanous "Aggregating what may have a subordinate collection"); 662e002dbc0SCarson Labrado // We need to use a specific response handler and send the 663e002dbc0SCarson Labrado // request to all known satellites 664e002dbc0SCarson Labrado getInstance().forwardContainsSubordinateRequests(thisReq, asyncResp, 665e002dbc0SCarson Labrado satelliteInfo); 666e002dbc0SCarson Labrado return; 667e002dbc0SCarson Labrado } 668e002dbc0SCarson Labrado 66939662a3bSEd Tanous const boost::urls::segments_view urlSegments = thisReq.url().segments(); 6707c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 6717c4c52cbSCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 6727c4c52cbSCarson Labrado const boost::urls::segments_view::const_iterator end = 6737c4c52cbSCarson Labrado urlSegments.end(); 6747c4c52cbSCarson Labrado 6757c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 6767c4c52cbSCarson Labrado it++; 6777c4c52cbSCarson Labrado it++; 6787c4c52cbSCarson Labrado for (; it != end; it++) 67946a81465SCarson Labrado { 6807c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 6817c4c52cbSCarson Labrado currentUrl.buffer())) 6827c4c52cbSCarson Labrado { 6837c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 6847c4c52cbSCarson Labrado // must contain an aggregation prefix 6857c4c52cbSCarson Labrado findSatellite(thisReq, asyncResp, satelliteInfo, *it); 68646a81465SCarson Labrado return; 68746a81465SCarson Labrado } 68846a81465SCarson Labrado 6897c4c52cbSCarson Labrado currentUrl.segments().push_back(*it); 69046a81465SCarson Labrado } 691db18fc98SCarson Labrado 692db18fc98SCarson Labrado // We shouldn't reach this point since we should've hit one of the 693db18fc98SCarson Labrado // previous exits 694db18fc98SCarson Labrado messages::internalError(asyncResp->res); 69546a81465SCarson Labrado } 69646a81465SCarson Labrado 69746a81465SCarson Labrado // Attempt to forward a request to the satellite BMC associated with the 69846a81465SCarson Labrado // prefix. 69946a81465SCarson Labrado void forwardRequest( 70046a81465SCarson Labrado const crow::Request& thisReq, 70146a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 70246a81465SCarson Labrado const std::string& prefix, 70346a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 70446a81465SCarson Labrado { 70546a81465SCarson Labrado const auto& sat = satelliteInfo.find(prefix); 70646a81465SCarson Labrado if (sat == satelliteInfo.end()) 70746a81465SCarson Labrado { 70846a81465SCarson Labrado // Realistically this shouldn't get called since we perform an 70946a81465SCarson Labrado // earlier check to make sure the prefix exists 71062598e31SEd Tanous BMCWEB_LOG_ERROR("Unrecognized satellite prefix \"{}\"", prefix); 71146a81465SCarson Labrado return; 71246a81465SCarson Labrado } 71346a81465SCarson Labrado 71446a81465SCarson Labrado // We need to strip the prefix from the request's path 715a716aa74SEd Tanous boost::urls::url targetURI(thisReq.target()); 716a716aa74SEd Tanous std::string path = thisReq.url().path(); 717a716aa74SEd Tanous size_t pos = path.find(prefix + "_"); 71846a81465SCarson Labrado if (pos == std::string::npos) 71946a81465SCarson Labrado { 72046a81465SCarson Labrado // If this fails then something went wrong 72162598e31SEd Tanous BMCWEB_LOG_ERROR("Error removing prefix \"{}_\" from request URI", 72262598e31SEd Tanous prefix); 72346a81465SCarson Labrado messages::internalError(asyncResp->res); 72446a81465SCarson Labrado return; 72546a81465SCarson Labrado } 726a716aa74SEd Tanous path.erase(pos, prefix.size() + 1); 72746a81465SCarson Labrado 72846a81465SCarson Labrado std::function<void(crow::Response&)> cb = 7291c0bb5c6SCarson Labrado std::bind_front(processResponse, prefix, asyncResp); 73046a81465SCarson Labrado 73146a81465SCarson Labrado std::string data = thisReq.req.body(); 732a716aa74SEd Tanous boost::urls::url url(sat->second); 733a716aa74SEd Tanous url.set_path(path); 734a716aa74SEd Tanous if (targetURI.has_query()) 735a716aa74SEd Tanous { 736a716aa74SEd Tanous url.set_query(targetURI.query()); 737a716aa74SEd Tanous } 738a716aa74SEd Tanous client.sendDataWithCallback(std::move(data), url, thisReq.fields(), 739a716aa74SEd Tanous thisReq.method(), cb); 74046a81465SCarson Labrado } 74146a81465SCarson Labrado 7424c30e226SCarson Labrado // Forward a request for a collection URI to each known satellite BMC 7434c30e226SCarson Labrado void forwardCollectionRequests( 7444c30e226SCarson Labrado const crow::Request& thisReq, 7454c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 7464c30e226SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 7474c30e226SCarson Labrado { 7484c30e226SCarson Labrado for (const auto& sat : satelliteInfo) 7494c30e226SCarson Labrado { 7504c30e226SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 7514c30e226SCarson Labrado processCollectionResponse, sat.first, asyncResp); 7524c30e226SCarson Labrado 753a716aa74SEd Tanous boost::urls::url url(sat.second); 754a716aa74SEd Tanous url.set_path(thisReq.url().path()); 755a716aa74SEd Tanous if (thisReq.url().has_query()) 756a716aa74SEd Tanous { 757a716aa74SEd Tanous url.set_query(thisReq.url().query()); 758a716aa74SEd Tanous } 7594c30e226SCarson Labrado std::string data = thisReq.req.body(); 760a716aa74SEd Tanous client.sendDataWithCallback(std::move(data), url, thisReq.fields(), 761a716aa74SEd Tanous thisReq.method(), cb); 7624c30e226SCarson Labrado } 7634c30e226SCarson Labrado } 7644c30e226SCarson Labrado 765e002dbc0SCarson Labrado // Forward request for a URI that is uptree of a top level collection to 766e002dbc0SCarson Labrado // each known satellite BMC 767e002dbc0SCarson Labrado void forwardContainsSubordinateRequests( 768e002dbc0SCarson Labrado const crow::Request& thisReq, 769e002dbc0SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 770e002dbc0SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 771e002dbc0SCarson Labrado { 772e002dbc0SCarson Labrado for (const auto& sat : satelliteInfo) 773e002dbc0SCarson Labrado { 774e002dbc0SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 775e002dbc0SCarson Labrado processContainsSubordinateResponse, sat.first, asyncResp); 776e002dbc0SCarson Labrado 777e002dbc0SCarson Labrado // will ignore an expanded resource in the response if that resource 778e002dbc0SCarson Labrado // is not already supported by the aggregating BMC 779e002dbc0SCarson Labrado // TODO: Improve the processing so that we don't have to strip query 780e002dbc0SCarson Labrado // params in this specific case 781a716aa74SEd Tanous boost::urls::url url(sat.second); 782a716aa74SEd Tanous url.set_path(thisReq.url().path()); 783a716aa74SEd Tanous 784e002dbc0SCarson Labrado std::string data = thisReq.req.body(); 785a716aa74SEd Tanous 786a716aa74SEd Tanous client.sendDataWithCallback(std::move(data), url, thisReq.fields(), 787a716aa74SEd Tanous thisReq.method(), cb); 788e002dbc0SCarson Labrado } 789e002dbc0SCarson Labrado } 790e002dbc0SCarson Labrado 79132d7d8ebSCarson Labrado public: 792f8ca6d79SEd Tanous explicit RedfishAggregator(boost::asio::io_context& ioc) : 793f8ca6d79SEd Tanous client(ioc, 794f8ca6d79SEd Tanous std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy())) 795f8ca6d79SEd Tanous { 796f8ca6d79SEd Tanous getSatelliteConfigs(constructorCallback); 797f8ca6d79SEd Tanous } 79832d7d8ebSCarson Labrado RedfishAggregator(const RedfishAggregator&) = delete; 79932d7d8ebSCarson Labrado RedfishAggregator& operator=(const RedfishAggregator&) = delete; 80032d7d8ebSCarson Labrado RedfishAggregator(RedfishAggregator&&) = delete; 80132d7d8ebSCarson Labrado RedfishAggregator& operator=(RedfishAggregator&&) = delete; 80232d7d8ebSCarson Labrado ~RedfishAggregator() = default; 80332d7d8ebSCarson Labrado 804f8ca6d79SEd Tanous static RedfishAggregator& getInstance(boost::asio::io_context* io = nullptr) 80532d7d8ebSCarson Labrado { 806f8ca6d79SEd Tanous static RedfishAggregator handler(*io); 80732d7d8ebSCarson Labrado return handler; 80832d7d8ebSCarson Labrado } 80932d7d8ebSCarson Labrado 8108b2521a5SCarson Labrado // Polls D-Bus to get all available satellite config information 8118b2521a5SCarson Labrado // Expects a handler which interacts with the returned configs 8128b2521a5SCarson Labrado static void getSatelliteConfigs( 8138b2521a5SCarson Labrado std::function< 8148b2521a5SCarson Labrado void(const boost::system::error_code&, 8158b2521a5SCarson Labrado const std::unordered_map<std::string, boost::urls::url>&)> 8168b2521a5SCarson Labrado handler) 8178b2521a5SCarson Labrado { 81862598e31SEd Tanous BMCWEB_LOG_DEBUG("Gathering satellite configs"); 8195eb468daSGeorge Liu sdbusplus::message::object_path path("/xyz/openbmc_project/inventory"); 8205eb468daSGeorge Liu dbus::utility::getManagedObjects( 8215eb468daSGeorge Liu "xyz.openbmc_project.EntityManager", path, 8228b2521a5SCarson Labrado [handler{std::move(handler)}]( 8238b2521a5SCarson Labrado const boost::system::error_code& ec, 8248b2521a5SCarson Labrado const dbus::utility::ManagedObjectType& objects) { 8258b2521a5SCarson Labrado std::unordered_map<std::string, boost::urls::url> satelliteInfo; 8268b2521a5SCarson Labrado if (ec) 8278b2521a5SCarson Labrado { 82862598e31SEd Tanous BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(), 82962598e31SEd Tanous ec.message()); 8308b2521a5SCarson Labrado handler(ec, satelliteInfo); 8318b2521a5SCarson Labrado return; 8328b2521a5SCarson Labrado } 8338b2521a5SCarson Labrado 8348b2521a5SCarson Labrado // Maps a chosen alias representing a satellite BMC to a url 8358b2521a5SCarson Labrado // containing the information required to create a http 8368b2521a5SCarson Labrado // connection to the satellite 8378b2521a5SCarson Labrado findSatelliteConfigs(objects, satelliteInfo); 8388b2521a5SCarson Labrado 8398b2521a5SCarson Labrado if (!satelliteInfo.empty()) 8408b2521a5SCarson Labrado { 84162598e31SEd Tanous BMCWEB_LOG_DEBUG( 84262598e31SEd Tanous "Redfish Aggregation enabled with {} satellite BMCs", 84362598e31SEd Tanous std::to_string(satelliteInfo.size())); 8448b2521a5SCarson Labrado } 8458b2521a5SCarson Labrado else 8468b2521a5SCarson Labrado { 84762598e31SEd Tanous BMCWEB_LOG_DEBUG( 84862598e31SEd Tanous "No satellite BMCs detected. Redfish Aggregation not enabled"); 8498b2521a5SCarson Labrado } 8508b2521a5SCarson Labrado handler(ec, satelliteInfo); 8515eb468daSGeorge Liu }); 8528b2521a5SCarson Labrado } 8538b2521a5SCarson Labrado 85446a81465SCarson Labrado // Processes the response returned by a satellite BMC and loads its 85546a81465SCarson Labrado // contents into asyncResp 85646a81465SCarson Labrado static void 8571c0bb5c6SCarson Labrado processResponse(std::string_view prefix, 8581c0bb5c6SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 85946a81465SCarson Labrado crow::Response& resp) 86046a81465SCarson Labrado { 86143e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 86243e14d38SCarson Labrado // overwrite the response headers in that case 86346b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 86446b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 86543e14d38SCarson Labrado { 86643e14d38SCarson Labrado asyncResp->res.result(resp.result()); 86743e14d38SCarson Labrado return; 86843e14d38SCarson Labrado } 86943e14d38SCarson Labrado 87032d7d8ebSCarson Labrado // We want to attempt prefix fixing regardless of response code 87146a81465SCarson Labrado // The resp will not have a json component 87246a81465SCarson Labrado // We need to create a json from resp's stringResponse 87346b30283SCarson Labrado std::string_view contentType = resp.getHeaderValue("Content-Type"); 87446b30283SCarson Labrado if (boost::iequals(contentType, "application/json") || 87546b30283SCarson Labrado boost::iequals(contentType, "application/json; charset=utf-8")) 87646a81465SCarson Labrado { 87789492a15SPatrick Williams nlohmann::json jsonVal = nlohmann::json::parse(resp.body(), nullptr, 87889492a15SPatrick Williams false); 87946a81465SCarson Labrado if (jsonVal.is_discarded()) 88046a81465SCarson Labrado { 88162598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 88246a81465SCarson Labrado messages::operationFailed(asyncResp->res); 88346a81465SCarson Labrado return; 88446a81465SCarson Labrado } 88546a81465SCarson Labrado 88662598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 88746a81465SCarson Labrado 8881c0bb5c6SCarson Labrado addPrefixes(jsonVal, prefix); 8891c0bb5c6SCarson Labrado 89062598e31SEd Tanous BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response"); 8911c0bb5c6SCarson Labrado 89246a81465SCarson Labrado asyncResp->res.result(resp.result()); 89346a81465SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 89446a81465SCarson Labrado 89562598e31SEd Tanous BMCWEB_LOG_DEBUG("Finished writing asyncResp"); 89646a81465SCarson Labrado } 89746a81465SCarson Labrado else 89846a81465SCarson Labrado { 8990af78d5aSKhang Kieu // We allow any Content-Type that is not "application/json" now 9000af78d5aSKhang Kieu asyncResp->res.result(resp.result()); 9010af78d5aSKhang Kieu asyncResp->res.write(resp.body()); 90246a81465SCarson Labrado } 9030af78d5aSKhang Kieu addAggregatedHeaders(asyncResp->res, resp, prefix); 90446a81465SCarson Labrado } 90546a81465SCarson Labrado 9064c30e226SCarson Labrado // Processes the collection response returned by a satellite BMC and merges 9074c30e226SCarson Labrado // its "@odata.id" values 9084c30e226SCarson Labrado static void processCollectionResponse( 9094c30e226SCarson Labrado const std::string& prefix, 9104c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 9114c30e226SCarson Labrado crow::Response& resp) 9124c30e226SCarson Labrado { 91343e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 91443e14d38SCarson Labrado // overwrite the response headers in that case 91546b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 91646b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 91743e14d38SCarson Labrado { 91843e14d38SCarson Labrado return; 91943e14d38SCarson Labrado } 92043e14d38SCarson Labrado 9214c30e226SCarson Labrado if (resp.resultInt() != 200) 9224c30e226SCarson Labrado { 92362598e31SEd Tanous BMCWEB_LOG_DEBUG( 92462598e31SEd Tanous "Collection resource does not exist in satellite BMC \"{}\"", 92562598e31SEd Tanous prefix); 9264c30e226SCarson Labrado // Return the error if we haven't had any successes 9274c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 9284c30e226SCarson Labrado { 92946b30283SCarson Labrado asyncResp->res.result(resp.result()); 93046b30283SCarson Labrado asyncResp->res.write(resp.body()); 9314c30e226SCarson Labrado } 9324c30e226SCarson Labrado return; 9334c30e226SCarson Labrado } 9344c30e226SCarson Labrado 9354c30e226SCarson Labrado // The resp will not have a json component 9364c30e226SCarson Labrado // We need to create a json from resp's stringResponse 93746b30283SCarson Labrado std::string_view contentType = resp.getHeaderValue("Content-Type"); 93846b30283SCarson Labrado if (boost::iequals(contentType, "application/json") || 93946b30283SCarson Labrado boost::iequals(contentType, "application/json; charset=utf-8")) 9404c30e226SCarson Labrado { 94189492a15SPatrick Williams nlohmann::json jsonVal = nlohmann::json::parse(resp.body(), nullptr, 94289492a15SPatrick Williams false); 9434c30e226SCarson Labrado if (jsonVal.is_discarded()) 9444c30e226SCarson Labrado { 94562598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 9464c30e226SCarson Labrado 9474c30e226SCarson Labrado // Notify the user if doing so won't overwrite a valid response 94846b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 9494c30e226SCarson Labrado { 9504c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 9514c30e226SCarson Labrado } 9524c30e226SCarson Labrado return; 9534c30e226SCarson Labrado } 9544c30e226SCarson Labrado 95562598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 9564c30e226SCarson Labrado 9574c30e226SCarson Labrado // Now we need to add the prefix to the URIs contained in the 9584c30e226SCarson Labrado // response. 9594c30e226SCarson Labrado addPrefixes(jsonVal, prefix); 9604c30e226SCarson Labrado 96162598e31SEd Tanous BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response"); 9624c30e226SCarson Labrado 9634c30e226SCarson Labrado // If this resource collection does not exist on the aggregating bmc 9644c30e226SCarson Labrado // and has not already been added from processing the response from 9654c30e226SCarson Labrado // a different satellite then we need to completely overwrite 9664c30e226SCarson Labrado // asyncResp 9674c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 9684c30e226SCarson Labrado { 9694c30e226SCarson Labrado // We only want to aggregate collections that contain a 9704c30e226SCarson Labrado // "Members" array 9714c30e226SCarson Labrado if ((!jsonVal.contains("Members")) && 9724c30e226SCarson Labrado (!jsonVal["Members"].is_array())) 9734c30e226SCarson Labrado { 97462598e31SEd Tanous BMCWEB_LOG_DEBUG( 97562598e31SEd Tanous "Skipping aggregating unsupported resource"); 9764c30e226SCarson Labrado return; 9774c30e226SCarson Labrado } 9784c30e226SCarson Labrado 97962598e31SEd Tanous BMCWEB_LOG_DEBUG( 98062598e31SEd Tanous "Collection does not exist, overwriting asyncResp"); 9814c30e226SCarson Labrado asyncResp->res.result(resp.result()); 9824c30e226SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 98343e14d38SCarson Labrado asyncResp->res.addHeader("Content-Type", "application/json"); 9844c30e226SCarson Labrado 98562598e31SEd Tanous BMCWEB_LOG_DEBUG("Finished overwriting asyncResp"); 9864c30e226SCarson Labrado } 9874c30e226SCarson Labrado else 9884c30e226SCarson Labrado { 9894c30e226SCarson Labrado // We only want to aggregate collections that contain a 9904c30e226SCarson Labrado // "Members" array 9914c30e226SCarson Labrado if ((!asyncResp->res.jsonValue.contains("Members")) && 9924c30e226SCarson Labrado (!asyncResp->res.jsonValue["Members"].is_array())) 9934c30e226SCarson Labrado 9944c30e226SCarson Labrado { 99562598e31SEd Tanous BMCWEB_LOG_DEBUG( 99662598e31SEd Tanous "Skipping aggregating unsupported resource"); 9974c30e226SCarson Labrado return; 9984c30e226SCarson Labrado } 9994c30e226SCarson Labrado 100062598e31SEd Tanous BMCWEB_LOG_DEBUG( 100162598e31SEd Tanous "Adding aggregated resources from \"{}\" to collection", 100262598e31SEd Tanous prefix); 10034c30e226SCarson Labrado 10044c30e226SCarson Labrado // TODO: This is a potential race condition with multiple 10054c30e226SCarson Labrado // satellites and the aggregating bmc attempting to write to 10064c30e226SCarson Labrado // update this array. May need to cascade calls to the next 10074c30e226SCarson Labrado // satellite at the end of this function. 10084c30e226SCarson Labrado // This is presumably not a concern when there is only a single 10094c30e226SCarson Labrado // satellite since the aggregating bmc should have completed 10104c30e226SCarson Labrado // before the response is received from the satellite. 10114c30e226SCarson Labrado 10124c30e226SCarson Labrado auto& members = asyncResp->res.jsonValue["Members"]; 10134c30e226SCarson Labrado auto& satMembers = jsonVal["Members"]; 10144c30e226SCarson Labrado for (auto& satMem : satMembers) 10154c30e226SCarson Labrado { 1016b2ba3072SPatrick Williams members.emplace_back(std::move(satMem)); 10174c30e226SCarson Labrado } 10184c30e226SCarson Labrado asyncResp->res.jsonValue["Members@odata.count"] = 10194c30e226SCarson Labrado members.size(); 10204c30e226SCarson Labrado 10214c30e226SCarson Labrado // TODO: Do we need to sort() after updating the array? 10224c30e226SCarson Labrado } 10234c30e226SCarson Labrado } 10244c30e226SCarson Labrado else 10254c30e226SCarson Labrado { 102662598e31SEd Tanous BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"", 102762598e31SEd Tanous prefix); 102843e14d38SCarson Labrado // We received a response that was not a json. 102946b30283SCarson Labrado // Notify the user only if we did not receive any valid responses 103046b30283SCarson Labrado // and if the resource collection does not already exist on the 103146b30283SCarson Labrado // aggregating BMC 103246b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 10334c30e226SCarson Labrado { 10344c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 10354c30e226SCarson Labrado } 10364c30e226SCarson Labrado } 10374c30e226SCarson Labrado } // End processCollectionResponse() 10384c30e226SCarson Labrado 103946b30283SCarson Labrado // Processes the response returned by a satellite BMC and merges any 104046b30283SCarson Labrado // properties whose "@odata.id" value is the URI or either a top level 104146b30283SCarson Labrado // collection or is uptree from a top level collection 104246b30283SCarson Labrado static void processContainsSubordinateResponse( 104346b30283SCarson Labrado const std::string& prefix, 104446b30283SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 104546b30283SCarson Labrado crow::Response& resp) 104646b30283SCarson Labrado { 104746b30283SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 104846b30283SCarson Labrado // overwrite the response headers in that case 104946b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 105046b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 105146b30283SCarson Labrado { 105246b30283SCarson Labrado return; 105346b30283SCarson Labrado } 105446b30283SCarson Labrado 105546b30283SCarson Labrado if (resp.resultInt() != 200) 105646b30283SCarson Labrado { 105762598e31SEd Tanous BMCWEB_LOG_DEBUG( 105862598e31SEd Tanous "Resource uptree from Collection does not exist in satellite BMC \"{}\"", 105962598e31SEd Tanous prefix); 106046b30283SCarson Labrado // Return the error if we haven't had any successes 106146b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 106246b30283SCarson Labrado { 106346b30283SCarson Labrado asyncResp->res.result(resp.result()); 106446b30283SCarson Labrado asyncResp->res.write(resp.body()); 106546b30283SCarson Labrado } 106646b30283SCarson Labrado return; 106746b30283SCarson Labrado } 106846b30283SCarson Labrado 106946b30283SCarson Labrado // The resp will not have a json component 107046b30283SCarson Labrado // We need to create a json from resp's stringResponse 107146b30283SCarson Labrado std::string_view contentType = resp.getHeaderValue("Content-Type"); 107246b30283SCarson Labrado if (boost::iequals(contentType, "application/json") || 107346b30283SCarson Labrado boost::iequals(contentType, "application/json; charset=utf-8")) 107446b30283SCarson Labrado { 107546b30283SCarson Labrado bool addedLinks = false; 107689492a15SPatrick Williams nlohmann::json jsonVal = nlohmann::json::parse(resp.body(), nullptr, 107789492a15SPatrick Williams false); 107846b30283SCarson Labrado if (jsonVal.is_discarded()) 107946b30283SCarson Labrado { 108062598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 108146b30283SCarson Labrado 108246b30283SCarson Labrado // Notify the user if doing so won't overwrite a valid response 108346b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 108446b30283SCarson Labrado { 108546b30283SCarson Labrado messages::operationFailed(asyncResp->res); 108646b30283SCarson Labrado } 108746b30283SCarson Labrado return; 108846b30283SCarson Labrado } 108946b30283SCarson Labrado 109062598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 109146b30283SCarson Labrado 109246b30283SCarson Labrado // Parse response and add properties missing from the AsyncResp 109346b30283SCarson Labrado // Valid properties will be of the form <property>.@odata.id and 109446b30283SCarson Labrado // @odata.id is a <URI>. In other words, the json should contain 109546b30283SCarson Labrado // multiple properties such that 109646b30283SCarson Labrado // {"<property>":{"@odata.id": "<URI>"}} 109746b30283SCarson Labrado nlohmann::json::object_t* object = 109846b30283SCarson Labrado jsonVal.get_ptr<nlohmann::json::object_t*>(); 109946b30283SCarson Labrado if (object == nullptr) 110046b30283SCarson Labrado { 110162598e31SEd Tanous BMCWEB_LOG_ERROR("Parsed JSON was not an object?"); 110246b30283SCarson Labrado return; 110346b30283SCarson Labrado } 110446b30283SCarson Labrado 110546b30283SCarson Labrado for (std::pair<const std::string, nlohmann::json>& prop : *object) 110646b30283SCarson Labrado { 110746b30283SCarson Labrado if (!prop.second.contains("@odata.id")) 110846b30283SCarson Labrado { 110946b30283SCarson Labrado continue; 111046b30283SCarson Labrado } 111146b30283SCarson Labrado 111246b30283SCarson Labrado std::string* strValue = 111346b30283SCarson Labrado prop.second["@odata.id"].get_ptr<std::string*>(); 111446b30283SCarson Labrado if (strValue == nullptr) 111546b30283SCarson Labrado { 111662598e31SEd Tanous BMCWEB_LOG_CRITICAL("Field wasn't a string????"); 111746b30283SCarson Labrado continue; 111846b30283SCarson Labrado } 111946b30283SCarson Labrado if (!searchCollectionsArray(*strValue, SearchType::CollOrCon)) 112046b30283SCarson Labrado { 112146b30283SCarson Labrado continue; 112246b30283SCarson Labrado } 112346b30283SCarson Labrado 112446b30283SCarson Labrado addedLinks = true; 112546b30283SCarson Labrado if (!asyncResp->res.jsonValue.contains(prop.first)) 112646b30283SCarson Labrado { 112746b30283SCarson Labrado // Only add the property if it did not already exist 112862598e31SEd Tanous BMCWEB_LOG_DEBUG("Adding link for {} from BMC {}", 112962598e31SEd Tanous *strValue, prefix); 113046b30283SCarson Labrado asyncResp->res.jsonValue[prop.first]["@odata.id"] = 113146b30283SCarson Labrado *strValue; 113246b30283SCarson Labrado continue; 113346b30283SCarson Labrado } 113446b30283SCarson Labrado } 113546b30283SCarson Labrado 113646b30283SCarson Labrado // If we added links to a previously unsuccessful (non-200) response 113746b30283SCarson Labrado // then we need to make sure the response contains the bare minimum 113846b30283SCarson Labrado // amount of additional information that we'd expect to have been 113946b30283SCarson Labrado // populated. 114046b30283SCarson Labrado if (addedLinks && (asyncResp->res.resultInt() != 200)) 114146b30283SCarson Labrado { 114246b30283SCarson Labrado // This resource didn't locally exist or an error 114346b30283SCarson Labrado // occurred while generating the response. Remove any 114446b30283SCarson Labrado // error messages and update the error code. 114546b30283SCarson Labrado asyncResp->res.jsonValue.erase( 114646b30283SCarson Labrado asyncResp->res.jsonValue.find("error")); 114746b30283SCarson Labrado asyncResp->res.result(resp.result()); 114846b30283SCarson Labrado 114946b30283SCarson Labrado const auto& it1 = object->find("@odata.id"); 115046b30283SCarson Labrado if (it1 != object->end()) 115146b30283SCarson Labrado { 115246b30283SCarson Labrado asyncResp->res.jsonValue["@odata.id"] = (it1->second); 115346b30283SCarson Labrado } 115446b30283SCarson Labrado const auto& it2 = object->find("@odata.type"); 115546b30283SCarson Labrado if (it2 != object->end()) 115646b30283SCarson Labrado { 115746b30283SCarson Labrado asyncResp->res.jsonValue["@odata.type"] = (it2->second); 115846b30283SCarson Labrado } 115946b30283SCarson Labrado const auto& it3 = object->find("Id"); 116046b30283SCarson Labrado if (it3 != object->end()) 116146b30283SCarson Labrado { 116246b30283SCarson Labrado asyncResp->res.jsonValue["Id"] = (it3->second); 116346b30283SCarson Labrado } 116446b30283SCarson Labrado const auto& it4 = object->find("Name"); 116546b30283SCarson Labrado if (it4 != object->end()) 116646b30283SCarson Labrado { 116746b30283SCarson Labrado asyncResp->res.jsonValue["Name"] = (it4->second); 116846b30283SCarson Labrado } 116946b30283SCarson Labrado } 117046b30283SCarson Labrado } 117146b30283SCarson Labrado else 117246b30283SCarson Labrado { 117362598e31SEd Tanous BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"", 117462598e31SEd Tanous prefix); 117546b30283SCarson Labrado // We received as response that was not a json 117646b30283SCarson Labrado // Notify the user only if we did not receive any valid responses, 117746b30283SCarson Labrado // and if the resource does not already exist on the aggregating BMC 117846b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 117946b30283SCarson Labrado { 118046b30283SCarson Labrado messages::operationFailed(asyncResp->res); 118146b30283SCarson Labrado } 118246b30283SCarson Labrado } 118346b30283SCarson Labrado } 118446b30283SCarson Labrado 118505916cefSCarson Labrado // Entry point to Redfish Aggregation 118605916cefSCarson Labrado // Returns Result stating whether or not we still need to locally handle the 118705916cefSCarson Labrado // request 118805916cefSCarson Labrado static Result 118905916cefSCarson Labrado beginAggregation(const crow::Request& thisReq, 119005916cefSCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 119105916cefSCarson Labrado { 119205916cefSCarson Labrado using crow::utility::OrMorePaths; 119305916cefSCarson Labrado using crow::utility::readUrlSegments; 119439662a3bSEd Tanous const boost::urls::url_view url = thisReq.url(); 1195411e6a11SCarson Labrado 1196411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such 1197411e6a11SCarson Labrado // as version mismatches between aggregator and satellite BMCs. For 1198411e6a11SCarson Labrado // now assume that the aggregator has all the schemas and versions that 1199411e6a11SCarson Labrado // the aggregated server has. 1200411e6a11SCarson Labrado if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", 1201411e6a11SCarson Labrado crow::utility::OrMorePaths())) 1202411e6a11SCarson Labrado { 1203411e6a11SCarson Labrado return Result::LocalHandle; 1204411e6a11SCarson Labrado } 1205411e6a11SCarson Labrado 12067c4c52cbSCarson Labrado // The first two segments should be "/redfish/v1". We need to check 12077c4c52cbSCarson Labrado // that before we can search topCollections 12087c4c52cbSCarson Labrado if (!crow::utility::readUrlSegments(url, "redfish", "v1", 12097c4c52cbSCarson Labrado crow::utility::OrMorePaths())) 121046a81465SCarson Labrado { 121146a81465SCarson Labrado return Result::LocalHandle; 121246a81465SCarson Labrado } 121305916cefSCarson Labrado 12147c4c52cbSCarson Labrado // Parse the URI to see if it begins with a known top level collection 12157c4c52cbSCarson Labrado // such as: 12167c4c52cbSCarson Labrado // /redfish/v1/Chassis 12177c4c52cbSCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory 12187c4c52cbSCarson Labrado const boost::urls::segments_view urlSegments = url.segments(); 12197c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 12207c4c52cbSCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 12217c4c52cbSCarson Labrado const boost::urls::segments_view::const_iterator end = 12227c4c52cbSCarson Labrado urlSegments.end(); 122305916cefSCarson Labrado 12247c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 12257c4c52cbSCarson Labrado it++; 12267c4c52cbSCarson Labrado it++; 12277c4c52cbSCarson Labrado for (; it != end; it++) 122805916cefSCarson Labrado { 1229d4413c5bSGeorge Liu const std::string& collectionItem = *it; 12307c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 12317c4c52cbSCarson Labrado currentUrl.buffer())) 12327c4c52cbSCarson Labrado { 12337c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 12347c4c52cbSCarson Labrado // might contain an aggregation prefix 12358b2521a5SCarson Labrado // TODO: This needs to be rethought when we can support multiple 12368b2521a5SCarson Labrado // satellites due to 12378b2521a5SCarson Labrado // /redfish/v1/AggregationService/AggregationSources/5B247A 12388b2521a5SCarson Labrado // being a local resource describing the satellite 12398b2521a5SCarson Labrado if (collectionItem.starts_with("5B247A_")) 124005916cefSCarson Labrado { 124162598e31SEd Tanous BMCWEB_LOG_DEBUG("Need to forward a request"); 124205916cefSCarson Labrado 124346a81465SCarson Labrado // Extract the prefix from the request's URI, retrieve the 12447c4c52cbSCarson Labrado // associated satellite config information, and then forward 12457c4c52cbSCarson Labrado // the request to that satellite. 12467c4c52cbSCarson Labrado startAggregation(AggregationType::Resource, thisReq, 12477c4c52cbSCarson Labrado asyncResp); 124805916cefSCarson Labrado return Result::NoLocalHandle; 124905916cefSCarson Labrado } 12507c4c52cbSCarson Labrado 12517c4c52cbSCarson Labrado // Handle collection URI with a trailing backslash 12527c4c52cbSCarson Labrado // e.g. /redfish/v1/Chassis/ 12537c4c52cbSCarson Labrado it++; 12547c4c52cbSCarson Labrado if ((it == end) && collectionItem.empty()) 12557c4c52cbSCarson Labrado { 12567c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, 12577c4c52cbSCarson Labrado asyncResp); 12587c4c52cbSCarson Labrado } 12597c4c52cbSCarson Labrado 12607c4c52cbSCarson Labrado // We didn't recognize the prefix or it's a collection with a 12617c4c52cbSCarson Labrado // trailing "/". In both cases we still want to locally handle 12627c4c52cbSCarson Labrado // the request 12637c4c52cbSCarson Labrado return Result::LocalHandle; 12647c4c52cbSCarson Labrado } 12657c4c52cbSCarson Labrado 12667c4c52cbSCarson Labrado currentUrl.segments().push_back(collectionItem); 12677c4c52cbSCarson Labrado } 12687c4c52cbSCarson Labrado 12697c4c52cbSCarson Labrado // If we made it here then currentUrl could contain a top level 12707c4c52cbSCarson Labrado // collection URI without a trailing "/", e.g. /redfish/v1/Chassis 12717c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 12727c4c52cbSCarson Labrado currentUrl.buffer())) 12737c4c52cbSCarson Labrado { 12747c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 127505916cefSCarson Labrado return Result::LocalHandle; 127605916cefSCarson Labrado } 127705916cefSCarson Labrado 1278e002dbc0SCarson Labrado // If nothing else then the request could be for a resource which has a 1279e002dbc0SCarson Labrado // top level collection as a subordinate 1280e002dbc0SCarson Labrado if (searchCollectionsArray(url.path(), SearchType::ContainsSubordinate)) 1281e002dbc0SCarson Labrado { 1282e002dbc0SCarson Labrado startAggregation(AggregationType::ContainsSubordinate, thisReq, 1283e002dbc0SCarson Labrado asyncResp); 1284e002dbc0SCarson Labrado return Result::LocalHandle; 1285e002dbc0SCarson Labrado } 1286e002dbc0SCarson Labrado 128762598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregation not required for {}", url.buffer()); 128805916cefSCarson Labrado return Result::LocalHandle; 128905916cefSCarson Labrado } 12907fb33566SCarson Labrado }; 12917fb33566SCarson Labrado 12927fb33566SCarson Labrado } // namespace redfish 1293