140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 37fb33566SCarson Labrado #pragma once 47fb33566SCarson Labrado 53ccb3adbSEd Tanous #include "aggregation_utils.hpp" 6d7857201SEd Tanous #include "async_resp.hpp" 73ccb3adbSEd Tanous #include "dbus_utility.hpp" 83ccb3adbSEd Tanous #include "error_messages.hpp" 93ccb3adbSEd Tanous #include "http_client.hpp" 10d7857201SEd Tanous #include "http_request.hpp" 11d7857201SEd Tanous #include "http_response.hpp" 129838eb20SEd Tanous #include "io_context_singleton.hpp" 13d7857201SEd Tanous #include "logging.hpp" 1418f8f608SEd Tanous #include "parsing.hpp" 15d7857201SEd Tanous #include "ssl_key_handler.hpp" 16d7857201SEd Tanous #include "utility.hpp" 177fb33566SCarson Labrado 18d7857201SEd Tanous #include <boost/beast/http/field.hpp> 19d7857201SEd Tanous #include <boost/beast/http/status.hpp> 20d7857201SEd Tanous #include <boost/beast/http/verb.hpp> 21d7857201SEd Tanous #include <boost/system/errc.hpp> 22d7857201SEd Tanous #include <boost/system/result.hpp> 23d7857201SEd Tanous #include <boost/url/param.hpp> 24d7857201SEd Tanous #include <boost/url/parse.hpp> 25d7857201SEd Tanous #include <boost/url/segments_ref.hpp> 26d7857201SEd Tanous #include <boost/url/segments_view.hpp> 27d7857201SEd Tanous #include <boost/url/url.hpp> 28d7857201SEd Tanous #include <boost/url/url_view.hpp> 29d7857201SEd Tanous #include <nlohmann/json.hpp> 30d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp> 31d7857201SEd Tanous 32d7857201SEd Tanous #include <algorithm> 337e8890c5SCarson Labrado #include <array> 34d7857201SEd Tanous #include <chrono> 35d7857201SEd Tanous #include <cstddef> 36d7857201SEd Tanous #include <cstdint> 37d7857201SEd Tanous #include <functional> 38d7857201SEd Tanous #include <limits> 39d7857201SEd Tanous #include <memory> 403544d2a7SEd Tanous #include <ranges> 41d7857201SEd Tanous #include <string> 423544d2a7SEd Tanous #include <string_view> 43d7857201SEd Tanous #include <system_error> 44d7857201SEd Tanous #include <unordered_map> 45d7857201SEd Tanous #include <utility> 46d7857201SEd Tanous #include <variant> 477e8890c5SCarson Labrado 487fb33566SCarson Labrado namespace redfish 497fb33566SCarson Labrado { 507fb33566SCarson Labrado 51d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB 52d14a48ffSCarson Labrado 5305916cefSCarson Labrado enum class Result 5405916cefSCarson Labrado { 5505916cefSCarson Labrado LocalHandle, 5605916cefSCarson Labrado NoLocalHandle 5705916cefSCarson Labrado }; 5805916cefSCarson Labrado 598fd333d6SCarson Labrado enum class SearchType 608fd333d6SCarson Labrado { 618fd333d6SCarson Labrado Collection, 628fd333d6SCarson Labrado CollOrCon, 638fd333d6SCarson Labrado ContainsSubordinate, 648fd333d6SCarson Labrado Resource 658fd333d6SCarson Labrado }; 668fd333d6SCarson Labrado 677e8890c5SCarson Labrado // clang-format off 687e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource 697e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a 707e8890c5SCarson Labrado // case-insensitive form of "uri". That version of the schema is associated 717e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification. Going forward, new URI 727e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as 737e8890c5SCarson Labrado // the spec is updated. NOTE: These have been pre-sorted in order to be 747e8890c5SCarson Labrado // compatible with binary search 757e8890c5SCarson Labrado constexpr std::array nonUriProperties{ 767e8890c5SCarson Labrado "@Redfish.ActionInfo", 777e8890c5SCarson Labrado // "@odata.context", // We can't fix /redfish/v1/$metadata URIs 787e8890c5SCarson Labrado "@odata.id", 797e8890c5SCarson Labrado // "Destination", // Only used by EventService and won't be a Redfish URI 807e8890c5SCarson Labrado // "HostName", // Isn't actually a Redfish URI 817e8890c5SCarson Labrado "Image", 827e8890c5SCarson Labrado "MetricProperty", 8332d7d8ebSCarson Labrado // "OriginOfCondition", // Is URI when in request, but is object in response 847e8890c5SCarson Labrado "TaskMonitor", 857e8890c5SCarson Labrado "target", // normal string, but target URI for POST to invoke an action 867e8890c5SCarson Labrado }; 877e8890c5SCarson Labrado // clang-format on 887e8890c5SCarson Labrado 898fd333d6SCarson Labrado // Search the top collection array to determine if the passed URI is of a 908fd333d6SCarson Labrado // desired type 918fd333d6SCarson Labrado inline bool searchCollectionsArray(std::string_view uri, 928fd333d6SCarson Labrado const SearchType searchType) 938fd333d6SCarson Labrado { 941a095906SJoonwon Kang boost::system::result<boost::urls::url> parsedUrl = 951a095906SJoonwon Kang boost::urls::parse_relative_ref(uri); 968fd333d6SCarson Labrado if (!parsedUrl) 978fd333d6SCarson Labrado { 981a095906SJoonwon Kang BMCWEB_LOG_ERROR("Failed to get target URI from {}", uri); 998fd333d6SCarson Labrado return false; 1008fd333d6SCarson Labrado } 1018fd333d6SCarson Labrado 1021a095906SJoonwon Kang parsedUrl->normalize(); 1031a095906SJoonwon Kang boost::urls::segments_ref segments = parsedUrl->segments(); 1041a095906SJoonwon Kang if (!segments.is_absolute()) 1058fd333d6SCarson Labrado { 1068fd333d6SCarson Labrado return false; 1078fd333d6SCarson Labrado } 1088fd333d6SCarson Labrado 1091a095906SJoonwon Kang // The passed URI must begin with "/redfish/v1", but we have to strip it 1101a095906SJoonwon Kang // from the URI since topCollections does not include it in its URIs. 1111a095906SJoonwon Kang if (segments.size() < 2) 1121a095906SJoonwon Kang { 1131a095906SJoonwon Kang return false; 1141a095906SJoonwon Kang } 1151a095906SJoonwon Kang if (segments.front() != "redfish") 1161a095906SJoonwon Kang { 1171a095906SJoonwon Kang return false; 1181a095906SJoonwon Kang } 1191a095906SJoonwon Kang segments.erase(segments.begin()); 1201a095906SJoonwon Kang if (segments.front() != "v1") 1211a095906SJoonwon Kang { 1221a095906SJoonwon Kang return false; 1231a095906SJoonwon Kang } 1241a095906SJoonwon Kang segments.erase(segments.begin()); 1251a095906SJoonwon Kang 1261a095906SJoonwon Kang // Exclude the trailing "/" if it exists such as in "/redfish/v1/". 1271a095906SJoonwon Kang if (!segments.empty() && segments.back().empty()) 1281a095906SJoonwon Kang { 1291a095906SJoonwon Kang segments.pop_back(); 1301a095906SJoonwon Kang } 1311a095906SJoonwon Kang 1321a095906SJoonwon Kang // If no segments then the passed URI was either "/redfish/v1" or 1338fd333d6SCarson Labrado // "/redfish/v1/". 1341a095906SJoonwon Kang if (segments.empty()) 1358fd333d6SCarson Labrado { 1368fd333d6SCarson Labrado return (searchType == SearchType::ContainsSubordinate) || 1378fd333d6SCarson Labrado (searchType == SearchType::CollOrCon); 1388fd333d6SCarson Labrado } 1391a095906SJoonwon Kang std::string_view url = segments.buffer(); 1403544d2a7SEd Tanous const auto* it = std::ranges::lower_bound(topCollections, url); 1418fd333d6SCarson Labrado if (it == topCollections.end()) 1428fd333d6SCarson Labrado { 1438fd333d6SCarson Labrado // parsedUrl is alphabetically after the last entry in the array so it 1448fd333d6SCarson Labrado // can't be a top collection or up tree from a top collection 1458fd333d6SCarson Labrado return false; 1468fd333d6SCarson Labrado } 1478fd333d6SCarson Labrado 1488fd333d6SCarson Labrado boost::urls::url collectionUrl(*it); 1498fd333d6SCarson Labrado boost::urls::segments_view collectionSegments = collectionUrl.segments(); 1508fd333d6SCarson Labrado boost::urls::segments_view::iterator itCollection = 1518fd333d6SCarson Labrado collectionSegments.begin(); 1528fd333d6SCarson Labrado const boost::urls::segments_view::const_iterator endCollection = 1538fd333d6SCarson Labrado collectionSegments.end(); 1548fd333d6SCarson Labrado 1558fd333d6SCarson Labrado // Each segment in the passed URI should match the found collection 1561a095906SJoonwon Kang for (const auto& segment : segments) 1578fd333d6SCarson Labrado { 1588fd333d6SCarson Labrado if (itCollection == endCollection) 1598fd333d6SCarson Labrado { 1608fd333d6SCarson Labrado // Leftover segments means the target is for an aggregation 1618fd333d6SCarson Labrado // supported resource 1628fd333d6SCarson Labrado return searchType == SearchType::Resource; 1638fd333d6SCarson Labrado } 1648fd333d6SCarson Labrado 1658fd333d6SCarson Labrado if (segment != (*itCollection)) 1668fd333d6SCarson Labrado { 1678fd333d6SCarson Labrado return false; 1688fd333d6SCarson Labrado } 1698fd333d6SCarson Labrado itCollection++; 1708fd333d6SCarson Labrado } 1718fd333d6SCarson Labrado 1728fd333d6SCarson Labrado // No remaining segments means the passed URI was a top level collection 1738fd333d6SCarson Labrado if (searchType == SearchType::Collection) 1748fd333d6SCarson Labrado { 1758fd333d6SCarson Labrado return itCollection == endCollection; 1768fd333d6SCarson Labrado } 1778fd333d6SCarson Labrado if (searchType == SearchType::ContainsSubordinate) 1788fd333d6SCarson Labrado { 1798fd333d6SCarson Labrado return itCollection != endCollection; 1808fd333d6SCarson Labrado } 1818fd333d6SCarson Labrado 1828fd333d6SCarson Labrado // Return this check instead of "true" in case other SearchTypes get added 1838fd333d6SCarson Labrado return searchType == SearchType::CollOrCon; 1848fd333d6SCarson Labrado } 1858fd333d6SCarson Labrado 1867e8890c5SCarson Labrado // Determines if the passed property contains a URI. Those property names 1877e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically 1887e8890c5SCarson Labrado // defined in the above array. 18926ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName) 1907e8890c5SCarson Labrado { 19118f8f608SEd Tanous if (propertyName.ends_with("uri") || propertyName.ends_with("Uri") || 19218f8f608SEd Tanous propertyName.ends_with("URI")) 19318f8f608SEd Tanous { 19418f8f608SEd Tanous return true; 19518f8f608SEd Tanous } 19618f8f608SEd Tanous return std::binary_search(nonUriProperties.begin(), nonUriProperties.end(), 1977e8890c5SCarson Labrado propertyName); 1987e8890c5SCarson Labrado } 1997e8890c5SCarson Labrado 2004ff0f1f4SEd Tanous inline void addPrefixToStringItem(std::string& strValue, 2010af78d5aSKhang Kieu std::string_view prefix) 2021c0bb5c6SCarson Labrado { 2031c0bb5c6SCarson Labrado // Make sure the value is a properly formatted URI 2040af78d5aSKhang Kieu auto parsed = boost::urls::parse_relative_ref(strValue); 2051c0bb5c6SCarson Labrado if (!parsed) 2061c0bb5c6SCarson Labrado { 207bf2ddedeSCarson Labrado // Note that DMTF URIs such as 208bf2ddedeSCarson Labrado // https://redfish.dmtf.org/registries/Base.1.15.0.json will fail this 209bf2ddedeSCarson Labrado // check and that's okay 210bf2ddedeSCarson Labrado BMCWEB_LOG_DEBUG("Couldn't parse URI from resource {}", strValue); 2111c0bb5c6SCarson Labrado return; 2121c0bb5c6SCarson Labrado } 2131c0bb5c6SCarson Labrado 214daadfb2eSEd Tanous const boost::urls::url_view& thisUrl = *parsed; 2151c0bb5c6SCarson Labrado 216411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such as 217411e6a11SCarson Labrado // version mismatches between aggregator and satellite BMCs. For now 218411e6a11SCarson Labrado // assume that the aggregator has all the schemas and versions that the 219411e6a11SCarson Labrado // aggregated server has. 220411e6a11SCarson Labrado if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas", 221411e6a11SCarson Labrado crow::utility::OrMorePaths())) 222411e6a11SCarson Labrado { 22362598e31SEd Tanous BMCWEB_LOG_DEBUG("Skipping JsonSchemas URI prefix fixing"); 224411e6a11SCarson Labrado return; 225411e6a11SCarson Labrado } 226411e6a11SCarson Labrado 22711987af6SCarson Labrado // The first two segments should be "/redfish/v1". We need to check that 22811987af6SCarson Labrado // before we can search topCollections 22911987af6SCarson Labrado if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 23011987af6SCarson Labrado crow::utility::OrMorePaths())) 2311c0bb5c6SCarson Labrado { 2321c0bb5c6SCarson Labrado return; 2331c0bb5c6SCarson Labrado } 2341c0bb5c6SCarson Labrado 23511987af6SCarson Labrado // Check array adding a segment each time until collection is identified 23611987af6SCarson Labrado // Add prefix to segment after the collection 23711987af6SCarson Labrado const boost::urls::segments_view urlSegments = thisUrl.segments(); 23811987af6SCarson Labrado bool addedPrefix = false; 23911987af6SCarson Labrado boost::urls::url url("/"); 2404a7fbefdSEd Tanous boost::urls::segments_view::const_iterator it = urlSegments.begin(); 24111987af6SCarson Labrado const boost::urls::segments_view::const_iterator end = urlSegments.end(); 24211987af6SCarson Labrado 24311987af6SCarson Labrado // Skip past the leading "/redfish/v1" 24411987af6SCarson Labrado it++; 24511987af6SCarson Labrado it++; 24611987af6SCarson Labrado for (; it != end; it++) 2471c0bb5c6SCarson Labrado { 24811987af6SCarson Labrado // Trailing "/" will result in an empty segment. In that case we need 24911987af6SCarson Labrado // to return so we don't apply a prefix to top level collections such 25011987af6SCarson Labrado // as "/redfish/v1/Chassis/" 25111987af6SCarson Labrado if ((*it).empty()) 25211987af6SCarson Labrado { 253411e6a11SCarson Labrado return; 2541c0bb5c6SCarson Labrado } 2551c0bb5c6SCarson Labrado 25611987af6SCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 25711987af6SCarson Labrado url.buffer())) 2581c0bb5c6SCarson Labrado { 25911987af6SCarson Labrado std::string collectionItem(prefix); 26011987af6SCarson Labrado collectionItem += "_" + (*it); 26111987af6SCarson Labrado url.segments().push_back(collectionItem); 26211987af6SCarson Labrado it++; 26311987af6SCarson Labrado addedPrefix = true; 26411987af6SCarson Labrado break; 26511987af6SCarson Labrado } 26611987af6SCarson Labrado 26711987af6SCarson Labrado url.segments().push_back(*it); 26811987af6SCarson Labrado } 26911987af6SCarson Labrado 27011987af6SCarson Labrado // Finish constructing the URL here (if needed) to avoid additional checks 27111987af6SCarson Labrado for (; it != end; it++) 27211987af6SCarson Labrado { 27311987af6SCarson Labrado url.segments().push_back(*it); 27411987af6SCarson Labrado } 27511987af6SCarson Labrado 27611987af6SCarson Labrado if (addedPrefix) 27711987af6SCarson Labrado { 27811987af6SCarson Labrado url.segments().insert(url.segments().begin(), {"redfish", "v1"}); 2790af78d5aSKhang Kieu strValue = url.buffer(); 2801c0bb5c6SCarson Labrado } 2811c0bb5c6SCarson Labrado } 2821c0bb5c6SCarson Labrado 2834ff0f1f4SEd Tanous inline void addPrefixToItem(nlohmann::json& item, std::string_view prefix) 2840af78d5aSKhang Kieu { 2850af78d5aSKhang Kieu std::string* strValue = item.get_ptr<std::string*>(); 2860af78d5aSKhang Kieu if (strValue == nullptr) 2870af78d5aSKhang Kieu { 288bf2ddedeSCarson Labrado // Values for properties like "InvalidURI" and "ResourceMissingAtURI" 289bf2ddedeSCarson Labrado // from within the Base Registry are objects instead of strings and will 290bf2ddedeSCarson Labrado // fall into this check 291bf2ddedeSCarson Labrado BMCWEB_LOG_DEBUG("Field was not a string"); 2920af78d5aSKhang Kieu return; 2930af78d5aSKhang Kieu } 2940af78d5aSKhang Kieu addPrefixToStringItem(*strValue, prefix); 2950af78d5aSKhang Kieu item = *strValue; 2960af78d5aSKhang Kieu } 2970af78d5aSKhang Kieu 2984ff0f1f4SEd Tanous inline void addAggregatedHeaders(crow::Response& asyncResp, 29924dadc88SCarson Labrado const crow::Response& resp, 3000af78d5aSKhang Kieu std::string_view prefix) 3010af78d5aSKhang Kieu { 3020af78d5aSKhang Kieu if (!resp.getHeaderValue("Content-Type").empty()) 3030af78d5aSKhang Kieu { 3040af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::content_type, 3050af78d5aSKhang Kieu resp.getHeaderValue("Content-Type")); 3060af78d5aSKhang Kieu } 3070af78d5aSKhang Kieu if (!resp.getHeaderValue("Allow").empty()) 3080af78d5aSKhang Kieu { 3090af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::allow, 3100af78d5aSKhang Kieu resp.getHeaderValue("Allow")); 3110af78d5aSKhang Kieu } 3120af78d5aSKhang Kieu std::string_view header = resp.getHeaderValue("Location"); 3130af78d5aSKhang Kieu if (!header.empty()) 3140af78d5aSKhang Kieu { 3150af78d5aSKhang Kieu std::string location(header); 3160af78d5aSKhang Kieu addPrefixToStringItem(location, prefix); 3170af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::location, location); 3180af78d5aSKhang Kieu } 3190af78d5aSKhang Kieu if (!resp.getHeaderValue("Retry-After").empty()) 3200af78d5aSKhang Kieu { 3210af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::retry_after, 3220af78d5aSKhang Kieu resp.getHeaderValue("Retry-After")); 3230af78d5aSKhang Kieu } 3240af78d5aSKhang Kieu // TODO: we need special handling for Link Header Value 3250af78d5aSKhang Kieu } 3260af78d5aSKhang Kieu 327b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others 3284ff0f1f4SEd Tanous inline void addPrefixToHeadersInResp(nlohmann::json& json, 3294ff0f1f4SEd Tanous std::string_view prefix) 330b27e1cbeSCarson Labrado { 331b27e1cbeSCarson Labrado // The passed in "HttpHeaders" should be an array of headers 332b27e1cbeSCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 333b27e1cbeSCarson Labrado if (array == nullptr) 334b27e1cbeSCarson Labrado { 33562598e31SEd Tanous BMCWEB_LOG_ERROR("Field wasn't an array_t????"); 336b27e1cbeSCarson Labrado return; 337b27e1cbeSCarson Labrado } 338b27e1cbeSCarson Labrado 339b27e1cbeSCarson Labrado for (nlohmann::json& item : *array) 340b27e1cbeSCarson Labrado { 341b27e1cbeSCarson Labrado // Each header is a single string with the form "<Field>: <Value>" 342b27e1cbeSCarson Labrado std::string* strHeader = item.get_ptr<std::string*>(); 343b27e1cbeSCarson Labrado if (strHeader == nullptr) 344b27e1cbeSCarson Labrado { 34562598e31SEd Tanous BMCWEB_LOG_CRITICAL("Field wasn't a string????"); 346b27e1cbeSCarson Labrado continue; 347b27e1cbeSCarson Labrado } 348b27e1cbeSCarson Labrado 349b27e1cbeSCarson Labrado constexpr std::string_view location = "Location: "; 350b27e1cbeSCarson Labrado if (strHeader->starts_with(location)) 351b27e1cbeSCarson Labrado { 352b27e1cbeSCarson Labrado std::string header = strHeader->substr(location.size()); 353b27e1cbeSCarson Labrado addPrefixToStringItem(header, prefix); 354b27e1cbeSCarson Labrado *strHeader = std::string(location) + header; 355b27e1cbeSCarson Labrado } 356b27e1cbeSCarson Labrado } 357b27e1cbeSCarson Labrado } 358b27e1cbeSCarson Labrado 3591c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for 3607e8890c5SCarson Labrado // an aggregated resource. 3614ff0f1f4SEd Tanous inline void addPrefixes(nlohmann::json& json, std::string_view prefix) 3621c0bb5c6SCarson Labrado { 3631c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 3641c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 3651c0bb5c6SCarson Labrado if (object != nullptr) 3661c0bb5c6SCarson Labrado { 3671c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 3681c0bb5c6SCarson Labrado { 3697e8890c5SCarson Labrado if (isPropertyUri(item.first)) 3701c0bb5c6SCarson Labrado { 3717e8890c5SCarson Labrado addPrefixToItem(item.second, prefix); 3721c0bb5c6SCarson Labrado continue; 3731c0bb5c6SCarson Labrado } 3741c0bb5c6SCarson Labrado 375b27e1cbeSCarson Labrado // "HttpHeaders" contains HTTP headers. Among those we need to 376b27e1cbeSCarson Labrado // attempt to fix the "Location" header 377b27e1cbeSCarson Labrado if (item.first == "HttpHeaders") 378b27e1cbeSCarson Labrado { 379b27e1cbeSCarson Labrado addPrefixToHeadersInResp(item.second, prefix); 380b27e1cbeSCarson Labrado continue; 381b27e1cbeSCarson Labrado } 382b27e1cbeSCarson Labrado 3838ece0e45SEd Tanous // Recursively parse the rest of the json 3841c0bb5c6SCarson Labrado addPrefixes(item.second, prefix); 3851c0bb5c6SCarson Labrado } 3861c0bb5c6SCarson Labrado return; 3871c0bb5c6SCarson Labrado } 3881c0bb5c6SCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 3891c0bb5c6SCarson Labrado if (array != nullptr) 3901c0bb5c6SCarson Labrado { 3911c0bb5c6SCarson Labrado for (nlohmann::json& item : *array) 3921c0bb5c6SCarson Labrado { 3931c0bb5c6SCarson Labrado addPrefixes(item, prefix); 3941c0bb5c6SCarson Labrado } 3951c0bb5c6SCarson Labrado } 3961c0bb5c6SCarson Labrado } 3971c0bb5c6SCarson Labrado 398d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode) 399a7a80296SCarson Labrado { 40032d7d8ebSCarson Labrado // Allow all response codes because we want to surface any satellite 40132d7d8ebSCarson Labrado // issue to the client 40262598e31SEd Tanous BMCWEB_LOG_DEBUG("Received {} response from satellite", respCode); 403d14a48ffSCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 404d14a48ffSCarson Labrado } 405d14a48ffSCarson Labrado 406d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy() 407d14a48ffSCarson Labrado { 4086bd30813SEd Tanous return {.maxRetryAttempts = 0, 409d14a48ffSCarson Labrado .requestByteLimit = aggregatorReadBodyLimit, 410d14a48ffSCarson Labrado .maxConnections = 20, 411d14a48ffSCarson Labrado .retryPolicyAction = "TerminateAfterRetries", 412d14a48ffSCarson Labrado .retryIntervalSecs = std::chrono::seconds(0), 413d14a48ffSCarson Labrado .invalidResp = aggregationRetryHandler}; 414d14a48ffSCarson Labrado } 415d14a48ffSCarson Labrado 416d14a48ffSCarson Labrado class RedfishAggregator 417d14a48ffSCarson Labrado { 418d14a48ffSCarson Labrado private: 419d14a48ffSCarson Labrado crow::HttpClient client; 420d14a48ffSCarson Labrado 4217fb33566SCarson Labrado // Dummy callback used by the Constructor so that it can report the number 4227fb33566SCarson Labrado // of satellite configs when the class is first created 4237fb33566SCarson Labrado static void constructorCallback( 4248b2521a5SCarson Labrado const boost::system::error_code& ec, 4257fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 4267fb33566SCarson Labrado { 4277fb33566SCarson Labrado if (ec) 4287fb33566SCarson Labrado { 42962598e31SEd Tanous BMCWEB_LOG_ERROR("Something went wrong while querying dbus!"); 4307fb33566SCarson Labrado return; 4317fb33566SCarson Labrado } 4327fb33566SCarson Labrado 43362598e31SEd Tanous BMCWEB_LOG_DEBUG("There were {} satellite configs found at startup", 43462598e31SEd Tanous std::to_string(satelliteInfo.size())); 4357fb33566SCarson Labrado } 4367fb33566SCarson Labrado 4377fb33566SCarson Labrado // Search D-Bus objects for satellite config objects and add their 4387fb33566SCarson Labrado // information if valid 4397fb33566SCarson Labrado static void findSatelliteConfigs( 4407fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects, 4417fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 4427fb33566SCarson Labrado { 4437fb33566SCarson Labrado for (const auto& objectPath : objects) 4447fb33566SCarson Labrado { 4457fb33566SCarson Labrado for (const auto& interface : objectPath.second) 4467fb33566SCarson Labrado { 4477fb33566SCarson Labrado if (interface.first == 4487fb33566SCarson Labrado "xyz.openbmc_project.Configuration.SatelliteController") 4497fb33566SCarson Labrado { 45062598e31SEd Tanous BMCWEB_LOG_DEBUG("Found Satellite Controller at {}", 45162598e31SEd Tanous objectPath.first.str); 4527fb33566SCarson Labrado 45305916cefSCarson Labrado if (!satelliteInfo.empty()) 45405916cefSCarson Labrado { 45562598e31SEd Tanous BMCWEB_LOG_ERROR( 45662598e31SEd Tanous "Redfish Aggregation only supports one satellite!"); 45762598e31SEd Tanous BMCWEB_LOG_DEBUG("Clearing all satellite data"); 45805916cefSCarson Labrado satelliteInfo.clear(); 45905916cefSCarson Labrado return; 46005916cefSCarson Labrado } 46105916cefSCarson Labrado 4625a3d934aSRohit PAI addSatelliteConfig(interface.second, satelliteInfo); 4637fb33566SCarson Labrado } 4647fb33566SCarson Labrado } 4657fb33566SCarson Labrado } 4667fb33566SCarson Labrado } 4677fb33566SCarson Labrado 4687fb33566SCarson Labrado // Parse the properties of a satellite config object and add the 4697fb33566SCarson Labrado // configuration if the properties are valid 4707fb33566SCarson Labrado static void addSatelliteConfig( 4717fb33566SCarson Labrado const dbus::utility::DBusPropertiesMap& properties, 4727fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 4737fb33566SCarson Labrado { 4747fb33566SCarson Labrado boost::urls::url url; 4755a3d934aSRohit PAI std::string prefix; 4767fb33566SCarson Labrado 4777fb33566SCarson Labrado for (const auto& prop : properties) 4787fb33566SCarson Labrado { 47905916cefSCarson Labrado if (prop.first == "Hostname") 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 Hostname value"); 4867fb33566SCarson Labrado return; 4877fb33566SCarson Labrado } 4887fb33566SCarson Labrado url.set_host(*propVal); 4897fb33566SCarson Labrado } 4907fb33566SCarson Labrado 4917fb33566SCarson Labrado else if (prop.first == "Port") 4927fb33566SCarson Labrado { 4937fb33566SCarson Labrado const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 4947fb33566SCarson Labrado if (propVal == nullptr) 4957fb33566SCarson Labrado { 49662598e31SEd Tanous BMCWEB_LOG_ERROR("Invalid Port value"); 4977fb33566SCarson Labrado return; 4987fb33566SCarson Labrado } 4997fb33566SCarson Labrado 5007fb33566SCarson Labrado if (*propVal > std::numeric_limits<uint16_t>::max()) 5017fb33566SCarson Labrado { 50262598e31SEd Tanous BMCWEB_LOG_ERROR("Port value out of range"); 5037fb33566SCarson Labrado return; 5047fb33566SCarson Labrado } 505079360aeSEd Tanous url.set_port(std::to_string(static_cast<uint16_t>(*propVal))); 5067fb33566SCarson Labrado } 5077fb33566SCarson Labrado 5087fb33566SCarson Labrado else if (prop.first == "AuthType") 5097fb33566SCarson Labrado { 5107fb33566SCarson Labrado const std::string* propVal = 5117fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 5127fb33566SCarson Labrado if (propVal == nullptr) 5137fb33566SCarson Labrado { 51462598e31SEd Tanous BMCWEB_LOG_ERROR("Invalid AuthType value"); 5157fb33566SCarson Labrado return; 5167fb33566SCarson Labrado } 5177fb33566SCarson Labrado 5187fb33566SCarson Labrado // For now assume authentication not required to communicate 5197fb33566SCarson Labrado // with the satellite BMC 5207fb33566SCarson Labrado if (*propVal != "None") 5217fb33566SCarson Labrado { 52262598e31SEd Tanous BMCWEB_LOG_ERROR( 52362598e31SEd Tanous "Unsupported AuthType value: {}, only \"none\" is supported", 52462598e31SEd Tanous *propVal); 5257fb33566SCarson Labrado return; 5267fb33566SCarson Labrado } 5277fb33566SCarson Labrado url.set_scheme("http"); 5287fb33566SCarson Labrado } 5295a3d934aSRohit PAI else if (prop.first == "Name") 5305a3d934aSRohit PAI { 5315a3d934aSRohit PAI const std::string* propVal = 5325a3d934aSRohit PAI std::get_if<std::string>(&prop.second); 5335a3d934aSRohit PAI if (propVal != nullptr && !propVal->empty()) 5345a3d934aSRohit PAI { 5355a3d934aSRohit PAI prefix = *propVal; 5365a3d934aSRohit PAI BMCWEB_LOG_DEBUG("Using Name property {} as prefix", 5375a3d934aSRohit PAI prefix); 5385a3d934aSRohit PAI } 5395a3d934aSRohit PAI else 5405a3d934aSRohit PAI { 5415a3d934aSRohit PAI BMCWEB_LOG_DEBUG( 5425a3d934aSRohit PAI "Invalid or empty Name property, invalid satellite config"); 5435a3d934aSRohit PAI return; 5445a3d934aSRohit PAI } 5455a3d934aSRohit PAI } 5467fb33566SCarson Labrado } // Finished reading properties 5477fb33566SCarson Labrado 5487fb33566SCarson Labrado // Make sure all required config information was made available 5497fb33566SCarson Labrado if (url.host().empty()) 5507fb33566SCarson Labrado { 5515a3d934aSRohit PAI BMCWEB_LOG_ERROR("Satellite config {} missing Host", prefix); 5527fb33566SCarson Labrado return; 5537fb33566SCarson Labrado } 5547fb33566SCarson Labrado 5557fb33566SCarson Labrado if (!url.has_port()) 5567fb33566SCarson Labrado { 5575a3d934aSRohit PAI BMCWEB_LOG_ERROR("Satellite config {} missing Port", prefix); 5587fb33566SCarson Labrado return; 5597fb33566SCarson Labrado } 5607fb33566SCarson Labrado 5617fb33566SCarson Labrado if (!url.has_scheme()) 5627fb33566SCarson Labrado { 5635a3d934aSRohit PAI BMCWEB_LOG_ERROR("Satellite config {} missing AuthType", prefix); 5647fb33566SCarson Labrado return; 5657fb33566SCarson Labrado } 5667fb33566SCarson Labrado 5677fb33566SCarson Labrado std::string resultString; 5685a3d934aSRohit PAI auto result = satelliteInfo.insert_or_assign(prefix, std::move(url)); 5697fb33566SCarson Labrado if (result.second) 5707fb33566SCarson Labrado { 5717fb33566SCarson Labrado resultString = "Added new satellite config "; 5727fb33566SCarson Labrado } 5737fb33566SCarson Labrado else 5747fb33566SCarson Labrado { 5757fb33566SCarson Labrado resultString = "Updated existing satellite config "; 5767fb33566SCarson Labrado } 5777fb33566SCarson Labrado 5785a3d934aSRohit PAI BMCWEB_LOG_DEBUG("{}{} at {}://{}", resultString, prefix, 57962598e31SEd Tanous result.first->second.scheme(), 58062598e31SEd Tanous result.first->second.encoded_host_and_port()); 5817fb33566SCarson Labrado } 5827fb33566SCarson Labrado 58346a81465SCarson Labrado enum AggregationType 58446a81465SCarson Labrado { 58546a81465SCarson Labrado Collection, 586e002dbc0SCarson Labrado ContainsSubordinate, 58746a81465SCarson Labrado Resource, 58846a81465SCarson Labrado }; 58946a81465SCarson Labrado 59066620686SEd Tanous void startAggregation( 591504af5a0SPatrick Williams AggregationType aggType, const crow::Request& thisReq, 59266620686SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) const 59346a81465SCarson Labrado { 594e002dbc0SCarson Labrado if (thisReq.method() != boost::beast::http::verb::get) 595e002dbc0SCarson Labrado { 596e002dbc0SCarson Labrado if (aggType == AggregationType::Collection) 597db18fc98SCarson Labrado { 59862598e31SEd Tanous BMCWEB_LOG_DEBUG( 59962598e31SEd Tanous "Only aggregate GET requests to top level collections"); 600db18fc98SCarson Labrado return; 601db18fc98SCarson Labrado } 602db18fc98SCarson Labrado 603e002dbc0SCarson Labrado if (aggType == AggregationType::ContainsSubordinate) 604e002dbc0SCarson Labrado { 60562598e31SEd Tanous BMCWEB_LOG_DEBUG( 60662598e31SEd Tanous "Only aggregate GET requests when uptree of a top level collection"); 607e002dbc0SCarson Labrado return; 608e002dbc0SCarson Labrado } 609e002dbc0SCarson Labrado } 610e002dbc0SCarson Labrado 61146a81465SCarson Labrado // Create a copy of thisReq so we we can still locally process the req 61246a81465SCarson Labrado std::error_code ec; 613761cdfa5SEd Tanous auto localReq = std::make_shared<crow::Request>(thisReq.copy()); 61446a81465SCarson Labrado if (ec) 61546a81465SCarson Labrado { 61662598e31SEd Tanous BMCWEB_LOG_ERROR("Failed to create copy of request"); 617e002dbc0SCarson Labrado if (aggType == AggregationType::Resource) 61846a81465SCarson Labrado { 61946a81465SCarson Labrado messages::internalError(asyncResp->res); 62046a81465SCarson Labrado } 62146a81465SCarson Labrado return; 62246a81465SCarson Labrado } 62346a81465SCarson Labrado 6246282bc71SEd Tanous if (aggType == AggregationType::Collection) 6256282bc71SEd Tanous { 6266282bc71SEd Tanous boost::urls::url& urlNew = localReq->url(); 6276282bc71SEd Tanous auto paramsIt = urlNew.params().begin(); 6286282bc71SEd Tanous while (paramsIt != urlNew.params().end()) 6296282bc71SEd Tanous { 6306282bc71SEd Tanous const boost::urls::param& param = *paramsIt; 6316282bc71SEd Tanous // only and $skip, params can't be passed to satellite 6326282bc71SEd Tanous // as applying these filters twice results in different results. 6336282bc71SEd Tanous // Removing them will cause them to only be processed in the 6346282bc71SEd Tanous // aggregator. Note, this still doesn't work for collections 6356282bc71SEd Tanous // that might return less than the complete collection by 6366282bc71SEd Tanous // default, but hopefully those are rare/nonexistent in top 6376282bc71SEd Tanous // collections. bmcweb doesn't implement any of these. 6386282bc71SEd Tanous if (param.key == "only" || param.key == "$skip") 6396282bc71SEd Tanous { 6406282bc71SEd Tanous BMCWEB_LOG_DEBUG( 6416282bc71SEd Tanous "Erasing \"{}\" param from request to top level collection", 6426282bc71SEd Tanous param.key); 6436282bc71SEd Tanous 6446282bc71SEd Tanous paramsIt = urlNew.params().erase(paramsIt); 6456282bc71SEd Tanous continue; 6466282bc71SEd Tanous } 6476282bc71SEd Tanous // Pass all other parameters 6486282bc71SEd Tanous paramsIt++; 6496282bc71SEd Tanous } 6506282bc71SEd Tanous localReq->target(urlNew.buffer()); 6516282bc71SEd Tanous } 6526282bc71SEd Tanous 653e002dbc0SCarson Labrado getSatelliteConfigs( 654e002dbc0SCarson Labrado std::bind_front(aggregateAndHandle, aggType, localReq, asyncResp)); 65546a81465SCarson Labrado } 65646a81465SCarson Labrado 657db18fc98SCarson Labrado static void findSatellite( 65846a81465SCarson Labrado const crow::Request& req, 65946a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 66046a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 66146a81465SCarson Labrado std::string_view memberName) 66246a81465SCarson Labrado { 66346a81465SCarson Labrado // Determine if the resource ID begins with a known prefix 66446a81465SCarson Labrado for (const auto& satellite : satelliteInfo) 66546a81465SCarson Labrado { 66646a81465SCarson Labrado std::string targetPrefix = satellite.first; 66746a81465SCarson Labrado targetPrefix += "_"; 66846a81465SCarson Labrado if (memberName.starts_with(targetPrefix)) 66946a81465SCarson Labrado { 67062598e31SEd Tanous BMCWEB_LOG_DEBUG("\"{}\" is a known prefix", satellite.first); 67146a81465SCarson Labrado 67246a81465SCarson Labrado // Remove the known prefix from the request's URI and 67346a81465SCarson Labrado // then forward to the associated satellite BMC 67446a81465SCarson Labrado getInstance().forwardRequest(req, asyncResp, satellite.first, 67546a81465SCarson Labrado satelliteInfo); 67646a81465SCarson Labrado return; 67746a81465SCarson Labrado } 67846a81465SCarson Labrado } 679db18fc98SCarson Labrado 680db18fc98SCarson Labrado // We didn't recognize the prefix and need to return a 404 68139662a3bSEd Tanous std::string nameStr = req.url().segments().back(); 682db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 68346a81465SCarson Labrado } 68446a81465SCarson Labrado 68546a81465SCarson Labrado // Intended to handle an incoming request based on if Redfish Aggregation 68646a81465SCarson Labrado // is enabled. Forwards request to satellite BMC if it exists. 68746a81465SCarson Labrado static void aggregateAndHandle( 688e002dbc0SCarson Labrado AggregationType aggType, 68946a81465SCarson Labrado const std::shared_ptr<crow::Request>& sharedReq, 69046a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 6918b2521a5SCarson Labrado const boost::system::error_code& ec, 69246a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 69346a81465SCarson Labrado { 69446a81465SCarson Labrado if (sharedReq == nullptr) 69546a81465SCarson Labrado { 69646a81465SCarson Labrado return; 69746a81465SCarson Labrado } 6988b2521a5SCarson Labrado // Something went wrong while querying dbus 6998b2521a5SCarson Labrado if (ec) 7008b2521a5SCarson Labrado { 7018b2521a5SCarson Labrado messages::internalError(asyncResp->res); 7028b2521a5SCarson Labrado return; 7038b2521a5SCarson Labrado } 704db18fc98SCarson Labrado 705db18fc98SCarson Labrado // No satellite configs means we don't need to keep attempting to 706db18fc98SCarson Labrado // aggregate 707db18fc98SCarson Labrado if (satelliteInfo.empty()) 708db18fc98SCarson Labrado { 709e002dbc0SCarson Labrado // For collections or resources that can contain a subordinate 710e002dbc0SCarson Labrado // top level collection we'll also handle the request locally so we 711db18fc98SCarson Labrado // don't need to write an error code 712e002dbc0SCarson Labrado if (aggType == AggregationType::Resource) 713db18fc98SCarson Labrado { 71439662a3bSEd Tanous std::string nameStr = sharedReq->url().segments().back(); 715db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 716db18fc98SCarson Labrado } 717db18fc98SCarson Labrado return; 718db18fc98SCarson Labrado } 719db18fc98SCarson Labrado 72046a81465SCarson Labrado const crow::Request& thisReq = *sharedReq; 72162598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregation is enabled, begin processing of {}", 72262598e31SEd Tanous thisReq.target()); 72346a81465SCarson Labrado 72446a81465SCarson Labrado // We previously determined the request is for a collection. No need to 72546a81465SCarson Labrado // check again 726e002dbc0SCarson Labrado if (aggType == AggregationType::Collection) 72746a81465SCarson Labrado { 72862598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregating a collection"); 7294c30e226SCarson Labrado // We need to use a specific response handler and send the 7304c30e226SCarson Labrado // request to all known satellites 7314c30e226SCarson Labrado getInstance().forwardCollectionRequests(thisReq, asyncResp, 7324c30e226SCarson Labrado satelliteInfo); 73346a81465SCarson Labrado return; 73446a81465SCarson Labrado } 73546a81465SCarson Labrado 736e002dbc0SCarson Labrado // We previously determined the request may contain a subordinate 737e002dbc0SCarson Labrado // collection. No need to check again 738e002dbc0SCarson Labrado if (aggType == AggregationType::ContainsSubordinate) 739e002dbc0SCarson Labrado { 74062598e31SEd Tanous BMCWEB_LOG_DEBUG( 74162598e31SEd Tanous "Aggregating what may have a subordinate collection"); 742e002dbc0SCarson Labrado // We need to use a specific response handler and send the 743e002dbc0SCarson Labrado // request to all known satellites 744e002dbc0SCarson Labrado getInstance().forwardContainsSubordinateRequests(thisReq, asyncResp, 745e002dbc0SCarson Labrado satelliteInfo); 746e002dbc0SCarson Labrado return; 747e002dbc0SCarson Labrado } 748e002dbc0SCarson Labrado 74939662a3bSEd Tanous const boost::urls::segments_view urlSegments = thisReq.url().segments(); 7507c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 7514a7fbefdSEd Tanous boost::urls::segments_view::const_iterator it = urlSegments.begin(); 7524a7fbefdSEd Tanous boost::urls::segments_view::const_iterator end = urlSegments.end(); 7537c4c52cbSCarson Labrado 7547c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 7557c4c52cbSCarson Labrado it++; 7567c4c52cbSCarson Labrado it++; 7577c4c52cbSCarson Labrado for (; it != end; it++) 75846a81465SCarson Labrado { 7597c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 7607c4c52cbSCarson Labrado currentUrl.buffer())) 7617c4c52cbSCarson Labrado { 7627c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 7637c4c52cbSCarson Labrado // must contain an aggregation prefix 7647c4c52cbSCarson Labrado findSatellite(thisReq, asyncResp, satelliteInfo, *it); 76546a81465SCarson Labrado return; 76646a81465SCarson Labrado } 76746a81465SCarson Labrado 7687c4c52cbSCarson Labrado currentUrl.segments().push_back(*it); 76946a81465SCarson Labrado } 770db18fc98SCarson Labrado 771db18fc98SCarson Labrado // We shouldn't reach this point since we should've hit one of the 772db18fc98SCarson Labrado // previous exits 773db18fc98SCarson Labrado messages::internalError(asyncResp->res); 77446a81465SCarson Labrado } 77546a81465SCarson Labrado 77646a81465SCarson Labrado // Attempt to forward a request to the satellite BMC associated with the 77746a81465SCarson Labrado // prefix. 77846a81465SCarson Labrado void forwardRequest( 77946a81465SCarson Labrado const crow::Request& thisReq, 78046a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 78146a81465SCarson Labrado const std::string& prefix, 78246a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 78346a81465SCarson Labrado { 78446a81465SCarson Labrado const auto& sat = satelliteInfo.find(prefix); 78546a81465SCarson Labrado if (sat == satelliteInfo.end()) 78646a81465SCarson Labrado { 78746a81465SCarson Labrado // Realistically this shouldn't get called since we perform an 78846a81465SCarson Labrado // earlier check to make sure the prefix exists 78962598e31SEd Tanous BMCWEB_LOG_ERROR("Unrecognized satellite prefix \"{}\"", prefix); 79046a81465SCarson Labrado return; 79146a81465SCarson Labrado } 79246a81465SCarson Labrado 79346a81465SCarson Labrado // We need to strip the prefix from the request's path 794a716aa74SEd Tanous boost::urls::url targetURI(thisReq.target()); 795a716aa74SEd Tanous std::string path = thisReq.url().path(); 796a716aa74SEd Tanous size_t pos = path.find(prefix + "_"); 79746a81465SCarson Labrado if (pos == std::string::npos) 79846a81465SCarson Labrado { 79946a81465SCarson Labrado // If this fails then something went wrong 80062598e31SEd Tanous BMCWEB_LOG_ERROR("Error removing prefix \"{}_\" from request URI", 80162598e31SEd Tanous prefix); 80246a81465SCarson Labrado messages::internalError(asyncResp->res); 80346a81465SCarson Labrado return; 80446a81465SCarson Labrado } 805a716aa74SEd Tanous path.erase(pos, prefix.size() + 1); 80646a81465SCarson Labrado 80746a81465SCarson Labrado std::function<void(crow::Response&)> cb = 8081c0bb5c6SCarson Labrado std::bind_front(processResponse, prefix, asyncResp); 80946a81465SCarson Labrado 81027b0cf90SEd Tanous std::string data = thisReq.body(); 811a716aa74SEd Tanous boost::urls::url url(sat->second); 812a716aa74SEd Tanous url.set_path(path); 813a716aa74SEd Tanous if (targetURI.has_query()) 814a716aa74SEd Tanous { 815a716aa74SEd Tanous url.set_query(targetURI.query()); 816a716aa74SEd Tanous } 81719bb362bSEd Tanous client.sendDataWithCallback(std::move(data), url, 81819bb362bSEd Tanous ensuressl::VerifyCertificate::Verify, 81919bb362bSEd Tanous thisReq.fields(), thisReq.method(), cb); 82046a81465SCarson Labrado } 82146a81465SCarson Labrado 8224c30e226SCarson Labrado // Forward a request for a collection URI to each known satellite BMC 8234c30e226SCarson Labrado void forwardCollectionRequests( 8244c30e226SCarson Labrado const crow::Request& thisReq, 8254c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 8264c30e226SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 8274c30e226SCarson Labrado { 8284c30e226SCarson Labrado for (const auto& sat : satelliteInfo) 8294c30e226SCarson Labrado { 8304c30e226SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 8314c30e226SCarson Labrado processCollectionResponse, sat.first, asyncResp); 8324c30e226SCarson Labrado 833a716aa74SEd Tanous boost::urls::url url(sat.second); 834a716aa74SEd Tanous url.set_path(thisReq.url().path()); 835a716aa74SEd Tanous if (thisReq.url().has_query()) 836a716aa74SEd Tanous { 837a716aa74SEd Tanous url.set_query(thisReq.url().query()); 838a716aa74SEd Tanous } 83927b0cf90SEd Tanous std::string data = thisReq.body(); 84019bb362bSEd Tanous client.sendDataWithCallback(std::move(data), url, 84119bb362bSEd Tanous ensuressl::VerifyCertificate::Verify, 84219bb362bSEd Tanous thisReq.fields(), thisReq.method(), cb); 8434c30e226SCarson Labrado } 8444c30e226SCarson Labrado } 8454c30e226SCarson Labrado 846e002dbc0SCarson Labrado // Forward request for a URI that is uptree of a top level collection to 847e002dbc0SCarson Labrado // each known satellite BMC 848e002dbc0SCarson Labrado void forwardContainsSubordinateRequests( 849e002dbc0SCarson Labrado const crow::Request& thisReq, 850e002dbc0SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 851e002dbc0SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 852e002dbc0SCarson Labrado { 853e002dbc0SCarson Labrado for (const auto& sat : satelliteInfo) 854e002dbc0SCarson Labrado { 855e002dbc0SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 856e002dbc0SCarson Labrado processContainsSubordinateResponse, sat.first, asyncResp); 857e002dbc0SCarson Labrado 858e002dbc0SCarson Labrado // will ignore an expanded resource in the response if that resource 859e002dbc0SCarson Labrado // is not already supported by the aggregating BMC 860e002dbc0SCarson Labrado // TODO: Improve the processing so that we don't have to strip query 861e002dbc0SCarson Labrado // params in this specific case 862a716aa74SEd Tanous boost::urls::url url(sat.second); 863a716aa74SEd Tanous url.set_path(thisReq.url().path()); 864a716aa74SEd Tanous 86527b0cf90SEd Tanous std::string data = thisReq.body(); 866a716aa74SEd Tanous 86719bb362bSEd Tanous client.sendDataWithCallback(std::move(data), url, 86819bb362bSEd Tanous ensuressl::VerifyCertificate::Verify, 86919bb362bSEd Tanous thisReq.fields(), thisReq.method(), cb); 870e002dbc0SCarson Labrado } 871e002dbc0SCarson Labrado } 872e002dbc0SCarson Labrado 87332d7d8ebSCarson Labrado public: 8749838eb20SEd Tanous explicit RedfishAggregator() : 8759838eb20SEd Tanous client(getIoContext(), 876f8ca6d79SEd Tanous std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy())) 877f8ca6d79SEd Tanous { 878f8ca6d79SEd Tanous getSatelliteConfigs(constructorCallback); 879f8ca6d79SEd Tanous } 88032d7d8ebSCarson Labrado RedfishAggregator(const RedfishAggregator&) = delete; 88132d7d8ebSCarson Labrado RedfishAggregator& operator=(const RedfishAggregator&) = delete; 88232d7d8ebSCarson Labrado RedfishAggregator(RedfishAggregator&&) = delete; 88332d7d8ebSCarson Labrado RedfishAggregator& operator=(RedfishAggregator&&) = delete; 88432d7d8ebSCarson Labrado ~RedfishAggregator() = default; 88532d7d8ebSCarson Labrado 8869838eb20SEd Tanous static RedfishAggregator& getInstance() 88732d7d8ebSCarson Labrado { 8889838eb20SEd Tanous static RedfishAggregator handler; 88932d7d8ebSCarson Labrado return handler; 89032d7d8ebSCarson Labrado } 89132d7d8ebSCarson Labrado 89266620686SEd Tanous // Aggregation sources from AggregationCollection 89366620686SEd Tanous std::unordered_map<std::string, boost::urls::url> currentAggregationSources; 89466620686SEd Tanous 8958b2521a5SCarson Labrado // Polls D-Bus to get all available satellite config information 8968b2521a5SCarson Labrado // Expects a handler which interacts with the returned configs 89766620686SEd Tanous void getSatelliteConfigs( 8988b2521a5SCarson Labrado std::function< 8998b2521a5SCarson Labrado void(const boost::system::error_code&, 9008b2521a5SCarson Labrado const std::unordered_map<std::string, boost::urls::url>&)> 90166620686SEd Tanous handler) const 9028b2521a5SCarson Labrado { 90362598e31SEd Tanous BMCWEB_LOG_DEBUG("Gathering satellite configs"); 90466620686SEd Tanous 90566620686SEd Tanous std::unordered_map<std::string, boost::urls::url> satelliteInfo( 90666620686SEd Tanous currentAggregationSources); 90766620686SEd Tanous 9085eb468daSGeorge Liu sdbusplus::message::object_path path("/xyz/openbmc_project/inventory"); 9095eb468daSGeorge Liu dbus::utility::getManagedObjects( 9105eb468daSGeorge Liu "xyz.openbmc_project.EntityManager", path, 91166620686SEd Tanous [handler{std::move(handler)}, 91266620686SEd Tanous satelliteInfo = std::move(satelliteInfo)]( 9138b2521a5SCarson Labrado const boost::system::error_code& ec, 91466620686SEd Tanous const dbus::utility::ManagedObjectType& objects) mutable { 9158b2521a5SCarson Labrado if (ec) 9168b2521a5SCarson Labrado { 91762598e31SEd Tanous BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(), 91862598e31SEd Tanous ec.message()); 9198b2521a5SCarson Labrado handler(ec, satelliteInfo); 9208b2521a5SCarson Labrado return; 9218b2521a5SCarson Labrado } 9228b2521a5SCarson Labrado 9238b2521a5SCarson Labrado // Maps a chosen alias representing a satellite BMC to a url 9248b2521a5SCarson Labrado // containing the information required to create a http 9258b2521a5SCarson Labrado // connection to the satellite 9268b2521a5SCarson Labrado findSatelliteConfigs(objects, satelliteInfo); 9278b2521a5SCarson Labrado 9288b2521a5SCarson Labrado if (!satelliteInfo.empty()) 9298b2521a5SCarson Labrado { 93062598e31SEd Tanous BMCWEB_LOG_DEBUG( 93162598e31SEd Tanous "Redfish Aggregation enabled with {} satellite BMCs", 93262598e31SEd Tanous std::to_string(satelliteInfo.size())); 9338b2521a5SCarson Labrado } 9348b2521a5SCarson Labrado else 9358b2521a5SCarson Labrado { 93662598e31SEd Tanous BMCWEB_LOG_DEBUG( 93762598e31SEd Tanous "No satellite BMCs detected. Redfish Aggregation not enabled"); 9388b2521a5SCarson Labrado } 9398b2521a5SCarson Labrado handler(ec, satelliteInfo); 9405eb468daSGeorge Liu }); 9418b2521a5SCarson Labrado } 9428b2521a5SCarson Labrado 94346a81465SCarson Labrado // Processes the response returned by a satellite BMC and loads its 94446a81465SCarson Labrado // contents into asyncResp 945504af5a0SPatrick Williams static void processResponse( 946504af5a0SPatrick Williams std::string_view prefix, 9471c0bb5c6SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 94846a81465SCarson Labrado crow::Response& resp) 94946a81465SCarson Labrado { 95043e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 95143e14d38SCarson Labrado // overwrite the response headers in that case 95246b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 95346b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 95443e14d38SCarson Labrado { 95543e14d38SCarson Labrado asyncResp->res.result(resp.result()); 95643e14d38SCarson Labrado return; 95743e14d38SCarson Labrado } 95843e14d38SCarson Labrado 95932d7d8ebSCarson Labrado // We want to attempt prefix fixing regardless of response code 96046a81465SCarson Labrado // The resp will not have a json component 96146a81465SCarson Labrado // We need to create a json from resp's stringResponse 96218f8f608SEd Tanous if (isJsonContentType(resp.getHeaderValue("Content-Type"))) 96346a81465SCarson Labrado { 964bd79bce8SPatrick Williams nlohmann::json jsonVal = 965bd79bce8SPatrick Williams nlohmann::json::parse(*resp.body(), nullptr, false); 96646a81465SCarson Labrado if (jsonVal.is_discarded()) 96746a81465SCarson Labrado { 96862598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 96946a81465SCarson Labrado messages::operationFailed(asyncResp->res); 97046a81465SCarson Labrado return; 97146a81465SCarson Labrado } 97246a81465SCarson Labrado 97362598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 97446a81465SCarson Labrado 9751c0bb5c6SCarson Labrado addPrefixes(jsonVal, prefix); 9761c0bb5c6SCarson Labrado 97762598e31SEd Tanous BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response"); 9781c0bb5c6SCarson Labrado 97946a81465SCarson Labrado asyncResp->res.result(resp.result()); 98046a81465SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 98146a81465SCarson Labrado 98262598e31SEd Tanous BMCWEB_LOG_DEBUG("Finished writing asyncResp"); 98346a81465SCarson Labrado } 98446a81465SCarson Labrado else 98546a81465SCarson Labrado { 9860af78d5aSKhang Kieu // We allow any Content-Type that is not "application/json" now 9870af78d5aSKhang Kieu asyncResp->res.result(resp.result()); 98827b0cf90SEd Tanous asyncResp->res.copyBody(resp); 98946a81465SCarson Labrado } 9900af78d5aSKhang Kieu addAggregatedHeaders(asyncResp->res, resp, prefix); 99146a81465SCarson Labrado } 99246a81465SCarson Labrado 9934c30e226SCarson Labrado // Processes the collection response returned by a satellite BMC and merges 9944c30e226SCarson Labrado // its "@odata.id" values 9954c30e226SCarson Labrado static void processCollectionResponse( 9964c30e226SCarson Labrado const std::string& prefix, 9974c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 9984c30e226SCarson Labrado crow::Response& resp) 9994c30e226SCarson Labrado { 100043e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 100143e14d38SCarson Labrado // overwrite the response headers in that case 100246b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 100346b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 100443e14d38SCarson Labrado { 100543e14d38SCarson Labrado return; 100643e14d38SCarson Labrado } 100743e14d38SCarson Labrado 10084c30e226SCarson Labrado if (resp.resultInt() != 200) 10094c30e226SCarson Labrado { 101062598e31SEd Tanous BMCWEB_LOG_DEBUG( 101162598e31SEd Tanous "Collection resource does not exist in satellite BMC \"{}\"", 101262598e31SEd Tanous prefix); 10134c30e226SCarson Labrado // Return the error if we haven't had any successes 10144c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 10154c30e226SCarson Labrado { 101646b30283SCarson Labrado asyncResp->res.result(resp.result()); 101727b0cf90SEd Tanous asyncResp->res.copyBody(resp); 10184c30e226SCarson Labrado } 10194c30e226SCarson Labrado return; 10204c30e226SCarson Labrado } 10214c30e226SCarson Labrado 10224c30e226SCarson Labrado // The resp will not have a json component 10234c30e226SCarson Labrado // We need to create a json from resp's stringResponse 102418f8f608SEd Tanous if (isJsonContentType(resp.getHeaderValue("Content-Type"))) 10254c30e226SCarson Labrado { 1026bd79bce8SPatrick Williams nlohmann::json jsonVal = 1027bd79bce8SPatrick Williams nlohmann::json::parse(*resp.body(), nullptr, false); 10284c30e226SCarson Labrado if (jsonVal.is_discarded()) 10294c30e226SCarson Labrado { 103062598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 10314c30e226SCarson Labrado 10324c30e226SCarson Labrado // Notify the user if doing so won't overwrite a valid response 103346b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 10344c30e226SCarson Labrado { 10354c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 10364c30e226SCarson Labrado } 10374c30e226SCarson Labrado return; 10384c30e226SCarson Labrado } 10394c30e226SCarson Labrado 104062598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 10414c30e226SCarson Labrado 10424c30e226SCarson Labrado // Now we need to add the prefix to the URIs contained in the 10434c30e226SCarson Labrado // response. 10444c30e226SCarson Labrado addPrefixes(jsonVal, prefix); 10454c30e226SCarson Labrado 104662598e31SEd Tanous BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response"); 10474c30e226SCarson Labrado 10484c30e226SCarson Labrado // If this resource collection does not exist on the aggregating bmc 10494c30e226SCarson Labrado // and has not already been added from processing the response from 10504c30e226SCarson Labrado // a different satellite then we need to completely overwrite 10514c30e226SCarson Labrado // asyncResp 10524c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 10534c30e226SCarson Labrado { 10544c30e226SCarson Labrado // We only want to aggregate collections that contain a 10554c30e226SCarson Labrado // "Members" array 10564c30e226SCarson Labrado if ((!jsonVal.contains("Members")) && 10574c30e226SCarson Labrado (!jsonVal["Members"].is_array())) 10584c30e226SCarson Labrado { 105962598e31SEd Tanous BMCWEB_LOG_DEBUG( 106062598e31SEd Tanous "Skipping aggregating unsupported resource"); 10614c30e226SCarson Labrado return; 10624c30e226SCarson Labrado } 10634c30e226SCarson Labrado 106462598e31SEd Tanous BMCWEB_LOG_DEBUG( 106562598e31SEd Tanous "Collection does not exist, overwriting asyncResp"); 10664c30e226SCarson Labrado asyncResp->res.result(resp.result()); 10674c30e226SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 106843e14d38SCarson Labrado asyncResp->res.addHeader("Content-Type", "application/json"); 10694c30e226SCarson Labrado 107062598e31SEd Tanous BMCWEB_LOG_DEBUG("Finished overwriting asyncResp"); 10714c30e226SCarson Labrado } 10724c30e226SCarson Labrado else 10734c30e226SCarson Labrado { 10744c30e226SCarson Labrado // We only want to aggregate collections that contain a 10754c30e226SCarson Labrado // "Members" array 10764c30e226SCarson Labrado if ((!asyncResp->res.jsonValue.contains("Members")) && 10774c30e226SCarson Labrado (!asyncResp->res.jsonValue["Members"].is_array())) 10784c30e226SCarson Labrado 10794c30e226SCarson Labrado { 108062598e31SEd Tanous BMCWEB_LOG_DEBUG( 108162598e31SEd Tanous "Skipping aggregating unsupported resource"); 10824c30e226SCarson Labrado return; 10834c30e226SCarson Labrado } 10844c30e226SCarson Labrado 108562598e31SEd Tanous BMCWEB_LOG_DEBUG( 108662598e31SEd Tanous "Adding aggregated resources from \"{}\" to collection", 108762598e31SEd Tanous prefix); 10884c30e226SCarson Labrado 10894c30e226SCarson Labrado // TODO: This is a potential race condition with multiple 10904c30e226SCarson Labrado // satellites and the aggregating bmc attempting to write to 10914c30e226SCarson Labrado // update this array. May need to cascade calls to the next 10924c30e226SCarson Labrado // satellite at the end of this function. 10934c30e226SCarson Labrado // This is presumably not a concern when there is only a single 10944c30e226SCarson Labrado // satellite since the aggregating bmc should have completed 10954c30e226SCarson Labrado // before the response is received from the satellite. 10964c30e226SCarson Labrado 10974c30e226SCarson Labrado auto& members = asyncResp->res.jsonValue["Members"]; 10984c30e226SCarson Labrado auto& satMembers = jsonVal["Members"]; 10994c30e226SCarson Labrado for (auto& satMem : satMembers) 11004c30e226SCarson Labrado { 1101b2ba3072SPatrick Williams members.emplace_back(std::move(satMem)); 11024c30e226SCarson Labrado } 11034c30e226SCarson Labrado asyncResp->res.jsonValue["Members@odata.count"] = 11044c30e226SCarson Labrado members.size(); 11054c30e226SCarson Labrado 11064c30e226SCarson Labrado // TODO: Do we need to sort() after updating the array? 11074c30e226SCarson Labrado } 11084c30e226SCarson Labrado } 11094c30e226SCarson Labrado else 11104c30e226SCarson Labrado { 111162598e31SEd Tanous BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"", 111262598e31SEd Tanous prefix); 111343e14d38SCarson Labrado // We received a response that was not a json. 111446b30283SCarson Labrado // Notify the user only if we did not receive any valid responses 111546b30283SCarson Labrado // and if the resource collection does not already exist on the 111646b30283SCarson Labrado // aggregating BMC 111746b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 11184c30e226SCarson Labrado { 11194c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 11204c30e226SCarson Labrado } 11214c30e226SCarson Labrado } 11224c30e226SCarson Labrado } // End processCollectionResponse() 11234c30e226SCarson Labrado 112446b30283SCarson Labrado // Processes the response returned by a satellite BMC and merges any 112546b30283SCarson Labrado // properties whose "@odata.id" value is the URI or either a top level 112646b30283SCarson Labrado // collection or is uptree from a top level collection 112746b30283SCarson Labrado static void processContainsSubordinateResponse( 112846b30283SCarson Labrado const std::string& prefix, 112946b30283SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 113046b30283SCarson Labrado crow::Response& resp) 113146b30283SCarson Labrado { 113246b30283SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 113346b30283SCarson Labrado // overwrite the response headers in that case 113446b30283SCarson Labrado if ((resp.result() == boost::beast::http::status::too_many_requests) || 113546b30283SCarson Labrado (resp.result() == boost::beast::http::status::bad_gateway)) 113646b30283SCarson Labrado { 113746b30283SCarson Labrado return; 113846b30283SCarson Labrado } 113946b30283SCarson Labrado 114046b30283SCarson Labrado if (resp.resultInt() != 200) 114146b30283SCarson Labrado { 114262598e31SEd Tanous BMCWEB_LOG_DEBUG( 114362598e31SEd Tanous "Resource uptree from Collection does not exist in satellite BMC \"{}\"", 114462598e31SEd Tanous prefix); 114546b30283SCarson Labrado // Return the error if we haven't had any successes 114646b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 114746b30283SCarson Labrado { 114846b30283SCarson Labrado asyncResp->res.result(resp.result()); 114927b0cf90SEd Tanous asyncResp->res.copyBody(resp); 115046b30283SCarson Labrado } 115146b30283SCarson Labrado return; 115246b30283SCarson Labrado } 115346b30283SCarson Labrado 115446b30283SCarson Labrado // The resp will not have a json component 115546b30283SCarson Labrado // We need to create a json from resp's stringResponse 115618f8f608SEd Tanous if (isJsonContentType(resp.getHeaderValue("Content-Type"))) 115746b30283SCarson Labrado { 115846b30283SCarson Labrado bool addedLinks = false; 1159bd79bce8SPatrick Williams nlohmann::json jsonVal = 1160bd79bce8SPatrick Williams nlohmann::json::parse(*resp.body(), nullptr, false); 116146b30283SCarson Labrado if (jsonVal.is_discarded()) 116246b30283SCarson Labrado { 116362598e31SEd Tanous BMCWEB_LOG_ERROR("Error parsing satellite response as JSON"); 116446b30283SCarson Labrado 116546b30283SCarson Labrado // Notify the user if doing so won't overwrite a valid response 116646b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 116746b30283SCarson Labrado { 116846b30283SCarson Labrado messages::operationFailed(asyncResp->res); 116946b30283SCarson Labrado } 117046b30283SCarson Labrado return; 117146b30283SCarson Labrado } 117246b30283SCarson Labrado 117362598e31SEd Tanous BMCWEB_LOG_DEBUG("Successfully parsed satellite response"); 117446b30283SCarson Labrado 117546b30283SCarson Labrado // Parse response and add properties missing from the AsyncResp 117646b30283SCarson Labrado // Valid properties will be of the form <property>.@odata.id and 117746b30283SCarson Labrado // @odata.id is a <URI>. In other words, the json should contain 117846b30283SCarson Labrado // multiple properties such that 117946b30283SCarson Labrado // {"<property>":{"@odata.id": "<URI>"}} 118046b30283SCarson Labrado nlohmann::json::object_t* object = 118146b30283SCarson Labrado jsonVal.get_ptr<nlohmann::json::object_t*>(); 118246b30283SCarson Labrado if (object == nullptr) 118346b30283SCarson Labrado { 118462598e31SEd Tanous BMCWEB_LOG_ERROR("Parsed JSON was not an object?"); 118546b30283SCarson Labrado return; 118646b30283SCarson Labrado } 118746b30283SCarson Labrado 118846b30283SCarson Labrado for (std::pair<const std::string, nlohmann::json>& prop : *object) 118946b30283SCarson Labrado { 119046b30283SCarson Labrado if (!prop.second.contains("@odata.id")) 119146b30283SCarson Labrado { 119246b30283SCarson Labrado continue; 119346b30283SCarson Labrado } 119446b30283SCarson Labrado 119546b30283SCarson Labrado std::string* strValue = 119646b30283SCarson Labrado prop.second["@odata.id"].get_ptr<std::string*>(); 119746b30283SCarson Labrado if (strValue == nullptr) 119846b30283SCarson Labrado { 119962598e31SEd Tanous BMCWEB_LOG_CRITICAL("Field wasn't a string????"); 120046b30283SCarson Labrado continue; 120146b30283SCarson Labrado } 120246b30283SCarson Labrado if (!searchCollectionsArray(*strValue, SearchType::CollOrCon)) 120346b30283SCarson Labrado { 120446b30283SCarson Labrado continue; 120546b30283SCarson Labrado } 120646b30283SCarson Labrado 120746b30283SCarson Labrado addedLinks = true; 120846b30283SCarson Labrado if (!asyncResp->res.jsonValue.contains(prop.first)) 120946b30283SCarson Labrado { 121046b30283SCarson Labrado // Only add the property if it did not already exist 121162598e31SEd Tanous BMCWEB_LOG_DEBUG("Adding link for {} from BMC {}", 121262598e31SEd Tanous *strValue, prefix); 121346b30283SCarson Labrado asyncResp->res.jsonValue[prop.first]["@odata.id"] = 121446b30283SCarson Labrado *strValue; 121546b30283SCarson Labrado continue; 121646b30283SCarson Labrado } 121746b30283SCarson Labrado } 121846b30283SCarson Labrado 121946b30283SCarson Labrado // If we added links to a previously unsuccessful (non-200) response 122046b30283SCarson Labrado // then we need to make sure the response contains the bare minimum 122146b30283SCarson Labrado // amount of additional information that we'd expect to have been 122246b30283SCarson Labrado // populated. 122346b30283SCarson Labrado if (addedLinks && (asyncResp->res.resultInt() != 200)) 122446b30283SCarson Labrado { 122546b30283SCarson Labrado // This resource didn't locally exist or an error 122646b30283SCarson Labrado // occurred while generating the response. Remove any 122746b30283SCarson Labrado // error messages and update the error code. 122846b30283SCarson Labrado asyncResp->res.jsonValue.erase( 122946b30283SCarson Labrado asyncResp->res.jsonValue.find("error")); 123046b30283SCarson Labrado asyncResp->res.result(resp.result()); 123146b30283SCarson Labrado 123246b30283SCarson Labrado const auto& it1 = object->find("@odata.id"); 123346b30283SCarson Labrado if (it1 != object->end()) 123446b30283SCarson Labrado { 123546b30283SCarson Labrado asyncResp->res.jsonValue["@odata.id"] = (it1->second); 123646b30283SCarson Labrado } 123746b30283SCarson Labrado const auto& it2 = object->find("@odata.type"); 123846b30283SCarson Labrado if (it2 != object->end()) 123946b30283SCarson Labrado { 124046b30283SCarson Labrado asyncResp->res.jsonValue["@odata.type"] = (it2->second); 124146b30283SCarson Labrado } 124246b30283SCarson Labrado const auto& it3 = object->find("Id"); 124346b30283SCarson Labrado if (it3 != object->end()) 124446b30283SCarson Labrado { 124546b30283SCarson Labrado asyncResp->res.jsonValue["Id"] = (it3->second); 124646b30283SCarson Labrado } 124746b30283SCarson Labrado const auto& it4 = object->find("Name"); 124846b30283SCarson Labrado if (it4 != object->end()) 124946b30283SCarson Labrado { 125046b30283SCarson Labrado asyncResp->res.jsonValue["Name"] = (it4->second); 125146b30283SCarson Labrado } 125246b30283SCarson Labrado } 125346b30283SCarson Labrado } 125446b30283SCarson Labrado else 125546b30283SCarson Labrado { 125662598e31SEd Tanous BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"", 125762598e31SEd Tanous prefix); 125846b30283SCarson Labrado // We received as response that was not a json 125946b30283SCarson Labrado // Notify the user only if we did not receive any valid responses, 126046b30283SCarson Labrado // and if the resource does not already exist on the aggregating BMC 126146b30283SCarson Labrado if (asyncResp->res.resultInt() != 200) 126246b30283SCarson Labrado { 126346b30283SCarson Labrado messages::operationFailed(asyncResp->res); 126446b30283SCarson Labrado } 126546b30283SCarson Labrado } 126646b30283SCarson Labrado } 126746b30283SCarson Labrado 126805916cefSCarson Labrado // Entry point to Redfish Aggregation 126905916cefSCarson Labrado // Returns Result stating whether or not we still need to locally handle the 127005916cefSCarson Labrado // request 127166620686SEd Tanous Result beginAggregation(const crow::Request& thisReq, 127205916cefSCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 127305916cefSCarson Labrado { 127405916cefSCarson Labrado using crow::utility::OrMorePaths; 127505916cefSCarson Labrado using crow::utility::readUrlSegments; 12764a7fbefdSEd Tanous boost::urls::url_view url = thisReq.url(); 1277411e6a11SCarson Labrado 1278411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such 1279411e6a11SCarson Labrado // as version mismatches between aggregator and satellite BMCs. For 1280411e6a11SCarson Labrado // now assume that the aggregator has all the schemas and versions that 1281411e6a11SCarson Labrado // the aggregated server has. 1282411e6a11SCarson Labrado if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", 1283411e6a11SCarson Labrado crow::utility::OrMorePaths())) 1284411e6a11SCarson Labrado { 1285411e6a11SCarson Labrado return Result::LocalHandle; 1286411e6a11SCarson Labrado } 1287411e6a11SCarson Labrado 12887c4c52cbSCarson Labrado // The first two segments should be "/redfish/v1". We need to check 12897c4c52cbSCarson Labrado // that before we can search topCollections 12907c4c52cbSCarson Labrado if (!crow::utility::readUrlSegments(url, "redfish", "v1", 12917c4c52cbSCarson Labrado crow::utility::OrMorePaths())) 129246a81465SCarson Labrado { 129346a81465SCarson Labrado return Result::LocalHandle; 129446a81465SCarson Labrado } 129505916cefSCarson Labrado 12967c4c52cbSCarson Labrado // Parse the URI to see if it begins with a known top level collection 12977c4c52cbSCarson Labrado // such as: 12987c4c52cbSCarson Labrado // /redfish/v1/Chassis 12997c4c52cbSCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory 13007c4c52cbSCarson Labrado const boost::urls::segments_view urlSegments = url.segments(); 13017c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 13024a7fbefdSEd Tanous boost::urls::segments_view::const_iterator it = urlSegments.begin(); 13034a7fbefdSEd Tanous boost::urls::segments_view::const_iterator end = urlSegments.end(); 130405916cefSCarson Labrado 13057c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 13067c4c52cbSCarson Labrado it++; 13077c4c52cbSCarson Labrado it++; 13087c4c52cbSCarson Labrado for (; it != end; it++) 130905916cefSCarson Labrado { 1310d4413c5bSGeorge Liu const std::string& collectionItem = *it; 13117c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 13127c4c52cbSCarson Labrado currentUrl.buffer())) 13137c4c52cbSCarson Labrado { 13147c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 13157c4c52cbSCarson Labrado // might contain an aggregation prefix 1316*71526116SKamran Hasan if (segmentHasPrefix(collectionItem)) 131705916cefSCarson Labrado { 131862598e31SEd Tanous BMCWEB_LOG_DEBUG("Need to forward a request"); 131905916cefSCarson Labrado 132046a81465SCarson Labrado // Extract the prefix from the request's URI, retrieve the 13217c4c52cbSCarson Labrado // associated satellite config information, and then forward 13227c4c52cbSCarson Labrado // the request to that satellite. 13237c4c52cbSCarson Labrado startAggregation(AggregationType::Resource, thisReq, 13247c4c52cbSCarson Labrado asyncResp); 132505916cefSCarson Labrado return Result::NoLocalHandle; 132605916cefSCarson Labrado } 13277c4c52cbSCarson Labrado 13287c4c52cbSCarson Labrado // Handle collection URI with a trailing backslash 13297c4c52cbSCarson Labrado // e.g. /redfish/v1/Chassis/ 13307c4c52cbSCarson Labrado it++; 13317c4c52cbSCarson Labrado if ((it == end) && collectionItem.empty()) 13327c4c52cbSCarson Labrado { 13337c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, 13347c4c52cbSCarson Labrado asyncResp); 13357c4c52cbSCarson Labrado } 13367c4c52cbSCarson Labrado 13377c4c52cbSCarson Labrado // We didn't recognize the prefix or it's a collection with a 13387c4c52cbSCarson Labrado // trailing "/". In both cases we still want to locally handle 13397c4c52cbSCarson Labrado // the request 13407c4c52cbSCarson Labrado return Result::LocalHandle; 13417c4c52cbSCarson Labrado } 13427c4c52cbSCarson Labrado 13437c4c52cbSCarson Labrado currentUrl.segments().push_back(collectionItem); 13447c4c52cbSCarson Labrado } 13457c4c52cbSCarson Labrado 13467c4c52cbSCarson Labrado // If we made it here then currentUrl could contain a top level 13477c4c52cbSCarson Labrado // collection URI without a trailing "/", e.g. /redfish/v1/Chassis 13487c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 13497c4c52cbSCarson Labrado currentUrl.buffer())) 13507c4c52cbSCarson Labrado { 13517c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 135205916cefSCarson Labrado return Result::LocalHandle; 135305916cefSCarson Labrado } 135405916cefSCarson Labrado 1355e002dbc0SCarson Labrado // If nothing else then the request could be for a resource which has a 1356e002dbc0SCarson Labrado // top level collection as a subordinate 1357e002dbc0SCarson Labrado if (searchCollectionsArray(url.path(), SearchType::ContainsSubordinate)) 1358e002dbc0SCarson Labrado { 1359e002dbc0SCarson Labrado startAggregation(AggregationType::ContainsSubordinate, thisReq, 1360e002dbc0SCarson Labrado asyncResp); 1361e002dbc0SCarson Labrado return Result::LocalHandle; 1362e002dbc0SCarson Labrado } 1363e002dbc0SCarson Labrado 136462598e31SEd Tanous BMCWEB_LOG_DEBUG("Aggregation not required for {}", url.buffer()); 136505916cefSCarson Labrado return Result::LocalHandle; 136605916cefSCarson Labrado } 1367*71526116SKamran Hasan 1368*71526116SKamran Hasan // Check if the given URL segment matches with any satellite prefix 1369*71526116SKamran Hasan // Assumes the given segment starts with <prefix>_ 1370*71526116SKamran Hasan bool segmentHasPrefix(const std::string& urlSegment) const 1371*71526116SKamran Hasan { 1372*71526116SKamran Hasan // TODO: handle this better 1373*71526116SKamran Hasan // For now 5B247A_ wont be in the currentAggregationSources map so 1374*71526116SKamran Hasan // check explicitly for now 1375*71526116SKamran Hasan if (urlSegment.starts_with("5B247A_")) 1376*71526116SKamran Hasan { 1377*71526116SKamran Hasan return true; 1378*71526116SKamran Hasan } 1379*71526116SKamran Hasan 1380*71526116SKamran Hasan // Find the first underscore 1381*71526116SKamran Hasan std::size_t underscorePos = urlSegment.find('_'); 1382*71526116SKamran Hasan if (underscorePos == std::string::npos) 1383*71526116SKamran Hasan { 1384*71526116SKamran Hasan return false; // No underscore, can't be a satellite prefix 1385*71526116SKamran Hasan } 1386*71526116SKamran Hasan 1387*71526116SKamran Hasan // Extract the prefix 1388*71526116SKamran Hasan std::string prefix = urlSegment.substr(0, underscorePos); 1389*71526116SKamran Hasan 1390*71526116SKamran Hasan // Check if this prefix exists 1391*71526116SKamran Hasan return currentAggregationSources.contains(prefix); 1392*71526116SKamran Hasan } 13937fb33566SCarson Labrado }; 13947fb33566SCarson Labrado 13957fb33566SCarson Labrado } // namespace redfish 1396