xref: /openbmc/bmcweb/redfish-core/src/subscription.cpp (revision 4a19a7b5e62ad2569913d944c84c19f1005c922f)
1 /*
2 Copyright (c) 2020 Intel Corporation
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8       http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #include "subscription.hpp"
17 
18 #include "dbus_singleton.hpp"
19 #include "event_log.hpp"
20 #include "event_logs_object_type.hpp"
21 #include "event_matches_filter.hpp"
22 #include "event_service_store.hpp"
23 #include "filter_expr_executor.hpp"
24 #include "heartbeat_messages.hpp"
25 #include "http_client.hpp"
26 #include "http_response.hpp"
27 #include "logging.hpp"
28 #include "metric_report.hpp"
29 #include "server_sent_event.hpp"
30 #include "ssl_key_handler.hpp"
31 #include "utils/time_utils.hpp"
32 
33 #include <boost/asio/error.hpp>
34 #include <boost/asio/io_context.hpp>
35 #include <boost/asio/steady_timer.hpp>
36 #include <boost/beast/http/field.hpp>
37 #include <boost/beast/http/fields.hpp>
38 #include <boost/beast/http/verb.hpp>
39 #include <boost/system/errc.hpp>
40 #include <boost/url/format.hpp>
41 #include <boost/url/url_view_base.hpp>
42 #include <nlohmann/json.hpp>
43 
44 #include <algorithm>
45 #include <chrono>
46 #include <cstdint>
47 #include <cstdlib>
48 #include <ctime>
49 #include <format>
50 #include <functional>
51 #include <memory>
52 #include <span>
53 #include <string>
54 #include <string_view>
55 #include <utility>
56 #include <vector>
57 
58 namespace redfish
59 {
60 
Subscription(std::shared_ptr<persistent_data::UserSubscription> userSubIn,const boost::urls::url_view_base & url,boost::asio::io_context & ioc)61 Subscription::Subscription(
62     std::shared_ptr<persistent_data::UserSubscription> userSubIn,
63     const boost::urls::url_view_base& url, boost::asio::io_context& ioc) :
64     userSub{std::move(userSubIn)},
65     policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
66 {
67     userSub->destinationUrl = url;
68     client.emplace(ioc, policy);
69     // Subscription constructor
70     policy->invalidResp = retryRespHandler;
71 }
72 
Subscription(crow::sse_socket::Connection & connIn)73 Subscription::Subscription(crow::sse_socket::Connection& connIn) :
74     userSub{std::make_shared<persistent_data::UserSubscription>()},
75     sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
76 {}
77 
78 // callback for subscription sendData
resHandler(const crow::Response & res)79 void Subscription::resHandler(const crow::Response& res)
80 {
81     BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
82 
83     if (!client)
84     {
85         BMCWEB_LOG_ERROR(
86             "Http client wasn't filled but http client callback was called.");
87         return;
88     }
89 
90     if (userSub->retryPolicy != "TerminateAfterRetries")
91     {
92         return;
93     }
94     if (client->isTerminated())
95     {
96         hbTimer.cancel();
97         if (deleter)
98         {
99             BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
100                             userSub->id);
101             deleter();
102         }
103     }
104 }
105 
sendHeartbeatEvent()106 void Subscription::sendHeartbeatEvent()
107 {
108     // send the heartbeat message
109     nlohmann::json eventMessage = messages::redfishServiceFunctional();
110     eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
111     eventMessage["OriginOfCondition"] =
112         std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
113     eventMessage["MemberId"] = "0";
114 
115     nlohmann::json::array_t eventRecord;
116     eventRecord.emplace_back(std::move(eventMessage));
117 
118     nlohmann::json msgJson;
119     msgJson["@odata.type"] = "#Event.v1_4_0.Event";
120     msgJson["Name"] = "Heartbeat";
121     msgJson["Events"] = std::move(eventRecord);
122 
123     std::string strMsg =
124         msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
125 
126     // Note, eventId here is always zero, because this is a a per subscription
127     // event and doesn't have an "ID"
128     uint64_t eventId = 0;
129     sendEventToSubscriber(eventId, std::move(strMsg));
130 }
131 
scheduleNextHeartbeatEvent()132 void Subscription::scheduleNextHeartbeatEvent()
133 {
134     hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
135     hbTimer.async_wait(
136         std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
137 }
138 
heartbeatParametersChanged()139 void Subscription::heartbeatParametersChanged()
140 {
141     hbTimer.cancel();
142 
143     if (userSub->sendHeartbeat)
144     {
145         scheduleNextHeartbeatEvent();
146     }
147 }
148 
onHbTimeout(const std::weak_ptr<Subscription> & weakSelf,const boost::system::error_code & ec)149 void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
150                                const boost::system::error_code& ec)
151 {
152     if (ec == boost::asio::error::operation_aborted)
153     {
154         BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
155         return;
156     }
157     if (ec == boost::system::errc::operation_canceled)
158     {
159         BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
160         return;
161     }
162     if (ec)
163     {
164         BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
165         return;
166     }
167 
168     std::shared_ptr<Subscription> self = weakSelf.lock();
169     if (!self)
170     {
171         BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
172         return;
173     }
174 
175     // Timer expired.
176     sendHeartbeatEvent();
177 
178     // reschedule heartbeat timer
179     scheduleNextHeartbeatEvent();
180 }
181 
sendEventToSubscriber(uint64_t eventId,std::string && msg)182 bool Subscription::sendEventToSubscriber(uint64_t eventId, std::string&& msg)
183 {
184     persistent_data::EventServiceConfig eventServiceConfig =
185         persistent_data::EventServiceStore::getInstance()
186             .getEventServiceConfig();
187     if (!eventServiceConfig.enabled)
188     {
189         return false;
190     }
191 
192     if (client)
193     {
194         boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
195         httpHeadersCopy.set(boost::beast::http::field::content_type,
196                             "application/json");
197         client->sendDataWithCallback(
198             std::move(msg), userSub->destinationUrl,
199             static_cast<ensuressl::VerifyCertificate>(
200                 userSub->verifyCertificate),
201             httpHeadersCopy, boost::beast::http::verb::post,
202             std::bind_front(&Subscription::resHandler, this));
203         return true;
204     }
205 
206     if (sseConn != nullptr)
207     {
208         sseConn->sendSseEvent(std::to_string(eventId), msg);
209     }
210     return true;
211 }
212 
filterAndSendEventLogs(uint64_t eventId,const std::vector<EventLogObjectsType> & eventRecords)213 void Subscription::filterAndSendEventLogs(
214     uint64_t eventId, const std::vector<EventLogObjectsType>& eventRecords)
215 {
216     nlohmann::json::array_t logEntryArray;
217     for (const EventLogObjectsType& logEntry : eventRecords)
218     {
219         BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id,
220                          logEntry.timestamp, logEntry.messageId);
221         std::vector<std::string_view> messageArgsView(
222             logEntry.messageArgs.begin(), logEntry.messageArgs.end());
223 
224         nlohmann::json::object_t bmcLogEntry;
225         if (event_log::formatEventLogEntry(
226                 eventId, logEntry.id, logEntry.messageId, messageArgsView,
227                 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
228         {
229             BMCWEB_LOG_WARNING("Read eventLog entry failed");
230             continue;
231         }
232 
233         if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
234         {
235             BMCWEB_LOG_DEBUG("Event {} did not match the filter",
236                              nlohmann::json(bmcLogEntry).dump());
237             continue;
238         }
239 
240         if (filter)
241         {
242             if (!memberMatches(bmcLogEntry, *filter))
243             {
244                 BMCWEB_LOG_DEBUG("Filter didn't match");
245                 continue;
246             }
247         }
248 
249         logEntryArray.emplace_back(std::move(bmcLogEntry));
250         eventId++;
251     }
252 
253     if (logEntryArray.empty())
254     {
255         BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
256         return;
257     }
258 
259     nlohmann::json msg;
260     msg["@odata.type"] = "#Event.v1_4_0.Event";
261     msg["Id"] = std::to_string(eventId);
262     msg["Name"] = "Event Log";
263     msg["Events"] = std::move(logEntryArray);
264     std::string strMsg =
265         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
266     sendEventToSubscriber(eventId, std::move(strMsg));
267 }
268 
filterAndSendReports(uint64_t eventId,const std::string & reportId,const telemetry::TimestampReadings & var)269 void Subscription::filterAndSendReports(uint64_t eventId,
270                                         const std::string& reportId,
271                                         const telemetry::TimestampReadings& var)
272 {
273     boost::urls::url mrdUri = boost::urls::format(
274         "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
275 
276     // Empty list means no filter. Send everything.
277     if (!userSub->metricReportDefinitions.empty())
278     {
279         if (std::ranges::find(userSub->metricReportDefinitions,
280                               mrdUri.buffer()) ==
281             userSub->metricReportDefinitions.end())
282         {
283             return;
284         }
285     }
286 
287     nlohmann::json msg;
288     if (!telemetry::fillReport(msg, reportId, var))
289     {
290         BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
291                          "Report with id {}",
292                          reportId);
293         return;
294     }
295 
296     // Context is set by user during Event subscription and it must be
297     // set for MetricReport response.
298     if (!userSub->customText.empty())
299     {
300         msg["Context"] = userSub->customText;
301     }
302 
303     std::string strMsg =
304         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
305     sendEventToSubscriber(eventId, std::move(strMsg));
306 }
307 
updateRetryConfig(uint32_t retryAttempts,uint32_t retryTimeoutInterval)308 void Subscription::updateRetryConfig(uint32_t retryAttempts,
309                                      uint32_t retryTimeoutInterval)
310 {
311     if (policy == nullptr)
312     {
313         BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
314         return;
315     }
316     policy->maxRetryAttempts = retryAttempts;
317     policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
318 }
319 
matchSseId(const crow::sse_socket::Connection & thisConn)320 bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
321 {
322     return &thisConn == sseConn;
323 }
324 
325 // Check used to indicate what response codes are valid as part of our retry
326 // policy.  2XX is considered acceptable
retryRespHandler(unsigned int respCode)327 boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
328 {
329     BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
330     if ((respCode < 200) || (respCode >= 300))
331     {
332         return boost::system::errc::make_error_code(
333             boost::system::errc::result_out_of_range);
334     }
335 
336     // Return 0 if the response code is valid
337     return boost::system::errc::make_error_code(boost::system::errc::success);
338 }
339 
340 } // namespace redfish
341