xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 1a095906dcc9fdf76e6b86f6a9c7b5a53a9da050)
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"
818f8f608SEd Tanous #include "parsing.hpp"
97fb33566SCarson Labrado 
107e8890c5SCarson Labrado #include <array>
113544d2a7SEd Tanous #include <ranges>
123544d2a7SEd Tanous #include <string_view>
137e8890c5SCarson Labrado 
147fb33566SCarson Labrado namespace redfish
157fb33566SCarson Labrado {
167fb33566SCarson Labrado 
17d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB
18d14a48ffSCarson Labrado 
1905916cefSCarson Labrado enum class Result
2005916cefSCarson Labrado {
2105916cefSCarson Labrado     LocalHandle,
2205916cefSCarson Labrado     NoLocalHandle
2305916cefSCarson Labrado };
2405916cefSCarson Labrado 
258fd333d6SCarson Labrado enum class SearchType
268fd333d6SCarson Labrado {
278fd333d6SCarson Labrado     Collection,
288fd333d6SCarson Labrado     CollOrCon,
298fd333d6SCarson Labrado     ContainsSubordinate,
308fd333d6SCarson Labrado     Resource
318fd333d6SCarson Labrado };
328fd333d6SCarson Labrado 
337e8890c5SCarson Labrado // clang-format off
347e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource
357e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a
367e8890c5SCarson Labrado // case-insensitive form of "uri".  That version of the schema is associated
377e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification.  Going forward, new URI
387e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as
397e8890c5SCarson Labrado // the spec is updated.  NOTE: These have been pre-sorted in order to be
407e8890c5SCarson Labrado // compatible with binary search
417e8890c5SCarson Labrado constexpr std::array nonUriProperties{
427e8890c5SCarson Labrado     "@Redfish.ActionInfo",
437e8890c5SCarson Labrado     // "@odata.context", // We can't fix /redfish/v1/$metadata URIs
447e8890c5SCarson Labrado     "@odata.id",
457e8890c5SCarson Labrado     // "Destination", // Only used by EventService and won't be a Redfish URI
467e8890c5SCarson Labrado     // "HostName", // Isn't actually a Redfish URI
477e8890c5SCarson Labrado     "Image",
487e8890c5SCarson Labrado     "MetricProperty",
4932d7d8ebSCarson Labrado     // "OriginOfCondition", // Is URI when in request, but is object in response
507e8890c5SCarson Labrado     "TaskMonitor",
517e8890c5SCarson Labrado     "target", // normal string, but target URI for POST to invoke an action
527e8890c5SCarson Labrado };
537e8890c5SCarson Labrado // clang-format on
547e8890c5SCarson Labrado 
558fd333d6SCarson Labrado // Search the top collection array to determine if the passed URI is of a
568fd333d6SCarson Labrado // desired type
578fd333d6SCarson Labrado inline bool searchCollectionsArray(std::string_view uri,
588fd333d6SCarson Labrado                                    const SearchType searchType)
598fd333d6SCarson Labrado {
60*1a095906SJoonwon Kang     boost::system::result<boost::urls::url> parsedUrl =
61*1a095906SJoonwon Kang         boost::urls::parse_relative_ref(uri);
628fd333d6SCarson Labrado     if (!parsedUrl)
638fd333d6SCarson Labrado     {
64*1a095906SJoonwon Kang         BMCWEB_LOG_ERROR("Failed to get target URI from {}", uri);
658fd333d6SCarson Labrado         return false;
668fd333d6SCarson Labrado     }
678fd333d6SCarson Labrado 
68*1a095906SJoonwon Kang     parsedUrl->normalize();
69*1a095906SJoonwon Kang     boost::urls::segments_ref segments = parsedUrl->segments();
70*1a095906SJoonwon Kang     if (!segments.is_absolute())
718fd333d6SCarson Labrado     {
728fd333d6SCarson Labrado         return false;
738fd333d6SCarson Labrado     }
748fd333d6SCarson Labrado 
75*1a095906SJoonwon Kang     // The passed URI must begin with "/redfish/v1", but we have to strip it
76*1a095906SJoonwon Kang     // from the URI since topCollections does not include it in its URIs.
77*1a095906SJoonwon Kang     if (segments.size() < 2)
78*1a095906SJoonwon Kang     {
79*1a095906SJoonwon Kang         return false;
80*1a095906SJoonwon Kang     }
81*1a095906SJoonwon Kang     if (segments.front() != "redfish")
82*1a095906SJoonwon Kang     {
83*1a095906SJoonwon Kang         return false;
84*1a095906SJoonwon Kang     }
85*1a095906SJoonwon Kang     segments.erase(segments.begin());
86*1a095906SJoonwon Kang     if (segments.front() != "v1")
87*1a095906SJoonwon Kang     {
88*1a095906SJoonwon Kang         return false;
89*1a095906SJoonwon Kang     }
90*1a095906SJoonwon Kang     segments.erase(segments.begin());
91*1a095906SJoonwon Kang 
92*1a095906SJoonwon Kang     // Exclude the trailing "/" if it exists such as in "/redfish/v1/".
93*1a095906SJoonwon Kang     if (!segments.empty() && segments.back().empty())
94*1a095906SJoonwon Kang     {
95*1a095906SJoonwon Kang         segments.pop_back();
96*1a095906SJoonwon Kang     }
97*1a095906SJoonwon Kang 
98*1a095906SJoonwon Kang     // If no segments then the passed URI was either "/redfish/v1" or
998fd333d6SCarson Labrado     // "/redfish/v1/".
100*1a095906SJoonwon Kang     if (segments.empty())
1018fd333d6SCarson Labrado     {
1028fd333d6SCarson Labrado         return (searchType == SearchType::ContainsSubordinate) ||
1038fd333d6SCarson Labrado                (searchType == SearchType::CollOrCon);
1048fd333d6SCarson Labrado     }
105*1a095906SJoonwon Kang     std::string_view url = segments.buffer();
1063544d2a7SEd Tanous     const auto* it = std::ranges::lower_bound(topCollections, url);
1078fd333d6SCarson Labrado     if (it == topCollections.end())
1088fd333d6SCarson Labrado     {
1098fd333d6SCarson Labrado         // parsedUrl is alphabetically after the last entry in the array so it
1108fd333d6SCarson Labrado         // can't be a top collection or up tree from a top collection
1118fd333d6SCarson Labrado         return false;
1128fd333d6SCarson Labrado     }
1138fd333d6SCarson Labrado 
1148fd333d6SCarson Labrado     boost::urls::url collectionUrl(*it);
1158fd333d6SCarson Labrado     boost::urls::segments_view collectionSegments = collectionUrl.segments();
1168fd333d6SCarson Labrado     boost::urls::segments_view::iterator itCollection =
1178fd333d6SCarson Labrado         collectionSegments.begin();
1188fd333d6SCarson Labrado     const boost::urls::segments_view::const_iterator endCollection =
1198fd333d6SCarson Labrado         collectionSegments.end();
1208fd333d6SCarson Labrado 
1218fd333d6SCarson Labrado     // Each segment in the passed URI should match the found collection
122*1a095906SJoonwon Kang     for (const auto& segment : segments)
1238fd333d6SCarson Labrado     {
1248fd333d6SCarson Labrado         if (itCollection == endCollection)
1258fd333d6SCarson Labrado         {
1268fd333d6SCarson Labrado             // Leftover segments means the target is for an aggregation
1278fd333d6SCarson Labrado             // supported resource
1288fd333d6SCarson Labrado             return searchType == SearchType::Resource;
1298fd333d6SCarson Labrado         }
1308fd333d6SCarson Labrado 
1318fd333d6SCarson Labrado         if (segment != (*itCollection))
1328fd333d6SCarson Labrado         {
1338fd333d6SCarson Labrado             return false;
1348fd333d6SCarson Labrado         }
1358fd333d6SCarson Labrado         itCollection++;
1368fd333d6SCarson Labrado     }
1378fd333d6SCarson Labrado 
1388fd333d6SCarson Labrado     // No remaining segments means the passed URI was a top level collection
1398fd333d6SCarson Labrado     if (searchType == SearchType::Collection)
1408fd333d6SCarson Labrado     {
1418fd333d6SCarson Labrado         return itCollection == endCollection;
1428fd333d6SCarson Labrado     }
1438fd333d6SCarson Labrado     if (searchType == SearchType::ContainsSubordinate)
1448fd333d6SCarson Labrado     {
1458fd333d6SCarson Labrado         return itCollection != endCollection;
1468fd333d6SCarson Labrado     }
1478fd333d6SCarson Labrado 
1488fd333d6SCarson Labrado     // Return this check instead of "true" in case other SearchTypes get added
1498fd333d6SCarson Labrado     return searchType == SearchType::CollOrCon;
1508fd333d6SCarson Labrado }
1518fd333d6SCarson Labrado 
1527e8890c5SCarson Labrado // Determines if the passed property contains a URI.  Those property names
1537e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically
1547e8890c5SCarson Labrado // defined in the above array.
15526ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName)
1567e8890c5SCarson Labrado {
15718f8f608SEd Tanous     if (propertyName.ends_with("uri") || propertyName.ends_with("Uri") ||
15818f8f608SEd Tanous         propertyName.ends_with("URI"))
15918f8f608SEd Tanous     {
16018f8f608SEd Tanous         return true;
16118f8f608SEd Tanous     }
16218f8f608SEd Tanous     return std::binary_search(nonUriProperties.begin(), nonUriProperties.end(),
1637e8890c5SCarson Labrado                               propertyName);
1647e8890c5SCarson Labrado }
1657e8890c5SCarson Labrado 
1660af78d5aSKhang Kieu static inline void addPrefixToStringItem(std::string& strValue,
1670af78d5aSKhang Kieu                                          std::string_view prefix)
1681c0bb5c6SCarson Labrado {
1691c0bb5c6SCarson Labrado     // Make sure the value is a properly formatted URI
1700af78d5aSKhang Kieu     auto parsed = boost::urls::parse_relative_ref(strValue);
1711c0bb5c6SCarson Labrado     if (!parsed)
1721c0bb5c6SCarson Labrado     {
173bf2ddedeSCarson Labrado         // Note that DMTF URIs such as
174bf2ddedeSCarson Labrado         // https://redfish.dmtf.org/registries/Base.1.15.0.json will fail this
175bf2ddedeSCarson Labrado         // check and that's okay
176bf2ddedeSCarson Labrado         BMCWEB_LOG_DEBUG("Couldn't parse URI from resource {}", strValue);
1771c0bb5c6SCarson Labrado         return;
1781c0bb5c6SCarson Labrado     }
1791c0bb5c6SCarson Labrado 
1801c0bb5c6SCarson Labrado     boost::urls::url_view thisUrl = *parsed;
1811c0bb5c6SCarson Labrado 
182411e6a11SCarson Labrado     // We don't need to aggregate JsonSchemas due to potential issues such as
183411e6a11SCarson Labrado     // version mismatches between aggregator and satellite BMCs.  For now
184411e6a11SCarson Labrado     // assume that the aggregator has all the schemas and versions that the
185411e6a11SCarson Labrado     // aggregated server has.
186411e6a11SCarson Labrado     if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas",
187411e6a11SCarson Labrado                                        crow::utility::OrMorePaths()))
188411e6a11SCarson Labrado     {
18962598e31SEd Tanous         BMCWEB_LOG_DEBUG("Skipping JsonSchemas URI prefix fixing");
190411e6a11SCarson Labrado         return;
191411e6a11SCarson Labrado     }
192411e6a11SCarson Labrado 
19311987af6SCarson Labrado     // The first two segments should be "/redfish/v1".  We need to check that
19411987af6SCarson Labrado     // before we can search topCollections
19511987af6SCarson Labrado     if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
19611987af6SCarson Labrado                                         crow::utility::OrMorePaths()))
1971c0bb5c6SCarson Labrado     {
1981c0bb5c6SCarson Labrado         return;
1991c0bb5c6SCarson Labrado     }
2001c0bb5c6SCarson Labrado 
20111987af6SCarson Labrado     // Check array adding a segment each time until collection is identified
20211987af6SCarson Labrado     // Add prefix to segment after the collection
20311987af6SCarson Labrado     const boost::urls::segments_view urlSegments = thisUrl.segments();
20411987af6SCarson Labrado     bool addedPrefix = false;
20511987af6SCarson Labrado     boost::urls::url url("/");
2064a7fbefdSEd Tanous     boost::urls::segments_view::const_iterator it = urlSegments.begin();
20711987af6SCarson Labrado     const boost::urls::segments_view::const_iterator end = urlSegments.end();
20811987af6SCarson Labrado 
20911987af6SCarson Labrado     // Skip past the leading "/redfish/v1"
21011987af6SCarson Labrado     it++;
21111987af6SCarson Labrado     it++;
21211987af6SCarson Labrado     for (; it != end; it++)
2131c0bb5c6SCarson Labrado     {
21411987af6SCarson Labrado         // Trailing "/" will result in an empty segment.  In that case we need
21511987af6SCarson Labrado         // to return so we don't apply a prefix to top level collections such
21611987af6SCarson Labrado         // as "/redfish/v1/Chassis/"
21711987af6SCarson Labrado         if ((*it).empty())
21811987af6SCarson Labrado         {
219411e6a11SCarson Labrado             return;
2201c0bb5c6SCarson Labrado         }
2211c0bb5c6SCarson Labrado 
22211987af6SCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
22311987af6SCarson Labrado                                url.buffer()))
2241c0bb5c6SCarson Labrado         {
22511987af6SCarson Labrado             std::string collectionItem(prefix);
22611987af6SCarson Labrado             collectionItem += "_" + (*it);
22711987af6SCarson Labrado             url.segments().push_back(collectionItem);
22811987af6SCarson Labrado             it++;
22911987af6SCarson Labrado             addedPrefix = true;
23011987af6SCarson Labrado             break;
23111987af6SCarson Labrado         }
23211987af6SCarson Labrado 
23311987af6SCarson Labrado         url.segments().push_back(*it);
23411987af6SCarson Labrado     }
23511987af6SCarson Labrado 
23611987af6SCarson Labrado     // Finish constructing the URL here (if needed) to avoid additional checks
23711987af6SCarson Labrado     for (; it != end; it++)
23811987af6SCarson Labrado     {
23911987af6SCarson Labrado         url.segments().push_back(*it);
24011987af6SCarson Labrado     }
24111987af6SCarson Labrado 
24211987af6SCarson Labrado     if (addedPrefix)
24311987af6SCarson Labrado     {
24411987af6SCarson Labrado         url.segments().insert(url.segments().begin(), {"redfish", "v1"});
2450af78d5aSKhang Kieu         strValue = url.buffer();
2461c0bb5c6SCarson Labrado     }
2471c0bb5c6SCarson Labrado }
2481c0bb5c6SCarson Labrado 
2490af78d5aSKhang Kieu static inline void addPrefixToItem(nlohmann::json& item,
2500af78d5aSKhang Kieu                                    std::string_view prefix)
2510af78d5aSKhang Kieu {
2520af78d5aSKhang Kieu     std::string* strValue = item.get_ptr<std::string*>();
2530af78d5aSKhang Kieu     if (strValue == nullptr)
2540af78d5aSKhang Kieu     {
255bf2ddedeSCarson Labrado         // Values for properties like "InvalidURI" and "ResourceMissingAtURI"
256bf2ddedeSCarson Labrado         // from within the Base Registry are objects instead of strings and will
257bf2ddedeSCarson Labrado         // fall into this check
258bf2ddedeSCarson Labrado         BMCWEB_LOG_DEBUG("Field was not a string");
2590af78d5aSKhang Kieu         return;
2600af78d5aSKhang Kieu     }
2610af78d5aSKhang Kieu     addPrefixToStringItem(*strValue, prefix);
2620af78d5aSKhang Kieu     item = *strValue;
2630af78d5aSKhang Kieu }
2640af78d5aSKhang Kieu 
2650af78d5aSKhang Kieu static inline void addAggregatedHeaders(crow::Response& asyncResp,
26624dadc88SCarson Labrado                                         const crow::Response& resp,
2670af78d5aSKhang Kieu                                         std::string_view prefix)
2680af78d5aSKhang Kieu {
2690af78d5aSKhang Kieu     if (!resp.getHeaderValue("Content-Type").empty())
2700af78d5aSKhang Kieu     {
2710af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::content_type,
2720af78d5aSKhang Kieu                             resp.getHeaderValue("Content-Type"));
2730af78d5aSKhang Kieu     }
2740af78d5aSKhang Kieu     if (!resp.getHeaderValue("Allow").empty())
2750af78d5aSKhang Kieu     {
2760af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::allow,
2770af78d5aSKhang Kieu                             resp.getHeaderValue("Allow"));
2780af78d5aSKhang Kieu     }
2790af78d5aSKhang Kieu     std::string_view header = resp.getHeaderValue("Location");
2800af78d5aSKhang Kieu     if (!header.empty())
2810af78d5aSKhang Kieu     {
2820af78d5aSKhang Kieu         std::string location(header);
2830af78d5aSKhang Kieu         addPrefixToStringItem(location, prefix);
2840af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::location, location);
2850af78d5aSKhang Kieu     }
2860af78d5aSKhang Kieu     if (!resp.getHeaderValue("Retry-After").empty())
2870af78d5aSKhang Kieu     {
2880af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::retry_after,
2890af78d5aSKhang Kieu                             resp.getHeaderValue("Retry-After"));
2900af78d5aSKhang Kieu     }
2910af78d5aSKhang Kieu     // TODO: we need special handling for Link Header Value
2920af78d5aSKhang Kieu }
2930af78d5aSKhang Kieu 
294b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others
295bd79bce8SPatrick Williams static inline void
296bd79bce8SPatrick Williams     addPrefixToHeadersInResp(nlohmann::json& json, std::string_view prefix)
297b27e1cbeSCarson Labrado {
298b27e1cbeSCarson Labrado     // The passed in "HttpHeaders" should be an array of headers
299b27e1cbeSCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
300b27e1cbeSCarson Labrado     if (array == nullptr)
301b27e1cbeSCarson Labrado     {
30262598e31SEd Tanous         BMCWEB_LOG_ERROR("Field wasn't an array_t????");
303b27e1cbeSCarson Labrado         return;
304b27e1cbeSCarson Labrado     }
305b27e1cbeSCarson Labrado 
306b27e1cbeSCarson Labrado     for (nlohmann::json& item : *array)
307b27e1cbeSCarson Labrado     {
308b27e1cbeSCarson Labrado         // Each header is a single string with the form "<Field>: <Value>"
309b27e1cbeSCarson Labrado         std::string* strHeader = item.get_ptr<std::string*>();
310b27e1cbeSCarson Labrado         if (strHeader == nullptr)
311b27e1cbeSCarson Labrado         {
31262598e31SEd Tanous             BMCWEB_LOG_CRITICAL("Field wasn't a string????");
313b27e1cbeSCarson Labrado             continue;
314b27e1cbeSCarson Labrado         }
315b27e1cbeSCarson Labrado 
316b27e1cbeSCarson Labrado         constexpr std::string_view location = "Location: ";
317b27e1cbeSCarson Labrado         if (strHeader->starts_with(location))
318b27e1cbeSCarson Labrado         {
319b27e1cbeSCarson Labrado             std::string header = strHeader->substr(location.size());
320b27e1cbeSCarson Labrado             addPrefixToStringItem(header, prefix);
321b27e1cbeSCarson Labrado             *strHeader = std::string(location) + header;
322b27e1cbeSCarson Labrado         }
323b27e1cbeSCarson Labrado     }
324b27e1cbeSCarson Labrado }
325b27e1cbeSCarson Labrado 
3261c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for
3277e8890c5SCarson Labrado // an aggregated resource.
3280af78d5aSKhang Kieu static inline void addPrefixes(nlohmann::json& json, std::string_view prefix)
3291c0bb5c6SCarson Labrado {
3301c0bb5c6SCarson Labrado     nlohmann::json::object_t* object =
3311c0bb5c6SCarson Labrado         json.get_ptr<nlohmann::json::object_t*>();
3321c0bb5c6SCarson Labrado     if (object != nullptr)
3331c0bb5c6SCarson Labrado     {
3341c0bb5c6SCarson Labrado         for (std::pair<const std::string, nlohmann::json>& item : *object)
3351c0bb5c6SCarson Labrado         {
3367e8890c5SCarson Labrado             if (isPropertyUri(item.first))
3371c0bb5c6SCarson Labrado             {
3387e8890c5SCarson Labrado                 addPrefixToItem(item.second, prefix);
3391c0bb5c6SCarson Labrado                 continue;
3401c0bb5c6SCarson Labrado             }
3411c0bb5c6SCarson Labrado 
342b27e1cbeSCarson Labrado             // "HttpHeaders" contains HTTP headers.  Among those we need to
343b27e1cbeSCarson Labrado             // attempt to fix the "Location" header
344b27e1cbeSCarson Labrado             if (item.first == "HttpHeaders")
345b27e1cbeSCarson Labrado             {
346b27e1cbeSCarson Labrado                 addPrefixToHeadersInResp(item.second, prefix);
347b27e1cbeSCarson Labrado                 continue;
348b27e1cbeSCarson Labrado             }
349b27e1cbeSCarson Labrado 
3508ece0e45SEd Tanous             // Recursively parse the rest of the json
3511c0bb5c6SCarson Labrado             addPrefixes(item.second, prefix);
3521c0bb5c6SCarson Labrado         }
3531c0bb5c6SCarson Labrado         return;
3541c0bb5c6SCarson Labrado     }
3551c0bb5c6SCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
3561c0bb5c6SCarson Labrado     if (array != nullptr)
3571c0bb5c6SCarson Labrado     {
3581c0bb5c6SCarson Labrado         for (nlohmann::json& item : *array)
3591c0bb5c6SCarson Labrado         {
3601c0bb5c6SCarson Labrado             addPrefixes(item, prefix);
3611c0bb5c6SCarson Labrado         }
3621c0bb5c6SCarson Labrado     }
3631c0bb5c6SCarson Labrado }
3641c0bb5c6SCarson Labrado 
365d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode)
366a7a80296SCarson Labrado {
36732d7d8ebSCarson Labrado     // Allow all response codes because we want to surface any satellite
36832d7d8ebSCarson Labrado     // issue to the client
36962598e31SEd Tanous     BMCWEB_LOG_DEBUG("Received {} response from satellite", respCode);
370d14a48ffSCarson Labrado     return boost::system::errc::make_error_code(boost::system::errc::success);
371d14a48ffSCarson Labrado }
372d14a48ffSCarson Labrado 
373d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy()
374d14a48ffSCarson Labrado {
3756bd30813SEd Tanous     return {.maxRetryAttempts = 0,
376d14a48ffSCarson Labrado             .requestByteLimit = aggregatorReadBodyLimit,
377d14a48ffSCarson Labrado             .maxConnections = 20,
378d14a48ffSCarson Labrado             .retryPolicyAction = "TerminateAfterRetries",
379d14a48ffSCarson Labrado             .retryIntervalSecs = std::chrono::seconds(0),
380d14a48ffSCarson Labrado             .invalidResp = aggregationRetryHandler};
381d14a48ffSCarson Labrado }
382d14a48ffSCarson Labrado 
383d14a48ffSCarson Labrado class RedfishAggregator
384d14a48ffSCarson Labrado {
385d14a48ffSCarson Labrado   private:
386d14a48ffSCarson Labrado     crow::HttpClient client;
387d14a48ffSCarson Labrado 
3887fb33566SCarson Labrado     // Dummy callback used by the Constructor so that it can report the number
3897fb33566SCarson Labrado     // of satellite configs when the class is first created
3907fb33566SCarson Labrado     static void constructorCallback(
3918b2521a5SCarson Labrado         const boost::system::error_code& ec,
3927fb33566SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
3937fb33566SCarson Labrado     {
3947fb33566SCarson Labrado         if (ec)
3957fb33566SCarson Labrado         {
39662598e31SEd Tanous             BMCWEB_LOG_ERROR("Something went wrong while querying dbus!");
3977fb33566SCarson Labrado             return;
3987fb33566SCarson Labrado         }
3997fb33566SCarson Labrado 
40062598e31SEd Tanous         BMCWEB_LOG_DEBUG("There were {} satellite configs found at startup",
40162598e31SEd Tanous                          std::to_string(satelliteInfo.size()));
4027fb33566SCarson Labrado     }
4037fb33566SCarson Labrado 
4047fb33566SCarson Labrado     // Search D-Bus objects for satellite config objects and add their
4057fb33566SCarson Labrado     // information if valid
4067fb33566SCarson Labrado     static void findSatelliteConfigs(
4077fb33566SCarson Labrado         const dbus::utility::ManagedObjectType& objects,
4087fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4097fb33566SCarson Labrado     {
4107fb33566SCarson Labrado         for (const auto& objectPath : objects)
4117fb33566SCarson Labrado         {
4127fb33566SCarson Labrado             for (const auto& interface : objectPath.second)
4137fb33566SCarson Labrado             {
4147fb33566SCarson Labrado                 if (interface.first ==
4157fb33566SCarson Labrado                     "xyz.openbmc_project.Configuration.SatelliteController")
4167fb33566SCarson Labrado                 {
41762598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Found Satellite Controller at {}",
41862598e31SEd Tanous                                      objectPath.first.str);
4197fb33566SCarson Labrado 
42005916cefSCarson Labrado                     if (!satelliteInfo.empty())
42105916cefSCarson Labrado                     {
42262598e31SEd Tanous                         BMCWEB_LOG_ERROR(
42362598e31SEd Tanous                             "Redfish Aggregation only supports one satellite!");
42462598e31SEd Tanous                         BMCWEB_LOG_DEBUG("Clearing all satellite data");
42505916cefSCarson Labrado                         satelliteInfo.clear();
42605916cefSCarson Labrado                         return;
42705916cefSCarson Labrado                     }
42805916cefSCarson Labrado 
42905916cefSCarson Labrado                     // For now assume there will only be one satellite config.
43005916cefSCarson Labrado                     // Assign it the name/prefix "5B247A"
43105916cefSCarson Labrado                     addSatelliteConfig("5B247A", interface.second,
43205916cefSCarson Labrado                                        satelliteInfo);
4337fb33566SCarson Labrado                 }
4347fb33566SCarson Labrado             }
4357fb33566SCarson Labrado         }
4367fb33566SCarson Labrado     }
4377fb33566SCarson Labrado 
4387fb33566SCarson Labrado     // Parse the properties of a satellite config object and add the
4397fb33566SCarson Labrado     // configuration if the properties are valid
4407fb33566SCarson Labrado     static void addSatelliteConfig(
44105916cefSCarson Labrado         const std::string& name,
4427fb33566SCarson Labrado         const dbus::utility::DBusPropertiesMap& properties,
4437fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4447fb33566SCarson Labrado     {
4457fb33566SCarson Labrado         boost::urls::url url;
4467fb33566SCarson Labrado 
4477fb33566SCarson Labrado         for (const auto& prop : properties)
4487fb33566SCarson Labrado         {
44905916cefSCarson Labrado             if (prop.first == "Hostname")
4507fb33566SCarson Labrado             {
4517fb33566SCarson Labrado                 const std::string* propVal =
4527fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
4537fb33566SCarson Labrado                 if (propVal == nullptr)
4547fb33566SCarson Labrado                 {
45562598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid Hostname value");
4567fb33566SCarson Labrado                     return;
4577fb33566SCarson Labrado                 }
4587fb33566SCarson Labrado                 url.set_host(*propVal);
4597fb33566SCarson Labrado             }
4607fb33566SCarson Labrado 
4617fb33566SCarson Labrado             else if (prop.first == "Port")
4627fb33566SCarson Labrado             {
4637fb33566SCarson Labrado                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
4647fb33566SCarson Labrado                 if (propVal == nullptr)
4657fb33566SCarson Labrado                 {
46662598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid Port value");
4677fb33566SCarson Labrado                     return;
4687fb33566SCarson Labrado                 }
4697fb33566SCarson Labrado 
4707fb33566SCarson Labrado                 if (*propVal > std::numeric_limits<uint16_t>::max())
4717fb33566SCarson Labrado                 {
47262598e31SEd Tanous                     BMCWEB_LOG_ERROR("Port value out of range");
4737fb33566SCarson Labrado                     return;
4747fb33566SCarson Labrado                 }
475079360aeSEd Tanous                 url.set_port(std::to_string(static_cast<uint16_t>(*propVal)));
4767fb33566SCarson Labrado             }
4777fb33566SCarson Labrado 
4787fb33566SCarson Labrado             else if (prop.first == "AuthType")
4797fb33566SCarson Labrado             {
4807fb33566SCarson Labrado                 const std::string* propVal =
4817fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
4827fb33566SCarson Labrado                 if (propVal == nullptr)
4837fb33566SCarson Labrado                 {
48462598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid AuthType value");
4857fb33566SCarson Labrado                     return;
4867fb33566SCarson Labrado                 }
4877fb33566SCarson Labrado 
4887fb33566SCarson Labrado                 // For now assume authentication not required to communicate
4897fb33566SCarson Labrado                 // with the satellite BMC
4907fb33566SCarson Labrado                 if (*propVal != "None")
4917fb33566SCarson Labrado                 {
49262598e31SEd Tanous                     BMCWEB_LOG_ERROR(
49362598e31SEd Tanous                         "Unsupported AuthType value: {}, only \"none\" is supported",
49462598e31SEd Tanous                         *propVal);
4957fb33566SCarson Labrado                     return;
4967fb33566SCarson Labrado                 }
4977fb33566SCarson Labrado                 url.set_scheme("http");
4987fb33566SCarson Labrado             }
4997fb33566SCarson Labrado         } // Finished reading properties
5007fb33566SCarson Labrado 
5017fb33566SCarson Labrado         // Make sure all required config information was made available
5027fb33566SCarson Labrado         if (url.host().empty())
5037fb33566SCarson Labrado         {
50462598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing Host", name);
5057fb33566SCarson Labrado             return;
5067fb33566SCarson Labrado         }
5077fb33566SCarson Labrado 
5087fb33566SCarson Labrado         if (!url.has_port())
5097fb33566SCarson Labrado         {
51062598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing Port", name);
5117fb33566SCarson Labrado             return;
5127fb33566SCarson Labrado         }
5137fb33566SCarson Labrado 
5147fb33566SCarson Labrado         if (!url.has_scheme())
5157fb33566SCarson Labrado         {
51662598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing AuthType", name);
5177fb33566SCarson Labrado             return;
5187fb33566SCarson Labrado         }
5197fb33566SCarson Labrado 
5207fb33566SCarson Labrado         std::string resultString;
5217fb33566SCarson Labrado         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
5227fb33566SCarson Labrado         if (result.second)
5237fb33566SCarson Labrado         {
5247fb33566SCarson Labrado             resultString = "Added new satellite config ";
5257fb33566SCarson Labrado         }
5267fb33566SCarson Labrado         else
5277fb33566SCarson Labrado         {
5287fb33566SCarson Labrado             resultString = "Updated existing satellite config ";
5297fb33566SCarson Labrado         }
5307fb33566SCarson Labrado 
53162598e31SEd Tanous         BMCWEB_LOG_DEBUG("{}{} at {}://{}", resultString, name,
53262598e31SEd Tanous                          result.first->second.scheme(),
53362598e31SEd Tanous                          result.first->second.encoded_host_and_port());
5347fb33566SCarson Labrado     }
5357fb33566SCarson Labrado 
53646a81465SCarson Labrado     enum AggregationType
53746a81465SCarson Labrado     {
53846a81465SCarson Labrado         Collection,
539e002dbc0SCarson Labrado         ContainsSubordinate,
54046a81465SCarson Labrado         Resource,
54146a81465SCarson Labrado     };
54246a81465SCarson Labrado 
54346a81465SCarson Labrado     static void
544e002dbc0SCarson Labrado         startAggregation(AggregationType aggType, const crow::Request& thisReq,
54546a81465SCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
54646a81465SCarson Labrado     {
547e002dbc0SCarson Labrado         if (thisReq.method() != boost::beast::http::verb::get)
548e002dbc0SCarson Labrado         {
549e002dbc0SCarson Labrado             if (aggType == AggregationType::Collection)
550db18fc98SCarson Labrado             {
55162598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
55262598e31SEd Tanous                     "Only aggregate GET requests to top level collections");
553db18fc98SCarson Labrado                 return;
554db18fc98SCarson Labrado             }
555db18fc98SCarson Labrado 
556e002dbc0SCarson Labrado             if (aggType == AggregationType::ContainsSubordinate)
557e002dbc0SCarson Labrado             {
55862598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
55962598e31SEd Tanous                     "Only aggregate GET requests when uptree of a top level collection");
560e002dbc0SCarson Labrado                 return;
561e002dbc0SCarson Labrado             }
562e002dbc0SCarson Labrado         }
563e002dbc0SCarson Labrado 
56446a81465SCarson Labrado         // Create a copy of thisReq so we we can still locally process the req
56546a81465SCarson Labrado         std::error_code ec;
56646a81465SCarson Labrado         auto localReq = std::make_shared<crow::Request>(thisReq.req, ec);
56746a81465SCarson Labrado         if (ec)
56846a81465SCarson Labrado         {
56962598e31SEd Tanous             BMCWEB_LOG_ERROR("Failed to create copy of request");
570e002dbc0SCarson Labrado             if (aggType == AggregationType::Resource)
57146a81465SCarson Labrado             {
57246a81465SCarson Labrado                 messages::internalError(asyncResp->res);
57346a81465SCarson Labrado             }
57446a81465SCarson Labrado             return;
57546a81465SCarson Labrado         }
57646a81465SCarson Labrado 
577e002dbc0SCarson Labrado         getSatelliteConfigs(
578e002dbc0SCarson Labrado             std::bind_front(aggregateAndHandle, aggType, localReq, asyncResp));
57946a81465SCarson Labrado     }
58046a81465SCarson Labrado 
581db18fc98SCarson Labrado     static void findSatellite(
58246a81465SCarson Labrado         const crow::Request& req,
58346a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
58446a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo,
58546a81465SCarson Labrado         std::string_view memberName)
58646a81465SCarson Labrado     {
58746a81465SCarson Labrado         // Determine if the resource ID begins with a known prefix
58846a81465SCarson Labrado         for (const auto& satellite : satelliteInfo)
58946a81465SCarson Labrado         {
59046a81465SCarson Labrado             std::string targetPrefix = satellite.first;
59146a81465SCarson Labrado             targetPrefix += "_";
59246a81465SCarson Labrado             if (memberName.starts_with(targetPrefix))
59346a81465SCarson Labrado             {
59462598e31SEd Tanous                 BMCWEB_LOG_DEBUG("\"{}\" is a known prefix", satellite.first);
59546a81465SCarson Labrado 
59646a81465SCarson Labrado                 // Remove the known prefix from the request's URI and
59746a81465SCarson Labrado                 // then forward to the associated satellite BMC
59846a81465SCarson Labrado                 getInstance().forwardRequest(req, asyncResp, satellite.first,
59946a81465SCarson Labrado                                              satelliteInfo);
60046a81465SCarson Labrado                 return;
60146a81465SCarson Labrado             }
60246a81465SCarson Labrado         }
603db18fc98SCarson Labrado 
604db18fc98SCarson Labrado         // We didn't recognize the prefix and need to return a 404
60539662a3bSEd Tanous         std::string nameStr = req.url().segments().back();
606db18fc98SCarson Labrado         messages::resourceNotFound(asyncResp->res, "", nameStr);
60746a81465SCarson Labrado     }
60846a81465SCarson Labrado 
60946a81465SCarson Labrado     // Intended to handle an incoming request based on if Redfish Aggregation
61046a81465SCarson Labrado     // is enabled.  Forwards request to satellite BMC if it exists.
61146a81465SCarson Labrado     static void aggregateAndHandle(
612e002dbc0SCarson Labrado         AggregationType aggType,
61346a81465SCarson Labrado         const std::shared_ptr<crow::Request>& sharedReq,
61446a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6158b2521a5SCarson Labrado         const boost::system::error_code& ec,
61646a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
61746a81465SCarson Labrado     {
61846a81465SCarson Labrado         if (sharedReq == nullptr)
61946a81465SCarson Labrado         {
62046a81465SCarson Labrado             return;
62146a81465SCarson Labrado         }
6228b2521a5SCarson Labrado         // Something went wrong while querying dbus
6238b2521a5SCarson Labrado         if (ec)
6248b2521a5SCarson Labrado         {
6258b2521a5SCarson Labrado             messages::internalError(asyncResp->res);
6268b2521a5SCarson Labrado             return;
6278b2521a5SCarson Labrado         }
628db18fc98SCarson Labrado 
629db18fc98SCarson Labrado         // No satellite configs means we don't need to keep attempting to
630db18fc98SCarson Labrado         // aggregate
631db18fc98SCarson Labrado         if (satelliteInfo.empty())
632db18fc98SCarson Labrado         {
633e002dbc0SCarson Labrado             // For collections or resources that can contain a subordinate
634e002dbc0SCarson Labrado             // top level collection we'll also handle the request locally so we
635db18fc98SCarson Labrado             // don't need to write an error code
636e002dbc0SCarson Labrado             if (aggType == AggregationType::Resource)
637db18fc98SCarson Labrado             {
63839662a3bSEd Tanous                 std::string nameStr = sharedReq->url().segments().back();
639db18fc98SCarson Labrado                 messages::resourceNotFound(asyncResp->res, "", nameStr);
640db18fc98SCarson Labrado             }
641db18fc98SCarson Labrado             return;
642db18fc98SCarson Labrado         }
643db18fc98SCarson Labrado 
64446a81465SCarson Labrado         const crow::Request& thisReq = *sharedReq;
64562598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation is enabled, begin processing of {}",
64662598e31SEd Tanous                          thisReq.target());
64746a81465SCarson Labrado 
64846a81465SCarson Labrado         // We previously determined the request is for a collection.  No need to
64946a81465SCarson Labrado         // check again
650e002dbc0SCarson Labrado         if (aggType == AggregationType::Collection)
65146a81465SCarson Labrado         {
65262598e31SEd Tanous             BMCWEB_LOG_DEBUG("Aggregating a collection");
6534c30e226SCarson Labrado             // We need to use a specific response handler and send the
6544c30e226SCarson Labrado             // request to all known satellites
6554c30e226SCarson Labrado             getInstance().forwardCollectionRequests(thisReq, asyncResp,
6564c30e226SCarson Labrado                                                     satelliteInfo);
65746a81465SCarson Labrado             return;
65846a81465SCarson Labrado         }
65946a81465SCarson Labrado 
660e002dbc0SCarson Labrado         // We previously determined the request may contain a subordinate
661e002dbc0SCarson Labrado         // collection.  No need to check again
662e002dbc0SCarson Labrado         if (aggType == AggregationType::ContainsSubordinate)
663e002dbc0SCarson Labrado         {
66462598e31SEd Tanous             BMCWEB_LOG_DEBUG(
66562598e31SEd Tanous                 "Aggregating what may have a subordinate collection");
666e002dbc0SCarson Labrado             // We need to use a specific response handler and send the
667e002dbc0SCarson Labrado             // request to all known satellites
668e002dbc0SCarson Labrado             getInstance().forwardContainsSubordinateRequests(thisReq, asyncResp,
669e002dbc0SCarson Labrado                                                              satelliteInfo);
670e002dbc0SCarson Labrado             return;
671e002dbc0SCarson Labrado         }
672e002dbc0SCarson Labrado 
67339662a3bSEd Tanous         const boost::urls::segments_view urlSegments = thisReq.url().segments();
6747c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
6754a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
6764a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
6777c4c52cbSCarson Labrado 
6787c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
6797c4c52cbSCarson Labrado         it++;
6807c4c52cbSCarson Labrado         it++;
6817c4c52cbSCarson Labrado         for (; it != end; it++)
68246a81465SCarson Labrado         {
6837c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
6847c4c52cbSCarson Labrado                                    currentUrl.buffer()))
6857c4c52cbSCarson Labrado             {
6867c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
6877c4c52cbSCarson Labrado                 // must contain an aggregation prefix
6887c4c52cbSCarson Labrado                 findSatellite(thisReq, asyncResp, satelliteInfo, *it);
68946a81465SCarson Labrado                 return;
69046a81465SCarson Labrado             }
69146a81465SCarson Labrado 
6927c4c52cbSCarson Labrado             currentUrl.segments().push_back(*it);
69346a81465SCarson Labrado         }
694db18fc98SCarson Labrado 
695db18fc98SCarson Labrado         // We shouldn't reach this point since we should've hit one of the
696db18fc98SCarson Labrado         // previous exits
697db18fc98SCarson Labrado         messages::internalError(asyncResp->res);
69846a81465SCarson Labrado     }
69946a81465SCarson Labrado 
70046a81465SCarson Labrado     // Attempt to forward a request to the satellite BMC associated with the
70146a81465SCarson Labrado     // prefix.
70246a81465SCarson Labrado     void forwardRequest(
70346a81465SCarson Labrado         const crow::Request& thisReq,
70446a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
70546a81465SCarson Labrado         const std::string& prefix,
70646a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
70746a81465SCarson Labrado     {
70846a81465SCarson Labrado         const auto& sat = satelliteInfo.find(prefix);
70946a81465SCarson Labrado         if (sat == satelliteInfo.end())
71046a81465SCarson Labrado         {
71146a81465SCarson Labrado             // Realistically this shouldn't get called since we perform an
71246a81465SCarson Labrado             // earlier check to make sure the prefix exists
71362598e31SEd Tanous             BMCWEB_LOG_ERROR("Unrecognized satellite prefix \"{}\"", prefix);
71446a81465SCarson Labrado             return;
71546a81465SCarson Labrado         }
71646a81465SCarson Labrado 
71746a81465SCarson Labrado         // We need to strip the prefix from the request's path
718a716aa74SEd Tanous         boost::urls::url targetURI(thisReq.target());
719a716aa74SEd Tanous         std::string path = thisReq.url().path();
720a716aa74SEd Tanous         size_t pos = path.find(prefix + "_");
72146a81465SCarson Labrado         if (pos == std::string::npos)
72246a81465SCarson Labrado         {
72346a81465SCarson Labrado             // If this fails then something went wrong
72462598e31SEd Tanous             BMCWEB_LOG_ERROR("Error removing prefix \"{}_\" from request URI",
72562598e31SEd Tanous                              prefix);
72646a81465SCarson Labrado             messages::internalError(asyncResp->res);
72746a81465SCarson Labrado             return;
72846a81465SCarson Labrado         }
729a716aa74SEd Tanous         path.erase(pos, prefix.size() + 1);
73046a81465SCarson Labrado 
73146a81465SCarson Labrado         std::function<void(crow::Response&)> cb =
7321c0bb5c6SCarson Labrado             std::bind_front(processResponse, prefix, asyncResp);
73346a81465SCarson Labrado 
73427b0cf90SEd Tanous         std::string data = thisReq.body();
735a716aa74SEd Tanous         boost::urls::url url(sat->second);
736a716aa74SEd Tanous         url.set_path(path);
737a716aa74SEd Tanous         if (targetURI.has_query())
738a716aa74SEd Tanous         {
739a716aa74SEd Tanous             url.set_query(targetURI.query());
740a716aa74SEd Tanous         }
74119bb362bSEd Tanous         client.sendDataWithCallback(std::move(data), url,
74219bb362bSEd Tanous                                     ensuressl::VerifyCertificate::Verify,
74319bb362bSEd Tanous                                     thisReq.fields(), thisReq.method(), cb);
74446a81465SCarson Labrado     }
74546a81465SCarson Labrado 
7464c30e226SCarson Labrado     // Forward a request for a collection URI to each known satellite BMC
7474c30e226SCarson Labrado     void forwardCollectionRequests(
7484c30e226SCarson Labrado         const crow::Request& thisReq,
7494c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
7504c30e226SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
7514c30e226SCarson Labrado     {
7524c30e226SCarson Labrado         for (const auto& sat : satelliteInfo)
7534c30e226SCarson Labrado         {
7544c30e226SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
7554c30e226SCarson Labrado                 processCollectionResponse, sat.first, asyncResp);
7564c30e226SCarson Labrado 
757a716aa74SEd Tanous             boost::urls::url url(sat.second);
758a716aa74SEd Tanous             url.set_path(thisReq.url().path());
759a716aa74SEd Tanous             if (thisReq.url().has_query())
760a716aa74SEd Tanous             {
761a716aa74SEd Tanous                 url.set_query(thisReq.url().query());
762a716aa74SEd Tanous             }
76327b0cf90SEd Tanous             std::string data = thisReq.body();
76419bb362bSEd Tanous             client.sendDataWithCallback(std::move(data), url,
76519bb362bSEd Tanous                                         ensuressl::VerifyCertificate::Verify,
76619bb362bSEd Tanous                                         thisReq.fields(), thisReq.method(), cb);
7674c30e226SCarson Labrado         }
7684c30e226SCarson Labrado     }
7694c30e226SCarson Labrado 
770e002dbc0SCarson Labrado     // Forward request for a URI that is uptree of a top level collection to
771e002dbc0SCarson Labrado     // each known satellite BMC
772e002dbc0SCarson Labrado     void forwardContainsSubordinateRequests(
773e002dbc0SCarson Labrado         const crow::Request& thisReq,
774e002dbc0SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
775e002dbc0SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
776e002dbc0SCarson Labrado     {
777e002dbc0SCarson Labrado         for (const auto& sat : satelliteInfo)
778e002dbc0SCarson Labrado         {
779e002dbc0SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
780e002dbc0SCarson Labrado                 processContainsSubordinateResponse, sat.first, asyncResp);
781e002dbc0SCarson Labrado 
782e002dbc0SCarson Labrado             // will ignore an expanded resource in the response if that resource
783e002dbc0SCarson Labrado             // is not already supported by the aggregating BMC
784e002dbc0SCarson Labrado             // TODO: Improve the processing so that we don't have to strip query
785e002dbc0SCarson Labrado             // params in this specific case
786a716aa74SEd Tanous             boost::urls::url url(sat.second);
787a716aa74SEd Tanous             url.set_path(thisReq.url().path());
788a716aa74SEd Tanous 
78927b0cf90SEd Tanous             std::string data = thisReq.body();
790a716aa74SEd Tanous 
79119bb362bSEd Tanous             client.sendDataWithCallback(std::move(data), url,
79219bb362bSEd Tanous                                         ensuressl::VerifyCertificate::Verify,
79319bb362bSEd Tanous                                         thisReq.fields(), thisReq.method(), cb);
794e002dbc0SCarson Labrado         }
795e002dbc0SCarson Labrado     }
796e002dbc0SCarson Labrado 
79732d7d8ebSCarson Labrado   public:
798f8ca6d79SEd Tanous     explicit RedfishAggregator(boost::asio::io_context& ioc) :
799f8ca6d79SEd Tanous         client(ioc,
800f8ca6d79SEd Tanous                std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy()))
801f8ca6d79SEd Tanous     {
802f8ca6d79SEd Tanous         getSatelliteConfigs(constructorCallback);
803f8ca6d79SEd Tanous     }
80432d7d8ebSCarson Labrado     RedfishAggregator(const RedfishAggregator&) = delete;
80532d7d8ebSCarson Labrado     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
80632d7d8ebSCarson Labrado     RedfishAggregator(RedfishAggregator&&) = delete;
80732d7d8ebSCarson Labrado     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
80832d7d8ebSCarson Labrado     ~RedfishAggregator() = default;
80932d7d8ebSCarson Labrado 
810f8ca6d79SEd Tanous     static RedfishAggregator& getInstance(boost::asio::io_context* io = nullptr)
81132d7d8ebSCarson Labrado     {
812f8ca6d79SEd Tanous         static RedfishAggregator handler(*io);
81332d7d8ebSCarson Labrado         return handler;
81432d7d8ebSCarson Labrado     }
81532d7d8ebSCarson Labrado 
8168b2521a5SCarson Labrado     // Polls D-Bus to get all available satellite config information
8178b2521a5SCarson Labrado     // Expects a handler which interacts with the returned configs
8188b2521a5SCarson Labrado     static void getSatelliteConfigs(
8198b2521a5SCarson Labrado         std::function<
8208b2521a5SCarson Labrado             void(const boost::system::error_code&,
8218b2521a5SCarson Labrado                  const std::unordered_map<std::string, boost::urls::url>&)>
8228b2521a5SCarson Labrado             handler)
8238b2521a5SCarson Labrado     {
82462598e31SEd Tanous         BMCWEB_LOG_DEBUG("Gathering satellite configs");
8255eb468daSGeorge Liu         sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
8265eb468daSGeorge Liu         dbus::utility::getManagedObjects(
8275eb468daSGeorge Liu             "xyz.openbmc_project.EntityManager", path,
8288b2521a5SCarson Labrado             [handler{std::move(handler)}](
8298b2521a5SCarson Labrado                 const boost::system::error_code& ec,
8308b2521a5SCarson Labrado                 const dbus::utility::ManagedObjectType& objects) {
8318b2521a5SCarson Labrado                 std::unordered_map<std::string, boost::urls::url> satelliteInfo;
8328b2521a5SCarson Labrado                 if (ec)
8338b2521a5SCarson Labrado                 {
83462598e31SEd Tanous                     BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(),
83562598e31SEd Tanous                                      ec.message());
8368b2521a5SCarson Labrado                     handler(ec, satelliteInfo);
8378b2521a5SCarson Labrado                     return;
8388b2521a5SCarson Labrado                 }
8398b2521a5SCarson Labrado 
8408b2521a5SCarson Labrado                 // Maps a chosen alias representing a satellite BMC to a url
8418b2521a5SCarson Labrado                 // containing the information required to create a http
8428b2521a5SCarson Labrado                 // connection to the satellite
8438b2521a5SCarson Labrado                 findSatelliteConfigs(objects, satelliteInfo);
8448b2521a5SCarson Labrado 
8458b2521a5SCarson Labrado                 if (!satelliteInfo.empty())
8468b2521a5SCarson Labrado                 {
84762598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
84862598e31SEd Tanous                         "Redfish Aggregation enabled with {} satellite BMCs",
84962598e31SEd Tanous                         std::to_string(satelliteInfo.size()));
8508b2521a5SCarson Labrado                 }
8518b2521a5SCarson Labrado                 else
8528b2521a5SCarson Labrado                 {
85362598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
85462598e31SEd Tanous                         "No satellite BMCs detected.  Redfish Aggregation not enabled");
8558b2521a5SCarson Labrado                 }
8568b2521a5SCarson Labrado                 handler(ec, satelliteInfo);
8575eb468daSGeorge Liu             });
8588b2521a5SCarson Labrado     }
8598b2521a5SCarson Labrado 
86046a81465SCarson Labrado     // Processes the response returned by a satellite BMC and loads its
86146a81465SCarson Labrado     // contents into asyncResp
86246a81465SCarson Labrado     static void
8631c0bb5c6SCarson Labrado         processResponse(std::string_view prefix,
8641c0bb5c6SCarson Labrado                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
86546a81465SCarson Labrado                         crow::Response& resp)
86646a81465SCarson Labrado     {
86743e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
86843e14d38SCarson Labrado         // overwrite the response headers in that case
86946b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
87046b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
87143e14d38SCarson Labrado         {
87243e14d38SCarson Labrado             asyncResp->res.result(resp.result());
87343e14d38SCarson Labrado             return;
87443e14d38SCarson Labrado         }
87543e14d38SCarson Labrado 
87632d7d8ebSCarson Labrado         // We want to attempt prefix fixing regardless of response code
87746a81465SCarson Labrado         // The resp will not have a json component
87846a81465SCarson Labrado         // We need to create a json from resp's stringResponse
87918f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
88046a81465SCarson Labrado         {
881bd79bce8SPatrick Williams             nlohmann::json jsonVal =
882bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
88346a81465SCarson Labrado             if (jsonVal.is_discarded())
88446a81465SCarson Labrado             {
88562598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
88646a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
88746a81465SCarson Labrado                 return;
88846a81465SCarson Labrado             }
88946a81465SCarson Labrado 
89062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
89146a81465SCarson Labrado 
8921c0bb5c6SCarson Labrado             addPrefixes(jsonVal, prefix);
8931c0bb5c6SCarson Labrado 
89462598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
8951c0bb5c6SCarson Labrado 
89646a81465SCarson Labrado             asyncResp->res.result(resp.result());
89746a81465SCarson Labrado             asyncResp->res.jsonValue = std::move(jsonVal);
89846a81465SCarson Labrado 
89962598e31SEd Tanous             BMCWEB_LOG_DEBUG("Finished writing asyncResp");
90046a81465SCarson Labrado         }
90146a81465SCarson Labrado         else
90246a81465SCarson Labrado         {
9030af78d5aSKhang Kieu             // We allow any Content-Type that is not "application/json" now
9040af78d5aSKhang Kieu             asyncResp->res.result(resp.result());
90527b0cf90SEd Tanous             asyncResp->res.copyBody(resp);
90646a81465SCarson Labrado         }
9070af78d5aSKhang Kieu         addAggregatedHeaders(asyncResp->res, resp, prefix);
90846a81465SCarson Labrado     }
90946a81465SCarson Labrado 
9104c30e226SCarson Labrado     // Processes the collection response returned by a satellite BMC and merges
9114c30e226SCarson Labrado     // its "@odata.id" values
9124c30e226SCarson Labrado     static void processCollectionResponse(
9134c30e226SCarson Labrado         const std::string& prefix,
9144c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
9154c30e226SCarson Labrado         crow::Response& resp)
9164c30e226SCarson Labrado     {
91743e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
91843e14d38SCarson Labrado         // overwrite the response headers in that case
91946b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
92046b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
92143e14d38SCarson Labrado         {
92243e14d38SCarson Labrado             return;
92343e14d38SCarson Labrado         }
92443e14d38SCarson Labrado 
9254c30e226SCarson Labrado         if (resp.resultInt() != 200)
9264c30e226SCarson Labrado         {
92762598e31SEd Tanous             BMCWEB_LOG_DEBUG(
92862598e31SEd Tanous                 "Collection resource does not exist in satellite BMC \"{}\"",
92962598e31SEd Tanous                 prefix);
9304c30e226SCarson Labrado             // Return the error if we haven't had any successes
9314c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
9324c30e226SCarson Labrado             {
93346b30283SCarson Labrado                 asyncResp->res.result(resp.result());
93427b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
9354c30e226SCarson Labrado             }
9364c30e226SCarson Labrado             return;
9374c30e226SCarson Labrado         }
9384c30e226SCarson Labrado 
9394c30e226SCarson Labrado         // The resp will not have a json component
9404c30e226SCarson Labrado         // We need to create a json from resp's stringResponse
94118f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
9424c30e226SCarson Labrado         {
943bd79bce8SPatrick Williams             nlohmann::json jsonVal =
944bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
9454c30e226SCarson Labrado             if (jsonVal.is_discarded())
9464c30e226SCarson Labrado             {
94762598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
9484c30e226SCarson Labrado 
9494c30e226SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
95046b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
9514c30e226SCarson Labrado                 {
9524c30e226SCarson Labrado                     messages::operationFailed(asyncResp->res);
9534c30e226SCarson Labrado                 }
9544c30e226SCarson Labrado                 return;
9554c30e226SCarson Labrado             }
9564c30e226SCarson Labrado 
95762598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
9584c30e226SCarson Labrado 
9594c30e226SCarson Labrado             // Now we need to add the prefix to the URIs contained in the
9604c30e226SCarson Labrado             // response.
9614c30e226SCarson Labrado             addPrefixes(jsonVal, prefix);
9624c30e226SCarson Labrado 
96362598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
9644c30e226SCarson Labrado 
9654c30e226SCarson Labrado             // If this resource collection does not exist on the aggregating bmc
9664c30e226SCarson Labrado             // and has not already been added from processing the response from
9674c30e226SCarson Labrado             // a different satellite then we need to completely overwrite
9684c30e226SCarson Labrado             // asyncResp
9694c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
9704c30e226SCarson Labrado             {
9714c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
9724c30e226SCarson Labrado                 // "Members" array
9734c30e226SCarson Labrado                 if ((!jsonVal.contains("Members")) &&
9744c30e226SCarson Labrado                     (!jsonVal["Members"].is_array()))
9754c30e226SCarson Labrado                 {
97662598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
97762598e31SEd Tanous                         "Skipping aggregating unsupported resource");
9784c30e226SCarson Labrado                     return;
9794c30e226SCarson Labrado                 }
9804c30e226SCarson Labrado 
98162598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
98262598e31SEd Tanous                     "Collection does not exist, overwriting asyncResp");
9834c30e226SCarson Labrado                 asyncResp->res.result(resp.result());
9844c30e226SCarson Labrado                 asyncResp->res.jsonValue = std::move(jsonVal);
98543e14d38SCarson Labrado                 asyncResp->res.addHeader("Content-Type", "application/json");
9864c30e226SCarson Labrado 
98762598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Finished overwriting asyncResp");
9884c30e226SCarson Labrado             }
9894c30e226SCarson Labrado             else
9904c30e226SCarson Labrado             {
9914c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
9924c30e226SCarson Labrado                 // "Members" array
9934c30e226SCarson Labrado                 if ((!asyncResp->res.jsonValue.contains("Members")) &&
9944c30e226SCarson Labrado                     (!asyncResp->res.jsonValue["Members"].is_array()))
9954c30e226SCarson Labrado 
9964c30e226SCarson Labrado                 {
99762598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
99862598e31SEd Tanous                         "Skipping aggregating unsupported resource");
9994c30e226SCarson Labrado                     return;
10004c30e226SCarson Labrado                 }
10014c30e226SCarson Labrado 
100262598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
100362598e31SEd Tanous                     "Adding aggregated resources from \"{}\" to collection",
100462598e31SEd Tanous                     prefix);
10054c30e226SCarson Labrado 
10064c30e226SCarson Labrado                 // TODO: This is a potential race condition with multiple
10074c30e226SCarson Labrado                 // satellites and the aggregating bmc attempting to write to
10084c30e226SCarson Labrado                 // update this array.  May need to cascade calls to the next
10094c30e226SCarson Labrado                 // satellite at the end of this function.
10104c30e226SCarson Labrado                 // This is presumably not a concern when there is only a single
10114c30e226SCarson Labrado                 // satellite since the aggregating bmc should have completed
10124c30e226SCarson Labrado                 // before the response is received from the satellite.
10134c30e226SCarson Labrado 
10144c30e226SCarson Labrado                 auto& members = asyncResp->res.jsonValue["Members"];
10154c30e226SCarson Labrado                 auto& satMembers = jsonVal["Members"];
10164c30e226SCarson Labrado                 for (auto& satMem : satMembers)
10174c30e226SCarson Labrado                 {
1018b2ba3072SPatrick Williams                     members.emplace_back(std::move(satMem));
10194c30e226SCarson Labrado                 }
10204c30e226SCarson Labrado                 asyncResp->res.jsonValue["Members@odata.count"] =
10214c30e226SCarson Labrado                     members.size();
10224c30e226SCarson Labrado 
10234c30e226SCarson Labrado                 // TODO: Do we need to sort() after updating the array?
10244c30e226SCarson Labrado             }
10254c30e226SCarson Labrado         }
10264c30e226SCarson Labrado         else
10274c30e226SCarson Labrado         {
102862598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
102962598e31SEd Tanous                              prefix);
103043e14d38SCarson Labrado             // We received a response that was not a json.
103146b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses
103246b30283SCarson Labrado             // and if the resource collection does not already exist on the
103346b30283SCarson Labrado             // aggregating BMC
103446b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10354c30e226SCarson Labrado             {
10364c30e226SCarson Labrado                 messages::operationFailed(asyncResp->res);
10374c30e226SCarson Labrado             }
10384c30e226SCarson Labrado         }
10394c30e226SCarson Labrado     } // End processCollectionResponse()
10404c30e226SCarson Labrado 
104146b30283SCarson Labrado     // Processes the response returned by a satellite BMC and merges any
104246b30283SCarson Labrado     // properties whose "@odata.id" value is the URI or either a top level
104346b30283SCarson Labrado     // collection or is uptree from a top level collection
104446b30283SCarson Labrado     static void processContainsSubordinateResponse(
104546b30283SCarson Labrado         const std::string& prefix,
104646b30283SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
104746b30283SCarson Labrado         crow::Response& resp)
104846b30283SCarson Labrado     {
104946b30283SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
105046b30283SCarson Labrado         // overwrite the response headers in that case
105146b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
105246b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
105346b30283SCarson Labrado         {
105446b30283SCarson Labrado             return;
105546b30283SCarson Labrado         }
105646b30283SCarson Labrado 
105746b30283SCarson Labrado         if (resp.resultInt() != 200)
105846b30283SCarson Labrado         {
105962598e31SEd Tanous             BMCWEB_LOG_DEBUG(
106062598e31SEd Tanous                 "Resource uptree from Collection does not exist in satellite BMC \"{}\"",
106162598e31SEd Tanous                 prefix);
106246b30283SCarson Labrado             // Return the error if we haven't had any successes
106346b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
106446b30283SCarson Labrado             {
106546b30283SCarson Labrado                 asyncResp->res.result(resp.result());
106627b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
106746b30283SCarson Labrado             }
106846b30283SCarson Labrado             return;
106946b30283SCarson Labrado         }
107046b30283SCarson Labrado 
107146b30283SCarson Labrado         // The resp will not have a json component
107246b30283SCarson Labrado         // We need to create a json from resp's stringResponse
107318f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
107446b30283SCarson Labrado         {
107546b30283SCarson Labrado             bool addedLinks = false;
1076bd79bce8SPatrick Williams             nlohmann::json jsonVal =
1077bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
107846b30283SCarson Labrado             if (jsonVal.is_discarded())
107946b30283SCarson Labrado             {
108062598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
108146b30283SCarson Labrado 
108246b30283SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
108346b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
108446b30283SCarson Labrado                 {
108546b30283SCarson Labrado                     messages::operationFailed(asyncResp->res);
108646b30283SCarson Labrado                 }
108746b30283SCarson Labrado                 return;
108846b30283SCarson Labrado             }
108946b30283SCarson Labrado 
109062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
109146b30283SCarson Labrado 
109246b30283SCarson Labrado             // Parse response and add properties missing from the AsyncResp
109346b30283SCarson Labrado             // Valid properties will be of the form <property>.@odata.id and
109446b30283SCarson Labrado             // @odata.id is a <URI>.  In other words, the json should contain
109546b30283SCarson Labrado             // multiple properties such that
109646b30283SCarson Labrado             // {"<property>":{"@odata.id": "<URI>"}}
109746b30283SCarson Labrado             nlohmann::json::object_t* object =
109846b30283SCarson Labrado                 jsonVal.get_ptr<nlohmann::json::object_t*>();
109946b30283SCarson Labrado             if (object == nullptr)
110046b30283SCarson Labrado             {
110162598e31SEd Tanous                 BMCWEB_LOG_ERROR("Parsed JSON was not an object?");
110246b30283SCarson Labrado                 return;
110346b30283SCarson Labrado             }
110446b30283SCarson Labrado 
110546b30283SCarson Labrado             for (std::pair<const std::string, nlohmann::json>& prop : *object)
110646b30283SCarson Labrado             {
110746b30283SCarson Labrado                 if (!prop.second.contains("@odata.id"))
110846b30283SCarson Labrado                 {
110946b30283SCarson Labrado                     continue;
111046b30283SCarson Labrado                 }
111146b30283SCarson Labrado 
111246b30283SCarson Labrado                 std::string* strValue =
111346b30283SCarson Labrado                     prop.second["@odata.id"].get_ptr<std::string*>();
111446b30283SCarson Labrado                 if (strValue == nullptr)
111546b30283SCarson Labrado                 {
111662598e31SEd Tanous                     BMCWEB_LOG_CRITICAL("Field wasn't a string????");
111746b30283SCarson Labrado                     continue;
111846b30283SCarson Labrado                 }
111946b30283SCarson Labrado                 if (!searchCollectionsArray(*strValue, SearchType::CollOrCon))
112046b30283SCarson Labrado                 {
112146b30283SCarson Labrado                     continue;
112246b30283SCarson Labrado                 }
112346b30283SCarson Labrado 
112446b30283SCarson Labrado                 addedLinks = true;
112546b30283SCarson Labrado                 if (!asyncResp->res.jsonValue.contains(prop.first))
112646b30283SCarson Labrado                 {
112746b30283SCarson Labrado                     // Only add the property if it did not already exist
112862598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Adding link for {} from BMC {}",
112962598e31SEd Tanous                                      *strValue, prefix);
113046b30283SCarson Labrado                     asyncResp->res.jsonValue[prop.first]["@odata.id"] =
113146b30283SCarson Labrado                         *strValue;
113246b30283SCarson Labrado                     continue;
113346b30283SCarson Labrado                 }
113446b30283SCarson Labrado             }
113546b30283SCarson Labrado 
113646b30283SCarson Labrado             // If we added links to a previously unsuccessful (non-200) response
113746b30283SCarson Labrado             // then we need to make sure the response contains the bare minimum
113846b30283SCarson Labrado             // amount of additional information that we'd expect to have been
113946b30283SCarson Labrado             // populated.
114046b30283SCarson Labrado             if (addedLinks && (asyncResp->res.resultInt() != 200))
114146b30283SCarson Labrado             {
114246b30283SCarson Labrado                 // This resource didn't locally exist or an error
114346b30283SCarson Labrado                 // occurred while generating the response.  Remove any
114446b30283SCarson Labrado                 // error messages and update the error code.
114546b30283SCarson Labrado                 asyncResp->res.jsonValue.erase(
114646b30283SCarson Labrado                     asyncResp->res.jsonValue.find("error"));
114746b30283SCarson Labrado                 asyncResp->res.result(resp.result());
114846b30283SCarson Labrado 
114946b30283SCarson Labrado                 const auto& it1 = object->find("@odata.id");
115046b30283SCarson Labrado                 if (it1 != object->end())
115146b30283SCarson Labrado                 {
115246b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.id"] = (it1->second);
115346b30283SCarson Labrado                 }
115446b30283SCarson Labrado                 const auto& it2 = object->find("@odata.type");
115546b30283SCarson Labrado                 if (it2 != object->end())
115646b30283SCarson Labrado                 {
115746b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.type"] = (it2->second);
115846b30283SCarson Labrado                 }
115946b30283SCarson Labrado                 const auto& it3 = object->find("Id");
116046b30283SCarson Labrado                 if (it3 != object->end())
116146b30283SCarson Labrado                 {
116246b30283SCarson Labrado                     asyncResp->res.jsonValue["Id"] = (it3->second);
116346b30283SCarson Labrado                 }
116446b30283SCarson Labrado                 const auto& it4 = object->find("Name");
116546b30283SCarson Labrado                 if (it4 != object->end())
116646b30283SCarson Labrado                 {
116746b30283SCarson Labrado                     asyncResp->res.jsonValue["Name"] = (it4->second);
116846b30283SCarson Labrado                 }
116946b30283SCarson Labrado             }
117046b30283SCarson Labrado         }
117146b30283SCarson Labrado         else
117246b30283SCarson Labrado         {
117362598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
117462598e31SEd Tanous                              prefix);
117546b30283SCarson Labrado             // We received as response that was not a json
117646b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses,
117746b30283SCarson Labrado             // and if the resource does not already exist on the aggregating BMC
117846b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
117946b30283SCarson Labrado             {
118046b30283SCarson Labrado                 messages::operationFailed(asyncResp->res);
118146b30283SCarson Labrado             }
118246b30283SCarson Labrado         }
118346b30283SCarson Labrado     }
118446b30283SCarson Labrado 
118505916cefSCarson Labrado     // Entry point to Redfish Aggregation
118605916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
118705916cefSCarson Labrado     // request
118805916cefSCarson Labrado     static Result
118905916cefSCarson Labrado         beginAggregation(const crow::Request& thisReq,
119005916cefSCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
119105916cefSCarson Labrado     {
119205916cefSCarson Labrado         using crow::utility::OrMorePaths;
119305916cefSCarson Labrado         using crow::utility::readUrlSegments;
11944a7fbefdSEd Tanous         boost::urls::url_view url = thisReq.url();
1195411e6a11SCarson Labrado 
1196411e6a11SCarson Labrado         // We don't need to aggregate JsonSchemas due to potential issues such
1197411e6a11SCarson Labrado         // as version mismatches between aggregator and satellite BMCs.  For
1198411e6a11SCarson Labrado         // now assume that the aggregator has all the schemas and versions that
1199411e6a11SCarson Labrado         // the aggregated server has.
1200411e6a11SCarson Labrado         if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas",
1201411e6a11SCarson Labrado                                            crow::utility::OrMorePaths()))
1202411e6a11SCarson Labrado         {
1203411e6a11SCarson Labrado             return Result::LocalHandle;
1204411e6a11SCarson Labrado         }
1205411e6a11SCarson Labrado 
12067c4c52cbSCarson Labrado         // The first two segments should be "/redfish/v1".  We need to check
12077c4c52cbSCarson Labrado         // that before we can search topCollections
12087c4c52cbSCarson Labrado         if (!crow::utility::readUrlSegments(url, "redfish", "v1",
12097c4c52cbSCarson Labrado                                             crow::utility::OrMorePaths()))
121046a81465SCarson Labrado         {
121146a81465SCarson Labrado             return Result::LocalHandle;
121246a81465SCarson Labrado         }
121305916cefSCarson Labrado 
12147c4c52cbSCarson Labrado         // Parse the URI to see if it begins with a known top level collection
12157c4c52cbSCarson Labrado         // such as:
12167c4c52cbSCarson Labrado         // /redfish/v1/Chassis
12177c4c52cbSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory
12187c4c52cbSCarson Labrado         const boost::urls::segments_view urlSegments = url.segments();
12197c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
12204a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
12214a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
122205916cefSCarson Labrado 
12237c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
12247c4c52cbSCarson Labrado         it++;
12257c4c52cbSCarson Labrado         it++;
12267c4c52cbSCarson Labrado         for (; it != end; it++)
122705916cefSCarson Labrado         {
1228d4413c5bSGeorge Liu             const std::string& collectionItem = *it;
12297c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
12307c4c52cbSCarson Labrado                                    currentUrl.buffer()))
12317c4c52cbSCarson Labrado             {
12327c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
12337c4c52cbSCarson Labrado                 // might contain an aggregation prefix
12348b2521a5SCarson Labrado                 // TODO: This needs to be rethought when we can support multiple
12358b2521a5SCarson Labrado                 // satellites due to
12368b2521a5SCarson Labrado                 // /redfish/v1/AggregationService/AggregationSources/5B247A
12378b2521a5SCarson Labrado                 // being a local resource describing the satellite
12388b2521a5SCarson Labrado                 if (collectionItem.starts_with("5B247A_"))
123905916cefSCarson Labrado                 {
124062598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Need to forward a request");
124105916cefSCarson Labrado 
124246a81465SCarson Labrado                     // Extract the prefix from the request's URI, retrieve the
12437c4c52cbSCarson Labrado                     // associated satellite config information, and then forward
12447c4c52cbSCarson Labrado                     // the request to that satellite.
12457c4c52cbSCarson Labrado                     startAggregation(AggregationType::Resource, thisReq,
12467c4c52cbSCarson Labrado                                      asyncResp);
124705916cefSCarson Labrado                     return Result::NoLocalHandle;
124805916cefSCarson Labrado                 }
12497c4c52cbSCarson Labrado 
12507c4c52cbSCarson Labrado                 // Handle collection URI with a trailing backslash
12517c4c52cbSCarson Labrado                 // e.g. /redfish/v1/Chassis/
12527c4c52cbSCarson Labrado                 it++;
12537c4c52cbSCarson Labrado                 if ((it == end) && collectionItem.empty())
12547c4c52cbSCarson Labrado                 {
12557c4c52cbSCarson Labrado                     startAggregation(AggregationType::Collection, thisReq,
12567c4c52cbSCarson Labrado                                      asyncResp);
12577c4c52cbSCarson Labrado                 }
12587c4c52cbSCarson Labrado 
12597c4c52cbSCarson Labrado                 // We didn't recognize the prefix or it's a collection with a
12607c4c52cbSCarson Labrado                 // trailing "/".  In both cases we still want to locally handle
12617c4c52cbSCarson Labrado                 // the request
12627c4c52cbSCarson Labrado                 return Result::LocalHandle;
12637c4c52cbSCarson Labrado             }
12647c4c52cbSCarson Labrado 
12657c4c52cbSCarson Labrado             currentUrl.segments().push_back(collectionItem);
12667c4c52cbSCarson Labrado         }
12677c4c52cbSCarson Labrado 
12687c4c52cbSCarson Labrado         // If we made it here then currentUrl could contain a top level
12697c4c52cbSCarson Labrado         // collection URI without a trailing "/", e.g. /redfish/v1/Chassis
12707c4c52cbSCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
12717c4c52cbSCarson Labrado                                currentUrl.buffer()))
12727c4c52cbSCarson Labrado         {
12737c4c52cbSCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
127405916cefSCarson Labrado             return Result::LocalHandle;
127505916cefSCarson Labrado         }
127605916cefSCarson Labrado 
1277e002dbc0SCarson Labrado         // If nothing else then the request could be for a resource which has a
1278e002dbc0SCarson Labrado         // top level collection as a subordinate
1279e002dbc0SCarson Labrado         if (searchCollectionsArray(url.path(), SearchType::ContainsSubordinate))
1280e002dbc0SCarson Labrado         {
1281e002dbc0SCarson Labrado             startAggregation(AggregationType::ContainsSubordinate, thisReq,
1282e002dbc0SCarson Labrado                              asyncResp);
1283e002dbc0SCarson Labrado             return Result::LocalHandle;
1284e002dbc0SCarson Labrado         }
1285e002dbc0SCarson Labrado 
128662598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation not required for {}", url.buffer());
128705916cefSCarson Labrado         return Result::LocalHandle;
128805916cefSCarson Labrado     }
12897fb33566SCarson Labrado };
12907fb33566SCarson Labrado 
12917fb33566SCarson Labrado } // namespace redfish
1292