xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
37fb33566SCarson Labrado #pragma once
47fb33566SCarson Labrado 
53ccb3adbSEd Tanous #include "aggregation_utils.hpp"
63ccb3adbSEd Tanous #include "dbus_utility.hpp"
73ccb3adbSEd Tanous #include "error_messages.hpp"
83ccb3adbSEd Tanous #include "http_client.hpp"
93ccb3adbSEd Tanous #include "http_connection.hpp"
1018f8f608SEd Tanous #include "parsing.hpp"
117fb33566SCarson Labrado 
127e8890c5SCarson Labrado #include <array>
133544d2a7SEd Tanous #include <ranges>
143544d2a7SEd Tanous #include <string_view>
157e8890c5SCarson Labrado 
167fb33566SCarson Labrado namespace redfish
177fb33566SCarson Labrado {
187fb33566SCarson Labrado 
19d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB
20d14a48ffSCarson Labrado 
2105916cefSCarson Labrado enum class Result
2205916cefSCarson Labrado {
2305916cefSCarson Labrado     LocalHandle,
2405916cefSCarson Labrado     NoLocalHandle
2505916cefSCarson Labrado };
2605916cefSCarson Labrado 
278fd333d6SCarson Labrado enum class SearchType
288fd333d6SCarson Labrado {
298fd333d6SCarson Labrado     Collection,
308fd333d6SCarson Labrado     CollOrCon,
318fd333d6SCarson Labrado     ContainsSubordinate,
328fd333d6SCarson Labrado     Resource
338fd333d6SCarson Labrado };
348fd333d6SCarson Labrado 
357e8890c5SCarson Labrado // clang-format off
367e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource
377e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a
387e8890c5SCarson Labrado // case-insensitive form of "uri".  That version of the schema is associated
397e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification.  Going forward, new URI
407e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as
417e8890c5SCarson Labrado // the spec is updated.  NOTE: These have been pre-sorted in order to be
427e8890c5SCarson Labrado // compatible with binary search
437e8890c5SCarson Labrado constexpr std::array nonUriProperties{
447e8890c5SCarson Labrado     "@Redfish.ActionInfo",
457e8890c5SCarson Labrado     // "@odata.context", // We can't fix /redfish/v1/$metadata URIs
467e8890c5SCarson Labrado     "@odata.id",
477e8890c5SCarson Labrado     // "Destination", // Only used by EventService and won't be a Redfish URI
487e8890c5SCarson Labrado     // "HostName", // Isn't actually a Redfish URI
497e8890c5SCarson Labrado     "Image",
507e8890c5SCarson Labrado     "MetricProperty",
5132d7d8ebSCarson Labrado     // "OriginOfCondition", // Is URI when in request, but is object in response
527e8890c5SCarson Labrado     "TaskMonitor",
537e8890c5SCarson Labrado     "target", // normal string, but target URI for POST to invoke an action
547e8890c5SCarson Labrado };
557e8890c5SCarson Labrado // clang-format on
567e8890c5SCarson Labrado 
578fd333d6SCarson Labrado // Search the top collection array to determine if the passed URI is of a
588fd333d6SCarson Labrado // desired type
598fd333d6SCarson Labrado inline bool searchCollectionsArray(std::string_view uri,
608fd333d6SCarson Labrado                                    const SearchType searchType)
618fd333d6SCarson Labrado {
621a095906SJoonwon Kang     boost::system::result<boost::urls::url> parsedUrl =
631a095906SJoonwon Kang         boost::urls::parse_relative_ref(uri);
648fd333d6SCarson Labrado     if (!parsedUrl)
658fd333d6SCarson Labrado     {
661a095906SJoonwon Kang         BMCWEB_LOG_ERROR("Failed to get target URI from {}", uri);
678fd333d6SCarson Labrado         return false;
688fd333d6SCarson Labrado     }
698fd333d6SCarson Labrado 
701a095906SJoonwon Kang     parsedUrl->normalize();
711a095906SJoonwon Kang     boost::urls::segments_ref segments = parsedUrl->segments();
721a095906SJoonwon Kang     if (!segments.is_absolute())
738fd333d6SCarson Labrado     {
748fd333d6SCarson Labrado         return false;
758fd333d6SCarson Labrado     }
768fd333d6SCarson Labrado 
771a095906SJoonwon Kang     // The passed URI must begin with "/redfish/v1", but we have to strip it
781a095906SJoonwon Kang     // from the URI since topCollections does not include it in its URIs.
791a095906SJoonwon Kang     if (segments.size() < 2)
801a095906SJoonwon Kang     {
811a095906SJoonwon Kang         return false;
821a095906SJoonwon Kang     }
831a095906SJoonwon Kang     if (segments.front() != "redfish")
841a095906SJoonwon Kang     {
851a095906SJoonwon Kang         return false;
861a095906SJoonwon Kang     }
871a095906SJoonwon Kang     segments.erase(segments.begin());
881a095906SJoonwon Kang     if (segments.front() != "v1")
891a095906SJoonwon Kang     {
901a095906SJoonwon Kang         return false;
911a095906SJoonwon Kang     }
921a095906SJoonwon Kang     segments.erase(segments.begin());
931a095906SJoonwon Kang 
941a095906SJoonwon Kang     // Exclude the trailing "/" if it exists such as in "/redfish/v1/".
951a095906SJoonwon Kang     if (!segments.empty() && segments.back().empty())
961a095906SJoonwon Kang     {
971a095906SJoonwon Kang         segments.pop_back();
981a095906SJoonwon Kang     }
991a095906SJoonwon Kang 
1001a095906SJoonwon Kang     // If no segments then the passed URI was either "/redfish/v1" or
1018fd333d6SCarson Labrado     // "/redfish/v1/".
1021a095906SJoonwon Kang     if (segments.empty())
1038fd333d6SCarson Labrado     {
1048fd333d6SCarson Labrado         return (searchType == SearchType::ContainsSubordinate) ||
1058fd333d6SCarson Labrado                (searchType == SearchType::CollOrCon);
1068fd333d6SCarson Labrado     }
1071a095906SJoonwon Kang     std::string_view url = segments.buffer();
1083544d2a7SEd Tanous     const auto* it = std::ranges::lower_bound(topCollections, url);
1098fd333d6SCarson Labrado     if (it == topCollections.end())
1108fd333d6SCarson Labrado     {
1118fd333d6SCarson Labrado         // parsedUrl is alphabetically after the last entry in the array so it
1128fd333d6SCarson Labrado         // can't be a top collection or up tree from a top collection
1138fd333d6SCarson Labrado         return false;
1148fd333d6SCarson Labrado     }
1158fd333d6SCarson Labrado 
1168fd333d6SCarson Labrado     boost::urls::url collectionUrl(*it);
1178fd333d6SCarson Labrado     boost::urls::segments_view collectionSegments = collectionUrl.segments();
1188fd333d6SCarson Labrado     boost::urls::segments_view::iterator itCollection =
1198fd333d6SCarson Labrado         collectionSegments.begin();
1208fd333d6SCarson Labrado     const boost::urls::segments_view::const_iterator endCollection =
1218fd333d6SCarson Labrado         collectionSegments.end();
1228fd333d6SCarson Labrado 
1238fd333d6SCarson Labrado     // Each segment in the passed URI should match the found collection
1241a095906SJoonwon Kang     for (const auto& segment : segments)
1258fd333d6SCarson Labrado     {
1268fd333d6SCarson Labrado         if (itCollection == endCollection)
1278fd333d6SCarson Labrado         {
1288fd333d6SCarson Labrado             // Leftover segments means the target is for an aggregation
1298fd333d6SCarson Labrado             // supported resource
1308fd333d6SCarson Labrado             return searchType == SearchType::Resource;
1318fd333d6SCarson Labrado         }
1328fd333d6SCarson Labrado 
1338fd333d6SCarson Labrado         if (segment != (*itCollection))
1348fd333d6SCarson Labrado         {
1358fd333d6SCarson Labrado             return false;
1368fd333d6SCarson Labrado         }
1378fd333d6SCarson Labrado         itCollection++;
1388fd333d6SCarson Labrado     }
1398fd333d6SCarson Labrado 
1408fd333d6SCarson Labrado     // No remaining segments means the passed URI was a top level collection
1418fd333d6SCarson Labrado     if (searchType == SearchType::Collection)
1428fd333d6SCarson Labrado     {
1438fd333d6SCarson Labrado         return itCollection == endCollection;
1448fd333d6SCarson Labrado     }
1458fd333d6SCarson Labrado     if (searchType == SearchType::ContainsSubordinate)
1468fd333d6SCarson Labrado     {
1478fd333d6SCarson Labrado         return itCollection != endCollection;
1488fd333d6SCarson Labrado     }
1498fd333d6SCarson Labrado 
1508fd333d6SCarson Labrado     // Return this check instead of "true" in case other SearchTypes get added
1518fd333d6SCarson Labrado     return searchType == SearchType::CollOrCon;
1528fd333d6SCarson Labrado }
1538fd333d6SCarson Labrado 
1547e8890c5SCarson Labrado // Determines if the passed property contains a URI.  Those property names
1557e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically
1567e8890c5SCarson Labrado // defined in the above array.
15726ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName)
1587e8890c5SCarson Labrado {
15918f8f608SEd Tanous     if (propertyName.ends_with("uri") || propertyName.ends_with("Uri") ||
16018f8f608SEd Tanous         propertyName.ends_with("URI"))
16118f8f608SEd Tanous     {
16218f8f608SEd Tanous         return true;
16318f8f608SEd Tanous     }
16418f8f608SEd Tanous     return std::binary_search(nonUriProperties.begin(), nonUriProperties.end(),
1657e8890c5SCarson Labrado                               propertyName);
1667e8890c5SCarson Labrado }
1677e8890c5SCarson Labrado 
1684ff0f1f4SEd Tanous inline void addPrefixToStringItem(std::string& strValue,
1690af78d5aSKhang Kieu                                   std::string_view prefix)
1701c0bb5c6SCarson Labrado {
1711c0bb5c6SCarson Labrado     // Make sure the value is a properly formatted URI
1720af78d5aSKhang Kieu     auto parsed = boost::urls::parse_relative_ref(strValue);
1731c0bb5c6SCarson Labrado     if (!parsed)
1741c0bb5c6SCarson Labrado     {
175bf2ddedeSCarson Labrado         // Note that DMTF URIs such as
176bf2ddedeSCarson Labrado         // https://redfish.dmtf.org/registries/Base.1.15.0.json will fail this
177bf2ddedeSCarson Labrado         // check and that's okay
178bf2ddedeSCarson Labrado         BMCWEB_LOG_DEBUG("Couldn't parse URI from resource {}", strValue);
1791c0bb5c6SCarson Labrado         return;
1801c0bb5c6SCarson Labrado     }
1811c0bb5c6SCarson Labrado 
182daadfb2eSEd Tanous     const boost::urls::url_view& thisUrl = *parsed;
1831c0bb5c6SCarson Labrado 
184411e6a11SCarson Labrado     // We don't need to aggregate JsonSchemas due to potential issues such as
185411e6a11SCarson Labrado     // version mismatches between aggregator and satellite BMCs.  For now
186411e6a11SCarson Labrado     // assume that the aggregator has all the schemas and versions that the
187411e6a11SCarson Labrado     // aggregated server has.
188411e6a11SCarson Labrado     if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas",
189411e6a11SCarson Labrado                                        crow::utility::OrMorePaths()))
190411e6a11SCarson Labrado     {
19162598e31SEd Tanous         BMCWEB_LOG_DEBUG("Skipping JsonSchemas URI prefix fixing");
192411e6a11SCarson Labrado         return;
193411e6a11SCarson Labrado     }
194411e6a11SCarson Labrado 
19511987af6SCarson Labrado     // The first two segments should be "/redfish/v1".  We need to check that
19611987af6SCarson Labrado     // before we can search topCollections
19711987af6SCarson Labrado     if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
19811987af6SCarson Labrado                                         crow::utility::OrMorePaths()))
1991c0bb5c6SCarson Labrado     {
2001c0bb5c6SCarson Labrado         return;
2011c0bb5c6SCarson Labrado     }
2021c0bb5c6SCarson Labrado 
20311987af6SCarson Labrado     // Check array adding a segment each time until collection is identified
20411987af6SCarson Labrado     // Add prefix to segment after the collection
20511987af6SCarson Labrado     const boost::urls::segments_view urlSegments = thisUrl.segments();
20611987af6SCarson Labrado     bool addedPrefix = false;
20711987af6SCarson Labrado     boost::urls::url url("/");
2084a7fbefdSEd Tanous     boost::urls::segments_view::const_iterator it = urlSegments.begin();
20911987af6SCarson Labrado     const boost::urls::segments_view::const_iterator end = urlSegments.end();
21011987af6SCarson Labrado 
21111987af6SCarson Labrado     // Skip past the leading "/redfish/v1"
21211987af6SCarson Labrado     it++;
21311987af6SCarson Labrado     it++;
21411987af6SCarson Labrado     for (; it != end; it++)
2151c0bb5c6SCarson Labrado     {
21611987af6SCarson Labrado         // Trailing "/" will result in an empty segment.  In that case we need
21711987af6SCarson Labrado         // to return so we don't apply a prefix to top level collections such
21811987af6SCarson Labrado         // as "/redfish/v1/Chassis/"
21911987af6SCarson Labrado         if ((*it).empty())
22011987af6SCarson Labrado         {
221411e6a11SCarson Labrado             return;
2221c0bb5c6SCarson Labrado         }
2231c0bb5c6SCarson Labrado 
22411987af6SCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
22511987af6SCarson Labrado                                url.buffer()))
2261c0bb5c6SCarson Labrado         {
22711987af6SCarson Labrado             std::string collectionItem(prefix);
22811987af6SCarson Labrado             collectionItem += "_" + (*it);
22911987af6SCarson Labrado             url.segments().push_back(collectionItem);
23011987af6SCarson Labrado             it++;
23111987af6SCarson Labrado             addedPrefix = true;
23211987af6SCarson Labrado             break;
23311987af6SCarson Labrado         }
23411987af6SCarson Labrado 
23511987af6SCarson Labrado         url.segments().push_back(*it);
23611987af6SCarson Labrado     }
23711987af6SCarson Labrado 
23811987af6SCarson Labrado     // Finish constructing the URL here (if needed) to avoid additional checks
23911987af6SCarson Labrado     for (; it != end; it++)
24011987af6SCarson Labrado     {
24111987af6SCarson Labrado         url.segments().push_back(*it);
24211987af6SCarson Labrado     }
24311987af6SCarson Labrado 
24411987af6SCarson Labrado     if (addedPrefix)
24511987af6SCarson Labrado     {
24611987af6SCarson Labrado         url.segments().insert(url.segments().begin(), {"redfish", "v1"});
2470af78d5aSKhang Kieu         strValue = url.buffer();
2481c0bb5c6SCarson Labrado     }
2491c0bb5c6SCarson Labrado }
2501c0bb5c6SCarson Labrado 
2514ff0f1f4SEd Tanous inline void addPrefixToItem(nlohmann::json& item, std::string_view prefix)
2520af78d5aSKhang Kieu {
2530af78d5aSKhang Kieu     std::string* strValue = item.get_ptr<std::string*>();
2540af78d5aSKhang Kieu     if (strValue == nullptr)
2550af78d5aSKhang Kieu     {
256bf2ddedeSCarson Labrado         // Values for properties like "InvalidURI" and "ResourceMissingAtURI"
257bf2ddedeSCarson Labrado         // from within the Base Registry are objects instead of strings and will
258bf2ddedeSCarson Labrado         // fall into this check
259bf2ddedeSCarson Labrado         BMCWEB_LOG_DEBUG("Field was not a string");
2600af78d5aSKhang Kieu         return;
2610af78d5aSKhang Kieu     }
2620af78d5aSKhang Kieu     addPrefixToStringItem(*strValue, prefix);
2630af78d5aSKhang Kieu     item = *strValue;
2640af78d5aSKhang Kieu }
2650af78d5aSKhang Kieu 
2664ff0f1f4SEd Tanous inline void addAggregatedHeaders(crow::Response& asyncResp,
26724dadc88SCarson Labrado                                  const crow::Response& resp,
2680af78d5aSKhang Kieu                                  std::string_view prefix)
2690af78d5aSKhang Kieu {
2700af78d5aSKhang Kieu     if (!resp.getHeaderValue("Content-Type").empty())
2710af78d5aSKhang Kieu     {
2720af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::content_type,
2730af78d5aSKhang Kieu                             resp.getHeaderValue("Content-Type"));
2740af78d5aSKhang Kieu     }
2750af78d5aSKhang Kieu     if (!resp.getHeaderValue("Allow").empty())
2760af78d5aSKhang Kieu     {
2770af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::allow,
2780af78d5aSKhang Kieu                             resp.getHeaderValue("Allow"));
2790af78d5aSKhang Kieu     }
2800af78d5aSKhang Kieu     std::string_view header = resp.getHeaderValue("Location");
2810af78d5aSKhang Kieu     if (!header.empty())
2820af78d5aSKhang Kieu     {
2830af78d5aSKhang Kieu         std::string location(header);
2840af78d5aSKhang Kieu         addPrefixToStringItem(location, prefix);
2850af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::location, location);
2860af78d5aSKhang Kieu     }
2870af78d5aSKhang Kieu     if (!resp.getHeaderValue("Retry-After").empty())
2880af78d5aSKhang Kieu     {
2890af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::retry_after,
2900af78d5aSKhang Kieu                             resp.getHeaderValue("Retry-After"));
2910af78d5aSKhang Kieu     }
2920af78d5aSKhang Kieu     // TODO: we need special handling for Link Header Value
2930af78d5aSKhang Kieu }
2940af78d5aSKhang Kieu 
295b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others
2964ff0f1f4SEd Tanous inline void addPrefixToHeadersInResp(nlohmann::json& json,
2974ff0f1f4SEd Tanous                                      std::string_view prefix)
298b27e1cbeSCarson Labrado {
299b27e1cbeSCarson Labrado     // The passed in "HttpHeaders" should be an array of headers
300b27e1cbeSCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
301b27e1cbeSCarson Labrado     if (array == nullptr)
302b27e1cbeSCarson Labrado     {
30362598e31SEd Tanous         BMCWEB_LOG_ERROR("Field wasn't an array_t????");
304b27e1cbeSCarson Labrado         return;
305b27e1cbeSCarson Labrado     }
306b27e1cbeSCarson Labrado 
307b27e1cbeSCarson Labrado     for (nlohmann::json& item : *array)
308b27e1cbeSCarson Labrado     {
309b27e1cbeSCarson Labrado         // Each header is a single string with the form "<Field>: <Value>"
310b27e1cbeSCarson Labrado         std::string* strHeader = item.get_ptr<std::string*>();
311b27e1cbeSCarson Labrado         if (strHeader == nullptr)
312b27e1cbeSCarson Labrado         {
31362598e31SEd Tanous             BMCWEB_LOG_CRITICAL("Field wasn't a string????");
314b27e1cbeSCarson Labrado             continue;
315b27e1cbeSCarson Labrado         }
316b27e1cbeSCarson Labrado 
317b27e1cbeSCarson Labrado         constexpr std::string_view location = "Location: ";
318b27e1cbeSCarson Labrado         if (strHeader->starts_with(location))
319b27e1cbeSCarson Labrado         {
320b27e1cbeSCarson Labrado             std::string header = strHeader->substr(location.size());
321b27e1cbeSCarson Labrado             addPrefixToStringItem(header, prefix);
322b27e1cbeSCarson Labrado             *strHeader = std::string(location) + header;
323b27e1cbeSCarson Labrado         }
324b27e1cbeSCarson Labrado     }
325b27e1cbeSCarson Labrado }
326b27e1cbeSCarson Labrado 
3271c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for
3287e8890c5SCarson Labrado // an aggregated resource.
3294ff0f1f4SEd Tanous inline void addPrefixes(nlohmann::json& json, std::string_view prefix)
3301c0bb5c6SCarson Labrado {
3311c0bb5c6SCarson Labrado     nlohmann::json::object_t* object =
3321c0bb5c6SCarson Labrado         json.get_ptr<nlohmann::json::object_t*>();
3331c0bb5c6SCarson Labrado     if (object != nullptr)
3341c0bb5c6SCarson Labrado     {
3351c0bb5c6SCarson Labrado         for (std::pair<const std::string, nlohmann::json>& item : *object)
3361c0bb5c6SCarson Labrado         {
3377e8890c5SCarson Labrado             if (isPropertyUri(item.first))
3381c0bb5c6SCarson Labrado             {
3397e8890c5SCarson Labrado                 addPrefixToItem(item.second, prefix);
3401c0bb5c6SCarson Labrado                 continue;
3411c0bb5c6SCarson Labrado             }
3421c0bb5c6SCarson Labrado 
343b27e1cbeSCarson Labrado             // "HttpHeaders" contains HTTP headers.  Among those we need to
344b27e1cbeSCarson Labrado             // attempt to fix the "Location" header
345b27e1cbeSCarson Labrado             if (item.first == "HttpHeaders")
346b27e1cbeSCarson Labrado             {
347b27e1cbeSCarson Labrado                 addPrefixToHeadersInResp(item.second, prefix);
348b27e1cbeSCarson Labrado                 continue;
349b27e1cbeSCarson Labrado             }
350b27e1cbeSCarson Labrado 
3518ece0e45SEd Tanous             // Recursively parse the rest of the json
3521c0bb5c6SCarson Labrado             addPrefixes(item.second, prefix);
3531c0bb5c6SCarson Labrado         }
3541c0bb5c6SCarson Labrado         return;
3551c0bb5c6SCarson Labrado     }
3561c0bb5c6SCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
3571c0bb5c6SCarson Labrado     if (array != nullptr)
3581c0bb5c6SCarson Labrado     {
3591c0bb5c6SCarson Labrado         for (nlohmann::json& item : *array)
3601c0bb5c6SCarson Labrado         {
3611c0bb5c6SCarson Labrado             addPrefixes(item, prefix);
3621c0bb5c6SCarson Labrado         }
3631c0bb5c6SCarson Labrado     }
3641c0bb5c6SCarson Labrado }
3651c0bb5c6SCarson Labrado 
366d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode)
367a7a80296SCarson Labrado {
36832d7d8ebSCarson Labrado     // Allow all response codes because we want to surface any satellite
36932d7d8ebSCarson Labrado     // issue to the client
37062598e31SEd Tanous     BMCWEB_LOG_DEBUG("Received {} response from satellite", respCode);
371d14a48ffSCarson Labrado     return boost::system::errc::make_error_code(boost::system::errc::success);
372d14a48ffSCarson Labrado }
373d14a48ffSCarson Labrado 
374d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy()
375d14a48ffSCarson Labrado {
3766bd30813SEd Tanous     return {.maxRetryAttempts = 0,
377d14a48ffSCarson Labrado             .requestByteLimit = aggregatorReadBodyLimit,
378d14a48ffSCarson Labrado             .maxConnections = 20,
379d14a48ffSCarson Labrado             .retryPolicyAction = "TerminateAfterRetries",
380d14a48ffSCarson Labrado             .retryIntervalSecs = std::chrono::seconds(0),
381d14a48ffSCarson Labrado             .invalidResp = aggregationRetryHandler};
382d14a48ffSCarson Labrado }
383d14a48ffSCarson Labrado 
384d14a48ffSCarson Labrado class RedfishAggregator
385d14a48ffSCarson Labrado {
386d14a48ffSCarson Labrado   private:
387d14a48ffSCarson Labrado     crow::HttpClient client;
388d14a48ffSCarson Labrado 
3897fb33566SCarson Labrado     // Dummy callback used by the Constructor so that it can report the number
3907fb33566SCarson Labrado     // of satellite configs when the class is first created
3917fb33566SCarson Labrado     static void constructorCallback(
3928b2521a5SCarson Labrado         const boost::system::error_code& ec,
3937fb33566SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
3947fb33566SCarson Labrado     {
3957fb33566SCarson Labrado         if (ec)
3967fb33566SCarson Labrado         {
39762598e31SEd Tanous             BMCWEB_LOG_ERROR("Something went wrong while querying dbus!");
3987fb33566SCarson Labrado             return;
3997fb33566SCarson Labrado         }
4007fb33566SCarson Labrado 
40162598e31SEd Tanous         BMCWEB_LOG_DEBUG("There were {} satellite configs found at startup",
40262598e31SEd Tanous                          std::to_string(satelliteInfo.size()));
4037fb33566SCarson Labrado     }
4047fb33566SCarson Labrado 
4057fb33566SCarson Labrado     // Search D-Bus objects for satellite config objects and add their
4067fb33566SCarson Labrado     // information if valid
4077fb33566SCarson Labrado     static void findSatelliteConfigs(
4087fb33566SCarson Labrado         const dbus::utility::ManagedObjectType& objects,
4097fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4107fb33566SCarson Labrado     {
4117fb33566SCarson Labrado         for (const auto& objectPath : objects)
4127fb33566SCarson Labrado         {
4137fb33566SCarson Labrado             for (const auto& interface : objectPath.second)
4147fb33566SCarson Labrado             {
4157fb33566SCarson Labrado                 if (interface.first ==
4167fb33566SCarson Labrado                     "xyz.openbmc_project.Configuration.SatelliteController")
4177fb33566SCarson Labrado                 {
41862598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Found Satellite Controller at {}",
41962598e31SEd Tanous                                      objectPath.first.str);
4207fb33566SCarson Labrado 
42105916cefSCarson Labrado                     if (!satelliteInfo.empty())
42205916cefSCarson Labrado                     {
42362598e31SEd Tanous                         BMCWEB_LOG_ERROR(
42462598e31SEd Tanous                             "Redfish Aggregation only supports one satellite!");
42562598e31SEd Tanous                         BMCWEB_LOG_DEBUG("Clearing all satellite data");
42605916cefSCarson Labrado                         satelliteInfo.clear();
42705916cefSCarson Labrado                         return;
42805916cefSCarson Labrado                     }
42905916cefSCarson Labrado 
43005916cefSCarson Labrado                     // For now assume there will only be one satellite config.
43105916cefSCarson Labrado                     // Assign it the name/prefix "5B247A"
43205916cefSCarson Labrado                     addSatelliteConfig("5B247A", interface.second,
43305916cefSCarson Labrado                                        satelliteInfo);
4347fb33566SCarson Labrado                 }
4357fb33566SCarson Labrado             }
4367fb33566SCarson Labrado         }
4377fb33566SCarson Labrado     }
4387fb33566SCarson Labrado 
4397fb33566SCarson Labrado     // Parse the properties of a satellite config object and add the
4407fb33566SCarson Labrado     // configuration if the properties are valid
4417fb33566SCarson Labrado     static void addSatelliteConfig(
44205916cefSCarson Labrado         const std::string& name,
4437fb33566SCarson Labrado         const dbus::utility::DBusPropertiesMap& properties,
4447fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4457fb33566SCarson Labrado     {
4467fb33566SCarson Labrado         boost::urls::url url;
4477fb33566SCarson Labrado 
4487fb33566SCarson Labrado         for (const auto& prop : properties)
4497fb33566SCarson Labrado         {
45005916cefSCarson Labrado             if (prop.first == "Hostname")
4517fb33566SCarson Labrado             {
4527fb33566SCarson Labrado                 const std::string* propVal =
4537fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
4547fb33566SCarson Labrado                 if (propVal == nullptr)
4557fb33566SCarson Labrado                 {
45662598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid Hostname value");
4577fb33566SCarson Labrado                     return;
4587fb33566SCarson Labrado                 }
4597fb33566SCarson Labrado                 url.set_host(*propVal);
4607fb33566SCarson Labrado             }
4617fb33566SCarson Labrado 
4627fb33566SCarson Labrado             else if (prop.first == "Port")
4637fb33566SCarson Labrado             {
4647fb33566SCarson Labrado                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
4657fb33566SCarson Labrado                 if (propVal == nullptr)
4667fb33566SCarson Labrado                 {
46762598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid Port value");
4687fb33566SCarson Labrado                     return;
4697fb33566SCarson Labrado                 }
4707fb33566SCarson Labrado 
4717fb33566SCarson Labrado                 if (*propVal > std::numeric_limits<uint16_t>::max())
4727fb33566SCarson Labrado                 {
47362598e31SEd Tanous                     BMCWEB_LOG_ERROR("Port value out of range");
4747fb33566SCarson Labrado                     return;
4757fb33566SCarson Labrado                 }
476079360aeSEd Tanous                 url.set_port(std::to_string(static_cast<uint16_t>(*propVal)));
4777fb33566SCarson Labrado             }
4787fb33566SCarson Labrado 
4797fb33566SCarson Labrado             else if (prop.first == "AuthType")
4807fb33566SCarson Labrado             {
4817fb33566SCarson Labrado                 const std::string* propVal =
4827fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
4837fb33566SCarson Labrado                 if (propVal == nullptr)
4847fb33566SCarson Labrado                 {
48562598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid AuthType value");
4867fb33566SCarson Labrado                     return;
4877fb33566SCarson Labrado                 }
4887fb33566SCarson Labrado 
4897fb33566SCarson Labrado                 // For now assume authentication not required to communicate
4907fb33566SCarson Labrado                 // with the satellite BMC
4917fb33566SCarson Labrado                 if (*propVal != "None")
4927fb33566SCarson Labrado                 {
49362598e31SEd Tanous                     BMCWEB_LOG_ERROR(
49462598e31SEd Tanous                         "Unsupported AuthType value: {}, only \"none\" is supported",
49562598e31SEd Tanous                         *propVal);
4967fb33566SCarson Labrado                     return;
4977fb33566SCarson Labrado                 }
4987fb33566SCarson Labrado                 url.set_scheme("http");
4997fb33566SCarson Labrado             }
5007fb33566SCarson Labrado         } // Finished reading properties
5017fb33566SCarson Labrado 
5027fb33566SCarson Labrado         // Make sure all required config information was made available
5037fb33566SCarson Labrado         if (url.host().empty())
5047fb33566SCarson Labrado         {
50562598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing Host", name);
5067fb33566SCarson Labrado             return;
5077fb33566SCarson Labrado         }
5087fb33566SCarson Labrado 
5097fb33566SCarson Labrado         if (!url.has_port())
5107fb33566SCarson Labrado         {
51162598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing Port", name);
5127fb33566SCarson Labrado             return;
5137fb33566SCarson Labrado         }
5147fb33566SCarson Labrado 
5157fb33566SCarson Labrado         if (!url.has_scheme())
5167fb33566SCarson Labrado         {
51762598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing AuthType", name);
5187fb33566SCarson Labrado             return;
5197fb33566SCarson Labrado         }
5207fb33566SCarson Labrado 
5217fb33566SCarson Labrado         std::string resultString;
5227fb33566SCarson Labrado         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
5237fb33566SCarson Labrado         if (result.second)
5247fb33566SCarson Labrado         {
5257fb33566SCarson Labrado             resultString = "Added new satellite config ";
5267fb33566SCarson Labrado         }
5277fb33566SCarson Labrado         else
5287fb33566SCarson Labrado         {
5297fb33566SCarson Labrado             resultString = "Updated existing satellite config ";
5307fb33566SCarson Labrado         }
5317fb33566SCarson Labrado 
53262598e31SEd Tanous         BMCWEB_LOG_DEBUG("{}{} at {}://{}", resultString, name,
53362598e31SEd Tanous                          result.first->second.scheme(),
53462598e31SEd Tanous                          result.first->second.encoded_host_and_port());
5357fb33566SCarson Labrado     }
5367fb33566SCarson Labrado 
53746a81465SCarson Labrado     enum AggregationType
53846a81465SCarson Labrado     {
53946a81465SCarson Labrado         Collection,
540e002dbc0SCarson Labrado         ContainsSubordinate,
54146a81465SCarson Labrado         Resource,
54246a81465SCarson Labrado     };
54346a81465SCarson Labrado 
54446a81465SCarson Labrado     static void
545e002dbc0SCarson Labrado         startAggregation(AggregationType aggType, const crow::Request& thisReq,
54646a81465SCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
54746a81465SCarson Labrado     {
548e002dbc0SCarson Labrado         if (thisReq.method() != boost::beast::http::verb::get)
549e002dbc0SCarson Labrado         {
550e002dbc0SCarson Labrado             if (aggType == AggregationType::Collection)
551db18fc98SCarson Labrado             {
55262598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
55362598e31SEd Tanous                     "Only aggregate GET requests to top level collections");
554db18fc98SCarson Labrado                 return;
555db18fc98SCarson Labrado             }
556db18fc98SCarson Labrado 
557e002dbc0SCarson Labrado             if (aggType == AggregationType::ContainsSubordinate)
558e002dbc0SCarson Labrado             {
55962598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
56062598e31SEd Tanous                     "Only aggregate GET requests when uptree of a top level collection");
561e002dbc0SCarson Labrado                 return;
562e002dbc0SCarson Labrado             }
563e002dbc0SCarson Labrado         }
564e002dbc0SCarson Labrado 
56546a81465SCarson Labrado         // Create a copy of thisReq so we we can still locally process the req
56646a81465SCarson Labrado         std::error_code ec;
56746a81465SCarson Labrado         auto localReq = std::make_shared<crow::Request>(thisReq.req, ec);
56846a81465SCarson Labrado         if (ec)
56946a81465SCarson Labrado         {
57062598e31SEd Tanous             BMCWEB_LOG_ERROR("Failed to create copy of request");
571e002dbc0SCarson Labrado             if (aggType == AggregationType::Resource)
57246a81465SCarson Labrado             {
57346a81465SCarson Labrado                 messages::internalError(asyncResp->res);
57446a81465SCarson Labrado             }
57546a81465SCarson Labrado             return;
57646a81465SCarson Labrado         }
57746a81465SCarson Labrado 
5786282bc71SEd Tanous         if (aggType == AggregationType::Collection)
5796282bc71SEd Tanous         {
5806282bc71SEd Tanous             boost::urls::url& urlNew = localReq->url();
5816282bc71SEd Tanous             auto paramsIt = urlNew.params().begin();
5826282bc71SEd Tanous             while (paramsIt != urlNew.params().end())
5836282bc71SEd Tanous             {
5846282bc71SEd Tanous                 const boost::urls::param& param = *paramsIt;
5856282bc71SEd Tanous                 // only and $skip, params can't be passed to satellite
5866282bc71SEd Tanous                 // as applying these filters twice results in different results.
5876282bc71SEd Tanous                 // Removing them will cause them to only be processed in the
5886282bc71SEd Tanous                 // aggregator. Note, this still doesn't work for collections
5896282bc71SEd Tanous                 // that might return less than the complete collection by
5906282bc71SEd Tanous                 // default, but hopefully those are rare/nonexistent in top
5916282bc71SEd Tanous                 // collections.  bmcweb doesn't implement any of these.
5926282bc71SEd Tanous                 if (param.key == "only" || param.key == "$skip")
5936282bc71SEd Tanous                 {
5946282bc71SEd Tanous                     BMCWEB_LOG_DEBUG(
5956282bc71SEd Tanous                         "Erasing \"{}\" param from request to top level collection",
5966282bc71SEd Tanous                         param.key);
5976282bc71SEd Tanous 
5986282bc71SEd Tanous                     paramsIt = urlNew.params().erase(paramsIt);
5996282bc71SEd Tanous                     continue;
6006282bc71SEd Tanous                 }
6016282bc71SEd Tanous                 // Pass all other parameters
6026282bc71SEd Tanous                 paramsIt++;
6036282bc71SEd Tanous             }
6046282bc71SEd Tanous             localReq->target(urlNew.buffer());
6056282bc71SEd Tanous         }
6066282bc71SEd Tanous 
607e002dbc0SCarson Labrado         getSatelliteConfigs(
608e002dbc0SCarson Labrado             std::bind_front(aggregateAndHandle, aggType, localReq, asyncResp));
60946a81465SCarson Labrado     }
61046a81465SCarson Labrado 
611db18fc98SCarson Labrado     static void findSatellite(
61246a81465SCarson Labrado         const crow::Request& req,
61346a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
61446a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo,
61546a81465SCarson Labrado         std::string_view memberName)
61646a81465SCarson Labrado     {
61746a81465SCarson Labrado         // Determine if the resource ID begins with a known prefix
61846a81465SCarson Labrado         for (const auto& satellite : satelliteInfo)
61946a81465SCarson Labrado         {
62046a81465SCarson Labrado             std::string targetPrefix = satellite.first;
62146a81465SCarson Labrado             targetPrefix += "_";
62246a81465SCarson Labrado             if (memberName.starts_with(targetPrefix))
62346a81465SCarson Labrado             {
62462598e31SEd Tanous                 BMCWEB_LOG_DEBUG("\"{}\" is a known prefix", satellite.first);
62546a81465SCarson Labrado 
62646a81465SCarson Labrado                 // Remove the known prefix from the request's URI and
62746a81465SCarson Labrado                 // then forward to the associated satellite BMC
62846a81465SCarson Labrado                 getInstance().forwardRequest(req, asyncResp, satellite.first,
62946a81465SCarson Labrado                                              satelliteInfo);
63046a81465SCarson Labrado                 return;
63146a81465SCarson Labrado             }
63246a81465SCarson Labrado         }
633db18fc98SCarson Labrado 
634db18fc98SCarson Labrado         // We didn't recognize the prefix and need to return a 404
63539662a3bSEd Tanous         std::string nameStr = req.url().segments().back();
636db18fc98SCarson Labrado         messages::resourceNotFound(asyncResp->res, "", nameStr);
63746a81465SCarson Labrado     }
63846a81465SCarson Labrado 
63946a81465SCarson Labrado     // Intended to handle an incoming request based on if Redfish Aggregation
64046a81465SCarson Labrado     // is enabled.  Forwards request to satellite BMC if it exists.
64146a81465SCarson Labrado     static void aggregateAndHandle(
642e002dbc0SCarson Labrado         AggregationType aggType,
64346a81465SCarson Labrado         const std::shared_ptr<crow::Request>& sharedReq,
64446a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6458b2521a5SCarson Labrado         const boost::system::error_code& ec,
64646a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
64746a81465SCarson Labrado     {
64846a81465SCarson Labrado         if (sharedReq == nullptr)
64946a81465SCarson Labrado         {
65046a81465SCarson Labrado             return;
65146a81465SCarson Labrado         }
6528b2521a5SCarson Labrado         // Something went wrong while querying dbus
6538b2521a5SCarson Labrado         if (ec)
6548b2521a5SCarson Labrado         {
6558b2521a5SCarson Labrado             messages::internalError(asyncResp->res);
6568b2521a5SCarson Labrado             return;
6578b2521a5SCarson Labrado         }
658db18fc98SCarson Labrado 
659db18fc98SCarson Labrado         // No satellite configs means we don't need to keep attempting to
660db18fc98SCarson Labrado         // aggregate
661db18fc98SCarson Labrado         if (satelliteInfo.empty())
662db18fc98SCarson Labrado         {
663e002dbc0SCarson Labrado             // For collections or resources that can contain a subordinate
664e002dbc0SCarson Labrado             // top level collection we'll also handle the request locally so we
665db18fc98SCarson Labrado             // don't need to write an error code
666e002dbc0SCarson Labrado             if (aggType == AggregationType::Resource)
667db18fc98SCarson Labrado             {
66839662a3bSEd Tanous                 std::string nameStr = sharedReq->url().segments().back();
669db18fc98SCarson Labrado                 messages::resourceNotFound(asyncResp->res, "", nameStr);
670db18fc98SCarson Labrado             }
671db18fc98SCarson Labrado             return;
672db18fc98SCarson Labrado         }
673db18fc98SCarson Labrado 
67446a81465SCarson Labrado         const crow::Request& thisReq = *sharedReq;
67562598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation is enabled, begin processing of {}",
67662598e31SEd Tanous                          thisReq.target());
67746a81465SCarson Labrado 
67846a81465SCarson Labrado         // We previously determined the request is for a collection.  No need to
67946a81465SCarson Labrado         // check again
680e002dbc0SCarson Labrado         if (aggType == AggregationType::Collection)
68146a81465SCarson Labrado         {
68262598e31SEd Tanous             BMCWEB_LOG_DEBUG("Aggregating a collection");
6834c30e226SCarson Labrado             // We need to use a specific response handler and send the
6844c30e226SCarson Labrado             // request to all known satellites
6854c30e226SCarson Labrado             getInstance().forwardCollectionRequests(thisReq, asyncResp,
6864c30e226SCarson Labrado                                                     satelliteInfo);
68746a81465SCarson Labrado             return;
68846a81465SCarson Labrado         }
68946a81465SCarson Labrado 
690e002dbc0SCarson Labrado         // We previously determined the request may contain a subordinate
691e002dbc0SCarson Labrado         // collection.  No need to check again
692e002dbc0SCarson Labrado         if (aggType == AggregationType::ContainsSubordinate)
693e002dbc0SCarson Labrado         {
69462598e31SEd Tanous             BMCWEB_LOG_DEBUG(
69562598e31SEd Tanous                 "Aggregating what may have a subordinate collection");
696e002dbc0SCarson Labrado             // We need to use a specific response handler and send the
697e002dbc0SCarson Labrado             // request to all known satellites
698e002dbc0SCarson Labrado             getInstance().forwardContainsSubordinateRequests(thisReq, asyncResp,
699e002dbc0SCarson Labrado                                                              satelliteInfo);
700e002dbc0SCarson Labrado             return;
701e002dbc0SCarson Labrado         }
702e002dbc0SCarson Labrado 
70339662a3bSEd Tanous         const boost::urls::segments_view urlSegments = thisReq.url().segments();
7047c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
7054a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
7064a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
7077c4c52cbSCarson Labrado 
7087c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
7097c4c52cbSCarson Labrado         it++;
7107c4c52cbSCarson Labrado         it++;
7117c4c52cbSCarson Labrado         for (; it != end; it++)
71246a81465SCarson Labrado         {
7137c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
7147c4c52cbSCarson Labrado                                    currentUrl.buffer()))
7157c4c52cbSCarson Labrado             {
7167c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
7177c4c52cbSCarson Labrado                 // must contain an aggregation prefix
7187c4c52cbSCarson Labrado                 findSatellite(thisReq, asyncResp, satelliteInfo, *it);
71946a81465SCarson Labrado                 return;
72046a81465SCarson Labrado             }
72146a81465SCarson Labrado 
7227c4c52cbSCarson Labrado             currentUrl.segments().push_back(*it);
72346a81465SCarson Labrado         }
724db18fc98SCarson Labrado 
725db18fc98SCarson Labrado         // We shouldn't reach this point since we should've hit one of the
726db18fc98SCarson Labrado         // previous exits
727db18fc98SCarson Labrado         messages::internalError(asyncResp->res);
72846a81465SCarson Labrado     }
72946a81465SCarson Labrado 
73046a81465SCarson Labrado     // Attempt to forward a request to the satellite BMC associated with the
73146a81465SCarson Labrado     // prefix.
73246a81465SCarson Labrado     void forwardRequest(
73346a81465SCarson Labrado         const crow::Request& thisReq,
73446a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
73546a81465SCarson Labrado         const std::string& prefix,
73646a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
73746a81465SCarson Labrado     {
73846a81465SCarson Labrado         const auto& sat = satelliteInfo.find(prefix);
73946a81465SCarson Labrado         if (sat == satelliteInfo.end())
74046a81465SCarson Labrado         {
74146a81465SCarson Labrado             // Realistically this shouldn't get called since we perform an
74246a81465SCarson Labrado             // earlier check to make sure the prefix exists
74362598e31SEd Tanous             BMCWEB_LOG_ERROR("Unrecognized satellite prefix \"{}\"", prefix);
74446a81465SCarson Labrado             return;
74546a81465SCarson Labrado         }
74646a81465SCarson Labrado 
74746a81465SCarson Labrado         // We need to strip the prefix from the request's path
748a716aa74SEd Tanous         boost::urls::url targetURI(thisReq.target());
749a716aa74SEd Tanous         std::string path = thisReq.url().path();
750a716aa74SEd Tanous         size_t pos = path.find(prefix + "_");
75146a81465SCarson Labrado         if (pos == std::string::npos)
75246a81465SCarson Labrado         {
75346a81465SCarson Labrado             // If this fails then something went wrong
75462598e31SEd Tanous             BMCWEB_LOG_ERROR("Error removing prefix \"{}_\" from request URI",
75562598e31SEd Tanous                              prefix);
75646a81465SCarson Labrado             messages::internalError(asyncResp->res);
75746a81465SCarson Labrado             return;
75846a81465SCarson Labrado         }
759a716aa74SEd Tanous         path.erase(pos, prefix.size() + 1);
76046a81465SCarson Labrado 
76146a81465SCarson Labrado         std::function<void(crow::Response&)> cb =
7621c0bb5c6SCarson Labrado             std::bind_front(processResponse, prefix, asyncResp);
76346a81465SCarson Labrado 
76427b0cf90SEd Tanous         std::string data = thisReq.body();
765a716aa74SEd Tanous         boost::urls::url url(sat->second);
766a716aa74SEd Tanous         url.set_path(path);
767a716aa74SEd Tanous         if (targetURI.has_query())
768a716aa74SEd Tanous         {
769a716aa74SEd Tanous             url.set_query(targetURI.query());
770a716aa74SEd Tanous         }
77119bb362bSEd Tanous         client.sendDataWithCallback(std::move(data), url,
77219bb362bSEd Tanous                                     ensuressl::VerifyCertificate::Verify,
77319bb362bSEd Tanous                                     thisReq.fields(), thisReq.method(), cb);
77446a81465SCarson Labrado     }
77546a81465SCarson Labrado 
7764c30e226SCarson Labrado     // Forward a request for a collection URI to each known satellite BMC
7774c30e226SCarson Labrado     void forwardCollectionRequests(
7784c30e226SCarson Labrado         const crow::Request& thisReq,
7794c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
7804c30e226SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
7814c30e226SCarson Labrado     {
7824c30e226SCarson Labrado         for (const auto& sat : satelliteInfo)
7834c30e226SCarson Labrado         {
7844c30e226SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
7854c30e226SCarson Labrado                 processCollectionResponse, sat.first, asyncResp);
7864c30e226SCarson Labrado 
787a716aa74SEd Tanous             boost::urls::url url(sat.second);
788a716aa74SEd Tanous             url.set_path(thisReq.url().path());
789a716aa74SEd Tanous             if (thisReq.url().has_query())
790a716aa74SEd Tanous             {
791a716aa74SEd Tanous                 url.set_query(thisReq.url().query());
792a716aa74SEd Tanous             }
79327b0cf90SEd Tanous             std::string data = thisReq.body();
79419bb362bSEd Tanous             client.sendDataWithCallback(std::move(data), url,
79519bb362bSEd Tanous                                         ensuressl::VerifyCertificate::Verify,
79619bb362bSEd Tanous                                         thisReq.fields(), thisReq.method(), cb);
7974c30e226SCarson Labrado         }
7984c30e226SCarson Labrado     }
7994c30e226SCarson Labrado 
800e002dbc0SCarson Labrado     // Forward request for a URI that is uptree of a top level collection to
801e002dbc0SCarson Labrado     // each known satellite BMC
802e002dbc0SCarson Labrado     void forwardContainsSubordinateRequests(
803e002dbc0SCarson Labrado         const crow::Request& thisReq,
804e002dbc0SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
805e002dbc0SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
806e002dbc0SCarson Labrado     {
807e002dbc0SCarson Labrado         for (const auto& sat : satelliteInfo)
808e002dbc0SCarson Labrado         {
809e002dbc0SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
810e002dbc0SCarson Labrado                 processContainsSubordinateResponse, sat.first, asyncResp);
811e002dbc0SCarson Labrado 
812e002dbc0SCarson Labrado             // will ignore an expanded resource in the response if that resource
813e002dbc0SCarson Labrado             // is not already supported by the aggregating BMC
814e002dbc0SCarson Labrado             // TODO: Improve the processing so that we don't have to strip query
815e002dbc0SCarson Labrado             // params in this specific case
816a716aa74SEd Tanous             boost::urls::url url(sat.second);
817a716aa74SEd Tanous             url.set_path(thisReq.url().path());
818a716aa74SEd Tanous 
81927b0cf90SEd Tanous             std::string data = thisReq.body();
820a716aa74SEd Tanous 
82119bb362bSEd Tanous             client.sendDataWithCallback(std::move(data), url,
82219bb362bSEd Tanous                                         ensuressl::VerifyCertificate::Verify,
82319bb362bSEd Tanous                                         thisReq.fields(), thisReq.method(), cb);
824e002dbc0SCarson Labrado         }
825e002dbc0SCarson Labrado     }
826e002dbc0SCarson Labrado 
82732d7d8ebSCarson Labrado   public:
828f8ca6d79SEd Tanous     explicit RedfishAggregator(boost::asio::io_context& ioc) :
829f8ca6d79SEd Tanous         client(ioc,
830f8ca6d79SEd Tanous                std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy()))
831f8ca6d79SEd Tanous     {
832f8ca6d79SEd Tanous         getSatelliteConfigs(constructorCallback);
833f8ca6d79SEd Tanous     }
83432d7d8ebSCarson Labrado     RedfishAggregator(const RedfishAggregator&) = delete;
83532d7d8ebSCarson Labrado     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
83632d7d8ebSCarson Labrado     RedfishAggregator(RedfishAggregator&&) = delete;
83732d7d8ebSCarson Labrado     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
83832d7d8ebSCarson Labrado     ~RedfishAggregator() = default;
83932d7d8ebSCarson Labrado 
840f8ca6d79SEd Tanous     static RedfishAggregator& getInstance(boost::asio::io_context* io = nullptr)
84132d7d8ebSCarson Labrado     {
842f8ca6d79SEd Tanous         static RedfishAggregator handler(*io);
84332d7d8ebSCarson Labrado         return handler;
84432d7d8ebSCarson Labrado     }
84532d7d8ebSCarson Labrado 
8468b2521a5SCarson Labrado     // Polls D-Bus to get all available satellite config information
8478b2521a5SCarson Labrado     // Expects a handler which interacts with the returned configs
8488b2521a5SCarson Labrado     static void getSatelliteConfigs(
8498b2521a5SCarson Labrado         std::function<
8508b2521a5SCarson Labrado             void(const boost::system::error_code&,
8518b2521a5SCarson Labrado                  const std::unordered_map<std::string, boost::urls::url>&)>
8528b2521a5SCarson Labrado             handler)
8538b2521a5SCarson Labrado     {
85462598e31SEd Tanous         BMCWEB_LOG_DEBUG("Gathering satellite configs");
8555eb468daSGeorge Liu         sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
8565eb468daSGeorge Liu         dbus::utility::getManagedObjects(
8575eb468daSGeorge Liu             "xyz.openbmc_project.EntityManager", path,
8588b2521a5SCarson Labrado             [handler{std::move(handler)}](
8598b2521a5SCarson Labrado                 const boost::system::error_code& ec,
8608b2521a5SCarson Labrado                 const dbus::utility::ManagedObjectType& objects) {
8618b2521a5SCarson Labrado                 std::unordered_map<std::string, boost::urls::url> satelliteInfo;
8628b2521a5SCarson Labrado                 if (ec)
8638b2521a5SCarson Labrado                 {
86462598e31SEd Tanous                     BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(),
86562598e31SEd Tanous                                      ec.message());
8668b2521a5SCarson Labrado                     handler(ec, satelliteInfo);
8678b2521a5SCarson Labrado                     return;
8688b2521a5SCarson Labrado                 }
8698b2521a5SCarson Labrado 
8708b2521a5SCarson Labrado                 // Maps a chosen alias representing a satellite BMC to a url
8718b2521a5SCarson Labrado                 // containing the information required to create a http
8728b2521a5SCarson Labrado                 // connection to the satellite
8738b2521a5SCarson Labrado                 findSatelliteConfigs(objects, satelliteInfo);
8748b2521a5SCarson Labrado 
8758b2521a5SCarson Labrado                 if (!satelliteInfo.empty())
8768b2521a5SCarson Labrado                 {
87762598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
87862598e31SEd Tanous                         "Redfish Aggregation enabled with {} satellite BMCs",
87962598e31SEd Tanous                         std::to_string(satelliteInfo.size()));
8808b2521a5SCarson Labrado                 }
8818b2521a5SCarson Labrado                 else
8828b2521a5SCarson Labrado                 {
88362598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
88462598e31SEd Tanous                         "No satellite BMCs detected.  Redfish Aggregation not enabled");
8858b2521a5SCarson Labrado                 }
8868b2521a5SCarson Labrado                 handler(ec, satelliteInfo);
8875eb468daSGeorge Liu             });
8888b2521a5SCarson Labrado     }
8898b2521a5SCarson Labrado 
89046a81465SCarson Labrado     // Processes the response returned by a satellite BMC and loads its
89146a81465SCarson Labrado     // contents into asyncResp
89246a81465SCarson Labrado     static void
8931c0bb5c6SCarson Labrado         processResponse(std::string_view prefix,
8941c0bb5c6SCarson Labrado                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
89546a81465SCarson Labrado                         crow::Response& resp)
89646a81465SCarson Labrado     {
89743e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
89843e14d38SCarson Labrado         // overwrite the response headers in that case
89946b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
90046b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
90143e14d38SCarson Labrado         {
90243e14d38SCarson Labrado             asyncResp->res.result(resp.result());
90343e14d38SCarson Labrado             return;
90443e14d38SCarson Labrado         }
90543e14d38SCarson Labrado 
90632d7d8ebSCarson Labrado         // We want to attempt prefix fixing regardless of response code
90746a81465SCarson Labrado         // The resp will not have a json component
90846a81465SCarson Labrado         // We need to create a json from resp's stringResponse
90918f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
91046a81465SCarson Labrado         {
911bd79bce8SPatrick Williams             nlohmann::json jsonVal =
912bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
91346a81465SCarson Labrado             if (jsonVal.is_discarded())
91446a81465SCarson Labrado             {
91562598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
91646a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
91746a81465SCarson Labrado                 return;
91846a81465SCarson Labrado             }
91946a81465SCarson Labrado 
92062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
92146a81465SCarson Labrado 
9221c0bb5c6SCarson Labrado             addPrefixes(jsonVal, prefix);
9231c0bb5c6SCarson Labrado 
92462598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
9251c0bb5c6SCarson Labrado 
92646a81465SCarson Labrado             asyncResp->res.result(resp.result());
92746a81465SCarson Labrado             asyncResp->res.jsonValue = std::move(jsonVal);
92846a81465SCarson Labrado 
92962598e31SEd Tanous             BMCWEB_LOG_DEBUG("Finished writing asyncResp");
93046a81465SCarson Labrado         }
93146a81465SCarson Labrado         else
93246a81465SCarson Labrado         {
9330af78d5aSKhang Kieu             // We allow any Content-Type that is not "application/json" now
9340af78d5aSKhang Kieu             asyncResp->res.result(resp.result());
93527b0cf90SEd Tanous             asyncResp->res.copyBody(resp);
93646a81465SCarson Labrado         }
9370af78d5aSKhang Kieu         addAggregatedHeaders(asyncResp->res, resp, prefix);
93846a81465SCarson Labrado     }
93946a81465SCarson Labrado 
9404c30e226SCarson Labrado     // Processes the collection response returned by a satellite BMC and merges
9414c30e226SCarson Labrado     // its "@odata.id" values
9424c30e226SCarson Labrado     static void processCollectionResponse(
9434c30e226SCarson Labrado         const std::string& prefix,
9444c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
9454c30e226SCarson Labrado         crow::Response& resp)
9464c30e226SCarson Labrado     {
94743e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
94843e14d38SCarson Labrado         // overwrite the response headers in that case
94946b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
95046b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
95143e14d38SCarson Labrado         {
95243e14d38SCarson Labrado             return;
95343e14d38SCarson Labrado         }
95443e14d38SCarson Labrado 
9554c30e226SCarson Labrado         if (resp.resultInt() != 200)
9564c30e226SCarson Labrado         {
95762598e31SEd Tanous             BMCWEB_LOG_DEBUG(
95862598e31SEd Tanous                 "Collection resource does not exist in satellite BMC \"{}\"",
95962598e31SEd Tanous                 prefix);
9604c30e226SCarson Labrado             // Return the error if we haven't had any successes
9614c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
9624c30e226SCarson Labrado             {
96346b30283SCarson Labrado                 asyncResp->res.result(resp.result());
96427b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
9654c30e226SCarson Labrado             }
9664c30e226SCarson Labrado             return;
9674c30e226SCarson Labrado         }
9684c30e226SCarson Labrado 
9694c30e226SCarson Labrado         // The resp will not have a json component
9704c30e226SCarson Labrado         // We need to create a json from resp's stringResponse
97118f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
9724c30e226SCarson Labrado         {
973bd79bce8SPatrick Williams             nlohmann::json jsonVal =
974bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
9754c30e226SCarson Labrado             if (jsonVal.is_discarded())
9764c30e226SCarson Labrado             {
97762598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
9784c30e226SCarson Labrado 
9794c30e226SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
98046b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
9814c30e226SCarson Labrado                 {
9824c30e226SCarson Labrado                     messages::operationFailed(asyncResp->res);
9834c30e226SCarson Labrado                 }
9844c30e226SCarson Labrado                 return;
9854c30e226SCarson Labrado             }
9864c30e226SCarson Labrado 
98762598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
9884c30e226SCarson Labrado 
9894c30e226SCarson Labrado             // Now we need to add the prefix to the URIs contained in the
9904c30e226SCarson Labrado             // response.
9914c30e226SCarson Labrado             addPrefixes(jsonVal, prefix);
9924c30e226SCarson Labrado 
99362598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
9944c30e226SCarson Labrado 
9954c30e226SCarson Labrado             // If this resource collection does not exist on the aggregating bmc
9964c30e226SCarson Labrado             // and has not already been added from processing the response from
9974c30e226SCarson Labrado             // a different satellite then we need to completely overwrite
9984c30e226SCarson Labrado             // asyncResp
9994c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10004c30e226SCarson Labrado             {
10014c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
10024c30e226SCarson Labrado                 // "Members" array
10034c30e226SCarson Labrado                 if ((!jsonVal.contains("Members")) &&
10044c30e226SCarson Labrado                     (!jsonVal["Members"].is_array()))
10054c30e226SCarson Labrado                 {
100662598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
100762598e31SEd Tanous                         "Skipping aggregating unsupported resource");
10084c30e226SCarson Labrado                     return;
10094c30e226SCarson Labrado                 }
10104c30e226SCarson Labrado 
101162598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
101262598e31SEd Tanous                     "Collection does not exist, overwriting asyncResp");
10134c30e226SCarson Labrado                 asyncResp->res.result(resp.result());
10144c30e226SCarson Labrado                 asyncResp->res.jsonValue = std::move(jsonVal);
101543e14d38SCarson Labrado                 asyncResp->res.addHeader("Content-Type", "application/json");
10164c30e226SCarson Labrado 
101762598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Finished overwriting asyncResp");
10184c30e226SCarson Labrado             }
10194c30e226SCarson Labrado             else
10204c30e226SCarson Labrado             {
10214c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
10224c30e226SCarson Labrado                 // "Members" array
10234c30e226SCarson Labrado                 if ((!asyncResp->res.jsonValue.contains("Members")) &&
10244c30e226SCarson Labrado                     (!asyncResp->res.jsonValue["Members"].is_array()))
10254c30e226SCarson Labrado 
10264c30e226SCarson Labrado                 {
102762598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
102862598e31SEd Tanous                         "Skipping aggregating unsupported resource");
10294c30e226SCarson Labrado                     return;
10304c30e226SCarson Labrado                 }
10314c30e226SCarson Labrado 
103262598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
103362598e31SEd Tanous                     "Adding aggregated resources from \"{}\" to collection",
103462598e31SEd Tanous                     prefix);
10354c30e226SCarson Labrado 
10364c30e226SCarson Labrado                 // TODO: This is a potential race condition with multiple
10374c30e226SCarson Labrado                 // satellites and the aggregating bmc attempting to write to
10384c30e226SCarson Labrado                 // update this array.  May need to cascade calls to the next
10394c30e226SCarson Labrado                 // satellite at the end of this function.
10404c30e226SCarson Labrado                 // This is presumably not a concern when there is only a single
10414c30e226SCarson Labrado                 // satellite since the aggregating bmc should have completed
10424c30e226SCarson Labrado                 // before the response is received from the satellite.
10434c30e226SCarson Labrado 
10444c30e226SCarson Labrado                 auto& members = asyncResp->res.jsonValue["Members"];
10454c30e226SCarson Labrado                 auto& satMembers = jsonVal["Members"];
10464c30e226SCarson Labrado                 for (auto& satMem : satMembers)
10474c30e226SCarson Labrado                 {
1048b2ba3072SPatrick Williams                     members.emplace_back(std::move(satMem));
10494c30e226SCarson Labrado                 }
10504c30e226SCarson Labrado                 asyncResp->res.jsonValue["Members@odata.count"] =
10514c30e226SCarson Labrado                     members.size();
10524c30e226SCarson Labrado 
10534c30e226SCarson Labrado                 // TODO: Do we need to sort() after updating the array?
10544c30e226SCarson Labrado             }
10554c30e226SCarson Labrado         }
10564c30e226SCarson Labrado         else
10574c30e226SCarson Labrado         {
105862598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
105962598e31SEd Tanous                              prefix);
106043e14d38SCarson Labrado             // We received a response that was not a json.
106146b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses
106246b30283SCarson Labrado             // and if the resource collection does not already exist on the
106346b30283SCarson Labrado             // aggregating BMC
106446b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10654c30e226SCarson Labrado             {
10664c30e226SCarson Labrado                 messages::operationFailed(asyncResp->res);
10674c30e226SCarson Labrado             }
10684c30e226SCarson Labrado         }
10694c30e226SCarson Labrado     } // End processCollectionResponse()
10704c30e226SCarson Labrado 
107146b30283SCarson Labrado     // Processes the response returned by a satellite BMC and merges any
107246b30283SCarson Labrado     // properties whose "@odata.id" value is the URI or either a top level
107346b30283SCarson Labrado     // collection or is uptree from a top level collection
107446b30283SCarson Labrado     static void processContainsSubordinateResponse(
107546b30283SCarson Labrado         const std::string& prefix,
107646b30283SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
107746b30283SCarson Labrado         crow::Response& resp)
107846b30283SCarson Labrado     {
107946b30283SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
108046b30283SCarson Labrado         // overwrite the response headers in that case
108146b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
108246b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
108346b30283SCarson Labrado         {
108446b30283SCarson Labrado             return;
108546b30283SCarson Labrado         }
108646b30283SCarson Labrado 
108746b30283SCarson Labrado         if (resp.resultInt() != 200)
108846b30283SCarson Labrado         {
108962598e31SEd Tanous             BMCWEB_LOG_DEBUG(
109062598e31SEd Tanous                 "Resource uptree from Collection does not exist in satellite BMC \"{}\"",
109162598e31SEd Tanous                 prefix);
109246b30283SCarson Labrado             // Return the error if we haven't had any successes
109346b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
109446b30283SCarson Labrado             {
109546b30283SCarson Labrado                 asyncResp->res.result(resp.result());
109627b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
109746b30283SCarson Labrado             }
109846b30283SCarson Labrado             return;
109946b30283SCarson Labrado         }
110046b30283SCarson Labrado 
110146b30283SCarson Labrado         // The resp will not have a json component
110246b30283SCarson Labrado         // We need to create a json from resp's stringResponse
110318f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
110446b30283SCarson Labrado         {
110546b30283SCarson Labrado             bool addedLinks = false;
1106bd79bce8SPatrick Williams             nlohmann::json jsonVal =
1107bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
110846b30283SCarson Labrado             if (jsonVal.is_discarded())
110946b30283SCarson Labrado             {
111062598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
111146b30283SCarson Labrado 
111246b30283SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
111346b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
111446b30283SCarson Labrado                 {
111546b30283SCarson Labrado                     messages::operationFailed(asyncResp->res);
111646b30283SCarson Labrado                 }
111746b30283SCarson Labrado                 return;
111846b30283SCarson Labrado             }
111946b30283SCarson Labrado 
112062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
112146b30283SCarson Labrado 
112246b30283SCarson Labrado             // Parse response and add properties missing from the AsyncResp
112346b30283SCarson Labrado             // Valid properties will be of the form <property>.@odata.id and
112446b30283SCarson Labrado             // @odata.id is a <URI>.  In other words, the json should contain
112546b30283SCarson Labrado             // multiple properties such that
112646b30283SCarson Labrado             // {"<property>":{"@odata.id": "<URI>"}}
112746b30283SCarson Labrado             nlohmann::json::object_t* object =
112846b30283SCarson Labrado                 jsonVal.get_ptr<nlohmann::json::object_t*>();
112946b30283SCarson Labrado             if (object == nullptr)
113046b30283SCarson Labrado             {
113162598e31SEd Tanous                 BMCWEB_LOG_ERROR("Parsed JSON was not an object?");
113246b30283SCarson Labrado                 return;
113346b30283SCarson Labrado             }
113446b30283SCarson Labrado 
113546b30283SCarson Labrado             for (std::pair<const std::string, nlohmann::json>& prop : *object)
113646b30283SCarson Labrado             {
113746b30283SCarson Labrado                 if (!prop.second.contains("@odata.id"))
113846b30283SCarson Labrado                 {
113946b30283SCarson Labrado                     continue;
114046b30283SCarson Labrado                 }
114146b30283SCarson Labrado 
114246b30283SCarson Labrado                 std::string* strValue =
114346b30283SCarson Labrado                     prop.second["@odata.id"].get_ptr<std::string*>();
114446b30283SCarson Labrado                 if (strValue == nullptr)
114546b30283SCarson Labrado                 {
114662598e31SEd Tanous                     BMCWEB_LOG_CRITICAL("Field wasn't a string????");
114746b30283SCarson Labrado                     continue;
114846b30283SCarson Labrado                 }
114946b30283SCarson Labrado                 if (!searchCollectionsArray(*strValue, SearchType::CollOrCon))
115046b30283SCarson Labrado                 {
115146b30283SCarson Labrado                     continue;
115246b30283SCarson Labrado                 }
115346b30283SCarson Labrado 
115446b30283SCarson Labrado                 addedLinks = true;
115546b30283SCarson Labrado                 if (!asyncResp->res.jsonValue.contains(prop.first))
115646b30283SCarson Labrado                 {
115746b30283SCarson Labrado                     // Only add the property if it did not already exist
115862598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Adding link for {} from BMC {}",
115962598e31SEd Tanous                                      *strValue, prefix);
116046b30283SCarson Labrado                     asyncResp->res.jsonValue[prop.first]["@odata.id"] =
116146b30283SCarson Labrado                         *strValue;
116246b30283SCarson Labrado                     continue;
116346b30283SCarson Labrado                 }
116446b30283SCarson Labrado             }
116546b30283SCarson Labrado 
116646b30283SCarson Labrado             // If we added links to a previously unsuccessful (non-200) response
116746b30283SCarson Labrado             // then we need to make sure the response contains the bare minimum
116846b30283SCarson Labrado             // amount of additional information that we'd expect to have been
116946b30283SCarson Labrado             // populated.
117046b30283SCarson Labrado             if (addedLinks && (asyncResp->res.resultInt() != 200))
117146b30283SCarson Labrado             {
117246b30283SCarson Labrado                 // This resource didn't locally exist or an error
117346b30283SCarson Labrado                 // occurred while generating the response.  Remove any
117446b30283SCarson Labrado                 // error messages and update the error code.
117546b30283SCarson Labrado                 asyncResp->res.jsonValue.erase(
117646b30283SCarson Labrado                     asyncResp->res.jsonValue.find("error"));
117746b30283SCarson Labrado                 asyncResp->res.result(resp.result());
117846b30283SCarson Labrado 
117946b30283SCarson Labrado                 const auto& it1 = object->find("@odata.id");
118046b30283SCarson Labrado                 if (it1 != object->end())
118146b30283SCarson Labrado                 {
118246b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.id"] = (it1->second);
118346b30283SCarson Labrado                 }
118446b30283SCarson Labrado                 const auto& it2 = object->find("@odata.type");
118546b30283SCarson Labrado                 if (it2 != object->end())
118646b30283SCarson Labrado                 {
118746b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.type"] = (it2->second);
118846b30283SCarson Labrado                 }
118946b30283SCarson Labrado                 const auto& it3 = object->find("Id");
119046b30283SCarson Labrado                 if (it3 != object->end())
119146b30283SCarson Labrado                 {
119246b30283SCarson Labrado                     asyncResp->res.jsonValue["Id"] = (it3->second);
119346b30283SCarson Labrado                 }
119446b30283SCarson Labrado                 const auto& it4 = object->find("Name");
119546b30283SCarson Labrado                 if (it4 != object->end())
119646b30283SCarson Labrado                 {
119746b30283SCarson Labrado                     asyncResp->res.jsonValue["Name"] = (it4->second);
119846b30283SCarson Labrado                 }
119946b30283SCarson Labrado             }
120046b30283SCarson Labrado         }
120146b30283SCarson Labrado         else
120246b30283SCarson Labrado         {
120362598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
120462598e31SEd Tanous                              prefix);
120546b30283SCarson Labrado             // We received as response that was not a json
120646b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses,
120746b30283SCarson Labrado             // and if the resource does not already exist on the aggregating BMC
120846b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
120946b30283SCarson Labrado             {
121046b30283SCarson Labrado                 messages::operationFailed(asyncResp->res);
121146b30283SCarson Labrado             }
121246b30283SCarson Labrado         }
121346b30283SCarson Labrado     }
121446b30283SCarson Labrado 
121505916cefSCarson Labrado     // Entry point to Redfish Aggregation
121605916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
121705916cefSCarson Labrado     // request
121805916cefSCarson Labrado     static Result
121905916cefSCarson Labrado         beginAggregation(const crow::Request& thisReq,
122005916cefSCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
122105916cefSCarson Labrado     {
122205916cefSCarson Labrado         using crow::utility::OrMorePaths;
122305916cefSCarson Labrado         using crow::utility::readUrlSegments;
12244a7fbefdSEd Tanous         boost::urls::url_view url = thisReq.url();
1225411e6a11SCarson Labrado 
1226411e6a11SCarson Labrado         // We don't need to aggregate JsonSchemas due to potential issues such
1227411e6a11SCarson Labrado         // as version mismatches between aggregator and satellite BMCs.  For
1228411e6a11SCarson Labrado         // now assume that the aggregator has all the schemas and versions that
1229411e6a11SCarson Labrado         // the aggregated server has.
1230411e6a11SCarson Labrado         if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas",
1231411e6a11SCarson Labrado                                            crow::utility::OrMorePaths()))
1232411e6a11SCarson Labrado         {
1233411e6a11SCarson Labrado             return Result::LocalHandle;
1234411e6a11SCarson Labrado         }
1235411e6a11SCarson Labrado 
12367c4c52cbSCarson Labrado         // The first two segments should be "/redfish/v1".  We need to check
12377c4c52cbSCarson Labrado         // that before we can search topCollections
12387c4c52cbSCarson Labrado         if (!crow::utility::readUrlSegments(url, "redfish", "v1",
12397c4c52cbSCarson Labrado                                             crow::utility::OrMorePaths()))
124046a81465SCarson Labrado         {
124146a81465SCarson Labrado             return Result::LocalHandle;
124246a81465SCarson Labrado         }
124305916cefSCarson Labrado 
12447c4c52cbSCarson Labrado         // Parse the URI to see if it begins with a known top level collection
12457c4c52cbSCarson Labrado         // such as:
12467c4c52cbSCarson Labrado         // /redfish/v1/Chassis
12477c4c52cbSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory
12487c4c52cbSCarson Labrado         const boost::urls::segments_view urlSegments = url.segments();
12497c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
12504a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
12514a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
125205916cefSCarson Labrado 
12537c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
12547c4c52cbSCarson Labrado         it++;
12557c4c52cbSCarson Labrado         it++;
12567c4c52cbSCarson Labrado         for (; it != end; it++)
125705916cefSCarson Labrado         {
1258d4413c5bSGeorge Liu             const std::string& collectionItem = *it;
12597c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
12607c4c52cbSCarson Labrado                                    currentUrl.buffer()))
12617c4c52cbSCarson Labrado             {
12627c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
12637c4c52cbSCarson Labrado                 // might contain an aggregation prefix
12648b2521a5SCarson Labrado                 // TODO: This needs to be rethought when we can support multiple
12658b2521a5SCarson Labrado                 // satellites due to
12668b2521a5SCarson Labrado                 // /redfish/v1/AggregationService/AggregationSources/5B247A
12678b2521a5SCarson Labrado                 // being a local resource describing the satellite
12688b2521a5SCarson Labrado                 if (collectionItem.starts_with("5B247A_"))
126905916cefSCarson Labrado                 {
127062598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Need to forward a request");
127105916cefSCarson Labrado 
127246a81465SCarson Labrado                     // Extract the prefix from the request's URI, retrieve the
12737c4c52cbSCarson Labrado                     // associated satellite config information, and then forward
12747c4c52cbSCarson Labrado                     // the request to that satellite.
12757c4c52cbSCarson Labrado                     startAggregation(AggregationType::Resource, thisReq,
12767c4c52cbSCarson Labrado                                      asyncResp);
127705916cefSCarson Labrado                     return Result::NoLocalHandle;
127805916cefSCarson Labrado                 }
12797c4c52cbSCarson Labrado 
12807c4c52cbSCarson Labrado                 // Handle collection URI with a trailing backslash
12817c4c52cbSCarson Labrado                 // e.g. /redfish/v1/Chassis/
12827c4c52cbSCarson Labrado                 it++;
12837c4c52cbSCarson Labrado                 if ((it == end) && collectionItem.empty())
12847c4c52cbSCarson Labrado                 {
12857c4c52cbSCarson Labrado                     startAggregation(AggregationType::Collection, thisReq,
12867c4c52cbSCarson Labrado                                      asyncResp);
12877c4c52cbSCarson Labrado                 }
12887c4c52cbSCarson Labrado 
12897c4c52cbSCarson Labrado                 // We didn't recognize the prefix or it's a collection with a
12907c4c52cbSCarson Labrado                 // trailing "/".  In both cases we still want to locally handle
12917c4c52cbSCarson Labrado                 // the request
12927c4c52cbSCarson Labrado                 return Result::LocalHandle;
12937c4c52cbSCarson Labrado             }
12947c4c52cbSCarson Labrado 
12957c4c52cbSCarson Labrado             currentUrl.segments().push_back(collectionItem);
12967c4c52cbSCarson Labrado         }
12977c4c52cbSCarson Labrado 
12987c4c52cbSCarson Labrado         // If we made it here then currentUrl could contain a top level
12997c4c52cbSCarson Labrado         // collection URI without a trailing "/", e.g. /redfish/v1/Chassis
13007c4c52cbSCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
13017c4c52cbSCarson Labrado                                currentUrl.buffer()))
13027c4c52cbSCarson Labrado         {
13037c4c52cbSCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
130405916cefSCarson Labrado             return Result::LocalHandle;
130505916cefSCarson Labrado         }
130605916cefSCarson Labrado 
1307e002dbc0SCarson Labrado         // If nothing else then the request could be for a resource which has a
1308e002dbc0SCarson Labrado         // top level collection as a subordinate
1309e002dbc0SCarson Labrado         if (searchCollectionsArray(url.path(), SearchType::ContainsSubordinate))
1310e002dbc0SCarson Labrado         {
1311e002dbc0SCarson Labrado             startAggregation(AggregationType::ContainsSubordinate, thisReq,
1312e002dbc0SCarson Labrado                              asyncResp);
1313e002dbc0SCarson Labrado             return Result::LocalHandle;
1314e002dbc0SCarson Labrado         }
1315e002dbc0SCarson Labrado 
131662598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation not required for {}", url.buffer());
131705916cefSCarson Labrado         return Result::LocalHandle;
131805916cefSCarson Labrado     }
13197fb33566SCarson Labrado };
13207fb33566SCarson Labrado 
13217fb33566SCarson Labrado } // namespace redfish
1322