xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 8fd333d664f87c73757e0c5e58c611dc0c3e76d7)
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 
24*8fd333d6SCarson Labrado enum class SearchType
25*8fd333d6SCarson Labrado {
26*8fd333d6SCarson Labrado     Collection,
27*8fd333d6SCarson Labrado     CollOrCon,
28*8fd333d6SCarson Labrado     ContainsSubordinate,
29*8fd333d6SCarson Labrado     Resource
30*8fd333d6SCarson Labrado };
31*8fd333d6SCarson Labrado 
327e8890c5SCarson Labrado // clang-format off
337e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource
347e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a
357e8890c5SCarson Labrado // case-insensitive form of "uri".  That version of the schema is associated
367e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification.  Going forward, new URI
377e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as
387e8890c5SCarson Labrado // the spec is updated.  NOTE: These have been pre-sorted in order to be
397e8890c5SCarson Labrado // compatible with binary search
407e8890c5SCarson Labrado constexpr std::array nonUriProperties{
417e8890c5SCarson Labrado     "@Redfish.ActionInfo",
427e8890c5SCarson Labrado     // "@odata.context", // We can't fix /redfish/v1/$metadata URIs
437e8890c5SCarson Labrado     "@odata.id",
447e8890c5SCarson Labrado     // "Destination", // Only used by EventService and won't be a Redfish URI
457e8890c5SCarson Labrado     // "HostName", // Isn't actually a Redfish URI
467e8890c5SCarson Labrado     "Image",
477e8890c5SCarson Labrado     "MetricProperty",
4832d7d8ebSCarson Labrado     // "OriginOfCondition", // Is URI when in request, but is object in response
497e8890c5SCarson Labrado     "TaskMonitor",
507e8890c5SCarson Labrado     "target", // normal string, but target URI for POST to invoke an action
517e8890c5SCarson Labrado };
527e8890c5SCarson Labrado // clang-format on
537e8890c5SCarson Labrado 
54*8fd333d6SCarson Labrado // Search the top collection array to determine if the passed URI is of a
55*8fd333d6SCarson Labrado // desired type
56*8fd333d6SCarson Labrado inline bool searchCollectionsArray(std::string_view uri,
57*8fd333d6SCarson Labrado                                    const SearchType searchType)
58*8fd333d6SCarson Labrado {
59*8fd333d6SCarson Labrado     constexpr std::string_view serviceRootUri = "/redfish/v1";
60*8fd333d6SCarson Labrado 
61*8fd333d6SCarson Labrado     // The passed URI must begin with "/redfish/v1", but we have to strip it
62*8fd333d6SCarson Labrado     // from the URI since topCollections does not include it in its URIs
63*8fd333d6SCarson Labrado     if (!uri.starts_with(serviceRootUri))
64*8fd333d6SCarson Labrado     {
65*8fd333d6SCarson Labrado         return false;
66*8fd333d6SCarson Labrado     }
67*8fd333d6SCarson Labrado 
68*8fd333d6SCarson Labrado     // Catch empty final segments such as "/redfish/v1/Chassis//"
69*8fd333d6SCarson Labrado     if (uri.ends_with("//"))
70*8fd333d6SCarson Labrado     {
71*8fd333d6SCarson Labrado         return false;
72*8fd333d6SCarson Labrado     }
73*8fd333d6SCarson Labrado 
74*8fd333d6SCarson Labrado     std::size_t parseCount = uri.size() - serviceRootUri.size();
75*8fd333d6SCarson Labrado     // Don't include the trailing "/" if it exists such as in "/redfish/v1/"
76*8fd333d6SCarson Labrado     if (uri.ends_with("/"))
77*8fd333d6SCarson Labrado     {
78*8fd333d6SCarson Labrado         parseCount--;
79*8fd333d6SCarson Labrado     }
80*8fd333d6SCarson Labrado 
81*8fd333d6SCarson Labrado     boost::urls::result<boost::urls::url_view> parsedUrl =
82*8fd333d6SCarson Labrado         boost::urls::parse_relative_ref(
83*8fd333d6SCarson Labrado             uri.substr(serviceRootUri.size(), parseCount));
84*8fd333d6SCarson Labrado     if (!parsedUrl)
85*8fd333d6SCarson Labrado     {
86*8fd333d6SCarson Labrado         BMCWEB_LOG_ERROR << "Failed to get target URI from "
87*8fd333d6SCarson Labrado                          << uri.substr(serviceRootUri.size());
88*8fd333d6SCarson Labrado         return false;
89*8fd333d6SCarson Labrado     }
90*8fd333d6SCarson Labrado 
91*8fd333d6SCarson Labrado     if (!parsedUrl->segments().is_absolute() && !parsedUrl->segments().empty())
92*8fd333d6SCarson Labrado     {
93*8fd333d6SCarson Labrado         return false;
94*8fd333d6SCarson Labrado     }
95*8fd333d6SCarson Labrado 
96*8fd333d6SCarson Labrado     // If no segments() then the passed URI was either "/redfish/v1" or
97*8fd333d6SCarson Labrado     // "/redfish/v1/".
98*8fd333d6SCarson Labrado     if (parsedUrl->segments().empty())
99*8fd333d6SCarson Labrado     {
100*8fd333d6SCarson Labrado         return (searchType == SearchType::ContainsSubordinate) ||
101*8fd333d6SCarson Labrado                (searchType == SearchType::CollOrCon);
102*8fd333d6SCarson Labrado     }
103*8fd333d6SCarson Labrado 
104*8fd333d6SCarson Labrado     const auto* it = std::lower_bound(
105*8fd333d6SCarson Labrado         topCollections.begin(), topCollections.end(), parsedUrl->buffer());
106*8fd333d6SCarson Labrado     if (it == topCollections.end())
107*8fd333d6SCarson Labrado     {
108*8fd333d6SCarson Labrado         // parsedUrl is alphabetically after the last entry in the array so it
109*8fd333d6SCarson Labrado         // can't be a top collection or up tree from a top collection
110*8fd333d6SCarson Labrado         return false;
111*8fd333d6SCarson Labrado     }
112*8fd333d6SCarson Labrado 
113*8fd333d6SCarson Labrado     boost::urls::url collectionUrl(*it);
114*8fd333d6SCarson Labrado     boost::urls::segments_view collectionSegments = collectionUrl.segments();
115*8fd333d6SCarson Labrado     boost::urls::segments_view::iterator itCollection =
116*8fd333d6SCarson Labrado         collectionSegments.begin();
117*8fd333d6SCarson Labrado     const boost::urls::segments_view::const_iterator endCollection =
118*8fd333d6SCarson Labrado         collectionSegments.end();
119*8fd333d6SCarson Labrado 
120*8fd333d6SCarson Labrado     // Each segment in the passed URI should match the found collection
121*8fd333d6SCarson Labrado     for (const auto& segment : parsedUrl->segments())
122*8fd333d6SCarson Labrado     {
123*8fd333d6SCarson Labrado         if (itCollection == endCollection)
124*8fd333d6SCarson Labrado         {
125*8fd333d6SCarson Labrado             // Leftover segments means the target is for an aggregation
126*8fd333d6SCarson Labrado             // supported resource
127*8fd333d6SCarson Labrado             return searchType == SearchType::Resource;
128*8fd333d6SCarson Labrado         }
129*8fd333d6SCarson Labrado 
130*8fd333d6SCarson Labrado         if (segment != (*itCollection))
131*8fd333d6SCarson Labrado         {
132*8fd333d6SCarson Labrado             return false;
133*8fd333d6SCarson Labrado         }
134*8fd333d6SCarson Labrado         itCollection++;
135*8fd333d6SCarson Labrado     }
136*8fd333d6SCarson Labrado 
137*8fd333d6SCarson Labrado     // No remaining segments means the passed URI was a top level collection
138*8fd333d6SCarson Labrado     if (searchType == SearchType::Collection)
139*8fd333d6SCarson Labrado     {
140*8fd333d6SCarson Labrado         return itCollection == endCollection;
141*8fd333d6SCarson Labrado     }
142*8fd333d6SCarson Labrado     if (searchType == SearchType::ContainsSubordinate)
143*8fd333d6SCarson Labrado     {
144*8fd333d6SCarson Labrado         return itCollection != endCollection;
145*8fd333d6SCarson Labrado     }
146*8fd333d6SCarson Labrado 
147*8fd333d6SCarson Labrado     // Return this check instead of "true" in case other SearchTypes get added
148*8fd333d6SCarson Labrado     return searchType == SearchType::CollOrCon;
149*8fd333d6SCarson Labrado }
150*8fd333d6SCarson Labrado 
1517e8890c5SCarson Labrado // Determines if the passed property contains a URI.  Those property names
1527e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically
1537e8890c5SCarson Labrado // defined in the above array.
15426ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName)
1557e8890c5SCarson Labrado {
1567e8890c5SCarson Labrado     return boost::iends_with(propertyName, "uri") ||
1577e8890c5SCarson Labrado            std::binary_search(nonUriProperties.begin(), nonUriProperties.end(),
1587e8890c5SCarson Labrado                               propertyName);
1597e8890c5SCarson Labrado }
1607e8890c5SCarson Labrado 
1610af78d5aSKhang Kieu static inline void addPrefixToStringItem(std::string& strValue,
1620af78d5aSKhang Kieu                                          std::string_view prefix)
1631c0bb5c6SCarson Labrado {
1641c0bb5c6SCarson Labrado     // Make sure the value is a properly formatted URI
1650af78d5aSKhang Kieu     auto parsed = boost::urls::parse_relative_ref(strValue);
1661c0bb5c6SCarson Labrado     if (!parsed)
1671c0bb5c6SCarson Labrado     {
1680af78d5aSKhang Kieu         BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << strValue;
1691c0bb5c6SCarson Labrado         return;
1701c0bb5c6SCarson Labrado     }
1711c0bb5c6SCarson Labrado 
1721c0bb5c6SCarson Labrado     boost::urls::url_view thisUrl = *parsed;
1731c0bb5c6SCarson Labrado 
174411e6a11SCarson Labrado     // We don't need to aggregate JsonSchemas due to potential issues such as
175411e6a11SCarson Labrado     // version mismatches between aggregator and satellite BMCs.  For now
176411e6a11SCarson Labrado     // assume that the aggregator has all the schemas and versions that the
177411e6a11SCarson Labrado     // aggregated server has.
178411e6a11SCarson Labrado     if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas",
179411e6a11SCarson Labrado                                        crow::utility::OrMorePaths()))
180411e6a11SCarson Labrado     {
181411e6a11SCarson Labrado         BMCWEB_LOG_DEBUG << "Skipping JsonSchemas URI prefix fixing";
182411e6a11SCarson Labrado         return;
183411e6a11SCarson Labrado     }
184411e6a11SCarson Labrado 
18511987af6SCarson Labrado     // The first two segments should be "/redfish/v1".  We need to check that
18611987af6SCarson Labrado     // before we can search topCollections
18711987af6SCarson Labrado     if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
18811987af6SCarson Labrado                                         crow::utility::OrMorePaths()))
1891c0bb5c6SCarson Labrado     {
1901c0bb5c6SCarson Labrado         return;
1911c0bb5c6SCarson Labrado     }
1921c0bb5c6SCarson Labrado 
19311987af6SCarson Labrado     // Check array adding a segment each time until collection is identified
19411987af6SCarson Labrado     // Add prefix to segment after the collection
19511987af6SCarson Labrado     const boost::urls::segments_view urlSegments = thisUrl.segments();
19611987af6SCarson Labrado     bool addedPrefix = false;
19711987af6SCarson Labrado     boost::urls::url url("/");
19811987af6SCarson Labrado     boost::urls::segments_view::iterator it = urlSegments.begin();
19911987af6SCarson Labrado     const boost::urls::segments_view::const_iterator end = urlSegments.end();
20011987af6SCarson Labrado 
20111987af6SCarson Labrado     // Skip past the leading "/redfish/v1"
20211987af6SCarson Labrado     it++;
20311987af6SCarson Labrado     it++;
20411987af6SCarson Labrado     for (; it != end; it++)
2051c0bb5c6SCarson Labrado     {
20611987af6SCarson Labrado         // Trailing "/" will result in an empty segment.  In that case we need
20711987af6SCarson Labrado         // to return so we don't apply a prefix to top level collections such
20811987af6SCarson Labrado         // as "/redfish/v1/Chassis/"
20911987af6SCarson Labrado         if ((*it).empty())
21011987af6SCarson Labrado         {
211411e6a11SCarson Labrado             return;
2121c0bb5c6SCarson Labrado         }
2131c0bb5c6SCarson Labrado 
21411987af6SCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
21511987af6SCarson Labrado                                url.buffer()))
2161c0bb5c6SCarson Labrado         {
21711987af6SCarson Labrado             std::string collectionItem(prefix);
21811987af6SCarson Labrado             collectionItem += "_" + (*it);
21911987af6SCarson Labrado             url.segments().push_back(collectionItem);
22011987af6SCarson Labrado             it++;
22111987af6SCarson Labrado             addedPrefix = true;
22211987af6SCarson Labrado             break;
22311987af6SCarson Labrado         }
22411987af6SCarson Labrado 
22511987af6SCarson Labrado         url.segments().push_back(*it);
22611987af6SCarson Labrado     }
22711987af6SCarson Labrado 
22811987af6SCarson Labrado     // Finish constructing the URL here (if needed) to avoid additional checks
22911987af6SCarson Labrado     for (; it != end; it++)
23011987af6SCarson Labrado     {
23111987af6SCarson Labrado         url.segments().push_back(*it);
23211987af6SCarson Labrado     }
23311987af6SCarson Labrado 
23411987af6SCarson Labrado     if (addedPrefix)
23511987af6SCarson Labrado     {
23611987af6SCarson Labrado         url.segments().insert(url.segments().begin(), {"redfish", "v1"});
2370af78d5aSKhang Kieu         strValue = url.buffer();
2381c0bb5c6SCarson Labrado     }
2391c0bb5c6SCarson Labrado }
2401c0bb5c6SCarson Labrado 
2410af78d5aSKhang Kieu static inline void addPrefixToItem(nlohmann::json& item,
2420af78d5aSKhang Kieu                                    std::string_view prefix)
2430af78d5aSKhang Kieu {
2440af78d5aSKhang Kieu     std::string* strValue = item.get_ptr<std::string*>();
2450af78d5aSKhang Kieu     if (strValue == nullptr)
2460af78d5aSKhang Kieu     {
2470af78d5aSKhang Kieu         BMCWEB_LOG_CRITICAL << "Field wasn't a string????";
2480af78d5aSKhang Kieu         return;
2490af78d5aSKhang Kieu     }
2500af78d5aSKhang Kieu     addPrefixToStringItem(*strValue, prefix);
2510af78d5aSKhang Kieu     item = *strValue;
2520af78d5aSKhang Kieu }
2530af78d5aSKhang Kieu 
2540af78d5aSKhang Kieu static inline void addAggregatedHeaders(crow::Response& asyncResp,
25524dadc88SCarson Labrado                                         const crow::Response& resp,
2560af78d5aSKhang Kieu                                         std::string_view prefix)
2570af78d5aSKhang Kieu {
2580af78d5aSKhang Kieu     if (!resp.getHeaderValue("Content-Type").empty())
2590af78d5aSKhang Kieu     {
2600af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::content_type,
2610af78d5aSKhang Kieu                             resp.getHeaderValue("Content-Type"));
2620af78d5aSKhang Kieu     }
2630af78d5aSKhang Kieu     if (!resp.getHeaderValue("Allow").empty())
2640af78d5aSKhang Kieu     {
2650af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::allow,
2660af78d5aSKhang Kieu                             resp.getHeaderValue("Allow"));
2670af78d5aSKhang Kieu     }
2680af78d5aSKhang Kieu     std::string_view header = resp.getHeaderValue("Location");
2690af78d5aSKhang Kieu     if (!header.empty())
2700af78d5aSKhang Kieu     {
2710af78d5aSKhang Kieu         std::string location(header);
2720af78d5aSKhang Kieu         addPrefixToStringItem(location, prefix);
2730af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::location, location);
2740af78d5aSKhang Kieu     }
2750af78d5aSKhang Kieu     if (!resp.getHeaderValue("Retry-After").empty())
2760af78d5aSKhang Kieu     {
2770af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::retry_after,
2780af78d5aSKhang Kieu                             resp.getHeaderValue("Retry-After"));
2790af78d5aSKhang Kieu     }
2800af78d5aSKhang Kieu     // TODO: we need special handling for Link Header Value
2810af78d5aSKhang Kieu }
2820af78d5aSKhang Kieu 
283b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others
284b27e1cbeSCarson Labrado static inline void addPrefixToHeadersInResp(nlohmann::json& json,
285b27e1cbeSCarson Labrado                                             std::string_view prefix)
286b27e1cbeSCarson Labrado {
287b27e1cbeSCarson Labrado     // The passed in "HttpHeaders" should be an array of headers
288b27e1cbeSCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
289b27e1cbeSCarson Labrado     if (array == nullptr)
290b27e1cbeSCarson Labrado     {
291b27e1cbeSCarson Labrado         BMCWEB_LOG_ERROR << "Field wasn't an array_t????";
292b27e1cbeSCarson Labrado         return;
293b27e1cbeSCarson Labrado     }
294b27e1cbeSCarson Labrado 
295b27e1cbeSCarson Labrado     for (nlohmann::json& item : *array)
296b27e1cbeSCarson Labrado     {
297b27e1cbeSCarson Labrado         // Each header is a single string with the form "<Field>: <Value>"
298b27e1cbeSCarson Labrado         std::string* strHeader = item.get_ptr<std::string*>();
299b27e1cbeSCarson Labrado         if (strHeader == nullptr)
300b27e1cbeSCarson Labrado         {
301b27e1cbeSCarson Labrado             BMCWEB_LOG_CRITICAL << "Field wasn't a string????";
302b27e1cbeSCarson Labrado             continue;
303b27e1cbeSCarson Labrado         }
304b27e1cbeSCarson Labrado 
305b27e1cbeSCarson Labrado         constexpr std::string_view location = "Location: ";
306b27e1cbeSCarson Labrado         if (strHeader->starts_with(location))
307b27e1cbeSCarson Labrado         {
308b27e1cbeSCarson Labrado             std::string header = strHeader->substr(location.size());
309b27e1cbeSCarson Labrado             addPrefixToStringItem(header, prefix);
310b27e1cbeSCarson Labrado             *strHeader = std::string(location) + header;
311b27e1cbeSCarson Labrado         }
312b27e1cbeSCarson Labrado     }
313b27e1cbeSCarson Labrado }
314b27e1cbeSCarson Labrado 
3151c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for
3167e8890c5SCarson Labrado // an aggregated resource.
3170af78d5aSKhang Kieu static inline void addPrefixes(nlohmann::json& json, std::string_view prefix)
3181c0bb5c6SCarson Labrado {
3191c0bb5c6SCarson Labrado     nlohmann::json::object_t* object =
3201c0bb5c6SCarson Labrado         json.get_ptr<nlohmann::json::object_t*>();
3211c0bb5c6SCarson Labrado     if (object != nullptr)
3221c0bb5c6SCarson Labrado     {
3231c0bb5c6SCarson Labrado         for (std::pair<const std::string, nlohmann::json>& item : *object)
3241c0bb5c6SCarson Labrado         {
3257e8890c5SCarson Labrado             if (isPropertyUri(item.first))
3261c0bb5c6SCarson Labrado             {
3277e8890c5SCarson Labrado                 addPrefixToItem(item.second, prefix);
3281c0bb5c6SCarson Labrado                 continue;
3291c0bb5c6SCarson Labrado             }
3301c0bb5c6SCarson Labrado 
331b27e1cbeSCarson Labrado             // "HttpHeaders" contains HTTP headers.  Among those we need to
332b27e1cbeSCarson Labrado             // attempt to fix the "Location" header
333b27e1cbeSCarson Labrado             if (item.first == "HttpHeaders")
334b27e1cbeSCarson Labrado             {
335b27e1cbeSCarson Labrado                 addPrefixToHeadersInResp(item.second, prefix);
336b27e1cbeSCarson Labrado                 continue;
337b27e1cbeSCarson Labrado             }
338b27e1cbeSCarson Labrado 
3391c0bb5c6SCarson Labrado             // Recusively parse the rest of the json
3401c0bb5c6SCarson Labrado             addPrefixes(item.second, prefix);
3411c0bb5c6SCarson Labrado         }
3421c0bb5c6SCarson Labrado         return;
3431c0bb5c6SCarson Labrado     }
3441c0bb5c6SCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
3451c0bb5c6SCarson Labrado     if (array != nullptr)
3461c0bb5c6SCarson Labrado     {
3471c0bb5c6SCarson Labrado         for (nlohmann::json& item : *array)
3481c0bb5c6SCarson Labrado         {
3491c0bb5c6SCarson Labrado             addPrefixes(item, prefix);
3501c0bb5c6SCarson Labrado         }
3511c0bb5c6SCarson Labrado     }
3521c0bb5c6SCarson Labrado }
3531c0bb5c6SCarson Labrado 
354d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode)
355a7a80296SCarson Labrado {
35632d7d8ebSCarson Labrado     // Allow all response codes because we want to surface any satellite
35732d7d8ebSCarson Labrado     // issue to the client
358d14a48ffSCarson Labrado     BMCWEB_LOG_DEBUG << "Received " << respCode << " response from satellite";
359d14a48ffSCarson Labrado     return boost::system::errc::make_error_code(boost::system::errc::success);
360d14a48ffSCarson Labrado }
361d14a48ffSCarson Labrado 
362d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy()
363d14a48ffSCarson Labrado {
364d14a48ffSCarson Labrado     return {.maxRetryAttempts = 1,
365d14a48ffSCarson Labrado             .requestByteLimit = aggregatorReadBodyLimit,
366d14a48ffSCarson Labrado             .maxConnections = 20,
367d14a48ffSCarson Labrado             .retryPolicyAction = "TerminateAfterRetries",
368d14a48ffSCarson Labrado             .retryIntervalSecs = std::chrono::seconds(0),
369d14a48ffSCarson Labrado             .invalidResp = aggregationRetryHandler};
370d14a48ffSCarson Labrado }
371d14a48ffSCarson Labrado 
372d14a48ffSCarson Labrado class RedfishAggregator
373d14a48ffSCarson Labrado {
374d14a48ffSCarson Labrado   private:
375d14a48ffSCarson Labrado     crow::HttpClient client;
376d14a48ffSCarson Labrado 
377d14a48ffSCarson Labrado     RedfishAggregator() :
378d14a48ffSCarson Labrado         client(std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy()))
379d14a48ffSCarson Labrado     {
380d14a48ffSCarson Labrado         getSatelliteConfigs(constructorCallback);
3819fa6d147SNan Zhou     }
382a7a80296SCarson Labrado 
3837fb33566SCarson Labrado     // Dummy callback used by the Constructor so that it can report the number
3847fb33566SCarson Labrado     // of satellite configs when the class is first created
3857fb33566SCarson Labrado     static void constructorCallback(
3868b2521a5SCarson Labrado         const boost::system::error_code& ec,
3877fb33566SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
3887fb33566SCarson Labrado     {
3897fb33566SCarson Labrado         if (ec)
3907fb33566SCarson Labrado         {
3918b2521a5SCarson Labrado             BMCWEB_LOG_ERROR << "Something went wrong while querying dbus!";
3927fb33566SCarson Labrado             return;
3937fb33566SCarson Labrado         }
3947fb33566SCarson Labrado 
3958b2521a5SCarson Labrado         BMCWEB_LOG_DEBUG << "There were "
3967fb33566SCarson Labrado                          << std::to_string(satelliteInfo.size())
3978b2521a5SCarson Labrado                          << " satellite configs found at startup";
3987fb33566SCarson Labrado     }
3997fb33566SCarson Labrado 
4007fb33566SCarson Labrado     // Search D-Bus objects for satellite config objects and add their
4017fb33566SCarson Labrado     // information if valid
4027fb33566SCarson Labrado     static void findSatelliteConfigs(
4037fb33566SCarson Labrado         const dbus::utility::ManagedObjectType& objects,
4047fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4057fb33566SCarson Labrado     {
4067fb33566SCarson Labrado         for (const auto& objectPath : objects)
4077fb33566SCarson Labrado         {
4087fb33566SCarson Labrado             for (const auto& interface : objectPath.second)
4097fb33566SCarson Labrado             {
4107fb33566SCarson Labrado                 if (interface.first ==
4117fb33566SCarson Labrado                     "xyz.openbmc_project.Configuration.SatelliteController")
4127fb33566SCarson Labrado                 {
4137fb33566SCarson Labrado                     BMCWEB_LOG_DEBUG << "Found Satellite Controller at "
4147fb33566SCarson Labrado                                      << objectPath.first.str;
4157fb33566SCarson Labrado 
41605916cefSCarson Labrado                     if (!satelliteInfo.empty())
41705916cefSCarson Labrado                     {
41805916cefSCarson Labrado                         BMCWEB_LOG_ERROR
41905916cefSCarson Labrado                             << "Redfish Aggregation only supports one satellite!";
42005916cefSCarson Labrado                         BMCWEB_LOG_DEBUG << "Clearing all satellite data";
42105916cefSCarson Labrado                         satelliteInfo.clear();
42205916cefSCarson Labrado                         return;
42305916cefSCarson Labrado                     }
42405916cefSCarson Labrado 
42505916cefSCarson Labrado                     // For now assume there will only be one satellite config.
42605916cefSCarson Labrado                     // Assign it the name/prefix "5B247A"
42705916cefSCarson Labrado                     addSatelliteConfig("5B247A", interface.second,
42805916cefSCarson Labrado                                        satelliteInfo);
4297fb33566SCarson Labrado                 }
4307fb33566SCarson Labrado             }
4317fb33566SCarson Labrado         }
4327fb33566SCarson Labrado     }
4337fb33566SCarson Labrado 
4347fb33566SCarson Labrado     // Parse the properties of a satellite config object and add the
4357fb33566SCarson Labrado     // configuration if the properties are valid
4367fb33566SCarson Labrado     static void addSatelliteConfig(
43705916cefSCarson Labrado         const std::string& name,
4387fb33566SCarson Labrado         const dbus::utility::DBusPropertiesMap& properties,
4397fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4407fb33566SCarson Labrado     {
4417fb33566SCarson Labrado         boost::urls::url url;
4427fb33566SCarson Labrado 
4437fb33566SCarson Labrado         for (const auto& prop : properties)
4447fb33566SCarson Labrado         {
44505916cefSCarson Labrado             if (prop.first == "Hostname")
4467fb33566SCarson Labrado             {
4477fb33566SCarson Labrado                 const std::string* propVal =
4487fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
4497fb33566SCarson Labrado                 if (propVal == nullptr)
4507fb33566SCarson Labrado                 {
4517fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid Hostname value";
4527fb33566SCarson Labrado                     return;
4537fb33566SCarson Labrado                 }
4547fb33566SCarson Labrado                 url.set_host(*propVal);
4557fb33566SCarson Labrado             }
4567fb33566SCarson Labrado 
4577fb33566SCarson Labrado             else if (prop.first == "Port")
4587fb33566SCarson Labrado             {
4597fb33566SCarson Labrado                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
4607fb33566SCarson Labrado                 if (propVal == nullptr)
4617fb33566SCarson Labrado                 {
4627fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid Port value";
4637fb33566SCarson Labrado                     return;
4647fb33566SCarson Labrado                 }
4657fb33566SCarson Labrado 
4667fb33566SCarson Labrado                 if (*propVal > std::numeric_limits<uint16_t>::max())
4677fb33566SCarson Labrado                 {
4687fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Port value out of range";
4697fb33566SCarson Labrado                     return;
4707fb33566SCarson Labrado                 }
471079360aeSEd Tanous                 url.set_port(std::to_string(static_cast<uint16_t>(*propVal)));
4727fb33566SCarson Labrado             }
4737fb33566SCarson Labrado 
4747fb33566SCarson Labrado             else if (prop.first == "AuthType")
4757fb33566SCarson Labrado             {
4767fb33566SCarson Labrado                 const std::string* propVal =
4777fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
4787fb33566SCarson Labrado                 if (propVal == nullptr)
4797fb33566SCarson Labrado                 {
4807fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid AuthType value";
4817fb33566SCarson Labrado                     return;
4827fb33566SCarson Labrado                 }
4837fb33566SCarson Labrado 
4847fb33566SCarson Labrado                 // For now assume authentication not required to communicate
4857fb33566SCarson Labrado                 // with the satellite BMC
4867fb33566SCarson Labrado                 if (*propVal != "None")
4877fb33566SCarson Labrado                 {
4887fb33566SCarson Labrado                     BMCWEB_LOG_ERROR
4897fb33566SCarson Labrado                         << "Unsupported AuthType value: " << *propVal
4907fb33566SCarson Labrado                         << ", only \"none\" is supported";
4917fb33566SCarson Labrado                     return;
4927fb33566SCarson Labrado                 }
4937fb33566SCarson Labrado                 url.set_scheme("http");
4947fb33566SCarson Labrado             }
4957fb33566SCarson Labrado         } // Finished reading properties
4967fb33566SCarson Labrado 
4977fb33566SCarson Labrado         // Make sure all required config information was made available
4987fb33566SCarson Labrado         if (url.host().empty())
4997fb33566SCarson Labrado         {
5007fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host";
5017fb33566SCarson Labrado             return;
5027fb33566SCarson Labrado         }
5037fb33566SCarson Labrado 
5047fb33566SCarson Labrado         if (!url.has_port())
5057fb33566SCarson Labrado         {
5067fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port";
5077fb33566SCarson Labrado             return;
5087fb33566SCarson Labrado         }
5097fb33566SCarson Labrado 
5107fb33566SCarson Labrado         if (!url.has_scheme())
5117fb33566SCarson Labrado         {
5127fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name
5137fb33566SCarson Labrado                              << " missing AuthType";
5147fb33566SCarson Labrado             return;
5157fb33566SCarson Labrado         }
5167fb33566SCarson Labrado 
5177fb33566SCarson Labrado         std::string resultString;
5187fb33566SCarson Labrado         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
5197fb33566SCarson Labrado         if (result.second)
5207fb33566SCarson Labrado         {
5217fb33566SCarson Labrado             resultString = "Added new satellite config ";
5227fb33566SCarson Labrado         }
5237fb33566SCarson Labrado         else
5247fb33566SCarson Labrado         {
5257fb33566SCarson Labrado             resultString = "Updated existing satellite config ";
5267fb33566SCarson Labrado         }
5277fb33566SCarson Labrado 
5287fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << resultString << name << " at "
5297fb33566SCarson Labrado                          << result.first->second.scheme() << "://"
5307fb33566SCarson Labrado                          << result.first->second.encoded_host_and_port();
5317fb33566SCarson Labrado     }
5327fb33566SCarson Labrado 
53346a81465SCarson Labrado     enum AggregationType
53446a81465SCarson Labrado     {
53546a81465SCarson Labrado         Collection,
53646a81465SCarson Labrado         Resource,
53746a81465SCarson Labrado     };
53846a81465SCarson Labrado 
53946a81465SCarson Labrado     static void
54046a81465SCarson Labrado         startAggregation(AggregationType isCollection,
54146a81465SCarson Labrado                          const crow::Request& thisReq,
54246a81465SCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
54346a81465SCarson Labrado     {
544db18fc98SCarson Labrado         if ((isCollection == AggregationType::Collection) &&
545db18fc98SCarson Labrado             (thisReq.method() != boost::beast::http::verb::get))
546db18fc98SCarson Labrado         {
547db18fc98SCarson Labrado             BMCWEB_LOG_DEBUG
548db18fc98SCarson Labrado                 << "Only aggregate GET requests to top level collections";
549db18fc98SCarson Labrado             return;
550db18fc98SCarson Labrado         }
551db18fc98SCarson Labrado 
55246a81465SCarson Labrado         // Create a copy of thisReq so we we can still locally process the req
55346a81465SCarson Labrado         std::error_code ec;
55446a81465SCarson Labrado         auto localReq = std::make_shared<crow::Request>(thisReq.req, ec);
55546a81465SCarson Labrado         if (ec)
55646a81465SCarson Labrado         {
55746a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Failed to create copy of request";
55846a81465SCarson Labrado             if (isCollection != AggregationType::Collection)
55946a81465SCarson Labrado             {
56046a81465SCarson Labrado                 messages::internalError(asyncResp->res);
56146a81465SCarson Labrado             }
56246a81465SCarson Labrado             return;
56346a81465SCarson Labrado         }
56446a81465SCarson Labrado 
56546a81465SCarson Labrado         getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection,
56646a81465SCarson Labrado                                             localReq, asyncResp));
56746a81465SCarson Labrado     }
56846a81465SCarson Labrado 
569db18fc98SCarson Labrado     static void findSatellite(
57046a81465SCarson Labrado         const crow::Request& req,
57146a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
57246a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo,
57346a81465SCarson Labrado         std::string_view memberName)
57446a81465SCarson Labrado     {
57546a81465SCarson Labrado         // Determine if the resource ID begins with a known prefix
57646a81465SCarson Labrado         for (const auto& satellite : satelliteInfo)
57746a81465SCarson Labrado         {
57846a81465SCarson Labrado             std::string targetPrefix = satellite.first;
57946a81465SCarson Labrado             targetPrefix += "_";
58046a81465SCarson Labrado             if (memberName.starts_with(targetPrefix))
58146a81465SCarson Labrado             {
58246a81465SCarson Labrado                 BMCWEB_LOG_DEBUG << "\"" << satellite.first
58346a81465SCarson Labrado                                  << "\" is a known prefix";
58446a81465SCarson Labrado 
58546a81465SCarson Labrado                 // Remove the known prefix from the request's URI and
58646a81465SCarson Labrado                 // then forward to the associated satellite BMC
58746a81465SCarson Labrado                 getInstance().forwardRequest(req, asyncResp, satellite.first,
58846a81465SCarson Labrado                                              satelliteInfo);
58946a81465SCarson Labrado                 return;
59046a81465SCarson Labrado             }
59146a81465SCarson Labrado         }
592db18fc98SCarson Labrado 
593db18fc98SCarson Labrado         // We didn't recognize the prefix and need to return a 404
59439662a3bSEd Tanous         std::string nameStr = req.url().segments().back();
595db18fc98SCarson Labrado         messages::resourceNotFound(asyncResp->res, "", nameStr);
59646a81465SCarson Labrado     }
59746a81465SCarson Labrado 
59846a81465SCarson Labrado     // Intended to handle an incoming request based on if Redfish Aggregation
59946a81465SCarson Labrado     // is enabled.  Forwards request to satellite BMC if it exists.
60046a81465SCarson Labrado     static void aggregateAndHandle(
60146a81465SCarson Labrado         AggregationType isCollection,
60246a81465SCarson Labrado         const std::shared_ptr<crow::Request>& sharedReq,
60346a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6048b2521a5SCarson Labrado         const boost::system::error_code& ec,
60546a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
60646a81465SCarson Labrado     {
60746a81465SCarson Labrado         if (sharedReq == nullptr)
60846a81465SCarson Labrado         {
60946a81465SCarson Labrado             return;
61046a81465SCarson Labrado         }
6118b2521a5SCarson Labrado         // Something went wrong while querying dbus
6128b2521a5SCarson Labrado         if (ec)
6138b2521a5SCarson Labrado         {
6148b2521a5SCarson Labrado             messages::internalError(asyncResp->res);
6158b2521a5SCarson Labrado             return;
6168b2521a5SCarson Labrado         }
617db18fc98SCarson Labrado 
618db18fc98SCarson Labrado         // No satellite configs means we don't need to keep attempting to
619db18fc98SCarson Labrado         // aggregate
620db18fc98SCarson Labrado         if (satelliteInfo.empty())
621db18fc98SCarson Labrado         {
622db18fc98SCarson Labrado             // For collections we'll also handle the request locally so we
623db18fc98SCarson Labrado             // don't need to write an error code
624db18fc98SCarson Labrado             if (isCollection == AggregationType::Resource)
625db18fc98SCarson Labrado             {
62639662a3bSEd Tanous                 std::string nameStr = sharedReq->url().segments().back();
627db18fc98SCarson Labrado                 messages::resourceNotFound(asyncResp->res, "", nameStr);
628db18fc98SCarson Labrado             }
629db18fc98SCarson Labrado             return;
630db18fc98SCarson Labrado         }
631db18fc98SCarson Labrado 
63246a81465SCarson Labrado         const crow::Request& thisReq = *sharedReq;
63346a81465SCarson Labrado         BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of "
63446a81465SCarson Labrado                          << thisReq.target();
63546a81465SCarson Labrado 
63646a81465SCarson Labrado         // We previously determined the request is for a collection.  No need to
63746a81465SCarson Labrado         // check again
63846a81465SCarson Labrado         if (isCollection == AggregationType::Collection)
63946a81465SCarson Labrado         {
64046a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Aggregating a collection";
6414c30e226SCarson Labrado             // We need to use a specific response handler and send the
6424c30e226SCarson Labrado             // request to all known satellites
6434c30e226SCarson Labrado             getInstance().forwardCollectionRequests(thisReq, asyncResp,
6444c30e226SCarson Labrado                                                     satelliteInfo);
64546a81465SCarson Labrado             return;
64646a81465SCarson Labrado         }
64746a81465SCarson Labrado 
64839662a3bSEd Tanous         const boost::urls::segments_view urlSegments = thisReq.url().segments();
6497c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
6507c4c52cbSCarson Labrado         boost::urls::segments_view::iterator it = urlSegments.begin();
6517c4c52cbSCarson Labrado         const boost::urls::segments_view::const_iterator end =
6527c4c52cbSCarson Labrado             urlSegments.end();
6537c4c52cbSCarson Labrado 
6547c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
6557c4c52cbSCarson Labrado         it++;
6567c4c52cbSCarson Labrado         it++;
6577c4c52cbSCarson Labrado         for (; it != end; it++)
65846a81465SCarson Labrado         {
6597c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
6607c4c52cbSCarson Labrado                                    currentUrl.buffer()))
6617c4c52cbSCarson Labrado             {
6627c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
6637c4c52cbSCarson Labrado                 // must contain an aggregation prefix
6647c4c52cbSCarson Labrado                 findSatellite(thisReq, asyncResp, satelliteInfo, *it);
66546a81465SCarson Labrado                 return;
66646a81465SCarson Labrado             }
66746a81465SCarson Labrado 
6687c4c52cbSCarson Labrado             currentUrl.segments().push_back(*it);
66946a81465SCarson Labrado         }
670db18fc98SCarson Labrado 
671db18fc98SCarson Labrado         // We shouldn't reach this point since we should've hit one of the
672db18fc98SCarson Labrado         // previous exits
673db18fc98SCarson Labrado         messages::internalError(asyncResp->res);
67446a81465SCarson Labrado     }
67546a81465SCarson Labrado 
67646a81465SCarson Labrado     // Attempt to forward a request to the satellite BMC associated with the
67746a81465SCarson Labrado     // prefix.
67846a81465SCarson Labrado     void forwardRequest(
67946a81465SCarson Labrado         const crow::Request& thisReq,
68046a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
68146a81465SCarson Labrado         const std::string& prefix,
68246a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
68346a81465SCarson Labrado     {
68446a81465SCarson Labrado         const auto& sat = satelliteInfo.find(prefix);
68546a81465SCarson Labrado         if (sat == satelliteInfo.end())
68646a81465SCarson Labrado         {
68746a81465SCarson Labrado             // Realistically this shouldn't get called since we perform an
68846a81465SCarson Labrado             // earlier check to make sure the prefix exists
68946a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix
69046a81465SCarson Labrado                              << "\"";
69146a81465SCarson Labrado             return;
69246a81465SCarson Labrado         }
69346a81465SCarson Labrado 
69446a81465SCarson Labrado         // We need to strip the prefix from the request's path
69546a81465SCarson Labrado         std::string targetURI(thisReq.target());
69646a81465SCarson Labrado         size_t pos = targetURI.find(prefix + "_");
69746a81465SCarson Labrado         if (pos == std::string::npos)
69846a81465SCarson Labrado         {
69946a81465SCarson Labrado             // If this fails then something went wrong
70046a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix
70146a81465SCarson Labrado                              << "_\" from request URI";
70246a81465SCarson Labrado             messages::internalError(asyncResp->res);
70346a81465SCarson Labrado             return;
70446a81465SCarson Labrado         }
70546a81465SCarson Labrado         targetURI.erase(pos, prefix.size() + 1);
70646a81465SCarson Labrado 
70746a81465SCarson Labrado         std::function<void(crow::Response&)> cb =
7081c0bb5c6SCarson Labrado             std::bind_front(processResponse, prefix, asyncResp);
70946a81465SCarson Labrado 
71046a81465SCarson Labrado         std::string data = thisReq.req.body();
711d14a48ffSCarson Labrado         client.sendDataWithCallback(data, std::string(sat->second.host()),
712d14a48ffSCarson Labrado                                     sat->second.port_number(), targetURI,
713d14a48ffSCarson Labrado                                     false /*useSSL*/, thisReq.fields(),
714d14a48ffSCarson Labrado                                     thisReq.method(), cb);
71546a81465SCarson Labrado     }
71646a81465SCarson Labrado 
7174c30e226SCarson Labrado     // Forward a request for a collection URI to each known satellite BMC
7184c30e226SCarson Labrado     void forwardCollectionRequests(
7194c30e226SCarson Labrado         const crow::Request& thisReq,
7204c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
7214c30e226SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
7224c30e226SCarson Labrado     {
7234c30e226SCarson Labrado         for (const auto& sat : satelliteInfo)
7244c30e226SCarson Labrado         {
7254c30e226SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
7264c30e226SCarson Labrado                 processCollectionResponse, sat.first, asyncResp);
7274c30e226SCarson Labrado 
7284c30e226SCarson Labrado             std::string targetURI(thisReq.target());
7294c30e226SCarson Labrado             std::string data = thisReq.req.body();
730d14a48ffSCarson Labrado             client.sendDataWithCallback(data, std::string(sat.second.host()),
731d14a48ffSCarson Labrado                                         sat.second.port_number(), targetURI,
732d14a48ffSCarson Labrado                                         false /*useSSL*/, thisReq.fields(),
733d14a48ffSCarson Labrado                                         thisReq.method(), cb);
7344c30e226SCarson Labrado         }
7354c30e226SCarson Labrado     }
7364c30e226SCarson Labrado 
73732d7d8ebSCarson Labrado   public:
73832d7d8ebSCarson Labrado     RedfishAggregator(const RedfishAggregator&) = delete;
73932d7d8ebSCarson Labrado     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
74032d7d8ebSCarson Labrado     RedfishAggregator(RedfishAggregator&&) = delete;
74132d7d8ebSCarson Labrado     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
74232d7d8ebSCarson Labrado     ~RedfishAggregator() = default;
74332d7d8ebSCarson Labrado 
74432d7d8ebSCarson Labrado     static RedfishAggregator& getInstance()
74532d7d8ebSCarson Labrado     {
74632d7d8ebSCarson Labrado         static RedfishAggregator handler;
74732d7d8ebSCarson Labrado         return handler;
74832d7d8ebSCarson Labrado     }
74932d7d8ebSCarson Labrado 
7508b2521a5SCarson Labrado     // Polls D-Bus to get all available satellite config information
7518b2521a5SCarson Labrado     // Expects a handler which interacts with the returned configs
7528b2521a5SCarson Labrado     static void getSatelliteConfigs(
7538b2521a5SCarson Labrado         std::function<
7548b2521a5SCarson Labrado             void(const boost::system::error_code&,
7558b2521a5SCarson Labrado                  const std::unordered_map<std::string, boost::urls::url>&)>
7568b2521a5SCarson Labrado             handler)
7578b2521a5SCarson Labrado     {
7588b2521a5SCarson Labrado         BMCWEB_LOG_DEBUG << "Gathering satellite configs";
7598b2521a5SCarson Labrado         crow::connections::systemBus->async_method_call(
7608b2521a5SCarson Labrado             [handler{std::move(handler)}](
7618b2521a5SCarson Labrado                 const boost::system::error_code& ec,
7628b2521a5SCarson Labrado                 const dbus::utility::ManagedObjectType& objects) {
7638b2521a5SCarson Labrado             std::unordered_map<std::string, boost::urls::url> satelliteInfo;
7648b2521a5SCarson Labrado             if (ec)
7658b2521a5SCarson Labrado             {
7668b2521a5SCarson Labrado                 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", "
7678b2521a5SCarson Labrado                                  << ec.message();
7688b2521a5SCarson Labrado                 handler(ec, satelliteInfo);
7698b2521a5SCarson Labrado                 return;
7708b2521a5SCarson Labrado             }
7718b2521a5SCarson Labrado 
7728b2521a5SCarson Labrado             // Maps a chosen alias representing a satellite BMC to a url
7738b2521a5SCarson Labrado             // containing the information required to create a http
7748b2521a5SCarson Labrado             // connection to the satellite
7758b2521a5SCarson Labrado             findSatelliteConfigs(objects, satelliteInfo);
7768b2521a5SCarson Labrado 
7778b2521a5SCarson Labrado             if (!satelliteInfo.empty())
7788b2521a5SCarson Labrado             {
7798b2521a5SCarson Labrado                 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with "
7808b2521a5SCarson Labrado                                  << std::to_string(satelliteInfo.size())
7818b2521a5SCarson Labrado                                  << " satellite BMCs";
7828b2521a5SCarson Labrado             }
7838b2521a5SCarson Labrado             else
7848b2521a5SCarson Labrado             {
7858b2521a5SCarson Labrado                 BMCWEB_LOG_DEBUG
7868b2521a5SCarson Labrado                     << "No satellite BMCs detected.  Redfish Aggregation not enabled";
7878b2521a5SCarson Labrado             }
7888b2521a5SCarson Labrado             handler(ec, satelliteInfo);
7898b2521a5SCarson Labrado             },
7908b2521a5SCarson Labrado             "xyz.openbmc_project.EntityManager",
7918b2521a5SCarson Labrado             "/xyz/openbmc_project/inventory",
7928b2521a5SCarson Labrado             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
7938b2521a5SCarson Labrado     }
7948b2521a5SCarson Labrado 
79546a81465SCarson Labrado     // Processes the response returned by a satellite BMC and loads its
79646a81465SCarson Labrado     // contents into asyncResp
79746a81465SCarson Labrado     static void
7981c0bb5c6SCarson Labrado         processResponse(std::string_view prefix,
7991c0bb5c6SCarson Labrado                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
80046a81465SCarson Labrado                         crow::Response& resp)
80146a81465SCarson Labrado     {
80243e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
80343e14d38SCarson Labrado         // overwrite the response headers in that case
80443e14d38SCarson Labrado         if ((resp.resultInt() == 429) || (resp.resultInt() == 502))
80543e14d38SCarson Labrado         {
80643e14d38SCarson Labrado             asyncResp->res.result(resp.result());
80743e14d38SCarson Labrado             return;
80843e14d38SCarson Labrado         }
80943e14d38SCarson Labrado 
81032d7d8ebSCarson Labrado         // We want to attempt prefix fixing regardless of response code
81146a81465SCarson Labrado         // The resp will not have a json component
81246a81465SCarson Labrado         // We need to create a json from resp's stringResponse
81346a81465SCarson Labrado         if (resp.getHeaderValue("Content-Type") == "application/json")
81446a81465SCarson Labrado         {
81546a81465SCarson Labrado             nlohmann::json jsonVal =
81646a81465SCarson Labrado                 nlohmann::json::parse(resp.body(), nullptr, false);
81746a81465SCarson Labrado             if (jsonVal.is_discarded())
81846a81465SCarson Labrado             {
81946a81465SCarson Labrado                 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON";
82046a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
82146a81465SCarson Labrado                 return;
82246a81465SCarson Labrado             }
82346a81465SCarson Labrado 
82446a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Successfully parsed satellite response";
82546a81465SCarson Labrado 
8261c0bb5c6SCarson Labrado             addPrefixes(jsonVal, prefix);
8271c0bb5c6SCarson Labrado 
8281c0bb5c6SCarson Labrado             BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response";
8291c0bb5c6SCarson Labrado 
83046a81465SCarson Labrado             asyncResp->res.result(resp.result());
83146a81465SCarson Labrado             asyncResp->res.jsonValue = std::move(jsonVal);
83246a81465SCarson Labrado 
83346a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Finished writing asyncResp";
83446a81465SCarson Labrado         }
83546a81465SCarson Labrado         else
83646a81465SCarson Labrado         {
8370af78d5aSKhang Kieu             // We allow any Content-Type that is not "application/json" now
8380af78d5aSKhang Kieu             asyncResp->res.result(resp.result());
8390af78d5aSKhang Kieu             asyncResp->res.write(resp.body());
84046a81465SCarson Labrado         }
8410af78d5aSKhang Kieu         addAggregatedHeaders(asyncResp->res, resp, prefix);
84246a81465SCarson Labrado     }
84346a81465SCarson Labrado 
8444c30e226SCarson Labrado     // Processes the collection response returned by a satellite BMC and merges
8454c30e226SCarson Labrado     // its "@odata.id" values
8464c30e226SCarson Labrado     static void processCollectionResponse(
8474c30e226SCarson Labrado         const std::string& prefix,
8484c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
8494c30e226SCarson Labrado         crow::Response& resp)
8504c30e226SCarson Labrado     {
85143e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
85243e14d38SCarson Labrado         // overwrite the response headers in that case
85343e14d38SCarson Labrado         if ((resp.resultInt() == 429) || (resp.resultInt() == 502))
85443e14d38SCarson Labrado         {
85543e14d38SCarson Labrado             return;
85643e14d38SCarson Labrado         }
85743e14d38SCarson Labrado 
8584c30e226SCarson Labrado         if (resp.resultInt() != 200)
8594c30e226SCarson Labrado         {
8604c30e226SCarson Labrado             BMCWEB_LOG_DEBUG
8614c30e226SCarson Labrado                 << "Collection resource does not exist in satellite BMC \""
8624c30e226SCarson Labrado                 << prefix << "\"";
8634c30e226SCarson Labrado             // Return the error if we haven't had any successes
8644c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
8654c30e226SCarson Labrado             {
8664c30e226SCarson Labrado                 asyncResp->res.stringResponse = std::move(resp.stringResponse);
8674c30e226SCarson Labrado             }
8684c30e226SCarson Labrado             return;
8694c30e226SCarson Labrado         }
8704c30e226SCarson Labrado 
8714c30e226SCarson Labrado         // The resp will not have a json component
8724c30e226SCarson Labrado         // We need to create a json from resp's stringResponse
8734c30e226SCarson Labrado         if (resp.getHeaderValue("Content-Type") == "application/json")
8744c30e226SCarson Labrado         {
8754c30e226SCarson Labrado             nlohmann::json jsonVal =
8764c30e226SCarson Labrado                 nlohmann::json::parse(resp.body(), nullptr, false);
8774c30e226SCarson Labrado             if (jsonVal.is_discarded())
8784c30e226SCarson Labrado             {
8794c30e226SCarson Labrado                 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON";
8804c30e226SCarson Labrado 
8814c30e226SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
8824c30e226SCarson Labrado                 if ((asyncResp->res.resultInt() != 200) &&
88343e14d38SCarson Labrado                     (asyncResp->res.resultInt() != 429) &&
8844c30e226SCarson Labrado                     (asyncResp->res.resultInt() != 502))
8854c30e226SCarson Labrado                 {
8864c30e226SCarson Labrado                     messages::operationFailed(asyncResp->res);
8874c30e226SCarson Labrado                 }
8884c30e226SCarson Labrado                 return;
8894c30e226SCarson Labrado             }
8904c30e226SCarson Labrado 
8914c30e226SCarson Labrado             BMCWEB_LOG_DEBUG << "Successfully parsed satellite response";
8924c30e226SCarson Labrado 
8934c30e226SCarson Labrado             // Now we need to add the prefix to the URIs contained in the
8944c30e226SCarson Labrado             // response.
8954c30e226SCarson Labrado             addPrefixes(jsonVal, prefix);
8964c30e226SCarson Labrado 
8974c30e226SCarson Labrado             BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response";
8984c30e226SCarson Labrado 
8994c30e226SCarson Labrado             // If this resource collection does not exist on the aggregating bmc
9004c30e226SCarson Labrado             // and has not already been added from processing the response from
9014c30e226SCarson Labrado             // a different satellite then we need to completely overwrite
9024c30e226SCarson Labrado             // asyncResp
9034c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
9044c30e226SCarson Labrado             {
9054c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
9064c30e226SCarson Labrado                 // "Members" array
9074c30e226SCarson Labrado                 if ((!jsonVal.contains("Members")) &&
9084c30e226SCarson Labrado                     (!jsonVal["Members"].is_array()))
9094c30e226SCarson Labrado                 {
9104c30e226SCarson Labrado                     BMCWEB_LOG_DEBUG
9114c30e226SCarson Labrado                         << "Skipping aggregating unsupported resource";
9124c30e226SCarson Labrado                     return;
9134c30e226SCarson Labrado                 }
9144c30e226SCarson Labrado 
9154c30e226SCarson Labrado                 BMCWEB_LOG_DEBUG
9164c30e226SCarson Labrado                     << "Collection does not exist, overwriting asyncResp";
9174c30e226SCarson Labrado                 asyncResp->res.result(resp.result());
9184c30e226SCarson Labrado                 asyncResp->res.jsonValue = std::move(jsonVal);
91943e14d38SCarson Labrado                 asyncResp->res.addHeader("Content-Type", "application/json");
9204c30e226SCarson Labrado 
9214c30e226SCarson Labrado                 BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp";
9224c30e226SCarson Labrado             }
9234c30e226SCarson Labrado             else
9244c30e226SCarson Labrado             {
9254c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
9264c30e226SCarson Labrado                 // "Members" array
9274c30e226SCarson Labrado                 if ((!asyncResp->res.jsonValue.contains("Members")) &&
9284c30e226SCarson Labrado                     (!asyncResp->res.jsonValue["Members"].is_array()))
9294c30e226SCarson Labrado 
9304c30e226SCarson Labrado                 {
9314c30e226SCarson Labrado                     BMCWEB_LOG_DEBUG
9324c30e226SCarson Labrado                         << "Skipping aggregating unsupported resource";
9334c30e226SCarson Labrado                     return;
9344c30e226SCarson Labrado                 }
9354c30e226SCarson Labrado 
9364c30e226SCarson Labrado                 BMCWEB_LOG_DEBUG << "Adding aggregated resources from \""
9374c30e226SCarson Labrado                                  << prefix << "\" to collection";
9384c30e226SCarson Labrado 
9394c30e226SCarson Labrado                 // TODO: This is a potential race condition with multiple
9404c30e226SCarson Labrado                 // satellites and the aggregating bmc attempting to write to
9414c30e226SCarson Labrado                 // update this array.  May need to cascade calls to the next
9424c30e226SCarson Labrado                 // satellite at the end of this function.
9434c30e226SCarson Labrado                 // This is presumably not a concern when there is only a single
9444c30e226SCarson Labrado                 // satellite since the aggregating bmc should have completed
9454c30e226SCarson Labrado                 // before the response is received from the satellite.
9464c30e226SCarson Labrado 
9474c30e226SCarson Labrado                 auto& members = asyncResp->res.jsonValue["Members"];
9484c30e226SCarson Labrado                 auto& satMembers = jsonVal["Members"];
9494c30e226SCarson Labrado                 for (auto& satMem : satMembers)
9504c30e226SCarson Labrado                 {
9514c30e226SCarson Labrado                     members.push_back(std::move(satMem));
9524c30e226SCarson Labrado                 }
9534c30e226SCarson Labrado                 asyncResp->res.jsonValue["Members@odata.count"] =
9544c30e226SCarson Labrado                     members.size();
9554c30e226SCarson Labrado 
9564c30e226SCarson Labrado                 // TODO: Do we need to sort() after updating the array?
9574c30e226SCarson Labrado             }
9584c30e226SCarson Labrado         }
9594c30e226SCarson Labrado         else
9604c30e226SCarson Labrado         {
9614c30e226SCarson Labrado             BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix
9624c30e226SCarson Labrado                              << "\"";
96343e14d38SCarson Labrado             // We received a response that was not a json.
9644c30e226SCarson Labrado             // Notify the user only if we did not receive any valid responses,
9654c30e226SCarson Labrado             // if the resource collection does not already exist on the
9664c30e226SCarson Labrado             // aggregating BMC, and if we did not already set this warning due
9674c30e226SCarson Labrado             // to a failure from a different satellite
9684c30e226SCarson Labrado             if ((asyncResp->res.resultInt() != 200) &&
96943e14d38SCarson Labrado                 (asyncResp->res.resultInt() != 429) &&
9704c30e226SCarson Labrado                 (asyncResp->res.resultInt() != 502))
9714c30e226SCarson Labrado             {
9724c30e226SCarson Labrado                 messages::operationFailed(asyncResp->res);
9734c30e226SCarson Labrado             }
9744c30e226SCarson Labrado         }
9754c30e226SCarson Labrado     } // End processCollectionResponse()
9764c30e226SCarson Labrado 
97705916cefSCarson Labrado     // Entry point to Redfish Aggregation
97805916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
97905916cefSCarson Labrado     // request
98005916cefSCarson Labrado     static Result
98105916cefSCarson Labrado         beginAggregation(const crow::Request& thisReq,
98205916cefSCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
98305916cefSCarson Labrado     {
98405916cefSCarson Labrado         using crow::utility::OrMorePaths;
98505916cefSCarson Labrado         using crow::utility::readUrlSegments;
98639662a3bSEd Tanous         const boost::urls::url_view url = thisReq.url();
987411e6a11SCarson Labrado 
988411e6a11SCarson Labrado         // We don't need to aggregate JsonSchemas due to potential issues such
989411e6a11SCarson Labrado         // as version mismatches between aggregator and satellite BMCs.  For
990411e6a11SCarson Labrado         // now assume that the aggregator has all the schemas and versions that
991411e6a11SCarson Labrado         // the aggregated server has.
992411e6a11SCarson Labrado         if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas",
993411e6a11SCarson Labrado                                            crow::utility::OrMorePaths()))
994411e6a11SCarson Labrado         {
995411e6a11SCarson Labrado             return Result::LocalHandle;
996411e6a11SCarson Labrado         }
997411e6a11SCarson Labrado 
9987c4c52cbSCarson Labrado         // The first two segments should be "/redfish/v1".  We need to check
9997c4c52cbSCarson Labrado         // that before we can search topCollections
10007c4c52cbSCarson Labrado         if (!crow::utility::readUrlSegments(url, "redfish", "v1",
10017c4c52cbSCarson Labrado                                             crow::utility::OrMorePaths()))
100246a81465SCarson Labrado         {
100346a81465SCarson Labrado             return Result::LocalHandle;
100446a81465SCarson Labrado         }
100505916cefSCarson Labrado 
10067c4c52cbSCarson Labrado         // Parse the URI to see if it begins with a known top level collection
10077c4c52cbSCarson Labrado         // such as:
10087c4c52cbSCarson Labrado         // /redfish/v1/Chassis
10097c4c52cbSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory
10107c4c52cbSCarson Labrado         const boost::urls::segments_view urlSegments = url.segments();
10117c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
10127c4c52cbSCarson Labrado         boost::urls::segments_view::iterator it = urlSegments.begin();
10137c4c52cbSCarson Labrado         const boost::urls::segments_view::const_iterator end =
10147c4c52cbSCarson Labrado             urlSegments.end();
101505916cefSCarson Labrado 
10167c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
10177c4c52cbSCarson Labrado         it++;
10187c4c52cbSCarson Labrado         it++;
10197c4c52cbSCarson Labrado         for (; it != end; it++)
102005916cefSCarson Labrado         {
1021d4413c5bSGeorge Liu             const std::string& collectionItem = *it;
10227c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
10237c4c52cbSCarson Labrado                                    currentUrl.buffer()))
10247c4c52cbSCarson Labrado             {
10257c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
10267c4c52cbSCarson Labrado                 // might contain an aggregation prefix
10278b2521a5SCarson Labrado                 // TODO: This needs to be rethought when we can support multiple
10288b2521a5SCarson Labrado                 // satellites due to
10298b2521a5SCarson Labrado                 // /redfish/v1/AggregationService/AggregationSources/5B247A
10308b2521a5SCarson Labrado                 // being a local resource describing the satellite
10318b2521a5SCarson Labrado                 if (collectionItem.starts_with("5B247A_"))
103205916cefSCarson Labrado                 {
103305916cefSCarson Labrado                     BMCWEB_LOG_DEBUG << "Need to forward a request";
103405916cefSCarson Labrado 
103546a81465SCarson Labrado                     // Extract the prefix from the request's URI, retrieve the
10367c4c52cbSCarson Labrado                     // associated satellite config information, and then forward
10377c4c52cbSCarson Labrado                     // the request to that satellite.
10387c4c52cbSCarson Labrado                     startAggregation(AggregationType::Resource, thisReq,
10397c4c52cbSCarson Labrado                                      asyncResp);
104005916cefSCarson Labrado                     return Result::NoLocalHandle;
104105916cefSCarson Labrado                 }
10427c4c52cbSCarson Labrado 
10437c4c52cbSCarson Labrado                 // Handle collection URI with a trailing backslash
10447c4c52cbSCarson Labrado                 // e.g. /redfish/v1/Chassis/
10457c4c52cbSCarson Labrado                 it++;
10467c4c52cbSCarson Labrado                 if ((it == end) && collectionItem.empty())
10477c4c52cbSCarson Labrado                 {
10487c4c52cbSCarson Labrado                     startAggregation(AggregationType::Collection, thisReq,
10497c4c52cbSCarson Labrado                                      asyncResp);
10507c4c52cbSCarson Labrado                 }
10517c4c52cbSCarson Labrado 
10527c4c52cbSCarson Labrado                 // We didn't recognize the prefix or it's a collection with a
10537c4c52cbSCarson Labrado                 // trailing "/".  In both cases we still want to locally handle
10547c4c52cbSCarson Labrado                 // the request
10557c4c52cbSCarson Labrado                 return Result::LocalHandle;
10567c4c52cbSCarson Labrado             }
10577c4c52cbSCarson Labrado 
10587c4c52cbSCarson Labrado             currentUrl.segments().push_back(collectionItem);
10597c4c52cbSCarson Labrado         }
10607c4c52cbSCarson Labrado 
10617c4c52cbSCarson Labrado         // If we made it here then currentUrl could contain a top level
10627c4c52cbSCarson Labrado         // collection URI without a trailing "/", e.g. /redfish/v1/Chassis
10637c4c52cbSCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
10647c4c52cbSCarson Labrado                                currentUrl.buffer()))
10657c4c52cbSCarson Labrado         {
10667c4c52cbSCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
106705916cefSCarson Labrado             return Result::LocalHandle;
106805916cefSCarson Labrado         }
106905916cefSCarson Labrado 
107005916cefSCarson Labrado         BMCWEB_LOG_DEBUG << "Aggregation not required";
107105916cefSCarson Labrado         return Result::LocalHandle;
107205916cefSCarson Labrado     }
10737fb33566SCarson Labrado };
10747fb33566SCarson Labrado 
10757fb33566SCarson Labrado } // namespace redfish
1076