1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 37fb33566SCarson Labrado #pragma once 47fb33566SCarson Labrado 53ccb3adbSEd Tanous #include "aggregation_utils.hpp" 63ccb3adbSEd Tanous #include "dbus_utility.hpp" 73ccb3adbSEd Tanous #include "error_messages.hpp" 83ccb3adbSEd Tanous #include "http_client.hpp" 93ccb3adbSEd Tanous #include "http_connection.hpp" 1018f8f608SEd Tanous #include "parsing.hpp" 117fb33566SCarson Labrado 127e8890c5SCarson Labrado #include <array> 133544d2a7SEd Tanous #include <ranges> 143544d2a7SEd Tanous #include <string_view> 157e8890c5SCarson Labrado 167fb33566SCarson Labrado namespace redfish 177fb33566SCarson Labrado { 187fb33566SCarson Labrado 19d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB 20d14a48ffSCarson Labrado 2105916cefSCarson Labrado enum class Result 2205916cefSCarson Labrado { 2305916cefSCarson Labrado LocalHandle, 2405916cefSCarson Labrado NoLocalHandle 2505916cefSCarson Labrado }; 2605916cefSCarson Labrado 278fd333d6SCarson Labrado enum class SearchType 288fd333d6SCarson Labrado { 298fd333d6SCarson Labrado Collection, 308fd333d6SCarson Labrado CollOrCon, 318fd333d6SCarson Labrado ContainsSubordinate, 328fd333d6SCarson Labrado Resource 338fd333d6SCarson Labrado }; 348fd333d6SCarson Labrado 357e8890c5SCarson Labrado // clang-format off 367e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource 377e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a 387e8890c5SCarson Labrado // case-insensitive form of "uri". That version of the schema is associated 397e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification. Going forward, new URI 407e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as 417e8890c5SCarson Labrado // the spec is updated. NOTE: These have been pre-sorted in order to be 427e8890c5SCarson Labrado // compatible with binary search 437e8890c5SCarson Labrado constexpr std::array nonUriProperties{ 447e8890c5SCarson Labrado "@Redfish.ActionInfo", 457e8890c5SCarson Labrado // "@odata.context", // We can't fix /redfish/v1/$metadata URIs 467e8890c5SCarson Labrado "@odata.id", 477e8890c5SCarson Labrado // "Destination", // Only used by EventService and won't be a Redfish URI 487e8890c5SCarson Labrado // "HostName", // Isn't actually a Redfish URI 497e8890c5SCarson Labrado "Image", 507e8890c5SCarson Labrado "MetricProperty", 5132d7d8ebSCarson Labrado // "OriginOfCondition", // Is URI when in request, but is object in response 527e8890c5SCarson Labrado "TaskMonitor", 537e8890c5SCarson Labrado "target", // normal string, but target URI for POST to invoke an action 547e8890c5SCarson Labrado }; 557e8890c5SCarson Labrado // clang-format on 567e8890c5SCarson Labrado 578fd333d6SCarson Labrado // Search the top collection array to determine if the passed URI is of a 588fd333d6SCarson Labrado // desired type 598fd333d6SCarson Labrado inline bool searchCollectionsArray(std::string_view uri, 608fd333d6SCarson Labrado const SearchType searchType) 618fd333d6SCarson Labrado { 621a095906SJoonwon Kang boost::system::result<boost::urls::url> parsedUrl = 631a095906SJoonwon Kang boost::urls::parse_relative_ref(uri); 648fd333d6SCarson Labrado if (!parsedUrl) 658fd333d6SCarson Labrado { 661a095906SJoonwon Kang BMCWEB_LOG_ERROR("Failed to get target URI from {}", uri); 678fd333d6SCarson Labrado return false; 688fd333d6SCarson Labrado } 698fd333d6SCarson Labrado 701a095906SJoonwon Kang parsedUrl->normalize(); 711a095906SJoonwon Kang boost::urls::segments_ref segments = parsedUrl->segments(); 721a095906SJoonwon Kang if (!segments.is_absolute()) 738fd333d6SCarson Labrado { 748fd333d6SCarson Labrado return false; 758fd333d6SCarson Labrado } 768fd333d6SCarson Labrado 771a095906SJoonwon Kang // The passed URI must begin with "/redfish/v1", but we have to strip it 781a095906SJoonwon Kang // from the URI since topCollections does not include it in its URIs. 791a095906SJoonwon Kang if (segments.size() < 2) 801a095906SJoonwon Kang { 811a095906SJoonwon Kang return false; 821a095906SJoonwon Kang } 831a095906SJoonwon Kang if (segments.front() != "redfish") 841a095906SJoonwon Kang { 851a095906SJoonwon Kang return false; 861a095906SJoonwon Kang } 871a095906SJoonwon Kang segments.erase(segments.begin()); 881a095906SJoonwon Kang if (segments.front() != "v1") 891a095906SJoonwon Kang { 901a095906SJoonwon Kang return false; 911a095906SJoonwon Kang } 921a095906SJoonwon Kang segments.erase(segments.begin()); 931a095906SJoonwon Kang 941a095906SJoonwon Kang // Exclude the trailing "/" if it exists such as in "/redfish/v1/". 951a095906SJoonwon Kang if (!segments.empty() && segments.back().empty()) 961a095906SJoonwon Kang { 971a095906SJoonwon Kang segments.pop_back(); 981a095906SJoonwon Kang } 991a095906SJoonwon Kang 1001a095906SJoonwon Kang // If no segments then the passed URI was either "/redfish/v1" or 1018fd333d6SCarson Labrado // "/redfish/v1/". 1021a095906SJoonwon Kang if (segments.empty()) 1038fd333d6SCarson Labrado { 1048fd333d6SCarson Labrado return (searchType == SearchType::ContainsSubordinate) || 1058fd333d6SCarson Labrado (searchType == SearchType::CollOrCon); 1068fd333d6SCarson Labrado } 1071a095906SJoonwon Kang std::string_view url = segments.buffer(); 1083544d2a7SEd Tanous const auto* it = std::ranges::lower_bound(topCollections, url); 1098fd333d6SCarson Labrado if (it == topCollections.end()) 1108fd333d6SCarson Labrado { 1118fd333d6SCarson Labrado // parsedUrl is alphabetically after the last entry in the array so it 1128fd333d6SCarson Labrado // can't be a top collection or up tree from a top collection 1138fd333d6SCarson Labrado return false; 1148fd333d6SCarson Labrado } 1158fd333d6SCarson Labrado 1168fd333d6SCarson Labrado boost::urls::url collectionUrl(*it); 1178fd333d6SCarson Labrado boost::urls::segments_view collectionSegments = collectionUrl.segments(); 1188fd333d6SCarson Labrado boost::urls::segments_view::iterator itCollection = 1198fd333d6SCarson Labrado collectionSegments.begin(); 1208fd333d6SCarson Labrado const boost::urls::segments_view::const_iterator endCollection = 1218fd333d6SCarson Labrado collectionSegments.end(); 1228fd333d6SCarson Labrado 1238fd333d6SCarson Labrado // Each segment in the passed URI should match the found collection 1241a095906SJoonwon Kang for (const auto& segment : segments) 1258fd333d6SCarson Labrado { 1268fd333d6SCarson Labrado if (itCollection == endCollection) 1278fd333d6SCarson Labrado { 1288fd333d6SCarson Labrado // Leftover segments means the target is for an aggregation 1298fd333d6SCarson Labrado // supported resource 1308fd333d6SCarson Labrado return searchType == SearchType::Resource; 1318fd333d6SCarson Labrado } 1328fd333d6SCarson Labrado 1338fd333d6SCarson Labrado if (segment != (*itCollection)) 1348fd333d6SCarson Labrado { 1358fd333d6SCarson Labrado return false; 1368fd333d6SCarson Labrado } 1378fd333d6SCarson Labrado itCollection++; 1388fd333d6SCarson Labrado } 1398fd333d6SCarson Labrado 1408fd333d6SCarson Labrado // No remaining segments means the passed URI was a top level collection 1418fd333d6SCarson Labrado if (searchType == SearchType::Collection) 1428fd333d6SCarson Labrado { 1438fd333d6SCarson Labrado return itCollection == endCollection; 1448fd333d6SCarson Labrado } 1458fd333d6SCarson Labrado if (searchType == SearchType::ContainsSubordinate) 1468fd333d6SCarson Labrado { 1478fd333d6SCarson Labrado return itCollection != endCollection; 1488fd333d6SCarson Labrado } 1498fd333d6SCarson Labrado 1508fd333d6SCarson Labrado // Return this check instead of "true" in case other SearchTypes get added 1518fd333d6SCarson Labrado return searchType == SearchType::CollOrCon; 1528fd333d6SCarson Labrado } 1538fd333d6SCarson Labrado 1547e8890c5SCarson Labrado // Determines if the passed property contains a URI. Those property names 1557e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically 1567e8890c5SCarson Labrado // defined in the above array. 15726ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName) 1587e8890c5SCarson Labrado { 15918f8f608SEd Tanous if (propertyName.ends_with("uri") || propertyName.ends_with("Uri") || 16018f8f608SEd Tanous propertyName.ends_with("URI")) 16118f8f608SEd Tanous { 16218f8f608SEd Tanous return true; 16318f8f608SEd Tanous } 16418f8f608SEd Tanous return std::binary_search(nonUriProperties.begin(), nonUriProperties.end(), 1657e8890c5SCarson Labrado propertyName); 1667e8890c5SCarson Labrado } 1677e8890c5SCarson Labrado 1684ff0f1f4SEd Tanous inline void addPrefixToStringItem(std::string& strValue, 1690af78d5aSKhang Kieu std::string_view prefix) 1701c0bb5c6SCarson Labrado { 1711c0bb5c6SCarson Labrado // Make sure the value is a properly formatted URI 1720af78d5aSKhang Kieu auto parsed = boost::urls::parse_relative_ref(strValue); 1731c0bb5c6SCarson Labrado if (!parsed) 1741c0bb5c6SCarson Labrado { 175bf2ddedeSCarson Labrado // Note that DMTF URIs such as 176bf2ddedeSCarson Labrado // https://redfish.dmtf.org/registries/Base.1.15.0.json will fail this 177bf2ddedeSCarson Labrado // check and that's okay 178bf2ddedeSCarson Labrado BMCWEB_LOG_DEBUG("Couldn't parse URI from resource {}", strValue); 1791c0bb5c6SCarson Labrado return; 1801c0bb5c6SCarson Labrado } 1811c0bb5c6SCarson Labrado 182daadfb2eSEd Tanous const boost::urls::url_view& thisUrl = *parsed; 1831c0bb5c6SCarson Labrado 184411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such as 185411e6a11SCarson Labrado // version mismatches between aggregator and satellite BMCs. For now 186411e6a11SCarson Labrado // assume that the aggregator has all the schemas and versions that the 187411e6a11SCarson Labrado // aggregated server has. 188411e6a11SCarson Labrado if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas", 189411e6a11SCarson Labrado crow::utility::OrMorePaths())) 190411e6a11SCarson Labrado { 19162598e31SEd Tanous BMCWEB_LOG_DEBUG("Skipping JsonSchemas URI prefix fixing"); 192411e6a11SCarson Labrado return; 193411e6a11SCarson Labrado } 194411e6a11SCarson Labrado 19511987af6SCarson Labrado // The first two segments should be "/redfish/v1". We need to check that 19611987af6SCarson Labrado // before we can search topCollections 19711987af6SCarson Labrado if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 19811987af6SCarson Labrado crow::utility::OrMorePaths())) 1991c0bb5c6SCarson Labrado { 2001c0bb5c6SCarson Labrado return; 2011c0bb5c6SCarson Labrado } 2021c0bb5c6SCarson Labrado 20311987af6SCarson Labrado // Check array adding a segment each time until collection is identified 20411987af6SCarson Labrado // Add prefix to segment after the collection 20511987af6SCarson Labrado const boost::urls::segments_view urlSegments = thisUrl.segments(); 20611987af6SCarson Labrado bool addedPrefix = false; 20711987af6SCarson Labrado boost::urls::url url("/"); 2084a7fbefdSEd Tanous boost::urls::segments_view::const_iterator it = urlSegments.begin(); 20911987af6SCarson Labrado const boost::urls::segments_view::const_iterator end = urlSegments.end(); 21011987af6SCarson Labrado 21111987af6SCarson Labrado // Skip past the leading "/redfish/v1" 21211987af6SCarson Labrado it++; 21311987af6SCarson Labrado it++; 21411987af6SCarson Labrado for (; it != end; it++) 2151c0bb5c6SCarson Labrado { 21611987af6SCarson Labrado // Trailing "/" will result in an empty segment. In that case we need 21711987af6SCarson Labrado // to return so we don't apply a prefix to top level collections such 21811987af6SCarson Labrado // as "/redfish/v1/Chassis/" 21911987af6SCarson Labrado if ((*it).empty()) 22011987af6SCarson Labrado { 221411e6a11SCarson Labrado return; 2221c0bb5c6SCarson Labrado } 2231c0bb5c6SCarson Labrado 22411987af6SCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 22511987af6SCarson Labrado url.buffer())) 2261c0bb5c6SCarson Labrado { 22711987af6SCarson Labrado std::string collectionItem(prefix); 22811987af6SCarson Labrado collectionItem += "_" + (*it); 22911987af6SCarson Labrado url.segments().push_back(collectionItem); 23011987af6SCarson Labrado it++; 23111987af6SCarson Labrado addedPrefix = true; 23211987af6SCarson Labrado break; 23311987af6SCarson Labrado } 23411987af6SCarson Labrado 23511987af6SCarson Labrado url.segments().push_back(*it); 23611987af6SCarson Labrado } 23711987af6SCarson Labrado 23811987af6SCarson Labrado // Finish constructing the URL here (if needed) to avoid additional checks 23911987af6SCarson Labrado for (; it != end; it++) 24011987af6SCarson Labrado { 24111987af6SCarson Labrado url.segments().push_back(*it); 24211987af6SCarson Labrado } 24311987af6SCarson Labrado 24411987af6SCarson Labrado if (addedPrefix) 24511987af6SCarson Labrado { 24611987af6SCarson Labrado url.segments().insert(url.segments().begin(), {"redfish", "v1"}); 2470af78d5aSKhang Kieu strValue = url.buffer(); 2481c0bb5c6SCarson Labrado } 2491c0bb5c6SCarson Labrado } 2501c0bb5c6SCarson Labrado 2514ff0f1f4SEd Tanous inline void addPrefixToItem(nlohmann::json& item, std::string_view prefix) 2520af78d5aSKhang Kieu { 2530af78d5aSKhang Kieu std::string* strValue = item.get_ptr<std::string*>(); 2540af78d5aSKhang Kieu if (strValue == nullptr) 2550af78d5aSKhang Kieu { 256bf2ddedeSCarson Labrado // Values for properties like "InvalidURI" and "ResourceMissingAtURI" 257bf2ddedeSCarson Labrado // from within the Base Registry are objects instead of strings and will 258bf2ddedeSCarson Labrado // fall into this check 259bf2ddedeSCarson Labrado BMCWEB_LOG_DEBUG("Field was not a string"); 2600af78d5aSKhang Kieu return; 2610af78d5aSKhang Kieu } 2620af78d5aSKhang Kieu addPrefixToStringItem(*strValue, prefix); 2630af78d5aSKhang Kieu item = *strValue; 2640af78d5aSKhang Kieu } 2650af78d5aSKhang Kieu 2664ff0f1f4SEd Tanous inline void addAggregatedHeaders(crow::Response& asyncResp, 26724dadc88SCarson Labrado const crow::Response& resp, 2680af78d5aSKhang Kieu std::string_view prefix) 2690af78d5aSKhang Kieu { 2700af78d5aSKhang Kieu if (!resp.getHeaderValue("Content-Type").empty()) 2710af78d5aSKhang Kieu { 2720af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::content_type, 2730af78d5aSKhang Kieu resp.getHeaderValue("Content-Type")); 2740af78d5aSKhang Kieu } 2750af78d5aSKhang Kieu if (!resp.getHeaderValue("Allow").empty()) 2760af78d5aSKhang Kieu { 2770af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::allow, 2780af78d5aSKhang Kieu resp.getHeaderValue("Allow")); 2790af78d5aSKhang Kieu } 2800af78d5aSKhang Kieu std::string_view header = resp.getHeaderValue("Location"); 2810af78d5aSKhang Kieu if (!header.empty()) 2820af78d5aSKhang Kieu { 2830af78d5aSKhang Kieu std::string location(header); 2840af78d5aSKhang Kieu addPrefixToStringItem(location, prefix); 2850af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::location, location); 2860af78d5aSKhang Kieu } 2870af78d5aSKhang Kieu if (!resp.getHeaderValue("Retry-After").empty()) 2880af78d5aSKhang Kieu { 2890af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::retry_after, 2900af78d5aSKhang Kieu resp.getHeaderValue("Retry-After")); 2910af78d5aSKhang Kieu } 2920af78d5aSKhang Kieu // TODO: we need special handling for Link Header Value 2930af78d5aSKhang Kieu } 2940af78d5aSKhang Kieu 295b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others 2964ff0f1f4SEd Tanous inline void addPrefixToHeadersInResp(nlohmann::json& json, 2974ff0f1f4SEd Tanous std::string_view prefix) 298b27e1cbeSCarson Labrado { 299b27e1cbeSCarson Labrado // The passed in "HttpHeaders" should be an array of headers 300b27e1cbeSCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 301b27e1cbeSCarson Labrado if (array == nullptr) 302b27e1cbeSCarson Labrado { 30362598e31SEd Tanous BMCWEB_LOG_ERROR("Field wasn't an array_t????"); 304b27e1cbeSCarson Labrado return; 305b27e1cbeSCarson Labrado } 306b27e1cbeSCarson Labrado 307b27e1cbeSCarson Labrado for (nlohmann::json& item : *array) 308b27e1cbeSCarson Labrado { 309b27e1cbeSCarson Labrado // Each header is a single string with the form "<Field>: <Value>" 310b27e1cbeSCarson Labrado std::string* strHeader = item.get_ptr<std::string*>(); 311b27e1cbeSCarson Labrado if (strHeader == nullptr) 312b27e1cbeSCarson Labrado { 31362598e31SEd Tanous BMCWEB_LOG_CRITICAL("Field wasn't a string????"); 314b27e1cbeSCarson Labrado continue; 315b27e1cbeSCarson Labrado } 316b27e1cbeSCarson Labrado 317b27e1cbeSCarson Labrado constexpr std::string_view location = "Location: "; 318b27e1cbeSCarson Labrado if (strHeader->starts_with(location)) 319b27e1cbeSCarson Labrado { 320b27e1cbeSCarson Labrado std::string header = strHeader->substr(location.size()); 321b27e1cbeSCarson Labrado addPrefixToStringItem(header, prefix); 322b27e1cbeSCarson Labrado *strHeader = std::string(location) + header; 323b27e1cbeSCarson Labrado } 324b27e1cbeSCarson Labrado } 325b27e1cbeSCarson Labrado } 326b27e1cbeSCarson Labrado 3271c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for 3287e8890c5SCarson Labrado // an aggregated resource. 3294ff0f1f4SEd Tanous inline void addPrefixes(nlohmann::json& json, std::string_view prefix) 3301c0bb5c6SCarson Labrado { 3311c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 3321c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 3331c0bb5c6SCarson Labrado if (object != nullptr) 3341c0bb5c6SCarson Labrado { 3351c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 3361c0bb5c6SCarson Labrado { 3377e8890c5SCarson Labrado if (isPropertyUri(item.first)) 3381c0bb5c6SCarson Labrado { 3397e8890c5SCarson Labrado addPrefixToItem(item.second, prefix); 3401c0bb5c6SCarson Labrado continue; 3411c0bb5c6SCarson Labrado } 3421c0bb5c6SCarson Labrado 343b27e1cbeSCarson Labrado // "HttpHeaders" contains HTTP headers. Among those we need to 344b27e1cbeSCarson Labrado // attempt to fix the "Location" header 345b27e1cbeSCarson Labrado if (item.first == "HttpHeaders") 346b27e1cbeSCarson Labrado { 347b27e1cbeSCarson Labrado addPrefixToHeadersInResp(item.second, prefix); 348b27e1cbeSCarson Labrado continue; 349b27e1cbeSCarson Labrado } 350b27e1cbeSCarson Labrado 3518ece0e45SEd Tanous // Recursively parse the rest of the json 3521c0bb5c6SCarson Labrado addPrefixes(item.second, prefix); 3531c0bb5c6SCarson Labrado } 3541c0bb5c6SCarson Labrado return; 3551c0bb5c6SCarson Labrado } 3561c0bb5c6SCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 3571c0bb5c6SCarson Labrado if (array != nullptr) 3581c0bb5c6SCarson Labrado { 3591c0bb5c6SCarson Labrado for (nlohmann::json& item : *array) 3601c0bb5c6SCarson Labrado { 3611c0bb5c6SCarson Labrado addPrefixes(item, prefix); 3621c0bb5c6SCarson Labrado } 3631c0bb5c6SCarson Labrado } 3641c0bb5c6SCarson Labrado } 3651c0bb5c6SCarson Labrado 366d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode) 367a7a80296SCarson Labrado { 36832d7d8ebSCarson Labrado // Allow all response codes because we want to surface any satellite 36932d7d8ebSCarson Labrado // issue to the client 37062598e31SEd Tanous BMCWEB_LOG_DEBUG("Received {} response from satellite", respCode); 371d14a48ffSCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 372d14a48ffSCarson Labrado } 373d14a48ffSCarson Labrado 374d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy() 375d14a48ffSCarson Labrado { 3766bd30813SEd Tanous return {.maxRetryAttempts = 0, 377d14a48ffSCarson Labrado .requestByteLimit = aggregatorReadBodyLimit, 378d14a48ffSCarson Labrado .maxConnections = 20, 379d14a48ffSCarson Labrado .retryPolicyAction = "TerminateAfterRetries", 380d14a48ffSCarson Labrado .retryIntervalSecs = std::chrono::seconds(0), 381d14a48ffSCarson Labrado .invalidResp = aggregationRetryHandler}; 382d14a48ffSCarson Labrado } 383d14a48ffSCarson Labrado 384d14a48ffSCarson Labrado class RedfishAggregator 385d14a48ffSCarson Labrado { 386d14a48ffSCarson Labrado private: 387d14a48ffSCarson Labrado crow::HttpClient client; 388d14a48ffSCarson Labrado 3897fb33566SCarson Labrado // Dummy callback used by the Constructor so that it can report the number 3907fb33566SCarson Labrado // of satellite configs when the class is first created 3917fb33566SCarson Labrado static void constructorCallback( 3928b2521a5SCarson Labrado const boost::system::error_code& ec, 3937fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 3947fb33566SCarson Labrado { 3957fb33566SCarson Labrado if (ec) 3967fb33566SCarson Labrado { 39762598e31SEd Tanous BMCWEB_LOG_ERROR("Something went wrong while querying dbus!"); 3987fb33566SCarson Labrado return; 3997fb33566SCarson Labrado } 4007fb33566SCarson Labrado 40162598e31SEd Tanous BMCWEB_LOG_DEBUG("There were {} satellite configs found at startup", 40262598e31SEd Tanous std::to_string(satelliteInfo.size())); 4037fb33566SCarson Labrado } 4047fb33566SCarson Labrado 4057fb33566SCarson Labrado // Search D-Bus objects for satellite config objects and add their 4067fb33566SCarson Labrado // information if valid 4077fb33566SCarson Labrado static void findSatelliteConfigs( 4087fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects, 4097fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 4107fb33566SCarson Labrado { 4117fb33566SCarson Labrado for (const auto& objectPath : objects) 4127fb33566SCarson Labrado { 4137fb33566SCarson Labrado for (const auto& interface : objectPath.second) 4147fb33566SCarson Labrado { 4157fb33566SCarson Labrado if (interface.first == 4167fb33566SCarson Labrado "xyz.openbmc_project.Configuration.SatelliteController") 4177fb33566SCarson Labrado { 41862598e31SEd Tanous BMCWEB_LOG_DEBUG("Found Satellite Controller at {}", 41962598e31SEd Tanous objectPath.first.str); 4207fb33566SCarson Labrado 42105916cefSCarson Labrado if (!satelliteInfo.empty()) 42205916cefSCarson Labrado { 42362598e31SEd Tanous BMCWEB_LOG_ERROR( 42462598e31SEd Tanous "Redfish Aggregation only supports one satellite!"); 42562598e31SEd Tanous BMCWEB_LOG_DEBUG("Clearing all satellite data"); 42605916cefSCarson Labrado satelliteInfo.clear(); 42705916cefSCarson Labrado return; 42805916cefSCarson Labrado } 42905916cefSCarson Labrado 43005916cefSCarson Labrado // For now assume there will only be one satellite config. 43105916cefSCarson Labrado // Assign it the name/prefix "5B247A" 43205916cefSCarson Labrado addSatelliteConfig("5B247A", interface.second, 43305916cefSCarson Labrado satelliteInfo); 4347fb33566SCarson Labrado } 4357fb33566SCarson Labrado } 4367fb33566SCarson Labrado } 4377fb33566SCarson Labrado } 4387fb33566SCarson Labrado 4397fb33566SCarson Labrado // Parse the properties of a satellite config object and add the 4407fb33566SCarson Labrado // configuration if the properties are valid 4417fb33566SCarson Labrado static void addSatelliteConfig( 44205916cefSCarson Labrado const std::string& name, 4437fb33566SCarson Labrado const dbus::utility::DBusPropertiesMap& properties, 4447fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 4457fb33566SCarson Labrado { 4467fb33566SCarson Labrado boost::urls::url url; 4477fb33566SCarson Labrado 4487fb33566SCarson Labrado for (const auto& prop : properties) 4497fb33566SCarson Labrado { 45005916cefSCarson Labrado if (prop.first == "Hostname") 4517fb33566SCarson Labrado { 4527fb33566SCarson Labrado const std::string* propVal = 4537fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 4547fb33566SCarson Labrado if (propVal == nullptr) 4557fb33566SCarson Labrado { 45662598e31SEd Tanous BMCWEB_LOG_ERROR("Invalid Hostname value"); 4577fb33566SCarson Labrado return; 4587fb33566SCarson Labrado } 4597fb33566SCarson Labrado url.set_host(*propVal); 4607fb33566SCarson Labrado } 4617fb33566SCarson Labrado 4627fb33566SCarson Labrado else if (prop.first == "Port") 4637fb33566SCarson Labrado { 4647fb33566SCarson Labrado const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 4657fb33566SCarson Labrado if (propVal == nullptr) 4667fb33566SCarson Labrado { 46762598e31SEd Tanous BMCWEB_LOG_ERROR("Invalid Port value"); 4687fb33566SCarson Labrado return; 4697fb33566SCarson Labrado } 4707fb33566SCarson Labrado 4717fb33566SCarson Labrado if (*propVal > std::numeric_limits<uint16_t>::max()) 4727fb33566SCarson Labrado { 47362598e31SEd Tanous BMCWEB_LOG_ERROR("Port value out of range"); 4747fb33566SCarson Labrado return; 4757fb33566SCarson Labrado } 476079360aeSEd Tanous url.set_port(std::to_string(static_cast<uint16_t>(*propVal))); 4777fb33566SCarson Labrado } 4787fb33566SCarson Labrado 4797fb33566SCarson Labrado else if (prop.first == "AuthType") 4807fb33566SCarson Labrado { 4817fb33566SCarson Labrado const std::string* propVal = 4827fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 4837fb33566SCarson Labrado if (propVal == nullptr) 4847fb33566SCarson Labrado { 48562598e31SEd Tanous BMCWEB_LOG_ERROR("Invalid AuthType value"); 4867fb33566SCarson Labrado return; 4877fb33566SCarson Labrado } 4887fb33566SCarson Labrado 4897fb33566SCarson Labrado // For now assume authentication not required to communicate 4907fb33566SCarson Labrado // with the satellite BMC 4917fb33566SCarson Labrado if (*propVal != "None") 4927fb33566SCarson Labrado { 49362598e31SEd Tanous BMCWEB_LOG_ERROR( 49462598e31SEd Tanous "Unsupported AuthType value: {}, only \"none\" is supported", 49562598e31SEd Tanous *propVal); 4967fb33566SCarson Labrado return; 4977fb33566SCarson Labrado } 4987fb33566SCarson Labrado url.set_scheme("http"); 4997fb33566SCarson Labrado } 5007fb33566SCarson Labrado } // Finished reading properties 5017fb33566SCarson Labrado 5027fb33566SCarson Labrado // Make sure all required config information was made available 5037fb33566SCarson Labrado if (url.host().empty()) 5047fb33566SCarson Labrado { 50562598e31SEd Tanous BMCWEB_LOG_ERROR("Satellite config {} missing Host", name); 5067fb33566SCarson Labrado return; 5077fb33566SCarson Labrado } 5087fb33566SCarson Labrado 5097fb33566SCarson Labrado if (!url.has_port()) 5107fb33566SCarson Labrado { 51162598e31SEd Tanous BMCWEB_LOG_ERROR("Satellite config {} missing Port", name); 5127fb33566SCarson Labrado return; 5137fb33566SCarson Labrado } 5147fb33566SCarson Labrado 5157fb33566SCarson Labrado if (!url.has_scheme()) 5167fb33566SCarson Labrado { 51762598e31SEd Tanous BMCWEB_LOG_ERROR("Satellite config {} missing AuthType", name); 5187fb33566SCarson Labrado return; 5197fb33566SCarson Labrado } 5207fb33566SCarson Labrado 5217fb33566SCarson Labrado std::string resultString; 5227fb33566SCarson Labrado auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 5237fb33566SCarson Labrado if (result.second) 5247fb33566SCarson Labrado { 5257fb33566SCarson Labrado resultString = "Added new satellite config "; 5267fb33566SCarson Labrado } 5277fb33566SCarson Labrado else 5287fb33566SCarson Labrado { 5297fb33566SCarson Labrado resultString = "Updated existing satellite config "; 5307fb33566SCarson Labrado } 5317fb33566SCarson Labrado 53262598e31SEd Tanous BMCWEB_LOG_DEBUG("{}{} at {}://{}", resultString, name, 53362598e31SEd Tanous result.first->second.scheme(), 53462598e31SEd Tanous result.first->second.encoded_host_and_port()); 5357fb33566SCarson Labrado } 5367fb33566SCarson Labrado 53746a81465SCarson Labrado enum AggregationType 53846a81465SCarson Labrado { 53946a81465SCarson Labrado Collection, 540e002dbc0SCarson Labrado ContainsSubordinate, 54146a81465SCarson Labrado Resource, 54246a81465SCarson Labrado }; 54346a81465SCarson Labrado 54446a81465SCarson Labrado static void 545e002dbc0SCarson Labrado startAggregation(AggregationType aggType, const crow::Request& thisReq, 54646a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 54746a81465SCarson Labrado { 548e002dbc0SCarson Labrado if (thisReq.method() != boost::beast::http::verb::get) 549e002dbc0SCarson Labrado { 550e002dbc0SCarson Labrado if (aggType == AggregationType::Collection) 551db18fc98SCarson Labrado { 55262598e31SEd Tanous BMCWEB_LOG_DEBUG( 55362598e31SEd Tanous "Only aggregate GET requests to top level collections"); 554db18fc98SCarson Labrado return; 555db18fc98SCarson Labrado } 556db18fc98SCarson Labrado 557e002dbc0SCarson Labrado if (aggType == AggregationType::ContainsSubordinate) 558e002dbc0SCarson Labrado { 55962598e31SEd Tanous BMCWEB_LOG_DEBUG( 56062598e31SEd Tanous "Only aggregate GET requests when uptree of a top level collection"); 561e002dbc0SCarson Labrado return; 562e002dbc0SCarson Labrado } 563e002dbc0SCarson Labrado } 564e002dbc0SCarson Labrado 56546a81465SCarson Labrado // Create a copy of thisReq so we we can still locally process the req 56646a81465SCarson Labrado std::error_code ec; 56746a81465SCarson Labrado auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 56846a81465SCarson Labrado if (ec) 56946a81465SCarson Labrado { 57062598e31SEd Tanous BMCWEB_LOG_ERROR("Failed to create copy of request"); 571e002dbc0SCarson Labrado if (aggType == AggregationType::Resource) 57246a81465SCarson Labrado { 57346a81465SCarson Labrado messages::internalError(asyncResp->res); 57446a81465SCarson Labrado } 57546a81465SCarson Labrado return; 57646a81465SCarson Labrado } 57746a81465SCarson Labrado 5786282bc71SEd Tanous if (aggType == AggregationType::Collection) 5796282bc71SEd Tanous { 5806282bc71SEd Tanous boost::urls::url& urlNew = localReq->url(); 5816282bc71SEd Tanous auto paramsIt = urlNew.params().begin(); 5826282bc71SEd Tanous while (paramsIt != urlNew.params().end()) 5836282bc71SEd Tanous { 5846282bc71SEd Tanous const boost::urls::param& param = *paramsIt; 5856282bc71SEd Tanous // only and $skip, params can't be passed to satellite 5866282bc71SEd Tanous // as applying these filters twice results in different results. 5876282bc71SEd Tanous // Removing them will cause them to only be processed in the 5886282bc71SEd Tanous // aggregator. Note, this still doesn't work for collections 5896282bc71SEd Tanous // that might return less than the complete collection by 5906282bc71SEd Tanous // default, but hopefully those are rare/nonexistent in top 5916282bc71SEd Tanous // collections. bmcweb doesn't implement any of these. 5926282bc71SEd Tanous if (param.key == "only" || param.key == "$skip") 5936282bc71SEd Tanous { 5946282bc71SEd Tanous BMCWEB_LOG_DEBUG( 5956282bc71SEd Tanous "Erasing \"{}\" param from request to top level collection", 5966282bc71SEd Tanous param.key); 5976282bc71SEd Tanous 5986282bc71SEd Tanous paramsIt = urlNew.params().erase(paramsIt); 5996282bc71SEd Tanous continue; 6006282bc71SEd Tanous } 6016282bc71SEd Tanous // Pass all other parameters 6026282bc71SEd Tanous paramsIt++; 6036282bc71SEd Tanous } 6046282bc71SEd Tanous localReq->target(urlNew.buffer()); 6056282bc71SEd Tanous } 6066282bc71SEd Tanous 607e002dbc0SCarson Labrado getSatelliteConfigs( 608e002dbc0SCarson Labrado std::bind_front(aggregateAndHandle, aggType, localReq, asyncResp)); 60946a81465SCarson Labrado } 61046a81465SCarson Labrado 611db18fc98SCarson Labrado static void findSatellite( 61246a81465SCarson Labrado const crow::Request& req, 61346a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 61446a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 61546a81465SCarson Labrado std::string_view memberName) 61646a81465SCarson Labrado { 61746a81465SCarson Labrado // Determine if the resource ID begins with a known prefix 61846a81465SCarson Labrado for (const auto& satellite : satelliteInfo) 61946a81465SCarson Labrado { 62046a81465SCarson Labrado std::string targetPrefix = satellite.first; 62146a81465SCarson Labrado targetPrefix += "_"; 62246a81465SCarson Labrado if (memberName.starts_with(targetPrefix)) 62346a81465SCarson Labrado { 62462598e31SEd Tanous BMCWEB_LOG_DEBUG("\"{}\" is a known prefix", satellite.first); 62546a81465SCarson Labrado 62646a81465SCarson Labrado // Remove the known prefix from the request's URI and 62746a81465SCarson Labrado // then forward to the associated satellite BMC 62846a81465SCarson Labrado getInstance().forwardRequest(req, asyncResp, satellite.first, 62946a81465SCarson Labrado satelliteInfo); 63046a81465SCarson Labrado return; 63146a81465SCarson Labrado } 63246a81465SCarson Labrado } 633db18fc98SCarson Labrado 634db18fc98SCarson Labrado // We didn't recognize the prefix and need to return a 404 63539662a3bSEd Tanous std::string nameStr = req.url().segments().back(); 636db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 63746a81465SCarson Labrado } 63846a81465SCarson Labrado 63946a81465SCarson Labrado // Intended to handle an incoming request based on if Redfish Aggregation 64046a81465SCarson Labrado // is enabled. Forwards request to satellite BMC if it exists. 64146a81465SCarson Labrado static void aggregateAndHandle( 642e002dbc0SCarson Labrado AggregationType aggType, 64346a81465SCarson Labrado const std::shared_ptr<crow::Request>& sharedReq, 64446a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 6458b2521a5SCarson Labrado const boost::system::error_code& ec, 64646a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 64746a81465SCarson Labrado { 64846a81465SCarson Labrado if (sharedReq == nullptr) 64946a81465SCarson Labrado { 65046a81465SCarson Labrado return; 65146a81465SCarson Labrado } 6528b2521a5SCarson Labrado // Something went wrong while querying dbus 6538b2521a5SCarson Labrado if (ec) 6548b2521a5SCarson Labrado { 6558b2521a5SCarson Labrado messages::internalError(asyncResp->res); 6568b2521a5SCarson Labrado return; 6578b2521a5SCarson Labrado } 658db18fc98SCarson Labrado 659db18fc98SCarson Labrado // No satellite configs means we don't need to keep attempting to 660db18fc98SCarson Labrado // aggregate 661db18fc98SCarson Labrado if (satelliteInfo.empty()) 662db18fc98SCarson Labrado { 663e002dbc0SCarson Labrado // For collections or resources that can contain a subordinate 664e002dbc0SCarson Labrado // top level collection we'll also handle the request locally so we 665db18fc98SCarson Labrado // don't need to write an error code 666e002dbc0SCarson Labrado if (aggType == AggregationType::Resource) 667db18fc98SCarson Labrado { 66839662a3bSEd Tanous std::string nameStr = sharedReq->url().segments().back(); 669db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 670db18fc98SCarson Labrado } 671db18fc98SCarson Labrado return; 672db18fc98SCarson Labrado } 673db18fc98SCarson Labrado 67446a81465SCarson Labrado const crow::Request& thisReq = *sharedReq; 67562598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregation is enabled, begin processing of {}", 67662598e31SEd Tanous thisReq.target()); 67746a81465SCarson Labrado 67846a81465SCarson Labrado // We previously determined the request is for a collection. No need to 67946a81465SCarson Labrado // check again 680e002dbc0SCarson Labrado if (aggType == AggregationType::Collection) 68146a81465SCarson Labrado { 68262598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregating a collection"); 6834c30e226SCarson Labrado // We need to use a specific response handler and send the 6844c30e226SCarson Labrado // request to all known satellites 6854c30e226SCarson Labrado getInstance().forwardCollectionRequests(thisReq, asyncResp, 6864c30e226SCarson Labrado satelliteInfo); 68746a81465SCarson Labrado return; 68846a81465SCarson Labrado } 68946a81465SCarson Labrado 690e002dbc0SCarson Labrado // We previously determined the request may contain a subordinate 691e002dbc0SCarson Labrado // collection. No need to check again 692e002dbc0SCarson Labrado if (aggType == AggregationType::ContainsSubordinate) 693e002dbc0SCarson Labrado { 69462598e31SEd Tanous BMCWEB_LOG_DEBUG( 69562598e31SEd Tanous "Aggregating what may have a subordinate collection"); 696e002dbc0SCarson Labrado // We need to use a specific response handler and send the 697e002dbc0SCarson Labrado // request to all known satellites 698e002dbc0SCarson Labrado getInstance().forwardContainsSubordinateRequests(thisReq, asyncResp, 699e002dbc0SCarson Labrado satelliteInfo); 700e002dbc0SCarson Labrado return; 701e002dbc0SCarson Labrado } 702e002dbc0SCarson Labrado 70339662a3bSEd Tanous const boost::urls::segments_view urlSegments = thisReq.url().segments(); 7047c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 7054a7fbefdSEd Tanous boost::urls::segments_view::const_iterator it = urlSegments.begin(); 7064a7fbefdSEd Tanous boost::urls::segments_view::const_iterator end = urlSegments.end(); 7077c4c52cbSCarson Labrado 7087c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 7097c4c52cbSCarson Labrado it++; 7107c4c52cbSCarson Labrado it++; 7117c4c52cbSCarson Labrado for (; it != end; it++) 71246a81465SCarson Labrado { 7137c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 7147c4c52cbSCarson Labrado currentUrl.buffer())) 7157c4c52cbSCarson Labrado { 7167c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 7177c4c52cbSCarson Labrado // must contain an aggregation prefix 7187c4c52cbSCarson Labrado findSatellite(thisReq, asyncResp, satelliteInfo, *it); 71946a81465SCarson Labrado return; 72046a81465SCarson Labrado } 72146a81465SCarson Labrado 7227c4c52cbSCarson Labrado currentUrl.segments().push_back(*it); 72346a81465SCarson Labrado } 724db18fc98SCarson Labrado 725db18fc98SCarson Labrado // We shouldn't reach this point since we should've hit one of the 726db18fc98SCarson Labrado // previous exits 727db18fc98SCarson Labrado messages::internalError(asyncResp->res); 72846a81465SCarson Labrado } 72946a81465SCarson Labrado 73046a81465SCarson Labrado // Attempt to forward a request to the satellite BMC associated with the 73146a81465SCarson Labrado // prefix. 73246a81465SCarson Labrado void forwardRequest( 73346a81465SCarson Labrado const crow::Request& thisReq, 73446a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 73546a81465SCarson Labrado const std::string& prefix, 73646a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 73746a81465SCarson Labrado { 73846a81465SCarson Labrado const auto& sat = satelliteInfo.find(prefix); 73946a81465SCarson Labrado if (sat == satelliteInfo.end()) 74046a81465SCarson Labrado { 74146a81465SCarson Labrado // Realistically this shouldn't get called since we perform an 74246a81465SCarson Labrado // earlier check to make sure the prefix exists 74362598e31SEd Tanous BMCWEB_LOG_ERROR("Unrecognized satellite prefix \"{}\"", prefix); 74446a81465SCarson Labrado return; 74546a81465SCarson Labrado } 74646a81465SCarson Labrado 74746a81465SCarson Labrado // We need to strip the prefix from the request's path 748a716aa74SEd Tanous boost::urls::url targetURI(thisReq.target()); 749a716aa74SEd Tanous std::string path = thisReq.url().path(); 750a716aa74SEd Tanous size_t pos = path.find(prefix + "_"); 75146a81465SCarson Labrado if (pos == std::string::npos) 75246a81465SCarson Labrado { 75346a81465SCarson Labrado // If this fails then something went wrong 75462598e31SEd Tanous BMCWEB_LOG_ERROR("Error removing prefix \"{}_\" from request URI", 75562598e31SEd Tanous prefix); 75646a81465SCarson Labrado messages::internalError(asyncResp->res); 75746a81465SCarson Labrado return; 75846a81465SCarson Labrado } 759a716aa74SEd Tanous path.erase(pos, prefix.size() + 1); 76046a81465SCarson Labrado 76146a81465SCarson Labrado std::function<void(crow::Response&)> cb = 7621c0bb5c6SCarson Labrado std::bind_front(processResponse, prefix, asyncResp); 76346a81465SCarson Labrado 76427b0cf90SEd Tanous std::string data = thisReq.body(); 765a716aa74SEd Tanous boost::urls::url url(sat->second); 766a716aa74SEd Tanous url.set_path(path); 767a716aa74SEd Tanous if (targetURI.has_query()) 768a716aa74SEd Tanous { 769a716aa74SEd Tanous url.set_query(targetURI.query()); 770a716aa74SEd Tanous } 77119bb362bSEd Tanous client.sendDataWithCallback(std::move(data), url, 77219bb362bSEd Tanous ensuressl::VerifyCertificate::Verify, 77319bb362bSEd Tanous thisReq.fields(), thisReq.method(), cb); 77446a81465SCarson Labrado } 77546a81465SCarson Labrado 7764c30e226SCarson Labrado // Forward a request for a collection URI to each known satellite BMC 7774c30e226SCarson Labrado void forwardCollectionRequests( 7784c30e226SCarson Labrado const crow::Request& thisReq, 7794c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 7804c30e226SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 7814c30e226SCarson Labrado { 7824c30e226SCarson Labrado for (const auto& sat : satelliteInfo) 7834c30e226SCarson Labrado { 7844c30e226SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 7854c30e226SCarson Labrado processCollectionResponse, sat.first, asyncResp); 7864c30e226SCarson Labrado 787a716aa74SEd Tanous boost::urls::url url(sat.second); 788a716aa74SEd Tanous url.set_path(thisReq.url().path()); 789a716aa74SEd Tanous if (thisReq.url().has_query()) 790a716aa74SEd Tanous { 791a716aa74SEd Tanous url.set_query(thisReq.url().query()); 792a716aa74SEd Tanous } 79327b0cf90SEd Tanous std::string data = thisReq.body(); 79419bb362bSEd Tanous client.sendDataWithCallback(std::move(data), url, 79519bb362bSEd Tanous ensuressl::VerifyCertificate::Verify, 79619bb362bSEd Tanous thisReq.fields(), thisReq.method(), cb); 7974c30e226SCarson Labrado } 7984c30e226SCarson Labrado } 7994c30e226SCarson Labrado 800e002dbc0SCarson Labrado // Forward request for a URI that is uptree of a top level collection to 801e002dbc0SCarson Labrado // each known satellite BMC 802e002dbc0SCarson Labrado void forwardContainsSubordinateRequests( 803e002dbc0SCarson Labrado const crow::Request& thisReq, 804e002dbc0SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 805e002dbc0SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 806e002dbc0SCarson Labrado { 807e002dbc0SCarson Labrado for (const auto& sat : satelliteInfo) 808e002dbc0SCarson Labrado { 809e002dbc0SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 810e002dbc0SCarson Labrado processContainsSubordinateResponse, sat.first, asyncResp); 811e002dbc0SCarson Labrado 812e002dbc0SCarson Labrado // will ignore an expanded resource in the response if that resource 813e002dbc0SCarson Labrado // is not already supported by the aggregating BMC 814e002dbc0SCarson Labrado // TODO: Improve the processing so that we don't have to strip query 815e002dbc0SCarson Labrado // params in this specific case 816a716aa74SEd Tanous boost::urls::url url(sat.second); 817a716aa74SEd Tanous url.set_path(thisReq.url().path()); 818a716aa74SEd Tanous 81927b0cf90SEd Tanous std::string data = thisReq.body(); 820a716aa74SEd Tanous 82119bb362bSEd Tanous client.sendDataWithCallback(std::move(data), url, 82219bb362bSEd Tanous ensuressl::VerifyCertificate::Verify, 82319bb362bSEd Tanous thisReq.fields(), thisReq.method(), cb); 824e002dbc0SCarson Labrado } 825e002dbc0SCarson Labrado } 826e002dbc0SCarson Labrado 82732d7d8ebSCarson Labrado public: 828f8ca6d79SEd Tanous explicit RedfishAggregator(boost::asio::io_context& ioc) : 829f8ca6d79SEd Tanous client(ioc, 830f8ca6d79SEd Tanous std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy())) 831f8ca6d79SEd Tanous { 832f8ca6d79SEd Tanous getSatelliteConfigs(constructorCallback); 833f8ca6d79SEd Tanous } 83432d7d8ebSCarson Labrado RedfishAggregator(const RedfishAggregator&) = delete; 83532d7d8ebSCarson Labrado RedfishAggregator& operator=(const RedfishAggregator&) = delete; 83632d7d8ebSCarson Labrado RedfishAggregator(RedfishAggregator&&) = delete; 83732d7d8ebSCarson Labrado RedfishAggregator& operator=(RedfishAggregator&&) = delete; 83832d7d8ebSCarson Labrado ~RedfishAggregator() = default; 83932d7d8ebSCarson Labrado 840f8ca6d79SEd Tanous static RedfishAggregator& getInstance(boost::asio::io_context* io = nullptr) 84132d7d8ebSCarson Labrado { 842f8ca6d79SEd Tanous static RedfishAggregator handler(*io); 84332d7d8ebSCarson Labrado return handler; 84432d7d8ebSCarson Labrado } 84532d7d8ebSCarson Labrado 8468b2521a5SCarson Labrado // Polls D-Bus to get all available satellite config information 8478b2521a5SCarson Labrado // Expects a handler which interacts with the returned configs 8488b2521a5SCarson Labrado static void getSatelliteConfigs( 8498b2521a5SCarson Labrado std::function< 8508b2521a5SCarson Labrado void(const boost::system::error_code&, 8518b2521a5SCarson Labrado const std::unordered_map<std::string, boost::urls::url>&)> 8528b2521a5SCarson Labrado handler) 8538b2521a5SCarson Labrado { 85462598e31SEd Tanous BMCWEB_LOG_DEBUG("Gathering satellite configs"); 8555eb468daSGeorge Liu sdbusplus::message::object_path path("/xyz/openbmc_project/inventory"); 8565eb468daSGeorge Liu dbus::utility::getManagedObjects( 8575eb468daSGeorge Liu "xyz.openbmc_project.EntityManager", path, 8588b2521a5SCarson Labrado [handler{std::move(handler)}]( 8598b2521a5SCarson Labrado const boost::system::error_code& ec, 8608b2521a5SCarson Labrado const dbus::utility::ManagedObjectType& objects) { 8618b2521a5SCarson Labrado std::unordered_map<std::string, boost::urls::url> satelliteInfo; 8628b2521a5SCarson Labrado if (ec) 8638b2521a5SCarson Labrado { 86462598e31SEd Tanous BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(), 86562598e31SEd Tanous ec.message()); 8668b2521a5SCarson Labrado handler(ec, satelliteInfo); 8678b2521a5SCarson Labrado return; 8688b2521a5SCarson Labrado } 8698b2521a5SCarson Labrado 8708b2521a5SCarson Labrado // Maps a chosen alias representing a satellite BMC to a url 8718b2521a5SCarson Labrado // containing the information required to create a http 8728b2521a5SCarson Labrado // connection to the satellite 8738b2521a5SCarson Labrado findSatelliteConfigs(objects, satelliteInfo); 8748b2521a5SCarson Labrado 8758b2521a5SCarson Labrado if (!satelliteInfo.empty()) 8768b2521a5SCarson Labrado { 87762598e31SEd Tanous BMCWEB_LOG_DEBUG( 87862598e31SEd Tanous "Redfish Aggregation enabled with {} satellite BMCs", 87962598e31SEd Tanous std::to_string(satelliteInfo.size())); 8808b2521a5SCarson Labrado } 8818b2521a5SCarson Labrado else 8828b2521a5SCarson Labrado { 88362598e31SEd Tanous BMCWEB_LOG_DEBUG( 88462598e31SEd Tanous "No satellite BMCs detected. Redfish Aggregation not enabled"); 8858b2521a5SCarson Labrado } 8868b2521a5SCarson Labrado handler(ec, satelliteInfo); 8875eb468daSGeorge Liu }); 8888b2521a5SCarson Labrado } 8898b2521a5SCarson Labrado 89046a81465SCarson Labrado // Processes the response returned by a satellite BMC and loads its 89146a81465SCarson Labrado // contents into asyncResp 89246a81465SCarson Labrado static void 8931c0bb5c6SCarson Labrado processResponse(std::string_view prefix, 8941c0bb5c6SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 89546a81465SCarson Labrado crow::Response& resp) 89646a81465SCarson Labrado { 89743e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 89843e14d38SCarson Labrado // overwrite the response headers in that case 89946b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 90046b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 90143e14d38SCarson Labrado { 90243e14d38SCarson Labrado asyncResp->res.result(resp.result()); 90343e14d38SCarson Labrado return; 90443e14d38SCarson Labrado } 90543e14d38SCarson Labrado 90632d7d8ebSCarson Labrado // We want to attempt prefix fixing regardless of response code 90746a81465SCarson Labrado // The resp will not have a json component 90846a81465SCarson Labrado // We need to create a json from resp's stringResponse 90918f8f608SEd Tanous if (isJsonContentType(resp.getHeaderValue("Content-Type"))) 91046a81465SCarson Labrado { 911bd79bce8SPatrick Williams nlohmann::json jsonVal = 912bd79bce8SPatrick Williams nlohmann::json::parse(*resp.body(), nullptr, false); 91346a81465SCarson Labrado if (jsonVal.is_discarded()) 91446a81465SCarson Labrado { 91562598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 91646a81465SCarson Labrado messages::operationFailed(asyncResp->res); 91746a81465SCarson Labrado return; 91846a81465SCarson Labrado } 91946a81465SCarson Labrado 92062598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 92146a81465SCarson Labrado 9221c0bb5c6SCarson Labrado addPrefixes(jsonVal, prefix); 9231c0bb5c6SCarson Labrado 92462598e31SEd Tanous BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response"); 9251c0bb5c6SCarson Labrado 92646a81465SCarson Labrado asyncResp->res.result(resp.result()); 92746a81465SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 92846a81465SCarson Labrado 92962598e31SEd Tanous BMCWEB_LOG_DEBUG("Finished writing asyncResp"); 93046a81465SCarson Labrado } 93146a81465SCarson Labrado else 93246a81465SCarson Labrado { 9330af78d5aSKhang Kieu // We allow any Content-Type that is not "application/json" now 9340af78d5aSKhang Kieu asyncResp->res.result(resp.result()); 93527b0cf90SEd Tanous asyncResp->res.copyBody(resp); 93646a81465SCarson Labrado } 9370af78d5aSKhang Kieu addAggregatedHeaders(asyncResp->res, resp, prefix); 93846a81465SCarson Labrado } 93946a81465SCarson Labrado 9404c30e226SCarson Labrado // Processes the collection response returned by a satellite BMC and merges 9414c30e226SCarson Labrado // its "@odata.id" values 9424c30e226SCarson Labrado static void processCollectionResponse( 9434c30e226SCarson Labrado const std::string& prefix, 9444c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 9454c30e226SCarson Labrado crow::Response& resp) 9464c30e226SCarson Labrado { 94743e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 94843e14d38SCarson Labrado // overwrite the response headers in that case 94946b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 95046b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 95143e14d38SCarson Labrado { 95243e14d38SCarson Labrado return; 95343e14d38SCarson Labrado } 95443e14d38SCarson Labrado 9554c30e226SCarson Labrado if (resp.resultInt() != 200) 9564c30e226SCarson Labrado { 95762598e31SEd Tanous BMCWEB_LOG_DEBUG( 95862598e31SEd Tanous "Collection resource does not exist in satellite BMC \"{}\"", 95962598e31SEd Tanous prefix); 9604c30e226SCarson Labrado // Return the error if we haven't had any successes 9614c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 9624c30e226SCarson Labrado { 96346b30283SCarson Labrado asyncResp->res.result(resp.result()); 96427b0cf90SEd Tanous asyncResp->res.copyBody(resp); 9654c30e226SCarson Labrado } 9664c30e226SCarson Labrado return; 9674c30e226SCarson Labrado } 9684c30e226SCarson Labrado 9694c30e226SCarson Labrado // The resp will not have a json component 9704c30e226SCarson Labrado // We need to create a json from resp's stringResponse 97118f8f608SEd Tanous if (isJsonContentType(resp.getHeaderValue("Content-Type"))) 9724c30e226SCarson Labrado { 973bd79bce8SPatrick Williams nlohmann::json jsonVal = 974bd79bce8SPatrick Williams nlohmann::json::parse(*resp.body(), nullptr, false); 9754c30e226SCarson Labrado if (jsonVal.is_discarded()) 9764c30e226SCarson Labrado { 97762598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 9784c30e226SCarson Labrado 9794c30e226SCarson Labrado // Notify the user if doing so won't overwrite a valid response 98046b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 9814c30e226SCarson Labrado { 9824c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 9834c30e226SCarson Labrado } 9844c30e226SCarson Labrado return; 9854c30e226SCarson Labrado } 9864c30e226SCarson Labrado 98762598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 9884c30e226SCarson Labrado 9894c30e226SCarson Labrado // Now we need to add the prefix to the URIs contained in the 9904c30e226SCarson Labrado // response. 9914c30e226SCarson Labrado addPrefixes(jsonVal, prefix); 9924c30e226SCarson Labrado 99362598e31SEd Tanous BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response"); 9944c30e226SCarson Labrado 9954c30e226SCarson Labrado // If this resource collection does not exist on the aggregating bmc 9964c30e226SCarson Labrado // and has not already been added from processing the response from 9974c30e226SCarson Labrado // a different satellite then we need to completely overwrite 9984c30e226SCarson Labrado // asyncResp 9994c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 10004c30e226SCarson Labrado { 10014c30e226SCarson Labrado // We only want to aggregate collections that contain a 10024c30e226SCarson Labrado // "Members" array 10034c30e226SCarson Labrado if ((!jsonVal.contains("Members")) && 10044c30e226SCarson Labrado (!jsonVal["Members"].is_array())) 10054c30e226SCarson Labrado { 100662598e31SEd Tanous BMCWEB_LOG_DEBUG( 100762598e31SEd Tanous "Skipping aggregating unsupported resource"); 10084c30e226SCarson Labrado return; 10094c30e226SCarson Labrado } 10104c30e226SCarson Labrado 101162598e31SEd Tanous BMCWEB_LOG_DEBUG( 101262598e31SEd Tanous "Collection does not exist, overwriting asyncResp"); 10134c30e226SCarson Labrado asyncResp->res.result(resp.result()); 10144c30e226SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 101543e14d38SCarson Labrado asyncResp->res.addHeader("Content-Type", "application/json"); 10164c30e226SCarson Labrado 101762598e31SEd Tanous BMCWEB_LOG_DEBUG("Finished overwriting asyncResp"); 10184c30e226SCarson Labrado } 10194c30e226SCarson Labrado else 10204c30e226SCarson Labrado { 10214c30e226SCarson Labrado // We only want to aggregate collections that contain a 10224c30e226SCarson Labrado // "Members" array 10234c30e226SCarson Labrado if ((!asyncResp->res.jsonValue.contains("Members")) && 10244c30e226SCarson Labrado (!asyncResp->res.jsonValue["Members"].is_array())) 10254c30e226SCarson Labrado 10264c30e226SCarson Labrado { 102762598e31SEd Tanous BMCWEB_LOG_DEBUG( 102862598e31SEd Tanous "Skipping aggregating unsupported resource"); 10294c30e226SCarson Labrado return; 10304c30e226SCarson Labrado } 10314c30e226SCarson Labrado 103262598e31SEd Tanous BMCWEB_LOG_DEBUG( 103362598e31SEd Tanous "Adding aggregated resources from \"{}\" to collection", 103462598e31SEd Tanous prefix); 10354c30e226SCarson Labrado 10364c30e226SCarson Labrado // TODO: This is a potential race condition with multiple 10374c30e226SCarson Labrado // satellites and the aggregating bmc attempting to write to 10384c30e226SCarson Labrado // update this array. May need to cascade calls to the next 10394c30e226SCarson Labrado // satellite at the end of this function. 10404c30e226SCarson Labrado // This is presumably not a concern when there is only a single 10414c30e226SCarson Labrado // satellite since the aggregating bmc should have completed 10424c30e226SCarson Labrado // before the response is received from the satellite. 10434c30e226SCarson Labrado 10444c30e226SCarson Labrado auto& members = asyncResp->res.jsonValue["Members"]; 10454c30e226SCarson Labrado auto& satMembers = jsonVal["Members"]; 10464c30e226SCarson Labrado for (auto& satMem : satMembers) 10474c30e226SCarson Labrado { 1048b2ba3072SPatrick Williams members.emplace_back(std::move(satMem)); 10494c30e226SCarson Labrado } 10504c30e226SCarson Labrado asyncResp->res.jsonValue["Members@odata.count"] = 10514c30e226SCarson Labrado members.size(); 10524c30e226SCarson Labrado 10534c30e226SCarson Labrado // TODO: Do we need to sort() after updating the array? 10544c30e226SCarson Labrado } 10554c30e226SCarson Labrado } 10564c30e226SCarson Labrado else 10574c30e226SCarson Labrado { 105862598e31SEd Tanous BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"", 105962598e31SEd Tanous prefix); 106043e14d38SCarson Labrado // We received a response that was not a json. 106146b30283SCarson Labrado // Notify the user only if we did not receive any valid responses 106246b30283SCarson Labrado // and if the resource collection does not already exist on the 106346b30283SCarson Labrado // aggregating BMC 106446b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 10654c30e226SCarson Labrado { 10664c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 10674c30e226SCarson Labrado } 10684c30e226SCarson Labrado } 10694c30e226SCarson Labrado } // End processCollectionResponse() 10704c30e226SCarson Labrado 107146b30283SCarson Labrado // Processes the response returned by a satellite BMC and merges any 107246b30283SCarson Labrado // properties whose "@odata.id" value is the URI or either a top level 107346b30283SCarson Labrado // collection or is uptree from a top level collection 107446b30283SCarson Labrado static void processContainsSubordinateResponse( 107546b30283SCarson Labrado const std::string& prefix, 107646b30283SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 107746b30283SCarson Labrado crow::Response& resp) 107846b30283SCarson Labrado { 107946b30283SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 108046b30283SCarson Labrado // overwrite the response headers in that case 108146b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 108246b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 108346b30283SCarson Labrado { 108446b30283SCarson Labrado return; 108546b30283SCarson Labrado } 108646b30283SCarson Labrado 108746b30283SCarson Labrado if (resp.resultInt() != 200) 108846b30283SCarson Labrado { 108962598e31SEd Tanous BMCWEB_LOG_DEBUG( 109062598e31SEd Tanous "Resource uptree from Collection does not exist in satellite BMC \"{}\"", 109162598e31SEd Tanous prefix); 109246b30283SCarson Labrado // Return the error if we haven't had any successes 109346b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 109446b30283SCarson Labrado { 109546b30283SCarson Labrado asyncResp->res.result(resp.result()); 109627b0cf90SEd Tanous asyncResp->res.copyBody(resp); 109746b30283SCarson Labrado } 109846b30283SCarson Labrado return; 109946b30283SCarson Labrado } 110046b30283SCarson Labrado 110146b30283SCarson Labrado // The resp will not have a json component 110246b30283SCarson Labrado // We need to create a json from resp's stringResponse 110318f8f608SEd Tanous if (isJsonContentType(resp.getHeaderValue("Content-Type"))) 110446b30283SCarson Labrado { 110546b30283SCarson Labrado bool addedLinks = false; 1106bd79bce8SPatrick Williams nlohmann::json jsonVal = 1107bd79bce8SPatrick Williams nlohmann::json::parse(*resp.body(), nullptr, false); 110846b30283SCarson Labrado if (jsonVal.is_discarded()) 110946b30283SCarson Labrado { 111062598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 111146b30283SCarson Labrado 111246b30283SCarson Labrado // Notify the user if doing so won't overwrite a valid response 111346b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 111446b30283SCarson Labrado { 111546b30283SCarson Labrado messages::operationFailed(asyncResp->res); 111646b30283SCarson Labrado } 111746b30283SCarson Labrado return; 111846b30283SCarson Labrado } 111946b30283SCarson Labrado 112062598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 112146b30283SCarson Labrado 112246b30283SCarson Labrado // Parse response and add properties missing from the AsyncResp 112346b30283SCarson Labrado // Valid properties will be of the form <property>.@odata.id and 112446b30283SCarson Labrado // @odata.id is a <URI>. In other words, the json should contain 112546b30283SCarson Labrado // multiple properties such that 112646b30283SCarson Labrado // {"<property>":{"@odata.id": "<URI>"}} 112746b30283SCarson Labrado nlohmann::json::object_t* object = 112846b30283SCarson Labrado jsonVal.get_ptr<nlohmann::json::object_t*>(); 112946b30283SCarson Labrado if (object == nullptr) 113046b30283SCarson Labrado { 113162598e31SEd Tanous BMCWEB_LOG_ERROR("Parsed JSON was not an object?"); 113246b30283SCarson Labrado return; 113346b30283SCarson Labrado } 113446b30283SCarson Labrado 113546b30283SCarson Labrado for (std::pair<const std::string, nlohmann::json>& prop : *object) 113646b30283SCarson Labrado { 113746b30283SCarson Labrado if (!prop.second.contains("@odata.id")) 113846b30283SCarson Labrado { 113946b30283SCarson Labrado continue; 114046b30283SCarson Labrado } 114146b30283SCarson Labrado 114246b30283SCarson Labrado std::string* strValue = 114346b30283SCarson Labrado prop.second["@odata.id"].get_ptr<std::string*>(); 114446b30283SCarson Labrado if (strValue == nullptr) 114546b30283SCarson Labrado { 114662598e31SEd Tanous BMCWEB_LOG_CRITICAL("Field wasn't a string????"); 114746b30283SCarson Labrado continue; 114846b30283SCarson Labrado } 114946b30283SCarson Labrado if (!searchCollectionsArray(*strValue, SearchType::CollOrCon)) 115046b30283SCarson Labrado { 115146b30283SCarson Labrado continue; 115246b30283SCarson Labrado } 115346b30283SCarson Labrado 115446b30283SCarson Labrado addedLinks = true; 115546b30283SCarson Labrado if (!asyncResp->res.jsonValue.contains(prop.first)) 115646b30283SCarson Labrado { 115746b30283SCarson Labrado // Only add the property if it did not already exist 115862598e31SEd Tanous BMCWEB_LOG_DEBUG("Adding link for {} from BMC {}", 115962598e31SEd Tanous *strValue, prefix); 116046b30283SCarson Labrado asyncResp->res.jsonValue[prop.first]["@odata.id"] = 116146b30283SCarson Labrado *strValue; 116246b30283SCarson Labrado continue; 116346b30283SCarson Labrado } 116446b30283SCarson Labrado } 116546b30283SCarson Labrado 116646b30283SCarson Labrado // If we added links to a previously unsuccessful (non-200) response 116746b30283SCarson Labrado // then we need to make sure the response contains the bare minimum 116846b30283SCarson Labrado // amount of additional information that we'd expect to have been 116946b30283SCarson Labrado // populated. 117046b30283SCarson Labrado if (addedLinks && (asyncResp->res.resultInt() != 200)) 117146b30283SCarson Labrado { 117246b30283SCarson Labrado // This resource didn't locally exist or an error 117346b30283SCarson Labrado // occurred while generating the response. Remove any 117446b30283SCarson Labrado // error messages and update the error code. 117546b30283SCarson Labrado asyncResp->res.jsonValue.erase( 117646b30283SCarson Labrado asyncResp->res.jsonValue.find("error")); 117746b30283SCarson Labrado asyncResp->res.result(resp.result()); 117846b30283SCarson Labrado 117946b30283SCarson Labrado const auto& it1 = object->find("@odata.id"); 118046b30283SCarson Labrado if (it1 != object->end()) 118146b30283SCarson Labrado { 118246b30283SCarson Labrado asyncResp->res.jsonValue["@odata.id"] = (it1->second); 118346b30283SCarson Labrado } 118446b30283SCarson Labrado const auto& it2 = object->find("@odata.type"); 118546b30283SCarson Labrado if (it2 != object->end()) 118646b30283SCarson Labrado { 118746b30283SCarson Labrado asyncResp->res.jsonValue["@odata.type"] = (it2->second); 118846b30283SCarson Labrado } 118946b30283SCarson Labrado const auto& it3 = object->find("Id"); 119046b30283SCarson Labrado if (it3 != object->end()) 119146b30283SCarson Labrado { 119246b30283SCarson Labrado asyncResp->res.jsonValue["Id"] = (it3->second); 119346b30283SCarson Labrado } 119446b30283SCarson Labrado const auto& it4 = object->find("Name"); 119546b30283SCarson Labrado if (it4 != object->end()) 119646b30283SCarson Labrado { 119746b30283SCarson Labrado asyncResp->res.jsonValue["Name"] = (it4->second); 119846b30283SCarson Labrado } 119946b30283SCarson Labrado } 120046b30283SCarson Labrado } 120146b30283SCarson Labrado else 120246b30283SCarson Labrado { 120362598e31SEd Tanous BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"", 120462598e31SEd Tanous prefix); 120546b30283SCarson Labrado // We received as response that was not a json 120646b30283SCarson Labrado // Notify the user only if we did not receive any valid responses, 120746b30283SCarson Labrado // and if the resource does not already exist on the aggregating BMC 120846b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 120946b30283SCarson Labrado { 121046b30283SCarson Labrado messages::operationFailed(asyncResp->res); 121146b30283SCarson Labrado } 121246b30283SCarson Labrado } 121346b30283SCarson Labrado } 121446b30283SCarson Labrado 121505916cefSCarson Labrado // Entry point to Redfish Aggregation 121605916cefSCarson Labrado // Returns Result stating whether or not we still need to locally handle the 121705916cefSCarson Labrado // request 121805916cefSCarson Labrado static Result 121905916cefSCarson Labrado beginAggregation(const crow::Request& thisReq, 122005916cefSCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 122105916cefSCarson Labrado { 122205916cefSCarson Labrado using crow::utility::OrMorePaths; 122305916cefSCarson Labrado using crow::utility::readUrlSegments; 12244a7fbefdSEd Tanous boost::urls::url_view url = thisReq.url(); 1225411e6a11SCarson Labrado 1226411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such 1227411e6a11SCarson Labrado // as version mismatches between aggregator and satellite BMCs. For 1228411e6a11SCarson Labrado // now assume that the aggregator has all the schemas and versions that 1229411e6a11SCarson Labrado // the aggregated server has. 1230411e6a11SCarson Labrado if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", 1231411e6a11SCarson Labrado crow::utility::OrMorePaths())) 1232411e6a11SCarson Labrado { 1233411e6a11SCarson Labrado return Result::LocalHandle; 1234411e6a11SCarson Labrado } 1235411e6a11SCarson Labrado 12367c4c52cbSCarson Labrado // The first two segments should be "/redfish/v1". We need to check 12377c4c52cbSCarson Labrado // that before we can search topCollections 12387c4c52cbSCarson Labrado if (!crow::utility::readUrlSegments(url, "redfish", "v1", 12397c4c52cbSCarson Labrado crow::utility::OrMorePaths())) 124046a81465SCarson Labrado { 124146a81465SCarson Labrado return Result::LocalHandle; 124246a81465SCarson Labrado } 124305916cefSCarson Labrado 12447c4c52cbSCarson Labrado // Parse the URI to see if it begins with a known top level collection 12457c4c52cbSCarson Labrado // such as: 12467c4c52cbSCarson Labrado // /redfish/v1/Chassis 12477c4c52cbSCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory 12487c4c52cbSCarson Labrado const boost::urls::segments_view urlSegments = url.segments(); 12497c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 12504a7fbefdSEd Tanous boost::urls::segments_view::const_iterator it = urlSegments.begin(); 12514a7fbefdSEd Tanous boost::urls::segments_view::const_iterator end = urlSegments.end(); 125205916cefSCarson Labrado 12537c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 12547c4c52cbSCarson Labrado it++; 12557c4c52cbSCarson Labrado it++; 12567c4c52cbSCarson Labrado for (; it != end; it++) 125705916cefSCarson Labrado { 1258d4413c5bSGeorge Liu const std::string& collectionItem = *it; 12597c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 12607c4c52cbSCarson Labrado currentUrl.buffer())) 12617c4c52cbSCarson Labrado { 12627c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 12637c4c52cbSCarson Labrado // might contain an aggregation prefix 12648b2521a5SCarson Labrado // TODO: This needs to be rethought when we can support multiple 12658b2521a5SCarson Labrado // satellites due to 12668b2521a5SCarson Labrado // /redfish/v1/AggregationService/AggregationSources/5B247A 12678b2521a5SCarson Labrado // being a local resource describing the satellite 12688b2521a5SCarson Labrado if (collectionItem.starts_with("5B247A_")) 126905916cefSCarson Labrado { 127062598e31SEd Tanous BMCWEB_LOG_DEBUG("Need to forward a request"); 127105916cefSCarson Labrado 127246a81465SCarson Labrado // Extract the prefix from the request's URI, retrieve the 12737c4c52cbSCarson Labrado // associated satellite config information, and then forward 12747c4c52cbSCarson Labrado // the request to that satellite. 12757c4c52cbSCarson Labrado startAggregation(AggregationType::Resource, thisReq, 12767c4c52cbSCarson Labrado asyncResp); 127705916cefSCarson Labrado return Result::NoLocalHandle; 127805916cefSCarson Labrado } 12797c4c52cbSCarson Labrado 12807c4c52cbSCarson Labrado // Handle collection URI with a trailing backslash 12817c4c52cbSCarson Labrado // e.g. /redfish/v1/Chassis/ 12827c4c52cbSCarson Labrado it++; 12837c4c52cbSCarson Labrado if ((it == end) && collectionItem.empty()) 12847c4c52cbSCarson Labrado { 12857c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, 12867c4c52cbSCarson Labrado asyncResp); 12877c4c52cbSCarson Labrado } 12887c4c52cbSCarson Labrado 12897c4c52cbSCarson Labrado // We didn't recognize the prefix or it's a collection with a 12907c4c52cbSCarson Labrado // trailing "/". In both cases we still want to locally handle 12917c4c52cbSCarson Labrado // the request 12927c4c52cbSCarson Labrado return Result::LocalHandle; 12937c4c52cbSCarson Labrado } 12947c4c52cbSCarson Labrado 12957c4c52cbSCarson Labrado currentUrl.segments().push_back(collectionItem); 12967c4c52cbSCarson Labrado } 12977c4c52cbSCarson Labrado 12987c4c52cbSCarson Labrado // If we made it here then currentUrl could contain a top level 12997c4c52cbSCarson Labrado // collection URI without a trailing "/", e.g. /redfish/v1/Chassis 13007c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 13017c4c52cbSCarson Labrado currentUrl.buffer())) 13027c4c52cbSCarson Labrado { 13037c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 130405916cefSCarson Labrado return Result::LocalHandle; 130505916cefSCarson Labrado } 130605916cefSCarson Labrado 1307e002dbc0SCarson Labrado // If nothing else then the request could be for a resource which has a 1308e002dbc0SCarson Labrado // top level collection as a subordinate 1309e002dbc0SCarson Labrado if (searchCollectionsArray(url.path(), SearchType::ContainsSubordinate)) 1310e002dbc0SCarson Labrado { 1311e002dbc0SCarson Labrado startAggregation(AggregationType::ContainsSubordinate, thisReq, 1312e002dbc0SCarson Labrado asyncResp); 1313e002dbc0SCarson Labrado return Result::LocalHandle; 1314e002dbc0SCarson Labrado } 1315e002dbc0SCarson Labrado 131662598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregation not required for {}", url.buffer()); 131705916cefSCarson Labrado return Result::LocalHandle; 131805916cefSCarson Labrado } 13197fb33566SCarson Labrado }; 13207fb33566SCarson Labrado 13217fb33566SCarson Labrado } // namespace redfish 1322