xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 05916cef0fce4dd2532e1e7e33f6abc4dcf339be)
17fb33566SCarson Labrado #pragma once
27fb33566SCarson Labrado 
37fb33566SCarson Labrado #include <http_client.hpp>
47fb33566SCarson Labrado 
57fb33566SCarson Labrado namespace redfish
67fb33566SCarson Labrado {
77fb33566SCarson Labrado 
8*05916cefSCarson Labrado enum class Result
9*05916cefSCarson Labrado {
10*05916cefSCarson Labrado     LocalHandle,
11*05916cefSCarson Labrado     NoLocalHandle
12*05916cefSCarson Labrado };
13*05916cefSCarson Labrado 
14*05916cefSCarson Labrado // Checks if the provided path is related to one of the resource collections
15*05916cefSCarson Labrado // under UpdateService
16*05916cefSCarson Labrado static inline bool
17*05916cefSCarson Labrado     isUpdateServiceCollection(const std::vector<std::string>& segments)
18*05916cefSCarson Labrado {
19*05916cefSCarson Labrado     if (segments.size() < 4)
20*05916cefSCarson Labrado     {
21*05916cefSCarson Labrado         return false;
22*05916cefSCarson Labrado     }
23*05916cefSCarson Labrado 
24*05916cefSCarson Labrado     return (segments[2] == "UpdateService") &&
25*05916cefSCarson Labrado            ((segments[3] == "FirmwareInventory") ||
26*05916cefSCarson Labrado             (segments[3] == "SoftwareInventory"));
27*05916cefSCarson Labrado }
28*05916cefSCarson Labrado 
297fb33566SCarson Labrado class RedfishAggregator
307fb33566SCarson Labrado {
317fb33566SCarson Labrado   private:
32a7a80296SCarson Labrado     const std::string retryPolicyName = "RedfishAggregation";
33a7a80296SCarson Labrado     const uint32_t retryAttempts = 5;
34a7a80296SCarson Labrado     const uint32_t retryTimeoutInterval = 0;
35a7a80296SCarson Labrado 
367fb33566SCarson Labrado     RedfishAggregator()
377fb33566SCarson Labrado     {
387fb33566SCarson Labrado         getSatelliteConfigs(constructorCallback);
39a7a80296SCarson Labrado 
40a7a80296SCarson Labrado         // Setup the retry policy to be used by Redfish Aggregation
41a7a80296SCarson Labrado         crow::HttpClient::getInstance().setRetryConfig(
42a7a80296SCarson Labrado             retryAttempts, retryTimeoutInterval, aggregationRetryHandler,
43a7a80296SCarson Labrado             retryPolicyName);
447fb33566SCarson Labrado     }
457fb33566SCarson Labrado 
46a7a80296SCarson Labrado     static inline boost::system::error_code
47a7a80296SCarson Labrado         aggregationRetryHandler(unsigned int respCode)
48a7a80296SCarson Labrado     {
49a7a80296SCarson Labrado         // As a default, assume 200X is alright.
50a7a80296SCarson Labrado         // We don't need to retry on a 404
51a7a80296SCarson Labrado         if ((respCode < 200) || ((respCode >= 300) && (respCode != 404)))
52a7a80296SCarson Labrado         {
53a7a80296SCarson Labrado             return boost::system::errc::make_error_code(
54a7a80296SCarson Labrado                 boost::system::errc::result_out_of_range);
55a7a80296SCarson Labrado         }
56a7a80296SCarson Labrado 
57a7a80296SCarson Labrado         // Return 0 if the response code is valid
58a7a80296SCarson Labrado         return boost::system::errc::make_error_code(
59a7a80296SCarson Labrado             boost::system::errc::success);
609fa6d147SNan Zhou     }
61a7a80296SCarson Labrado 
627fb33566SCarson Labrado     // Dummy callback used by the Constructor so that it can report the number
637fb33566SCarson Labrado     // of satellite configs when the class is first created
647fb33566SCarson Labrado     static void constructorCallback(
657fb33566SCarson Labrado         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
667fb33566SCarson Labrado     {
677fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << "There were "
687fb33566SCarson Labrado                          << std::to_string(satelliteInfo.size())
697fb33566SCarson Labrado                          << " satellite configs found at startup";
707fb33566SCarson Labrado     }
717fb33566SCarson Labrado 
727fb33566SCarson Labrado     // Polls D-Bus to get all available satellite config information
737fb33566SCarson Labrado     // Expects a handler which interacts with the returned configs
747fb33566SCarson Labrado     static void getSatelliteConfigs(
757fb33566SCarson Labrado         const std::function<void(
767fb33566SCarson Labrado             const std::unordered_map<std::string, boost::urls::url>&)>& handler)
777fb33566SCarson Labrado     {
787fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << "Gathering satellite configs";
797fb33566SCarson Labrado         crow::connections::systemBus->async_method_call(
807fb33566SCarson Labrado             [handler](const boost::system::error_code ec,
817fb33566SCarson Labrado                       const dbus::utility::ManagedObjectType& objects) {
827fb33566SCarson Labrado             if (ec)
837fb33566SCarson Labrado             {
84002d39b4SEd Tanous                 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", "
85002d39b4SEd Tanous                                  << ec.message();
867fb33566SCarson Labrado                 return;
877fb33566SCarson Labrado             }
887fb33566SCarson Labrado 
897fb33566SCarson Labrado             // Maps a chosen alias representing a satellite BMC to a url
907fb33566SCarson Labrado             // containing the information required to create a http
917fb33566SCarson Labrado             // connection to the satellite
927fb33566SCarson Labrado             std::unordered_map<std::string, boost::urls::url> satelliteInfo;
937fb33566SCarson Labrado 
947fb33566SCarson Labrado             findSatelliteConfigs(objects, satelliteInfo);
957fb33566SCarson Labrado 
967fb33566SCarson Labrado             if (!satelliteInfo.empty())
977fb33566SCarson Labrado             {
987fb33566SCarson Labrado                 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with "
997fb33566SCarson Labrado                                  << std::to_string(satelliteInfo.size())
1007fb33566SCarson Labrado                                  << " satellite BMCs";
1017fb33566SCarson Labrado             }
1027fb33566SCarson Labrado             else
1037fb33566SCarson Labrado             {
1047fb33566SCarson Labrado                 BMCWEB_LOG_DEBUG
1057fb33566SCarson Labrado                     << "No satellite BMCs detected.  Redfish Aggregation not enabled";
1067fb33566SCarson Labrado             }
1077fb33566SCarson Labrado             handler(satelliteInfo);
1087fb33566SCarson Labrado             },
1097fb33566SCarson Labrado             "xyz.openbmc_project.EntityManager", "/",
1107fb33566SCarson Labrado             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1117fb33566SCarson Labrado     }
1127fb33566SCarson Labrado 
1137fb33566SCarson Labrado     // Search D-Bus objects for satellite config objects and add their
1147fb33566SCarson Labrado     // information if valid
1157fb33566SCarson Labrado     static void findSatelliteConfigs(
1167fb33566SCarson Labrado         const dbus::utility::ManagedObjectType& objects,
1177fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
1187fb33566SCarson Labrado     {
1197fb33566SCarson Labrado         for (const auto& objectPath : objects)
1207fb33566SCarson Labrado         {
1217fb33566SCarson Labrado             for (const auto& interface : objectPath.second)
1227fb33566SCarson Labrado             {
1237fb33566SCarson Labrado                 if (interface.first ==
1247fb33566SCarson Labrado                     "xyz.openbmc_project.Configuration.SatelliteController")
1257fb33566SCarson Labrado                 {
1267fb33566SCarson Labrado                     BMCWEB_LOG_DEBUG << "Found Satellite Controller at "
1277fb33566SCarson Labrado                                      << objectPath.first.str;
1287fb33566SCarson Labrado 
129*05916cefSCarson Labrado                     if (!satelliteInfo.empty())
130*05916cefSCarson Labrado                     {
131*05916cefSCarson Labrado                         BMCWEB_LOG_ERROR
132*05916cefSCarson Labrado                             << "Redfish Aggregation only supports one satellite!";
133*05916cefSCarson Labrado                         BMCWEB_LOG_DEBUG << "Clearing all satellite data";
134*05916cefSCarson Labrado                         satelliteInfo.clear();
135*05916cefSCarson Labrado                         return;
136*05916cefSCarson Labrado                     }
137*05916cefSCarson Labrado 
138*05916cefSCarson Labrado                     // For now assume there will only be one satellite config.
139*05916cefSCarson Labrado                     // Assign it the name/prefix "5B247A"
140*05916cefSCarson Labrado                     addSatelliteConfig("5B247A", interface.second,
141*05916cefSCarson Labrado                                        satelliteInfo);
1427fb33566SCarson Labrado                 }
1437fb33566SCarson Labrado             }
1447fb33566SCarson Labrado         }
1457fb33566SCarson Labrado     }
1467fb33566SCarson Labrado 
1477fb33566SCarson Labrado     // Parse the properties of a satellite config object and add the
1487fb33566SCarson Labrado     // configuration if the properties are valid
1497fb33566SCarson Labrado     static void addSatelliteConfig(
150*05916cefSCarson Labrado         const std::string& name,
1517fb33566SCarson Labrado         const dbus::utility::DBusPropertiesMap& properties,
1527fb33566SCarson Labrado         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
1537fb33566SCarson Labrado     {
1547fb33566SCarson Labrado         boost::urls::url url;
1557fb33566SCarson Labrado 
1567fb33566SCarson Labrado         for (const auto& prop : properties)
1577fb33566SCarson Labrado         {
158*05916cefSCarson Labrado             if (prop.first == "Hostname")
1597fb33566SCarson Labrado             {
1607fb33566SCarson Labrado                 const std::string* propVal =
1617fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
1627fb33566SCarson Labrado                 if (propVal == nullptr)
1637fb33566SCarson Labrado                 {
1647fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid Hostname value";
1657fb33566SCarson Labrado                     return;
1667fb33566SCarson Labrado                 }
1677fb33566SCarson Labrado                 url.set_host(*propVal);
1687fb33566SCarson Labrado             }
1697fb33566SCarson Labrado 
1707fb33566SCarson Labrado             else if (prop.first == "Port")
1717fb33566SCarson Labrado             {
1727fb33566SCarson Labrado                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
1737fb33566SCarson Labrado                 if (propVal == nullptr)
1747fb33566SCarson Labrado                 {
1757fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid Port value";
1767fb33566SCarson Labrado                     return;
1777fb33566SCarson Labrado                 }
1787fb33566SCarson Labrado 
1797fb33566SCarson Labrado                 if (*propVal > std::numeric_limits<uint16_t>::max())
1807fb33566SCarson Labrado                 {
1817fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Port value out of range";
1827fb33566SCarson Labrado                     return;
1837fb33566SCarson Labrado                 }
1847fb33566SCarson Labrado                 url.set_port(static_cast<uint16_t>(*propVal));
1857fb33566SCarson Labrado             }
1867fb33566SCarson Labrado 
1877fb33566SCarson Labrado             else if (prop.first == "AuthType")
1887fb33566SCarson Labrado             {
1897fb33566SCarson Labrado                 const std::string* propVal =
1907fb33566SCarson Labrado                     std::get_if<std::string>(&prop.second);
1917fb33566SCarson Labrado                 if (propVal == nullptr)
1927fb33566SCarson Labrado                 {
1937fb33566SCarson Labrado                     BMCWEB_LOG_ERROR << "Invalid AuthType value";
1947fb33566SCarson Labrado                     return;
1957fb33566SCarson Labrado                 }
1967fb33566SCarson Labrado 
1977fb33566SCarson Labrado                 // For now assume authentication not required to communicate
1987fb33566SCarson Labrado                 // with the satellite BMC
1997fb33566SCarson Labrado                 if (*propVal != "None")
2007fb33566SCarson Labrado                 {
2017fb33566SCarson Labrado                     BMCWEB_LOG_ERROR
2027fb33566SCarson Labrado                         << "Unsupported AuthType value: " << *propVal
2037fb33566SCarson Labrado                         << ", only \"none\" is supported";
2047fb33566SCarson Labrado                     return;
2057fb33566SCarson Labrado                 }
2067fb33566SCarson Labrado                 url.set_scheme("http");
2077fb33566SCarson Labrado             }
2087fb33566SCarson Labrado         } // Finished reading properties
2097fb33566SCarson Labrado 
2107fb33566SCarson Labrado         // Make sure all required config information was made available
2117fb33566SCarson Labrado         if (url.host().empty())
2127fb33566SCarson Labrado         {
2137fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host";
2147fb33566SCarson Labrado             return;
2157fb33566SCarson Labrado         }
2167fb33566SCarson Labrado 
2177fb33566SCarson Labrado         if (!url.has_port())
2187fb33566SCarson Labrado         {
2197fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port";
2207fb33566SCarson Labrado             return;
2217fb33566SCarson Labrado         }
2227fb33566SCarson Labrado 
2237fb33566SCarson Labrado         if (!url.has_scheme())
2247fb33566SCarson Labrado         {
2257fb33566SCarson Labrado             BMCWEB_LOG_ERROR << "Satellite config " << name
2267fb33566SCarson Labrado                              << " missing AuthType";
2277fb33566SCarson Labrado             return;
2287fb33566SCarson Labrado         }
2297fb33566SCarson Labrado 
2307fb33566SCarson Labrado         std::string resultString;
2317fb33566SCarson Labrado         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
2327fb33566SCarson Labrado         if (result.second)
2337fb33566SCarson Labrado         {
2347fb33566SCarson Labrado             resultString = "Added new satellite config ";
2357fb33566SCarson Labrado         }
2367fb33566SCarson Labrado         else
2377fb33566SCarson Labrado         {
2387fb33566SCarson Labrado             resultString = "Updated existing satellite config ";
2397fb33566SCarson Labrado         }
2407fb33566SCarson Labrado 
2417fb33566SCarson Labrado         BMCWEB_LOG_DEBUG << resultString << name << " at "
2427fb33566SCarson Labrado                          << result.first->second.scheme() << "://"
2437fb33566SCarson Labrado                          << result.first->second.encoded_host_and_port();
2447fb33566SCarson Labrado     }
2457fb33566SCarson Labrado 
2467fb33566SCarson Labrado   public:
2477fb33566SCarson Labrado     RedfishAggregator(const RedfishAggregator&) = delete;
2487fb33566SCarson Labrado     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
2497fb33566SCarson Labrado     RedfishAggregator(RedfishAggregator&&) = delete;
2507fb33566SCarson Labrado     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
2517fb33566SCarson Labrado     ~RedfishAggregator() = default;
2527fb33566SCarson Labrado 
2537fb33566SCarson Labrado     static RedfishAggregator& getInstance()
2547fb33566SCarson Labrado     {
2557fb33566SCarson Labrado         static RedfishAggregator handler;
2567fb33566SCarson Labrado         return handler;
2577fb33566SCarson Labrado     }
258*05916cefSCarson Labrado 
259*05916cefSCarson Labrado     // Entry point to Redfish Aggregation
260*05916cefSCarson Labrado     // Returns Result stating whether or not we still need to locally handle the
261*05916cefSCarson Labrado     // request
262*05916cefSCarson Labrado     static Result
263*05916cefSCarson Labrado         beginAggregation(const crow::Request& thisReq,
264*05916cefSCarson Labrado                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
265*05916cefSCarson Labrado     {
266*05916cefSCarson Labrado         using crow::utility::OrMorePaths;
267*05916cefSCarson Labrado         using crow::utility::readUrlSegments;
268*05916cefSCarson Labrado         const boost::urls::url_view& url = thisReq.urlView;
269*05916cefSCarson Labrado         // UpdateService is the only top level resource that is not a Collection
270*05916cefSCarson Labrado         if (readUrlSegments(url, "redfish", "v1", "UpdateService"))
271*05916cefSCarson Labrado         {
272*05916cefSCarson Labrado             return Result::LocalHandle;
273*05916cefSCarson Labrado         }
274*05916cefSCarson Labrado 
275*05916cefSCarson Labrado         // Is the request for a resource collection?:
276*05916cefSCarson Labrado         // /redfish/v1/<resource>
277*05916cefSCarson Labrado         // e.g. /redfish/v1/Chassis
278*05916cefSCarson Labrado         std::string collectionName;
279*05916cefSCarson Labrado         if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName)))
280*05916cefSCarson Labrado         {
281*05916cefSCarson Labrado             return Result::LocalHandle;
282*05916cefSCarson Labrado         }
283*05916cefSCarson Labrado 
284*05916cefSCarson Labrado         // We know that the ID of an aggregated resource will begin with
285*05916cefSCarson Labrado         // "5B247A".  For the most part the URI will begin like this:
286*05916cefSCarson Labrado         // /redfish/v1/<resource>/<resource ID>
287*05916cefSCarson Labrado         // Note, FirmwareInventory and SoftwareInventory are "special" because
288*05916cefSCarson Labrado         // they are two levels deep, but still need aggregated
289*05916cefSCarson Labrado         // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID>
290*05916cefSCarson Labrado         // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID>
291*05916cefSCarson Labrado         std::string memberName;
292*05916cefSCarson Labrado         if (readUrlSegments(url, "redfish", "v1", "UpdateService",
293*05916cefSCarson Labrado                             "SoftwareInventory", std::ref(memberName),
294*05916cefSCarson Labrado                             OrMorePaths()) ||
295*05916cefSCarson Labrado             readUrlSegments(url, "redfish", "v1", "UpdateService",
296*05916cefSCarson Labrado                             "FirmwareInventory", std::ref(memberName),
297*05916cefSCarson Labrado                             OrMorePaths()) ||
298*05916cefSCarson Labrado             readUrlSegments(url, "redfish", "v1", std::ref(collectionName),
299*05916cefSCarson Labrado                             std::ref(memberName), OrMorePaths()))
300*05916cefSCarson Labrado         {
301*05916cefSCarson Labrado             if (memberName.starts_with("5B247A"))
302*05916cefSCarson Labrado             {
303*05916cefSCarson Labrado                 BMCWEB_LOG_DEBUG << "Need to forward a request";
304*05916cefSCarson Labrado 
305*05916cefSCarson Labrado                 // TODO: Extract the prefix from the request's URI, retrieve
306*05916cefSCarson Labrado                 // the associated satellite config information, and then
307*05916cefSCarson Labrado                 // forward the request to that satellite.
308*05916cefSCarson Labrado                 redfish::messages::internalError(asyncResp->res);
309*05916cefSCarson Labrado                 return Result::NoLocalHandle;
310*05916cefSCarson Labrado             }
311*05916cefSCarson Labrado             return Result::LocalHandle;
312*05916cefSCarson Labrado         }
313*05916cefSCarson Labrado 
314*05916cefSCarson Labrado         BMCWEB_LOG_DEBUG << "Aggregation not required";
315*05916cefSCarson Labrado         return Result::LocalHandle;
316*05916cefSCarson Labrado     }
3177fb33566SCarson Labrado };
3187fb33566SCarson Labrado 
3197fb33566SCarson Labrado } // namespace redfish
320