1 #pragma once
2 
3 #include <http_client.hpp>
4 
5 namespace redfish
6 {
7 
8 class RedfishAggregator
9 {
10   private:
11     const std::string retryPolicyName = "RedfishAggregation";
12     const uint32_t retryAttempts = 5;
13     const uint32_t retryTimeoutInterval = 0;
14 
15     RedfishAggregator()
16     {
17         getSatelliteConfigs(constructorCallback);
18 
19         // Setup the retry policy to be used by Redfish Aggregation
20         crow::HttpClient::getInstance().setRetryConfig(
21             retryAttempts, retryTimeoutInterval, aggregationRetryHandler,
22             retryPolicyName);
23     }
24 
25     static inline boost::system::error_code
26         aggregationRetryHandler(unsigned int respCode)
27     {
28         // As a default, assume 200X is alright.
29         // We don't need to retry on a 404
30         if ((respCode < 200) || ((respCode >= 300) && (respCode != 404)))
31         {
32             return boost::system::errc::make_error_code(
33                 boost::system::errc::result_out_of_range);
34         }
35 
36         // Return 0 if the response code is valid
37         return boost::system::errc::make_error_code(
38             boost::system::errc::success);
39     };
40 
41     // Dummy callback used by the Constructor so that it can report the number
42     // of satellite configs when the class is first created
43     static void constructorCallback(
44         const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
45     {
46         BMCWEB_LOG_DEBUG << "There were "
47                          << std::to_string(satelliteInfo.size())
48                          << " satellite configs found at startup";
49     }
50 
51     // Polls D-Bus to get all available satellite config information
52     // Expects a handler which interacts with the returned configs
53     static void getSatelliteConfigs(
54         const std::function<void(
55             const std::unordered_map<std::string, boost::urls::url>&)>& handler)
56     {
57         BMCWEB_LOG_DEBUG << "Gathering satellite configs";
58         crow::connections::systemBus->async_method_call(
59             [handler](const boost::system::error_code ec,
60                       const dbus::utility::ManagedObjectType& objects) {
61             if (ec)
62             {
63                 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", "
64                                  << ec.message();
65                 return;
66             }
67 
68             // Maps a chosen alias representing a satellite BMC to a url
69             // containing the information required to create a http
70             // connection to the satellite
71             std::unordered_map<std::string, boost::urls::url> satelliteInfo;
72 
73             findSatelliteConfigs(objects, satelliteInfo);
74 
75             if (!satelliteInfo.empty())
76             {
77                 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with "
78                                  << std::to_string(satelliteInfo.size())
79                                  << " satellite BMCs";
80             }
81             else
82             {
83                 BMCWEB_LOG_DEBUG
84                     << "No satellite BMCs detected.  Redfish Aggregation not enabled";
85             }
86             handler(satelliteInfo);
87             },
88             "xyz.openbmc_project.EntityManager", "/",
89             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
90     }
91 
92     // Search D-Bus objects for satellite config objects and add their
93     // information if valid
94     static void findSatelliteConfigs(
95         const dbus::utility::ManagedObjectType& objects,
96         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
97     {
98         for (const auto& objectPath : objects)
99         {
100             for (const auto& interface : objectPath.second)
101             {
102                 if (interface.first ==
103                     "xyz.openbmc_project.Configuration.SatelliteController")
104                 {
105                     BMCWEB_LOG_DEBUG << "Found Satellite Controller at "
106                                      << objectPath.first.str;
107 
108                     addSatelliteConfig(interface.second, satelliteInfo);
109                 }
110             }
111         }
112     }
113 
114     // Parse the properties of a satellite config object and add the
115     // configuration if the properties are valid
116     static void addSatelliteConfig(
117         const dbus::utility::DBusPropertiesMap& properties,
118         std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
119     {
120         boost::urls::url url;
121         std::string name;
122 
123         for (const auto& prop : properties)
124         {
125             if (prop.first == "Name")
126             {
127                 const std::string* propVal =
128                     std::get_if<std::string>(&prop.second);
129                 if (propVal == nullptr)
130                 {
131                     BMCWEB_LOG_ERROR << "Invalid Name value";
132                     return;
133                 }
134 
135                 // The IDs will become <Name>_<ID> so the name should not
136                 // contain a '_'
137                 if (propVal->find('_') != std::string::npos)
138                 {
139                     BMCWEB_LOG_ERROR << "Name cannot contain a \"_\"";
140                     return;
141                 }
142                 name = *propVal;
143             }
144 
145             else if (prop.first == "Hostname")
146             {
147                 const std::string* propVal =
148                     std::get_if<std::string>(&prop.second);
149                 if (propVal == nullptr)
150                 {
151                     BMCWEB_LOG_ERROR << "Invalid Hostname value";
152                     return;
153                 }
154                 url.set_host(*propVal);
155             }
156 
157             else if (prop.first == "Port")
158             {
159                 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second);
160                 if (propVal == nullptr)
161                 {
162                     BMCWEB_LOG_ERROR << "Invalid Port value";
163                     return;
164                 }
165 
166                 if (*propVal > std::numeric_limits<uint16_t>::max())
167                 {
168                     BMCWEB_LOG_ERROR << "Port value out of range";
169                     return;
170                 }
171                 url.set_port(static_cast<uint16_t>(*propVal));
172             }
173 
174             else if (prop.first == "AuthType")
175             {
176                 const std::string* propVal =
177                     std::get_if<std::string>(&prop.second);
178                 if (propVal == nullptr)
179                 {
180                     BMCWEB_LOG_ERROR << "Invalid AuthType value";
181                     return;
182                 }
183 
184                 // For now assume authentication not required to communicate
185                 // with the satellite BMC
186                 if (*propVal != "None")
187                 {
188                     BMCWEB_LOG_ERROR
189                         << "Unsupported AuthType value: " << *propVal
190                         << ", only \"none\" is supported";
191                     return;
192                 }
193                 url.set_scheme("http");
194             }
195         } // Finished reading properties
196 
197         // Make sure all required config information was made available
198         if (name.empty())
199         {
200             BMCWEB_LOG_ERROR << "Satellite config missing Name";
201             return;
202         }
203 
204         if (url.host().empty())
205         {
206             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host";
207             return;
208         }
209 
210         if (!url.has_port())
211         {
212             BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port";
213             return;
214         }
215 
216         if (!url.has_scheme())
217         {
218             BMCWEB_LOG_ERROR << "Satellite config " << name
219                              << " missing AuthType";
220             return;
221         }
222 
223         std::string resultString;
224         auto result = satelliteInfo.insert_or_assign(name, std::move(url));
225         if (result.second)
226         {
227             resultString = "Added new satellite config ";
228         }
229         else
230         {
231             resultString = "Updated existing satellite config ";
232         }
233 
234         BMCWEB_LOG_DEBUG << resultString << name << " at "
235                          << result.first->second.scheme() << "://"
236                          << result.first->second.encoded_host_and_port();
237     }
238 
239   public:
240     RedfishAggregator(const RedfishAggregator&) = delete;
241     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
242     RedfishAggregator(RedfishAggregator&&) = delete;
243     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
244     ~RedfishAggregator() = default;
245 
246     static RedfishAggregator& getInstance()
247     {
248         static RedfishAggregator handler;
249         return handler;
250     }
251 };
252 
253 } // namespace redfish
254