xref: /openbmc/bmcweb/redfish-core/src/subscription.cpp (revision 0309c216fd52fc998e990e43743eeab2d1b7ca84)
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 
111     std::string heartEventId = std::to_string(eventSeqNum);
112     eventMessage["EventId"] = heartEventId;
113     eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
114     eventMessage["OriginOfCondition"] =
115         std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
116     eventMessage["MemberId"] = "0";
117 
118     nlohmann::json::array_t eventRecord;
119     eventRecord.emplace_back(std::move(eventMessage));
120 
121     nlohmann::json msgJson;
122     msgJson["@odata.type"] = "#Event.v1_4_0.Event";
123     msgJson["Name"] = "Heartbeat";
124     msgJson["Id"] = heartEventId;
125     msgJson["Events"] = std::move(eventRecord);
126 
127     std::string strMsg =
128         msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
129     sendEventToSubscriber(std::move(strMsg));
130     eventSeqNum++;
131 }
132 
scheduleNextHeartbeatEvent()133 void Subscription::scheduleNextHeartbeatEvent()
134 {
135     hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
136     hbTimer.async_wait(
137         std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
138 }
139 
heartbeatParametersChanged()140 void Subscription::heartbeatParametersChanged()
141 {
142     hbTimer.cancel();
143 
144     if (userSub->sendHeartbeat)
145     {
146         scheduleNextHeartbeatEvent();
147     }
148 }
149 
onHbTimeout(const std::weak_ptr<Subscription> & weakSelf,const boost::system::error_code & ec)150 void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
151                                const boost::system::error_code& ec)
152 {
153     if (ec == boost::asio::error::operation_aborted)
154     {
155         BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
156         return;
157     }
158     if (ec == boost::system::errc::operation_canceled)
159     {
160         BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
161         return;
162     }
163     if (ec)
164     {
165         BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
166         return;
167     }
168 
169     std::shared_ptr<Subscription> self = weakSelf.lock();
170     if (!self)
171     {
172         BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
173         return;
174     }
175 
176     // Timer expired.
177     sendHeartbeatEvent();
178 
179     // reschedule heartbeat timer
180     scheduleNextHeartbeatEvent();
181 }
182 
sendEventToSubscriber(std::string && msg)183 bool Subscription::sendEventToSubscriber(std::string&& msg)
184 {
185     persistent_data::EventServiceConfig eventServiceConfig =
186         persistent_data::EventServiceStore::getInstance()
187             .getEventServiceConfig();
188     if (!eventServiceConfig.enabled)
189     {
190         return false;
191     }
192 
193     if (client)
194     {
195         boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
196         httpHeadersCopy.set(boost::beast::http::field::content_type,
197                             "application/json");
198         client->sendDataWithCallback(
199             std::move(msg), userSub->destinationUrl,
200             static_cast<ensuressl::VerifyCertificate>(
201                 userSub->verifyCertificate),
202             httpHeadersCopy, boost::beast::http::verb::post,
203             std::bind_front(&Subscription::resHandler, this));
204         return true;
205     }
206 
207     if (sseConn != nullptr)
208     {
209         eventSeqNum++;
210         sseConn->sendSseEvent(std::to_string(eventSeqNum), msg);
211     }
212     return true;
213 }
214 
sendTestEventLog(TestEvent & testEvent)215 bool Subscription::sendTestEventLog(TestEvent& testEvent)
216 {
217     nlohmann::json::array_t logEntryArray;
218     nlohmann::json& logEntryJson = logEntryArray.emplace_back();
219 
220     if (testEvent.eventGroupId)
221     {
222         logEntryJson["EventGroupId"] = *testEvent.eventGroupId;
223     }
224 
225     if (testEvent.eventId)
226     {
227         logEntryJson["EventId"] = *testEvent.eventId;
228     }
229 
230     if (testEvent.eventTimestamp)
231     {
232         logEntryJson["EventTimestamp"] = *testEvent.eventTimestamp;
233     }
234 
235     if (testEvent.originOfCondition)
236     {
237         logEntryJson["OriginOfCondition"]["@odata.id"] =
238             *testEvent.originOfCondition;
239     }
240     if (testEvent.severity)
241     {
242         logEntryJson["Severity"] = *testEvent.severity;
243     }
244 
245     if (testEvent.message)
246     {
247         logEntryJson["Message"] = *testEvent.message;
248     }
249 
250     if (testEvent.resolution)
251     {
252         logEntryJson["Resolution"] = *testEvent.resolution;
253     }
254 
255     if (testEvent.messageId)
256     {
257         logEntryJson["MessageId"] = *testEvent.messageId;
258     }
259 
260     if (testEvent.messageArgs)
261     {
262         logEntryJson["MessageArgs"] = *testEvent.messageArgs;
263     }
264     // MemberId is 0 : since we are sending one event record.
265     logEntryJson["MemberId"] = "0";
266 
267     nlohmann::json msg;
268     msg["@odata.type"] = "#Event.v1_4_0.Event";
269     msg["Id"] = std::to_string(eventSeqNum);
270     msg["Name"] = "Event Log";
271     msg["Events"] = logEntryArray;
272 
273     std::string strMsg =
274         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
275     return sendEventToSubscriber(std::move(strMsg));
276 }
277 
filterAndSendEventLogs(const std::vector<EventLogObjectsType> & eventRecords)278 void Subscription::filterAndSendEventLogs(
279     const std::vector<EventLogObjectsType>& eventRecords)
280 {
281     nlohmann::json::array_t logEntryArray;
282     for (const EventLogObjectsType& logEntry : eventRecords)
283     {
284         BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id,
285                          logEntry.timestamp, logEntry.messageId);
286         std::vector<std::string_view> messageArgsView(
287             logEntry.messageArgs.begin(), logEntry.messageArgs.end());
288 
289         nlohmann::json::object_t bmcLogEntry;
290         if (event_log::formatEventLogEntry(
291                 logEntry.id, logEntry.messageId, messageArgsView,
292                 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
293         {
294             BMCWEB_LOG_WARNING("Read eventLog entry failed");
295             continue;
296         }
297 
298         if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
299         {
300             BMCWEB_LOG_DEBUG("Event {} did not match the filter",
301                              nlohmann::json(bmcLogEntry).dump());
302             continue;
303         }
304 
305         if (filter)
306         {
307             if (!memberMatches(bmcLogEntry, *filter))
308             {
309                 BMCWEB_LOG_DEBUG("Filter didn't match");
310                 continue;
311             }
312         }
313 
314         logEntryArray.emplace_back(std::move(bmcLogEntry));
315     }
316 
317     if (logEntryArray.empty())
318     {
319         BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
320         return;
321     }
322 
323     nlohmann::json msg;
324     msg["@odata.type"] = "#Event.v1_4_0.Event";
325     msg["Id"] = std::to_string(eventSeqNum);
326     msg["Name"] = "Event Log";
327     msg["Events"] = std::move(logEntryArray);
328     std::string strMsg =
329         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
330     sendEventToSubscriber(std::move(strMsg));
331     eventSeqNum++;
332 }
333 
filterAndSendReports(const std::string & reportId,const telemetry::TimestampReadings & var)334 void Subscription::filterAndSendReports(const std::string& reportId,
335                                         const telemetry::TimestampReadings& var)
336 {
337     boost::urls::url mrdUri = boost::urls::format(
338         "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
339 
340     // Empty list means no filter. Send everything.
341     if (!userSub->metricReportDefinitions.empty())
342     {
343         if (std::ranges::find(userSub->metricReportDefinitions,
344                               mrdUri.buffer()) ==
345             userSub->metricReportDefinitions.end())
346         {
347             return;
348         }
349     }
350 
351     nlohmann::json msg;
352     if (!telemetry::fillReport(msg, reportId, var))
353     {
354         BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
355                          "Report with id {}",
356                          reportId);
357         return;
358     }
359 
360     // Context is set by user during Event subscription and it must be
361     // set for MetricReport response.
362     if (!userSub->customText.empty())
363     {
364         msg["Context"] = userSub->customText;
365     }
366 
367     std::string strMsg =
368         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
369     sendEventToSubscriber(std::move(strMsg));
370 }
371 
updateRetryConfig(uint32_t retryAttempts,uint32_t retryTimeoutInterval)372 void Subscription::updateRetryConfig(uint32_t retryAttempts,
373                                      uint32_t retryTimeoutInterval)
374 {
375     if (policy == nullptr)
376     {
377         BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
378         return;
379     }
380     policy->maxRetryAttempts = retryAttempts;
381     policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
382 }
383 
getEventSeqNum() const384 uint64_t Subscription::getEventSeqNum() const
385 {
386     return eventSeqNum;
387 }
388 
matchSseId(const crow::sse_socket::Connection & thisConn)389 bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
390 {
391     return &thisConn == sseConn;
392 }
393 
394 // Check used to indicate what response codes are valid as part of our retry
395 // policy.  2XX is considered acceptable
retryRespHandler(unsigned int respCode)396 boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
397 {
398     BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
399     if ((respCode < 200) || (respCode >= 300))
400     {
401         return boost::system::errc::make_error_code(
402             boost::system::errc::result_out_of_range);
403     }
404 
405     // Return 0 if the response code is valid
406     return boost::system::errc::make_error_code(boost::system::errc::success);
407 }
408 
409 } // namespace redfish
410