blob: f81fdc6c92b6913d151484bc871de90cfddb725b [file] [log] [blame]
Gunnar Mills5ffbc9a2025-04-24 08:41:49 -05001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
3// SPDX-FileCopyrightText: Copyright 2020 Intel Corporation
Alexander Hansen02c1e292024-11-15 14:30:40 +01004#include "subscription.hpp"
5
Myung Baefb546102024-10-29 10:21:26 -05006#include "dbus_singleton.hpp"
Alexander Hansenb80ba2e2024-11-18 12:24:35 +01007#include "event_log.hpp"
Alexander Hansen02c1e292024-11-15 14:30:40 +01008#include "event_logs_object_type.hpp"
9#include "event_matches_filter.hpp"
Alexander Hansen02c1e292024-11-15 14:30:40 +010010#include "event_service_store.hpp"
11#include "filter_expr_executor.hpp"
Myung Baefb546102024-10-29 10:21:26 -050012#include "heartbeat_messages.hpp"
Alexander Hansen02c1e292024-11-15 14:30:40 +010013#include "http_client.hpp"
14#include "http_response.hpp"
15#include "logging.hpp"
16#include "metric_report.hpp"
17#include "server_sent_event.hpp"
18#include "ssl_key_handler.hpp"
19#include "utils/time_utils.hpp"
20
Myung Baefb546102024-10-29 10:21:26 -050021#include <boost/asio/error.hpp>
Alexander Hansen02c1e292024-11-15 14:30:40 +010022#include <boost/asio/io_context.hpp>
Myung Baefb546102024-10-29 10:21:26 -050023#include <boost/asio/steady_timer.hpp>
Ed Tanous4ac78942024-12-02 08:25:38 -080024#include <boost/beast/http/field.hpp>
25#include <boost/beast/http/fields.hpp>
Alexander Hansen02c1e292024-11-15 14:30:40 +010026#include <boost/beast/http/verb.hpp>
27#include <boost/system/errc.hpp>
28#include <boost/url/format.hpp>
29#include <boost/url/url_view_base.hpp>
30#include <nlohmann/json.hpp>
31
32#include <algorithm>
Myung Baefb546102024-10-29 10:21:26 -050033#include <chrono>
Alexander Hansen02c1e292024-11-15 14:30:40 +010034#include <cstdint>
35#include <cstdlib>
36#include <ctime>
37#include <format>
38#include <functional>
39#include <memory>
40#include <span>
41#include <string>
42#include <string_view>
43#include <utility>
44#include <vector>
45
46namespace redfish
47{
48
49Subscription::Subscription(
50 std::shared_ptr<persistent_data::UserSubscription> userSubIn,
51 const boost::urls::url_view_base& url, boost::asio::io_context& ioc) :
52 userSub{std::move(userSubIn)},
Myung Baefb546102024-10-29 10:21:26 -050053 policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
Alexander Hansen02c1e292024-11-15 14:30:40 +010054{
55 userSub->destinationUrl = url;
56 client.emplace(ioc, policy);
57 // Subscription constructor
58 policy->invalidResp = retryRespHandler;
59}
60
61Subscription::Subscription(crow::sse_socket::Connection& connIn) :
62 userSub{std::make_shared<persistent_data::UserSubscription>()},
Myung Baefb546102024-10-29 10:21:26 -050063 sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
Alexander Hansen02c1e292024-11-15 14:30:40 +010064{}
65
66// callback for subscription sendData
Myung Bae99ff0dd2025-04-22 14:38:36 -050067void Subscription::resHandler(const std::shared_ptr<Subscription>& /*self*/,
68 const crow::Response& res)
Alexander Hansen02c1e292024-11-15 14:30:40 +010069{
70 BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
71
72 if (!client)
73 {
74 BMCWEB_LOG_ERROR(
75 "Http client wasn't filled but http client callback was called.");
76 return;
77 }
78
79 if (userSub->retryPolicy != "TerminateAfterRetries")
80 {
81 return;
82 }
83 if (client->isTerminated())
84 {
Myung Baefb546102024-10-29 10:21:26 -050085 hbTimer.cancel();
Alexander Hansen02c1e292024-11-15 14:30:40 +010086 if (deleter)
87 {
88 BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
89 userSub->id);
90 deleter();
91 }
92 }
93}
94
Myung Baefb546102024-10-29 10:21:26 -050095void Subscription::sendHeartbeatEvent()
96{
97 // send the heartbeat message
98 nlohmann::json eventMessage = messages::redfishServiceFunctional();
Myung Baefb546102024-10-29 10:21:26 -050099 eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
100 eventMessage["OriginOfCondition"] =
101 std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
102 eventMessage["MemberId"] = "0";
103
104 nlohmann::json::array_t eventRecord;
105 eventRecord.emplace_back(std::move(eventMessage));
106
107 nlohmann::json msgJson;
108 msgJson["@odata.type"] = "#Event.v1_4_0.Event";
109 msgJson["Name"] = "Heartbeat";
Myung Baefb546102024-10-29 10:21:26 -0500110 msgJson["Events"] = std::move(eventRecord);
111
112 std::string strMsg =
113 msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800114
115 // Note, eventId here is always zero, because this is a a per subscription
116 // event and doesn't have an "ID"
117 uint64_t eventId = 0;
118 sendEventToSubscriber(eventId, std::move(strMsg));
Myung Baefb546102024-10-29 10:21:26 -0500119}
120
121void Subscription::scheduleNextHeartbeatEvent()
122{
123 hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
124 hbTimer.async_wait(
125 std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
126}
127
128void Subscription::heartbeatParametersChanged()
129{
130 hbTimer.cancel();
131
132 if (userSub->sendHeartbeat)
133 {
134 scheduleNextHeartbeatEvent();
135 }
136}
137
138void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
139 const boost::system::error_code& ec)
140{
141 if (ec == boost::asio::error::operation_aborted)
142 {
143 BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
144 return;
145 }
146 if (ec == boost::system::errc::operation_canceled)
147 {
148 BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
149 return;
150 }
151 if (ec)
152 {
153 BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
154 return;
155 }
156
157 std::shared_ptr<Subscription> self = weakSelf.lock();
158 if (!self)
159 {
160 BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
161 return;
162 }
163
164 // Timer expired.
165 sendHeartbeatEvent();
166
167 // reschedule heartbeat timer
168 scheduleNextHeartbeatEvent();
169}
170
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800171bool Subscription::sendEventToSubscriber(uint64_t eventId, std::string&& msg)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100172{
173 persistent_data::EventServiceConfig eventServiceConfig =
174 persistent_data::EventServiceStore::getInstance()
175 .getEventServiceConfig();
176 if (!eventServiceConfig.enabled)
177 {
178 return false;
179 }
180
181 if (client)
182 {
Ed Tanous4ac78942024-12-02 08:25:38 -0800183 boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
184 httpHeadersCopy.set(boost::beast::http::field::content_type,
185 "application/json");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100186 client->sendDataWithCallback(
187 std::move(msg), userSub->destinationUrl,
188 static_cast<ensuressl::VerifyCertificate>(
189 userSub->verifyCertificate),
Ed Tanous4ac78942024-12-02 08:25:38 -0800190 httpHeadersCopy, boost::beast::http::verb::post,
Myung Bae99ff0dd2025-04-22 14:38:36 -0500191 std::bind_front(&Subscription::resHandler, this,
192 shared_from_this()));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100193 return true;
194 }
195
196 if (sseConn != nullptr)
197 {
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800198 sseConn->sendSseEvent(std::to_string(eventId), msg);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100199 }
200 return true;
201}
202
Alexander Hansen02c1e292024-11-15 14:30:40 +0100203void Subscription::filterAndSendEventLogs(
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800204 uint64_t eventId, const std::vector<EventLogObjectsType>& eventRecords)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100205{
206 nlohmann::json::array_t logEntryArray;
207 for (const EventLogObjectsType& logEntry : eventRecords)
208 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800209 BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id,
210 logEntry.timestamp, logEntry.messageId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100211 std::vector<std::string_view> messageArgsView(
212 logEntry.messageArgs.begin(), logEntry.messageArgs.end());
213
214 nlohmann::json::object_t bmcLogEntry;
215 if (event_log::formatEventLogEntry(
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800216 eventId, logEntry.id, logEntry.messageId, messageArgsView,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100217 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
218 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800219 BMCWEB_LOG_WARNING("Read eventLog entry failed");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100220 continue;
221 }
222
223 if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
224 {
225 BMCWEB_LOG_DEBUG("Event {} did not match the filter",
226 nlohmann::json(bmcLogEntry).dump());
227 continue;
228 }
229
230 if (filter)
231 {
232 if (!memberMatches(bmcLogEntry, *filter))
233 {
234 BMCWEB_LOG_DEBUG("Filter didn't match");
235 continue;
236 }
237 }
238
239 logEntryArray.emplace_back(std::move(bmcLogEntry));
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800240 eventId++;
Alexander Hansen02c1e292024-11-15 14:30:40 +0100241 }
242
243 if (logEntryArray.empty())
244 {
245 BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
246 return;
247 }
248
249 nlohmann::json msg;
250 msg["@odata.type"] = "#Event.v1_4_0.Event";
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800251 msg["Id"] = std::to_string(eventId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100252 msg["Name"] = "Event Log";
253 msg["Events"] = std::move(logEntryArray);
254 std::string strMsg =
255 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800256 sendEventToSubscriber(eventId, std::move(strMsg));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100257}
258
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800259void Subscription::filterAndSendReports(uint64_t eventId,
260 const std::string& reportId,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100261 const telemetry::TimestampReadings& var)
262{
263 boost::urls::url mrdUri = boost::urls::format(
264 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
265
266 // Empty list means no filter. Send everything.
267 if (!userSub->metricReportDefinitions.empty())
268 {
269 if (std::ranges::find(userSub->metricReportDefinitions,
270 mrdUri.buffer()) ==
271 userSub->metricReportDefinitions.end())
272 {
273 return;
274 }
275 }
276
277 nlohmann::json msg;
278 if (!telemetry::fillReport(msg, reportId, var))
279 {
280 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
281 "Report with id {}",
282 reportId);
283 return;
284 }
285
286 // Context is set by user during Event subscription and it must be
287 // set for MetricReport response.
288 if (!userSub->customText.empty())
289 {
290 msg["Context"] = userSub->customText;
291 }
292
293 std::string strMsg =
294 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800295 sendEventToSubscriber(eventId, std::move(strMsg));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100296}
297
298void Subscription::updateRetryConfig(uint32_t retryAttempts,
299 uint32_t retryTimeoutInterval)
300{
301 if (policy == nullptr)
302 {
303 BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
304 return;
305 }
306 policy->maxRetryAttempts = retryAttempts;
307 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
308}
309
Alexander Hansen02c1e292024-11-15 14:30:40 +0100310bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
311{
312 return &thisConn == sseConn;
313}
314
315// Check used to indicate what response codes are valid as part of our retry
316// policy. 2XX is considered acceptable
317boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
318{
319 BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
320 if ((respCode < 200) || (respCode >= 300))
321 {
322 return boost::system::errc::make_error_code(
323 boost::system::errc::result_out_of_range);
324 }
325
326 // Return 0 if the response code is valid
327 return boost::system::errc::make_error_code(boost::system::errc::success);
328}
329
330} // namespace redfish