17fb33566SCarson Labrado #pragma once 27fb33566SCarson Labrado 33ccb3adbSEd Tanous #include "aggregation_utils.hpp" 43ccb3adbSEd Tanous #include "dbus_utility.hpp" 53ccb3adbSEd Tanous #include "error_messages.hpp" 63ccb3adbSEd Tanous #include "http_client.hpp" 73ccb3adbSEd Tanous #include "http_connection.hpp" 83ccb3adbSEd Tanous 9411e6a11SCarson Labrado #include <boost/algorithm/string/predicate.hpp> 107fb33566SCarson Labrado 117e8890c5SCarson Labrado #include <array> 127e8890c5SCarson Labrado 137fb33566SCarson Labrado namespace redfish 147fb33566SCarson Labrado { 157fb33566SCarson Labrado 16d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB 17d14a48ffSCarson Labrado 1805916cefSCarson Labrado enum class Result 1905916cefSCarson Labrado { 2005916cefSCarson Labrado LocalHandle, 2105916cefSCarson Labrado NoLocalHandle 2205916cefSCarson Labrado }; 2305916cefSCarson Labrado 247e8890c5SCarson Labrado // clang-format off 257e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource 267e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a 277e8890c5SCarson Labrado // case-insensitive form of "uri". That version of the schema is associated 287e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification. Going forward, new URI 297e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as 307e8890c5SCarson Labrado // the spec is updated. NOTE: These have been pre-sorted in order to be 317e8890c5SCarson Labrado // compatible with binary search 327e8890c5SCarson Labrado constexpr std::array nonUriProperties{ 337e8890c5SCarson Labrado "@Redfish.ActionInfo", 347e8890c5SCarson Labrado // "@odata.context", // We can't fix /redfish/v1/$metadata URIs 357e8890c5SCarson Labrado "@odata.id", 367e8890c5SCarson Labrado // "Destination", // Only used by EventService and won't be a Redfish URI 377e8890c5SCarson Labrado // "HostName", // Isn't actually a Redfish URI 387e8890c5SCarson Labrado "Image", 397e8890c5SCarson Labrado "MetricProperty", 4032d7d8ebSCarson Labrado // "OriginOfCondition", // Is URI when in request, but is object in response 417e8890c5SCarson Labrado "TaskMonitor", 427e8890c5SCarson Labrado "target", // normal string, but target URI for POST to invoke an action 437e8890c5SCarson Labrado }; 447e8890c5SCarson Labrado // clang-format on 457e8890c5SCarson Labrado 467e8890c5SCarson Labrado // Determines if the passed property contains a URI. Those property names 477e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically 487e8890c5SCarson Labrado // defined in the above array. 4926ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName) 507e8890c5SCarson Labrado { 517e8890c5SCarson Labrado return boost::iends_with(propertyName, "uri") || 527e8890c5SCarson Labrado std::binary_search(nonUriProperties.begin(), nonUriProperties.end(), 537e8890c5SCarson Labrado propertyName); 547e8890c5SCarson Labrado } 557e8890c5SCarson Labrado 560af78d5aSKhang Kieu static inline void addPrefixToStringItem(std::string& strValue, 570af78d5aSKhang Kieu std::string_view prefix) 581c0bb5c6SCarson Labrado { 591c0bb5c6SCarson Labrado // Make sure the value is a properly formatted URI 600af78d5aSKhang Kieu auto parsed = boost::urls::parse_relative_ref(strValue); 611c0bb5c6SCarson Labrado if (!parsed) 621c0bb5c6SCarson Labrado { 630af78d5aSKhang Kieu BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << strValue; 641c0bb5c6SCarson Labrado return; 651c0bb5c6SCarson Labrado } 661c0bb5c6SCarson Labrado 671c0bb5c6SCarson Labrado boost::urls::url_view thisUrl = *parsed; 681c0bb5c6SCarson Labrado 69411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such as 70411e6a11SCarson Labrado // version mismatches between aggregator and satellite BMCs. For now 71411e6a11SCarson Labrado // assume that the aggregator has all the schemas and versions that the 72411e6a11SCarson Labrado // aggregated server has. 73411e6a11SCarson Labrado if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas", 74411e6a11SCarson Labrado crow::utility::OrMorePaths())) 75411e6a11SCarson Labrado { 76411e6a11SCarson Labrado BMCWEB_LOG_DEBUG << "Skipping JsonSchemas URI prefix fixing"; 77411e6a11SCarson Labrado return; 78411e6a11SCarson Labrado } 79411e6a11SCarson Labrado 8011987af6SCarson Labrado // The first two segments should be "/redfish/v1". We need to check that 8111987af6SCarson Labrado // before we can search topCollections 8211987af6SCarson Labrado if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 8311987af6SCarson Labrado crow::utility::OrMorePaths())) 841c0bb5c6SCarson Labrado { 851c0bb5c6SCarson Labrado return; 861c0bb5c6SCarson Labrado } 871c0bb5c6SCarson Labrado 8811987af6SCarson Labrado // Check array adding a segment each time until collection is identified 8911987af6SCarson Labrado // Add prefix to segment after the collection 9011987af6SCarson Labrado const boost::urls::segments_view urlSegments = thisUrl.segments(); 9111987af6SCarson Labrado bool addedPrefix = false; 9211987af6SCarson Labrado boost::urls::url url("/"); 9311987af6SCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 9411987af6SCarson Labrado const boost::urls::segments_view::const_iterator end = urlSegments.end(); 9511987af6SCarson Labrado 9611987af6SCarson Labrado // Skip past the leading "/redfish/v1" 9711987af6SCarson Labrado it++; 9811987af6SCarson Labrado it++; 9911987af6SCarson Labrado for (; it != end; it++) 1001c0bb5c6SCarson Labrado { 10111987af6SCarson Labrado // Trailing "/" will result in an empty segment. In that case we need 10211987af6SCarson Labrado // to return so we don't apply a prefix to top level collections such 10311987af6SCarson Labrado // as "/redfish/v1/Chassis/" 10411987af6SCarson Labrado if ((*it).empty()) 10511987af6SCarson Labrado { 106411e6a11SCarson Labrado return; 1071c0bb5c6SCarson Labrado } 1081c0bb5c6SCarson Labrado 10911987af6SCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 11011987af6SCarson Labrado url.buffer())) 1111c0bb5c6SCarson Labrado { 11211987af6SCarson Labrado std::string collectionItem(prefix); 11311987af6SCarson Labrado collectionItem += "_" + (*it); 11411987af6SCarson Labrado url.segments().push_back(collectionItem); 11511987af6SCarson Labrado it++; 11611987af6SCarson Labrado addedPrefix = true; 11711987af6SCarson Labrado break; 11811987af6SCarson Labrado } 11911987af6SCarson Labrado 12011987af6SCarson Labrado url.segments().push_back(*it); 12111987af6SCarson Labrado } 12211987af6SCarson Labrado 12311987af6SCarson Labrado // Finish constructing the URL here (if needed) to avoid additional checks 12411987af6SCarson Labrado for (; it != end; it++) 12511987af6SCarson Labrado { 12611987af6SCarson Labrado url.segments().push_back(*it); 12711987af6SCarson Labrado } 12811987af6SCarson Labrado 12911987af6SCarson Labrado if (addedPrefix) 13011987af6SCarson Labrado { 13111987af6SCarson Labrado url.segments().insert(url.segments().begin(), {"redfish", "v1"}); 1320af78d5aSKhang Kieu strValue = url.buffer(); 1331c0bb5c6SCarson Labrado } 1341c0bb5c6SCarson Labrado } 1351c0bb5c6SCarson Labrado 1360af78d5aSKhang Kieu static inline void addPrefixToItem(nlohmann::json& item, 1370af78d5aSKhang Kieu std::string_view prefix) 1380af78d5aSKhang Kieu { 1390af78d5aSKhang Kieu std::string* strValue = item.get_ptr<std::string*>(); 1400af78d5aSKhang Kieu if (strValue == nullptr) 1410af78d5aSKhang Kieu { 1420af78d5aSKhang Kieu BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; 1430af78d5aSKhang Kieu return; 1440af78d5aSKhang Kieu } 1450af78d5aSKhang Kieu addPrefixToStringItem(*strValue, prefix); 1460af78d5aSKhang Kieu item = *strValue; 1470af78d5aSKhang Kieu } 1480af78d5aSKhang Kieu 1490af78d5aSKhang Kieu static inline void addAggregatedHeaders(crow::Response& asyncResp, 15024dadc88SCarson Labrado const crow::Response& resp, 1510af78d5aSKhang Kieu std::string_view prefix) 1520af78d5aSKhang Kieu { 1530af78d5aSKhang Kieu if (!resp.getHeaderValue("Content-Type").empty()) 1540af78d5aSKhang Kieu { 1550af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::content_type, 1560af78d5aSKhang Kieu resp.getHeaderValue("Content-Type")); 1570af78d5aSKhang Kieu } 1580af78d5aSKhang Kieu if (!resp.getHeaderValue("Allow").empty()) 1590af78d5aSKhang Kieu { 1600af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::allow, 1610af78d5aSKhang Kieu resp.getHeaderValue("Allow")); 1620af78d5aSKhang Kieu } 1630af78d5aSKhang Kieu std::string_view header = resp.getHeaderValue("Location"); 1640af78d5aSKhang Kieu if (!header.empty()) 1650af78d5aSKhang Kieu { 1660af78d5aSKhang Kieu std::string location(header); 1670af78d5aSKhang Kieu addPrefixToStringItem(location, prefix); 1680af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::location, location); 1690af78d5aSKhang Kieu } 1700af78d5aSKhang Kieu if (!resp.getHeaderValue("Retry-After").empty()) 1710af78d5aSKhang Kieu { 1720af78d5aSKhang Kieu asyncResp.addHeader(boost::beast::http::field::retry_after, 1730af78d5aSKhang Kieu resp.getHeaderValue("Retry-After")); 1740af78d5aSKhang Kieu } 1750af78d5aSKhang Kieu // TODO: we need special handling for Link Header Value 1760af78d5aSKhang Kieu } 1770af78d5aSKhang Kieu 178*b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others 179*b27e1cbeSCarson Labrado static inline void addPrefixToHeadersInResp(nlohmann::json& json, 180*b27e1cbeSCarson Labrado std::string_view prefix) 181*b27e1cbeSCarson Labrado { 182*b27e1cbeSCarson Labrado // The passed in "HttpHeaders" should be an array of headers 183*b27e1cbeSCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 184*b27e1cbeSCarson Labrado if (array == nullptr) 185*b27e1cbeSCarson Labrado { 186*b27e1cbeSCarson Labrado BMCWEB_LOG_ERROR << "Field wasn't an array_t????"; 187*b27e1cbeSCarson Labrado return; 188*b27e1cbeSCarson Labrado } 189*b27e1cbeSCarson Labrado 190*b27e1cbeSCarson Labrado for (nlohmann::json& item : *array) 191*b27e1cbeSCarson Labrado { 192*b27e1cbeSCarson Labrado // Each header is a single string with the form "<Field>: <Value>" 193*b27e1cbeSCarson Labrado std::string* strHeader = item.get_ptr<std::string*>(); 194*b27e1cbeSCarson Labrado if (strHeader == nullptr) 195*b27e1cbeSCarson Labrado { 196*b27e1cbeSCarson Labrado BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; 197*b27e1cbeSCarson Labrado continue; 198*b27e1cbeSCarson Labrado } 199*b27e1cbeSCarson Labrado 200*b27e1cbeSCarson Labrado constexpr std::string_view location = "Location: "; 201*b27e1cbeSCarson Labrado if (strHeader->starts_with(location)) 202*b27e1cbeSCarson Labrado { 203*b27e1cbeSCarson Labrado std::string header = strHeader->substr(location.size()); 204*b27e1cbeSCarson Labrado addPrefixToStringItem(header, prefix); 205*b27e1cbeSCarson Labrado *strHeader = std::string(location) + header; 206*b27e1cbeSCarson Labrado } 207*b27e1cbeSCarson Labrado } 208*b27e1cbeSCarson Labrado } 209*b27e1cbeSCarson Labrado 2101c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for 2117e8890c5SCarson Labrado // an aggregated resource. 2120af78d5aSKhang Kieu static inline void addPrefixes(nlohmann::json& json, std::string_view prefix) 2131c0bb5c6SCarson Labrado { 2141c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 2151c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 2161c0bb5c6SCarson Labrado if (object != nullptr) 2171c0bb5c6SCarson Labrado { 2181c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 2191c0bb5c6SCarson Labrado { 2207e8890c5SCarson Labrado if (isPropertyUri(item.first)) 2211c0bb5c6SCarson Labrado { 2227e8890c5SCarson Labrado addPrefixToItem(item.second, prefix); 2231c0bb5c6SCarson Labrado continue; 2241c0bb5c6SCarson Labrado } 2251c0bb5c6SCarson Labrado 226*b27e1cbeSCarson Labrado // "HttpHeaders" contains HTTP headers. Among those we need to 227*b27e1cbeSCarson Labrado // attempt to fix the "Location" header 228*b27e1cbeSCarson Labrado if (item.first == "HttpHeaders") 229*b27e1cbeSCarson Labrado { 230*b27e1cbeSCarson Labrado addPrefixToHeadersInResp(item.second, prefix); 231*b27e1cbeSCarson Labrado continue; 232*b27e1cbeSCarson Labrado } 233*b27e1cbeSCarson Labrado 2341c0bb5c6SCarson Labrado // Recusively parse the rest of the json 2351c0bb5c6SCarson Labrado addPrefixes(item.second, prefix); 2361c0bb5c6SCarson Labrado } 2371c0bb5c6SCarson Labrado return; 2381c0bb5c6SCarson Labrado } 2391c0bb5c6SCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 2401c0bb5c6SCarson Labrado if (array != nullptr) 2411c0bb5c6SCarson Labrado { 2421c0bb5c6SCarson Labrado for (nlohmann::json& item : *array) 2431c0bb5c6SCarson Labrado { 2441c0bb5c6SCarson Labrado addPrefixes(item, prefix); 2451c0bb5c6SCarson Labrado } 2461c0bb5c6SCarson Labrado } 2471c0bb5c6SCarson Labrado } 2481c0bb5c6SCarson Labrado 249d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode) 250a7a80296SCarson Labrado { 25132d7d8ebSCarson Labrado // Allow all response codes because we want to surface any satellite 25232d7d8ebSCarson Labrado // issue to the client 253d14a48ffSCarson Labrado BMCWEB_LOG_DEBUG << "Received " << respCode << " response from satellite"; 254d14a48ffSCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 255d14a48ffSCarson Labrado } 256d14a48ffSCarson Labrado 257d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy() 258d14a48ffSCarson Labrado { 259d14a48ffSCarson Labrado return {.maxRetryAttempts = 1, 260d14a48ffSCarson Labrado .requestByteLimit = aggregatorReadBodyLimit, 261d14a48ffSCarson Labrado .maxConnections = 20, 262d14a48ffSCarson Labrado .retryPolicyAction = "TerminateAfterRetries", 263d14a48ffSCarson Labrado .retryIntervalSecs = std::chrono::seconds(0), 264d14a48ffSCarson Labrado .invalidResp = aggregationRetryHandler}; 265d14a48ffSCarson Labrado } 266d14a48ffSCarson Labrado 267d14a48ffSCarson Labrado class RedfishAggregator 268d14a48ffSCarson Labrado { 269d14a48ffSCarson Labrado private: 270d14a48ffSCarson Labrado crow::HttpClient client; 271d14a48ffSCarson Labrado 272d14a48ffSCarson Labrado RedfishAggregator() : 273d14a48ffSCarson Labrado client(std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy())) 274d14a48ffSCarson Labrado { 275d14a48ffSCarson Labrado getSatelliteConfigs(constructorCallback); 2769fa6d147SNan Zhou } 277a7a80296SCarson Labrado 2787fb33566SCarson Labrado // Dummy callback used by the Constructor so that it can report the number 2797fb33566SCarson Labrado // of satellite configs when the class is first created 2807fb33566SCarson Labrado static void constructorCallback( 2818b2521a5SCarson Labrado const boost::system::error_code& ec, 2827fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 2837fb33566SCarson Labrado { 2847fb33566SCarson Labrado if (ec) 2857fb33566SCarson Labrado { 2868b2521a5SCarson Labrado BMCWEB_LOG_ERROR << "Something went wrong while querying dbus!"; 2877fb33566SCarson Labrado return; 2887fb33566SCarson Labrado } 2897fb33566SCarson Labrado 2908b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "There were " 2917fb33566SCarson Labrado << std::to_string(satelliteInfo.size()) 2928b2521a5SCarson Labrado << " satellite configs found at startup"; 2937fb33566SCarson Labrado } 2947fb33566SCarson Labrado 2957fb33566SCarson Labrado // Search D-Bus objects for satellite config objects and add their 2967fb33566SCarson Labrado // information if valid 2977fb33566SCarson Labrado static void findSatelliteConfigs( 2987fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects, 2997fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 3007fb33566SCarson Labrado { 3017fb33566SCarson Labrado for (const auto& objectPath : objects) 3027fb33566SCarson Labrado { 3037fb33566SCarson Labrado for (const auto& interface : objectPath.second) 3047fb33566SCarson Labrado { 3057fb33566SCarson Labrado if (interface.first == 3067fb33566SCarson Labrado "xyz.openbmc_project.Configuration.SatelliteController") 3077fb33566SCarson Labrado { 3087fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Found Satellite Controller at " 3097fb33566SCarson Labrado << objectPath.first.str; 3107fb33566SCarson Labrado 31105916cefSCarson Labrado if (!satelliteInfo.empty()) 31205916cefSCarson Labrado { 31305916cefSCarson Labrado BMCWEB_LOG_ERROR 31405916cefSCarson Labrado << "Redfish Aggregation only supports one satellite!"; 31505916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Clearing all satellite data"; 31605916cefSCarson Labrado satelliteInfo.clear(); 31705916cefSCarson Labrado return; 31805916cefSCarson Labrado } 31905916cefSCarson Labrado 32005916cefSCarson Labrado // For now assume there will only be one satellite config. 32105916cefSCarson Labrado // Assign it the name/prefix "5B247A" 32205916cefSCarson Labrado addSatelliteConfig("5B247A", interface.second, 32305916cefSCarson Labrado satelliteInfo); 3247fb33566SCarson Labrado } 3257fb33566SCarson Labrado } 3267fb33566SCarson Labrado } 3277fb33566SCarson Labrado } 3287fb33566SCarson Labrado 3297fb33566SCarson Labrado // Parse the properties of a satellite config object and add the 3307fb33566SCarson Labrado // configuration if the properties are valid 3317fb33566SCarson Labrado static void addSatelliteConfig( 33205916cefSCarson Labrado const std::string& name, 3337fb33566SCarson Labrado const dbus::utility::DBusPropertiesMap& properties, 3347fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 3357fb33566SCarson Labrado { 3367fb33566SCarson Labrado boost::urls::url url; 3377fb33566SCarson Labrado 3387fb33566SCarson Labrado for (const auto& prop : properties) 3397fb33566SCarson Labrado { 34005916cefSCarson Labrado if (prop.first == "Hostname") 3417fb33566SCarson Labrado { 3427fb33566SCarson Labrado const std::string* propVal = 3437fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 3447fb33566SCarson Labrado if (propVal == nullptr) 3457fb33566SCarson Labrado { 3467fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Hostname value"; 3477fb33566SCarson Labrado return; 3487fb33566SCarson Labrado } 3497fb33566SCarson Labrado url.set_host(*propVal); 3507fb33566SCarson Labrado } 3517fb33566SCarson Labrado 3527fb33566SCarson Labrado else if (prop.first == "Port") 3537fb33566SCarson Labrado { 3547fb33566SCarson Labrado const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 3557fb33566SCarson Labrado if (propVal == nullptr) 3567fb33566SCarson Labrado { 3577fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Port value"; 3587fb33566SCarson Labrado return; 3597fb33566SCarson Labrado } 3607fb33566SCarson Labrado 3617fb33566SCarson Labrado if (*propVal > std::numeric_limits<uint16_t>::max()) 3627fb33566SCarson Labrado { 3637fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Port value out of range"; 3647fb33566SCarson Labrado return; 3657fb33566SCarson Labrado } 366079360aeSEd Tanous url.set_port(std::to_string(static_cast<uint16_t>(*propVal))); 3677fb33566SCarson Labrado } 3687fb33566SCarson Labrado 3697fb33566SCarson Labrado else if (prop.first == "AuthType") 3707fb33566SCarson Labrado { 3717fb33566SCarson Labrado const std::string* propVal = 3727fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 3737fb33566SCarson Labrado if (propVal == nullptr) 3747fb33566SCarson Labrado { 3757fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid AuthType value"; 3767fb33566SCarson Labrado return; 3777fb33566SCarson Labrado } 3787fb33566SCarson Labrado 3797fb33566SCarson Labrado // For now assume authentication not required to communicate 3807fb33566SCarson Labrado // with the satellite BMC 3817fb33566SCarson Labrado if (*propVal != "None") 3827fb33566SCarson Labrado { 3837fb33566SCarson Labrado BMCWEB_LOG_ERROR 3847fb33566SCarson Labrado << "Unsupported AuthType value: " << *propVal 3857fb33566SCarson Labrado << ", only \"none\" is supported"; 3867fb33566SCarson Labrado return; 3877fb33566SCarson Labrado } 3887fb33566SCarson Labrado url.set_scheme("http"); 3897fb33566SCarson Labrado } 3907fb33566SCarson Labrado } // Finished reading properties 3917fb33566SCarson Labrado 3927fb33566SCarson Labrado // Make sure all required config information was made available 3937fb33566SCarson Labrado if (url.host().empty()) 3947fb33566SCarson Labrado { 3957fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; 3967fb33566SCarson Labrado return; 3977fb33566SCarson Labrado } 3987fb33566SCarson Labrado 3997fb33566SCarson Labrado if (!url.has_port()) 4007fb33566SCarson Labrado { 4017fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; 4027fb33566SCarson Labrado return; 4037fb33566SCarson Labrado } 4047fb33566SCarson Labrado 4057fb33566SCarson Labrado if (!url.has_scheme()) 4067fb33566SCarson Labrado { 4077fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name 4087fb33566SCarson Labrado << " missing AuthType"; 4097fb33566SCarson Labrado return; 4107fb33566SCarson Labrado } 4117fb33566SCarson Labrado 4127fb33566SCarson Labrado std::string resultString; 4137fb33566SCarson Labrado auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 4147fb33566SCarson Labrado if (result.second) 4157fb33566SCarson Labrado { 4167fb33566SCarson Labrado resultString = "Added new satellite config "; 4177fb33566SCarson Labrado } 4187fb33566SCarson Labrado else 4197fb33566SCarson Labrado { 4207fb33566SCarson Labrado resultString = "Updated existing satellite config "; 4217fb33566SCarson Labrado } 4227fb33566SCarson Labrado 4237fb33566SCarson Labrado BMCWEB_LOG_DEBUG << resultString << name << " at " 4247fb33566SCarson Labrado << result.first->second.scheme() << "://" 4257fb33566SCarson Labrado << result.first->second.encoded_host_and_port(); 4267fb33566SCarson Labrado } 4277fb33566SCarson Labrado 42846a81465SCarson Labrado enum AggregationType 42946a81465SCarson Labrado { 43046a81465SCarson Labrado Collection, 43146a81465SCarson Labrado Resource, 43246a81465SCarson Labrado }; 43346a81465SCarson Labrado 43446a81465SCarson Labrado static void 43546a81465SCarson Labrado startAggregation(AggregationType isCollection, 43646a81465SCarson Labrado const crow::Request& thisReq, 43746a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 43846a81465SCarson Labrado { 439db18fc98SCarson Labrado if ((isCollection == AggregationType::Collection) && 440db18fc98SCarson Labrado (thisReq.method() != boost::beast::http::verb::get)) 441db18fc98SCarson Labrado { 442db18fc98SCarson Labrado BMCWEB_LOG_DEBUG 443db18fc98SCarson Labrado << "Only aggregate GET requests to top level collections"; 444db18fc98SCarson Labrado return; 445db18fc98SCarson Labrado } 446db18fc98SCarson Labrado 44746a81465SCarson Labrado // Create a copy of thisReq so we we can still locally process the req 44846a81465SCarson Labrado std::error_code ec; 44946a81465SCarson Labrado auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 45046a81465SCarson Labrado if (ec) 45146a81465SCarson Labrado { 45246a81465SCarson Labrado BMCWEB_LOG_ERROR << "Failed to create copy of request"; 45346a81465SCarson Labrado if (isCollection != AggregationType::Collection) 45446a81465SCarson Labrado { 45546a81465SCarson Labrado messages::internalError(asyncResp->res); 45646a81465SCarson Labrado } 45746a81465SCarson Labrado return; 45846a81465SCarson Labrado } 45946a81465SCarson Labrado 46046a81465SCarson Labrado getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection, 46146a81465SCarson Labrado localReq, asyncResp)); 46246a81465SCarson Labrado } 46346a81465SCarson Labrado 464db18fc98SCarson Labrado static void findSatellite( 46546a81465SCarson Labrado const crow::Request& req, 46646a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 46746a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 46846a81465SCarson Labrado std::string_view memberName) 46946a81465SCarson Labrado { 47046a81465SCarson Labrado // Determine if the resource ID begins with a known prefix 47146a81465SCarson Labrado for (const auto& satellite : satelliteInfo) 47246a81465SCarson Labrado { 47346a81465SCarson Labrado std::string targetPrefix = satellite.first; 47446a81465SCarson Labrado targetPrefix += "_"; 47546a81465SCarson Labrado if (memberName.starts_with(targetPrefix)) 47646a81465SCarson Labrado { 47746a81465SCarson Labrado BMCWEB_LOG_DEBUG << "\"" << satellite.first 47846a81465SCarson Labrado << "\" is a known prefix"; 47946a81465SCarson Labrado 48046a81465SCarson Labrado // Remove the known prefix from the request's URI and 48146a81465SCarson Labrado // then forward to the associated satellite BMC 48246a81465SCarson Labrado getInstance().forwardRequest(req, asyncResp, satellite.first, 48346a81465SCarson Labrado satelliteInfo); 48446a81465SCarson Labrado return; 48546a81465SCarson Labrado } 48646a81465SCarson Labrado } 487db18fc98SCarson Labrado 488db18fc98SCarson Labrado // We didn't recognize the prefix and need to return a 404 48939662a3bSEd Tanous std::string nameStr = req.url().segments().back(); 490db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 49146a81465SCarson Labrado } 49246a81465SCarson Labrado 49346a81465SCarson Labrado // Intended to handle an incoming request based on if Redfish Aggregation 49446a81465SCarson Labrado // is enabled. Forwards request to satellite BMC if it exists. 49546a81465SCarson Labrado static void aggregateAndHandle( 49646a81465SCarson Labrado AggregationType isCollection, 49746a81465SCarson Labrado const std::shared_ptr<crow::Request>& sharedReq, 49846a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4998b2521a5SCarson Labrado const boost::system::error_code& ec, 50046a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 50146a81465SCarson Labrado { 50246a81465SCarson Labrado if (sharedReq == nullptr) 50346a81465SCarson Labrado { 50446a81465SCarson Labrado return; 50546a81465SCarson Labrado } 5068b2521a5SCarson Labrado // Something went wrong while querying dbus 5078b2521a5SCarson Labrado if (ec) 5088b2521a5SCarson Labrado { 5098b2521a5SCarson Labrado messages::internalError(asyncResp->res); 5108b2521a5SCarson Labrado return; 5118b2521a5SCarson Labrado } 512db18fc98SCarson Labrado 513db18fc98SCarson Labrado // No satellite configs means we don't need to keep attempting to 514db18fc98SCarson Labrado // aggregate 515db18fc98SCarson Labrado if (satelliteInfo.empty()) 516db18fc98SCarson Labrado { 517db18fc98SCarson Labrado // For collections we'll also handle the request locally so we 518db18fc98SCarson Labrado // don't need to write an error code 519db18fc98SCarson Labrado if (isCollection == AggregationType::Resource) 520db18fc98SCarson Labrado { 52139662a3bSEd Tanous std::string nameStr = sharedReq->url().segments().back(); 522db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 523db18fc98SCarson Labrado } 524db18fc98SCarson Labrado return; 525db18fc98SCarson Labrado } 526db18fc98SCarson Labrado 52746a81465SCarson Labrado const crow::Request& thisReq = *sharedReq; 52846a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " 52946a81465SCarson Labrado << thisReq.target(); 53046a81465SCarson Labrado 53146a81465SCarson Labrado // We previously determined the request is for a collection. No need to 53246a81465SCarson Labrado // check again 53346a81465SCarson Labrado if (isCollection == AggregationType::Collection) 53446a81465SCarson Labrado { 53546a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregating a collection"; 5364c30e226SCarson Labrado // We need to use a specific response handler and send the 5374c30e226SCarson Labrado // request to all known satellites 5384c30e226SCarson Labrado getInstance().forwardCollectionRequests(thisReq, asyncResp, 5394c30e226SCarson Labrado satelliteInfo); 54046a81465SCarson Labrado return; 54146a81465SCarson Labrado } 54246a81465SCarson Labrado 54339662a3bSEd Tanous const boost::urls::segments_view urlSegments = thisReq.url().segments(); 5447c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 5457c4c52cbSCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 5467c4c52cbSCarson Labrado const boost::urls::segments_view::const_iterator end = 5477c4c52cbSCarson Labrado urlSegments.end(); 5487c4c52cbSCarson Labrado 5497c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 5507c4c52cbSCarson Labrado it++; 5517c4c52cbSCarson Labrado it++; 5527c4c52cbSCarson Labrado for (; it != end; it++) 55346a81465SCarson Labrado { 5547c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 5557c4c52cbSCarson Labrado currentUrl.buffer())) 5567c4c52cbSCarson Labrado { 5577c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 5587c4c52cbSCarson Labrado // must contain an aggregation prefix 5597c4c52cbSCarson Labrado findSatellite(thisReq, asyncResp, satelliteInfo, *it); 56046a81465SCarson Labrado return; 56146a81465SCarson Labrado } 56246a81465SCarson Labrado 5637c4c52cbSCarson Labrado currentUrl.segments().push_back(*it); 56446a81465SCarson Labrado } 565db18fc98SCarson Labrado 566db18fc98SCarson Labrado // We shouldn't reach this point since we should've hit one of the 567db18fc98SCarson Labrado // previous exits 568db18fc98SCarson Labrado messages::internalError(asyncResp->res); 56946a81465SCarson Labrado } 57046a81465SCarson Labrado 57146a81465SCarson Labrado // Attempt to forward a request to the satellite BMC associated with the 57246a81465SCarson Labrado // prefix. 57346a81465SCarson Labrado void forwardRequest( 57446a81465SCarson Labrado const crow::Request& thisReq, 57546a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 57646a81465SCarson Labrado const std::string& prefix, 57746a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 57846a81465SCarson Labrado { 57946a81465SCarson Labrado const auto& sat = satelliteInfo.find(prefix); 58046a81465SCarson Labrado if (sat == satelliteInfo.end()) 58146a81465SCarson Labrado { 58246a81465SCarson Labrado // Realistically this shouldn't get called since we perform an 58346a81465SCarson Labrado // earlier check to make sure the prefix exists 58446a81465SCarson Labrado BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix 58546a81465SCarson Labrado << "\""; 58646a81465SCarson Labrado return; 58746a81465SCarson Labrado } 58846a81465SCarson Labrado 58946a81465SCarson Labrado // We need to strip the prefix from the request's path 59046a81465SCarson Labrado std::string targetURI(thisReq.target()); 59146a81465SCarson Labrado size_t pos = targetURI.find(prefix + "_"); 59246a81465SCarson Labrado if (pos == std::string::npos) 59346a81465SCarson Labrado { 59446a81465SCarson Labrado // If this fails then something went wrong 59546a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix 59646a81465SCarson Labrado << "_\" from request URI"; 59746a81465SCarson Labrado messages::internalError(asyncResp->res); 59846a81465SCarson Labrado return; 59946a81465SCarson Labrado } 60046a81465SCarson Labrado targetURI.erase(pos, prefix.size() + 1); 60146a81465SCarson Labrado 60246a81465SCarson Labrado std::function<void(crow::Response&)> cb = 6031c0bb5c6SCarson Labrado std::bind_front(processResponse, prefix, asyncResp); 60446a81465SCarson Labrado 60546a81465SCarson Labrado std::string data = thisReq.req.body(); 606d14a48ffSCarson Labrado client.sendDataWithCallback(data, std::string(sat->second.host()), 607d14a48ffSCarson Labrado sat->second.port_number(), targetURI, 608d14a48ffSCarson Labrado false /*useSSL*/, thisReq.fields(), 609d14a48ffSCarson Labrado thisReq.method(), cb); 61046a81465SCarson Labrado } 61146a81465SCarson Labrado 6124c30e226SCarson Labrado // Forward a request for a collection URI to each known satellite BMC 6134c30e226SCarson Labrado void forwardCollectionRequests( 6144c30e226SCarson Labrado const crow::Request& thisReq, 6154c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 6164c30e226SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 6174c30e226SCarson Labrado { 6184c30e226SCarson Labrado for (const auto& sat : satelliteInfo) 6194c30e226SCarson Labrado { 6204c30e226SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 6214c30e226SCarson Labrado processCollectionResponse, sat.first, asyncResp); 6224c30e226SCarson Labrado 6234c30e226SCarson Labrado std::string targetURI(thisReq.target()); 6244c30e226SCarson Labrado std::string data = thisReq.req.body(); 625d14a48ffSCarson Labrado client.sendDataWithCallback(data, std::string(sat.second.host()), 626d14a48ffSCarson Labrado sat.second.port_number(), targetURI, 627d14a48ffSCarson Labrado false /*useSSL*/, thisReq.fields(), 628d14a48ffSCarson Labrado thisReq.method(), cb); 6294c30e226SCarson Labrado } 6304c30e226SCarson Labrado } 6314c30e226SCarson Labrado 63232d7d8ebSCarson Labrado public: 63332d7d8ebSCarson Labrado RedfishAggregator(const RedfishAggregator&) = delete; 63432d7d8ebSCarson Labrado RedfishAggregator& operator=(const RedfishAggregator&) = delete; 63532d7d8ebSCarson Labrado RedfishAggregator(RedfishAggregator&&) = delete; 63632d7d8ebSCarson Labrado RedfishAggregator& operator=(RedfishAggregator&&) = delete; 63732d7d8ebSCarson Labrado ~RedfishAggregator() = default; 63832d7d8ebSCarson Labrado 63932d7d8ebSCarson Labrado static RedfishAggregator& getInstance() 64032d7d8ebSCarson Labrado { 64132d7d8ebSCarson Labrado static RedfishAggregator handler; 64232d7d8ebSCarson Labrado return handler; 64332d7d8ebSCarson Labrado } 64432d7d8ebSCarson Labrado 6458b2521a5SCarson Labrado // Polls D-Bus to get all available satellite config information 6468b2521a5SCarson Labrado // Expects a handler which interacts with the returned configs 6478b2521a5SCarson Labrado static void getSatelliteConfigs( 6488b2521a5SCarson Labrado std::function< 6498b2521a5SCarson Labrado void(const boost::system::error_code&, 6508b2521a5SCarson Labrado const std::unordered_map<std::string, boost::urls::url>&)> 6518b2521a5SCarson Labrado handler) 6528b2521a5SCarson Labrado { 6538b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "Gathering satellite configs"; 6548b2521a5SCarson Labrado crow::connections::systemBus->async_method_call( 6558b2521a5SCarson Labrado [handler{std::move(handler)}]( 6568b2521a5SCarson Labrado const boost::system::error_code& ec, 6578b2521a5SCarson Labrado const dbus::utility::ManagedObjectType& objects) { 6588b2521a5SCarson Labrado std::unordered_map<std::string, boost::urls::url> satelliteInfo; 6598b2521a5SCarson Labrado if (ec) 6608b2521a5SCarson Labrado { 6618b2521a5SCarson Labrado BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", " 6628b2521a5SCarson Labrado << ec.message(); 6638b2521a5SCarson Labrado handler(ec, satelliteInfo); 6648b2521a5SCarson Labrado return; 6658b2521a5SCarson Labrado } 6668b2521a5SCarson Labrado 6678b2521a5SCarson Labrado // Maps a chosen alias representing a satellite BMC to a url 6688b2521a5SCarson Labrado // containing the information required to create a http 6698b2521a5SCarson Labrado // connection to the satellite 6708b2521a5SCarson Labrado findSatelliteConfigs(objects, satelliteInfo); 6718b2521a5SCarson Labrado 6728b2521a5SCarson Labrado if (!satelliteInfo.empty()) 6738b2521a5SCarson Labrado { 6748b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " 6758b2521a5SCarson Labrado << std::to_string(satelliteInfo.size()) 6768b2521a5SCarson Labrado << " satellite BMCs"; 6778b2521a5SCarson Labrado } 6788b2521a5SCarson Labrado else 6798b2521a5SCarson Labrado { 6808b2521a5SCarson Labrado BMCWEB_LOG_DEBUG 6818b2521a5SCarson Labrado << "No satellite BMCs detected. Redfish Aggregation not enabled"; 6828b2521a5SCarson Labrado } 6838b2521a5SCarson Labrado handler(ec, satelliteInfo); 6848b2521a5SCarson Labrado }, 6858b2521a5SCarson Labrado "xyz.openbmc_project.EntityManager", 6868b2521a5SCarson Labrado "/xyz/openbmc_project/inventory", 6878b2521a5SCarson Labrado "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 6888b2521a5SCarson Labrado } 6898b2521a5SCarson Labrado 69046a81465SCarson Labrado // Processes the response returned by a satellite BMC and loads its 69146a81465SCarson Labrado // contents into asyncResp 69246a81465SCarson Labrado static void 6931c0bb5c6SCarson Labrado processResponse(std::string_view prefix, 6941c0bb5c6SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 69546a81465SCarson Labrado crow::Response& resp) 69646a81465SCarson Labrado { 69743e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 69843e14d38SCarson Labrado // overwrite the response headers in that case 69943e14d38SCarson Labrado if ((resp.resultInt() == 429) || (resp.resultInt() == 502)) 70043e14d38SCarson Labrado { 70143e14d38SCarson Labrado asyncResp->res.result(resp.result()); 70243e14d38SCarson Labrado return; 70343e14d38SCarson Labrado } 70443e14d38SCarson Labrado 70532d7d8ebSCarson Labrado // We want to attempt prefix fixing regardless of response code 70646a81465SCarson Labrado // The resp will not have a json component 70746a81465SCarson Labrado // We need to create a json from resp's stringResponse 70846a81465SCarson Labrado if (resp.getHeaderValue("Content-Type") == "application/json") 70946a81465SCarson Labrado { 71046a81465SCarson Labrado nlohmann::json jsonVal = 71146a81465SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 71246a81465SCarson Labrado if (jsonVal.is_discarded()) 71346a81465SCarson Labrado { 71446a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 71546a81465SCarson Labrado messages::operationFailed(asyncResp->res); 71646a81465SCarson Labrado return; 71746a81465SCarson Labrado } 71846a81465SCarson Labrado 71946a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 72046a81465SCarson Labrado 7211c0bb5c6SCarson Labrado addPrefixes(jsonVal, prefix); 7221c0bb5c6SCarson Labrado 7231c0bb5c6SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 7241c0bb5c6SCarson Labrado 72546a81465SCarson Labrado asyncResp->res.result(resp.result()); 72646a81465SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 72746a81465SCarson Labrado 72846a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; 72946a81465SCarson Labrado } 73046a81465SCarson Labrado else 73146a81465SCarson Labrado { 7320af78d5aSKhang Kieu // We allow any Content-Type that is not "application/json" now 7330af78d5aSKhang Kieu asyncResp->res.result(resp.result()); 7340af78d5aSKhang Kieu asyncResp->res.write(resp.body()); 73546a81465SCarson Labrado } 7360af78d5aSKhang Kieu addAggregatedHeaders(asyncResp->res, resp, prefix); 73746a81465SCarson Labrado } 73846a81465SCarson Labrado 7394c30e226SCarson Labrado // Processes the collection response returned by a satellite BMC and merges 7404c30e226SCarson Labrado // its "@odata.id" values 7414c30e226SCarson Labrado static void processCollectionResponse( 7424c30e226SCarson Labrado const std::string& prefix, 7434c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 7444c30e226SCarson Labrado crow::Response& resp) 7454c30e226SCarson Labrado { 74643e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 74743e14d38SCarson Labrado // overwrite the response headers in that case 74843e14d38SCarson Labrado if ((resp.resultInt() == 429) || (resp.resultInt() == 502)) 74943e14d38SCarson Labrado { 75043e14d38SCarson Labrado return; 75143e14d38SCarson Labrado } 75243e14d38SCarson Labrado 7534c30e226SCarson Labrado if (resp.resultInt() != 200) 7544c30e226SCarson Labrado { 7554c30e226SCarson Labrado BMCWEB_LOG_DEBUG 7564c30e226SCarson Labrado << "Collection resource does not exist in satellite BMC \"" 7574c30e226SCarson Labrado << prefix << "\""; 7584c30e226SCarson Labrado // Return the error if we haven't had any successes 7594c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 7604c30e226SCarson Labrado { 7614c30e226SCarson Labrado asyncResp->res.stringResponse = std::move(resp.stringResponse); 7624c30e226SCarson Labrado } 7634c30e226SCarson Labrado return; 7644c30e226SCarson Labrado } 7654c30e226SCarson Labrado 7664c30e226SCarson Labrado // The resp will not have a json component 7674c30e226SCarson Labrado // We need to create a json from resp's stringResponse 7684c30e226SCarson Labrado if (resp.getHeaderValue("Content-Type") == "application/json") 7694c30e226SCarson Labrado { 7704c30e226SCarson Labrado nlohmann::json jsonVal = 7714c30e226SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 7724c30e226SCarson Labrado if (jsonVal.is_discarded()) 7734c30e226SCarson Labrado { 7744c30e226SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 7754c30e226SCarson Labrado 7764c30e226SCarson Labrado // Notify the user if doing so won't overwrite a valid response 7774c30e226SCarson Labrado if ((asyncResp->res.resultInt() != 200) && 77843e14d38SCarson Labrado (asyncResp->res.resultInt() != 429) && 7794c30e226SCarson Labrado (asyncResp->res.resultInt() != 502)) 7804c30e226SCarson Labrado { 7814c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 7824c30e226SCarson Labrado } 7834c30e226SCarson Labrado return; 7844c30e226SCarson Labrado } 7854c30e226SCarson Labrado 7864c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 7874c30e226SCarson Labrado 7884c30e226SCarson Labrado // Now we need to add the prefix to the URIs contained in the 7894c30e226SCarson Labrado // response. 7904c30e226SCarson Labrado addPrefixes(jsonVal, prefix); 7914c30e226SCarson Labrado 7924c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 7934c30e226SCarson Labrado 7944c30e226SCarson Labrado // If this resource collection does not exist on the aggregating bmc 7954c30e226SCarson Labrado // and has not already been added from processing the response from 7964c30e226SCarson Labrado // a different satellite then we need to completely overwrite 7974c30e226SCarson Labrado // asyncResp 7984c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 7994c30e226SCarson Labrado { 8004c30e226SCarson Labrado // We only want to aggregate collections that contain a 8014c30e226SCarson Labrado // "Members" array 8024c30e226SCarson Labrado if ((!jsonVal.contains("Members")) && 8034c30e226SCarson Labrado (!jsonVal["Members"].is_array())) 8044c30e226SCarson Labrado { 8054c30e226SCarson Labrado BMCWEB_LOG_DEBUG 8064c30e226SCarson Labrado << "Skipping aggregating unsupported resource"; 8074c30e226SCarson Labrado return; 8084c30e226SCarson Labrado } 8094c30e226SCarson Labrado 8104c30e226SCarson Labrado BMCWEB_LOG_DEBUG 8114c30e226SCarson Labrado << "Collection does not exist, overwriting asyncResp"; 8124c30e226SCarson Labrado asyncResp->res.result(resp.result()); 8134c30e226SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 81443e14d38SCarson Labrado asyncResp->res.addHeader("Content-Type", "application/json"); 8154c30e226SCarson Labrado 8164c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; 8174c30e226SCarson Labrado } 8184c30e226SCarson Labrado else 8194c30e226SCarson Labrado { 8204c30e226SCarson Labrado // We only want to aggregate collections that contain a 8214c30e226SCarson Labrado // "Members" array 8224c30e226SCarson Labrado if ((!asyncResp->res.jsonValue.contains("Members")) && 8234c30e226SCarson Labrado (!asyncResp->res.jsonValue["Members"].is_array())) 8244c30e226SCarson Labrado 8254c30e226SCarson Labrado { 8264c30e226SCarson Labrado BMCWEB_LOG_DEBUG 8274c30e226SCarson Labrado << "Skipping aggregating unsupported resource"; 8284c30e226SCarson Labrado return; 8294c30e226SCarson Labrado } 8304c30e226SCarson Labrado 8314c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" 8324c30e226SCarson Labrado << prefix << "\" to collection"; 8334c30e226SCarson Labrado 8344c30e226SCarson Labrado // TODO: This is a potential race condition with multiple 8354c30e226SCarson Labrado // satellites and the aggregating bmc attempting to write to 8364c30e226SCarson Labrado // update this array. May need to cascade calls to the next 8374c30e226SCarson Labrado // satellite at the end of this function. 8384c30e226SCarson Labrado // This is presumably not a concern when there is only a single 8394c30e226SCarson Labrado // satellite since the aggregating bmc should have completed 8404c30e226SCarson Labrado // before the response is received from the satellite. 8414c30e226SCarson Labrado 8424c30e226SCarson Labrado auto& members = asyncResp->res.jsonValue["Members"]; 8434c30e226SCarson Labrado auto& satMembers = jsonVal["Members"]; 8444c30e226SCarson Labrado for (auto& satMem : satMembers) 8454c30e226SCarson Labrado { 8464c30e226SCarson Labrado members.push_back(std::move(satMem)); 8474c30e226SCarson Labrado } 8484c30e226SCarson Labrado asyncResp->res.jsonValue["Members@odata.count"] = 8494c30e226SCarson Labrado members.size(); 8504c30e226SCarson Labrado 8514c30e226SCarson Labrado // TODO: Do we need to sort() after updating the array? 8524c30e226SCarson Labrado } 8534c30e226SCarson Labrado } 8544c30e226SCarson Labrado else 8554c30e226SCarson Labrado { 8564c30e226SCarson Labrado BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 8574c30e226SCarson Labrado << "\""; 85843e14d38SCarson Labrado // We received a response that was not a json. 8594c30e226SCarson Labrado // Notify the user only if we did not receive any valid responses, 8604c30e226SCarson Labrado // if the resource collection does not already exist on the 8614c30e226SCarson Labrado // aggregating BMC, and if we did not already set this warning due 8624c30e226SCarson Labrado // to a failure from a different satellite 8634c30e226SCarson Labrado if ((asyncResp->res.resultInt() != 200) && 86443e14d38SCarson Labrado (asyncResp->res.resultInt() != 429) && 8654c30e226SCarson Labrado (asyncResp->res.resultInt() != 502)) 8664c30e226SCarson Labrado { 8674c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 8684c30e226SCarson Labrado } 8694c30e226SCarson Labrado } 8704c30e226SCarson Labrado } // End processCollectionResponse() 8714c30e226SCarson Labrado 87205916cefSCarson Labrado // Entry point to Redfish Aggregation 87305916cefSCarson Labrado // Returns Result stating whether or not we still need to locally handle the 87405916cefSCarson Labrado // request 87505916cefSCarson Labrado static Result 87605916cefSCarson Labrado beginAggregation(const crow::Request& thisReq, 87705916cefSCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 87805916cefSCarson Labrado { 87905916cefSCarson Labrado using crow::utility::OrMorePaths; 88005916cefSCarson Labrado using crow::utility::readUrlSegments; 88139662a3bSEd Tanous const boost::urls::url_view url = thisReq.url(); 882411e6a11SCarson Labrado 883411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such 884411e6a11SCarson Labrado // as version mismatches between aggregator and satellite BMCs. For 885411e6a11SCarson Labrado // now assume that the aggregator has all the schemas and versions that 886411e6a11SCarson Labrado // the aggregated server has. 887411e6a11SCarson Labrado if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", 888411e6a11SCarson Labrado crow::utility::OrMorePaths())) 889411e6a11SCarson Labrado { 890411e6a11SCarson Labrado return Result::LocalHandle; 891411e6a11SCarson Labrado } 892411e6a11SCarson Labrado 8937c4c52cbSCarson Labrado // The first two segments should be "/redfish/v1". We need to check 8947c4c52cbSCarson Labrado // that before we can search topCollections 8957c4c52cbSCarson Labrado if (!crow::utility::readUrlSegments(url, "redfish", "v1", 8967c4c52cbSCarson Labrado crow::utility::OrMorePaths())) 89746a81465SCarson Labrado { 89846a81465SCarson Labrado return Result::LocalHandle; 89946a81465SCarson Labrado } 90005916cefSCarson Labrado 9017c4c52cbSCarson Labrado // Parse the URI to see if it begins with a known top level collection 9027c4c52cbSCarson Labrado // such as: 9037c4c52cbSCarson Labrado // /redfish/v1/Chassis 9047c4c52cbSCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory 9057c4c52cbSCarson Labrado const boost::urls::segments_view urlSegments = url.segments(); 9067c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 9077c4c52cbSCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 9087c4c52cbSCarson Labrado const boost::urls::segments_view::const_iterator end = 9097c4c52cbSCarson Labrado urlSegments.end(); 91005916cefSCarson Labrado 9117c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 9127c4c52cbSCarson Labrado it++; 9137c4c52cbSCarson Labrado it++; 9147c4c52cbSCarson Labrado for (; it != end; it++) 91505916cefSCarson Labrado { 916d4413c5bSGeorge Liu const std::string& collectionItem = *it; 9177c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 9187c4c52cbSCarson Labrado currentUrl.buffer())) 9197c4c52cbSCarson Labrado { 9207c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 9217c4c52cbSCarson Labrado // might contain an aggregation prefix 9228b2521a5SCarson Labrado // TODO: This needs to be rethought when we can support multiple 9238b2521a5SCarson Labrado // satellites due to 9248b2521a5SCarson Labrado // /redfish/v1/AggregationService/AggregationSources/5B247A 9258b2521a5SCarson Labrado // being a local resource describing the satellite 9268b2521a5SCarson Labrado if (collectionItem.starts_with("5B247A_")) 92705916cefSCarson Labrado { 92805916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Need to forward a request"; 92905916cefSCarson Labrado 93046a81465SCarson Labrado // Extract the prefix from the request's URI, retrieve the 9317c4c52cbSCarson Labrado // associated satellite config information, and then forward 9327c4c52cbSCarson Labrado // the request to that satellite. 9337c4c52cbSCarson Labrado startAggregation(AggregationType::Resource, thisReq, 9347c4c52cbSCarson Labrado asyncResp); 93505916cefSCarson Labrado return Result::NoLocalHandle; 93605916cefSCarson Labrado } 9377c4c52cbSCarson Labrado 9387c4c52cbSCarson Labrado // Handle collection URI with a trailing backslash 9397c4c52cbSCarson Labrado // e.g. /redfish/v1/Chassis/ 9407c4c52cbSCarson Labrado it++; 9417c4c52cbSCarson Labrado if ((it == end) && collectionItem.empty()) 9427c4c52cbSCarson Labrado { 9437c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, 9447c4c52cbSCarson Labrado asyncResp); 9457c4c52cbSCarson Labrado } 9467c4c52cbSCarson Labrado 9477c4c52cbSCarson Labrado // We didn't recognize the prefix or it's a collection with a 9487c4c52cbSCarson Labrado // trailing "/". In both cases we still want to locally handle 9497c4c52cbSCarson Labrado // the request 9507c4c52cbSCarson Labrado return Result::LocalHandle; 9517c4c52cbSCarson Labrado } 9527c4c52cbSCarson Labrado 9537c4c52cbSCarson Labrado currentUrl.segments().push_back(collectionItem); 9547c4c52cbSCarson Labrado } 9557c4c52cbSCarson Labrado 9567c4c52cbSCarson Labrado // If we made it here then currentUrl could contain a top level 9577c4c52cbSCarson Labrado // collection URI without a trailing "/", e.g. /redfish/v1/Chassis 9587c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 9597c4c52cbSCarson Labrado currentUrl.buffer())) 9607c4c52cbSCarson Labrado { 9617c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 96205916cefSCarson Labrado return Result::LocalHandle; 96305916cefSCarson Labrado } 96405916cefSCarson Labrado 96505916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation not required"; 96605916cefSCarson Labrado return Result::LocalHandle; 96705916cefSCarson Labrado } 9687fb33566SCarson Labrado }; 9697fb33566SCarson Labrado 9707fb33566SCarson Labrado } // namespace redfish 971