xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 1c0bb5c6f90b772150accb1f590227589e2179ff)
17fb33566SCarson Labrado #pragma once
27fb33566SCarson Labrado 
346a81465SCarson Labrado #include <dbus_utility.hpp>
446a81465SCarson Labrado #include <error_messages.hpp>
57fb33566SCarson Labrado #include <http_client.hpp>
646a81465SCarson Labrado #include <http_connection.hpp>
77fb33566SCarson Labrado 
87fb33566SCarson Labrado namespace redfish
97fb33566SCarson Labrado {
107fb33566SCarson Labrado 
1105916cefSCarson Labrado enum class Result
1205916cefSCarson Labrado {
1305916cefSCarson Labrado     LocalHandle,
1405916cefSCarson Labrado     NoLocalHandle
1505916cefSCarson Labrado };
1605916cefSCarson Labrado 
17*1c0bb5c6SCarson Labrado static void addPrefixToItem(nlohmann::json& item, std::string_view prefix)
18*1c0bb5c6SCarson Labrado {
19*1c0bb5c6SCarson Labrado     std::string* strValue = item.get_ptr<std::string*>();
20*1c0bb5c6SCarson Labrado     if (strValue == nullptr)
21*1c0bb5c6SCarson Labrado     {
22*1c0bb5c6SCarson Labrado         BMCWEB_LOG_CRITICAL << "Field wasn't a string????";
23*1c0bb5c6SCarson Labrado         return;
24*1c0bb5c6SCarson Labrado     }
25*1c0bb5c6SCarson Labrado     // Make sure the value is a properly formatted URI
26*1c0bb5c6SCarson Labrado     auto parsed = boost::urls::parse_relative_ref(*strValue);
27*1c0bb5c6SCarson Labrado     if (!parsed)
28*1c0bb5c6SCarson Labrado     {
29*1c0bb5c6SCarson Labrado         BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << *strValue;
30*1c0bb5c6SCarson Labrado         return;
31*1c0bb5c6SCarson Labrado     }
32*1c0bb5c6SCarson Labrado 
33*1c0bb5c6SCarson Labrado     boost::urls::url_view thisUrl = *parsed;
34*1c0bb5c6SCarson Labrado 
35*1c0bb5c6SCarson Labrado     // We don't need to add prefixes to these URIs since
36*1c0bb5c6SCarson Labrado     // /redfish/v1/UpdateService/ itself is not a collection
37*1c0bb5c6SCarson Labrado     // /redfish/v1/UpdateService/FirmwareInventory
38*1c0bb5c6SCarson Labrado     // /redfish/v1/UpdateService/SoftwareInventory
39*1c0bb5c6SCarson Labrado     if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
40*1c0bb5c6SCarson Labrado                                        "UpdateService", "FirmwareInventory") ||
41*1c0bb5c6SCarson Labrado         crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
42*1c0bb5c6SCarson Labrado                                        "UpdateService", "SoftwareInventory"))
43*1c0bb5c6SCarson Labrado     {
44*1c0bb5c6SCarson Labrado         BMCWEB_LOG_DEBUG << "Skipping UpdateService URI prefix fixing";
45*1c0bb5c6SCarson Labrado         return;
46*1c0bb5c6SCarson Labrado     }
47*1c0bb5c6SCarson Labrado 
48*1c0bb5c6SCarson Labrado     // We also need to aggregate FirmwareInventory and
49*1c0bb5c6SCarson Labrado     // SoftwareInventory so add an extra offset
50*1c0bb5c6SCarson Labrado     // /redfish/v1/UpdateService/FirmwareInventory/<id>
51*1c0bb5c6SCarson Labrado     // /redfish/v1/UpdateService/SoftwareInventory/<id>
52*1c0bb5c6SCarson Labrado     std::string collectionName;
53*1c0bb5c6SCarson Labrado     std::string softwareItem;
54*1c0bb5c6SCarson Labrado     if (crow::utility::readUrlSegments(
55*1c0bb5c6SCarson Labrado             thisUrl, "redfish", "v1", "UpdateService", std::ref(collectionName),
56*1c0bb5c6SCarson Labrado             std::ref(softwareItem), crow::utility::OrMorePaths()))
57*1c0bb5c6SCarson Labrado     {
58*1c0bb5c6SCarson Labrado         softwareItem.insert(0, "_");
59*1c0bb5c6SCarson Labrado         softwareItem.insert(0, prefix);
60*1c0bb5c6SCarson Labrado         item = crow::utility::replaceUrlSegment(thisUrl, 4, softwareItem);
61*1c0bb5c6SCarson Labrado     }
62*1c0bb5c6SCarson Labrado 
63*1c0bb5c6SCarson Labrado     // A collection URI that ends with "/" such as
64*1c0bb5c6SCarson Labrado     // "/redfish/v1/Chassis/" will have 4 segments so we need to
65*1c0bb5c6SCarson Labrado     // make sure we don't try to add a prefix to an empty segment
66*1c0bb5c6SCarson Labrado     if (crow::utility::readUrlSegments(
67*1c0bb5c6SCarson Labrado             thisUrl, "redfish", "v1", std::ref(collectionName),
68*1c0bb5c6SCarson Labrado             std::ref(softwareItem), crow::utility::OrMorePaths()))
69*1c0bb5c6SCarson Labrado     {
70*1c0bb5c6SCarson Labrado         softwareItem.insert(0, "_");
71*1c0bb5c6SCarson Labrado         softwareItem.insert(0, prefix);
72*1c0bb5c6SCarson Labrado         item = crow::utility::replaceUrlSegment(thisUrl, 3, softwareItem);
73*1c0bb5c6SCarson Labrado     }
74*1c0bb5c6SCarson Labrado }
75*1c0bb5c6SCarson Labrado 
76*1c0bb5c6SCarson Labrado // We need to attempt to update all URIs under Actions
77*1c0bb5c6SCarson Labrado static void addPrefixesToActions(nlohmann::json& json, std::string_view prefix)
78*1c0bb5c6SCarson Labrado {
79*1c0bb5c6SCarson Labrado     nlohmann::json::object_t* object =
80*1c0bb5c6SCarson Labrado         json.get_ptr<nlohmann::json::object_t*>();
81*1c0bb5c6SCarson Labrado     if (object != nullptr)
82*1c0bb5c6SCarson Labrado     {
83*1c0bb5c6SCarson Labrado         for (std::pair<const std::string, nlohmann::json>& item : *object)
84*1c0bb5c6SCarson Labrado         {
85*1c0bb5c6SCarson Labrado             std::string* strValue = item.second.get_ptr<std::string*>();
86*1c0bb5c6SCarson Labrado             if (strValue != nullptr)
87*1c0bb5c6SCarson Labrado             {
88*1c0bb5c6SCarson Labrado                 addPrefixToItem(item.second, prefix);
89*1c0bb5c6SCarson Labrado             }
90*1c0bb5c6SCarson Labrado             else
91*1c0bb5c6SCarson Labrado             {
92*1c0bb5c6SCarson Labrado                 addPrefixesToActions(item.second, prefix);
93*1c0bb5c6SCarson Labrado             }
94*1c0bb5c6SCarson Labrado         }
95*1c0bb5c6SCarson Labrado     }
96*1c0bb5c6SCarson Labrado }
97*1c0bb5c6SCarson Labrado 
98*1c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for
99*1c0bb5c6SCarson Labrado // and aggregated resource.
100*1c0bb5c6SCarson Labrado static void addPrefixes(nlohmann::json& json, std::string_view prefix)
101*1c0bb5c6SCarson Labrado {
102*1c0bb5c6SCarson Labrado     nlohmann::json::object_t* object =
103*1c0bb5c6SCarson Labrado         json.get_ptr<nlohmann::json::object_t*>();
104*1c0bb5c6SCarson Labrado     if (object != nullptr)
105*1c0bb5c6SCarson Labrado     {
106*1c0bb5c6SCarson Labrado         for (std::pair<const std::string, nlohmann::json>& item : *object)
107*1c0bb5c6SCarson Labrado         {
108*1c0bb5c6SCarson Labrado             if (item.first == "Actions")
109*1c0bb5c6SCarson Labrado             {
110*1c0bb5c6SCarson Labrado                 addPrefixesToActions(item.second, prefix);
111*1c0bb5c6SCarson Labrado                 continue;
112*1c0bb5c6SCarson Labrado             }
113*1c0bb5c6SCarson Labrado 
114*1c0bb5c6SCarson Labrado             if ((item.first == "@odata.id") || (item.first.ends_with("URI")))
115*1c0bb5c6SCarson Labrado             {
116*1c0bb5c6SCarson Labrado                 addPrefixToItem(item.second, prefix);
117*1c0bb5c6SCarson Labrado             }
118*1c0bb5c6SCarson Labrado             // Recusively parse the rest of the json
119*1c0bb5c6SCarson Labrado             addPrefixes(item.second, prefix);
120*1c0bb5c6SCarson Labrado         }
121*1c0bb5c6SCarson Labrado         return;
122*1c0bb5c6SCarson Labrado     }
123*1c0bb5c6SCarson Labrado     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
124*1c0bb5c6SCarson Labrado     if (array != nullptr)
125*1c0bb5c6SCarson Labrado     {
126*1c0bb5c6SCarson Labrado         for (nlohmann::json& item : *array)
127*1c0bb5c6SCarson Labrado         {
128*1c0bb5c6SCarson Labrado             addPrefixes(item, prefix);
129*1c0bb5c6SCarson Labrado         }
130*1c0bb5c6SCarson Labrado     }
131*1c0bb5c6SCarson Labrado }
132*1c0bb5c6SCarson Labrado 
1337fb33566SCarson Labrado class RedfishAggregator
1347fb33566SCarson Labrado {
1357fb33566SCarson Labrado   private:
136a7a80296SCarson Labrado     const std::string retryPolicyName = "RedfishAggregation";
137a7a80296SCarson Labrado     const uint32_t retryAttempts = 5;
138a7a80296SCarson Labrado     const uint32_t retryTimeoutInterval = 0;
13946a81465SCarson Labrado     const std::string id = "Aggregator";
140a7a80296SCarson Labrado 
1417fb33566SCarson Labrado     RedfishAggregator()
1427fb33566SCarson Labrado     {
1437fb33566SCarson Labrado         getSatelliteConfigs(constructorCallback);
144a7a80296SCarson Labrado 
145a7a80296SCarson Labrado         // Setup the retry policy to be used by Redfish Aggregation
146a7a80296SCarson Labrado         crow::HttpClient::getInstance().setRetryConfig(
147a7a80296SCarson Labrado             retryAttempts, retryTimeoutInterval, aggregationRetryHandler,
148a7a80296SCarson Labrado             retryPolicyName);
1497fb33566SCarson Labrado     }
1507fb33566SCarson Labrado 
151a7a80296SCarson Labrado     static inline boost::system::error_code
152a7a80296SCarson Labrado         aggregationRetryHandler(unsigned int respCode)
153a7a80296SCarson Labrado     {
154a7a80296SCarson Labrado         // As a default, assume 200X is alright.
155a7a80296SCarson Labrado         // We don't need to retry on a 404
156a7a80296SCarson Labrado         if ((respCode < 200) || ((respCode >= 300) && (respCode != 404)))
157a7a80296SCarson Labrado         {
158a7a80296SCarson Labrado             return boost::system::errc::make_error_code(
159a7a80296SCarson Labrado                 boost::system::errc::result_out_of_range);
160a7a80296SCarson Labrado         }
161a7a80296SCarson Labrado 
162a7a80296SCarson Labrado         // Return 0 if the response code is valid
163a7a80296SCarson Labrado         return boost::system::errc::make_error_code(
164a7a80296SCarson Labrado             boost::system::errc::success);
1659fa6d147SNan Zhou     }
166a7a80296SCarson Labrado 
1677fb33566SCarson Labrado     // Dummy callback used by the Constructor so that it can report the number
1687fb33566SCarson Labrado     // of satellite configs when the class is first created
1697fb33566SCarson Labrado     static void constructorCallback(
1707fb33566SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
1717fb33566SCarson Labrado     {
1727fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << "There were "
1737fb33566SCarson Labrado                          << std::to_string(satelliteInfo.size())
1747fb33566SCarson Labrado                          << " satellite configs found at startup";
1757fb33566SCarson Labrado     }
1767fb33566SCarson Labrado 
1777fb33566SCarson Labrado     // Polls D-Bus to get all available satellite config information
1787fb33566SCarson Labrado     // Expects a handler which interacts with the returned configs
1797fb33566SCarson Labrado     static void getSatelliteConfigs(
1807fb33566SCarson Labrado         const std::function<void(
1817fb33566SCarson Labrado             const std::unordered_map<std::string, boost::urls::url>&)>& handler)
1827fb33566SCarson Labrado     {
1837fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << "Gathering satellite configs";
1847fb33566SCarson Labrado         crow::connections::systemBus->async_method_call(
1857fb33566SCarson Labrado             [handler](const boost::system::error_code ec,
1867fb33566SCarson Labrado                       const dbus::utility::ManagedObjectType& objects) {
1877fb33566SCarson Labrado             if (ec)
1887fb33566SCarson Labrado             {
189002d39b4SEd Tanous                 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", "
190002d39b4SEd Tanous                                  << ec.message();
1917fb33566SCarson Labrado                 return;
1927fb33566SCarson Labrado             }
1937fb33566SCarson Labrado 
1947fb33566SCarson Labrado             // Maps a chosen alias representing a satellite BMC to a url
1957fb33566SCarson Labrado             // containing the information required to create a http
1967fb33566SCarson Labrado             // connection to the satellite
1977fb33566SCarson Labrado             std::unordered_map<std::string, boost::urls::url> satelliteInfo;
1987fb33566SCarson Labrado 
1997fb33566SCarson Labrado             findSatelliteConfigs(objects, satelliteInfo);
2007fb33566SCarson Labrado 
2017fb33566SCarson Labrado             if (!satelliteInfo.empty())
2027fb33566SCarson Labrado             {
2037fb33566SCarson Labrado                 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with "
2047fb33566SCarson Labrado                                  << std::to_string(satelliteInfo.size())
2057fb33566SCarson Labrado                                  << " satellite BMCs";
2067fb33566SCarson Labrado             }
2077fb33566SCarson Labrado             else
2087fb33566SCarson Labrado             {
2097fb33566SCarson Labrado                 BMCWEB_LOG_DEBUG
2107fb33566SCarson Labrado                     << "No satellite BMCs detected.  Redfish Aggregation not enabled";
2117fb33566SCarson Labrado             }
2127fb33566SCarson Labrado             handler(satelliteInfo);
2137fb33566SCarson Labrado             },
2147fb33566SCarson Labrado             "xyz.openbmc_project.EntityManager", "/",
2157fb33566SCarson Labrado             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
2167fb33566SCarson Labrado     }
2177fb33566SCarson Labrado 
2187fb33566SCarson Labrado     // Search D-Bus objects for satellite config objects and add their
2197fb33566SCarson Labrado     // information if valid
2207fb33566SCarson Labrado     static void findSatelliteConfigs(
2217fb33566SCarson Labrado         const dbus::utility::ManagedObjectType& objects,
2227fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
2237fb33566SCarson Labrado     {
2247fb33566SCarson Labrado         for (const auto& objectPath : objects)
2257fb33566SCarson Labrado         {
2267fb33566SCarson Labrado             for (const auto& interface : objectPath.second)
2277fb33566SCarson Labrado             {
2287fb33566SCarson Labrado                 if (interface.first ==
2297fb33566SCarson Labrado                     "xyz.openbmc_project.Configuration.SatelliteController")
2307fb33566SCarson Labrado                 {
2317fb33566SCarson Labrado                     BMCWEB_LOG_DEBUG << "Found Satellite Controller at "
2327fb33566SCarson Labrado                                      << objectPath.first.str;
2337fb33566SCarson Labrado 
23405916cefSCarson Labrado                     if (!satelliteInfo.empty())
23505916cefSCarson Labrado                     {
23605916cefSCarson Labrado                         BMCWEB_LOG_ERROR
23705916cefSCarson Labrado                             << "Redfish Aggregation only supports one satellite!";
23805916cefSCarson Labrado                         BMCWEB_LOG_DEBUG << "Clearing all satellite data";
23905916cefSCarson Labrado                         satelliteInfo.clear();
24005916cefSCarson Labrado                         return;
24105916cefSCarson Labrado                     }
24205916cefSCarson Labrado 
24305916cefSCarson Labrado                     // For now assume there will only be one satellite config.
24405916cefSCarson Labrado                     // Assign it the name/prefix "5B247A"
24505916cefSCarson Labrado                     addSatelliteConfig("5B247A", interface.second,
24605916cefSCarson Labrado                                        satelliteInfo);
2477fb33566SCarson Labrado                 }
2487fb33566SCarson Labrado             }
2497fb33566SCarson Labrado         }
2507fb33566SCarson Labrado     }
2517fb33566SCarson Labrado 
2527fb33566SCarson Labrado     // Parse the properties of a satellite config object and add the
2537fb33566SCarson Labrado     // configuration if the properties are valid
2547fb33566SCarson Labrado     static void addSatelliteConfig(
25505916cefSCarson Labrado         const std::string& name,
2567fb33566SCarson Labrado         const dbus::utility::DBusPropertiesMap& properties,
2577fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
2587fb33566SCarson Labrado     {
2597fb33566SCarson Labrado         boost::urls::url url;
2607fb33566SCarson Labrado 
2617fb33566SCarson Labrado         for (const auto& prop : properties)
2627fb33566SCarson Labrado         {
26305916cefSCarson Labrado             if (prop.first == "Hostname")
2647fb33566SCarson Labrado             {
2657fb33566SCarson Labrado                 const std::string* propVal =
2667fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
2677fb33566SCarson Labrado                 if (propVal == nullptr)
2687fb33566SCarson Labrado                 {
2697fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid Hostname value";
2707fb33566SCarson Labrado                     return;
2717fb33566SCarson Labrado                 }
2727fb33566SCarson Labrado                 url.set_host(*propVal);
2737fb33566SCarson Labrado             }
2747fb33566SCarson Labrado 
2757fb33566SCarson Labrado             else if (prop.first == "Port")
2767fb33566SCarson Labrado             {
2777fb33566SCarson Labrado                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
2787fb33566SCarson Labrado                 if (propVal == nullptr)
2797fb33566SCarson Labrado                 {
2807fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid Port value";
2817fb33566SCarson Labrado                     return;
2827fb33566SCarson Labrado                 }
2837fb33566SCarson Labrado 
2847fb33566SCarson Labrado                 if (*propVal > std::numeric_limits<uint16_t>::max())
2857fb33566SCarson Labrado                 {
2867fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Port value out of range";
2877fb33566SCarson Labrado                     return;
2887fb33566SCarson Labrado                 }
2897fb33566SCarson Labrado                 url.set_port(static_cast<uint16_t>(*propVal));
2907fb33566SCarson Labrado             }
2917fb33566SCarson Labrado 
2927fb33566SCarson Labrado             else if (prop.first == "AuthType")
2937fb33566SCarson Labrado             {
2947fb33566SCarson Labrado                 const std::string* propVal =
2957fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
2967fb33566SCarson Labrado                 if (propVal == nullptr)
2977fb33566SCarson Labrado                 {
2987fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid AuthType value";
2997fb33566SCarson Labrado                     return;
3007fb33566SCarson Labrado                 }
3017fb33566SCarson Labrado 
3027fb33566SCarson Labrado                 // For now assume authentication not required to communicate
3037fb33566SCarson Labrado                 // with the satellite BMC
3047fb33566SCarson Labrado                 if (*propVal != "None")
3057fb33566SCarson Labrado                 {
3067fb33566SCarson Labrado                     BMCWEB_LOG_ERROR
3077fb33566SCarson Labrado                         << "Unsupported AuthType value: " << *propVal
3087fb33566SCarson Labrado                         << ", only \"none\" is supported";
3097fb33566SCarson Labrado                     return;
3107fb33566SCarson Labrado                 }
3117fb33566SCarson Labrado                 url.set_scheme("http");
3127fb33566SCarson Labrado             }
3137fb33566SCarson Labrado         } // Finished reading properties
3147fb33566SCarson Labrado 
3157fb33566SCarson Labrado         // Make sure all required config information was made available
3167fb33566SCarson Labrado         if (url.host().empty())
3177fb33566SCarson Labrado         {
3187fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host";
3197fb33566SCarson Labrado             return;
3207fb33566SCarson Labrado         }
3217fb33566SCarson Labrado 
3227fb33566SCarson Labrado         if (!url.has_port())
3237fb33566SCarson Labrado         {
3247fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port";
3257fb33566SCarson Labrado             return;
3267fb33566SCarson Labrado         }
3277fb33566SCarson Labrado 
3287fb33566SCarson Labrado         if (!url.has_scheme())
3297fb33566SCarson Labrado         {
3307fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name
3317fb33566SCarson Labrado                              << " missing AuthType";
3327fb33566SCarson Labrado             return;
3337fb33566SCarson Labrado         }
3347fb33566SCarson Labrado 
3357fb33566SCarson Labrado         std::string resultString;
3367fb33566SCarson Labrado         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
3377fb33566SCarson Labrado         if (result.second)
3387fb33566SCarson Labrado         {
3397fb33566SCarson Labrado             resultString = "Added new satellite config ";
3407fb33566SCarson Labrado         }
3417fb33566SCarson Labrado         else
3427fb33566SCarson Labrado         {
3437fb33566SCarson Labrado             resultString = "Updated existing satellite config ";
3447fb33566SCarson Labrado         }
3457fb33566SCarson Labrado 
3467fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << resultString << name << " at "
3477fb33566SCarson Labrado                          << result.first->second.scheme() << "://"
3487fb33566SCarson Labrado                          << result.first->second.encoded_host_and_port();
3497fb33566SCarson Labrado     }
3507fb33566SCarson Labrado 
35146a81465SCarson Labrado     enum AggregationType
35246a81465SCarson Labrado     {
35346a81465SCarson Labrado         Collection,
35446a81465SCarson Labrado         Resource,
35546a81465SCarson Labrado     };
35646a81465SCarson Labrado 
35746a81465SCarson Labrado     static void
35846a81465SCarson Labrado         startAggregation(AggregationType isCollection,
35946a81465SCarson Labrado                          const crow::Request& thisReq,
36046a81465SCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
36146a81465SCarson Labrado     {
36246a81465SCarson Labrado         // Create a copy of thisReq so we we can still locally process the req
36346a81465SCarson Labrado         std::error_code ec;
36446a81465SCarson Labrado         auto localReq = std::make_shared<crow::Request>(thisReq.req, ec);
36546a81465SCarson Labrado         if (ec)
36646a81465SCarson Labrado         {
36746a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Failed to create copy of request";
36846a81465SCarson Labrado             if (isCollection != AggregationType::Collection)
36946a81465SCarson Labrado             {
37046a81465SCarson Labrado                 messages::internalError(asyncResp->res);
37146a81465SCarson Labrado             }
37246a81465SCarson Labrado             return;
37346a81465SCarson Labrado         }
37446a81465SCarson Labrado 
37546a81465SCarson Labrado         getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection,
37646a81465SCarson Labrado                                             localReq, asyncResp));
37746a81465SCarson Labrado     }
37846a81465SCarson Labrado 
37946a81465SCarson Labrado     static void findSatelite(
38046a81465SCarson Labrado         const crow::Request& req,
38146a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
38246a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo,
38346a81465SCarson Labrado         std::string_view memberName)
38446a81465SCarson Labrado     {
38546a81465SCarson Labrado         // Determine if the resource ID begins with a known prefix
38646a81465SCarson Labrado         for (const auto& satellite : satelliteInfo)
38746a81465SCarson Labrado         {
38846a81465SCarson Labrado             std::string targetPrefix = satellite.first;
38946a81465SCarson Labrado             targetPrefix += "_";
39046a81465SCarson Labrado             if (memberName.starts_with(targetPrefix))
39146a81465SCarson Labrado             {
39246a81465SCarson Labrado                 BMCWEB_LOG_DEBUG << "\"" << satellite.first
39346a81465SCarson Labrado                                  << "\" is a known prefix";
39446a81465SCarson Labrado 
39546a81465SCarson Labrado                 // Remove the known prefix from the request's URI and
39646a81465SCarson Labrado                 // then forward to the associated satellite BMC
39746a81465SCarson Labrado                 getInstance().forwardRequest(req, asyncResp, satellite.first,
39846a81465SCarson Labrado                                              satelliteInfo);
39946a81465SCarson Labrado                 return;
40046a81465SCarson Labrado             }
40146a81465SCarson Labrado         }
40246a81465SCarson Labrado     }
40346a81465SCarson Labrado 
40446a81465SCarson Labrado     // Intended to handle an incoming request based on if Redfish Aggregation
40546a81465SCarson Labrado     // is enabled.  Forwards request to satellite BMC if it exists.
40646a81465SCarson Labrado     static void aggregateAndHandle(
40746a81465SCarson Labrado         AggregationType isCollection,
40846a81465SCarson Labrado         const std::shared_ptr<crow::Request>& sharedReq,
40946a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
41046a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
41146a81465SCarson Labrado     {
41246a81465SCarson Labrado         if (sharedReq == nullptr)
41346a81465SCarson Labrado         {
41446a81465SCarson Labrado             return;
41546a81465SCarson Labrado         }
41646a81465SCarson Labrado         const crow::Request& thisReq = *sharedReq;
41746a81465SCarson Labrado         BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of "
41846a81465SCarson Labrado                          << thisReq.target();
41946a81465SCarson Labrado 
42046a81465SCarson Labrado         // We previously determined the request is for a collection.  No need to
42146a81465SCarson Labrado         // check again
42246a81465SCarson Labrado         if (isCollection == AggregationType::Collection)
42346a81465SCarson Labrado         {
42446a81465SCarson Labrado             // TODO: This should instead be handled so that we can
42546a81465SCarson Labrado             // aggregate the satellite resource collections
42646a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Aggregating a collection";
42746a81465SCarson Labrado             return;
42846a81465SCarson Labrado         }
42946a81465SCarson Labrado 
43046a81465SCarson Labrado         std::string updateServiceName;
43146a81465SCarson Labrado         std::string memberName;
43246a81465SCarson Labrado         if (crow::utility::readUrlSegments(
43346a81465SCarson Labrado                 thisReq.urlView, "redfish", "v1", "UpdateService",
43446a81465SCarson Labrado                 std::ref(updateServiceName), std::ref(memberName),
43546a81465SCarson Labrado                 crow::utility::OrMorePaths()))
43646a81465SCarson Labrado         {
43746a81465SCarson Labrado             // Must be FirmwareInventory or SoftwareInventory
43846a81465SCarson Labrado             findSatelite(thisReq, asyncResp, satelliteInfo, memberName);
43946a81465SCarson Labrado             return;
44046a81465SCarson Labrado         }
44146a81465SCarson Labrado 
44246a81465SCarson Labrado         std::string collectionName;
44346a81465SCarson Labrado         if (crow::utility::readUrlSegments(
44446a81465SCarson Labrado                 thisReq.urlView, "redfish", "v1", std::ref(collectionName),
44546a81465SCarson Labrado                 std::ref(memberName), crow::utility::OrMorePaths()))
44646a81465SCarson Labrado         {
44746a81465SCarson Labrado             findSatelite(thisReq, asyncResp, satelliteInfo, memberName);
44846a81465SCarson Labrado         }
44946a81465SCarson Labrado     }
45046a81465SCarson Labrado 
45146a81465SCarson Labrado     // Attempt to forward a request to the satellite BMC associated with the
45246a81465SCarson Labrado     // prefix.
45346a81465SCarson Labrado     void forwardRequest(
45446a81465SCarson Labrado         const crow::Request& thisReq,
45546a81465SCarson Labrado         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
45646a81465SCarson Labrado         const std::string& prefix,
45746a81465SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
45846a81465SCarson Labrado     {
45946a81465SCarson Labrado         const auto& sat = satelliteInfo.find(prefix);
46046a81465SCarson Labrado         if (sat == satelliteInfo.end())
46146a81465SCarson Labrado         {
46246a81465SCarson Labrado             // Realistically this shouldn't get called since we perform an
46346a81465SCarson Labrado             // earlier check to make sure the prefix exists
46446a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix
46546a81465SCarson Labrado                              << "\"";
46646a81465SCarson Labrado             return;
46746a81465SCarson Labrado         }
46846a81465SCarson Labrado 
46946a81465SCarson Labrado         // We need to strip the prefix from the request's path
47046a81465SCarson Labrado         std::string targetURI(thisReq.target());
47146a81465SCarson Labrado         size_t pos = targetURI.find(prefix + "_");
47246a81465SCarson Labrado         if (pos == std::string::npos)
47346a81465SCarson Labrado         {
47446a81465SCarson Labrado             // If this fails then something went wrong
47546a81465SCarson Labrado             BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix
47646a81465SCarson Labrado                              << "_\" from request URI";
47746a81465SCarson Labrado             messages::internalError(asyncResp->res);
47846a81465SCarson Labrado             return;
47946a81465SCarson Labrado         }
48046a81465SCarson Labrado         targetURI.erase(pos, prefix.size() + 1);
48146a81465SCarson Labrado 
48246a81465SCarson Labrado         std::function<void(crow::Response&)> cb =
483*1c0bb5c6SCarson Labrado             std::bind_front(processResponse, prefix, asyncResp);
48446a81465SCarson Labrado 
48546a81465SCarson Labrado         std::string data = thisReq.req.body();
48646a81465SCarson Labrado         crow::HttpClient::getInstance().sendDataWithCallback(
48746a81465SCarson Labrado             data, id, std::string(sat->second.host()),
48846a81465SCarson Labrado             sat->second.port_number(), targetURI, thisReq.fields,
48946a81465SCarson Labrado             thisReq.method(), retryPolicyName, cb);
49046a81465SCarson Labrado     }
49146a81465SCarson Labrado 
49246a81465SCarson Labrado     // Processes the response returned by a satellite BMC and loads its
49346a81465SCarson Labrado     // contents into asyncResp
49446a81465SCarson Labrado     static void
495*1c0bb5c6SCarson Labrado         processResponse(std::string_view prefix,
496*1c0bb5c6SCarson Labrado                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
49746a81465SCarson Labrado                         crow::Response& resp)
49846a81465SCarson Labrado     {
49946a81465SCarson Labrado         // No processing needed if the request wasn't successful
50046a81465SCarson Labrado         if (resp.resultInt() != 200)
50146a81465SCarson Labrado         {
50246a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "No need to parse satellite response";
50346a81465SCarson Labrado             asyncResp->res.stringResponse = std::move(resp.stringResponse);
50446a81465SCarson Labrado             return;
50546a81465SCarson Labrado         }
50646a81465SCarson Labrado 
50746a81465SCarson Labrado         // The resp will not have a json component
50846a81465SCarson Labrado         // We need to create a json from resp's stringResponse
50946a81465SCarson Labrado         if (resp.getHeaderValue("Content-Type") == "application/json")
51046a81465SCarson Labrado         {
51146a81465SCarson Labrado             nlohmann::json jsonVal =
51246a81465SCarson Labrado                 nlohmann::json::parse(resp.body(), nullptr, false);
51346a81465SCarson Labrado             if (jsonVal.is_discarded())
51446a81465SCarson Labrado             {
51546a81465SCarson Labrado                 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON";
51646a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
51746a81465SCarson Labrado                 return;
51846a81465SCarson Labrado             }
51946a81465SCarson Labrado 
52046a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Successfully parsed satellite response";
52146a81465SCarson Labrado 
52246a81465SCarson Labrado             // TODO: For collections we  want to add the satellite responses to
52346a81465SCarson Labrado             // our response rather than just straight overwriting them if our
52446a81465SCarson Labrado             // local handling was successful (i.e. would return a 200).
52546a81465SCarson Labrado 
526*1c0bb5c6SCarson Labrado             addPrefixes(jsonVal, prefix);
527*1c0bb5c6SCarson Labrado 
528*1c0bb5c6SCarson Labrado             BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response";
529*1c0bb5c6SCarson Labrado 
53046a81465SCarson Labrado             asyncResp->res.stringResponse.emplace(
53146a81465SCarson Labrado                 boost::beast::http::response<
53246a81465SCarson Labrado                     boost::beast::http::string_body>{});
53346a81465SCarson Labrado             asyncResp->res.result(resp.result());
53446a81465SCarson Labrado             asyncResp->res.jsonValue = std::move(jsonVal);
53546a81465SCarson Labrado 
53646a81465SCarson Labrado             BMCWEB_LOG_DEBUG << "Finished writing asyncResp";
53746a81465SCarson Labrado         }
53846a81465SCarson Labrado         else
53946a81465SCarson Labrado         {
54046a81465SCarson Labrado             if (!resp.body().empty())
54146a81465SCarson Labrado             {
54246a81465SCarson Labrado                 // We received a 200 response without the correct Content-Type
54346a81465SCarson Labrado                 // so return an Operation Failed error
54446a81465SCarson Labrado                 BMCWEB_LOG_ERROR
54546a81465SCarson Labrado                     << "Satellite response must be of type \"application/json\"";
54646a81465SCarson Labrado                 messages::operationFailed(asyncResp->res);
54746a81465SCarson Labrado             }
54846a81465SCarson Labrado         }
54946a81465SCarson Labrado     }
55046a81465SCarson Labrado 
5517fb33566SCarson Labrado   public:
5527fb33566SCarson Labrado     RedfishAggregator(const RedfishAggregator&) = delete;
5537fb33566SCarson Labrado     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
5547fb33566SCarson Labrado     RedfishAggregator(RedfishAggregator&&) = delete;
5557fb33566SCarson Labrado     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
5567fb33566SCarson Labrado     ~RedfishAggregator() = default;
5577fb33566SCarson Labrado 
5587fb33566SCarson Labrado     static RedfishAggregator& getInstance()
5597fb33566SCarson Labrado     {
5607fb33566SCarson Labrado         static RedfishAggregator handler;
5617fb33566SCarson Labrado         return handler;
5627fb33566SCarson Labrado     }
56305916cefSCarson Labrado 
56405916cefSCarson Labrado     // Entry point to Redfish Aggregation
56505916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
56605916cefSCarson Labrado     // request
56705916cefSCarson Labrado     static Result
56805916cefSCarson Labrado         beginAggregation(const crow::Request& thisReq,
56905916cefSCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
57005916cefSCarson Labrado     {
57105916cefSCarson Labrado         using crow::utility::OrMorePaths;
57205916cefSCarson Labrado         using crow::utility::readUrlSegments;
57305916cefSCarson Labrado         const boost::urls::url_view& url = thisReq.urlView;
57405916cefSCarson Labrado         // UpdateService is the only top level resource that is not a Collection
57505916cefSCarson Labrado         if (readUrlSegments(url, "redfish", "v1", "UpdateService"))
57605916cefSCarson Labrado         {
57705916cefSCarson Labrado             return Result::LocalHandle;
57805916cefSCarson Labrado         }
57946a81465SCarson Labrado         if (readUrlSegments(url, "redfish", "v1", "UpdateService",
58046a81465SCarson Labrado                             "SoftwareInventory") ||
58146a81465SCarson Labrado             readUrlSegments(url, "redfish", "v1", "UpdateService",
58246a81465SCarson Labrado                             "FirmwareInventory"))
58346a81465SCarson Labrado         {
58446a81465SCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
58546a81465SCarson Labrado             return Result::LocalHandle;
58646a81465SCarson Labrado         }
58705916cefSCarson Labrado 
58805916cefSCarson Labrado         // Is the request for a resource collection?:
58905916cefSCarson Labrado         // /redfish/v1/<resource>
59005916cefSCarson Labrado         // e.g. /redfish/v1/Chassis
59105916cefSCarson Labrado         std::string collectionName;
59205916cefSCarson Labrado         if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName)))
59305916cefSCarson Labrado         {
59446a81465SCarson Labrado             startAggregation(AggregationType::Collection, thisReq, asyncResp);
59505916cefSCarson Labrado             return Result::LocalHandle;
59605916cefSCarson Labrado         }
59705916cefSCarson Labrado 
59805916cefSCarson Labrado         // We know that the ID of an aggregated resource will begin with
59905916cefSCarson Labrado         // "5B247A".  For the most part the URI will begin like this:
60005916cefSCarson Labrado         // /redfish/v1/<resource>/<resource ID>
60105916cefSCarson Labrado         // Note, FirmwareInventory and SoftwareInventory are "special" because
60205916cefSCarson Labrado         // they are two levels deep, but still need aggregated
60305916cefSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID>
60405916cefSCarson Labrado         // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID>
60505916cefSCarson Labrado         std::string memberName;
60605916cefSCarson Labrado         if (readUrlSegments(url, "redfish", "v1", "UpdateService",
60705916cefSCarson Labrado                             "SoftwareInventory", std::ref(memberName),
60805916cefSCarson Labrado                             OrMorePaths()) ||
60905916cefSCarson Labrado             readUrlSegments(url, "redfish", "v1", "UpdateService",
61005916cefSCarson Labrado                             "FirmwareInventory", std::ref(memberName),
61105916cefSCarson Labrado                             OrMorePaths()) ||
61205916cefSCarson Labrado             readUrlSegments(url, "redfish", "v1", std::ref(collectionName),
61305916cefSCarson Labrado                             std::ref(memberName), OrMorePaths()))
61405916cefSCarson Labrado         {
61505916cefSCarson Labrado             if (memberName.starts_with("5B247A"))
61605916cefSCarson Labrado             {
61705916cefSCarson Labrado                 BMCWEB_LOG_DEBUG << "Need to forward a request";
61805916cefSCarson Labrado 
61946a81465SCarson Labrado                 // Extract the prefix from the request's URI, retrieve the
62046a81465SCarson Labrado                 // associated satellite config information, and then forward the
62146a81465SCarson Labrado                 // request to that satellite.
62246a81465SCarson Labrado                 startAggregation(AggregationType::Resource, thisReq, asyncResp);
62305916cefSCarson Labrado                 return Result::NoLocalHandle;
62405916cefSCarson Labrado             }
62505916cefSCarson Labrado             return Result::LocalHandle;
62605916cefSCarson Labrado         }
62705916cefSCarson Labrado 
62805916cefSCarson Labrado         BMCWEB_LOG_DEBUG << "Aggregation not required";
62905916cefSCarson Labrado         return Result::LocalHandle;
63005916cefSCarson Labrado     }
6317fb33566SCarson Labrado };
6327fb33566SCarson Labrado 
6337fb33566SCarson Labrado } // namespace redfish
634