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 16*d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB 17*d14a48ffSCarson 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 1781c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for 1797e8890c5SCarson Labrado // an aggregated resource. 1800af78d5aSKhang Kieu static inline void addPrefixes(nlohmann::json& json, std::string_view prefix) 1811c0bb5c6SCarson Labrado { 1821c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 1831c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 1841c0bb5c6SCarson Labrado if (object != nullptr) 1851c0bb5c6SCarson Labrado { 1861c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 1871c0bb5c6SCarson Labrado { 1887e8890c5SCarson Labrado if (isPropertyUri(item.first)) 1891c0bb5c6SCarson Labrado { 1907e8890c5SCarson Labrado addPrefixToItem(item.second, prefix); 1911c0bb5c6SCarson Labrado continue; 1921c0bb5c6SCarson Labrado } 1931c0bb5c6SCarson Labrado 1941c0bb5c6SCarson Labrado // Recusively parse the rest of the json 1951c0bb5c6SCarson Labrado addPrefixes(item.second, prefix); 1961c0bb5c6SCarson Labrado } 1971c0bb5c6SCarson Labrado return; 1981c0bb5c6SCarson Labrado } 1991c0bb5c6SCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 2001c0bb5c6SCarson Labrado if (array != nullptr) 2011c0bb5c6SCarson Labrado { 2021c0bb5c6SCarson Labrado for (nlohmann::json& item : *array) 2031c0bb5c6SCarson Labrado { 2041c0bb5c6SCarson Labrado addPrefixes(item, prefix); 2051c0bb5c6SCarson Labrado } 2061c0bb5c6SCarson Labrado } 2071c0bb5c6SCarson Labrado } 2081c0bb5c6SCarson Labrado 209*d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode) 210a7a80296SCarson Labrado { 21132d7d8ebSCarson Labrado // Allow all response codes because we want to surface any satellite 21232d7d8ebSCarson Labrado // issue to the client 213*d14a48ffSCarson Labrado BMCWEB_LOG_DEBUG << "Received " << respCode << " response from satellite"; 214*d14a48ffSCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 215*d14a48ffSCarson Labrado } 216*d14a48ffSCarson Labrado 217*d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy() 218*d14a48ffSCarson Labrado { 219*d14a48ffSCarson Labrado return {.maxRetryAttempts = 1, 220*d14a48ffSCarson Labrado .requestByteLimit = aggregatorReadBodyLimit, 221*d14a48ffSCarson Labrado .maxConnections = 20, 222*d14a48ffSCarson Labrado .retryPolicyAction = "TerminateAfterRetries", 223*d14a48ffSCarson Labrado .retryIntervalSecs = std::chrono::seconds(0), 224*d14a48ffSCarson Labrado .invalidResp = aggregationRetryHandler}; 225*d14a48ffSCarson Labrado } 226*d14a48ffSCarson Labrado 227*d14a48ffSCarson Labrado class RedfishAggregator 228*d14a48ffSCarson Labrado { 229*d14a48ffSCarson Labrado private: 230*d14a48ffSCarson Labrado crow::HttpClient client; 231*d14a48ffSCarson Labrado 232*d14a48ffSCarson Labrado RedfishAggregator() : 233*d14a48ffSCarson Labrado client(std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy())) 234*d14a48ffSCarson Labrado { 235*d14a48ffSCarson Labrado getSatelliteConfigs(constructorCallback); 2369fa6d147SNan Zhou } 237a7a80296SCarson Labrado 2387fb33566SCarson Labrado // Dummy callback used by the Constructor so that it can report the number 2397fb33566SCarson Labrado // of satellite configs when the class is first created 2407fb33566SCarson Labrado static void constructorCallback( 2418b2521a5SCarson Labrado const boost::system::error_code& ec, 2427fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 2437fb33566SCarson Labrado { 2447fb33566SCarson Labrado if (ec) 2457fb33566SCarson Labrado { 2468b2521a5SCarson Labrado BMCWEB_LOG_ERROR << "Something went wrong while querying dbus!"; 2477fb33566SCarson Labrado return; 2487fb33566SCarson Labrado } 2497fb33566SCarson Labrado 2508b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "There were " 2517fb33566SCarson Labrado << std::to_string(satelliteInfo.size()) 2528b2521a5SCarson Labrado << " satellite configs found at startup"; 2537fb33566SCarson Labrado } 2547fb33566SCarson Labrado 2557fb33566SCarson Labrado // Search D-Bus objects for satellite config objects and add their 2567fb33566SCarson Labrado // information if valid 2577fb33566SCarson Labrado static void findSatelliteConfigs( 2587fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects, 2597fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 2607fb33566SCarson Labrado { 2617fb33566SCarson Labrado for (const auto& objectPath : objects) 2627fb33566SCarson Labrado { 2637fb33566SCarson Labrado for (const auto& interface : objectPath.second) 2647fb33566SCarson Labrado { 2657fb33566SCarson Labrado if (interface.first == 2667fb33566SCarson Labrado "xyz.openbmc_project.Configuration.SatelliteController") 2677fb33566SCarson Labrado { 2687fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Found Satellite Controller at " 2697fb33566SCarson Labrado << objectPath.first.str; 2707fb33566SCarson Labrado 27105916cefSCarson Labrado if (!satelliteInfo.empty()) 27205916cefSCarson Labrado { 27305916cefSCarson Labrado BMCWEB_LOG_ERROR 27405916cefSCarson Labrado << "Redfish Aggregation only supports one satellite!"; 27505916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Clearing all satellite data"; 27605916cefSCarson Labrado satelliteInfo.clear(); 27705916cefSCarson Labrado return; 27805916cefSCarson Labrado } 27905916cefSCarson Labrado 28005916cefSCarson Labrado // For now assume there will only be one satellite config. 28105916cefSCarson Labrado // Assign it the name/prefix "5B247A" 28205916cefSCarson Labrado addSatelliteConfig("5B247A", interface.second, 28305916cefSCarson Labrado satelliteInfo); 2847fb33566SCarson Labrado } 2857fb33566SCarson Labrado } 2867fb33566SCarson Labrado } 2877fb33566SCarson Labrado } 2887fb33566SCarson Labrado 2897fb33566SCarson Labrado // Parse the properties of a satellite config object and add the 2907fb33566SCarson Labrado // configuration if the properties are valid 2917fb33566SCarson Labrado static void addSatelliteConfig( 29205916cefSCarson Labrado const std::string& name, 2937fb33566SCarson Labrado const dbus::utility::DBusPropertiesMap& properties, 2947fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 2957fb33566SCarson Labrado { 2967fb33566SCarson Labrado boost::urls::url url; 2977fb33566SCarson Labrado 2987fb33566SCarson Labrado for (const auto& prop : properties) 2997fb33566SCarson Labrado { 30005916cefSCarson Labrado if (prop.first == "Hostname") 3017fb33566SCarson Labrado { 3027fb33566SCarson Labrado const std::string* propVal = 3037fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 3047fb33566SCarson Labrado if (propVal == nullptr) 3057fb33566SCarson Labrado { 3067fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Hostname value"; 3077fb33566SCarson Labrado return; 3087fb33566SCarson Labrado } 3097fb33566SCarson Labrado url.set_host(*propVal); 3107fb33566SCarson Labrado } 3117fb33566SCarson Labrado 3127fb33566SCarson Labrado else if (prop.first == "Port") 3137fb33566SCarson Labrado { 3147fb33566SCarson Labrado const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 3157fb33566SCarson Labrado if (propVal == nullptr) 3167fb33566SCarson Labrado { 3177fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Port value"; 3187fb33566SCarson Labrado return; 3197fb33566SCarson Labrado } 3207fb33566SCarson Labrado 3217fb33566SCarson Labrado if (*propVal > std::numeric_limits<uint16_t>::max()) 3227fb33566SCarson Labrado { 3237fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Port value out of range"; 3247fb33566SCarson Labrado return; 3257fb33566SCarson Labrado } 326079360aeSEd Tanous url.set_port(std::to_string(static_cast<uint16_t>(*propVal))); 3277fb33566SCarson Labrado } 3287fb33566SCarson Labrado 3297fb33566SCarson Labrado else if (prop.first == "AuthType") 3307fb33566SCarson Labrado { 3317fb33566SCarson Labrado const std::string* propVal = 3327fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 3337fb33566SCarson Labrado if (propVal == nullptr) 3347fb33566SCarson Labrado { 3357fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid AuthType value"; 3367fb33566SCarson Labrado return; 3377fb33566SCarson Labrado } 3387fb33566SCarson Labrado 3397fb33566SCarson Labrado // For now assume authentication not required to communicate 3407fb33566SCarson Labrado // with the satellite BMC 3417fb33566SCarson Labrado if (*propVal != "None") 3427fb33566SCarson Labrado { 3437fb33566SCarson Labrado BMCWEB_LOG_ERROR 3447fb33566SCarson Labrado << "Unsupported AuthType value: " << *propVal 3457fb33566SCarson Labrado << ", only \"none\" is supported"; 3467fb33566SCarson Labrado return; 3477fb33566SCarson Labrado } 3487fb33566SCarson Labrado url.set_scheme("http"); 3497fb33566SCarson Labrado } 3507fb33566SCarson Labrado } // Finished reading properties 3517fb33566SCarson Labrado 3527fb33566SCarson Labrado // Make sure all required config information was made available 3537fb33566SCarson Labrado if (url.host().empty()) 3547fb33566SCarson Labrado { 3557fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; 3567fb33566SCarson Labrado return; 3577fb33566SCarson Labrado } 3587fb33566SCarson Labrado 3597fb33566SCarson Labrado if (!url.has_port()) 3607fb33566SCarson Labrado { 3617fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; 3627fb33566SCarson Labrado return; 3637fb33566SCarson Labrado } 3647fb33566SCarson Labrado 3657fb33566SCarson Labrado if (!url.has_scheme()) 3667fb33566SCarson Labrado { 3677fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name 3687fb33566SCarson Labrado << " missing AuthType"; 3697fb33566SCarson Labrado return; 3707fb33566SCarson Labrado } 3717fb33566SCarson Labrado 3727fb33566SCarson Labrado std::string resultString; 3737fb33566SCarson Labrado auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 3747fb33566SCarson Labrado if (result.second) 3757fb33566SCarson Labrado { 3767fb33566SCarson Labrado resultString = "Added new satellite config "; 3777fb33566SCarson Labrado } 3787fb33566SCarson Labrado else 3797fb33566SCarson Labrado { 3807fb33566SCarson Labrado resultString = "Updated existing satellite config "; 3817fb33566SCarson Labrado } 3827fb33566SCarson Labrado 3837fb33566SCarson Labrado BMCWEB_LOG_DEBUG << resultString << name << " at " 3847fb33566SCarson Labrado << result.first->second.scheme() << "://" 3857fb33566SCarson Labrado << result.first->second.encoded_host_and_port(); 3867fb33566SCarson Labrado } 3877fb33566SCarson Labrado 38846a81465SCarson Labrado enum AggregationType 38946a81465SCarson Labrado { 39046a81465SCarson Labrado Collection, 39146a81465SCarson Labrado Resource, 39246a81465SCarson Labrado }; 39346a81465SCarson Labrado 39446a81465SCarson Labrado static void 39546a81465SCarson Labrado startAggregation(AggregationType isCollection, 39646a81465SCarson Labrado const crow::Request& thisReq, 39746a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 39846a81465SCarson Labrado { 399db18fc98SCarson Labrado if ((isCollection == AggregationType::Collection) && 400db18fc98SCarson Labrado (thisReq.method() != boost::beast::http::verb::get)) 401db18fc98SCarson Labrado { 402db18fc98SCarson Labrado BMCWEB_LOG_DEBUG 403db18fc98SCarson Labrado << "Only aggregate GET requests to top level collections"; 404db18fc98SCarson Labrado return; 405db18fc98SCarson Labrado } 406db18fc98SCarson Labrado 40746a81465SCarson Labrado // Create a copy of thisReq so we we can still locally process the req 40846a81465SCarson Labrado std::error_code ec; 40946a81465SCarson Labrado auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 41046a81465SCarson Labrado if (ec) 41146a81465SCarson Labrado { 41246a81465SCarson Labrado BMCWEB_LOG_ERROR << "Failed to create copy of request"; 41346a81465SCarson Labrado if (isCollection != AggregationType::Collection) 41446a81465SCarson Labrado { 41546a81465SCarson Labrado messages::internalError(asyncResp->res); 41646a81465SCarson Labrado } 41746a81465SCarson Labrado return; 41846a81465SCarson Labrado } 41946a81465SCarson Labrado 42046a81465SCarson Labrado getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection, 42146a81465SCarson Labrado localReq, asyncResp)); 42246a81465SCarson Labrado } 42346a81465SCarson Labrado 424db18fc98SCarson Labrado static void findSatellite( 42546a81465SCarson Labrado const crow::Request& req, 42646a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 42746a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 42846a81465SCarson Labrado std::string_view memberName) 42946a81465SCarson Labrado { 43046a81465SCarson Labrado // Determine if the resource ID begins with a known prefix 43146a81465SCarson Labrado for (const auto& satellite : satelliteInfo) 43246a81465SCarson Labrado { 43346a81465SCarson Labrado std::string targetPrefix = satellite.first; 43446a81465SCarson Labrado targetPrefix += "_"; 43546a81465SCarson Labrado if (memberName.starts_with(targetPrefix)) 43646a81465SCarson Labrado { 43746a81465SCarson Labrado BMCWEB_LOG_DEBUG << "\"" << satellite.first 43846a81465SCarson Labrado << "\" is a known prefix"; 43946a81465SCarson Labrado 44046a81465SCarson Labrado // Remove the known prefix from the request's URI and 44146a81465SCarson Labrado // then forward to the associated satellite BMC 44246a81465SCarson Labrado getInstance().forwardRequest(req, asyncResp, satellite.first, 44346a81465SCarson Labrado satelliteInfo); 44446a81465SCarson Labrado return; 44546a81465SCarson Labrado } 44646a81465SCarson Labrado } 447db18fc98SCarson Labrado 448db18fc98SCarson Labrado // We didn't recognize the prefix and need to return a 404 44939662a3bSEd Tanous std::string nameStr = req.url().segments().back(); 450db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 45146a81465SCarson Labrado } 45246a81465SCarson Labrado 45346a81465SCarson Labrado // Intended to handle an incoming request based on if Redfish Aggregation 45446a81465SCarson Labrado // is enabled. Forwards request to satellite BMC if it exists. 45546a81465SCarson Labrado static void aggregateAndHandle( 45646a81465SCarson Labrado AggregationType isCollection, 45746a81465SCarson Labrado const std::shared_ptr<crow::Request>& sharedReq, 45846a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4598b2521a5SCarson Labrado const boost::system::error_code& ec, 46046a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 46146a81465SCarson Labrado { 46246a81465SCarson Labrado if (sharedReq == nullptr) 46346a81465SCarson Labrado { 46446a81465SCarson Labrado return; 46546a81465SCarson Labrado } 4668b2521a5SCarson Labrado // Something went wrong while querying dbus 4678b2521a5SCarson Labrado if (ec) 4688b2521a5SCarson Labrado { 4698b2521a5SCarson Labrado messages::internalError(asyncResp->res); 4708b2521a5SCarson Labrado return; 4718b2521a5SCarson Labrado } 472db18fc98SCarson Labrado 473db18fc98SCarson Labrado // No satellite configs means we don't need to keep attempting to 474db18fc98SCarson Labrado // aggregate 475db18fc98SCarson Labrado if (satelliteInfo.empty()) 476db18fc98SCarson Labrado { 477db18fc98SCarson Labrado // For collections we'll also handle the request locally so we 478db18fc98SCarson Labrado // don't need to write an error code 479db18fc98SCarson Labrado if (isCollection == AggregationType::Resource) 480db18fc98SCarson Labrado { 48139662a3bSEd Tanous std::string nameStr = sharedReq->url().segments().back(); 482db18fc98SCarson Labrado messages::resourceNotFound(asyncResp->res, "", nameStr); 483db18fc98SCarson Labrado } 484db18fc98SCarson Labrado return; 485db18fc98SCarson Labrado } 486db18fc98SCarson Labrado 48746a81465SCarson Labrado const crow::Request& thisReq = *sharedReq; 48846a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " 48946a81465SCarson Labrado << thisReq.target(); 49046a81465SCarson Labrado 49146a81465SCarson Labrado // We previously determined the request is for a collection. No need to 49246a81465SCarson Labrado // check again 49346a81465SCarson Labrado if (isCollection == AggregationType::Collection) 49446a81465SCarson Labrado { 49546a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregating a collection"; 4964c30e226SCarson Labrado // We need to use a specific response handler and send the 4974c30e226SCarson Labrado // request to all known satellites 4984c30e226SCarson Labrado getInstance().forwardCollectionRequests(thisReq, asyncResp, 4994c30e226SCarson Labrado satelliteInfo); 50046a81465SCarson Labrado return; 50146a81465SCarson Labrado } 50246a81465SCarson Labrado 50339662a3bSEd Tanous const boost::urls::segments_view urlSegments = thisReq.url().segments(); 5047c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 5057c4c52cbSCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 5067c4c52cbSCarson Labrado const boost::urls::segments_view::const_iterator end = 5077c4c52cbSCarson Labrado urlSegments.end(); 5087c4c52cbSCarson Labrado 5097c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 5107c4c52cbSCarson Labrado it++; 5117c4c52cbSCarson Labrado it++; 5127c4c52cbSCarson Labrado for (; it != end; it++) 51346a81465SCarson Labrado { 5147c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 5157c4c52cbSCarson Labrado currentUrl.buffer())) 5167c4c52cbSCarson Labrado { 5177c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 5187c4c52cbSCarson Labrado // must contain an aggregation prefix 5197c4c52cbSCarson Labrado findSatellite(thisReq, asyncResp, satelliteInfo, *it); 52046a81465SCarson Labrado return; 52146a81465SCarson Labrado } 52246a81465SCarson Labrado 5237c4c52cbSCarson Labrado currentUrl.segments().push_back(*it); 52446a81465SCarson Labrado } 525db18fc98SCarson Labrado 526db18fc98SCarson Labrado // We shouldn't reach this point since we should've hit one of the 527db18fc98SCarson Labrado // previous exits 528db18fc98SCarson Labrado messages::internalError(asyncResp->res); 52946a81465SCarson Labrado } 53046a81465SCarson Labrado 53146a81465SCarson Labrado // Attempt to forward a request to the satellite BMC associated with the 53246a81465SCarson Labrado // prefix. 53346a81465SCarson Labrado void forwardRequest( 53446a81465SCarson Labrado const crow::Request& thisReq, 53546a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 53646a81465SCarson Labrado const std::string& prefix, 53746a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 53846a81465SCarson Labrado { 53946a81465SCarson Labrado const auto& sat = satelliteInfo.find(prefix); 54046a81465SCarson Labrado if (sat == satelliteInfo.end()) 54146a81465SCarson Labrado { 54246a81465SCarson Labrado // Realistically this shouldn't get called since we perform an 54346a81465SCarson Labrado // earlier check to make sure the prefix exists 54446a81465SCarson Labrado BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix 54546a81465SCarson Labrado << "\""; 54646a81465SCarson Labrado return; 54746a81465SCarson Labrado } 54846a81465SCarson Labrado 54946a81465SCarson Labrado // We need to strip the prefix from the request's path 55046a81465SCarson Labrado std::string targetURI(thisReq.target()); 55146a81465SCarson Labrado size_t pos = targetURI.find(prefix + "_"); 55246a81465SCarson Labrado if (pos == std::string::npos) 55346a81465SCarson Labrado { 55446a81465SCarson Labrado // If this fails then something went wrong 55546a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix 55646a81465SCarson Labrado << "_\" from request URI"; 55746a81465SCarson Labrado messages::internalError(asyncResp->res); 55846a81465SCarson Labrado return; 55946a81465SCarson Labrado } 56046a81465SCarson Labrado targetURI.erase(pos, prefix.size() + 1); 56146a81465SCarson Labrado 56246a81465SCarson Labrado std::function<void(crow::Response&)> cb = 5631c0bb5c6SCarson Labrado std::bind_front(processResponse, prefix, asyncResp); 56446a81465SCarson Labrado 56546a81465SCarson Labrado std::string data = thisReq.req.body(); 566*d14a48ffSCarson Labrado client.sendDataWithCallback(data, std::string(sat->second.host()), 567*d14a48ffSCarson Labrado sat->second.port_number(), targetURI, 568*d14a48ffSCarson Labrado false /*useSSL*/, thisReq.fields(), 569*d14a48ffSCarson Labrado thisReq.method(), cb); 57046a81465SCarson Labrado } 57146a81465SCarson Labrado 5724c30e226SCarson Labrado // Forward a request for a collection URI to each known satellite BMC 5734c30e226SCarson Labrado void forwardCollectionRequests( 5744c30e226SCarson Labrado const crow::Request& thisReq, 5754c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 5764c30e226SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 5774c30e226SCarson Labrado { 5784c30e226SCarson Labrado for (const auto& sat : satelliteInfo) 5794c30e226SCarson Labrado { 5804c30e226SCarson Labrado std::function<void(crow::Response&)> cb = std::bind_front( 5814c30e226SCarson Labrado processCollectionResponse, sat.first, asyncResp); 5824c30e226SCarson Labrado 5834c30e226SCarson Labrado std::string targetURI(thisReq.target()); 5844c30e226SCarson Labrado std::string data = thisReq.req.body(); 585*d14a48ffSCarson Labrado client.sendDataWithCallback(data, std::string(sat.second.host()), 586*d14a48ffSCarson Labrado sat.second.port_number(), targetURI, 587*d14a48ffSCarson Labrado false /*useSSL*/, thisReq.fields(), 588*d14a48ffSCarson Labrado thisReq.method(), cb); 5894c30e226SCarson Labrado } 5904c30e226SCarson Labrado } 5914c30e226SCarson Labrado 59232d7d8ebSCarson Labrado public: 59332d7d8ebSCarson Labrado RedfishAggregator(const RedfishAggregator&) = delete; 59432d7d8ebSCarson Labrado RedfishAggregator& operator=(const RedfishAggregator&) = delete; 59532d7d8ebSCarson Labrado RedfishAggregator(RedfishAggregator&&) = delete; 59632d7d8ebSCarson Labrado RedfishAggregator& operator=(RedfishAggregator&&) = delete; 59732d7d8ebSCarson Labrado ~RedfishAggregator() = default; 59832d7d8ebSCarson Labrado 59932d7d8ebSCarson Labrado static RedfishAggregator& getInstance() 60032d7d8ebSCarson Labrado { 60132d7d8ebSCarson Labrado static RedfishAggregator handler; 60232d7d8ebSCarson Labrado return handler; 60332d7d8ebSCarson Labrado } 60432d7d8ebSCarson Labrado 6058b2521a5SCarson Labrado // Polls D-Bus to get all available satellite config information 6068b2521a5SCarson Labrado // Expects a handler which interacts with the returned configs 6078b2521a5SCarson Labrado static void getSatelliteConfigs( 6088b2521a5SCarson Labrado std::function< 6098b2521a5SCarson Labrado void(const boost::system::error_code&, 6108b2521a5SCarson Labrado const std::unordered_map<std::string, boost::urls::url>&)> 6118b2521a5SCarson Labrado handler) 6128b2521a5SCarson Labrado { 6138b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "Gathering satellite configs"; 6148b2521a5SCarson Labrado crow::connections::systemBus->async_method_call( 6158b2521a5SCarson Labrado [handler{std::move(handler)}]( 6168b2521a5SCarson Labrado const boost::system::error_code& ec, 6178b2521a5SCarson Labrado const dbus::utility::ManagedObjectType& objects) { 6188b2521a5SCarson Labrado std::unordered_map<std::string, boost::urls::url> satelliteInfo; 6198b2521a5SCarson Labrado if (ec) 6208b2521a5SCarson Labrado { 6218b2521a5SCarson Labrado BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", " 6228b2521a5SCarson Labrado << ec.message(); 6238b2521a5SCarson Labrado handler(ec, satelliteInfo); 6248b2521a5SCarson Labrado return; 6258b2521a5SCarson Labrado } 6268b2521a5SCarson Labrado 6278b2521a5SCarson Labrado // Maps a chosen alias representing a satellite BMC to a url 6288b2521a5SCarson Labrado // containing the information required to create a http 6298b2521a5SCarson Labrado // connection to the satellite 6308b2521a5SCarson Labrado findSatelliteConfigs(objects, satelliteInfo); 6318b2521a5SCarson Labrado 6328b2521a5SCarson Labrado if (!satelliteInfo.empty()) 6338b2521a5SCarson Labrado { 6348b2521a5SCarson Labrado BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " 6358b2521a5SCarson Labrado << std::to_string(satelliteInfo.size()) 6368b2521a5SCarson Labrado << " satellite BMCs"; 6378b2521a5SCarson Labrado } 6388b2521a5SCarson Labrado else 6398b2521a5SCarson Labrado { 6408b2521a5SCarson Labrado BMCWEB_LOG_DEBUG 6418b2521a5SCarson Labrado << "No satellite BMCs detected. Redfish Aggregation not enabled"; 6428b2521a5SCarson Labrado } 6438b2521a5SCarson Labrado handler(ec, satelliteInfo); 6448b2521a5SCarson Labrado }, 6458b2521a5SCarson Labrado "xyz.openbmc_project.EntityManager", 6468b2521a5SCarson Labrado "/xyz/openbmc_project/inventory", 6478b2521a5SCarson Labrado "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 6488b2521a5SCarson Labrado } 6498b2521a5SCarson Labrado 65046a81465SCarson Labrado // Processes the response returned by a satellite BMC and loads its 65146a81465SCarson Labrado // contents into asyncResp 65246a81465SCarson Labrado static void 6531c0bb5c6SCarson Labrado processResponse(std::string_view prefix, 6541c0bb5c6SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 65546a81465SCarson Labrado crow::Response& resp) 65646a81465SCarson Labrado { 65743e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 65843e14d38SCarson Labrado // overwrite the response headers in that case 65943e14d38SCarson Labrado if ((resp.resultInt() == 429) || (resp.resultInt() == 502)) 66043e14d38SCarson Labrado { 66143e14d38SCarson Labrado asyncResp->res.result(resp.result()); 66243e14d38SCarson Labrado return; 66343e14d38SCarson Labrado } 66443e14d38SCarson Labrado 66532d7d8ebSCarson Labrado // We want to attempt prefix fixing regardless of response code 66646a81465SCarson Labrado // The resp will not have a json component 66746a81465SCarson Labrado // We need to create a json from resp's stringResponse 66846a81465SCarson Labrado if (resp.getHeaderValue("Content-Type") == "application/json") 66946a81465SCarson Labrado { 67046a81465SCarson Labrado nlohmann::json jsonVal = 67146a81465SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 67246a81465SCarson Labrado if (jsonVal.is_discarded()) 67346a81465SCarson Labrado { 67446a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 67546a81465SCarson Labrado messages::operationFailed(asyncResp->res); 67646a81465SCarson Labrado return; 67746a81465SCarson Labrado } 67846a81465SCarson Labrado 67946a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 68046a81465SCarson Labrado 6811c0bb5c6SCarson Labrado addPrefixes(jsonVal, prefix); 6821c0bb5c6SCarson Labrado 6831c0bb5c6SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 6841c0bb5c6SCarson Labrado 68546a81465SCarson Labrado asyncResp->res.result(resp.result()); 68646a81465SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 68746a81465SCarson Labrado 68846a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; 68946a81465SCarson Labrado } 69046a81465SCarson Labrado else 69146a81465SCarson Labrado { 6920af78d5aSKhang Kieu // We allow any Content-Type that is not "application/json" now 6930af78d5aSKhang Kieu asyncResp->res.result(resp.result()); 6940af78d5aSKhang Kieu asyncResp->res.write(resp.body()); 69546a81465SCarson Labrado } 6960af78d5aSKhang Kieu addAggregatedHeaders(asyncResp->res, resp, prefix); 69746a81465SCarson Labrado } 69846a81465SCarson Labrado 6994c30e226SCarson Labrado // Processes the collection response returned by a satellite BMC and merges 7004c30e226SCarson Labrado // its "@odata.id" values 7014c30e226SCarson Labrado static void processCollectionResponse( 7024c30e226SCarson Labrado const std::string& prefix, 7034c30e226SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 7044c30e226SCarson Labrado crow::Response& resp) 7054c30e226SCarson Labrado { 70643e14d38SCarson Labrado // 429 and 502 mean we didn't actually send the request so don't 70743e14d38SCarson Labrado // overwrite the response headers in that case 70843e14d38SCarson Labrado if ((resp.resultInt() == 429) || (resp.resultInt() == 502)) 70943e14d38SCarson Labrado { 71043e14d38SCarson Labrado return; 71143e14d38SCarson Labrado } 71243e14d38SCarson Labrado 7134c30e226SCarson Labrado if (resp.resultInt() != 200) 7144c30e226SCarson Labrado { 7154c30e226SCarson Labrado BMCWEB_LOG_DEBUG 7164c30e226SCarson Labrado << "Collection resource does not exist in satellite BMC \"" 7174c30e226SCarson Labrado << prefix << "\""; 7184c30e226SCarson Labrado // Return the error if we haven't had any successes 7194c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 7204c30e226SCarson Labrado { 7214c30e226SCarson Labrado asyncResp->res.stringResponse = std::move(resp.stringResponse); 7224c30e226SCarson Labrado } 7234c30e226SCarson Labrado return; 7244c30e226SCarson Labrado } 7254c30e226SCarson Labrado 7264c30e226SCarson Labrado // The resp will not have a json component 7274c30e226SCarson Labrado // We need to create a json from resp's stringResponse 7284c30e226SCarson Labrado if (resp.getHeaderValue("Content-Type") == "application/json") 7294c30e226SCarson Labrado { 7304c30e226SCarson Labrado nlohmann::json jsonVal = 7314c30e226SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 7324c30e226SCarson Labrado if (jsonVal.is_discarded()) 7334c30e226SCarson Labrado { 7344c30e226SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 7354c30e226SCarson Labrado 7364c30e226SCarson Labrado // Notify the user if doing so won't overwrite a valid response 7374c30e226SCarson Labrado if ((asyncResp->res.resultInt() != 200) && 73843e14d38SCarson Labrado (asyncResp->res.resultInt() != 429) && 7394c30e226SCarson Labrado (asyncResp->res.resultInt() != 502)) 7404c30e226SCarson Labrado { 7414c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 7424c30e226SCarson Labrado } 7434c30e226SCarson Labrado return; 7444c30e226SCarson Labrado } 7454c30e226SCarson Labrado 7464c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 7474c30e226SCarson Labrado 7484c30e226SCarson Labrado // Now we need to add the prefix to the URIs contained in the 7494c30e226SCarson Labrado // response. 7504c30e226SCarson Labrado addPrefixes(jsonVal, prefix); 7514c30e226SCarson Labrado 7524c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 7534c30e226SCarson Labrado 7544c30e226SCarson Labrado // If this resource collection does not exist on the aggregating bmc 7554c30e226SCarson Labrado // and has not already been added from processing the response from 7564c30e226SCarson Labrado // a different satellite then we need to completely overwrite 7574c30e226SCarson Labrado // asyncResp 7584c30e226SCarson Labrado if (asyncResp->res.resultInt() != 200) 7594c30e226SCarson Labrado { 7604c30e226SCarson Labrado // We only want to aggregate collections that contain a 7614c30e226SCarson Labrado // "Members" array 7624c30e226SCarson Labrado if ((!jsonVal.contains("Members")) && 7634c30e226SCarson Labrado (!jsonVal["Members"].is_array())) 7644c30e226SCarson Labrado { 7654c30e226SCarson Labrado BMCWEB_LOG_DEBUG 7664c30e226SCarson Labrado << "Skipping aggregating unsupported resource"; 7674c30e226SCarson Labrado return; 7684c30e226SCarson Labrado } 7694c30e226SCarson Labrado 7704c30e226SCarson Labrado BMCWEB_LOG_DEBUG 7714c30e226SCarson Labrado << "Collection does not exist, overwriting asyncResp"; 7724c30e226SCarson Labrado asyncResp->res.result(resp.result()); 7734c30e226SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 77443e14d38SCarson Labrado asyncResp->res.addHeader("Content-Type", "application/json"); 7754c30e226SCarson Labrado 7764c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; 7774c30e226SCarson Labrado } 7784c30e226SCarson Labrado else 7794c30e226SCarson Labrado { 7804c30e226SCarson Labrado // We only want to aggregate collections that contain a 7814c30e226SCarson Labrado // "Members" array 7824c30e226SCarson Labrado if ((!asyncResp->res.jsonValue.contains("Members")) && 7834c30e226SCarson Labrado (!asyncResp->res.jsonValue["Members"].is_array())) 7844c30e226SCarson Labrado 7854c30e226SCarson Labrado { 7864c30e226SCarson Labrado BMCWEB_LOG_DEBUG 7874c30e226SCarson Labrado << "Skipping aggregating unsupported resource"; 7884c30e226SCarson Labrado return; 7894c30e226SCarson Labrado } 7904c30e226SCarson Labrado 7914c30e226SCarson Labrado BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" 7924c30e226SCarson Labrado << prefix << "\" to collection"; 7934c30e226SCarson Labrado 7944c30e226SCarson Labrado // TODO: This is a potential race condition with multiple 7954c30e226SCarson Labrado // satellites and the aggregating bmc attempting to write to 7964c30e226SCarson Labrado // update this array. May need to cascade calls to the next 7974c30e226SCarson Labrado // satellite at the end of this function. 7984c30e226SCarson Labrado // This is presumably not a concern when there is only a single 7994c30e226SCarson Labrado // satellite since the aggregating bmc should have completed 8004c30e226SCarson Labrado // before the response is received from the satellite. 8014c30e226SCarson Labrado 8024c30e226SCarson Labrado auto& members = asyncResp->res.jsonValue["Members"]; 8034c30e226SCarson Labrado auto& satMembers = jsonVal["Members"]; 8044c30e226SCarson Labrado for (auto& satMem : satMembers) 8054c30e226SCarson Labrado { 8064c30e226SCarson Labrado members.push_back(std::move(satMem)); 8074c30e226SCarson Labrado } 8084c30e226SCarson Labrado asyncResp->res.jsonValue["Members@odata.count"] = 8094c30e226SCarson Labrado members.size(); 8104c30e226SCarson Labrado 8114c30e226SCarson Labrado // TODO: Do we need to sort() after updating the array? 8124c30e226SCarson Labrado } 8134c30e226SCarson Labrado } 8144c30e226SCarson Labrado else 8154c30e226SCarson Labrado { 8164c30e226SCarson Labrado BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 8174c30e226SCarson Labrado << "\""; 81843e14d38SCarson Labrado // We received a response that was not a json. 8194c30e226SCarson Labrado // Notify the user only if we did not receive any valid responses, 8204c30e226SCarson Labrado // if the resource collection does not already exist on the 8214c30e226SCarson Labrado // aggregating BMC, and if we did not already set this warning due 8224c30e226SCarson Labrado // to a failure from a different satellite 8234c30e226SCarson Labrado if ((asyncResp->res.resultInt() != 200) && 82443e14d38SCarson Labrado (asyncResp->res.resultInt() != 429) && 8254c30e226SCarson Labrado (asyncResp->res.resultInt() != 502)) 8264c30e226SCarson Labrado { 8274c30e226SCarson Labrado messages::operationFailed(asyncResp->res); 8284c30e226SCarson Labrado } 8294c30e226SCarson Labrado } 8304c30e226SCarson Labrado } // End processCollectionResponse() 8314c30e226SCarson Labrado 83205916cefSCarson Labrado // Entry point to Redfish Aggregation 83305916cefSCarson Labrado // Returns Result stating whether or not we still need to locally handle the 83405916cefSCarson Labrado // request 83505916cefSCarson Labrado static Result 83605916cefSCarson Labrado beginAggregation(const crow::Request& thisReq, 83705916cefSCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 83805916cefSCarson Labrado { 83905916cefSCarson Labrado using crow::utility::OrMorePaths; 84005916cefSCarson Labrado using crow::utility::readUrlSegments; 84139662a3bSEd Tanous const boost::urls::url_view url = thisReq.url(); 842411e6a11SCarson Labrado 843411e6a11SCarson Labrado // We don't need to aggregate JsonSchemas due to potential issues such 844411e6a11SCarson Labrado // as version mismatches between aggregator and satellite BMCs. For 845411e6a11SCarson Labrado // now assume that the aggregator has all the schemas and versions that 846411e6a11SCarson Labrado // the aggregated server has. 847411e6a11SCarson Labrado if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", 848411e6a11SCarson Labrado crow::utility::OrMorePaths())) 849411e6a11SCarson Labrado { 850411e6a11SCarson Labrado return Result::LocalHandle; 851411e6a11SCarson Labrado } 852411e6a11SCarson Labrado 8537c4c52cbSCarson Labrado // The first two segments should be "/redfish/v1". We need to check 8547c4c52cbSCarson Labrado // that before we can search topCollections 8557c4c52cbSCarson Labrado if (!crow::utility::readUrlSegments(url, "redfish", "v1", 8567c4c52cbSCarson Labrado crow::utility::OrMorePaths())) 85746a81465SCarson Labrado { 85846a81465SCarson Labrado return Result::LocalHandle; 85946a81465SCarson Labrado } 86005916cefSCarson Labrado 8617c4c52cbSCarson Labrado // Parse the URI to see if it begins with a known top level collection 8627c4c52cbSCarson Labrado // such as: 8637c4c52cbSCarson Labrado // /redfish/v1/Chassis 8647c4c52cbSCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory 8657c4c52cbSCarson Labrado const boost::urls::segments_view urlSegments = url.segments(); 8667c4c52cbSCarson Labrado boost::urls::url currentUrl("/"); 8677c4c52cbSCarson Labrado boost::urls::segments_view::iterator it = urlSegments.begin(); 8687c4c52cbSCarson Labrado const boost::urls::segments_view::const_iterator end = 8697c4c52cbSCarson Labrado urlSegments.end(); 87005916cefSCarson Labrado 8717c4c52cbSCarson Labrado // Skip past the leading "/redfish/v1" 8727c4c52cbSCarson Labrado it++; 8737c4c52cbSCarson Labrado it++; 8747c4c52cbSCarson Labrado for (; it != end; it++) 87505916cefSCarson Labrado { 876d4413c5bSGeorge Liu const std::string& collectionItem = *it; 8777c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 8787c4c52cbSCarson Labrado currentUrl.buffer())) 8797c4c52cbSCarson Labrado { 8807c4c52cbSCarson Labrado // We've matched a resource collection so this current segment 8817c4c52cbSCarson Labrado // might contain an aggregation prefix 8828b2521a5SCarson Labrado // TODO: This needs to be rethought when we can support multiple 8838b2521a5SCarson Labrado // satellites due to 8848b2521a5SCarson Labrado // /redfish/v1/AggregationService/AggregationSources/5B247A 8858b2521a5SCarson Labrado // being a local resource describing the satellite 8868b2521a5SCarson Labrado if (collectionItem.starts_with("5B247A_")) 88705916cefSCarson Labrado { 88805916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Need to forward a request"; 88905916cefSCarson Labrado 89046a81465SCarson Labrado // Extract the prefix from the request's URI, retrieve the 8917c4c52cbSCarson Labrado // associated satellite config information, and then forward 8927c4c52cbSCarson Labrado // the request to that satellite. 8937c4c52cbSCarson Labrado startAggregation(AggregationType::Resource, thisReq, 8947c4c52cbSCarson Labrado asyncResp); 89505916cefSCarson Labrado return Result::NoLocalHandle; 89605916cefSCarson Labrado } 8977c4c52cbSCarson Labrado 8987c4c52cbSCarson Labrado // Handle collection URI with a trailing backslash 8997c4c52cbSCarson Labrado // e.g. /redfish/v1/Chassis/ 9007c4c52cbSCarson Labrado it++; 9017c4c52cbSCarson Labrado if ((it == end) && collectionItem.empty()) 9027c4c52cbSCarson Labrado { 9037c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, 9047c4c52cbSCarson Labrado asyncResp); 9057c4c52cbSCarson Labrado } 9067c4c52cbSCarson Labrado 9077c4c52cbSCarson Labrado // We didn't recognize the prefix or it's a collection with a 9087c4c52cbSCarson Labrado // trailing "/". In both cases we still want to locally handle 9097c4c52cbSCarson Labrado // the request 9107c4c52cbSCarson Labrado return Result::LocalHandle; 9117c4c52cbSCarson Labrado } 9127c4c52cbSCarson Labrado 9137c4c52cbSCarson Labrado currentUrl.segments().push_back(collectionItem); 9147c4c52cbSCarson Labrado } 9157c4c52cbSCarson Labrado 9167c4c52cbSCarson Labrado // If we made it here then currentUrl could contain a top level 9177c4c52cbSCarson Labrado // collection URI without a trailing "/", e.g. /redfish/v1/Chassis 9187c4c52cbSCarson Labrado if (std::binary_search(topCollections.begin(), topCollections.end(), 9197c4c52cbSCarson Labrado currentUrl.buffer())) 9207c4c52cbSCarson Labrado { 9217c4c52cbSCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 92205916cefSCarson Labrado return Result::LocalHandle; 92305916cefSCarson Labrado } 92405916cefSCarson Labrado 92505916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation not required"; 92605916cefSCarson Labrado return Result::LocalHandle; 92705916cefSCarson Labrado } 9287fb33566SCarson Labrado }; 9297fb33566SCarson Labrado 9307fb33566SCarson Labrado } // namespace redfish 931