xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 71526116e1eb38a983df0ca12de63348d37e0e9b)
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"
6d7857201SEd Tanous #include "async_resp.hpp"
73ccb3adbSEd Tanous #include "dbus_utility.hpp"
83ccb3adbSEd Tanous #include "error_messages.hpp"
93ccb3adbSEd Tanous #include "http_client.hpp"
10d7857201SEd Tanous #include "http_request.hpp"
11d7857201SEd Tanous #include "http_response.hpp"
129838eb20SEd Tanous #include "io_context_singleton.hpp"
13d7857201SEd Tanous #include "logging.hpp"
1418f8f608SEd Tanous #include "parsing.hpp"
15d7857201SEd Tanous #include "ssl_key_handler.hpp"
16d7857201SEd Tanous #include "utility.hpp"
177fb33566SCarson Labrado 
18d7857201SEd Tanous #include <boost/beast/http/field.hpp>
19d7857201SEd Tanous #include <boost/beast/http/status.hpp>
20d7857201SEd Tanous #include <boost/beast/http/verb.hpp>
21d7857201SEd Tanous #include <boost/system/errc.hpp>
22d7857201SEd Tanous #include <boost/system/result.hpp>
23d7857201SEd Tanous #include <boost/url/param.hpp>
24d7857201SEd Tanous #include <boost/url/parse.hpp>
25d7857201SEd Tanous #include <boost/url/segments_ref.hpp>
26d7857201SEd Tanous #include <boost/url/segments_view.hpp>
27d7857201SEd Tanous #include <boost/url/url.hpp>
28d7857201SEd Tanous #include <boost/url/url_view.hpp>
29d7857201SEd Tanous #include <nlohmann/json.hpp>
30d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp>
31d7857201SEd Tanous 
32d7857201SEd Tanous #include <algorithm>
337e8890c5SCarson Labrado #include <array>
34d7857201SEd Tanous #include <chrono>
35d7857201SEd Tanous #include <cstddef>
36d7857201SEd Tanous #include <cstdint>
37d7857201SEd Tanous #include <functional>
38d7857201SEd Tanous #include <limits>
39d7857201SEd Tanous #include <memory>
403544d2a7SEd Tanous #include <ranges>
41d7857201SEd Tanous #include <string>
423544d2a7SEd Tanous #include <string_view>
43d7857201SEd Tanous #include <system_error>
44d7857201SEd Tanous #include <unordered_map>
45d7857201SEd Tanous #include <utility>
46d7857201SEd 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 
4625a3d934aSRohit PAI                     addSatelliteConfig(interface.second, satelliteInfo);
4637fb33566SCarson Labrado                 }
4647fb33566SCarson Labrado             }
4657fb33566SCarson Labrado         }
4667fb33566SCarson Labrado     }
4677fb33566SCarson Labrado 
4687fb33566SCarson Labrado     // Parse the properties of a satellite config object and add the
4697fb33566SCarson Labrado     // configuration if the properties are valid
4707fb33566SCarson Labrado     static void addSatelliteConfig(
4717fb33566SCarson Labrado         const dbus::utility::DBusPropertiesMap& properties,
4727fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
4737fb33566SCarson Labrado     {
4747fb33566SCarson Labrado         boost::urls::url url;
4755a3d934aSRohit PAI         std::string prefix;
4767fb33566SCarson Labrado 
4777fb33566SCarson Labrado         for (const auto& prop : properties)
4787fb33566SCarson Labrado         {
47905916cefSCarson Labrado             if (prop.first == "Hostname")
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 Hostname value");
4867fb33566SCarson Labrado                     return;
4877fb33566SCarson Labrado                 }
4887fb33566SCarson Labrado                 url.set_host(*propVal);
4897fb33566SCarson Labrado             }
4907fb33566SCarson Labrado 
4917fb33566SCarson Labrado             else if (prop.first == "Port")
4927fb33566SCarson Labrado             {
4937fb33566SCarson Labrado                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
4947fb33566SCarson Labrado                 if (propVal == nullptr)
4957fb33566SCarson Labrado                 {
49662598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid Port value");
4977fb33566SCarson Labrado                     return;
4987fb33566SCarson Labrado                 }
4997fb33566SCarson Labrado 
5007fb33566SCarson Labrado                 if (*propVal > std::numeric_limits<uint16_t>::max())
5017fb33566SCarson Labrado                 {
50262598e31SEd Tanous                     BMCWEB_LOG_ERROR("Port value out of range");
5037fb33566SCarson Labrado                     return;
5047fb33566SCarson Labrado                 }
505079360aeSEd Tanous                 url.set_port(std::to_string(static_cast<uint16_t>(*propVal)));
5067fb33566SCarson Labrado             }
5077fb33566SCarson Labrado 
5087fb33566SCarson Labrado             else if (prop.first == "AuthType")
5097fb33566SCarson Labrado             {
5107fb33566SCarson Labrado                 const std::string* propVal =
5117fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
5127fb33566SCarson Labrado                 if (propVal == nullptr)
5137fb33566SCarson Labrado                 {
51462598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid AuthType value");
5157fb33566SCarson Labrado                     return;
5167fb33566SCarson Labrado                 }
5177fb33566SCarson Labrado 
5187fb33566SCarson Labrado                 // For now assume authentication not required to communicate
5197fb33566SCarson Labrado                 // with the satellite BMC
5207fb33566SCarson Labrado                 if (*propVal != "None")
5217fb33566SCarson Labrado                 {
52262598e31SEd Tanous                     BMCWEB_LOG_ERROR(
52362598e31SEd Tanous                         "Unsupported AuthType value: {}, only \"none\" is supported",
52462598e31SEd Tanous                         *propVal);
5257fb33566SCarson Labrado                     return;
5267fb33566SCarson Labrado                 }
5277fb33566SCarson Labrado                 url.set_scheme("http");
5287fb33566SCarson Labrado             }
5295a3d934aSRohit PAI             else if (prop.first == "Name")
5305a3d934aSRohit PAI             {
5315a3d934aSRohit PAI                 const std::string* propVal =
5325a3d934aSRohit PAI                     std::get_if<std::string>(&prop.second);
5335a3d934aSRohit PAI                 if (propVal != nullptr && !propVal->empty())
5345a3d934aSRohit PAI                 {
5355a3d934aSRohit PAI                     prefix = *propVal;
5365a3d934aSRohit PAI                     BMCWEB_LOG_DEBUG("Using Name property {} as prefix",
5375a3d934aSRohit PAI                                      prefix);
5385a3d934aSRohit PAI                 }
5395a3d934aSRohit PAI                 else
5405a3d934aSRohit PAI                 {
5415a3d934aSRohit PAI                     BMCWEB_LOG_DEBUG(
5425a3d934aSRohit PAI                         "Invalid or empty Name property, invalid satellite config");
5435a3d934aSRohit PAI                     return;
5445a3d934aSRohit PAI                 }
5455a3d934aSRohit PAI             }
5467fb33566SCarson Labrado         } // Finished reading properties
5477fb33566SCarson Labrado 
5487fb33566SCarson Labrado         // Make sure all required config information was made available
5497fb33566SCarson Labrado         if (url.host().empty())
5507fb33566SCarson Labrado         {
5515a3d934aSRohit PAI             BMCWEB_LOG_ERROR("Satellite config {} missing Host", prefix);
5527fb33566SCarson Labrado             return;
5537fb33566SCarson Labrado         }
5547fb33566SCarson Labrado 
5557fb33566SCarson Labrado         if (!url.has_port())
5567fb33566SCarson Labrado         {
5575a3d934aSRohit PAI             BMCWEB_LOG_ERROR("Satellite config {} missing Port", prefix);
5587fb33566SCarson Labrado             return;
5597fb33566SCarson Labrado         }
5607fb33566SCarson Labrado 
5617fb33566SCarson Labrado         if (!url.has_scheme())
5627fb33566SCarson Labrado         {
5635a3d934aSRohit PAI             BMCWEB_LOG_ERROR("Satellite config {} missing AuthType", prefix);
5647fb33566SCarson Labrado             return;
5657fb33566SCarson Labrado         }
5667fb33566SCarson Labrado 
5677fb33566SCarson Labrado         std::string resultString;
5685a3d934aSRohit PAI         auto result = satelliteInfo.insert_or_assign(prefix, std::move(url));
5697fb33566SCarson Labrado         if (result.second)
5707fb33566SCarson Labrado         {
5717fb33566SCarson Labrado             resultString = "Added new satellite config ";
5727fb33566SCarson Labrado         }
5737fb33566SCarson Labrado         else
5747fb33566SCarson Labrado         {
5757fb33566SCarson Labrado             resultString = "Updated existing satellite config ";
5767fb33566SCarson Labrado         }
5777fb33566SCarson Labrado 
5785a3d934aSRohit PAI         BMCWEB_LOG_DEBUG("{}{} at {}://{}", resultString, prefix,
57962598e31SEd Tanous                          result.first->second.scheme(),
58062598e31SEd Tanous                          result.first->second.encoded_host_and_port());
5817fb33566SCarson Labrado     }
5827fb33566SCarson Labrado 
58346a81465SCarson Labrado     enum AggregationType
58446a81465SCarson Labrado     {
58546a81465SCarson Labrado         Collection,
586e002dbc0SCarson Labrado         ContainsSubordinate,
58746a81465SCarson Labrado         Resource,
58846a81465SCarson Labrado     };
58946a81465SCarson Labrado 
59066620686SEd Tanous     void startAggregation(
591504af5a0SPatrick Williams         AggregationType aggType, const crow::Request& thisReq,
59266620686SEd Tanous         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) const
59346a81465SCarson Labrado     {
594e002dbc0SCarson Labrado         if (thisReq.method() != boost::beast::http::verb::get)
595e002dbc0SCarson Labrado         {
596e002dbc0SCarson Labrado             if (aggType == AggregationType::Collection)
597db18fc98SCarson Labrado             {
59862598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
59962598e31SEd Tanous                     "Only aggregate GET requests to top level collections");
600db18fc98SCarson Labrado                 return;
601db18fc98SCarson Labrado             }
602db18fc98SCarson Labrado 
603e002dbc0SCarson Labrado             if (aggType == AggregationType::ContainsSubordinate)
604e002dbc0SCarson Labrado             {
60562598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
60662598e31SEd Tanous                     "Only aggregate GET requests when uptree of a top level collection");
607e002dbc0SCarson Labrado                 return;
608e002dbc0SCarson Labrado             }
609e002dbc0SCarson Labrado         }
610e002dbc0SCarson Labrado 
61146a81465SCarson Labrado         // Create a copy of thisReq so we we can still locally process the req
61246a81465SCarson Labrado         std::error_code ec;
613761cdfa5SEd Tanous         auto localReq = std::make_shared<crow::Request>(thisReq.copy());
61446a81465SCarson Labrado         if (ec)
61546a81465SCarson Labrado         {
61662598e31SEd Tanous             BMCWEB_LOG_ERROR("Failed to create copy of request");
617e002dbc0SCarson Labrado             if (aggType == AggregationType::Resource)
61846a81465SCarson Labrado             {
61946a81465SCarson Labrado                 messages::internalError(asyncResp->res);
62046a81465SCarson Labrado             }
62146a81465SCarson Labrado             return;
62246a81465SCarson Labrado         }
62346a81465SCarson Labrado 
6246282bc71SEd Tanous         if (aggType == AggregationType::Collection)
6256282bc71SEd Tanous         {
6266282bc71SEd Tanous             boost::urls::url& urlNew = localReq->url();
6276282bc71SEd Tanous             auto paramsIt = urlNew.params().begin();
6286282bc71SEd Tanous             while (paramsIt != urlNew.params().end())
6296282bc71SEd Tanous             {
6306282bc71SEd Tanous                 const boost::urls::param& param = *paramsIt;
6316282bc71SEd Tanous                 // only and $skip, params can't be passed to satellite
6326282bc71SEd Tanous                 // as applying these filters twice results in different results.
6336282bc71SEd Tanous                 // Removing them will cause them to only be processed in the
6346282bc71SEd Tanous                 // aggregator. Note, this still doesn't work for collections
6356282bc71SEd Tanous                 // that might return less than the complete collection by
6366282bc71SEd Tanous                 // default, but hopefully those are rare/nonexistent in top
6376282bc71SEd Tanous                 // collections.  bmcweb doesn't implement any of these.
6386282bc71SEd Tanous                 if (param.key == "only" || param.key == "$skip")
6396282bc71SEd Tanous                 {
6406282bc71SEd Tanous                     BMCWEB_LOG_DEBUG(
6416282bc71SEd Tanous                         "Erasing \"{}\" param from request to top level collection",
6426282bc71SEd Tanous                         param.key);
6436282bc71SEd Tanous 
6446282bc71SEd Tanous                     paramsIt = urlNew.params().erase(paramsIt);
6456282bc71SEd Tanous                     continue;
6466282bc71SEd Tanous                 }
6476282bc71SEd Tanous                 // Pass all other parameters
6486282bc71SEd Tanous                 paramsIt++;
6496282bc71SEd Tanous             }
6506282bc71SEd Tanous             localReq->target(urlNew.buffer());
6516282bc71SEd Tanous         }
6526282bc71SEd Tanous 
653e002dbc0SCarson Labrado         getSatelliteConfigs(
654e002dbc0SCarson Labrado             std::bind_front(aggregateAndHandle, aggType, localReq, asyncResp));
65546a81465SCarson Labrado     }
65646a81465SCarson Labrado 
657db18fc98SCarson Labrado     static void findSatellite(
65846a81465SCarson Labrado         const crow::Request& req,
65946a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
66046a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo,
66146a81465SCarson Labrado         std::string_view memberName)
66246a81465SCarson Labrado     {
66346a81465SCarson Labrado         // Determine if the resource ID begins with a known prefix
66446a81465SCarson Labrado         for (const auto& satellite : satelliteInfo)
66546a81465SCarson Labrado         {
66646a81465SCarson Labrado             std::string targetPrefix = satellite.first;
66746a81465SCarson Labrado             targetPrefix += "_";
66846a81465SCarson Labrado             if (memberName.starts_with(targetPrefix))
66946a81465SCarson Labrado             {
67062598e31SEd Tanous                 BMCWEB_LOG_DEBUG("\"{}\" is a known prefix", satellite.first);
67146a81465SCarson Labrado 
67246a81465SCarson Labrado                 // Remove the known prefix from the request's URI and
67346a81465SCarson Labrado                 // then forward to the associated satellite BMC
67446a81465SCarson Labrado                 getInstance().forwardRequest(req, asyncResp, satellite.first,
67546a81465SCarson Labrado                                              satelliteInfo);
67646a81465SCarson Labrado                 return;
67746a81465SCarson Labrado             }
67846a81465SCarson Labrado         }
679db18fc98SCarson Labrado 
680db18fc98SCarson Labrado         // We didn't recognize the prefix and need to return a 404
68139662a3bSEd Tanous         std::string nameStr = req.url().segments().back();
682db18fc98SCarson Labrado         messages::resourceNotFound(asyncResp->res, "", nameStr);
68346a81465SCarson Labrado     }
68446a81465SCarson Labrado 
68546a81465SCarson Labrado     // Intended to handle an incoming request based on if Redfish Aggregation
68646a81465SCarson Labrado     // is enabled.  Forwards request to satellite BMC if it exists.
68746a81465SCarson Labrado     static void aggregateAndHandle(
688e002dbc0SCarson Labrado         AggregationType aggType,
68946a81465SCarson Labrado         const std::shared_ptr<crow::Request>& sharedReq,
69046a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6918b2521a5SCarson Labrado         const boost::system::error_code& ec,
69246a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
69346a81465SCarson Labrado     {
69446a81465SCarson Labrado         if (sharedReq == nullptr)
69546a81465SCarson Labrado         {
69646a81465SCarson Labrado             return;
69746a81465SCarson Labrado         }
6988b2521a5SCarson Labrado         // Something went wrong while querying dbus
6998b2521a5SCarson Labrado         if (ec)
7008b2521a5SCarson Labrado         {
7018b2521a5SCarson Labrado             messages::internalError(asyncResp->res);
7028b2521a5SCarson Labrado             return;
7038b2521a5SCarson Labrado         }
704db18fc98SCarson Labrado 
705db18fc98SCarson Labrado         // No satellite configs means we don't need to keep attempting to
706db18fc98SCarson Labrado         // aggregate
707db18fc98SCarson Labrado         if (satelliteInfo.empty())
708db18fc98SCarson Labrado         {
709e002dbc0SCarson Labrado             // For collections or resources that can contain a subordinate
710e002dbc0SCarson Labrado             // top level collection we'll also handle the request locally so we
711db18fc98SCarson Labrado             // don't need to write an error code
712e002dbc0SCarson Labrado             if (aggType == AggregationType::Resource)
713db18fc98SCarson Labrado             {
71439662a3bSEd Tanous                 std::string nameStr = sharedReq->url().segments().back();
715db18fc98SCarson Labrado                 messages::resourceNotFound(asyncResp->res, "", nameStr);
716db18fc98SCarson Labrado             }
717db18fc98SCarson Labrado             return;
718db18fc98SCarson Labrado         }
719db18fc98SCarson Labrado 
72046a81465SCarson Labrado         const crow::Request& thisReq = *sharedReq;
72162598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation is enabled, begin processing of {}",
72262598e31SEd Tanous                          thisReq.target());
72346a81465SCarson Labrado 
72446a81465SCarson Labrado         // We previously determined the request is for a collection.  No need to
72546a81465SCarson Labrado         // check again
726e002dbc0SCarson Labrado         if (aggType == AggregationType::Collection)
72746a81465SCarson Labrado         {
72862598e31SEd Tanous             BMCWEB_LOG_DEBUG("Aggregating a collection");
7294c30e226SCarson Labrado             // We need to use a specific response handler and send the
7304c30e226SCarson Labrado             // request to all known satellites
7314c30e226SCarson Labrado             getInstance().forwardCollectionRequests(thisReq, asyncResp,
7324c30e226SCarson Labrado                                                     satelliteInfo);
73346a81465SCarson Labrado             return;
73446a81465SCarson Labrado         }
73546a81465SCarson Labrado 
736e002dbc0SCarson Labrado         // We previously determined the request may contain a subordinate
737e002dbc0SCarson Labrado         // collection.  No need to check again
738e002dbc0SCarson Labrado         if (aggType == AggregationType::ContainsSubordinate)
739e002dbc0SCarson Labrado         {
74062598e31SEd Tanous             BMCWEB_LOG_DEBUG(
74162598e31SEd Tanous                 "Aggregating what may have a subordinate collection");
742e002dbc0SCarson Labrado             // We need to use a specific response handler and send the
743e002dbc0SCarson Labrado             // request to all known satellites
744e002dbc0SCarson Labrado             getInstance().forwardContainsSubordinateRequests(thisReq, asyncResp,
745e002dbc0SCarson Labrado                                                              satelliteInfo);
746e002dbc0SCarson Labrado             return;
747e002dbc0SCarson Labrado         }
748e002dbc0SCarson Labrado 
74939662a3bSEd Tanous         const boost::urls::segments_view urlSegments = thisReq.url().segments();
7507c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
7514a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
7524a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
7537c4c52cbSCarson Labrado 
7547c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
7557c4c52cbSCarson Labrado         it++;
7567c4c52cbSCarson Labrado         it++;
7577c4c52cbSCarson Labrado         for (; it != end; it++)
75846a81465SCarson Labrado         {
7597c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
7607c4c52cbSCarson Labrado                                    currentUrl.buffer()))
7617c4c52cbSCarson Labrado             {
7627c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
7637c4c52cbSCarson Labrado                 // must contain an aggregation prefix
7647c4c52cbSCarson Labrado                 findSatellite(thisReq, asyncResp, satelliteInfo, *it);
76546a81465SCarson Labrado                 return;
76646a81465SCarson Labrado             }
76746a81465SCarson Labrado 
7687c4c52cbSCarson Labrado             currentUrl.segments().push_back(*it);
76946a81465SCarson Labrado         }
770db18fc98SCarson Labrado 
771db18fc98SCarson Labrado         // We shouldn't reach this point since we should've hit one of the
772db18fc98SCarson Labrado         // previous exits
773db18fc98SCarson Labrado         messages::internalError(asyncResp->res);
77446a81465SCarson Labrado     }
77546a81465SCarson Labrado 
77646a81465SCarson Labrado     // Attempt to forward a request to the satellite BMC associated with the
77746a81465SCarson Labrado     // prefix.
77846a81465SCarson Labrado     void forwardRequest(
77946a81465SCarson Labrado         const crow::Request& thisReq,
78046a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
78146a81465SCarson Labrado         const std::string& prefix,
78246a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
78346a81465SCarson Labrado     {
78446a81465SCarson Labrado         const auto& sat = satelliteInfo.find(prefix);
78546a81465SCarson Labrado         if (sat == satelliteInfo.end())
78646a81465SCarson Labrado         {
78746a81465SCarson Labrado             // Realistically this shouldn't get called since we perform an
78846a81465SCarson Labrado             // earlier check to make sure the prefix exists
78962598e31SEd Tanous             BMCWEB_LOG_ERROR("Unrecognized satellite prefix \"{}\"", prefix);
79046a81465SCarson Labrado             return;
79146a81465SCarson Labrado         }
79246a81465SCarson Labrado 
79346a81465SCarson Labrado         // We need to strip the prefix from the request's path
794a716aa74SEd Tanous         boost::urls::url targetURI(thisReq.target());
795a716aa74SEd Tanous         std::string path = thisReq.url().path();
796a716aa74SEd Tanous         size_t pos = path.find(prefix + "_");
79746a81465SCarson Labrado         if (pos == std::string::npos)
79846a81465SCarson Labrado         {
79946a81465SCarson Labrado             // If this fails then something went wrong
80062598e31SEd Tanous             BMCWEB_LOG_ERROR("Error removing prefix \"{}_\" from request URI",
80162598e31SEd Tanous                              prefix);
80246a81465SCarson Labrado             messages::internalError(asyncResp->res);
80346a81465SCarson Labrado             return;
80446a81465SCarson Labrado         }
805a716aa74SEd Tanous         path.erase(pos, prefix.size() + 1);
80646a81465SCarson Labrado 
80746a81465SCarson Labrado         std::function<void(crow::Response&)> cb =
8081c0bb5c6SCarson Labrado             std::bind_front(processResponse, prefix, asyncResp);
80946a81465SCarson Labrado 
81027b0cf90SEd Tanous         std::string data = thisReq.body();
811a716aa74SEd Tanous         boost::urls::url url(sat->second);
812a716aa74SEd Tanous         url.set_path(path);
813a716aa74SEd Tanous         if (targetURI.has_query())
814a716aa74SEd Tanous         {
815a716aa74SEd Tanous             url.set_query(targetURI.query());
816a716aa74SEd Tanous         }
81719bb362bSEd Tanous         client.sendDataWithCallback(std::move(data), url,
81819bb362bSEd Tanous                                     ensuressl::VerifyCertificate::Verify,
81919bb362bSEd Tanous                                     thisReq.fields(), thisReq.method(), cb);
82046a81465SCarson Labrado     }
82146a81465SCarson Labrado 
8224c30e226SCarson Labrado     // Forward a request for a collection URI to each known satellite BMC
8234c30e226SCarson Labrado     void forwardCollectionRequests(
8244c30e226SCarson Labrado         const crow::Request& thisReq,
8254c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
8264c30e226SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
8274c30e226SCarson Labrado     {
8284c30e226SCarson Labrado         for (const auto& sat : satelliteInfo)
8294c30e226SCarson Labrado         {
8304c30e226SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
8314c30e226SCarson Labrado                 processCollectionResponse, sat.first, asyncResp);
8324c30e226SCarson Labrado 
833a716aa74SEd Tanous             boost::urls::url url(sat.second);
834a716aa74SEd Tanous             url.set_path(thisReq.url().path());
835a716aa74SEd Tanous             if (thisReq.url().has_query())
836a716aa74SEd Tanous             {
837a716aa74SEd Tanous                 url.set_query(thisReq.url().query());
838a716aa74SEd Tanous             }
83927b0cf90SEd Tanous             std::string data = thisReq.body();
84019bb362bSEd Tanous             client.sendDataWithCallback(std::move(data), url,
84119bb362bSEd Tanous                                         ensuressl::VerifyCertificate::Verify,
84219bb362bSEd Tanous                                         thisReq.fields(), thisReq.method(), cb);
8434c30e226SCarson Labrado         }
8444c30e226SCarson Labrado     }
8454c30e226SCarson Labrado 
846e002dbc0SCarson Labrado     // Forward request for a URI that is uptree of a top level collection to
847e002dbc0SCarson Labrado     // each known satellite BMC
848e002dbc0SCarson Labrado     void forwardContainsSubordinateRequests(
849e002dbc0SCarson Labrado         const crow::Request& thisReq,
850e002dbc0SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
851e002dbc0SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
852e002dbc0SCarson Labrado     {
853e002dbc0SCarson Labrado         for (const auto& sat : satelliteInfo)
854e002dbc0SCarson Labrado         {
855e002dbc0SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
856e002dbc0SCarson Labrado                 processContainsSubordinateResponse, sat.first, asyncResp);
857e002dbc0SCarson Labrado 
858e002dbc0SCarson Labrado             // will ignore an expanded resource in the response if that resource
859e002dbc0SCarson Labrado             // is not already supported by the aggregating BMC
860e002dbc0SCarson Labrado             // TODO: Improve the processing so that we don't have to strip query
861e002dbc0SCarson Labrado             // params in this specific case
862a716aa74SEd Tanous             boost::urls::url url(sat.second);
863a716aa74SEd Tanous             url.set_path(thisReq.url().path());
864a716aa74SEd Tanous 
86527b0cf90SEd Tanous             std::string data = thisReq.body();
866a716aa74SEd Tanous 
86719bb362bSEd Tanous             client.sendDataWithCallback(std::move(data), url,
86819bb362bSEd Tanous                                         ensuressl::VerifyCertificate::Verify,
86919bb362bSEd Tanous                                         thisReq.fields(), thisReq.method(), cb);
870e002dbc0SCarson Labrado         }
871e002dbc0SCarson Labrado     }
872e002dbc0SCarson Labrado 
87332d7d8ebSCarson Labrado   public:
8749838eb20SEd Tanous     explicit RedfishAggregator() :
8759838eb20SEd Tanous         client(getIoContext(),
876f8ca6d79SEd Tanous                std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy()))
877f8ca6d79SEd Tanous     {
878f8ca6d79SEd Tanous         getSatelliteConfigs(constructorCallback);
879f8ca6d79SEd Tanous     }
88032d7d8ebSCarson Labrado     RedfishAggregator(const RedfishAggregator&) = delete;
88132d7d8ebSCarson Labrado     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
88232d7d8ebSCarson Labrado     RedfishAggregator(RedfishAggregator&&) = delete;
88332d7d8ebSCarson Labrado     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
88432d7d8ebSCarson Labrado     ~RedfishAggregator() = default;
88532d7d8ebSCarson Labrado 
8869838eb20SEd Tanous     static RedfishAggregator& getInstance()
88732d7d8ebSCarson Labrado     {
8889838eb20SEd Tanous         static RedfishAggregator handler;
88932d7d8ebSCarson Labrado         return handler;
89032d7d8ebSCarson Labrado     }
89132d7d8ebSCarson Labrado 
89266620686SEd Tanous     // Aggregation sources from AggregationCollection
89366620686SEd Tanous     std::unordered_map<std::string, boost::urls::url> currentAggregationSources;
89466620686SEd Tanous 
8958b2521a5SCarson Labrado     // Polls D-Bus to get all available satellite config information
8968b2521a5SCarson Labrado     // Expects a handler which interacts with the returned configs
89766620686SEd Tanous     void getSatelliteConfigs(
8988b2521a5SCarson Labrado         std::function<
8998b2521a5SCarson Labrado             void(const boost::system::error_code&,
9008b2521a5SCarson Labrado                  const std::unordered_map<std::string, boost::urls::url>&)>
90166620686SEd Tanous             handler) const
9028b2521a5SCarson Labrado     {
90362598e31SEd Tanous         BMCWEB_LOG_DEBUG("Gathering satellite configs");
90466620686SEd Tanous 
90566620686SEd Tanous         std::unordered_map<std::string, boost::urls::url> satelliteInfo(
90666620686SEd Tanous             currentAggregationSources);
90766620686SEd Tanous 
9085eb468daSGeorge Liu         sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
9095eb468daSGeorge Liu         dbus::utility::getManagedObjects(
9105eb468daSGeorge Liu             "xyz.openbmc_project.EntityManager", path,
91166620686SEd Tanous             [handler{std::move(handler)},
91266620686SEd Tanous              satelliteInfo = std::move(satelliteInfo)](
9138b2521a5SCarson Labrado                 const boost::system::error_code& ec,
91466620686SEd Tanous                 const dbus::utility::ManagedObjectType& objects) mutable {
9158b2521a5SCarson Labrado                 if (ec)
9168b2521a5SCarson Labrado                 {
91762598e31SEd Tanous                     BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(),
91862598e31SEd Tanous                                      ec.message());
9198b2521a5SCarson Labrado                     handler(ec, satelliteInfo);
9208b2521a5SCarson Labrado                     return;
9218b2521a5SCarson Labrado                 }
9228b2521a5SCarson Labrado 
9238b2521a5SCarson Labrado                 // Maps a chosen alias representing a satellite BMC to a url
9248b2521a5SCarson Labrado                 // containing the information required to create a http
9258b2521a5SCarson Labrado                 // connection to the satellite
9268b2521a5SCarson Labrado                 findSatelliteConfigs(objects, satelliteInfo);
9278b2521a5SCarson Labrado 
9288b2521a5SCarson Labrado                 if (!satelliteInfo.empty())
9298b2521a5SCarson Labrado                 {
93062598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
93162598e31SEd Tanous                         "Redfish Aggregation enabled with {} satellite BMCs",
93262598e31SEd Tanous                         std::to_string(satelliteInfo.size()));
9338b2521a5SCarson Labrado                 }
9348b2521a5SCarson Labrado                 else
9358b2521a5SCarson Labrado                 {
93662598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
93762598e31SEd Tanous                         "No satellite BMCs detected.  Redfish Aggregation not enabled");
9388b2521a5SCarson Labrado                 }
9398b2521a5SCarson Labrado                 handler(ec, satelliteInfo);
9405eb468daSGeorge Liu             });
9418b2521a5SCarson Labrado     }
9428b2521a5SCarson Labrado 
94346a81465SCarson Labrado     // Processes the response returned by a satellite BMC and loads its
94446a81465SCarson Labrado     // contents into asyncResp
945504af5a0SPatrick Williams     static void processResponse(
946504af5a0SPatrick Williams         std::string_view prefix,
9471c0bb5c6SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
94846a81465SCarson Labrado         crow::Response& resp)
94946a81465SCarson Labrado     {
95043e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
95143e14d38SCarson Labrado         // overwrite the response headers in that case
95246b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
95346b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
95443e14d38SCarson Labrado         {
95543e14d38SCarson Labrado             asyncResp->res.result(resp.result());
95643e14d38SCarson Labrado             return;
95743e14d38SCarson Labrado         }
95843e14d38SCarson Labrado 
95932d7d8ebSCarson Labrado         // We want to attempt prefix fixing regardless of response code
96046a81465SCarson Labrado         // The resp will not have a json component
96146a81465SCarson Labrado         // We need to create a json from resp's stringResponse
96218f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
96346a81465SCarson Labrado         {
964bd79bce8SPatrick Williams             nlohmann::json jsonVal =
965bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
96646a81465SCarson Labrado             if (jsonVal.is_discarded())
96746a81465SCarson Labrado             {
96862598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
96946a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
97046a81465SCarson Labrado                 return;
97146a81465SCarson Labrado             }
97246a81465SCarson Labrado 
97362598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
97446a81465SCarson Labrado 
9751c0bb5c6SCarson Labrado             addPrefixes(jsonVal, prefix);
9761c0bb5c6SCarson Labrado 
97762598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
9781c0bb5c6SCarson Labrado 
97946a81465SCarson Labrado             asyncResp->res.result(resp.result());
98046a81465SCarson Labrado             asyncResp->res.jsonValue = std::move(jsonVal);
98146a81465SCarson Labrado 
98262598e31SEd Tanous             BMCWEB_LOG_DEBUG("Finished writing asyncResp");
98346a81465SCarson Labrado         }
98446a81465SCarson Labrado         else
98546a81465SCarson Labrado         {
9860af78d5aSKhang Kieu             // We allow any Content-Type that is not "application/json" now
9870af78d5aSKhang Kieu             asyncResp->res.result(resp.result());
98827b0cf90SEd Tanous             asyncResp->res.copyBody(resp);
98946a81465SCarson Labrado         }
9900af78d5aSKhang Kieu         addAggregatedHeaders(asyncResp->res, resp, prefix);
99146a81465SCarson Labrado     }
99246a81465SCarson Labrado 
9934c30e226SCarson Labrado     // Processes the collection response returned by a satellite BMC and merges
9944c30e226SCarson Labrado     // its "@odata.id" values
9954c30e226SCarson Labrado     static void processCollectionResponse(
9964c30e226SCarson Labrado         const std::string& prefix,
9974c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
9984c30e226SCarson Labrado         crow::Response& resp)
9994c30e226SCarson Labrado     {
100043e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
100143e14d38SCarson Labrado         // overwrite the response headers in that case
100246b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
100346b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
100443e14d38SCarson Labrado         {
100543e14d38SCarson Labrado             return;
100643e14d38SCarson Labrado         }
100743e14d38SCarson Labrado 
10084c30e226SCarson Labrado         if (resp.resultInt() != 200)
10094c30e226SCarson Labrado         {
101062598e31SEd Tanous             BMCWEB_LOG_DEBUG(
101162598e31SEd Tanous                 "Collection resource does not exist in satellite BMC \"{}\"",
101262598e31SEd Tanous                 prefix);
10134c30e226SCarson Labrado             // Return the error if we haven't had any successes
10144c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10154c30e226SCarson Labrado             {
101646b30283SCarson Labrado                 asyncResp->res.result(resp.result());
101727b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
10184c30e226SCarson Labrado             }
10194c30e226SCarson Labrado             return;
10204c30e226SCarson Labrado         }
10214c30e226SCarson Labrado 
10224c30e226SCarson Labrado         // The resp will not have a json component
10234c30e226SCarson Labrado         // We need to create a json from resp's stringResponse
102418f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
10254c30e226SCarson Labrado         {
1026bd79bce8SPatrick Williams             nlohmann::json jsonVal =
1027bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
10284c30e226SCarson Labrado             if (jsonVal.is_discarded())
10294c30e226SCarson Labrado             {
103062598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
10314c30e226SCarson Labrado 
10324c30e226SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
103346b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
10344c30e226SCarson Labrado                 {
10354c30e226SCarson Labrado                     messages::operationFailed(asyncResp->res);
10364c30e226SCarson Labrado                 }
10374c30e226SCarson Labrado                 return;
10384c30e226SCarson Labrado             }
10394c30e226SCarson Labrado 
104062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
10414c30e226SCarson Labrado 
10424c30e226SCarson Labrado             // Now we need to add the prefix to the URIs contained in the
10434c30e226SCarson Labrado             // response.
10444c30e226SCarson Labrado             addPrefixes(jsonVal, prefix);
10454c30e226SCarson Labrado 
104662598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
10474c30e226SCarson Labrado 
10484c30e226SCarson Labrado             // If this resource collection does not exist on the aggregating bmc
10494c30e226SCarson Labrado             // and has not already been added from processing the response from
10504c30e226SCarson Labrado             // a different satellite then we need to completely overwrite
10514c30e226SCarson Labrado             // asyncResp
10524c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10534c30e226SCarson Labrado             {
10544c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
10554c30e226SCarson Labrado                 // "Members" array
10564c30e226SCarson Labrado                 if ((!jsonVal.contains("Members")) &&
10574c30e226SCarson Labrado                     (!jsonVal["Members"].is_array()))
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                     "Collection does not exist, overwriting asyncResp");
10664c30e226SCarson Labrado                 asyncResp->res.result(resp.result());
10674c30e226SCarson Labrado                 asyncResp->res.jsonValue = std::move(jsonVal);
106843e14d38SCarson Labrado                 asyncResp->res.addHeader("Content-Type", "application/json");
10694c30e226SCarson Labrado 
107062598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Finished overwriting asyncResp");
10714c30e226SCarson Labrado             }
10724c30e226SCarson Labrado             else
10734c30e226SCarson Labrado             {
10744c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
10754c30e226SCarson Labrado                 // "Members" array
10764c30e226SCarson Labrado                 if ((!asyncResp->res.jsonValue.contains("Members")) &&
10774c30e226SCarson Labrado                     (!asyncResp->res.jsonValue["Members"].is_array()))
10784c30e226SCarson Labrado 
10794c30e226SCarson Labrado                 {
108062598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
108162598e31SEd Tanous                         "Skipping aggregating unsupported resource");
10824c30e226SCarson Labrado                     return;
10834c30e226SCarson Labrado                 }
10844c30e226SCarson Labrado 
108562598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
108662598e31SEd Tanous                     "Adding aggregated resources from \"{}\" to collection",
108762598e31SEd Tanous                     prefix);
10884c30e226SCarson Labrado 
10894c30e226SCarson Labrado                 // TODO: This is a potential race condition with multiple
10904c30e226SCarson Labrado                 // satellites and the aggregating bmc attempting to write to
10914c30e226SCarson Labrado                 // update this array.  May need to cascade calls to the next
10924c30e226SCarson Labrado                 // satellite at the end of this function.
10934c30e226SCarson Labrado                 // This is presumably not a concern when there is only a single
10944c30e226SCarson Labrado                 // satellite since the aggregating bmc should have completed
10954c30e226SCarson Labrado                 // before the response is received from the satellite.
10964c30e226SCarson Labrado 
10974c30e226SCarson Labrado                 auto& members = asyncResp->res.jsonValue["Members"];
10984c30e226SCarson Labrado                 auto& satMembers = jsonVal["Members"];
10994c30e226SCarson Labrado                 for (auto& satMem : satMembers)
11004c30e226SCarson Labrado                 {
1101b2ba3072SPatrick Williams                     members.emplace_back(std::move(satMem));
11024c30e226SCarson Labrado                 }
11034c30e226SCarson Labrado                 asyncResp->res.jsonValue["Members@odata.count"] =
11044c30e226SCarson Labrado                     members.size();
11054c30e226SCarson Labrado 
11064c30e226SCarson Labrado                 // TODO: Do we need to sort() after updating the array?
11074c30e226SCarson Labrado             }
11084c30e226SCarson Labrado         }
11094c30e226SCarson Labrado         else
11104c30e226SCarson Labrado         {
111162598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
111262598e31SEd Tanous                              prefix);
111343e14d38SCarson Labrado             // We received a response that was not a json.
111446b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses
111546b30283SCarson Labrado             // and if the resource collection does not already exist on the
111646b30283SCarson Labrado             // aggregating BMC
111746b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
11184c30e226SCarson Labrado             {
11194c30e226SCarson Labrado                 messages::operationFailed(asyncResp->res);
11204c30e226SCarson Labrado             }
11214c30e226SCarson Labrado         }
11224c30e226SCarson Labrado     } // End processCollectionResponse()
11234c30e226SCarson Labrado 
112446b30283SCarson Labrado     // Processes the response returned by a satellite BMC and merges any
112546b30283SCarson Labrado     // properties whose "@odata.id" value is the URI or either a top level
112646b30283SCarson Labrado     // collection or is uptree from a top level collection
112746b30283SCarson Labrado     static void processContainsSubordinateResponse(
112846b30283SCarson Labrado         const std::string& prefix,
112946b30283SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
113046b30283SCarson Labrado         crow::Response& resp)
113146b30283SCarson Labrado     {
113246b30283SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
113346b30283SCarson Labrado         // overwrite the response headers in that case
113446b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
113546b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
113646b30283SCarson Labrado         {
113746b30283SCarson Labrado             return;
113846b30283SCarson Labrado         }
113946b30283SCarson Labrado 
114046b30283SCarson Labrado         if (resp.resultInt() != 200)
114146b30283SCarson Labrado         {
114262598e31SEd Tanous             BMCWEB_LOG_DEBUG(
114362598e31SEd Tanous                 "Resource uptree from Collection does not exist in satellite BMC \"{}\"",
114462598e31SEd Tanous                 prefix);
114546b30283SCarson Labrado             // Return the error if we haven't had any successes
114646b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
114746b30283SCarson Labrado             {
114846b30283SCarson Labrado                 asyncResp->res.result(resp.result());
114927b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
115046b30283SCarson Labrado             }
115146b30283SCarson Labrado             return;
115246b30283SCarson Labrado         }
115346b30283SCarson Labrado 
115446b30283SCarson Labrado         // The resp will not have a json component
115546b30283SCarson Labrado         // We need to create a json from resp's stringResponse
115618f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
115746b30283SCarson Labrado         {
115846b30283SCarson Labrado             bool addedLinks = false;
1159bd79bce8SPatrick Williams             nlohmann::json jsonVal =
1160bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
116146b30283SCarson Labrado             if (jsonVal.is_discarded())
116246b30283SCarson Labrado             {
116362598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
116446b30283SCarson Labrado 
116546b30283SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
116646b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
116746b30283SCarson Labrado                 {
116846b30283SCarson Labrado                     messages::operationFailed(asyncResp->res);
116946b30283SCarson Labrado                 }
117046b30283SCarson Labrado                 return;
117146b30283SCarson Labrado             }
117246b30283SCarson Labrado 
117362598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
117446b30283SCarson Labrado 
117546b30283SCarson Labrado             // Parse response and add properties missing from the AsyncResp
117646b30283SCarson Labrado             // Valid properties will be of the form <property>.@odata.id and
117746b30283SCarson Labrado             // @odata.id is a <URI>.  In other words, the json should contain
117846b30283SCarson Labrado             // multiple properties such that
117946b30283SCarson Labrado             // {"<property>":{"@odata.id": "<URI>"}}
118046b30283SCarson Labrado             nlohmann::json::object_t* object =
118146b30283SCarson Labrado                 jsonVal.get_ptr<nlohmann::json::object_t*>();
118246b30283SCarson Labrado             if (object == nullptr)
118346b30283SCarson Labrado             {
118462598e31SEd Tanous                 BMCWEB_LOG_ERROR("Parsed JSON was not an object?");
118546b30283SCarson Labrado                 return;
118646b30283SCarson Labrado             }
118746b30283SCarson Labrado 
118846b30283SCarson Labrado             for (std::pair<const std::string, nlohmann::json>& prop : *object)
118946b30283SCarson Labrado             {
119046b30283SCarson Labrado                 if (!prop.second.contains("@odata.id"))
119146b30283SCarson Labrado                 {
119246b30283SCarson Labrado                     continue;
119346b30283SCarson Labrado                 }
119446b30283SCarson Labrado 
119546b30283SCarson Labrado                 std::string* strValue =
119646b30283SCarson Labrado                     prop.second["@odata.id"].get_ptr<std::string*>();
119746b30283SCarson Labrado                 if (strValue == nullptr)
119846b30283SCarson Labrado                 {
119962598e31SEd Tanous                     BMCWEB_LOG_CRITICAL("Field wasn't a string????");
120046b30283SCarson Labrado                     continue;
120146b30283SCarson Labrado                 }
120246b30283SCarson Labrado                 if (!searchCollectionsArray(*strValue, SearchType::CollOrCon))
120346b30283SCarson Labrado                 {
120446b30283SCarson Labrado                     continue;
120546b30283SCarson Labrado                 }
120646b30283SCarson Labrado 
120746b30283SCarson Labrado                 addedLinks = true;
120846b30283SCarson Labrado                 if (!asyncResp->res.jsonValue.contains(prop.first))
120946b30283SCarson Labrado                 {
121046b30283SCarson Labrado                     // Only add the property if it did not already exist
121162598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Adding link for {} from BMC {}",
121262598e31SEd Tanous                                      *strValue, prefix);
121346b30283SCarson Labrado                     asyncResp->res.jsonValue[prop.first]["@odata.id"] =
121446b30283SCarson Labrado                         *strValue;
121546b30283SCarson Labrado                     continue;
121646b30283SCarson Labrado                 }
121746b30283SCarson Labrado             }
121846b30283SCarson Labrado 
121946b30283SCarson Labrado             // If we added links to a previously unsuccessful (non-200) response
122046b30283SCarson Labrado             // then we need to make sure the response contains the bare minimum
122146b30283SCarson Labrado             // amount of additional information that we'd expect to have been
122246b30283SCarson Labrado             // populated.
122346b30283SCarson Labrado             if (addedLinks && (asyncResp->res.resultInt() != 200))
122446b30283SCarson Labrado             {
122546b30283SCarson Labrado                 // This resource didn't locally exist or an error
122646b30283SCarson Labrado                 // occurred while generating the response.  Remove any
122746b30283SCarson Labrado                 // error messages and update the error code.
122846b30283SCarson Labrado                 asyncResp->res.jsonValue.erase(
122946b30283SCarson Labrado                     asyncResp->res.jsonValue.find("error"));
123046b30283SCarson Labrado                 asyncResp->res.result(resp.result());
123146b30283SCarson Labrado 
123246b30283SCarson Labrado                 const auto& it1 = object->find("@odata.id");
123346b30283SCarson Labrado                 if (it1 != object->end())
123446b30283SCarson Labrado                 {
123546b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.id"] = (it1->second);
123646b30283SCarson Labrado                 }
123746b30283SCarson Labrado                 const auto& it2 = object->find("@odata.type");
123846b30283SCarson Labrado                 if (it2 != object->end())
123946b30283SCarson Labrado                 {
124046b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.type"] = (it2->second);
124146b30283SCarson Labrado                 }
124246b30283SCarson Labrado                 const auto& it3 = object->find("Id");
124346b30283SCarson Labrado                 if (it3 != object->end())
124446b30283SCarson Labrado                 {
124546b30283SCarson Labrado                     asyncResp->res.jsonValue["Id"] = (it3->second);
124646b30283SCarson Labrado                 }
124746b30283SCarson Labrado                 const auto& it4 = object->find("Name");
124846b30283SCarson Labrado                 if (it4 != object->end())
124946b30283SCarson Labrado                 {
125046b30283SCarson Labrado                     asyncResp->res.jsonValue["Name"] = (it4->second);
125146b30283SCarson Labrado                 }
125246b30283SCarson Labrado             }
125346b30283SCarson Labrado         }
125446b30283SCarson Labrado         else
125546b30283SCarson Labrado         {
125662598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
125762598e31SEd Tanous                              prefix);
125846b30283SCarson Labrado             // We received as response that was not a json
125946b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses,
126046b30283SCarson Labrado             // and if the resource does not already exist on the aggregating BMC
126146b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
126246b30283SCarson Labrado             {
126346b30283SCarson Labrado                 messages::operationFailed(asyncResp->res);
126446b30283SCarson Labrado             }
126546b30283SCarson Labrado         }
126646b30283SCarson Labrado     }
126746b30283SCarson Labrado 
126805916cefSCarson Labrado     // Entry point to Redfish Aggregation
126905916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
127005916cefSCarson Labrado     // request
127166620686SEd Tanous     Result beginAggregation(const crow::Request& thisReq,
127205916cefSCarson Labrado                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
127305916cefSCarson Labrado     {
127405916cefSCarson Labrado         using crow::utility::OrMorePaths;
127505916cefSCarson Labrado         using crow::utility::readUrlSegments;
12764a7fbefdSEd Tanous         boost::urls::url_view url = thisReq.url();
1277411e6a11SCarson Labrado 
1278411e6a11SCarson Labrado         // We don't need to aggregate JsonSchemas due to potential issues such
1279411e6a11SCarson Labrado         // as version mismatches between aggregator and satellite BMCs.  For
1280411e6a11SCarson Labrado         // now assume that the aggregator has all the schemas and versions that
1281411e6a11SCarson Labrado         // the aggregated server has.
1282411e6a11SCarson Labrado         if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas",
1283411e6a11SCarson Labrado                                            crow::utility::OrMorePaths()))
1284411e6a11SCarson Labrado         {
1285411e6a11SCarson Labrado             return Result::LocalHandle;
1286411e6a11SCarson Labrado         }
1287411e6a11SCarson Labrado 
12887c4c52cbSCarson Labrado         // The first two segments should be "/redfish/v1".  We need to check
12897c4c52cbSCarson Labrado         // that before we can search topCollections
12907c4c52cbSCarson Labrado         if (!crow::utility::readUrlSegments(url, "redfish", "v1",
12917c4c52cbSCarson Labrado                                             crow::utility::OrMorePaths()))
129246a81465SCarson Labrado         {
129346a81465SCarson Labrado             return Result::LocalHandle;
129446a81465SCarson Labrado         }
129505916cefSCarson Labrado 
12967c4c52cbSCarson Labrado         // Parse the URI to see if it begins with a known top level collection
12977c4c52cbSCarson Labrado         // such as:
12987c4c52cbSCarson Labrado         // /redfish/v1/Chassis
12997c4c52cbSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory
13007c4c52cbSCarson Labrado         const boost::urls::segments_view urlSegments = url.segments();
13017c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
13024a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
13034a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
130405916cefSCarson Labrado 
13057c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
13067c4c52cbSCarson Labrado         it++;
13077c4c52cbSCarson Labrado         it++;
13087c4c52cbSCarson Labrado         for (; it != end; it++)
130905916cefSCarson Labrado         {
1310d4413c5bSGeorge Liu             const std::string& collectionItem = *it;
13117c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
13127c4c52cbSCarson Labrado                                    currentUrl.buffer()))
13137c4c52cbSCarson Labrado             {
13147c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
13157c4c52cbSCarson Labrado                 // might contain an aggregation prefix
1316*71526116SKamran Hasan                 if (segmentHasPrefix(collectionItem))
131705916cefSCarson Labrado                 {
131862598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Need to forward a request");
131905916cefSCarson Labrado 
132046a81465SCarson Labrado                     // Extract the prefix from the request's URI, retrieve the
13217c4c52cbSCarson Labrado                     // associated satellite config information, and then forward
13227c4c52cbSCarson Labrado                     // the request to that satellite.
13237c4c52cbSCarson Labrado                     startAggregation(AggregationType::Resource, thisReq,
13247c4c52cbSCarson Labrado                                      asyncResp);
132505916cefSCarson Labrado                     return Result::NoLocalHandle;
132605916cefSCarson Labrado                 }
13277c4c52cbSCarson Labrado 
13287c4c52cbSCarson Labrado                 // Handle collection URI with a trailing backslash
13297c4c52cbSCarson Labrado                 // e.g. /redfish/v1/Chassis/
13307c4c52cbSCarson Labrado                 it++;
13317c4c52cbSCarson Labrado                 if ((it == end) && collectionItem.empty())
13327c4c52cbSCarson Labrado                 {
13337c4c52cbSCarson Labrado                     startAggregation(AggregationType::Collection, thisReq,
13347c4c52cbSCarson Labrado                                      asyncResp);
13357c4c52cbSCarson Labrado                 }
13367c4c52cbSCarson Labrado 
13377c4c52cbSCarson Labrado                 // We didn't recognize the prefix or it's a collection with a
13387c4c52cbSCarson Labrado                 // trailing "/".  In both cases we still want to locally handle
13397c4c52cbSCarson Labrado                 // the request
13407c4c52cbSCarson Labrado                 return Result::LocalHandle;
13417c4c52cbSCarson Labrado             }
13427c4c52cbSCarson Labrado 
13437c4c52cbSCarson Labrado             currentUrl.segments().push_back(collectionItem);
13447c4c52cbSCarson Labrado         }
13457c4c52cbSCarson Labrado 
13467c4c52cbSCarson Labrado         // If we made it here then currentUrl could contain a top level
13477c4c52cbSCarson Labrado         // collection URI without a trailing "/", e.g. /redfish/v1/Chassis
13487c4c52cbSCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
13497c4c52cbSCarson Labrado                                currentUrl.buffer()))
13507c4c52cbSCarson Labrado         {
13517c4c52cbSCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
135205916cefSCarson Labrado             return Result::LocalHandle;
135305916cefSCarson Labrado         }
135405916cefSCarson Labrado 
1355e002dbc0SCarson Labrado         // If nothing else then the request could be for a resource which has a
1356e002dbc0SCarson Labrado         // top level collection as a subordinate
1357e002dbc0SCarson Labrado         if (searchCollectionsArray(url.path(), SearchType::ContainsSubordinate))
1358e002dbc0SCarson Labrado         {
1359e002dbc0SCarson Labrado             startAggregation(AggregationType::ContainsSubordinate, thisReq,
1360e002dbc0SCarson Labrado                              asyncResp);
1361e002dbc0SCarson Labrado             return Result::LocalHandle;
1362e002dbc0SCarson Labrado         }
1363e002dbc0SCarson Labrado 
136462598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation not required for {}", url.buffer());
136505916cefSCarson Labrado         return Result::LocalHandle;
136605916cefSCarson Labrado     }
1367*71526116SKamran Hasan 
1368*71526116SKamran Hasan     // Check if the given URL segment matches with any satellite prefix
1369*71526116SKamran Hasan     // Assumes the given segment starts with <prefix>_
1370*71526116SKamran Hasan     bool segmentHasPrefix(const std::string& urlSegment) const
1371*71526116SKamran Hasan     {
1372*71526116SKamran Hasan         // TODO: handle this better
1373*71526116SKamran Hasan         // For now 5B247A_ wont be in the currentAggregationSources map so
1374*71526116SKamran Hasan         // check explicitly for now
1375*71526116SKamran Hasan         if (urlSegment.starts_with("5B247A_"))
1376*71526116SKamran Hasan         {
1377*71526116SKamran Hasan             return true;
1378*71526116SKamran Hasan         }
1379*71526116SKamran Hasan 
1380*71526116SKamran Hasan         // Find the first underscore
1381*71526116SKamran Hasan         std::size_t underscorePos = urlSegment.find('_');
1382*71526116SKamran Hasan         if (underscorePos == std::string::npos)
1383*71526116SKamran Hasan         {
1384*71526116SKamran Hasan             return false; // No underscore, can't be a satellite prefix
1385*71526116SKamran Hasan         }
1386*71526116SKamran Hasan 
1387*71526116SKamran Hasan         // Extract the prefix
1388*71526116SKamran Hasan         std::string prefix = urlSegment.substr(0, underscorePos);
1389*71526116SKamran Hasan 
1390*71526116SKamran Hasan         // Check if this prefix exists
1391*71526116SKamran Hasan         return currentAggregationSources.contains(prefix);
1392*71526116SKamran Hasan     }
13937fb33566SCarson Labrado };
13947fb33566SCarson Labrado 
13957fb33566SCarson Labrado } // namespace redfish
1396