xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 5a3d934a15a7ba2e2c5269143d95df6581e793c6)
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 
462*5a3d934aSRohit 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;
475*5a3d934aSRohit 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             }
529*5a3d934aSRohit PAI             else if (prop.first == "Name")
530*5a3d934aSRohit PAI             {
531*5a3d934aSRohit PAI                 const std::string* propVal =
532*5a3d934aSRohit PAI                     std::get_if<std::string>(&prop.second);
533*5a3d934aSRohit PAI                 if (propVal != nullptr && !propVal->empty())
534*5a3d934aSRohit PAI                 {
535*5a3d934aSRohit PAI                     prefix = *propVal;
536*5a3d934aSRohit PAI                     BMCWEB_LOG_DEBUG("Using Name property {} as prefix",
537*5a3d934aSRohit PAI                                      prefix);
538*5a3d934aSRohit PAI                 }
539*5a3d934aSRohit PAI                 else
540*5a3d934aSRohit PAI                 {
541*5a3d934aSRohit PAI                     BMCWEB_LOG_DEBUG(
542*5a3d934aSRohit PAI                         "Invalid or empty Name property, invalid satellite config");
543*5a3d934aSRohit PAI                     return;
544*5a3d934aSRohit PAI                 }
545*5a3d934aSRohit 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         {
551*5a3d934aSRohit PAI             BMCWEB_LOG_ERROR("Satellite config {} missing Host", prefix);
5527fb33566SCarson Labrado             return;
5537fb33566SCarson Labrado         }
5547fb33566SCarson Labrado 
5557fb33566SCarson Labrado         if (!url.has_port())
5567fb33566SCarson Labrado         {
557*5a3d934aSRohit PAI             BMCWEB_LOG_ERROR("Satellite config {} missing Port", prefix);
5587fb33566SCarson Labrado             return;
5597fb33566SCarson Labrado         }
5607fb33566SCarson Labrado 
5617fb33566SCarson Labrado         if (!url.has_scheme())
5627fb33566SCarson Labrado         {
563*5a3d934aSRohit PAI             BMCWEB_LOG_ERROR("Satellite config {} missing AuthType", prefix);
5647fb33566SCarson Labrado             return;
5657fb33566SCarson Labrado         }
5667fb33566SCarson Labrado 
5677fb33566SCarson Labrado         std::string resultString;
568*5a3d934aSRohit 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 
578*5a3d934aSRohit 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 
590504af5a0SPatrick Williams     static void startAggregation(
591504af5a0SPatrick Williams         AggregationType aggType, const crow::Request& thisReq,
59246a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
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 
8928b2521a5SCarson Labrado     // Polls D-Bus to get all available satellite config information
8938b2521a5SCarson Labrado     // Expects a handler which interacts with the returned configs
8948b2521a5SCarson Labrado     static void getSatelliteConfigs(
8958b2521a5SCarson Labrado         std::function<
8968b2521a5SCarson Labrado             void(const boost::system::error_code&,
8978b2521a5SCarson Labrado                  const std::unordered_map<std::string, boost::urls::url>&)>
8988b2521a5SCarson Labrado             handler)
8998b2521a5SCarson Labrado     {
90062598e31SEd Tanous         BMCWEB_LOG_DEBUG("Gathering satellite configs");
9015eb468daSGeorge Liu         sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
9025eb468daSGeorge Liu         dbus::utility::getManagedObjects(
9035eb468daSGeorge Liu             "xyz.openbmc_project.EntityManager", path,
9048b2521a5SCarson Labrado             [handler{std::move(handler)}](
9058b2521a5SCarson Labrado                 const boost::system::error_code& ec,
9068b2521a5SCarson Labrado                 const dbus::utility::ManagedObjectType& objects) {
9078b2521a5SCarson Labrado                 std::unordered_map<std::string, boost::urls::url> satelliteInfo;
9088b2521a5SCarson Labrado                 if (ec)
9098b2521a5SCarson Labrado                 {
91062598e31SEd Tanous                     BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(),
91162598e31SEd Tanous                                      ec.message());
9128b2521a5SCarson Labrado                     handler(ec, satelliteInfo);
9138b2521a5SCarson Labrado                     return;
9148b2521a5SCarson Labrado                 }
9158b2521a5SCarson Labrado 
9168b2521a5SCarson Labrado                 // Maps a chosen alias representing a satellite BMC to a url
9178b2521a5SCarson Labrado                 // containing the information required to create a http
9188b2521a5SCarson Labrado                 // connection to the satellite
9198b2521a5SCarson Labrado                 findSatelliteConfigs(objects, satelliteInfo);
9208b2521a5SCarson Labrado 
9218b2521a5SCarson Labrado                 if (!satelliteInfo.empty())
9228b2521a5SCarson Labrado                 {
92362598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
92462598e31SEd Tanous                         "Redfish Aggregation enabled with {} satellite BMCs",
92562598e31SEd Tanous                         std::to_string(satelliteInfo.size()));
9268b2521a5SCarson Labrado                 }
9278b2521a5SCarson Labrado                 else
9288b2521a5SCarson Labrado                 {
92962598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
93062598e31SEd Tanous                         "No satellite BMCs detected.  Redfish Aggregation not enabled");
9318b2521a5SCarson Labrado                 }
9328b2521a5SCarson Labrado                 handler(ec, satelliteInfo);
9335eb468daSGeorge Liu             });
9348b2521a5SCarson Labrado     }
9358b2521a5SCarson Labrado 
93646a81465SCarson Labrado     // Processes the response returned by a satellite BMC and loads its
93746a81465SCarson Labrado     // contents into asyncResp
938504af5a0SPatrick Williams     static void processResponse(
939504af5a0SPatrick Williams         std::string_view prefix,
9401c0bb5c6SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
94146a81465SCarson Labrado         crow::Response& resp)
94246a81465SCarson Labrado     {
94343e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
94443e14d38SCarson Labrado         // overwrite the response headers in that case
94546b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
94646b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
94743e14d38SCarson Labrado         {
94843e14d38SCarson Labrado             asyncResp->res.result(resp.result());
94943e14d38SCarson Labrado             return;
95043e14d38SCarson Labrado         }
95143e14d38SCarson Labrado 
95232d7d8ebSCarson Labrado         // We want to attempt prefix fixing regardless of response code
95346a81465SCarson Labrado         // The resp will not have a json component
95446a81465SCarson Labrado         // We need to create a json from resp's stringResponse
95518f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
95646a81465SCarson Labrado         {
957bd79bce8SPatrick Williams             nlohmann::json jsonVal =
958bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
95946a81465SCarson Labrado             if (jsonVal.is_discarded())
96046a81465SCarson Labrado             {
96162598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
96246a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
96346a81465SCarson Labrado                 return;
96446a81465SCarson Labrado             }
96546a81465SCarson Labrado 
96662598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
96746a81465SCarson Labrado 
9681c0bb5c6SCarson Labrado             addPrefixes(jsonVal, prefix);
9691c0bb5c6SCarson Labrado 
97062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
9711c0bb5c6SCarson Labrado 
97246a81465SCarson Labrado             asyncResp->res.result(resp.result());
97346a81465SCarson Labrado             asyncResp->res.jsonValue = std::move(jsonVal);
97446a81465SCarson Labrado 
97562598e31SEd Tanous             BMCWEB_LOG_DEBUG("Finished writing asyncResp");
97646a81465SCarson Labrado         }
97746a81465SCarson Labrado         else
97846a81465SCarson Labrado         {
9790af78d5aSKhang Kieu             // We allow any Content-Type that is not "application/json" now
9800af78d5aSKhang Kieu             asyncResp->res.result(resp.result());
98127b0cf90SEd Tanous             asyncResp->res.copyBody(resp);
98246a81465SCarson Labrado         }
9830af78d5aSKhang Kieu         addAggregatedHeaders(asyncResp->res, resp, prefix);
98446a81465SCarson Labrado     }
98546a81465SCarson Labrado 
9864c30e226SCarson Labrado     // Processes the collection response returned by a satellite BMC and merges
9874c30e226SCarson Labrado     // its "@odata.id" values
9884c30e226SCarson Labrado     static void processCollectionResponse(
9894c30e226SCarson Labrado         const std::string& prefix,
9904c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
9914c30e226SCarson Labrado         crow::Response& resp)
9924c30e226SCarson Labrado     {
99343e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
99443e14d38SCarson Labrado         // overwrite the response headers in that case
99546b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
99646b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
99743e14d38SCarson Labrado         {
99843e14d38SCarson Labrado             return;
99943e14d38SCarson Labrado         }
100043e14d38SCarson Labrado 
10014c30e226SCarson Labrado         if (resp.resultInt() != 200)
10024c30e226SCarson Labrado         {
100362598e31SEd Tanous             BMCWEB_LOG_DEBUG(
100462598e31SEd Tanous                 "Collection resource does not exist in satellite BMC \"{}\"",
100562598e31SEd Tanous                 prefix);
10064c30e226SCarson Labrado             // Return the error if we haven't had any successes
10074c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10084c30e226SCarson Labrado             {
100946b30283SCarson Labrado                 asyncResp->res.result(resp.result());
101027b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
10114c30e226SCarson Labrado             }
10124c30e226SCarson Labrado             return;
10134c30e226SCarson Labrado         }
10144c30e226SCarson Labrado 
10154c30e226SCarson Labrado         // The resp will not have a json component
10164c30e226SCarson Labrado         // We need to create a json from resp's stringResponse
101718f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
10184c30e226SCarson Labrado         {
1019bd79bce8SPatrick Williams             nlohmann::json jsonVal =
1020bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
10214c30e226SCarson Labrado             if (jsonVal.is_discarded())
10224c30e226SCarson Labrado             {
102362598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
10244c30e226SCarson Labrado 
10254c30e226SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
102646b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
10274c30e226SCarson Labrado                 {
10284c30e226SCarson Labrado                     messages::operationFailed(asyncResp->res);
10294c30e226SCarson Labrado                 }
10304c30e226SCarson Labrado                 return;
10314c30e226SCarson Labrado             }
10324c30e226SCarson Labrado 
103362598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
10344c30e226SCarson Labrado 
10354c30e226SCarson Labrado             // Now we need to add the prefix to the URIs contained in the
10364c30e226SCarson Labrado             // response.
10374c30e226SCarson Labrado             addPrefixes(jsonVal, prefix);
10384c30e226SCarson Labrado 
103962598e31SEd Tanous             BMCWEB_LOG_DEBUG("Added prefix to parsed satellite response");
10404c30e226SCarson Labrado 
10414c30e226SCarson Labrado             // If this resource collection does not exist on the aggregating bmc
10424c30e226SCarson Labrado             // and has not already been added from processing the response from
10434c30e226SCarson Labrado             // a different satellite then we need to completely overwrite
10444c30e226SCarson Labrado             // asyncResp
10454c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
10464c30e226SCarson Labrado             {
10474c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
10484c30e226SCarson Labrado                 // "Members" array
10494c30e226SCarson Labrado                 if ((!jsonVal.contains("Members")) &&
10504c30e226SCarson Labrado                     (!jsonVal["Members"].is_array()))
10514c30e226SCarson Labrado                 {
105262598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
105362598e31SEd Tanous                         "Skipping aggregating unsupported resource");
10544c30e226SCarson Labrado                     return;
10554c30e226SCarson Labrado                 }
10564c30e226SCarson Labrado 
105762598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
105862598e31SEd Tanous                     "Collection does not exist, overwriting asyncResp");
10594c30e226SCarson Labrado                 asyncResp->res.result(resp.result());
10604c30e226SCarson Labrado                 asyncResp->res.jsonValue = std::move(jsonVal);
106143e14d38SCarson Labrado                 asyncResp->res.addHeader("Content-Type", "application/json");
10624c30e226SCarson Labrado 
106362598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Finished overwriting asyncResp");
10644c30e226SCarson Labrado             }
10654c30e226SCarson Labrado             else
10664c30e226SCarson Labrado             {
10674c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
10684c30e226SCarson Labrado                 // "Members" array
10694c30e226SCarson Labrado                 if ((!asyncResp->res.jsonValue.contains("Members")) &&
10704c30e226SCarson Labrado                     (!asyncResp->res.jsonValue["Members"].is_array()))
10714c30e226SCarson Labrado 
10724c30e226SCarson Labrado                 {
107362598e31SEd Tanous                     BMCWEB_LOG_DEBUG(
107462598e31SEd Tanous                         "Skipping aggregating unsupported resource");
10754c30e226SCarson Labrado                     return;
10764c30e226SCarson Labrado                 }
10774c30e226SCarson Labrado 
107862598e31SEd Tanous                 BMCWEB_LOG_DEBUG(
107962598e31SEd Tanous                     "Adding aggregated resources from \"{}\" to collection",
108062598e31SEd Tanous                     prefix);
10814c30e226SCarson Labrado 
10824c30e226SCarson Labrado                 // TODO: This is a potential race condition with multiple
10834c30e226SCarson Labrado                 // satellites and the aggregating bmc attempting to write to
10844c30e226SCarson Labrado                 // update this array.  May need to cascade calls to the next
10854c30e226SCarson Labrado                 // satellite at the end of this function.
10864c30e226SCarson Labrado                 // This is presumably not a concern when there is only a single
10874c30e226SCarson Labrado                 // satellite since the aggregating bmc should have completed
10884c30e226SCarson Labrado                 // before the response is received from the satellite.
10894c30e226SCarson Labrado 
10904c30e226SCarson Labrado                 auto& members = asyncResp->res.jsonValue["Members"];
10914c30e226SCarson Labrado                 auto& satMembers = jsonVal["Members"];
10924c30e226SCarson Labrado                 for (auto& satMem : satMembers)
10934c30e226SCarson Labrado                 {
1094b2ba3072SPatrick Williams                     members.emplace_back(std::move(satMem));
10954c30e226SCarson Labrado                 }
10964c30e226SCarson Labrado                 asyncResp->res.jsonValue["Members@odata.count"] =
10974c30e226SCarson Labrado                     members.size();
10984c30e226SCarson Labrado 
10994c30e226SCarson Labrado                 // TODO: Do we need to sort() after updating the array?
11004c30e226SCarson Labrado             }
11014c30e226SCarson Labrado         }
11024c30e226SCarson Labrado         else
11034c30e226SCarson Labrado         {
110462598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
110562598e31SEd Tanous                              prefix);
110643e14d38SCarson Labrado             // We received a response that was not a json.
110746b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses
110846b30283SCarson Labrado             // and if the resource collection does not already exist on the
110946b30283SCarson Labrado             // aggregating BMC
111046b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
11114c30e226SCarson Labrado             {
11124c30e226SCarson Labrado                 messages::operationFailed(asyncResp->res);
11134c30e226SCarson Labrado             }
11144c30e226SCarson Labrado         }
11154c30e226SCarson Labrado     } // End processCollectionResponse()
11164c30e226SCarson Labrado 
111746b30283SCarson Labrado     // Processes the response returned by a satellite BMC and merges any
111846b30283SCarson Labrado     // properties whose "@odata.id" value is the URI or either a top level
111946b30283SCarson Labrado     // collection or is uptree from a top level collection
112046b30283SCarson Labrado     static void processContainsSubordinateResponse(
112146b30283SCarson Labrado         const std::string& prefix,
112246b30283SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
112346b30283SCarson Labrado         crow::Response& resp)
112446b30283SCarson Labrado     {
112546b30283SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
112646b30283SCarson Labrado         // overwrite the response headers in that case
112746b30283SCarson Labrado         if ((resp.result() == boost::beast::http::status::too_many_requests) ||
112846b30283SCarson Labrado             (resp.result() == boost::beast::http::status::bad_gateway))
112946b30283SCarson Labrado         {
113046b30283SCarson Labrado             return;
113146b30283SCarson Labrado         }
113246b30283SCarson Labrado 
113346b30283SCarson Labrado         if (resp.resultInt() != 200)
113446b30283SCarson Labrado         {
113562598e31SEd Tanous             BMCWEB_LOG_DEBUG(
113662598e31SEd Tanous                 "Resource uptree from Collection does not exist in satellite BMC \"{}\"",
113762598e31SEd Tanous                 prefix);
113846b30283SCarson Labrado             // Return the error if we haven't had any successes
113946b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
114046b30283SCarson Labrado             {
114146b30283SCarson Labrado                 asyncResp->res.result(resp.result());
114227b0cf90SEd Tanous                 asyncResp->res.copyBody(resp);
114346b30283SCarson Labrado             }
114446b30283SCarson Labrado             return;
114546b30283SCarson Labrado         }
114646b30283SCarson Labrado 
114746b30283SCarson Labrado         // The resp will not have a json component
114846b30283SCarson Labrado         // We need to create a json from resp's stringResponse
114918f8f608SEd Tanous         if (isJsonContentType(resp.getHeaderValue("Content-Type")))
115046b30283SCarson Labrado         {
115146b30283SCarson Labrado             bool addedLinks = false;
1152bd79bce8SPatrick Williams             nlohmann::json jsonVal =
1153bd79bce8SPatrick Williams                 nlohmann::json::parse(*resp.body(), nullptr, false);
115446b30283SCarson Labrado             if (jsonVal.is_discarded())
115546b30283SCarson Labrado             {
115662598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
115746b30283SCarson Labrado 
115846b30283SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
115946b30283SCarson Labrado                 if (asyncResp->res.resultInt() != 200)
116046b30283SCarson Labrado                 {
116146b30283SCarson Labrado                     messages::operationFailed(asyncResp->res);
116246b30283SCarson Labrado                 }
116346b30283SCarson Labrado                 return;
116446b30283SCarson Labrado             }
116546b30283SCarson Labrado 
116662598e31SEd Tanous             BMCWEB_LOG_DEBUG("Successfully parsed satellite response");
116746b30283SCarson Labrado 
116846b30283SCarson Labrado             // Parse response and add properties missing from the AsyncResp
116946b30283SCarson Labrado             // Valid properties will be of the form <property>.@odata.id and
117046b30283SCarson Labrado             // @odata.id is a <URI>.  In other words, the json should contain
117146b30283SCarson Labrado             // multiple properties such that
117246b30283SCarson Labrado             // {"<property>":{"@odata.id": "<URI>"}}
117346b30283SCarson Labrado             nlohmann::json::object_t* object =
117446b30283SCarson Labrado                 jsonVal.get_ptr<nlohmann::json::object_t*>();
117546b30283SCarson Labrado             if (object == nullptr)
117646b30283SCarson Labrado             {
117762598e31SEd Tanous                 BMCWEB_LOG_ERROR("Parsed JSON was not an object?");
117846b30283SCarson Labrado                 return;
117946b30283SCarson Labrado             }
118046b30283SCarson Labrado 
118146b30283SCarson Labrado             for (std::pair<const std::string, nlohmann::json>& prop : *object)
118246b30283SCarson Labrado             {
118346b30283SCarson Labrado                 if (!prop.second.contains("@odata.id"))
118446b30283SCarson Labrado                 {
118546b30283SCarson Labrado                     continue;
118646b30283SCarson Labrado                 }
118746b30283SCarson Labrado 
118846b30283SCarson Labrado                 std::string* strValue =
118946b30283SCarson Labrado                     prop.second["@odata.id"].get_ptr<std::string*>();
119046b30283SCarson Labrado                 if (strValue == nullptr)
119146b30283SCarson Labrado                 {
119262598e31SEd Tanous                     BMCWEB_LOG_CRITICAL("Field wasn't a string????");
119346b30283SCarson Labrado                     continue;
119446b30283SCarson Labrado                 }
119546b30283SCarson Labrado                 if (!searchCollectionsArray(*strValue, SearchType::CollOrCon))
119646b30283SCarson Labrado                 {
119746b30283SCarson Labrado                     continue;
119846b30283SCarson Labrado                 }
119946b30283SCarson Labrado 
120046b30283SCarson Labrado                 addedLinks = true;
120146b30283SCarson Labrado                 if (!asyncResp->res.jsonValue.contains(prop.first))
120246b30283SCarson Labrado                 {
120346b30283SCarson Labrado                     // Only add the property if it did not already exist
120462598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Adding link for {} from BMC {}",
120562598e31SEd Tanous                                      *strValue, prefix);
120646b30283SCarson Labrado                     asyncResp->res.jsonValue[prop.first]["@odata.id"] =
120746b30283SCarson Labrado                         *strValue;
120846b30283SCarson Labrado                     continue;
120946b30283SCarson Labrado                 }
121046b30283SCarson Labrado             }
121146b30283SCarson Labrado 
121246b30283SCarson Labrado             // If we added links to a previously unsuccessful (non-200) response
121346b30283SCarson Labrado             // then we need to make sure the response contains the bare minimum
121446b30283SCarson Labrado             // amount of additional information that we'd expect to have been
121546b30283SCarson Labrado             // populated.
121646b30283SCarson Labrado             if (addedLinks && (asyncResp->res.resultInt() != 200))
121746b30283SCarson Labrado             {
121846b30283SCarson Labrado                 // This resource didn't locally exist or an error
121946b30283SCarson Labrado                 // occurred while generating the response.  Remove any
122046b30283SCarson Labrado                 // error messages and update the error code.
122146b30283SCarson Labrado                 asyncResp->res.jsonValue.erase(
122246b30283SCarson Labrado                     asyncResp->res.jsonValue.find("error"));
122346b30283SCarson Labrado                 asyncResp->res.result(resp.result());
122446b30283SCarson Labrado 
122546b30283SCarson Labrado                 const auto& it1 = object->find("@odata.id");
122646b30283SCarson Labrado                 if (it1 != object->end())
122746b30283SCarson Labrado                 {
122846b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.id"] = (it1->second);
122946b30283SCarson Labrado                 }
123046b30283SCarson Labrado                 const auto& it2 = object->find("@odata.type");
123146b30283SCarson Labrado                 if (it2 != object->end())
123246b30283SCarson Labrado                 {
123346b30283SCarson Labrado                     asyncResp->res.jsonValue["@odata.type"] = (it2->second);
123446b30283SCarson Labrado                 }
123546b30283SCarson Labrado                 const auto& it3 = object->find("Id");
123646b30283SCarson Labrado                 if (it3 != object->end())
123746b30283SCarson Labrado                 {
123846b30283SCarson Labrado                     asyncResp->res.jsonValue["Id"] = (it3->second);
123946b30283SCarson Labrado                 }
124046b30283SCarson Labrado                 const auto& it4 = object->find("Name");
124146b30283SCarson Labrado                 if (it4 != object->end())
124246b30283SCarson Labrado                 {
124346b30283SCarson Labrado                     asyncResp->res.jsonValue["Name"] = (it4->second);
124446b30283SCarson Labrado                 }
124546b30283SCarson Labrado             }
124646b30283SCarson Labrado         }
124746b30283SCarson Labrado         else
124846b30283SCarson Labrado         {
124962598e31SEd Tanous             BMCWEB_LOG_ERROR("Received unparsable response from \"{}\"",
125062598e31SEd Tanous                              prefix);
125146b30283SCarson Labrado             // We received as response that was not a json
125246b30283SCarson Labrado             // Notify the user only if we did not receive any valid responses,
125346b30283SCarson Labrado             // and if the resource does not already exist on the aggregating BMC
125446b30283SCarson Labrado             if (asyncResp->res.resultInt() != 200)
125546b30283SCarson Labrado             {
125646b30283SCarson Labrado                 messages::operationFailed(asyncResp->res);
125746b30283SCarson Labrado             }
125846b30283SCarson Labrado         }
125946b30283SCarson Labrado     }
126046b30283SCarson Labrado 
126105916cefSCarson Labrado     // Entry point to Redfish Aggregation
126205916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
126305916cefSCarson Labrado     // request
1264504af5a0SPatrick Williams     static Result beginAggregation(
1265504af5a0SPatrick Williams         const crow::Request& thisReq,
126605916cefSCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
126705916cefSCarson Labrado     {
126805916cefSCarson Labrado         using crow::utility::OrMorePaths;
126905916cefSCarson Labrado         using crow::utility::readUrlSegments;
12704a7fbefdSEd Tanous         boost::urls::url_view url = thisReq.url();
1271411e6a11SCarson Labrado 
1272411e6a11SCarson Labrado         // We don't need to aggregate JsonSchemas due to potential issues such
1273411e6a11SCarson Labrado         // as version mismatches between aggregator and satellite BMCs.  For
1274411e6a11SCarson Labrado         // now assume that the aggregator has all the schemas and versions that
1275411e6a11SCarson Labrado         // the aggregated server has.
1276411e6a11SCarson Labrado         if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas",
1277411e6a11SCarson Labrado                                            crow::utility::OrMorePaths()))
1278411e6a11SCarson Labrado         {
1279411e6a11SCarson Labrado             return Result::LocalHandle;
1280411e6a11SCarson Labrado         }
1281411e6a11SCarson Labrado 
12827c4c52cbSCarson Labrado         // The first two segments should be "/redfish/v1".  We need to check
12837c4c52cbSCarson Labrado         // that before we can search topCollections
12847c4c52cbSCarson Labrado         if (!crow::utility::readUrlSegments(url, "redfish", "v1",
12857c4c52cbSCarson Labrado                                             crow::utility::OrMorePaths()))
128646a81465SCarson Labrado         {
128746a81465SCarson Labrado             return Result::LocalHandle;
128846a81465SCarson Labrado         }
128905916cefSCarson Labrado 
12907c4c52cbSCarson Labrado         // Parse the URI to see if it begins with a known top level collection
12917c4c52cbSCarson Labrado         // such as:
12927c4c52cbSCarson Labrado         // /redfish/v1/Chassis
12937c4c52cbSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory
12947c4c52cbSCarson Labrado         const boost::urls::segments_view urlSegments = url.segments();
12957c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
12964a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator it = urlSegments.begin();
12974a7fbefdSEd Tanous         boost::urls::segments_view::const_iterator end = urlSegments.end();
129805916cefSCarson Labrado 
12997c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
13007c4c52cbSCarson Labrado         it++;
13017c4c52cbSCarson Labrado         it++;
13027c4c52cbSCarson Labrado         for (; it != end; it++)
130305916cefSCarson Labrado         {
1304d4413c5bSGeorge Liu             const std::string& collectionItem = *it;
13057c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
13067c4c52cbSCarson Labrado                                    currentUrl.buffer()))
13077c4c52cbSCarson Labrado             {
13087c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
13097c4c52cbSCarson Labrado                 // might contain an aggregation prefix
13108b2521a5SCarson Labrado                 // TODO: This needs to be rethought when we can support multiple
13118b2521a5SCarson Labrado                 // satellites due to
13128b2521a5SCarson Labrado                 // /redfish/v1/AggregationService/AggregationSources/5B247A
13138b2521a5SCarson Labrado                 // being a local resource describing the satellite
13148b2521a5SCarson Labrado                 if (collectionItem.starts_with("5B247A_"))
131505916cefSCarson Labrado                 {
131662598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Need to forward a request");
131705916cefSCarson Labrado 
131846a81465SCarson Labrado                     // Extract the prefix from the request's URI, retrieve the
13197c4c52cbSCarson Labrado                     // associated satellite config information, and then forward
13207c4c52cbSCarson Labrado                     // the request to that satellite.
13217c4c52cbSCarson Labrado                     startAggregation(AggregationType::Resource, thisReq,
13227c4c52cbSCarson Labrado                                      asyncResp);
132305916cefSCarson Labrado                     return Result::NoLocalHandle;
132405916cefSCarson Labrado                 }
13257c4c52cbSCarson Labrado 
13267c4c52cbSCarson Labrado                 // Handle collection URI with a trailing backslash
13277c4c52cbSCarson Labrado                 // e.g. /redfish/v1/Chassis/
13287c4c52cbSCarson Labrado                 it++;
13297c4c52cbSCarson Labrado                 if ((it == end) && collectionItem.empty())
13307c4c52cbSCarson Labrado                 {
13317c4c52cbSCarson Labrado                     startAggregation(AggregationType::Collection, thisReq,
13327c4c52cbSCarson Labrado                                      asyncResp);
13337c4c52cbSCarson Labrado                 }
13347c4c52cbSCarson Labrado 
13357c4c52cbSCarson Labrado                 // We didn't recognize the prefix or it's a collection with a
13367c4c52cbSCarson Labrado                 // trailing "/".  In both cases we still want to locally handle
13377c4c52cbSCarson Labrado                 // the request
13387c4c52cbSCarson Labrado                 return Result::LocalHandle;
13397c4c52cbSCarson Labrado             }
13407c4c52cbSCarson Labrado 
13417c4c52cbSCarson Labrado             currentUrl.segments().push_back(collectionItem);
13427c4c52cbSCarson Labrado         }
13437c4c52cbSCarson Labrado 
13447c4c52cbSCarson Labrado         // If we made it here then currentUrl could contain a top level
13457c4c52cbSCarson Labrado         // collection URI without a trailing "/", e.g. /redfish/v1/Chassis
13467c4c52cbSCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
13477c4c52cbSCarson Labrado                                currentUrl.buffer()))
13487c4c52cbSCarson Labrado         {
13497c4c52cbSCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
135005916cefSCarson Labrado             return Result::LocalHandle;
135105916cefSCarson Labrado         }
135205916cefSCarson Labrado 
1353e002dbc0SCarson Labrado         // If nothing else then the request could be for a resource which has a
1354e002dbc0SCarson Labrado         // top level collection as a subordinate
1355e002dbc0SCarson Labrado         if (searchCollectionsArray(url.path(), SearchType::ContainsSubordinate))
1356e002dbc0SCarson Labrado         {
1357e002dbc0SCarson Labrado             startAggregation(AggregationType::ContainsSubordinate, thisReq,
1358e002dbc0SCarson Labrado                              asyncResp);
1359e002dbc0SCarson Labrado             return Result::LocalHandle;
1360e002dbc0SCarson Labrado         }
1361e002dbc0SCarson Labrado 
136262598e31SEd Tanous         BMCWEB_LOG_DEBUG("Aggregation not required for {}", url.buffer());
136305916cefSCarson Labrado         return Result::LocalHandle;
136405916cefSCarson Labrado     }
13657fb33566SCarson Labrado };
13667fb33566SCarson Labrado 
13677fb33566SCarson Labrado } // namespace redfish
1368