xref: /openbmc/bmcweb/features/redfish/include/redfish_aggregator.hpp (revision 05916cef0fce4dd2532e1e7e33f6abc4dcf339be)
1 #pragma once
2 
3 #include <http_client.hpp>
4 
5 namespace redfish
6 {
7 
8 enum class Result
9 {
10     LocalHandle,
11     NoLocalHandle
12 };
13 
14 // Checks if the provided path is related to one of the resource collections
15 // under UpdateService
16 static inline bool
17     isUpdateServiceCollection(const std::vector<std::string>& segments)
18 {
19     if (segments.size() < 4)
20     {
21         return false;
22     }
23 
24     return (segments[2] == "UpdateService") &&
25            ((segments[3] == "FirmwareInventory") ||
26             (segments[3] == "SoftwareInventory"));
27 }
28 
29 class RedfishAggregator
30 {
31   private:
32     const std::string retryPolicyName = "RedfishAggregation";
33     const uint32_t retryAttempts = 5;
34     const uint32_t retryTimeoutInterval = 0;
35 
36     RedfishAggregator()
37     {
38         getSatelliteConfigs(constructorCallback);
39 
40         // Setup the retry policy to be used by Redfish Aggregation
41         crow::HttpClient::getInstance().setRetryConfig(
42             retryAttempts, retryTimeoutInterval, aggregationRetryHandler,
43             retryPolicyName);
44     }
45 
46     static inline boost::system::error_code
47         aggregationRetryHandler(unsigned int respCode)
48     {
49         // As a default, assume 200X is alright.
50         // We don't need to retry on a 404
51         if ((respCode < 200) || ((respCode >= 300) && (respCode != 404)))
52         {
53             return boost::system::errc::make_error_code(
54                 boost::system::errc::result_out_of_range);
55         }
56 
57         // Return 0 if the response code is valid
58         return boost::system::errc::make_error_code(
59             boost::system::errc::success);
60     }
61 
62     // Dummy callback used by the Constructor so that it can report the number
63     // of satellite configs when the class is first created
64     static void constructorCallback(
65         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
66     {
67         BMCWEB_LOG_DEBUG << "There were "
68                          << std::to_string(satelliteInfo.size())
69                          << " satellite configs found at startup";
70     }
71 
72     // Polls D-Bus to get all available satellite config information
73     // Expects a handler which interacts with the returned configs
74     static void getSatelliteConfigs(
75         const std::function<void(
76             const std::unordered_map<std::string, boost::urls::url>&)>& handler)
77     {
78         BMCWEB_LOG_DEBUG << "Gathering satellite configs";
79         crow::connections::systemBus->async_method_call(
80             [handler](const boost::system::error_code ec,
81                       const dbus::utility::ManagedObjectType& objects) {
82             if (ec)
83             {
84                 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", "
85                                  << ec.message();
86                 return;
87             }
88 
89             // Maps a chosen alias representing a satellite BMC to a url
90             // containing the information required to create a http
91             // connection to the satellite
92             std::unordered_map<std::string, boost::urls::url> satelliteInfo;
93 
94             findSatelliteConfigs(objects, satelliteInfo);
95 
96             if (!satelliteInfo.empty())
97             {
98                 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with "
99                                  << std::to_string(satelliteInfo.size())
100                                  << " satellite BMCs";
101             }
102             else
103             {
104                 BMCWEB_LOG_DEBUG
105                     << "No satellite BMCs detected.  Redfish Aggregation not enabled";
106             }
107             handler(satelliteInfo);
108             },
109             "xyz.openbmc_project.EntityManager", "/",
110             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
111     }
112 
113     // Search D-Bus objects for satellite config objects and add their
114     // information if valid
115     static void findSatelliteConfigs(
116         const dbus::utility::ManagedObjectType& objects,
117         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
118     {
119         for (const auto& objectPath : objects)
120         {
121             for (const auto& interface : objectPath.second)
122             {
123                 if (interface.first ==
124                     "xyz.openbmc_project.Configuration.SatelliteController")
125                 {
126                     BMCWEB_LOG_DEBUG << "Found Satellite Controller at "
127                                      << objectPath.first.str;
128 
129                     if (!satelliteInfo.empty())
130                     {
131                         BMCWEB_LOG_ERROR
132                             << "Redfish Aggregation only supports one satellite!";
133                         BMCWEB_LOG_DEBUG << "Clearing all satellite data";
134                         satelliteInfo.clear();
135                         return;
136                     }
137 
138                     // For now assume there will only be one satellite config.
139                     // Assign it the name/prefix "5B247A"
140                     addSatelliteConfig("5B247A", interface.second,
141                                        satelliteInfo);
142                 }
143             }
144         }
145     }
146 
147     // Parse the properties of a satellite config object and add the
148     // configuration if the properties are valid
149     static void addSatelliteConfig(
150         const std::string& name,
151         const dbus::utility::DBusPropertiesMap& properties,
152         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
153     {
154         boost::urls::url url;
155 
156         for (const auto& prop : properties)
157         {
158             if (prop.first == "Hostname")
159             {
160                 const std::string* propVal =
161                     std::get_if<std::string>(&prop.second);
162                 if (propVal == nullptr)
163                 {
164                     BMCWEB_LOG_ERROR << "Invalid Hostname value";
165                     return;
166                 }
167                 url.set_host(*propVal);
168             }
169 
170             else if (prop.first == "Port")
171             {
172                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
173                 if (propVal == nullptr)
174                 {
175                     BMCWEB_LOG_ERROR << "Invalid Port value";
176                     return;
177                 }
178 
179                 if (*propVal > std::numeric_limits<uint16_t>::max())
180                 {
181                     BMCWEB_LOG_ERROR << "Port value out of range";
182                     return;
183                 }
184                 url.set_port(static_cast<uint16_t>(*propVal));
185             }
186 
187             else if (prop.first == "AuthType")
188             {
189                 const std::string* propVal =
190                     std::get_if<std::string>(&prop.second);
191                 if (propVal == nullptr)
192                 {
193                     BMCWEB_LOG_ERROR << "Invalid AuthType value";
194                     return;
195                 }
196 
197                 // For now assume authentication not required to communicate
198                 // with the satellite BMC
199                 if (*propVal != "None")
200                 {
201                     BMCWEB_LOG_ERROR
202                         << "Unsupported AuthType value: " << *propVal
203                         << ", only \"none\" is supported";
204                     return;
205                 }
206                 url.set_scheme("http");
207             }
208         } // Finished reading properties
209 
210         // Make sure all required config information was made available
211         if (url.host().empty())
212         {
213             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host";
214             return;
215         }
216 
217         if (!url.has_port())
218         {
219             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port";
220             return;
221         }
222 
223         if (!url.has_scheme())
224         {
225             BMCWEB_LOG_ERROR << "Satellite config " << name
226                              << " missing AuthType";
227             return;
228         }
229 
230         std::string resultString;
231         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
232         if (result.second)
233         {
234             resultString = "Added new satellite config ";
235         }
236         else
237         {
238             resultString = "Updated existing satellite config ";
239         }
240 
241         BMCWEB_LOG_DEBUG << resultString << name << " at "
242                          << result.first->second.scheme() << "://"
243                          << result.first->second.encoded_host_and_port();
244     }
245 
246   public:
247     RedfishAggregator(const RedfishAggregator&) = delete;
248     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
249     RedfishAggregator(RedfishAggregator&&) = delete;
250     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
251     ~RedfishAggregator() = default;
252 
253     static RedfishAggregator& getInstance()
254     {
255         static RedfishAggregator handler;
256         return handler;
257     }
258 
259     // Entry point to Redfish Aggregation
260     // Returns Result stating whether or not we still need to locally handle the
261     // request
262     static Result
263         beginAggregation(const crow::Request& thisReq,
264                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
265     {
266         using crow::utility::OrMorePaths;
267         using crow::utility::readUrlSegments;
268         const boost::urls::url_view& url = thisReq.urlView;
269         // UpdateService is the only top level resource that is not a Collection
270         if (readUrlSegments(url, "redfish", "v1", "UpdateService"))
271         {
272             return Result::LocalHandle;
273         }
274 
275         // Is the request for a resource collection?:
276         // /redfish/v1/<resource>
277         // e.g. /redfish/v1/Chassis
278         std::string collectionName;
279         if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName)))
280         {
281             return Result::LocalHandle;
282         }
283 
284         // We know that the ID of an aggregated resource will begin with
285         // "5B247A".  For the most part the URI will begin like this:
286         // /redfish/v1/<resource>/<resource ID>
287         // Note, FirmwareInventory and SoftwareInventory are "special" because
288         // they are two levels deep, but still need aggregated
289         // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID>
290         // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID>
291         std::string memberName;
292         if (readUrlSegments(url, "redfish", "v1", "UpdateService",
293                             "SoftwareInventory", std::ref(memberName),
294                             OrMorePaths()) ||
295             readUrlSegments(url, "redfish", "v1", "UpdateService",
296                             "FirmwareInventory", std::ref(memberName),
297                             OrMorePaths()) ||
298             readUrlSegments(url, "redfish", "v1", std::ref(collectionName),
299                             std::ref(memberName), OrMorePaths()))
300         {
301             if (memberName.starts_with("5B247A"))
302             {
303                 BMCWEB_LOG_DEBUG << "Need to forward a request";
304 
305                 // TODO: Extract the prefix from the request's URI, retrieve
306                 // the associated satellite config information, and then
307                 // forward the request to that satellite.
308                 redfish::messages::internalError(asyncResp->res);
309                 return Result::NoLocalHandle;
310             }
311             return Result::LocalHandle;
312         }
313 
314         BMCWEB_LOG_DEBUG << "Aggregation not required";
315         return Result::LocalHandle;
316     }
317 };
318 
319 } // namespace redfish
320