xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision b27e1cbefc2a12a838de209decb7e22b5ba1b2c0)
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