xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 43e14d38178f6e9a362d2f066a05fecd41503d84)
17fb33566SCarson Labrado #pragma once
27fb33566SCarson Labrado 
33ccb3adbSEd Tanous #include "aggregation_utils.hpp"
43ccb3adbSEd Tanous #include "dbus_utility.hpp"
53ccb3adbSEd Tanous #include "error_messages.hpp"
63ccb3adbSEd Tanous #include "http_client.hpp"
73ccb3adbSEd Tanous #include "http_connection.hpp"
83ccb3adbSEd Tanous 
9411e6a11SCarson Labrado #include <boost/algorithm/string/predicate.hpp>
107fb33566SCarson Labrado 
117e8890c5SCarson Labrado #include <array>
127e8890c5SCarson Labrado 
137fb33566SCarson Labrado namespace redfish
147fb33566SCarson Labrado {
157fb33566SCarson Labrado 
1605916cefSCarson Labrado enum class Result
1705916cefSCarson Labrado {
1805916cefSCarson Labrado     LocalHandle,
1905916cefSCarson Labrado     NoLocalHandle
2005916cefSCarson Labrado };
2105916cefSCarson Labrado 
227e8890c5SCarson Labrado // clang-format off
237e8890c5SCarson Labrado // These are all of the properties as of version 2022.2 of the Redfish Resource
247e8890c5SCarson Labrado // and Schema Guide whose Type is "string (URI)" and the name does not end in a
257e8890c5SCarson Labrado // case-insensitive form of "uri".  That version of the schema is associated
267e8890c5SCarson Labrado // with version 1.16.0 of the Redfish Specification.  Going forward, new URI
277e8890c5SCarson Labrado // properties should end in URI so this list should not need to be maintained as
287e8890c5SCarson Labrado // the spec is updated.  NOTE: These have been pre-sorted in order to be
297e8890c5SCarson Labrado // compatible with binary search
307e8890c5SCarson Labrado constexpr std::array nonUriProperties{
317e8890c5SCarson Labrado     "@Redfish.ActionInfo",
327e8890c5SCarson Labrado     // "@odata.context", // We can't fix /redfish/v1/$metadata URIs
337e8890c5SCarson Labrado     "@odata.id",
347e8890c5SCarson Labrado     // "Destination", // Only used by EventService and won't be a Redfish URI
357e8890c5SCarson Labrado     // "HostName", // Isn't actually a Redfish URI
367e8890c5SCarson Labrado     "Image",
377e8890c5SCarson Labrado     "MetricProperty",
3832d7d8ebSCarson Labrado     // "OriginOfCondition", // Is URI when in request, but is object in response
397e8890c5SCarson Labrado     "TaskMonitor",
407e8890c5SCarson Labrado     "target", // normal string, but target URI for POST to invoke an action
417e8890c5SCarson Labrado };
427e8890c5SCarson Labrado // clang-format on
437e8890c5SCarson Labrado 
447e8890c5SCarson Labrado // Determines if the passed property contains a URI.  Those property names
457e8890c5SCarson Labrado // either end with a case-insensitive version of "uri" or are specifically
467e8890c5SCarson Labrado // defined in the above array.
477e8890c5SCarson Labrado inline bool isPropertyUri(const std::string_view propertyName)
487e8890c5SCarson Labrado {
497e8890c5SCarson Labrado     return boost::iends_with(propertyName, "uri") ||
507e8890c5SCarson Labrado            std::binary_search(nonUriProperties.begin(), nonUriProperties.end(),
517e8890c5SCarson Labrado                               propertyName);
527e8890c5SCarson Labrado }
537e8890c5SCarson Labrado 
541c0bb5c6SCarson Labrado static void addPrefixToItem(nlohmann::json& item, std::string_view prefix)
551c0bb5c6SCarson Labrado {
561c0bb5c6SCarson Labrado     std::string* strValue = item.get_ptr<std::string*>();
571c0bb5c6SCarson Labrado     if (strValue == nullptr)
581c0bb5c6SCarson Labrado     {
591c0bb5c6SCarson Labrado         BMCWEB_LOG_CRITICAL << "Field wasn't a string????";
601c0bb5c6SCarson Labrado         return;
611c0bb5c6SCarson Labrado     }
621c0bb5c6SCarson Labrado     // Make sure the value is a properly formatted URI
631c0bb5c6SCarson Labrado     auto parsed = boost::urls::parse_relative_ref(*strValue);
641c0bb5c6SCarson Labrado     if (!parsed)
651c0bb5c6SCarson Labrado     {
661c0bb5c6SCarson Labrado         BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << *strValue;
671c0bb5c6SCarson Labrado         return;
681c0bb5c6SCarson Labrado     }
691c0bb5c6SCarson Labrado 
701c0bb5c6SCarson Labrado     boost::urls::url_view thisUrl = *parsed;
711c0bb5c6SCarson Labrado 
72411e6a11SCarson Labrado     // We don't need to aggregate JsonSchemas due to potential issues such as
73411e6a11SCarson Labrado     // version mismatches between aggregator and satellite BMCs.  For now
74411e6a11SCarson Labrado     // assume that the aggregator has all the schemas and versions that the
75411e6a11SCarson Labrado     // aggregated server has.
76411e6a11SCarson Labrado     if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas",
77411e6a11SCarson Labrado                                        crow::utility::OrMorePaths()))
78411e6a11SCarson Labrado     {
79411e6a11SCarson Labrado         BMCWEB_LOG_DEBUG << "Skipping JsonSchemas URI prefix fixing";
80411e6a11SCarson Labrado         return;
81411e6a11SCarson Labrado     }
82411e6a11SCarson Labrado 
8311987af6SCarson Labrado     // The first two segments should be "/redfish/v1".  We need to check that
8411987af6SCarson Labrado     // before we can search topCollections
8511987af6SCarson Labrado     if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
8611987af6SCarson Labrado                                         crow::utility::OrMorePaths()))
871c0bb5c6SCarson Labrado     {
881c0bb5c6SCarson Labrado         return;
891c0bb5c6SCarson Labrado     }
901c0bb5c6SCarson Labrado 
9111987af6SCarson Labrado     // Check array adding a segment each time until collection is identified
9211987af6SCarson Labrado     // Add prefix to segment after the collection
9311987af6SCarson Labrado     const boost::urls::segments_view urlSegments = thisUrl.segments();
9411987af6SCarson Labrado     bool addedPrefix = false;
9511987af6SCarson Labrado     boost::urls::url url("/");
9611987af6SCarson Labrado     boost::urls::segments_view::iterator it = urlSegments.begin();
9711987af6SCarson Labrado     const boost::urls::segments_view::const_iterator end = urlSegments.end();
9811987af6SCarson Labrado 
9911987af6SCarson Labrado     // Skip past the leading "/redfish/v1"
10011987af6SCarson Labrado     it++;
10111987af6SCarson Labrado     it++;
10211987af6SCarson Labrado     for (; it != end; it++)
1031c0bb5c6SCarson Labrado     {
10411987af6SCarson Labrado         // Trailing "/" will result in an empty segment.  In that case we need
10511987af6SCarson Labrado         // to return so we don't apply a prefix to top level collections such
10611987af6SCarson Labrado         // as "/redfish/v1/Chassis/"
10711987af6SCarson Labrado         if ((*it).empty())
10811987af6SCarson Labrado         {
109411e6a11SCarson Labrado             return;
1101c0bb5c6SCarson Labrado         }
1111c0bb5c6SCarson Labrado 
11211987af6SCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
11311987af6SCarson Labrado                                url.buffer()))
1141c0bb5c6SCarson Labrado         {
11511987af6SCarson Labrado             std::string collectionItem(prefix);
11611987af6SCarson Labrado             collectionItem += "_" + (*it);
11711987af6SCarson Labrado             url.segments().push_back(collectionItem);
11811987af6SCarson Labrado             it++;
11911987af6SCarson Labrado             addedPrefix = true;
12011987af6SCarson Labrado             break;
12111987af6SCarson Labrado         }
12211987af6SCarson Labrado 
12311987af6SCarson Labrado         url.segments().push_back(*it);
12411987af6SCarson Labrado     }
12511987af6SCarson Labrado 
12611987af6SCarson Labrado     // Finish constructing the URL here (if needed) to avoid additional checks
12711987af6SCarson Labrado     for (; it != end; it++)
12811987af6SCarson Labrado     {
12911987af6SCarson Labrado         url.segments().push_back(*it);
13011987af6SCarson Labrado     }
13111987af6SCarson Labrado 
13211987af6SCarson Labrado     if (addedPrefix)
13311987af6SCarson Labrado     {
13411987af6SCarson Labrado         url.segments().insert(url.segments().begin(), {"redfish", "v1"});
13511987af6SCarson Labrado         item = url;
1361c0bb5c6SCarson Labrado     }
1371c0bb5c6SCarson Labrado }
1381c0bb5c6SCarson Labrado 
1391c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for
1407e8890c5SCarson Labrado // an aggregated resource.
1411c0bb5c6SCarson Labrado static void addPrefixes(nlohmann::json& json, std::string_view prefix)
1421c0bb5c6SCarson Labrado {
1431c0bb5c6SCarson Labrado     nlohmann::json::object_t* object =
1441c0bb5c6SCarson Labrado         json.get_ptr<nlohmann::json::object_t*>();
1451c0bb5c6SCarson Labrado     if (object != nullptr)
1461c0bb5c6SCarson Labrado     {
1471c0bb5c6SCarson Labrado         for (std::pair<const std::string, nlohmann::json>& item : *object)
1481c0bb5c6SCarson Labrado         {
1497e8890c5SCarson Labrado             if (isPropertyUri(item.first))
1501c0bb5c6SCarson Labrado             {
1517e8890c5SCarson Labrado                 addPrefixToItem(item.second, prefix);
1521c0bb5c6SCarson Labrado                 continue;
1531c0bb5c6SCarson Labrado             }
1541c0bb5c6SCarson Labrado 
1551c0bb5c6SCarson Labrado             // Recusively parse the rest of the json
1561c0bb5c6SCarson Labrado             addPrefixes(item.second, prefix);
1571c0bb5c6SCarson Labrado         }
1581c0bb5c6SCarson Labrado         return;
1591c0bb5c6SCarson Labrado     }
1601c0bb5c6SCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
1611c0bb5c6SCarson Labrado     if (array != nullptr)
1621c0bb5c6SCarson Labrado     {
1631c0bb5c6SCarson Labrado         for (nlohmann::json& item : *array)
1641c0bb5c6SCarson Labrado         {
1651c0bb5c6SCarson Labrado             addPrefixes(item, prefix);
1661c0bb5c6SCarson Labrado         }
1671c0bb5c6SCarson Labrado     }
1681c0bb5c6SCarson Labrado }
1691c0bb5c6SCarson Labrado 
1707fb33566SCarson Labrado class RedfishAggregator
1717fb33566SCarson Labrado {
1727fb33566SCarson Labrado   private:
173a7a80296SCarson Labrado     const std::string retryPolicyName = "RedfishAggregation";
174ce969437SCarson Labrado     const std::string retryPolicyAction = "TerminateAfterRetries";
175ce969437SCarson Labrado     const uint32_t retryAttempts = 1;
176a7a80296SCarson Labrado     const uint32_t retryTimeoutInterval = 0;
17746a81465SCarson Labrado     const std::string id = "Aggregator";
178a7a80296SCarson Labrado 
1797fb33566SCarson Labrado     RedfishAggregator()
1807fb33566SCarson Labrado     {
1817fb33566SCarson Labrado         getSatelliteConfigs(constructorCallback);
182a7a80296SCarson Labrado 
183a7a80296SCarson Labrado         // Setup the retry policy to be used by Redfish Aggregation
184a7a80296SCarson Labrado         crow::HttpClient::getInstance().setRetryConfig(
185a7a80296SCarson Labrado             retryAttempts, retryTimeoutInterval, aggregationRetryHandler,
186a7a80296SCarson Labrado             retryPolicyName);
187ce969437SCarson Labrado         crow::HttpClient::getInstance().setRetryPolicy(retryPolicyAction,
188ce969437SCarson Labrado                                                        retryPolicyName);
1897fb33566SCarson Labrado     }
1907fb33566SCarson Labrado 
191a7a80296SCarson Labrado     static inline boost::system::error_code
192a7a80296SCarson Labrado         aggregationRetryHandler(unsigned int respCode)
193a7a80296SCarson Labrado     {
19432d7d8ebSCarson Labrado         // Allow all response codes because we want to surface any satellite
19532d7d8ebSCarson Labrado         // issue to the client
19632d7d8ebSCarson Labrado         BMCWEB_LOG_DEBUG << "Received " << respCode
19732d7d8ebSCarson Labrado                          << " response from satellite";
198a7a80296SCarson Labrado         return boost::system::errc::make_error_code(
199a7a80296SCarson Labrado             boost::system::errc::success);
2009fa6d147SNan Zhou     }
201a7a80296SCarson Labrado 
2027fb33566SCarson Labrado     // Dummy callback used by the Constructor so that it can report the number
2037fb33566SCarson Labrado     // of satellite configs when the class is first created
2047fb33566SCarson Labrado     static void constructorCallback(
2057fb33566SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
2067fb33566SCarson Labrado     {
2077fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << "There were "
2087fb33566SCarson Labrado                          << std::to_string(satelliteInfo.size())
2097fb33566SCarson Labrado                          << " satellite configs found at startup";
2107fb33566SCarson Labrado     }
2117fb33566SCarson Labrado 
2127fb33566SCarson Labrado     // Polls D-Bus to get all available satellite config information
2137fb33566SCarson Labrado     // Expects a handler which interacts with the returned configs
2147fb33566SCarson Labrado     static void getSatelliteConfigs(
2157fb33566SCarson Labrado         const std::function<void(
2167fb33566SCarson Labrado             const std::unordered_map<std::string, boost::urls::url>&)>& handler)
2177fb33566SCarson Labrado     {
2187fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << "Gathering satellite configs";
2197fb33566SCarson Labrado         crow::connections::systemBus->async_method_call(
2207fb33566SCarson Labrado             [handler](const boost::system::error_code ec,
2217fb33566SCarson Labrado                       const dbus::utility::ManagedObjectType& objects) {
2227fb33566SCarson Labrado             if (ec)
2237fb33566SCarson Labrado             {
224002d39b4SEd Tanous                 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", "
225002d39b4SEd Tanous                                  << ec.message();
2267fb33566SCarson Labrado                 return;
2277fb33566SCarson Labrado             }
2287fb33566SCarson Labrado 
2297fb33566SCarson Labrado             // Maps a chosen alias representing a satellite BMC to a url
2307fb33566SCarson Labrado             // containing the information required to create a http
2317fb33566SCarson Labrado             // connection to the satellite
2327fb33566SCarson Labrado             std::unordered_map<std::string, boost::urls::url> satelliteInfo;
2337fb33566SCarson Labrado 
2347fb33566SCarson Labrado             findSatelliteConfigs(objects, satelliteInfo);
2357fb33566SCarson Labrado 
2367fb33566SCarson Labrado             if (!satelliteInfo.empty())
2377fb33566SCarson Labrado             {
2387fb33566SCarson Labrado                 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with "
2397fb33566SCarson Labrado                                  << std::to_string(satelliteInfo.size())
2407fb33566SCarson Labrado                                  << " satellite BMCs";
2417fb33566SCarson Labrado             }
2427fb33566SCarson Labrado             else
2437fb33566SCarson Labrado             {
2447fb33566SCarson Labrado                 BMCWEB_LOG_DEBUG
2457fb33566SCarson Labrado                     << "No satellite BMCs detected.  Redfish Aggregation not enabled";
2467fb33566SCarson Labrado             }
2477fb33566SCarson Labrado             handler(satelliteInfo);
2487fb33566SCarson Labrado             },
249c106b67aSNan Zhou             "xyz.openbmc_project.EntityManager",
250c106b67aSNan Zhou             "/xyz/openbmc_project/inventory",
2517fb33566SCarson Labrado             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
2527fb33566SCarson Labrado     }
2537fb33566SCarson Labrado 
2547fb33566SCarson Labrado     // Search D-Bus objects for satellite config objects and add their
2557fb33566SCarson Labrado     // information if valid
2567fb33566SCarson Labrado     static void findSatelliteConfigs(
2577fb33566SCarson Labrado         const dbus::utility::ManagedObjectType& objects,
2587fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
2597fb33566SCarson Labrado     {
2607fb33566SCarson Labrado         for (const auto& objectPath : objects)
2617fb33566SCarson Labrado         {
2627fb33566SCarson Labrado             for (const auto& interface : objectPath.second)
2637fb33566SCarson Labrado             {
2647fb33566SCarson Labrado                 if (interface.first ==
2657fb33566SCarson Labrado                     "xyz.openbmc_project.Configuration.SatelliteController")
2667fb33566SCarson Labrado                 {
2677fb33566SCarson Labrado                     BMCWEB_LOG_DEBUG << "Found Satellite Controller at "
2687fb33566SCarson Labrado                                      << objectPath.first.str;
2697fb33566SCarson Labrado 
27005916cefSCarson Labrado                     if (!satelliteInfo.empty())
27105916cefSCarson Labrado                     {
27205916cefSCarson Labrado                         BMCWEB_LOG_ERROR
27305916cefSCarson Labrado                             << "Redfish Aggregation only supports one satellite!";
27405916cefSCarson Labrado                         BMCWEB_LOG_DEBUG << "Clearing all satellite data";
27505916cefSCarson Labrado                         satelliteInfo.clear();
27605916cefSCarson Labrado                         return;
27705916cefSCarson Labrado                     }
27805916cefSCarson Labrado 
27905916cefSCarson Labrado                     // For now assume there will only be one satellite config.
28005916cefSCarson Labrado                     // Assign it the name/prefix "5B247A"
28105916cefSCarson Labrado                     addSatelliteConfig("5B247A", interface.second,
28205916cefSCarson Labrado                                        satelliteInfo);
2837fb33566SCarson Labrado                 }
2847fb33566SCarson Labrado             }
2857fb33566SCarson Labrado         }
2867fb33566SCarson Labrado     }
2877fb33566SCarson Labrado 
2887fb33566SCarson Labrado     // Parse the properties of a satellite config object and add the
2897fb33566SCarson Labrado     // configuration if the properties are valid
2907fb33566SCarson Labrado     static void addSatelliteConfig(
29105916cefSCarson Labrado         const std::string& name,
2927fb33566SCarson Labrado         const dbus::utility::DBusPropertiesMap& properties,
2937fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
2947fb33566SCarson Labrado     {
2957fb33566SCarson Labrado         boost::urls::url url;
2967fb33566SCarson Labrado 
2977fb33566SCarson Labrado         for (const auto& prop : properties)
2987fb33566SCarson Labrado         {
29905916cefSCarson Labrado             if (prop.first == "Hostname")
3007fb33566SCarson Labrado             {
3017fb33566SCarson Labrado                 const std::string* propVal =
3027fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
3037fb33566SCarson Labrado                 if (propVal == nullptr)
3047fb33566SCarson Labrado                 {
3057fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid Hostname value";
3067fb33566SCarson Labrado                     return;
3077fb33566SCarson Labrado                 }
3087fb33566SCarson Labrado                 url.set_host(*propVal);
3097fb33566SCarson Labrado             }
3107fb33566SCarson Labrado 
3117fb33566SCarson Labrado             else if (prop.first == "Port")
3127fb33566SCarson Labrado             {
3137fb33566SCarson Labrado                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
3147fb33566SCarson Labrado                 if (propVal == nullptr)
3157fb33566SCarson Labrado                 {
3167fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid Port value";
3177fb33566SCarson Labrado                     return;
3187fb33566SCarson Labrado                 }
3197fb33566SCarson Labrado 
3207fb33566SCarson Labrado                 if (*propVal > std::numeric_limits<uint16_t>::max())
3217fb33566SCarson Labrado                 {
3227fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Port value out of range";
3237fb33566SCarson Labrado                     return;
3247fb33566SCarson Labrado                 }
325079360aeSEd Tanous                 url.set_port(std::to_string(static_cast<uint16_t>(*propVal)));
3267fb33566SCarson Labrado             }
3277fb33566SCarson Labrado 
3287fb33566SCarson Labrado             else if (prop.first == "AuthType")
3297fb33566SCarson Labrado             {
3307fb33566SCarson Labrado                 const std::string* propVal =
3317fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
3327fb33566SCarson Labrado                 if (propVal == nullptr)
3337fb33566SCarson Labrado                 {
3347fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid AuthType value";
3357fb33566SCarson Labrado                     return;
3367fb33566SCarson Labrado                 }
3377fb33566SCarson Labrado 
3387fb33566SCarson Labrado                 // For now assume authentication not required to communicate
3397fb33566SCarson Labrado                 // with the satellite BMC
3407fb33566SCarson Labrado                 if (*propVal != "None")
3417fb33566SCarson Labrado                 {
3427fb33566SCarson Labrado                     BMCWEB_LOG_ERROR
3437fb33566SCarson Labrado                         << "Unsupported AuthType value: " << *propVal
3447fb33566SCarson Labrado                         << ", only \"none\" is supported";
3457fb33566SCarson Labrado                     return;
3467fb33566SCarson Labrado                 }
3477fb33566SCarson Labrado                 url.set_scheme("http");
3487fb33566SCarson Labrado             }
3497fb33566SCarson Labrado         } // Finished reading properties
3507fb33566SCarson Labrado 
3517fb33566SCarson Labrado         // Make sure all required config information was made available
3527fb33566SCarson Labrado         if (url.host().empty())
3537fb33566SCarson Labrado         {
3547fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host";
3557fb33566SCarson Labrado             return;
3567fb33566SCarson Labrado         }
3577fb33566SCarson Labrado 
3587fb33566SCarson Labrado         if (!url.has_port())
3597fb33566SCarson Labrado         {
3607fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port";
3617fb33566SCarson Labrado             return;
3627fb33566SCarson Labrado         }
3637fb33566SCarson Labrado 
3647fb33566SCarson Labrado         if (!url.has_scheme())
3657fb33566SCarson Labrado         {
3667fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name
3677fb33566SCarson Labrado                              << " missing AuthType";
3687fb33566SCarson Labrado             return;
3697fb33566SCarson Labrado         }
3707fb33566SCarson Labrado 
3717fb33566SCarson Labrado         std::string resultString;
3727fb33566SCarson Labrado         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
3737fb33566SCarson Labrado         if (result.second)
3747fb33566SCarson Labrado         {
3757fb33566SCarson Labrado             resultString = "Added new satellite config ";
3767fb33566SCarson Labrado         }
3777fb33566SCarson Labrado         else
3787fb33566SCarson Labrado         {
3797fb33566SCarson Labrado             resultString = "Updated existing satellite config ";
3807fb33566SCarson Labrado         }
3817fb33566SCarson Labrado 
3827fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << resultString << name << " at "
3837fb33566SCarson Labrado                          << result.first->second.scheme() << "://"
3847fb33566SCarson Labrado                          << result.first->second.encoded_host_and_port();
3857fb33566SCarson Labrado     }
3867fb33566SCarson Labrado 
38746a81465SCarson Labrado     enum AggregationType
38846a81465SCarson Labrado     {
38946a81465SCarson Labrado         Collection,
39046a81465SCarson Labrado         Resource,
39146a81465SCarson Labrado     };
39246a81465SCarson Labrado 
39346a81465SCarson Labrado     static void
39446a81465SCarson Labrado         startAggregation(AggregationType isCollection,
39546a81465SCarson Labrado                          const crow::Request& thisReq,
39646a81465SCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
39746a81465SCarson Labrado     {
398db18fc98SCarson Labrado         if ((isCollection == AggregationType::Collection) &&
399db18fc98SCarson Labrado             (thisReq.method() != boost::beast::http::verb::get))
400db18fc98SCarson Labrado         {
401db18fc98SCarson Labrado             BMCWEB_LOG_DEBUG
402db18fc98SCarson Labrado                 << "Only aggregate GET requests to top level collections";
403db18fc98SCarson Labrado             return;
404db18fc98SCarson Labrado         }
405db18fc98SCarson Labrado 
40646a81465SCarson Labrado         // Create a copy of thisReq so we we can still locally process the req
40746a81465SCarson Labrado         std::error_code ec;
40846a81465SCarson Labrado         auto localReq = std::make_shared<crow::Request>(thisReq.req, ec);
40946a81465SCarson Labrado         if (ec)
41046a81465SCarson Labrado         {
41146a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Failed to create copy of request";
41246a81465SCarson Labrado             if (isCollection != AggregationType::Collection)
41346a81465SCarson Labrado             {
41446a81465SCarson Labrado                 messages::internalError(asyncResp->res);
41546a81465SCarson Labrado             }
41646a81465SCarson Labrado             return;
41746a81465SCarson Labrado         }
41846a81465SCarson Labrado 
41946a81465SCarson Labrado         getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection,
42046a81465SCarson Labrado                                             localReq, asyncResp));
42146a81465SCarson Labrado     }
42246a81465SCarson Labrado 
423db18fc98SCarson Labrado     static void findSatellite(
42446a81465SCarson Labrado         const crow::Request& req,
42546a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
42646a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo,
42746a81465SCarson Labrado         std::string_view memberName)
42846a81465SCarson Labrado     {
42946a81465SCarson Labrado         // Determine if the resource ID begins with a known prefix
43046a81465SCarson Labrado         for (const auto& satellite : satelliteInfo)
43146a81465SCarson Labrado         {
43246a81465SCarson Labrado             std::string targetPrefix = satellite.first;
43346a81465SCarson Labrado             targetPrefix += "_";
43446a81465SCarson Labrado             if (memberName.starts_with(targetPrefix))
43546a81465SCarson Labrado             {
43646a81465SCarson Labrado                 BMCWEB_LOG_DEBUG << "\"" << satellite.first
43746a81465SCarson Labrado                                  << "\" is a known prefix";
43846a81465SCarson Labrado 
43946a81465SCarson Labrado                 // Remove the known prefix from the request's URI and
44046a81465SCarson Labrado                 // then forward to the associated satellite BMC
44146a81465SCarson Labrado                 getInstance().forwardRequest(req, asyncResp, satellite.first,
44246a81465SCarson Labrado                                              satelliteInfo);
44346a81465SCarson Labrado                 return;
44446a81465SCarson Labrado             }
44546a81465SCarson Labrado         }
446db18fc98SCarson Labrado 
447db18fc98SCarson Labrado         // We didn't recognize the prefix and need to return a 404
44893f7a0d6SEd Tanous         std::string nameStr = req.urlView.segments().back();
449db18fc98SCarson Labrado         messages::resourceNotFound(asyncResp->res, "", nameStr);
45046a81465SCarson Labrado     }
45146a81465SCarson Labrado 
45246a81465SCarson Labrado     // Intended to handle an incoming request based on if Redfish Aggregation
45346a81465SCarson Labrado     // is enabled.  Forwards request to satellite BMC if it exists.
45446a81465SCarson Labrado     static void aggregateAndHandle(
45546a81465SCarson Labrado         AggregationType isCollection,
45646a81465SCarson Labrado         const std::shared_ptr<crow::Request>& sharedReq,
45746a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
45846a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
45946a81465SCarson Labrado     {
46046a81465SCarson Labrado         if (sharedReq == nullptr)
46146a81465SCarson Labrado         {
46246a81465SCarson Labrado             return;
46346a81465SCarson Labrado         }
464db18fc98SCarson Labrado 
465db18fc98SCarson Labrado         // No satellite configs means we don't need to keep attempting to
466db18fc98SCarson Labrado         // aggregate
467db18fc98SCarson Labrado         if (satelliteInfo.empty())
468db18fc98SCarson Labrado         {
469db18fc98SCarson Labrado             // For collections we'll also handle the request locally so we
470db18fc98SCarson Labrado             // don't need to write an error code
471db18fc98SCarson Labrado             if (isCollection == AggregationType::Resource)
472db18fc98SCarson Labrado             {
47393f7a0d6SEd Tanous                 std::string nameStr = sharedReq->urlView.segments().back();
474db18fc98SCarson Labrado                 messages::resourceNotFound(asyncResp->res, "", nameStr);
475db18fc98SCarson Labrado             }
476db18fc98SCarson Labrado             return;
477db18fc98SCarson Labrado         }
478db18fc98SCarson Labrado 
47946a81465SCarson Labrado         const crow::Request& thisReq = *sharedReq;
48046a81465SCarson Labrado         BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of "
48146a81465SCarson Labrado                          << thisReq.target();
48246a81465SCarson Labrado 
48346a81465SCarson Labrado         // We previously determined the request is for a collection.  No need to
48446a81465SCarson Labrado         // check again
48546a81465SCarson Labrado         if (isCollection == AggregationType::Collection)
48646a81465SCarson Labrado         {
48746a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Aggregating a collection";
4884c30e226SCarson Labrado             // We need to use a specific response handler and send the
4894c30e226SCarson Labrado             // request to all known satellites
4904c30e226SCarson Labrado             getInstance().forwardCollectionRequests(thisReq, asyncResp,
4914c30e226SCarson Labrado                                                     satelliteInfo);
49246a81465SCarson Labrado             return;
49346a81465SCarson Labrado         }
49446a81465SCarson Labrado 
4957c4c52cbSCarson Labrado         const boost::urls::segments_view urlSegments =
4967c4c52cbSCarson Labrado             thisReq.urlView.segments();
4977c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
4987c4c52cbSCarson Labrado         boost::urls::segments_view::iterator it = urlSegments.begin();
4997c4c52cbSCarson Labrado         const boost::urls::segments_view::const_iterator end =
5007c4c52cbSCarson Labrado             urlSegments.end();
5017c4c52cbSCarson Labrado 
5027c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
5037c4c52cbSCarson Labrado         it++;
5047c4c52cbSCarson Labrado         it++;
5057c4c52cbSCarson Labrado         for (; it != end; it++)
50646a81465SCarson Labrado         {
5077c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
5087c4c52cbSCarson Labrado                                    currentUrl.buffer()))
5097c4c52cbSCarson Labrado             {
5107c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
5117c4c52cbSCarson Labrado                 // must contain an aggregation prefix
5127c4c52cbSCarson Labrado                 findSatellite(thisReq, asyncResp, satelliteInfo, *it);
51346a81465SCarson Labrado                 return;
51446a81465SCarson Labrado             }
51546a81465SCarson Labrado 
5167c4c52cbSCarson Labrado             currentUrl.segments().push_back(*it);
51746a81465SCarson Labrado         }
518db18fc98SCarson Labrado 
519db18fc98SCarson Labrado         // We shouldn't reach this point since we should've hit one of the
520db18fc98SCarson Labrado         // previous exits
521db18fc98SCarson Labrado         messages::internalError(asyncResp->res);
52246a81465SCarson Labrado     }
52346a81465SCarson Labrado 
52446a81465SCarson Labrado     // Attempt to forward a request to the satellite BMC associated with the
52546a81465SCarson Labrado     // prefix.
52646a81465SCarson Labrado     void forwardRequest(
52746a81465SCarson Labrado         const crow::Request& thisReq,
52846a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
52946a81465SCarson Labrado         const std::string& prefix,
53046a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
53146a81465SCarson Labrado     {
53246a81465SCarson Labrado         const auto& sat = satelliteInfo.find(prefix);
53346a81465SCarson Labrado         if (sat == satelliteInfo.end())
53446a81465SCarson Labrado         {
53546a81465SCarson Labrado             // Realistically this shouldn't get called since we perform an
53646a81465SCarson Labrado             // earlier check to make sure the prefix exists
53746a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix
53846a81465SCarson Labrado                              << "\"";
53946a81465SCarson Labrado             return;
54046a81465SCarson Labrado         }
54146a81465SCarson Labrado 
54246a81465SCarson Labrado         // We need to strip the prefix from the request's path
54346a81465SCarson Labrado         std::string targetURI(thisReq.target());
54446a81465SCarson Labrado         size_t pos = targetURI.find(prefix + "_");
54546a81465SCarson Labrado         if (pos == std::string::npos)
54646a81465SCarson Labrado         {
54746a81465SCarson Labrado             // If this fails then something went wrong
54846a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix
54946a81465SCarson Labrado                              << "_\" from request URI";
55046a81465SCarson Labrado             messages::internalError(asyncResp->res);
55146a81465SCarson Labrado             return;
55246a81465SCarson Labrado         }
55346a81465SCarson Labrado         targetURI.erase(pos, prefix.size() + 1);
55446a81465SCarson Labrado 
55546a81465SCarson Labrado         std::function<void(crow::Response&)> cb =
5561c0bb5c6SCarson Labrado             std::bind_front(processResponse, prefix, asyncResp);
55746a81465SCarson Labrado 
55846a81465SCarson Labrado         std::string data = thisReq.req.body();
55946a81465SCarson Labrado         crow::HttpClient::getInstance().sendDataWithCallback(
56046a81465SCarson Labrado             data, id, std::string(sat->second.host()),
561e38778a5SAppaRao Puli             sat->second.port_number(), targetURI, false /*useSSL*/,
562e38778a5SAppaRao Puli             thisReq.fields, thisReq.method(), retryPolicyName, cb);
56346a81465SCarson Labrado     }
56446a81465SCarson Labrado 
5654c30e226SCarson Labrado     // Forward a request for a collection URI to each known satellite BMC
5664c30e226SCarson Labrado     void forwardCollectionRequests(
5674c30e226SCarson Labrado         const crow::Request& thisReq,
5684c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
5694c30e226SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
5704c30e226SCarson Labrado     {
5714c30e226SCarson Labrado         for (const auto& sat : satelliteInfo)
5724c30e226SCarson Labrado         {
5734c30e226SCarson Labrado             std::function<void(crow::Response&)> cb = std::bind_front(
5744c30e226SCarson Labrado                 processCollectionResponse, sat.first, asyncResp);
5754c30e226SCarson Labrado 
5764c30e226SCarson Labrado             std::string targetURI(thisReq.target());
5774c30e226SCarson Labrado             std::string data = thisReq.req.body();
5784c30e226SCarson Labrado             crow::HttpClient::getInstance().sendDataWithCallback(
5794c30e226SCarson Labrado                 data, id, std::string(sat.second.host()),
580e38778a5SAppaRao Puli                 sat.second.port_number(), targetURI, false /*useSSL*/,
581e38778a5SAppaRao Puli                 thisReq.fields, thisReq.method(), retryPolicyName, cb);
5824c30e226SCarson Labrado         }
5834c30e226SCarson Labrado     }
5844c30e226SCarson Labrado 
58532d7d8ebSCarson Labrado   public:
58632d7d8ebSCarson Labrado     RedfishAggregator(const RedfishAggregator&) = delete;
58732d7d8ebSCarson Labrado     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
58832d7d8ebSCarson Labrado     RedfishAggregator(RedfishAggregator&&) = delete;
58932d7d8ebSCarson Labrado     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
59032d7d8ebSCarson Labrado     ~RedfishAggregator() = default;
59132d7d8ebSCarson Labrado 
59232d7d8ebSCarson Labrado     static RedfishAggregator& getInstance()
59332d7d8ebSCarson Labrado     {
59432d7d8ebSCarson Labrado         static RedfishAggregator handler;
59532d7d8ebSCarson Labrado         return handler;
59632d7d8ebSCarson Labrado     }
59732d7d8ebSCarson Labrado 
59846a81465SCarson Labrado     // Processes the response returned by a satellite BMC and loads its
59946a81465SCarson Labrado     // contents into asyncResp
60046a81465SCarson Labrado     static void
6011c0bb5c6SCarson Labrado         processResponse(std::string_view prefix,
6021c0bb5c6SCarson Labrado                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
60346a81465SCarson Labrado                         crow::Response& resp)
60446a81465SCarson Labrado     {
605*43e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
606*43e14d38SCarson Labrado         // overwrite the response headers in that case
607*43e14d38SCarson Labrado         if ((resp.resultInt() == 429) || (resp.resultInt() == 502))
608*43e14d38SCarson Labrado         {
609*43e14d38SCarson Labrado             asyncResp->res.result(resp.result());
610*43e14d38SCarson Labrado             return;
611*43e14d38SCarson Labrado         }
612*43e14d38SCarson Labrado 
61332d7d8ebSCarson Labrado         // We want to attempt prefix fixing regardless of response code
61446a81465SCarson Labrado         // The resp will not have a json component
61546a81465SCarson Labrado         // We need to create a json from resp's stringResponse
61646a81465SCarson Labrado         if (resp.getHeaderValue("Content-Type") == "application/json")
61746a81465SCarson Labrado         {
61846a81465SCarson Labrado             nlohmann::json jsonVal =
61946a81465SCarson Labrado                 nlohmann::json::parse(resp.body(), nullptr, false);
62046a81465SCarson Labrado             if (jsonVal.is_discarded())
62146a81465SCarson Labrado             {
62246a81465SCarson Labrado                 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON";
62346a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
62446a81465SCarson Labrado                 return;
62546a81465SCarson Labrado             }
62646a81465SCarson Labrado 
62746a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Successfully parsed satellite response";
62846a81465SCarson Labrado 
6291c0bb5c6SCarson Labrado             addPrefixes(jsonVal, prefix);
6301c0bb5c6SCarson Labrado 
6311c0bb5c6SCarson Labrado             BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response";
6321c0bb5c6SCarson Labrado 
63346a81465SCarson Labrado             asyncResp->res.result(resp.result());
63446a81465SCarson Labrado             asyncResp->res.jsonValue = std::move(jsonVal);
63546a81465SCarson Labrado 
63646a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Finished writing asyncResp";
63746a81465SCarson Labrado         }
63846a81465SCarson Labrado         else
63946a81465SCarson Labrado         {
64046a81465SCarson Labrado             if (!resp.body().empty())
64146a81465SCarson Labrado             {
64232d7d8ebSCarson Labrado                 // We received a valid response without the correct
64332d7d8ebSCarson Labrado                 // Content-Type so return an Operation Failed error
64446a81465SCarson Labrado                 BMCWEB_LOG_ERROR
64546a81465SCarson Labrado                     << "Satellite response must be of type \"application/json\"";
64646a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
64746a81465SCarson Labrado             }
64846a81465SCarson Labrado         }
64946a81465SCarson Labrado     }
65046a81465SCarson Labrado 
6514c30e226SCarson Labrado     // Processes the collection response returned by a satellite BMC and merges
6524c30e226SCarson Labrado     // its "@odata.id" values
6534c30e226SCarson Labrado     static void processCollectionResponse(
6544c30e226SCarson Labrado         const std::string& prefix,
6554c30e226SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6564c30e226SCarson Labrado         crow::Response& resp)
6574c30e226SCarson Labrado     {
658*43e14d38SCarson Labrado         // 429 and 502 mean we didn't actually send the request so don't
659*43e14d38SCarson Labrado         // overwrite the response headers in that case
660*43e14d38SCarson Labrado         if ((resp.resultInt() == 429) || (resp.resultInt() == 502))
661*43e14d38SCarson Labrado         {
662*43e14d38SCarson Labrado             return;
663*43e14d38SCarson Labrado         }
664*43e14d38SCarson Labrado 
6654c30e226SCarson Labrado         if (resp.resultInt() != 200)
6664c30e226SCarson Labrado         {
6674c30e226SCarson Labrado             BMCWEB_LOG_DEBUG
6684c30e226SCarson Labrado                 << "Collection resource does not exist in satellite BMC \""
6694c30e226SCarson Labrado                 << prefix << "\"";
6704c30e226SCarson Labrado             // Return the error if we haven't had any successes
6714c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
6724c30e226SCarson Labrado             {
6734c30e226SCarson Labrado                 asyncResp->res.stringResponse = std::move(resp.stringResponse);
6744c30e226SCarson Labrado             }
6754c30e226SCarson Labrado             return;
6764c30e226SCarson Labrado         }
6774c30e226SCarson Labrado 
6784c30e226SCarson Labrado         // The resp will not have a json component
6794c30e226SCarson Labrado         // We need to create a json from resp's stringResponse
6804c30e226SCarson Labrado         if (resp.getHeaderValue("Content-Type") == "application/json")
6814c30e226SCarson Labrado         {
6824c30e226SCarson Labrado             nlohmann::json jsonVal =
6834c30e226SCarson Labrado                 nlohmann::json::parse(resp.body(), nullptr, false);
6844c30e226SCarson Labrado             if (jsonVal.is_discarded())
6854c30e226SCarson Labrado             {
6864c30e226SCarson Labrado                 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON";
6874c30e226SCarson Labrado 
6884c30e226SCarson Labrado                 // Notify the user if doing so won't overwrite a valid response
6894c30e226SCarson Labrado                 if ((asyncResp->res.resultInt() != 200) &&
690*43e14d38SCarson Labrado                     (asyncResp->res.resultInt() != 429) &&
6914c30e226SCarson Labrado                     (asyncResp->res.resultInt() != 502))
6924c30e226SCarson Labrado                 {
6934c30e226SCarson Labrado                     messages::operationFailed(asyncResp->res);
6944c30e226SCarson Labrado                 }
6954c30e226SCarson Labrado                 return;
6964c30e226SCarson Labrado             }
6974c30e226SCarson Labrado 
6984c30e226SCarson Labrado             BMCWEB_LOG_DEBUG << "Successfully parsed satellite response";
6994c30e226SCarson Labrado 
7004c30e226SCarson Labrado             // Now we need to add the prefix to the URIs contained in the
7014c30e226SCarson Labrado             // response.
7024c30e226SCarson Labrado             addPrefixes(jsonVal, prefix);
7034c30e226SCarson Labrado 
7044c30e226SCarson Labrado             BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response";
7054c30e226SCarson Labrado 
7064c30e226SCarson Labrado             // If this resource collection does not exist on the aggregating bmc
7074c30e226SCarson Labrado             // and has not already been added from processing the response from
7084c30e226SCarson Labrado             // a different satellite then we need to completely overwrite
7094c30e226SCarson Labrado             // asyncResp
7104c30e226SCarson Labrado             if (asyncResp->res.resultInt() != 200)
7114c30e226SCarson Labrado             {
7124c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
7134c30e226SCarson Labrado                 // "Members" array
7144c30e226SCarson Labrado                 if ((!jsonVal.contains("Members")) &&
7154c30e226SCarson Labrado                     (!jsonVal["Members"].is_array()))
7164c30e226SCarson Labrado                 {
7174c30e226SCarson Labrado                     BMCWEB_LOG_DEBUG
7184c30e226SCarson Labrado                         << "Skipping aggregating unsupported resource";
7194c30e226SCarson Labrado                     return;
7204c30e226SCarson Labrado                 }
7214c30e226SCarson Labrado 
7224c30e226SCarson Labrado                 BMCWEB_LOG_DEBUG
7234c30e226SCarson Labrado                     << "Collection does not exist, overwriting asyncResp";
7244c30e226SCarson Labrado                 asyncResp->res.result(resp.result());
7254c30e226SCarson Labrado                 asyncResp->res.jsonValue = std::move(jsonVal);
726*43e14d38SCarson Labrado                 asyncResp->res.addHeader("Content-Type", "application/json");
7274c30e226SCarson Labrado 
7284c30e226SCarson Labrado                 BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp";
7294c30e226SCarson Labrado             }
7304c30e226SCarson Labrado             else
7314c30e226SCarson Labrado             {
7324c30e226SCarson Labrado                 // We only want to aggregate collections that contain a
7334c30e226SCarson Labrado                 // "Members" array
7344c30e226SCarson Labrado                 if ((!asyncResp->res.jsonValue.contains("Members")) &&
7354c30e226SCarson Labrado                     (!asyncResp->res.jsonValue["Members"].is_array()))
7364c30e226SCarson Labrado 
7374c30e226SCarson Labrado                 {
7384c30e226SCarson Labrado                     BMCWEB_LOG_DEBUG
7394c30e226SCarson Labrado                         << "Skipping aggregating unsupported resource";
7404c30e226SCarson Labrado                     return;
7414c30e226SCarson Labrado                 }
7424c30e226SCarson Labrado 
7434c30e226SCarson Labrado                 BMCWEB_LOG_DEBUG << "Adding aggregated resources from \""
7444c30e226SCarson Labrado                                  << prefix << "\" to collection";
7454c30e226SCarson Labrado 
7464c30e226SCarson Labrado                 // TODO: This is a potential race condition with multiple
7474c30e226SCarson Labrado                 // satellites and the aggregating bmc attempting to write to
7484c30e226SCarson Labrado                 // update this array.  May need to cascade calls to the next
7494c30e226SCarson Labrado                 // satellite at the end of this function.
7504c30e226SCarson Labrado                 // This is presumably not a concern when there is only a single
7514c30e226SCarson Labrado                 // satellite since the aggregating bmc should have completed
7524c30e226SCarson Labrado                 // before the response is received from the satellite.
7534c30e226SCarson Labrado 
7544c30e226SCarson Labrado                 auto& members = asyncResp->res.jsonValue["Members"];
7554c30e226SCarson Labrado                 auto& satMembers = jsonVal["Members"];
7564c30e226SCarson Labrado                 for (auto& satMem : satMembers)
7574c30e226SCarson Labrado                 {
7584c30e226SCarson Labrado                     members.push_back(std::move(satMem));
7594c30e226SCarson Labrado                 }
7604c30e226SCarson Labrado                 asyncResp->res.jsonValue["Members@odata.count"] =
7614c30e226SCarson Labrado                     members.size();
7624c30e226SCarson Labrado 
7634c30e226SCarson Labrado                 // TODO: Do we need to sort() after updating the array?
7644c30e226SCarson Labrado             }
7654c30e226SCarson Labrado         }
7664c30e226SCarson Labrado         else
7674c30e226SCarson Labrado         {
7684c30e226SCarson Labrado             BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix
7694c30e226SCarson Labrado                              << "\"";
770*43e14d38SCarson Labrado             // We received a response that was not a json.
7714c30e226SCarson Labrado             // Notify the user only if we did not receive any valid responses,
7724c30e226SCarson Labrado             // if the resource collection does not already exist on the
7734c30e226SCarson Labrado             // aggregating BMC, and if we did not already set this warning due
7744c30e226SCarson Labrado             // to a failure from a different satellite
7754c30e226SCarson Labrado             if ((asyncResp->res.resultInt() != 200) &&
776*43e14d38SCarson Labrado                 (asyncResp->res.resultInt() != 429) &&
7774c30e226SCarson Labrado                 (asyncResp->res.resultInt() != 502))
7784c30e226SCarson Labrado             {
7794c30e226SCarson Labrado                 messages::operationFailed(asyncResp->res);
7804c30e226SCarson Labrado             }
7814c30e226SCarson Labrado         }
7824c30e226SCarson Labrado     } // End processCollectionResponse()
7834c30e226SCarson Labrado 
78405916cefSCarson Labrado     // Entry point to Redfish Aggregation
78505916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
78605916cefSCarson Labrado     // request
78705916cefSCarson Labrado     static Result
78805916cefSCarson Labrado         beginAggregation(const crow::Request& thisReq,
78905916cefSCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
79005916cefSCarson Labrado     {
79105916cefSCarson Labrado         using crow::utility::OrMorePaths;
79205916cefSCarson Labrado         using crow::utility::readUrlSegments;
79311987af6SCarson Labrado         const boost::urls::url_view url = thisReq.urlView;
794411e6a11SCarson Labrado 
795411e6a11SCarson Labrado         // We don't need to aggregate JsonSchemas due to potential issues such
796411e6a11SCarson Labrado         // as version mismatches between aggregator and satellite BMCs.  For
797411e6a11SCarson Labrado         // now assume that the aggregator has all the schemas and versions that
798411e6a11SCarson Labrado         // the aggregated server has.
799411e6a11SCarson Labrado         if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas",
800411e6a11SCarson Labrado                                            crow::utility::OrMorePaths()))
801411e6a11SCarson Labrado         {
802411e6a11SCarson Labrado             return Result::LocalHandle;
803411e6a11SCarson Labrado         }
804411e6a11SCarson Labrado 
8057c4c52cbSCarson Labrado         // The first two segments should be "/redfish/v1".  We need to check
8067c4c52cbSCarson Labrado         // that before we can search topCollections
8077c4c52cbSCarson Labrado         if (!crow::utility::readUrlSegments(url, "redfish", "v1",
8087c4c52cbSCarson Labrado                                             crow::utility::OrMorePaths()))
80946a81465SCarson Labrado         {
81046a81465SCarson Labrado             return Result::LocalHandle;
81146a81465SCarson Labrado         }
81205916cefSCarson Labrado 
8137c4c52cbSCarson Labrado         // Parse the URI to see if it begins with a known top level collection
8147c4c52cbSCarson Labrado         // such as:
8157c4c52cbSCarson Labrado         // /redfish/v1/Chassis
8167c4c52cbSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory
8177c4c52cbSCarson Labrado         const boost::urls::segments_view urlSegments = url.segments();
8187c4c52cbSCarson Labrado         boost::urls::url currentUrl("/");
8197c4c52cbSCarson Labrado         boost::urls::segments_view::iterator it = urlSegments.begin();
8207c4c52cbSCarson Labrado         const boost::urls::segments_view::const_iterator end =
8217c4c52cbSCarson Labrado             urlSegments.end();
82205916cefSCarson Labrado 
8237c4c52cbSCarson Labrado         // Skip past the leading "/redfish/v1"
8247c4c52cbSCarson Labrado         it++;
8257c4c52cbSCarson Labrado         it++;
8267c4c52cbSCarson Labrado         for (; it != end; it++)
82705916cefSCarson Labrado         {
828d4413c5bSGeorge Liu             const std::string& collectionItem = *it;
8297c4c52cbSCarson Labrado             if (std::binary_search(topCollections.begin(), topCollections.end(),
8307c4c52cbSCarson Labrado                                    currentUrl.buffer()))
8317c4c52cbSCarson Labrado             {
8327c4c52cbSCarson Labrado                 // We've matched a resource collection so this current segment
8337c4c52cbSCarson Labrado                 // might contain an aggregation prefix
8347c4c52cbSCarson Labrado                 if (collectionItem.starts_with("5B247A"))
83505916cefSCarson Labrado                 {
83605916cefSCarson Labrado                     BMCWEB_LOG_DEBUG << "Need to forward a request";
83705916cefSCarson Labrado 
83846a81465SCarson Labrado                     // Extract the prefix from the request's URI, retrieve the
8397c4c52cbSCarson Labrado                     // associated satellite config information, and then forward
8407c4c52cbSCarson Labrado                     // the request to that satellite.
8417c4c52cbSCarson Labrado                     startAggregation(AggregationType::Resource, thisReq,
8427c4c52cbSCarson Labrado                                      asyncResp);
84305916cefSCarson Labrado                     return Result::NoLocalHandle;
84405916cefSCarson Labrado                 }
8457c4c52cbSCarson Labrado 
8467c4c52cbSCarson Labrado                 // Handle collection URI with a trailing backslash
8477c4c52cbSCarson Labrado                 // e.g. /redfish/v1/Chassis/
8487c4c52cbSCarson Labrado                 it++;
8497c4c52cbSCarson Labrado                 if ((it == end) && collectionItem.empty())
8507c4c52cbSCarson Labrado                 {
8517c4c52cbSCarson Labrado                     startAggregation(AggregationType::Collection, thisReq,
8527c4c52cbSCarson Labrado                                      asyncResp);
8537c4c52cbSCarson Labrado                 }
8547c4c52cbSCarson Labrado 
8557c4c52cbSCarson Labrado                 // We didn't recognize the prefix or it's a collection with a
8567c4c52cbSCarson Labrado                 // trailing "/".  In both cases we still want to locally handle
8577c4c52cbSCarson Labrado                 // the request
8587c4c52cbSCarson Labrado                 return Result::LocalHandle;
8597c4c52cbSCarson Labrado             }
8607c4c52cbSCarson Labrado 
8617c4c52cbSCarson Labrado             currentUrl.segments().push_back(collectionItem);
8627c4c52cbSCarson Labrado         }
8637c4c52cbSCarson Labrado 
8647c4c52cbSCarson Labrado         // If we made it here then currentUrl could contain a top level
8657c4c52cbSCarson Labrado         // collection URI without a trailing "/", e.g. /redfish/v1/Chassis
8667c4c52cbSCarson Labrado         if (std::binary_search(topCollections.begin(), topCollections.end(),
8677c4c52cbSCarson Labrado                                currentUrl.buffer()))
8687c4c52cbSCarson Labrado         {
8697c4c52cbSCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
87005916cefSCarson Labrado             return Result::LocalHandle;
87105916cefSCarson Labrado         }
87205916cefSCarson Labrado 
87305916cefSCarson Labrado         BMCWEB_LOG_DEBUG << "Aggregation not required";
87405916cefSCarson Labrado         return Result::LocalHandle;
87505916cefSCarson Labrado     }
8767fb33566SCarson Labrado };
8777fb33566SCarson Labrado 
8787fb33566SCarson Labrado } // namespace redfish
879