1 #pragma once
2 
3 #include <dbus_utility.hpp>
4 #include <error_messages.hpp>
5 #include <http_client.hpp>
6 #include <http_connection.hpp>
7 
8 namespace redfish
9 {
10 
11 enum class Result
12 {
13     LocalHandle,
14     NoLocalHandle
15 };
16 
17 static void addPrefixToItem(nlohmann::json& item, std::string_view prefix)
18 {
19     std::string* strValue = item.get_ptr<std::string*>();
20     if (strValue == nullptr)
21     {
22         BMCWEB_LOG_CRITICAL << "Field wasn't a string????";
23         return;
24     }
25     // Make sure the value is a properly formatted URI
26     auto parsed = boost::urls::parse_relative_ref(*strValue);
27     if (!parsed)
28     {
29         BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << *strValue;
30         return;
31     }
32 
33     boost::urls::url_view thisUrl = *parsed;
34 
35     // We don't need to add prefixes to these URIs since
36     // /redfish/v1/UpdateService/ itself is not a collection
37     // /redfish/v1/UpdateService/FirmwareInventory
38     // /redfish/v1/UpdateService/SoftwareInventory
39     if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
40                                        "UpdateService", "FirmwareInventory") ||
41         crow::utility::readUrlSegments(thisUrl, "redfish", "v1",
42                                        "UpdateService", "SoftwareInventory"))
43     {
44         BMCWEB_LOG_DEBUG << "Skipping UpdateService URI prefix fixing";
45         return;
46     }
47 
48     // We also need to aggregate FirmwareInventory and
49     // SoftwareInventory so add an extra offset
50     // /redfish/v1/UpdateService/FirmwareInventory/<id>
51     // /redfish/v1/UpdateService/SoftwareInventory/<id>
52     std::string collectionName;
53     std::string softwareItem;
54     if (crow::utility::readUrlSegments(
55             thisUrl, "redfish", "v1", "UpdateService", std::ref(collectionName),
56             std::ref(softwareItem), crow::utility::OrMorePaths()))
57     {
58         softwareItem.insert(0, "_");
59         softwareItem.insert(0, prefix);
60         item = crow::utility::replaceUrlSegment(thisUrl, 4, softwareItem);
61     }
62 
63     // A collection URI that ends with "/" such as
64     // "/redfish/v1/Chassis/" will have 4 segments so we need to
65     // make sure we don't try to add a prefix to an empty segment
66     if (crow::utility::readUrlSegments(
67             thisUrl, "redfish", "v1", std::ref(collectionName),
68             std::ref(softwareItem), crow::utility::OrMorePaths()))
69     {
70         softwareItem.insert(0, "_");
71         softwareItem.insert(0, prefix);
72         item = crow::utility::replaceUrlSegment(thisUrl, 3, softwareItem);
73     }
74 }
75 
76 // We need to attempt to update all URIs under Actions
77 static void addPrefixesToActions(nlohmann::json& json, std::string_view prefix)
78 {
79     nlohmann::json::object_t* object =
80         json.get_ptr<nlohmann::json::object_t*>();
81     if (object != nullptr)
82     {
83         for (std::pair<const std::string, nlohmann::json>& item : *object)
84         {
85             std::string* strValue = item.second.get_ptr<std::string*>();
86             if (strValue != nullptr)
87             {
88                 addPrefixToItem(item.second, prefix);
89             }
90             else
91             {
92                 addPrefixesToActions(item.second, prefix);
93             }
94         }
95     }
96 }
97 
98 // Search the json for all URIs and add the supplied prefix if the URI is for
99 // and aggregated resource.
100 static void addPrefixes(nlohmann::json& json, std::string_view prefix)
101 {
102     nlohmann::json::object_t* object =
103         json.get_ptr<nlohmann::json::object_t*>();
104     if (object != nullptr)
105     {
106         for (std::pair<const std::string, nlohmann::json>& item : *object)
107         {
108             if (item.first == "Actions")
109             {
110                 addPrefixesToActions(item.second, prefix);
111                 continue;
112             }
113 
114             if ((item.first == "@odata.id") || (item.first.ends_with("URI")))
115             {
116                 addPrefixToItem(item.second, prefix);
117             }
118             // Recusively parse the rest of the json
119             addPrefixes(item.second, prefix);
120         }
121         return;
122     }
123     nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>();
124     if (array != nullptr)
125     {
126         for (nlohmann::json& item : *array)
127         {
128             addPrefixes(item, prefix);
129         }
130     }
131 }
132 
133 class RedfishAggregator
134 {
135   private:
136     const std::string retryPolicyName = "RedfishAggregation";
137     const std::string retryPolicyAction = "TerminateAfterRetries";
138     const uint32_t retryAttempts = 1;
139     const uint32_t retryTimeoutInterval = 0;
140     const std::string id = "Aggregator";
141 
142     RedfishAggregator()
143     {
144         getSatelliteConfigs(constructorCallback);
145 
146         // Setup the retry policy to be used by Redfish Aggregation
147         crow::HttpClient::getInstance().setRetryConfig(
148             retryAttempts, retryTimeoutInterval, aggregationRetryHandler,
149             retryPolicyName);
150         crow::HttpClient::getInstance().setRetryPolicy(retryPolicyAction,
151                                                        retryPolicyName);
152     }
153 
154     static inline boost::system::error_code
155         aggregationRetryHandler(unsigned int respCode)
156     {
157         // As a default, assume 200X is alright.
158         // We don't need to retry on a 404
159         if ((respCode < 200) || ((respCode >= 300) && (respCode != 404)))
160         {
161             return boost::system::errc::make_error_code(
162                 boost::system::errc::result_out_of_range);
163         }
164 
165         // Return 0 if the response code is valid
166         return boost::system::errc::make_error_code(
167             boost::system::errc::success);
168     }
169 
170     // Dummy callback used by the Constructor so that it can report the number
171     // of satellite configs when the class is first created
172     static void constructorCallback(
173         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
174     {
175         BMCWEB_LOG_DEBUG << "There were "
176                          << std::to_string(satelliteInfo.size())
177                          << " satellite configs found at startup";
178     }
179 
180     // Polls D-Bus to get all available satellite config information
181     // Expects a handler which interacts with the returned configs
182     static void getSatelliteConfigs(
183         const std::function<void(
184             const std::unordered_map<std::string, boost::urls::url>&)>& handler)
185     {
186         BMCWEB_LOG_DEBUG << "Gathering satellite configs";
187         crow::connections::systemBus->async_method_call(
188             [handler](const boost::system::error_code ec,
189                       const dbus::utility::ManagedObjectType& objects) {
190             if (ec)
191             {
192                 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", "
193                                  << ec.message();
194                 return;
195             }
196 
197             // Maps a chosen alias representing a satellite BMC to a url
198             // containing the information required to create a http
199             // connection to the satellite
200             std::unordered_map<std::string, boost::urls::url> satelliteInfo;
201 
202             findSatelliteConfigs(objects, satelliteInfo);
203 
204             if (!satelliteInfo.empty())
205             {
206                 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with "
207                                  << std::to_string(satelliteInfo.size())
208                                  << " satellite BMCs";
209             }
210             else
211             {
212                 BMCWEB_LOG_DEBUG
213                     << "No satellite BMCs detected.  Redfish Aggregation not enabled";
214             }
215             handler(satelliteInfo);
216             },
217             "xyz.openbmc_project.EntityManager", "/",
218             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
219     }
220 
221     // Search D-Bus objects for satellite config objects and add their
222     // information if valid
223     static void findSatelliteConfigs(
224         const dbus::utility::ManagedObjectType& objects,
225         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
226     {
227         for (const auto& objectPath : objects)
228         {
229             for (const auto& interface : objectPath.second)
230             {
231                 if (interface.first ==
232                     "xyz.openbmc_project.Configuration.SatelliteController")
233                 {
234                     BMCWEB_LOG_DEBUG << "Found Satellite Controller at "
235                                      << objectPath.first.str;
236 
237                     if (!satelliteInfo.empty())
238                     {
239                         BMCWEB_LOG_ERROR
240                             << "Redfish Aggregation only supports one satellite!";
241                         BMCWEB_LOG_DEBUG << "Clearing all satellite data";
242                         satelliteInfo.clear();
243                         return;
244                     }
245 
246                     // For now assume there will only be one satellite config.
247                     // Assign it the name/prefix "5B247A"
248                     addSatelliteConfig("5B247A", interface.second,
249                                        satelliteInfo);
250                 }
251             }
252         }
253     }
254 
255     // Parse the properties of a satellite config object and add the
256     // configuration if the properties are valid
257     static void addSatelliteConfig(
258         const std::string& name,
259         const dbus::utility::DBusPropertiesMap& properties,
260         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
261     {
262         boost::urls::url url;
263 
264         for (const auto& prop : properties)
265         {
266             if (prop.first == "Hostname")
267             {
268                 const std::string* propVal =
269                     std::get_if<std::string>(&prop.second);
270                 if (propVal == nullptr)
271                 {
272                     BMCWEB_LOG_ERROR << "Invalid Hostname value";
273                     return;
274                 }
275                 url.set_host(*propVal);
276             }
277 
278             else if (prop.first == "Port")
279             {
280                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
281                 if (propVal == nullptr)
282                 {
283                     BMCWEB_LOG_ERROR << "Invalid Port value";
284                     return;
285                 }
286 
287                 if (*propVal > std::numeric_limits<uint16_t>::max())
288                 {
289                     BMCWEB_LOG_ERROR << "Port value out of range";
290                     return;
291                 }
292                 url.set_port(static_cast<uint16_t>(*propVal));
293             }
294 
295             else if (prop.first == "AuthType")
296             {
297                 const std::string* propVal =
298                     std::get_if<std::string>(&prop.second);
299                 if (propVal == nullptr)
300                 {
301                     BMCWEB_LOG_ERROR << "Invalid AuthType value";
302                     return;
303                 }
304 
305                 // For now assume authentication not required to communicate
306                 // with the satellite BMC
307                 if (*propVal != "None")
308                 {
309                     BMCWEB_LOG_ERROR
310                         << "Unsupported AuthType value: " << *propVal
311                         << ", only \"none\" is supported";
312                     return;
313                 }
314                 url.set_scheme("http");
315             }
316         } // Finished reading properties
317 
318         // Make sure all required config information was made available
319         if (url.host().empty())
320         {
321             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host";
322             return;
323         }
324 
325         if (!url.has_port())
326         {
327             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port";
328             return;
329         }
330 
331         if (!url.has_scheme())
332         {
333             BMCWEB_LOG_ERROR << "Satellite config " << name
334                              << " missing AuthType";
335             return;
336         }
337 
338         std::string resultString;
339         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
340         if (result.second)
341         {
342             resultString = "Added new satellite config ";
343         }
344         else
345         {
346             resultString = "Updated existing satellite config ";
347         }
348 
349         BMCWEB_LOG_DEBUG << resultString << name << " at "
350                          << result.first->second.scheme() << "://"
351                          << result.first->second.encoded_host_and_port();
352     }
353 
354     enum AggregationType
355     {
356         Collection,
357         Resource,
358     };
359 
360     static void
361         startAggregation(AggregationType isCollection,
362                          const crow::Request& thisReq,
363                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
364     {
365         // Create a copy of thisReq so we we can still locally process the req
366         std::error_code ec;
367         auto localReq = std::make_shared<crow::Request>(thisReq.req, ec);
368         if (ec)
369         {
370             BMCWEB_LOG_ERROR << "Failed to create copy of request";
371             if (isCollection != AggregationType::Collection)
372             {
373                 messages::internalError(asyncResp->res);
374             }
375             return;
376         }
377 
378         getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection,
379                                             localReq, asyncResp));
380     }
381 
382     static void findSatelite(
383         const crow::Request& req,
384         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
385         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo,
386         std::string_view memberName)
387     {
388         // Determine if the resource ID begins with a known prefix
389         for (const auto& satellite : satelliteInfo)
390         {
391             std::string targetPrefix = satellite.first;
392             targetPrefix += "_";
393             if (memberName.starts_with(targetPrefix))
394             {
395                 BMCWEB_LOG_DEBUG << "\"" << satellite.first
396                                  << "\" is a known prefix";
397 
398                 // Remove the known prefix from the request's URI and
399                 // then forward to the associated satellite BMC
400                 getInstance().forwardRequest(req, asyncResp, satellite.first,
401                                              satelliteInfo);
402                 return;
403             }
404         }
405     }
406 
407     // Intended to handle an incoming request based on if Redfish Aggregation
408     // is enabled.  Forwards request to satellite BMC if it exists.
409     static void aggregateAndHandle(
410         AggregationType isCollection,
411         const std::shared_ptr<crow::Request>& sharedReq,
412         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
413         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
414     {
415         if (sharedReq == nullptr)
416         {
417             return;
418         }
419         const crow::Request& thisReq = *sharedReq;
420         BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of "
421                          << thisReq.target();
422 
423         // We previously determined the request is for a collection.  No need to
424         // check again
425         if (isCollection == AggregationType::Collection)
426         {
427             BMCWEB_LOG_DEBUG << "Aggregating a collection";
428             // We need to use a specific response handler and send the
429             // request to all known satellites
430             getInstance().forwardCollectionRequests(thisReq, asyncResp,
431                                                     satelliteInfo);
432             return;
433         }
434 
435         std::string updateServiceName;
436         std::string memberName;
437         if (crow::utility::readUrlSegments(
438                 thisReq.urlView, "redfish", "v1", "UpdateService",
439                 std::ref(updateServiceName), std::ref(memberName),
440                 crow::utility::OrMorePaths()))
441         {
442             // Must be FirmwareInventory or SoftwareInventory
443             findSatelite(thisReq, asyncResp, satelliteInfo, memberName);
444             return;
445         }
446 
447         std::string collectionName;
448         if (crow::utility::readUrlSegments(
449                 thisReq.urlView, "redfish", "v1", std::ref(collectionName),
450                 std::ref(memberName), crow::utility::OrMorePaths()))
451         {
452             findSatelite(thisReq, asyncResp, satelliteInfo, memberName);
453         }
454     }
455 
456     // Attempt to forward a request to the satellite BMC associated with the
457     // prefix.
458     void forwardRequest(
459         const crow::Request& thisReq,
460         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
461         const std::string& prefix,
462         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
463     {
464         const auto& sat = satelliteInfo.find(prefix);
465         if (sat == satelliteInfo.end())
466         {
467             // Realistically this shouldn't get called since we perform an
468             // earlier check to make sure the prefix exists
469             BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix
470                              << "\"";
471             return;
472         }
473 
474         // We need to strip the prefix from the request's path
475         std::string targetURI(thisReq.target());
476         size_t pos = targetURI.find(prefix + "_");
477         if (pos == std::string::npos)
478         {
479             // If this fails then something went wrong
480             BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix
481                              << "_\" from request URI";
482             messages::internalError(asyncResp->res);
483             return;
484         }
485         targetURI.erase(pos, prefix.size() + 1);
486 
487         std::function<void(crow::Response&)> cb =
488             std::bind_front(processResponse, prefix, asyncResp);
489 
490         std::string data = thisReq.req.body();
491         crow::HttpClient::getInstance().sendDataWithCallback(
492             data, id, std::string(sat->second.host()),
493             sat->second.port_number(), targetURI, false /*useSSL*/,
494             thisReq.fields, thisReq.method(), retryPolicyName, cb);
495     }
496 
497     // Forward a request for a collection URI to each known satellite BMC
498     void forwardCollectionRequests(
499         const crow::Request& thisReq,
500         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
501         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
502     {
503         for (const auto& sat : satelliteInfo)
504         {
505             std::function<void(crow::Response&)> cb = std::bind_front(
506                 processCollectionResponse, sat.first, asyncResp);
507 
508             std::string targetURI(thisReq.target());
509             std::string data = thisReq.req.body();
510             crow::HttpClient::getInstance().sendDataWithCallback(
511                 data, id, std::string(sat.second.host()),
512                 sat.second.port_number(), targetURI, false /*useSSL*/,
513                 thisReq.fields, thisReq.method(), retryPolicyName, cb);
514         }
515     }
516 
517     // Processes the response returned by a satellite BMC and loads its
518     // contents into asyncResp
519     static void
520         processResponse(std::string_view prefix,
521                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
522                         crow::Response& resp)
523     {
524         // No processing needed if the request wasn't successful
525         if (resp.resultInt() != 200)
526         {
527             BMCWEB_LOG_DEBUG << "No need to parse satellite response";
528             asyncResp->res.stringResponse = std::move(resp.stringResponse);
529             return;
530         }
531 
532         // The resp will not have a json component
533         // We need to create a json from resp's stringResponse
534         if (resp.getHeaderValue("Content-Type") == "application/json")
535         {
536             nlohmann::json jsonVal =
537                 nlohmann::json::parse(resp.body(), nullptr, false);
538             if (jsonVal.is_discarded())
539             {
540                 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON";
541                 messages::operationFailed(asyncResp->res);
542                 return;
543             }
544 
545             BMCWEB_LOG_DEBUG << "Successfully parsed satellite response";
546 
547             // TODO: For collections we  want to add the satellite responses to
548             // our response rather than just straight overwriting them if our
549             // local handling was successful (i.e. would return a 200).
550             addPrefixes(jsonVal, prefix);
551 
552             BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response";
553 
554             asyncResp->res.stringResponse.emplace(
555                 boost::beast::http::response<
556                     boost::beast::http::string_body>{});
557             asyncResp->res.result(resp.result());
558             asyncResp->res.jsonValue = std::move(jsonVal);
559 
560             BMCWEB_LOG_DEBUG << "Finished writing asyncResp";
561         }
562         else
563         {
564             if (!resp.body().empty())
565             {
566                 // We received a 200 response without the correct Content-Type
567                 // so return an Operation Failed error
568                 BMCWEB_LOG_ERROR
569                     << "Satellite response must be of type \"application/json\"";
570                 messages::operationFailed(asyncResp->res);
571             }
572         }
573     }
574 
575     // Processes the collection response returned by a satellite BMC and merges
576     // its "@odata.id" values
577     static void processCollectionResponse(
578         const std::string& prefix,
579         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
580         crow::Response& resp)
581     {
582         if (resp.resultInt() != 200)
583         {
584             BMCWEB_LOG_DEBUG
585                 << "Collection resource does not exist in satellite BMC \""
586                 << prefix << "\"";
587             // Return the error if we haven't had any successes
588             if (asyncResp->res.resultInt() != 200)
589             {
590                 asyncResp->res.stringResponse = std::move(resp.stringResponse);
591             }
592             return;
593         }
594 
595         // The resp will not have a json component
596         // We need to create a json from resp's stringResponse
597         if (resp.getHeaderValue("Content-Type") == "application/json")
598         {
599             nlohmann::json jsonVal =
600                 nlohmann::json::parse(resp.body(), nullptr, false);
601             if (jsonVal.is_discarded())
602             {
603                 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON";
604 
605                 // Notify the user if doing so won't overwrite a valid response
606                 if ((asyncResp->res.resultInt() != 200) &&
607                     (asyncResp->res.resultInt() != 502))
608                 {
609                     messages::operationFailed(asyncResp->res);
610                 }
611                 return;
612             }
613 
614             BMCWEB_LOG_DEBUG << "Successfully parsed satellite response";
615 
616             // Now we need to add the prefix to the URIs contained in the
617             // response.
618             addPrefixes(jsonVal, prefix);
619 
620             BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response";
621 
622             // If this resource collection does not exist on the aggregating bmc
623             // and has not already been added from processing the response from
624             // a different satellite then we need to completely overwrite
625             // asyncResp
626             if (asyncResp->res.resultInt() != 200)
627             {
628                 // We only want to aggregate collections that contain a
629                 // "Members" array
630                 if ((!jsonVal.contains("Members")) &&
631                     (!jsonVal["Members"].is_array()))
632                 {
633                     BMCWEB_LOG_DEBUG
634                         << "Skipping aggregating unsupported resource";
635                     return;
636                 }
637 
638                 BMCWEB_LOG_DEBUG
639                     << "Collection does not exist, overwriting asyncResp";
640                 asyncResp->res.stringResponse.emplace(
641                     boost::beast::http::response<
642                         boost::beast::http::string_body>{});
643                 asyncResp->res.result(resp.result());
644                 asyncResp->res.jsonValue = std::move(jsonVal);
645 
646                 BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp";
647             }
648             else
649             {
650                 // We only want to aggregate collections that contain a
651                 // "Members" array
652                 if ((!asyncResp->res.jsonValue.contains("Members")) &&
653                     (!asyncResp->res.jsonValue["Members"].is_array()))
654 
655                 {
656                     BMCWEB_LOG_DEBUG
657                         << "Skipping aggregating unsupported resource";
658                     return;
659                 }
660 
661                 BMCWEB_LOG_DEBUG << "Adding aggregated resources from \""
662                                  << prefix << "\" to collection";
663 
664                 // TODO: This is a potential race condition with multiple
665                 // satellites and the aggregating bmc attempting to write to
666                 // update this array.  May need to cascade calls to the next
667                 // satellite at the end of this function.
668                 // This is presumably not a concern when there is only a single
669                 // satellite since the aggregating bmc should have completed
670                 // before the response is received from the satellite.
671 
672                 auto& members = asyncResp->res.jsonValue["Members"];
673                 auto& satMembers = jsonVal["Members"];
674                 for (auto& satMem : satMembers)
675                 {
676                     members.push_back(std::move(satMem));
677                 }
678                 asyncResp->res.jsonValue["Members@odata.count"] =
679                     members.size();
680 
681                 // TODO: Do we need to sort() after updating the array?
682             }
683         }
684         else
685         {
686             BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix
687                              << "\"";
688             // We received as response that was not a json
689             // Notify the user only if we did not receive any valid responses,
690             // if the resource collection does not already exist on the
691             // aggregating BMC, and if we did not already set this warning due
692             // to a failure from a different satellite
693             if ((asyncResp->res.resultInt() != 200) &&
694                 (asyncResp->res.resultInt() != 502))
695             {
696                 messages::operationFailed(asyncResp->res);
697             }
698         }
699     } // End processCollectionResponse()
700 
701   public:
702     RedfishAggregator(const RedfishAggregator&) = delete;
703     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
704     RedfishAggregator(RedfishAggregator&&) = delete;
705     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
706     ~RedfishAggregator() = default;
707 
708     static RedfishAggregator& getInstance()
709     {
710         static RedfishAggregator handler;
711         return handler;
712     }
713 
714     // Entry point to Redfish Aggregation
715     // Returns Result stating whether or not we still need to locally handle the
716     // request
717     static Result
718         beginAggregation(const crow::Request& thisReq,
719                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
720     {
721         using crow::utility::OrMorePaths;
722         using crow::utility::readUrlSegments;
723         const boost::urls::url_view& url = thisReq.urlView;
724         // UpdateService is the only top level resource that is not a Collection
725         if (readUrlSegments(url, "redfish", "v1", "UpdateService"))
726         {
727             return Result::LocalHandle;
728         }
729         if (readUrlSegments(url, "redfish", "v1", "UpdateService",
730                             "SoftwareInventory") ||
731             readUrlSegments(url, "redfish", "v1", "UpdateService",
732                             "FirmwareInventory"))
733         {
734             startAggregation(AggregationType::Collection, thisReq, asyncResp);
735             return Result::LocalHandle;
736         }
737 
738         // Is the request for a resource collection?:
739         // /redfish/v1/<resource>
740         // e.g. /redfish/v1/Chassis
741         std::string collectionName;
742         if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName)))
743         {
744             startAggregation(AggregationType::Collection, thisReq, asyncResp);
745             return Result::LocalHandle;
746         }
747 
748         // We know that the ID of an aggregated resource will begin with
749         // "5B247A".  For the most part the URI will begin like this:
750         // /redfish/v1/<resource>/<resource ID>
751         // Note, FirmwareInventory and SoftwareInventory are "special" because
752         // they are two levels deep, but still need aggregated
753         // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID>
754         // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID>
755         std::string memberName;
756         if (readUrlSegments(url, "redfish", "v1", "UpdateService",
757                             "SoftwareInventory", std::ref(memberName),
758                             OrMorePaths()) ||
759             readUrlSegments(url, "redfish", "v1", "UpdateService",
760                             "FirmwareInventory", std::ref(memberName),
761                             OrMorePaths()) ||
762             readUrlSegments(url, "redfish", "v1", std::ref(collectionName),
763                             std::ref(memberName), OrMorePaths()))
764         {
765             if (memberName.starts_with("5B247A"))
766             {
767                 BMCWEB_LOG_DEBUG << "Need to forward a request";
768 
769                 // Extract the prefix from the request's URI, retrieve the
770                 // associated satellite config information, and then forward the
771                 // request to that satellite.
772                 startAggregation(AggregationType::Resource, thisReq, asyncResp);
773                 return Result::NoLocalHandle;
774             }
775             return Result::LocalHandle;
776         }
777 
778         BMCWEB_LOG_DEBUG << "Aggregation not required";
779         return Result::LocalHandle;
780     }
781 };
782 
783 } // namespace redfish
784