xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision d78572018fc2022091ff8b8eb5a7fef2172ba3d6)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
37fb33566SCarson Labrado #pragma once
47fb33566SCarson Labrado 
53ccb3adbSEd Tanous #include "aggregation_utils.hpp"
6*d7857201SEd Tanous #include "async_resp.hpp"
73ccb3adbSEd Tanous #include "dbus_utility.hpp"
83ccb3adbSEd Tanous #include "error_messages.hpp"
93ccb3adbSEd Tanous #include "http_client.hpp"
10*d7857201SEd Tanous #include "http_request.hpp"
11*d7857201SEd Tanous #include "http_response.hpp"
12*d7857201SEd Tanous #include "logging.hpp"
1318f8f608SEd Tanous #include "parsing.hpp"
14*d7857201SEd Tanous #include "ssl_key_handler.hpp"
15*d7857201SEd Tanous #include "utility.hpp"
167fb33566SCarson Labrado 
17*d7857201SEd Tanous #include <boost/asio/io_context.hpp>
18*d7857201SEd Tanous #include <boost/beast/http/field.hpp>
19*d7857201SEd Tanous #include <boost/beast/http/status.hpp>
20*d7857201SEd Tanous #include <boost/beast/http/verb.hpp>
21*d7857201SEd Tanous #include <boost/system/errc.hpp>
22*d7857201SEd Tanous #include <boost/system/result.hpp>
23*d7857201SEd Tanous #include <boost/url/param.hpp>
24*d7857201SEd Tanous #include <boost/url/parse.hpp>
25*d7857201SEd Tanous #include <boost/url/segments_ref.hpp>
26*d7857201SEd Tanous #include <boost/url/segments_view.hpp>
27*d7857201SEd Tanous #include <boost/url/url.hpp>
28*d7857201SEd Tanous #include <boost/url/url_view.hpp>
29*d7857201SEd Tanous #include <nlohmann/json.hpp>
30*d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp>
31*d7857201SEd Tanous 
32*d7857201SEd Tanous #include <algorithm>
337e8890c5SCarson Labrado #include <array>
34*d7857201SEd Tanous #include <chrono>
35*d7857201SEd Tanous #include <cstddef>
36*d7857201SEd Tanous #include <cstdint>
37*d7857201SEd Tanous #include <functional>
38*d7857201SEd Tanous #include <limits>
39*d7857201SEd Tanous #include <memory>
403544d2a7SEd Tanous #include <ranges>
41*d7857201SEd Tanous #include <string>
423544d2a7SEd Tanous #include <string_view>
43*d7857201SEd Tanous #include <system_error>
44*d7857201SEd Tanous #include <unordered_map>
45*d7857201SEd Tanous #include <utility>
46*d7857201SEd Tanous #include <variant>
477e8890c5SCarson Labrado 
487fb33566SCarson Labrado namespace redfish
497fb33566SCarson Labrado {
507fb33566SCarson Labrado 
51d14a48ffSCarson Labrado constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB
52d14a48ffSCarson Labrado 
5305916cefSCarson Labrado enum class Result
5405916cefSCarson Labrado {
5505916cefSCarson Labrado     LocalHandle,
5605916cefSCarson Labrado     NoLocalHandle
5705916cefSCarson Labrado };
5805916cefSCarson Labrado 
598fd333d6SCarson Labrado enum class SearchType
608fd333d6SCarson Labrado {
618fd333d6SCarson Labrado     Collection,
628fd333d6SCarson Labrado     CollOrCon,
638fd333d6SCarson Labrado     ContainsSubordinate,
648fd333d6SCarson Labrado     Resource
658fd333d6SCarson Labrado };
668fd333d6SCarson Labrado 
677e8890c5SCarson Labrado // clang-format off
687e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource
697e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a
707e8890c5SCarson Labrado // case-insensitive form of "uri".  That version of the schema is associated
717e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification.  Going forward, new URI
727e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as
737e8890c5SCarson Labrado // the spec is updated.  NOTE: These have been pre-sorted in order to be
747e8890c5SCarson Labrado // compatible with binary search
757e8890c5SCarson Labrado constexpr std::array nonUriProperties{
767e8890c5SCarson Labrado     "@Redfish.ActionInfo",
777e8890c5SCarson Labrado     // "@odata.context", // We can't fix /redfish/v1/$metadata URIs
787e8890c5SCarson Labrado     "@odata.id",
797e8890c5SCarson Labrado     // "Destination", // Only used by EventService and won't be a Redfish URI
807e8890c5SCarson Labrado     // "HostName", // Isn't actually a Redfish URI
817e8890c5SCarson Labrado     "Image",
827e8890c5SCarson Labrado     "MetricProperty",
8332d7d8ebSCarson Labrado     // "OriginOfCondition", // Is URI when in request, but is object in response
847e8890c5SCarson Labrado     "TaskMonitor",
857e8890c5SCarson Labrado     "target", // normal string, but target URI for POST to invoke an action
867e8890c5SCarson Labrado };
877e8890c5SCarson Labrado // clang-format on
887e8890c5SCarson Labrado 
898fd333d6SCarson Labrado // Search the top collection array to determine if the passed URI is of a
908fd333d6SCarson Labrado // desired type
918fd333d6SCarson Labrado inline bool searchCollectionsArray(std::string_view uri,
928fd333d6SCarson Labrado                                    const SearchType searchType)
938fd333d6SCarson Labrado {
941a095906SJoonwon Kang     boost::system::result<boost::urls::url> parsedUrl =
951a095906SJoonwon Kang         boost::urls::parse_relative_ref(uri);
968fd333d6SCarson Labrado     if (!parsedUrl)
978fd333d6SCarson Labrado     {
981a095906SJoonwon Kang         BMCWEB_LOG_ERROR("Failed to get target URI from {}", uri);
998fd333d6SCarson Labrado         return false;
1008fd333d6SCarson Labrado     }
1018fd333d6SCarson Labrado 
1021a095906SJoonwon Kang     parsedUrl->normalize();
1031a095906SJoonwon Kang     boost::urls::segments_ref segments = parsedUrl->segments();
1041a095906SJoonwon Kang     if (!segments.is_absolute())
1058fd333d6SCarson Labrado     {
1068fd333d6SCarson Labrado         return false;
1078fd333d6SCarson Labrado     }
1088fd333d6SCarson Labrado 
1091a095906SJoonwon Kang     // The passed URI must begin with "/redfish/v1", but we have to strip it
1101a095906SJoonwon Kang     // from the URI since topCollections does not include it in its URIs.
1111a095906SJoonwon Kang     if (segments.size() < 2)
1121a095906SJoonwon Kang     {
1131a095906SJoonwon Kang         return false;
1141a095906SJoonwon Kang     }
1151a095906SJoonwon Kang     if (segments.front() != "redfish")
1161a095906SJoonwon Kang     {
1171a095906SJoonwon Kang         return false;
1181a095906SJoonwon Kang     }
1191a095906SJoonwon Kang     segments.erase(segments.begin());
1201a095906SJoonwon Kang     if (segments.front() != "v1")
1211a095906SJoonwon Kang     {
1221a095906SJoonwon Kang         return false;
1231a095906SJoonwon Kang     }
1241a095906SJoonwon Kang     segments.erase(segments.begin());
1251a095906SJoonwon Kang 
1261a095906SJoonwon Kang     // Exclude the trailing "/" if it exists such as in "/redfish/v1/".
1271a095906SJoonwon Kang     if (!segments.empty() && segments.back().empty())
1281a095906SJoonwon Kang     {
1291a095906SJoonwon Kang         segments.pop_back();
1301a095906SJoonwon Kang     }
1311a095906SJoonwon Kang 
1321a095906SJoonwon Kang     // If no segments then the passed URI was either "/redfish/v1" or
1338fd333d6SCarson Labrado     // "/redfish/v1/".
1341a095906SJoonwon Kang     if (segments.empty())
1358fd333d6SCarson Labrado     {
1368fd333d6SCarson Labrado         return (searchType == SearchType::ContainsSubordinate) ||
1378fd333d6SCarson Labrado                (searchType == SearchType::CollOrCon);
1388fd333d6SCarson Labrado     }
1391a095906SJoonwon Kang     std::string_view url = segments.buffer();
1403544d2a7SEd Tanous     const auto* it = std::ranges::lower_bound(topCollections, url);
1418fd333d6SCarson Labrado     if (it == topCollections.end())
1428fd333d6SCarson Labrado     {
1438fd333d6SCarson Labrado         // parsedUrl is alphabetically after the last entry in the array so it
1448fd333d6SCarson Labrado         // can't be a top collection or up tree from a top collection
1458fd333d6SCarson Labrado         return false;
1468fd333d6SCarson Labrado     }
1478fd333d6SCarson Labrado 
1488fd333d6SCarson Labrado     boost::urls::url collectionUrl(*it);
1498fd333d6SCarson Labrado     boost::urls::segments_view collectionSegments = collectionUrl.segments();
1508fd333d6SCarson Labrado     boost::urls::segments_view::iterator itCollection =
1518fd333d6SCarson Labrado         collectionSegments.begin();
1528fd333d6SCarson Labrado     const boost::urls::segments_view::const_iterator endCollection =
1538fd333d6SCarson Labrado         collectionSegments.end();
1548fd333d6SCarson Labrado 
1558fd333d6SCarson Labrado     // Each segment in the passed URI should match the found collection
1561a095906SJoonwon Kang     for (const auto& segment : segments)
1578fd333d6SCarson Labrado     {
1588fd333d6SCarson Labrado         if (itCollection == endCollection)
1598fd333d6SCarson Labrado         {
1608fd333d6SCarson Labrado             // Leftover segments means the target is for an aggregation
1618fd333d6SCarson Labrado             // supported resource
1628fd333d6SCarson Labrado             return searchType == SearchType::Resource;
1638fd333d6SCarson Labrado         }
1648fd333d6SCarson Labrado 
1658fd333d6SCarson Labrado         if (segment != (*itCollection))
1668fd333d6SCarson Labrado         {
1678fd333d6SCarson Labrado             return false;
1688fd333d6SCarson Labrado         }
1698fd333d6SCarson Labrado         itCollection++;
1708fd333d6SCarson Labrado     }
1718fd333d6SCarson Labrado 
1728fd333d6SCarson Labrado     // No remaining segments means the passed URI was a top level collection
1738fd333d6SCarson Labrado     if (searchType == SearchType::Collection)
1748fd333d6SCarson Labrado     {
1758fd333d6SCarson Labrado         return itCollection == endCollection;
1768fd333d6SCarson Labrado     }
1778fd333d6SCarson Labrado     if (searchType == SearchType::ContainsSubordinate)
1788fd333d6SCarson Labrado     {
1798fd333d6SCarson Labrado         return itCollection != endCollection;
1808fd333d6SCarson Labrado     }
1818fd333d6SCarson Labrado 
1828fd333d6SCarson Labrado     // Return this check instead of "true" in case other SearchTypes get added
1838fd333d6SCarson Labrado     return searchType == SearchType::CollOrCon;
1848fd333d6SCarson Labrado }
1858fd333d6SCarson Labrado 
1867e8890c5SCarson Labrado // Determines if the passed property contains a URI.  Those property names
1877e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically
1887e8890c5SCarson Labrado // defined in the above array.
18926ccae32SEd Tanous inline bool isPropertyUri(std::string_view propertyName)
1907e8890c5SCarson Labrado {
19118f8f608SEd Tanous     if (propertyName.ends_with("uri") || propertyName.ends_with("Uri") ||
19218f8f608SEd Tanous         propertyName.ends_with("URI"))
19318f8f608SEd Tanous     {
19418f8f608SEd Tanous         return true;
19518f8f608SEd Tanous     }
19618f8f608SEd Tanous     return std::binary_search(nonUriProperties.begin(), nonUriProperties.end(),
1977e8890c5SCarson Labrado                               propertyName);
1987e8890c5SCarson Labrado }
1997e8890c5SCarson Labrado 
2004ff0f1f4SEd Tanous inline void addPrefixToStringItem(std::string& strValue,
2010af78d5aSKhang Kieu                                   std::string_view prefix)
2021c0bb5c6SCarson Labrado {
2031c0bb5c6SCarson Labrado     // Make sure the value is a properly formatted URI
2040af78d5aSKhang Kieu     auto parsed = boost::urls::parse_relative_ref(strValue);
2051c0bb5c6SCarson Labrado     if (!parsed)
2061c0bb5c6SCarson Labrado     {
207bf2ddedeSCarson Labrado         // Note that DMTF URIs such as
208bf2ddedeSCarson Labrado         // https://redfish.dmtf.org/registries/Base.1.15.0.json will fail this
209bf2ddedeSCarson Labrado         // check and that's okay
210bf2ddedeSCarson Labrado         BMCWEB_LOG_DEBUG("Couldn't parse URI from resource {}", strValue);
2111c0bb5c6SCarson Labrado         return;
2121c0bb5c6SCarson Labrado     }
2131c0bb5c6SCarson Labrado 
214daadfb2eSEd Tanous     const boost::urls::url_view& thisUrl = *parsed;
2151c0bb5c6SCarson Labrado 
216411e6a11SCarson Labrado     // We don't need to aggregate JsonSchemas due to potential issues such as
217411e6a11SCarson Labrado     // version mismatches between aggregator and satellite BMCs.  For now
218411e6a11SCarson Labrado     // assume that the aggregator has all the schemas and versions that the
219411e6a11SCarson Labrado     // aggregated server has.
220411e6a11SCarson Labrado     if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas",
221411e6a11SCarson Labrado                                        crow::utility::OrMorePaths()))
222411e6a11SCarson Labrado     {
22362598e31SEd Tanous         BMCWEB_LOG_DEBUG("Skipping JsonSchemas URI prefix fixing");
224411e6a11SCarson Labrado         return;
225411e6a11SCarson Labrado     }
226411e6a11SCarson Labrado 
22711987af6SCarson Labrado     // The first two segments should be "/redfish/v1".  We need to check that
22811987af6SCarson Labrado     // before we can search topCollections
22911987af6SCarson Labrado     if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
23011987af6SCarson Labrado                                         crow::utility::OrMorePaths()))
2311c0bb5c6SCarson Labrado     {
2321c0bb5c6SCarson Labrado         return;
2331c0bb5c6SCarson Labrado     }
2341c0bb5c6SCarson Labrado 
23511987af6SCarson Labrado     // Check array adding a segment each time until collection is identified
23611987af6SCarson Labrado     // Add prefix to segment after the collection
23711987af6SCarson Labrado     const boost::urls::segments_view urlSegments = thisUrl.segments();
23811987af6SCarson Labrado     bool addedPrefix = false;
23911987af6SCarson Labrado     boost::urls::url url("/");
2404a7fbefdSEd Tanous     boost::urls::segments_view::const_iterator it = urlSegments.begin();
24111987af6SCarson Labrado     const boost::urls::segments_view::const_iterator end = urlSegments.end();
24211987af6SCarson Labrado 
24311987af6SCarson Labrado     // Skip past the leading "/redfish/v1"
24411987af6SCarson Labrado     it++;
24511987af6SCarson Labrado     it++;
24611987af6SCarson Labrado     for (; it != end; it++)
2471c0bb5c6SCarson Labrado     {
24811987af6SCarson Labrado         // Trailing "/" will result in an empty segment.  In that case we need
24911987af6SCarson Labrado         // to return so we don't apply a prefix to top level collections such
25011987af6SCarson Labrado         // as "/redfish/v1/Chassis/"
25111987af6SCarson Labrado         if ((*it).empty())
25211987af6SCarson Labrado         {
253411e6a11SCarson Labrado             return;
2541c0bb5c6SCarson Labrado         }
2551c0bb5c6SCarson Labrado 
25611987af6SCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
25711987af6SCarson Labrado                                url.buffer()))
2581c0bb5c6SCarson Labrado         {
25911987af6SCarson Labrado             std::string collectionItem(prefix);
26011987af6SCarson Labrado             collectionItem += "_" + (*it);
26111987af6SCarson Labrado             url.segments().push_back(collectionItem);
26211987af6SCarson Labrado             it++;
26311987af6SCarson Labrado             addedPrefix = true;
26411987af6SCarson Labrado             break;
26511987af6SCarson Labrado         }
26611987af6SCarson Labrado 
26711987af6SCarson Labrado         url.segments().push_back(*it);
26811987af6SCarson Labrado     }
26911987af6SCarson Labrado 
27011987af6SCarson Labrado     // Finish constructing the URL here (if needed) to avoid additional checks
27111987af6SCarson Labrado     for (; it != end; it++)
27211987af6SCarson Labrado     {
27311987af6SCarson Labrado         url.segments().push_back(*it);
27411987af6SCarson Labrado     }
27511987af6SCarson Labrado 
27611987af6SCarson Labrado     if (addedPrefix)
27711987af6SCarson Labrado     {
27811987af6SCarson Labrado         url.segments().insert(url.segments().begin(), {"redfish", "v1"});
2790af78d5aSKhang Kieu         strValue = url.buffer();
2801c0bb5c6SCarson Labrado     }
2811c0bb5c6SCarson Labrado }
2821c0bb5c6SCarson Labrado 
2834ff0f1f4SEd Tanous inline void addPrefixToItem(nlohmann::json& item, std::string_view prefix)
2840af78d5aSKhang Kieu {
2850af78d5aSKhang Kieu     std::string* strValue = item.get_ptr<std::string*>();
2860af78d5aSKhang Kieu     if (strValue == nullptr)
2870af78d5aSKhang Kieu     {
288bf2ddedeSCarson Labrado         // Values for properties like "InvalidURI" and "ResourceMissingAtURI"
289bf2ddedeSCarson Labrado         // from within the Base Registry are objects instead of strings and will
290bf2ddedeSCarson Labrado         // fall into this check
291bf2ddedeSCarson Labrado         BMCWEB_LOG_DEBUG("Field was not a string");
2920af78d5aSKhang Kieu         return;
2930af78d5aSKhang Kieu     }
2940af78d5aSKhang Kieu     addPrefixToStringItem(*strValue, prefix);
2950af78d5aSKhang Kieu     item = *strValue;
2960af78d5aSKhang Kieu }
2970af78d5aSKhang Kieu 
2984ff0f1f4SEd Tanous inline void addAggregatedHeaders(crow::Response& asyncResp,
29924dadc88SCarson Labrado                                  const crow::Response& resp,
3000af78d5aSKhang Kieu                                  std::string_view prefix)
3010af78d5aSKhang Kieu {
3020af78d5aSKhang Kieu     if (!resp.getHeaderValue("Content-Type").empty())
3030af78d5aSKhang Kieu     {
3040af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::content_type,
3050af78d5aSKhang Kieu                             resp.getHeaderValue("Content-Type"));
3060af78d5aSKhang Kieu     }
3070af78d5aSKhang Kieu     if (!resp.getHeaderValue("Allow").empty())
3080af78d5aSKhang Kieu     {
3090af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::allow,
3100af78d5aSKhang Kieu                             resp.getHeaderValue("Allow"));
3110af78d5aSKhang Kieu     }
3120af78d5aSKhang Kieu     std::string_view header = resp.getHeaderValue("Location");
3130af78d5aSKhang Kieu     if (!header.empty())
3140af78d5aSKhang Kieu     {
3150af78d5aSKhang Kieu         std::string location(header);
3160af78d5aSKhang Kieu         addPrefixToStringItem(location, prefix);
3170af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::location, location);
3180af78d5aSKhang Kieu     }
3190af78d5aSKhang Kieu     if (!resp.getHeaderValue("Retry-After").empty())
3200af78d5aSKhang Kieu     {
3210af78d5aSKhang Kieu         asyncResp.addHeader(boost::beast::http::field::retry_after,
3220af78d5aSKhang Kieu                             resp.getHeaderValue("Retry-After"));
3230af78d5aSKhang Kieu     }
3240af78d5aSKhang Kieu     // TODO: we need special handling for Link Header Value
3250af78d5aSKhang Kieu }
3260af78d5aSKhang Kieu 
327b27e1cbeSCarson Labrado // Fix HTTP headers which appear in responses from Task resources among others
3284ff0f1f4SEd Tanous inline void addPrefixToHeadersInResp(nlohmann::json& json,
3294ff0f1f4SEd Tanous                                      std::string_view prefix)
330b27e1cbeSCarson Labrado {
331b27e1cbeSCarson Labrado     // The passed in "HttpHeaders" should be an array of headers
332b27e1cbeSCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
333b27e1cbeSCarson Labrado     if (array == nullptr)
334b27e1cbeSCarson Labrado     {
33562598e31SEd Tanous         BMCWEB_LOG_ERROR("Field wasn't an array_t????");
336b27e1cbeSCarson Labrado         return;
337b27e1cbeSCarson Labrado     }
338b27e1cbeSCarson Labrado 
339b27e1cbeSCarson Labrado     for (nlohmann::json& item : *array)
340b27e1cbeSCarson Labrado     {
341b27e1cbeSCarson Labrado         // Each header is a single string with the form "<Field>: <Value>"
342b27e1cbeSCarson Labrado         std::string* strHeader = item.get_ptr<std::string*>();
343b27e1cbeSCarson Labrado         if (strHeader == nullptr)
344b27e1cbeSCarson Labrado         {
34562598e31SEd Tanous             BMCWEB_LOG_CRITICAL("Field wasn't a string????");
346b27e1cbeSCarson Labrado             continue;
347b27e1cbeSCarson Labrado         }
348b27e1cbeSCarson Labrado 
349b27e1cbeSCarson Labrado         constexpr std::string_view location = "Location: ";
350b27e1cbeSCarson Labrado         if (strHeader->starts_with(location))
351b27e1cbeSCarson Labrado         {
352b27e1cbeSCarson Labrado             std::string header = strHeader->substr(location.size());
353b27e1cbeSCarson Labrado             addPrefixToStringItem(header, prefix);
354b27e1cbeSCarson Labrado             *strHeader = std::string(location) + header;
355b27e1cbeSCarson Labrado         }
356b27e1cbeSCarson Labrado     }
357b27e1cbeSCarson Labrado }
358b27e1cbeSCarson Labrado 
3591c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for
3607e8890c5SCarson Labrado // an aggregated resource.
3614ff0f1f4SEd Tanous inline void addPrefixes(nlohmann::json& json, std::string_view prefix)
3621c0bb5c6SCarson Labrado {
3631c0bb5c6SCarson Labrado     nlohmann::json::object_t* object =
3641c0bb5c6SCarson Labrado         json.get_ptr<nlohmann::json::object_t*>();
3651c0bb5c6SCarson Labrado     if (object != nullptr)
3661c0bb5c6SCarson Labrado     {
3671c0bb5c6SCarson Labrado         for (std::pair<const std::string, nlohmann::json>& item : *object)
3681c0bb5c6SCarson Labrado         {
3697e8890c5SCarson Labrado             if (isPropertyUri(item.first))
3701c0bb5c6SCarson Labrado             {
3717e8890c5SCarson Labrado                 addPrefixToItem(item.second, prefix);
3721c0bb5c6SCarson Labrado                 continue;
3731c0bb5c6SCarson Labrado             }
3741c0bb5c6SCarson Labrado 
375b27e1cbeSCarson Labrado             // "HttpHeaders" contains HTTP headers.  Among those we need to
376b27e1cbeSCarson Labrado             // attempt to fix the "Location" header
377b27e1cbeSCarson Labrado             if (item.first == "HttpHeaders")
378b27e1cbeSCarson Labrado             {
379b27e1cbeSCarson Labrado                 addPrefixToHeadersInResp(item.second, prefix);
380b27e1cbeSCarson Labrado                 continue;
381b27e1cbeSCarson Labrado             }
382b27e1cbeSCarson Labrado 
3838ece0e45SEd Tanous             // Recursively parse the rest of the json
3841c0bb5c6SCarson Labrado             addPrefixes(item.second, prefix);
3851c0bb5c6SCarson Labrado         }
3861c0bb5c6SCarson Labrado         return;
3871c0bb5c6SCarson Labrado     }
3881c0bb5c6SCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
3891c0bb5c6SCarson Labrado     if (array != nullptr)
3901c0bb5c6SCarson Labrado     {
3911c0bb5c6SCarson Labrado         for (nlohmann::json& item : *array)
3921c0bb5c6SCarson Labrado         {
3931c0bb5c6SCarson Labrado             addPrefixes(item, prefix);
3941c0bb5c6SCarson Labrado         }
3951c0bb5c6SCarson Labrado     }
3961c0bb5c6SCarson Labrado }
3971c0bb5c6SCarson Labrado 
398d14a48ffSCarson Labrado inline boost::system::error_code aggregationRetryHandler(unsigned int respCode)
399a7a80296SCarson Labrado {
40032d7d8ebSCarson Labrado     // Allow all response codes because we want to surface any satellite
40132d7d8ebSCarson Labrado     // issue to the client
40262598e31SEd Tanous     BMCWEB_LOG_DEBUG("Received {} response from satellite", respCode);
403d14a48ffSCarson Labrado     return boost::system::errc::make_error_code(boost::system::errc::success);
404d14a48ffSCarson Labrado }
405d14a48ffSCarson Labrado 
406d14a48ffSCarson Labrado inline crow::ConnectionPolicy getAggregationPolicy()
407d14a48ffSCarson Labrado {
4086bd30813SEd Tanous     return {.maxRetryAttempts = 0,
409d14a48ffSCarson Labrado             .requestByteLimit = aggregatorReadBodyLimit,
410d14a48ffSCarson Labrado             .maxConnections = 20,
411d14a48ffSCarson Labrado             .retryPolicyAction = "TerminateAfterRetries",
412d14a48ffSCarson Labrado             .retryIntervalSecs = std::chrono::seconds(0),
413d14a48ffSCarson Labrado             .invalidResp = aggregationRetryHandler};
414d14a48ffSCarson Labrado }
415d14a48ffSCarson Labrado 
416d14a48ffSCarson Labrado class RedfishAggregator
417d14a48ffSCarson Labrado {
418d14a48ffSCarson Labrado   private:
419d14a48ffSCarson Labrado     crow::HttpClient client;
420d14a48ffSCarson Labrado 
4217fb33566SCarson Labrado     // Dummy callback used by the Constructor so that it can report the number
4227fb33566SCarson Labrado     // of satellite configs when the class is first created
4237fb33566SCarson Labrado     static void constructorCallback(
4248b2521a5SCarson Labrado         const boost::system::error_code& ec,
4257fb33566SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4267fb33566SCarson Labrado     {
4277fb33566SCarson Labrado         if (ec)
4287fb33566SCarson Labrado         {
42962598e31SEd Tanous             BMCWEB_LOG_ERROR("Something went wrong while querying dbus!");
4307fb33566SCarson Labrado             return;
4317fb33566SCarson Labrado         }
4327fb33566SCarson Labrado 
43362598e31SEd Tanous         BMCWEB_LOG_DEBUG("There were {} satellite configs found at startup",
43462598e31SEd Tanous                          std::to_string(satelliteInfo.size()));
4357fb33566SCarson Labrado     }
4367fb33566SCarson Labrado 
4377fb33566SCarson Labrado     // Search D-Bus objects for satellite config objects and add their
4387fb33566SCarson Labrado     // information if valid
4397fb33566SCarson Labrado     static void findSatelliteConfigs(
4407fb33566SCarson Labrado         const dbus::utility::ManagedObjectType& objects,
4417fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4427fb33566SCarson Labrado     {
4437fb33566SCarson Labrado         for (const auto& objectPath : objects)
4447fb33566SCarson Labrado         {
4457fb33566SCarson Labrado             for (const auto& interface : objectPath.second)
4467fb33566SCarson Labrado             {
4477fb33566SCarson Labrado                 if (interface.first ==
4487fb33566SCarson Labrado                     "xyz.openbmc_project.Configuration.SatelliteController")
4497fb33566SCarson Labrado                 {
45062598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Found Satellite Controller at {}",
45162598e31SEd Tanous                                      objectPath.first.str);
4527fb33566SCarson Labrado 
45305916cefSCarson Labrado                     if (!satelliteInfo.empty())
45405916cefSCarson Labrado                     {
45562598e31SEd Tanous                         BMCWEB_LOG_ERROR(
45662598e31SEd Tanous                             "Redfish Aggregation only supports one satellite!");
45762598e31SEd Tanous                         BMCWEB_LOG_DEBUG("Clearing all satellite data");
45805916cefSCarson Labrado                         satelliteInfo.clear();
45905916cefSCarson Labrado                         return;
46005916cefSCarson Labrado                     }
46105916cefSCarson Labrado 
46205916cefSCarson Labrado                     // For now assume there will only be one satellite config.
46305916cefSCarson Labrado                     // Assign it the name/prefix "5B247A"
46405916cefSCarson Labrado                     addSatelliteConfig("5B247A", interface.second,
46505916cefSCarson Labrado                                        satelliteInfo);
4667fb33566SCarson Labrado                 }
4677fb33566SCarson Labrado             }
4687fb33566SCarson Labrado         }
4697fb33566SCarson Labrado     }
4707fb33566SCarson Labrado 
4717fb33566SCarson Labrado     // Parse the properties of a satellite config object and add the
4727fb33566SCarson Labrado     // configuration if the properties are valid
4737fb33566SCarson Labrado     static void addSatelliteConfig(
47405916cefSCarson Labrado         const std::string& name,
4757fb33566SCarson Labrado         const dbus::utility::DBusPropertiesMap& properties,
4767fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4777fb33566SCarson Labrado     {
4787fb33566SCarson Labrado         boost::urls::url url;
4797fb33566SCarson Labrado 
4807fb33566SCarson Labrado         for (const auto& prop : properties)
4817fb33566SCarson Labrado         {
48205916cefSCarson Labrado             if (prop.first == "Hostname")
4837fb33566SCarson Labrado             {
4847fb33566SCarson Labrado                 const std::string* propVal =
4857fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
4867fb33566SCarson Labrado                 if (propVal == nullptr)
4877fb33566SCarson Labrado                 {
48862598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid Hostname value");
4897fb33566SCarson Labrado                     return;
4907fb33566SCarson Labrado                 }
4917fb33566SCarson Labrado                 url.set_host(*propVal);
4927fb33566SCarson Labrado             }
4937fb33566SCarson Labrado 
4947fb33566SCarson Labrado             else if (prop.first == "Port")
4957fb33566SCarson Labrado             {
4967fb33566SCarson Labrado                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
4977fb33566SCarson Labrado                 if (propVal == nullptr)
4987fb33566SCarson Labrado                 {
49962598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid Port value");
5007fb33566SCarson Labrado                     return;
5017fb33566SCarson Labrado                 }
5027fb33566SCarson Labrado 
5037fb33566SCarson Labrado                 if (*propVal > std::numeric_limits<uint16_t>::max())
5047fb33566SCarson Labrado                 {
50562598e31SEd Tanous                     BMCWEB_LOG_ERROR("Port value out of range");
5067fb33566SCarson Labrado                     return;
5077fb33566SCarson Labrado                 }
508079360aeSEd Tanous                 url.set_port(std::to_string(static_cast<uint16_t>(*propVal)));
5097fb33566SCarson Labrado             }
5107fb33566SCarson Labrado 
5117fb33566SCarson Labrado             else if (prop.first == "AuthType")
5127fb33566SCarson Labrado             {
5137fb33566SCarson Labrado                 const std::string* propVal =
5147fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
5157fb33566SCarson Labrado                 if (propVal == nullptr)
5167fb33566SCarson Labrado                 {
51762598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid AuthType value");
5187fb33566SCarson Labrado                     return;
5197fb33566SCarson Labrado                 }
5207fb33566SCarson Labrado 
5217fb33566SCarson Labrado                 // For now assume authentication not required to communicate
5227fb33566SCarson Labrado                 // with the satellite BMC
5237fb33566SCarson Labrado                 if (*propVal != "None")
5247fb33566SCarson Labrado                 {
52562598e31SEd Tanous                     BMCWEB_LOG_ERROR(
52662598e31SEd Tanous                         "Unsupported AuthType value: {}, only \"none\" is supported",
52762598e31SEd Tanous                         *propVal);
5287fb33566SCarson Labrado                     return;
5297fb33566SCarson Labrado                 }
5307fb33566SCarson Labrado                 url.set_scheme("http");
5317fb33566SCarson Labrado             }
5327fb33566SCarson Labrado         } // Finished reading properties
5337fb33566SCarson Labrado 
5347fb33566SCarson Labrado         // Make sure all required config information was made available
5357fb33566SCarson Labrado         if (url.host().empty())
5367fb33566SCarson Labrado         {
53762598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing Host", name);
5387fb33566SCarson Labrado             return;
5397fb33566SCarson Labrado         }
5407fb33566SCarson Labrado 
5417fb33566SCarson Labrado         if (!url.has_port())
5427fb33566SCarson Labrado         {
54362598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing Port", name);
5447fb33566SCarson Labrado             return;
5457fb33566SCarson Labrado         }
5467fb33566SCarson Labrado 
5477fb33566SCarson Labrado         if (!url.has_scheme())
5487fb33566SCarson Labrado         {
54962598e31SEd Tanous             BMCWEB_LOG_ERROR("Satellite config {} missing AuthType", name);
5507fb33566SCarson Labrado             return;
5517fb33566SCarson Labrado         }
5527fb33566SCarson Labrado 
5537fb33566SCarson Labrado         std::string resultString;
5547fb33566SCarson Labrado         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
5557fb33566SCarson Labrado         if (result.second)
5567fb33566SCarson Labrado         {
5577fb33566SCarson Labrado             resultString = "Added new satellite config ";
5587fb33566SCarson Labrado         }
5597fb33566SCarson Labrado         else
5607fb33566SCarson Labrado         {
5617fb33566SCarson Labrado             resultString = "Updated existing satellite config ";
5627fb33566SCarson Labrado         }
5637fb33566SCarson Labrado 
56462598e31SEd Tanous         BMCWEB_LOG_DEBUG("{}{} at {}://{}", resultString, name,
56562598e31SEd Tanous                          result.first->second.scheme(),
56662598e31SEd Tanous                          result.first->second.encoded_host_and_port());
5677fb33566SCarson Labrado     }
5687fb33566SCarson Labrado 
56946a81465SCarson Labrado     enum AggregationType
57046a81465SCarson Labrado     {
57146a81465SCarson Labrado         Collection,
572e002dbc0SCarson Labrado         ContainsSubordinate,
57346a81465SCarson Labrado         Resource,
57446a81465SCarson Labrado     };
57546a81465SCarson Labrado 
57646a81465SCarson Labrado     static void
577e002dbc0SCarson Labrado         startAggregation(AggregationType aggType, const crow::Request& thisReq,
57846a81465SCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
57946a81465SCarson Labrado     {
580e002dbc0SCarson Labrado         if (thisReq.method() != boost::beast::http::verb::get)
581e002dbc0SCarson Labrado         {
582e002dbc0SCarson Labrado             if (aggType == AggregationType::Collection)
583db18fc98SCarson Labrado             {
58462598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
58562598e31SEd Tanous                     "Only aggregate GET requests to top level collections");
586db18fc98SCarson Labrado                 return;
587db18fc98SCarson Labrado             }
588db18fc98SCarson Labrado 
589e002dbc0SCarson Labrado             if (aggType == AggregationType::ContainsSubordinate)
590e002dbc0SCarson Labrado             {
59162598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
59262598e31SEd Tanous                     "Only aggregate GET requests when uptree of a top level collection");
593e002dbc0SCarson Labrado                 return;
594e002dbc0SCarson Labrado             }
595e002dbc0SCarson Labrado         }
596e002dbc0SCarson Labrado 
59746a81465SCarson Labrado         // Create a copy of thisReq so we we can still locally process the req
59846a81465SCarson Labrado         std::error_code ec;
59946a81465SCarson Labrado         auto localReq = std::make_shared<crow::Request>(thisReq.req, ec);
60046a81465SCarson Labrado         if (ec)
60146a81465SCarson Labrado         {
60262598e31SEd Tanous             BMCWEB_LOG_ERROR("Failed to create copy of request");
603e002dbc0SCarson Labrado             if (aggType == AggregationType::Resource)
60446a81465SCarson Labrado             {
60546a81465SCarson Labrado                 messages::internalError(asyncResp->res);
60646a81465SCarson Labrado             }
60746a81465SCarson Labrado             return;
60846a81465SCarson Labrado         }
60946a81465SCarson Labrado 
6106282bc71SEd Tanous         if (aggType == AggregationType::Collection)
6116282bc71SEd Tanous         {
6126282bc71SEd Tanous             boost::urls::url& urlNew = localReq->url();
6136282bc71SEd Tanous             auto paramsIt = urlNew.params().begin();
6146282bc71SEd Tanous             while (paramsIt != urlNew.params().end())
6156282bc71SEd Tanous             {
6166282bc71SEd Tanous                 const boost::urls::param& param = *paramsIt;
6176282bc71SEd Tanous                 // only and $skip, params can't be passed to satellite
6186282bc71SEd Tanous                 // as applying these filters twice results in different results.
6196282bc71SEd Tanous                 // Removing them will cause them to only be processed in the
6206282bc71SEd Tanous                 // aggregator. Note, this still doesn't work for collections
6216282bc71SEd Tanous                 // that might return less than the complete collection by
6226282bc71SEd Tanous                 // default, but hopefully those are rare/nonexistent in top
6236282bc71SEd Tanous                 // collections.  bmcweb doesn't implement any of these.
6246282bc71SEd Tanous                 if (param.key == "only" || param.key == "$skip")
6256282bc71SEd Tanous                 {
6266282bc71SEd Tanous                     BMCWEB_LOG_DEBUG(
6276282bc71SEd Tanous                         "Erasing \"{}\" param from request to top level collection",
6286282bc71SEd Tanous                         param.key);
6296282bc71SEd Tanous 
6306282bc71SEd Tanous                     paramsIt = urlNew.params().erase(paramsIt);
6316282bc71SEd Tanous                     continue;
6326282bc71SEd Tanous                 }
6336282bc71SEd Tanous                 // Pass all other parameters
6346282bc71SEd Tanous                 paramsIt++;
6356282bc71SEd Tanous             }
6366282bc71SEd Tanous             localReq->target(urlNew.buffer());
6376282bc71SEd Tanous         }
6386282bc71SEd Tanous 
639e002dbc0SCarson Labrado         getSatelliteConfigs(
640e002dbc0SCarson Labrado             std::bind_front(aggregateAndHandle, aggType, localReq, asyncResp));
64146a81465SCarson Labrado     }
64246a81465SCarson Labrado 
643db18fc98SCarson Labrado     static void findSatellite(
64446a81465SCarson Labrado         const crow::Request& req,
64546a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
64646a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo,
64746a81465SCarson Labrado         std::string_view memberName)
64846a81465SCarson Labrado     {
64946a81465SCarson Labrado         // Determine if the resource ID begins with a known prefix
65046a81465SCarson Labrado         for (const auto& satellite : satelliteInfo)
65146a81465SCarson Labrado         {
65246a81465SCarson Labrado             std::string targetPrefix = satellite.first;
65346a81465SCarson Labrado             targetPrefix += "_";
65446a81465SCarson Labrado             if (memberName.starts_with(targetPrefix))
65546a81465SCarson Labrado             {
65662598e31SEd Tanous                 BMCWEB_LOG_DEBUG("\"{}\" is a known prefix", satellite.first);
65746a81465SCarson Labrado 
65846a81465SCarson Labrado                 // Remove the known prefix from the request's URI and
65946a81465SCarson Labrado                 // then forward to the associated satellite BMC
66046a81465SCarson Labrado                 getInstance().forwardRequest(req, asyncResp, satellite.first,
66146a81465SCarson Labrado                                              satelliteInfo);
66246a81465SCarson Labrado                 return;
66346a81465SCarson Labrado             }
66446a81465SCarson Labrado         }
665db18fc98SCarson Labrado 
666db18fc98SCarson Labrado         // We didn't recognize the prefix and need to return a 404
66739662a3bSEd Tanous         std::string nameStr = req.url().segments().back();
668db18fc98SCarson Labrado         messages::resourceNotFound(asyncResp->res, "", nameStr);
66946a81465SCarson Labrado     }
67046a81465SCarson Labrado 
67146a81465SCarson Labrado     // Intended to handle an incoming request based on if Redfish Aggregation
67246a81465SCarson Labrado     // is enabled.  Forwards request to satellite BMC if it exists.
67346a81465SCarson Labrado     static void aggregateAndHandle(
674e002dbc0SCarson Labrado         AggregationType aggType,
67546a81465SCarson Labrado         const std::shared_ptr<crow::Request>& sharedReq,
67646a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6778b2521a5SCarson Labrado         const boost::system::error_code& ec,
67846a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
67946a81465SCarson Labrado     {
68046a81465SCarson Labrado         if (sharedReq == nullptr)
68146a81465SCarson Labrado         {
68246a81465SCarson Labrado             return;
68346a81465SCarson Labrado         }
6848b2521a5SCarson Labrado         // Something went wrong while querying dbus
6858b2521a5SCarson Labrado         if (ec)
6868b2521a5SCarson Labrado         {
6878b2521a5SCarson Labrado             messages::internalError(asyncResp->res);
6888b2521a5SCarson Labrado             return;
6898b2521a5SCarson Labrado         }
690db18fc98SCarson Labrado 
691db18fc98SCarson Labrado         // No satellite configs means we don't need to keep attempting to
692db18fc98SCarson Labrado         // aggregate
693db18fc98SCarson Labrado         if (satelliteInfo.empty())
694db18fc98SCarson Labrado         {
695e002dbc0SCarson Labrado             // For collections or resources that can contain a subordinate
696e002dbc0SCarson Labrado             // top level collection we'll also handle the request locally so we
697db18fc98SCarson Labrado             // don't need to write an error code
698e002dbc0SCarson Labrado             if (aggType == AggregationType::Resource)
699db18fc98SCarson Labrado             {
70039662a3bSEd Tanous                 std::string nameStr = sharedReq->url().segments().back();
701db18fc98SCarson Labrado                 messages::resourceNotFound(asyncResp->res, "", nameStr);
702db18fc98SCarson Labrado             }
703db18fc98SCarson Labrado             return;
704db18fc98SCarson Labrado         }
705db18fc98SCarson Labrado 
70646a81465SCarson Labrado         const crow::Request& thisReq = *sharedReq;
70762598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation is enabled, begin processing of {}",
70862598e31SEd Tanous                          thisReq.target());
70946a81465SCarson Labrado 
71046a81465SCarson Labrado         // We previously determined the request is for a collection.  No need to
71146a81465SCarson Labrado         // check again
712e002dbc0SCarson Labrado         if (aggType == AggregationType::Collection)
71346a81465SCarson Labrado         {
71462598e31SEd Tanous             BMCWEB_LOG_DEBUG("Aggregating a collection");
7154c30e226SCarson Labrado             // We need to use a specific response handler and send the
7164c30e226SCarson Labrado             // request to all known satellites
7174c30e226SCarson Labrado             getInstance().forwardCollectionRequests(thisReq, asyncResp,
7184c30e226SCarson Labrado                                                     satelliteInfo);
71946a81465SCarson Labrado             return;
72046a81465SCarson Labrado         }
72146a81465SCarson Labrado 
722e002dbc0SCarson Labrado         // We previously determined the request may contain a subordinate
723e002dbc0SCarson Labrado         // collection.  No need to check again
724e002dbc0SCarson Labrado         if (aggType == AggregationType::ContainsSubordinate)
725e002dbc0SCarson Labrado         {
72662598e31SEd Tanous             BMCWEB_LOG_DEBUG(
72762598e31SEd Tanous                 "Aggregating what may have a subordinate collection");
728e002dbc0SCarson Labrado             // We need to use a specific response handler and send the
729e002dbc0SCarson Labrado             // request to all known satellites
730e002dbc0SCarson Labrado             getInstance().forwardContainsSubordinateRequests(thisReq, asyncResp,
731e002dbc0SCarson Labrado                                                              satelliteInfo);
732e002dbc0SCarson Labrado             return;
733e002dbc0SCarson Labrado         }
734e002dbc0SCarson Labrado 
73539662a3bSEd Tanous         const boost::urls::segments_view urlSegments = thisReq.url().segments();
7367c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
7374a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
7384a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
7397c4c52cbSCarson Labrado 
7407c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
7417c4c52cbSCarson Labrado         it++;
7427c4c52cbSCarson Labrado         it++;
7437c4c52cbSCarson Labrado         for (; it != end; it++)
74446a81465SCarson Labrado         {
7457c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
7467c4c52cbSCarson Labrado                                    currentUrl.buffer()))
7477c4c52cbSCarson Labrado             {
7487c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
7497c4c52cbSCarson Labrado                 // must contain an aggregation prefix
7507c4c52cbSCarson Labrado                 findSatellite(thisReq, asyncResp, satelliteInfo, *it);
75146a81465SCarson Labrado                 return;
75246a81465SCarson Labrado             }
75346a81465SCarson Labrado 
7547c4c52cbSCarson Labrado             currentUrl.segments().push_back(*it);
75546a81465SCarson Labrado         }
756db18fc98SCarson Labrado 
757db18fc98SCarson Labrado         // We shouldn't reach this point since we should've hit one of the
758db18fc98SCarson Labrado         // previous exits
759db18fc98SCarson Labrado         messages::internalError(asyncResp->res);
76046a81465SCarson Labrado     }
76146a81465SCarson Labrado 
76246a81465SCarson Labrado     // Attempt to forward a request to the satellite BMC associated with the
76346a81465SCarson Labrado     // prefix.
76446a81465SCarson Labrado     void forwardRequest(
76546a81465SCarson Labrado         const crow::Request& thisReq,
76646a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
76746a81465SCarson Labrado         const std::string& prefix,
76846a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
76946a81465SCarson Labrado     {
77046a81465SCarson Labrado         const auto& sat = satelliteInfo.find(prefix);
77146a81465SCarson Labrado         if (sat == satelliteInfo.end())
77246a81465SCarson Labrado         {
77346a81465SCarson Labrado             // Realistically this shouldn't get called since we perform an
77446a81465SCarson Labrado             // earlier check to make sure the prefix exists
77562598e31SEd Tanous             BMCWEB_LOG_ERROR("Unrecognized satellite prefix \"{}\"", prefix);
77646a81465SCarson Labrado             return;
77746a81465SCarson Labrado         }
77846a81465SCarson Labrado 
77946a81465SCarson Labrado         // We need to strip the prefix from the request's path
780a716aa74SEd Tanous         boost::urls::url targetURI(thisReq.target());
781a716aa74SEd Tanous         std::string path = thisReq.url().path();
782a716aa74SEd Tanous         size_t pos = path.find(prefix + "_");
78346a81465SCarson Labrado         if (pos == std::string::npos)
78446a81465SCarson Labrado         {
78546a81465SCarson Labrado             // If this fails then something went wrong
78662598e31SEd Tanous             BMCWEB_LOG_ERROR("Error removing prefix \"{}_\" from request URI",
78762598e31SEd Tanous                              prefix);
78846a81465SCarson Labrado             messages::internalError(asyncResp->res);
78946a81465SCarson Labrado             return;
79046a81465SCarson Labrado         }
791a716aa74SEd Tanous         path.erase(pos, prefix.size() + 1);
79246a81465SCarson Labrado 
79346a81465SCarson Labrado         std::function<void(crow::Response&)> cb =
7941c0bb5c6SCarson Labrado             std::bind_front(processResponse, prefix, asyncResp);
79546a81465SCarson Labrado 
79627b0cf90SEd Tanous         std::string data = thisReq.body();
797a716aa74SEd Tanous         boost::urls::url url(sat->second);
798a716aa74SEd Tanous         url.set_path(path);
799a716aa74SEd Tanous         if (targetURI.has_query())
800a716aa74SEd Tanous         {
801a716aa74SEd Tanous             url.set_query(targetURI.query());
802a716aa74SEd Tanous         }
80319bb362bSEd Tanous         client.sendDataWithCallback(std::move(data), url,
80419bb362bSEd Tanous                                     ensuressl::VerifyCertificate::Verify,
80519bb362bSEd Tanous                                     thisReq.fields(), thisReq.method(), cb);
80646a81465SCarson Labrado     }
80746a81465SCarson Labrado 
8084c30e226SCarson Labrado     // Forward a request for a collection URI to each known satellite BMC
8094c30e226SCarson Labrado     void forwardCollectionRequests(
8104c30e226SCarson Labrado         const crow::Request& thisReq,
8114c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
8124c30e226SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
8134c30e226SCarson Labrado     {
8144c30e226SCarson Labrado         for (const auto& sat : satelliteInfo)
8154c30e226SCarson Labrado         {
8164c30e226SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
8174c30e226SCarson Labrado                 processCollectionResponse, sat.first, asyncResp);
8184c30e226SCarson Labrado 
819a716aa74SEd Tanous             boost::urls::url url(sat.second);
820a716aa74SEd Tanous             url.set_path(thisReq.url().path());
821a716aa74SEd Tanous             if (thisReq.url().has_query())
822a716aa74SEd Tanous             {
823a716aa74SEd Tanous                 url.set_query(thisReq.url().query());
824a716aa74SEd Tanous             }
82527b0cf90SEd Tanous             std::string data = thisReq.body();
82619bb362bSEd Tanous             client.sendDataWithCallback(std::move(data), url,
82719bb362bSEd Tanous                                         ensuressl::VerifyCertificate::Verify,
82819bb362bSEd Tanous                                         thisReq.fields(), thisReq.method(), cb);
8294c30e226SCarson Labrado         }
8304c30e226SCarson Labrado     }
8314c30e226SCarson Labrado 
832e002dbc0SCarson Labrado     // Forward request for a URI that is uptree of a top level collection to
833e002dbc0SCarson Labrado     // each known satellite BMC
834e002dbc0SCarson Labrado     void forwardContainsSubordinateRequests(
835e002dbc0SCarson Labrado         const crow::Request& thisReq,
836e002dbc0SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
837e002dbc0SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
838e002dbc0SCarson Labrado     {
839e002dbc0SCarson Labrado         for (const auto& sat : satelliteInfo)
840e002dbc0SCarson Labrado         {
841e002dbc0SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
842e002dbc0SCarson Labrado                 processContainsSubordinateResponse, sat.first, asyncResp);
843e002dbc0SCarson Labrado 
844e002dbc0SCarson Labrado             // will ignore an expanded resource in the response if that resource
845e002dbc0SCarson Labrado             // is not already supported by the aggregating BMC
846e002dbc0SCarson Labrado             // TODO: Improve the processing so that we don't have to strip query
847e002dbc0SCarson Labrado             // params in this specific case
848a716aa74SEd Tanous             boost::urls::url url(sat.second);
849a716aa74SEd Tanous             url.set_path(thisReq.url().path());
850a716aa74SEd Tanous 
85127b0cf90SEd Tanous             std::string data = thisReq.body();
852a716aa74SEd Tanous 
85319bb362bSEd Tanous             client.sendDataWithCallback(std::move(data), url,
85419bb362bSEd Tanous                                         ensuressl::VerifyCertificate::Verify,
85519bb362bSEd Tanous                                         thisReq.fields(), thisReq.method(), cb);
856e002dbc0SCarson Labrado         }
857e002dbc0SCarson Labrado     }
858e002dbc0SCarson Labrado 
85932d7d8ebSCarson Labrado   public:
860f8ca6d79SEd Tanous     explicit RedfishAggregator(boost::asio::io_context& ioc) :
861f8ca6d79SEd Tanous         client(ioc,
862f8ca6d79SEd Tanous                std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy()))
863f8ca6d79SEd Tanous     {
864f8ca6d79SEd Tanous         getSatelliteConfigs(constructorCallback);
865f8ca6d79SEd Tanous     }
86632d7d8ebSCarson Labrado     RedfishAggregator(const RedfishAggregator&) = delete;
86732d7d8ebSCarson Labrado     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
86832d7d8ebSCarson Labrado     RedfishAggregator(RedfishAggregator&&) = delete;
86932d7d8ebSCarson Labrado     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
87032d7d8ebSCarson Labrado     ~RedfishAggregator() = default;
87132d7d8ebSCarson Labrado 
872f8ca6d79SEd Tanous     static RedfishAggregator& getInstance(boost::asio::io_context* io = nullptr)
87332d7d8ebSCarson Labrado     {
874f8ca6d79SEd Tanous         static RedfishAggregator handler(*io);
87532d7d8ebSCarson Labrado         return handler;
87632d7d8ebSCarson Labrado     }
87732d7d8ebSCarson Labrado 
8788b2521a5SCarson Labrado     // Polls D-Bus to get all available satellite config information
8798b2521a5SCarson Labrado     // Expects a handler which interacts with the returned configs
8808b2521a5SCarson Labrado     static void getSatelliteConfigs(
8818b2521a5SCarson Labrado         std::function<
8828b2521a5SCarson Labrado             void(const boost::system::error_code&,
8838b2521a5SCarson Labrado                  const std::unordered_map<std::string, boost::urls::url>&)>
8848b2521a5SCarson Labrado             handler)
8858b2521a5SCarson Labrado     {
88662598e31SEd Tanous         BMCWEB_LOG_DEBUG("Gathering satellite configs");
8875eb468daSGeorge Liu         sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
8885eb468daSGeorge Liu         dbus::utility::getManagedObjects(
8895eb468daSGeorge Liu             "xyz.openbmc_project.EntityManager", path,
8908b2521a5SCarson Labrado             [handler{std::move(handler)}](
8918b2521a5SCarson Labrado                 const boost::system::error_code& ec,
8928b2521a5SCarson Labrado                 const dbus::utility::ManagedObjectType& objects) {
8938b2521a5SCarson Labrado                 std::unordered_map<std::string, boost::urls::url> satelliteInfo;
8948b2521a5SCarson Labrado                 if (ec)
8958b2521a5SCarson Labrado                 {
89662598e31SEd Tanous                     BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(),
89762598e31SEd Tanous                                      ec.message());
8988b2521a5SCarson Labrado                     handler(ec, satelliteInfo);
8998b2521a5SCarson Labrado                     return;
9008b2521a5SCarson Labrado                 }
9018b2521a5SCarson Labrado 
9028b2521a5SCarson Labrado                 // Maps a chosen alias representing a satellite BMC to a url
9038b2521a5SCarson Labrado                 // containing the information required to create a http
9048b2521a5SCarson Labrado                 // connection to the satellite
9058b2521a5SCarson Labrado                 findSatelliteConfigs(objects, satelliteInfo);
9068b2521a5SCarson Labrado 
9078b2521a5SCarson Labrado                 if (!satelliteInfo.empty())
9088b2521a5SCarson Labrado                 {
90962598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
91062598e31SEd Tanous                         "Redfish Aggregation enabled with {} satellite BMCs",
91162598e31SEd Tanous                         std::to_string(satelliteInfo.size()));
9128b2521a5SCarson Labrado                 }
9138b2521a5SCarson Labrado                 else
9148b2521a5SCarson Labrado                 {
91562598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
91662598e31SEd Tanous                         "No satellite BMCs detected.  Redfish Aggregation not enabled");
9178b2521a5SCarson Labrado                 }
9188b2521a5SCarson Labrado                 handler(ec, satelliteInfo);
9195eb468daSGeorge Liu             });
9208b2521a5SCarson Labrado     }
9218b2521a5SCarson Labrado 
92246a81465SCarson Labrado     // Processes the response returned by a satellite BMC and loads its
92346a81465SCarson Labrado     // contents into asyncResp
92446a81465SCarson Labrado     static void
9251c0bb5c6SCarson Labrado         processResponse(std::string_view prefix,
9261c0bb5c6SCarson Labrado                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
92746a81465SCarson Labrado                         crow::Response& resp)
92846a81465SCarson Labrado     {
92943e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
93043e14d38SCarson Labrado         // overwrite the response headers in that case
93146b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
93246b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
93343e14d38SCarson Labrado         {
93443e14d38SCarson Labrado             asyncResp->res.result(resp.result());
93543e14d38SCarson Labrado             return;
93643e14d38SCarson Labrado         }
93743e14d38SCarson Labrado 
93832d7d8ebSCarson Labrado         // We want to attempt prefix fixing regardless of response code
93946a81465SCarson Labrado         // The resp will not have a json component
94046a81465SCarson Labrado         // We need to create a json from resp's stringResponse
94118f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
94246a81465SCarson Labrado         {
943bd79bce8SPatrick Williams             nlohmann::json jsonVal =
944bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
94546a81465SCarson Labrado             if (jsonVal.is_discarded())
94646a81465SCarson Labrado             {
94762598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
94846a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
94946a81465SCarson Labrado                 return;
95046a81465SCarson Labrado             }
95146a81465SCarson Labrado 
95262598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
95346a81465SCarson Labrado 
9541c0bb5c6SCarson Labrado             addPrefixes(jsonVal, prefix);
9551c0bb5c6SCarson Labrado 
95662598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
9571c0bb5c6SCarson Labrado 
95846a81465SCarson Labrado             asyncResp->res.result(resp.result());
95946a81465SCarson Labrado             asyncResp->res.jsonValue = std::move(jsonVal);
96046a81465SCarson Labrado 
96162598e31SEd Tanous             BMCWEB_LOG_DEBUG("Finished writing asyncResp");
96246a81465SCarson Labrado         }
96346a81465SCarson Labrado         else
96446a81465SCarson Labrado         {
9650af78d5aSKhang Kieu             // We allow any Content-Type that is not "application/json" now
9660af78d5aSKhang Kieu             asyncResp->res.result(resp.result());
96727b0cf90SEd Tanous             asyncResp->res.copyBody(resp);
96846a81465SCarson Labrado         }
9690af78d5aSKhang Kieu         addAggregatedHeaders(asyncResp->res, resp, prefix);
97046a81465SCarson Labrado     }
97146a81465SCarson Labrado 
9724c30e226SCarson Labrado     // Processes the collection response returned by a satellite BMC and merges
9734c30e226SCarson Labrado     // its "@odata.id" values
9744c30e226SCarson Labrado     static void processCollectionResponse(
9754c30e226SCarson Labrado         const std::string& prefix,
9764c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
9774c30e226SCarson Labrado         crow::Response& resp)
9784c30e226SCarson Labrado     {
97943e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
98043e14d38SCarson Labrado         // overwrite the response headers in that case
98146b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
98246b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
98343e14d38SCarson Labrado         {
98443e14d38SCarson Labrado             return;
98543e14d38SCarson Labrado         }
98643e14d38SCarson Labrado 
9874c30e226SCarson Labrado         if (resp.resultInt() != 200)
9884c30e226SCarson Labrado         {
98962598e31SEd Tanous             BMCWEB_LOG_DEBUG(
99062598e31SEd Tanous                 "Collection resource does not exist in satellite BMC \"{}\"",
99162598e31SEd Tanous                 prefix);
9924c30e226SCarson Labrado             // Return the error if we haven't had any successes
9934c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
9944c30e226SCarson Labrado             {
99546b30283SCarson Labrado                 asyncResp->res.result(resp.result());
99627b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
9974c30e226SCarson Labrado             }
9984c30e226SCarson Labrado             return;
9994c30e226SCarson Labrado         }
10004c30e226SCarson Labrado 
10014c30e226SCarson Labrado         // The resp will not have a json component
10024c30e226SCarson Labrado         // We need to create a json from resp's stringResponse
100318f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
10044c30e226SCarson Labrado         {
1005bd79bce8SPatrick Williams             nlohmann::json jsonVal =
1006bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
10074c30e226SCarson Labrado             if (jsonVal.is_discarded())
10084c30e226SCarson Labrado             {
100962598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
10104c30e226SCarson Labrado 
10114c30e226SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
101246b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
10134c30e226SCarson Labrado                 {
10144c30e226SCarson Labrado                     messages::operationFailed(asyncResp->res);
10154c30e226SCarson Labrado                 }
10164c30e226SCarson Labrado                 return;
10174c30e226SCarson Labrado             }
10184c30e226SCarson Labrado 
101962598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
10204c30e226SCarson Labrado 
10214c30e226SCarson Labrado             // Now we need to add the prefix to the URIs contained in the
10224c30e226SCarson Labrado             // response.
10234c30e226SCarson Labrado             addPrefixes(jsonVal, prefix);
10244c30e226SCarson Labrado 
102562598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
10264c30e226SCarson Labrado 
10274c30e226SCarson Labrado             // If this resource collection does not exist on the aggregating bmc
10284c30e226SCarson Labrado             // and has not already been added from processing the response from
10294c30e226SCarson Labrado             // a different satellite then we need to completely overwrite
10304c30e226SCarson Labrado             // asyncResp
10314c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10324c30e226SCarson Labrado             {
10334c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
10344c30e226SCarson Labrado                 // "Members" array
10354c30e226SCarson Labrado                 if ((!jsonVal.contains("Members")) &&
10364c30e226SCarson Labrado                     (!jsonVal["Members"].is_array()))
10374c30e226SCarson Labrado                 {
103862598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
103962598e31SEd Tanous                         "Skipping aggregating unsupported resource");
10404c30e226SCarson Labrado                     return;
10414c30e226SCarson Labrado                 }
10424c30e226SCarson Labrado 
104362598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
104462598e31SEd Tanous                     "Collection does not exist, overwriting asyncResp");
10454c30e226SCarson Labrado                 asyncResp->res.result(resp.result());
10464c30e226SCarson Labrado                 asyncResp->res.jsonValue = std::move(jsonVal);
104743e14d38SCarson Labrado                 asyncResp->res.addHeader("Content-Type", "application/json");
10484c30e226SCarson Labrado 
104962598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Finished overwriting asyncResp");
10504c30e226SCarson Labrado             }
10514c30e226SCarson Labrado             else
10524c30e226SCarson Labrado             {
10534c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
10544c30e226SCarson Labrado                 // "Members" array
10554c30e226SCarson Labrado                 if ((!asyncResp->res.jsonValue.contains("Members")) &&
10564c30e226SCarson Labrado                     (!asyncResp->res.jsonValue["Members"].is_array()))
10574c30e226SCarson Labrado 
10584c30e226SCarson Labrado                 {
105962598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
106062598e31SEd Tanous                         "Skipping aggregating unsupported resource");
10614c30e226SCarson Labrado                     return;
10624c30e226SCarson Labrado                 }
10634c30e226SCarson Labrado 
106462598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
106562598e31SEd Tanous                     "Adding aggregated resources from \"{}\" to collection",
106662598e31SEd Tanous                     prefix);
10674c30e226SCarson Labrado 
10684c30e226SCarson Labrado                 // TODO: This is a potential race condition with multiple
10694c30e226SCarson Labrado                 // satellites and the aggregating bmc attempting to write to
10704c30e226SCarson Labrado                 // update this array.  May need to cascade calls to the next
10714c30e226SCarson Labrado                 // satellite at the end of this function.
10724c30e226SCarson Labrado                 // This is presumably not a concern when there is only a single
10734c30e226SCarson Labrado                 // satellite since the aggregating bmc should have completed
10744c30e226SCarson Labrado                 // before the response is received from the satellite.
10754c30e226SCarson Labrado 
10764c30e226SCarson Labrado                 auto& members = asyncResp->res.jsonValue["Members"];
10774c30e226SCarson Labrado                 auto& satMembers = jsonVal["Members"];
10784c30e226SCarson Labrado                 for (auto& satMem : satMembers)
10794c30e226SCarson Labrado                 {
1080b2ba3072SPatrick Williams                     members.emplace_back(std::move(satMem));
10814c30e226SCarson Labrado                 }
10824c30e226SCarson Labrado                 asyncResp->res.jsonValue["Members@odata.count"] =
10834c30e226SCarson Labrado                     members.size();
10844c30e226SCarson Labrado 
10854c30e226SCarson Labrado                 // TODO: Do we need to sort() after updating the array?
10864c30e226SCarson Labrado             }
10874c30e226SCarson Labrado         }
10884c30e226SCarson Labrado         else
10894c30e226SCarson Labrado         {
109062598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
109162598e31SEd Tanous                              prefix);
109243e14d38SCarson Labrado             // We received a response that was not a json.
109346b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses
109446b30283SCarson Labrado             // and if the resource collection does not already exist on the
109546b30283SCarson Labrado             // aggregating BMC
109646b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10974c30e226SCarson Labrado             {
10984c30e226SCarson Labrado                 messages::operationFailed(asyncResp->res);
10994c30e226SCarson Labrado             }
11004c30e226SCarson Labrado         }
11014c30e226SCarson Labrado     } // End processCollectionResponse()
11024c30e226SCarson Labrado 
110346b30283SCarson Labrado     // Processes the response returned by a satellite BMC and merges any
110446b30283SCarson Labrado     // properties whose "@odata.id" value is the URI or either a top level
110546b30283SCarson Labrado     // collection or is uptree from a top level collection
110646b30283SCarson Labrado     static void processContainsSubordinateResponse(
110746b30283SCarson Labrado         const std::string& prefix,
110846b30283SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
110946b30283SCarson Labrado         crow::Response& resp)
111046b30283SCarson Labrado     {
111146b30283SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
111246b30283SCarson Labrado         // overwrite the response headers in that case
111346b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
111446b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
111546b30283SCarson Labrado         {
111646b30283SCarson Labrado             return;
111746b30283SCarson Labrado         }
111846b30283SCarson Labrado 
111946b30283SCarson Labrado         if (resp.resultInt() != 200)
112046b30283SCarson Labrado         {
112162598e31SEd Tanous             BMCWEB_LOG_DEBUG(
112262598e31SEd Tanous                 "Resource uptree from Collection does not exist in satellite BMC \"{}\"",
112362598e31SEd Tanous                 prefix);
112446b30283SCarson Labrado             // Return the error if we haven't had any successes
112546b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
112646b30283SCarson Labrado             {
112746b30283SCarson Labrado                 asyncResp->res.result(resp.result());
112827b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
112946b30283SCarson Labrado             }
113046b30283SCarson Labrado             return;
113146b30283SCarson Labrado         }
113246b30283SCarson Labrado 
113346b30283SCarson Labrado         // The resp will not have a json component
113446b30283SCarson Labrado         // We need to create a json from resp's stringResponse
113518f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
113646b30283SCarson Labrado         {
113746b30283SCarson Labrado             bool addedLinks = false;
1138bd79bce8SPatrick Williams             nlohmann::json jsonVal =
1139bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
114046b30283SCarson Labrado             if (jsonVal.is_discarded())
114146b30283SCarson Labrado             {
114262598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
114346b30283SCarson Labrado 
114446b30283SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
114546b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
114646b30283SCarson Labrado                 {
114746b30283SCarson Labrado                     messages::operationFailed(asyncResp->res);
114846b30283SCarson Labrado                 }
114946b30283SCarson Labrado                 return;
115046b30283SCarson Labrado             }
115146b30283SCarson Labrado 
115262598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
115346b30283SCarson Labrado 
115446b30283SCarson Labrado             // Parse response and add properties missing from the AsyncResp
115546b30283SCarson Labrado             // Valid properties will be of the form <property>.@odata.id and
115646b30283SCarson Labrado             // @odata.id is a <URI>.  In other words, the json should contain
115746b30283SCarson Labrado             // multiple properties such that
115846b30283SCarson Labrado             // {"<property>":{"@odata.id": "<URI>"}}
115946b30283SCarson Labrado             nlohmann::json::object_t* object =
116046b30283SCarson Labrado                 jsonVal.get_ptr<nlohmann::json::object_t*>();
116146b30283SCarson Labrado             if (object == nullptr)
116246b30283SCarson Labrado             {
116362598e31SEd Tanous                 BMCWEB_LOG_ERROR("Parsed JSON was not an object?");
116446b30283SCarson Labrado                 return;
116546b30283SCarson Labrado             }
116646b30283SCarson Labrado 
116746b30283SCarson Labrado             for (std::pair<const std::string, nlohmann::json>& prop : *object)
116846b30283SCarson Labrado             {
116946b30283SCarson Labrado                 if (!prop.second.contains("@odata.id"))
117046b30283SCarson Labrado                 {
117146b30283SCarson Labrado                     continue;
117246b30283SCarson Labrado                 }
117346b30283SCarson Labrado 
117446b30283SCarson Labrado                 std::string* strValue =
117546b30283SCarson Labrado                     prop.second["@odata.id"].get_ptr<std::string*>();
117646b30283SCarson Labrado                 if (strValue == nullptr)
117746b30283SCarson Labrado                 {
117862598e31SEd Tanous                     BMCWEB_LOG_CRITICAL("Field wasn't a string????");
117946b30283SCarson Labrado                     continue;
118046b30283SCarson Labrado                 }
118146b30283SCarson Labrado                 if (!searchCollectionsArray(*strValue, SearchType::CollOrCon))
118246b30283SCarson Labrado                 {
118346b30283SCarson Labrado                     continue;
118446b30283SCarson Labrado                 }
118546b30283SCarson Labrado 
118646b30283SCarson Labrado                 addedLinks = true;
118746b30283SCarson Labrado                 if (!asyncResp->res.jsonValue.contains(prop.first))
118846b30283SCarson Labrado                 {
118946b30283SCarson Labrado                     // Only add the property if it did not already exist
119062598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Adding link for {} from BMC {}",
119162598e31SEd Tanous                                      *strValue, prefix);
119246b30283SCarson Labrado                     asyncResp->res.jsonValue[prop.first]["@odata.id"] =
119346b30283SCarson Labrado                         *strValue;
119446b30283SCarson Labrado                     continue;
119546b30283SCarson Labrado                 }
119646b30283SCarson Labrado             }
119746b30283SCarson Labrado 
119846b30283SCarson Labrado             // If we added links to a previously unsuccessful (non-200) response
119946b30283SCarson Labrado             // then we need to make sure the response contains the bare minimum
120046b30283SCarson Labrado             // amount of additional information that we'd expect to have been
120146b30283SCarson Labrado             // populated.
120246b30283SCarson Labrado             if (addedLinks && (asyncResp->res.resultInt() != 200))
120346b30283SCarson Labrado             {
120446b30283SCarson Labrado                 // This resource didn't locally exist or an error
120546b30283SCarson Labrado                 // occurred while generating the response.  Remove any
120646b30283SCarson Labrado                 // error messages and update the error code.
120746b30283SCarson Labrado                 asyncResp->res.jsonValue.erase(
120846b30283SCarson Labrado                     asyncResp->res.jsonValue.find("error"));
120946b30283SCarson Labrado                 asyncResp->res.result(resp.result());
121046b30283SCarson Labrado 
121146b30283SCarson Labrado                 const auto& it1 = object->find("@odata.id");
121246b30283SCarson Labrado                 if (it1 != object->end())
121346b30283SCarson Labrado                 {
121446b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.id"] = (it1->second);
121546b30283SCarson Labrado                 }
121646b30283SCarson Labrado                 const auto& it2 = object->find("@odata.type");
121746b30283SCarson Labrado                 if (it2 != object->end())
121846b30283SCarson Labrado                 {
121946b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.type"] = (it2->second);
122046b30283SCarson Labrado                 }
122146b30283SCarson Labrado                 const auto& it3 = object->find("Id");
122246b30283SCarson Labrado                 if (it3 != object->end())
122346b30283SCarson Labrado                 {
122446b30283SCarson Labrado                     asyncResp->res.jsonValue["Id"] = (it3->second);
122546b30283SCarson Labrado                 }
122646b30283SCarson Labrado                 const auto& it4 = object->find("Name");
122746b30283SCarson Labrado                 if (it4 != object->end())
122846b30283SCarson Labrado                 {
122946b30283SCarson Labrado                     asyncResp->res.jsonValue["Name"] = (it4->second);
123046b30283SCarson Labrado                 }
123146b30283SCarson Labrado             }
123246b30283SCarson Labrado         }
123346b30283SCarson Labrado         else
123446b30283SCarson Labrado         {
123562598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
123662598e31SEd Tanous                              prefix);
123746b30283SCarson Labrado             // We received as response that was not a json
123846b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses,
123946b30283SCarson Labrado             // and if the resource does not already exist on the aggregating BMC
124046b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
124146b30283SCarson Labrado             {
124246b30283SCarson Labrado                 messages::operationFailed(asyncResp->res);
124346b30283SCarson Labrado             }
124446b30283SCarson Labrado         }
124546b30283SCarson Labrado     }
124646b30283SCarson Labrado 
124705916cefSCarson Labrado     // Entry point to Redfish Aggregation
124805916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
124905916cefSCarson Labrado     // request
125005916cefSCarson Labrado     static Result
125105916cefSCarson Labrado         beginAggregation(const crow::Request& thisReq,
125205916cefSCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
125305916cefSCarson Labrado     {
125405916cefSCarson Labrado         using crow::utility::OrMorePaths;
125505916cefSCarson Labrado         using crow::utility::readUrlSegments;
12564a7fbefdSEd Tanous         boost::urls::url_view url = thisReq.url();
1257411e6a11SCarson Labrado 
1258411e6a11SCarson Labrado         // We don't need to aggregate JsonSchemas due to potential issues such
1259411e6a11SCarson Labrado         // as version mismatches between aggregator and satellite BMCs.  For
1260411e6a11SCarson Labrado         // now assume that the aggregator has all the schemas and versions that
1261411e6a11SCarson Labrado         // the aggregated server has.
1262411e6a11SCarson Labrado         if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas",
1263411e6a11SCarson Labrado                                            crow::utility::OrMorePaths()))
1264411e6a11SCarson Labrado         {
1265411e6a11SCarson Labrado             return Result::LocalHandle;
1266411e6a11SCarson Labrado         }
1267411e6a11SCarson Labrado 
12687c4c52cbSCarson Labrado         // The first two segments should be "/redfish/v1".  We need to check
12697c4c52cbSCarson Labrado         // that before we can search topCollections
12707c4c52cbSCarson Labrado         if (!crow::utility::readUrlSegments(url, "redfish", "v1",
12717c4c52cbSCarson Labrado                                             crow::utility::OrMorePaths()))
127246a81465SCarson Labrado         {
127346a81465SCarson Labrado             return Result::LocalHandle;
127446a81465SCarson Labrado         }
127505916cefSCarson Labrado 
12767c4c52cbSCarson Labrado         // Parse the URI to see if it begins with a known top level collection
12777c4c52cbSCarson Labrado         // such as:
12787c4c52cbSCarson Labrado         // /redfish/v1/Chassis
12797c4c52cbSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory
12807c4c52cbSCarson Labrado         const boost::urls::segments_view urlSegments = url.segments();
12817c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
12824a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
12834a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
128405916cefSCarson Labrado 
12857c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
12867c4c52cbSCarson Labrado         it++;
12877c4c52cbSCarson Labrado         it++;
12887c4c52cbSCarson Labrado         for (; it != end; it++)
128905916cefSCarson Labrado         {
1290d4413c5bSGeorge Liu             const std::string& collectionItem = *it;
12917c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
12927c4c52cbSCarson Labrado                                    currentUrl.buffer()))
12937c4c52cbSCarson Labrado             {
12947c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
12957c4c52cbSCarson Labrado                 // might contain an aggregation prefix
12968b2521a5SCarson Labrado                 // TODO: This needs to be rethought when we can support multiple
12978b2521a5SCarson Labrado                 // satellites due to
12988b2521a5SCarson Labrado                 // /redfish/v1/AggregationService/AggregationSources/5B247A
12998b2521a5SCarson Labrado                 // being a local resource describing the satellite
13008b2521a5SCarson Labrado                 if (collectionItem.starts_with("5B247A_"))
130105916cefSCarson Labrado                 {
130262598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Need to forward a request");
130305916cefSCarson Labrado 
130446a81465SCarson Labrado                     // Extract the prefix from the request's URI, retrieve the
13057c4c52cbSCarson Labrado                     // associated satellite config information, and then forward
13067c4c52cbSCarson Labrado                     // the request to that satellite.
13077c4c52cbSCarson Labrado                     startAggregation(AggregationType::Resource, thisReq,
13087c4c52cbSCarson Labrado                                      asyncResp);
130905916cefSCarson Labrado                     return Result::NoLocalHandle;
131005916cefSCarson Labrado                 }
13117c4c52cbSCarson Labrado 
13127c4c52cbSCarson Labrado                 // Handle collection URI with a trailing backslash
13137c4c52cbSCarson Labrado                 // e.g. /redfish/v1/Chassis/
13147c4c52cbSCarson Labrado                 it++;
13157c4c52cbSCarson Labrado                 if ((it == end) && collectionItem.empty())
13167c4c52cbSCarson Labrado                 {
13177c4c52cbSCarson Labrado                     startAggregation(AggregationType::Collection, thisReq,
13187c4c52cbSCarson Labrado                                      asyncResp);
13197c4c52cbSCarson Labrado                 }
13207c4c52cbSCarson Labrado 
13217c4c52cbSCarson Labrado                 // We didn't recognize the prefix or it's a collection with a
13227c4c52cbSCarson Labrado                 // trailing "/".  In both cases we still want to locally handle
13237c4c52cbSCarson Labrado                 // the request
13247c4c52cbSCarson Labrado                 return Result::LocalHandle;
13257c4c52cbSCarson Labrado             }
13267c4c52cbSCarson Labrado 
13277c4c52cbSCarson Labrado             currentUrl.segments().push_back(collectionItem);
13287c4c52cbSCarson Labrado         }
13297c4c52cbSCarson Labrado 
13307c4c52cbSCarson Labrado         // If we made it here then currentUrl could contain a top level
13317c4c52cbSCarson Labrado         // collection URI without a trailing "/", e.g. /redfish/v1/Chassis
13327c4c52cbSCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
13337c4c52cbSCarson Labrado                                currentUrl.buffer()))
13347c4c52cbSCarson Labrado         {
13357c4c52cbSCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
133605916cefSCarson Labrado             return Result::LocalHandle;
133705916cefSCarson Labrado         }
133805916cefSCarson Labrado 
1339e002dbc0SCarson Labrado         // If nothing else then the request could be for a resource which has a
1340e002dbc0SCarson Labrado         // top level collection as a subordinate
1341e002dbc0SCarson Labrado         if (searchCollectionsArray(url.path(), SearchType::ContainsSubordinate))
1342e002dbc0SCarson Labrado         {
1343e002dbc0SCarson Labrado             startAggregation(AggregationType::ContainsSubordinate, thisReq,
1344e002dbc0SCarson Labrado                              asyncResp);
1345e002dbc0SCarson Labrado             return Result::LocalHandle;
1346e002dbc0SCarson Labrado         }
1347e002dbc0SCarson Labrado 
134862598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation not required for {}", url.buffer());
134905916cefSCarson Labrado         return Result::LocalHandle;
135005916cefSCarson Labrado     }
13517fb33566SCarson Labrado };
13527fb33566SCarson Labrado 
13537fb33566SCarson Labrado } // namespace redfish
1354