blob: 69836a8383a7de9bf8abaac89c0bd8d2b4279950 [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"
Alexander Hansen02c1e292024-11-15 14:30:40 +010016#include "server_sent_event.hpp"
17#include "ssl_key_handler.hpp"
Ed Tanousf86cdd72025-08-12 17:50:13 -070018#include "telemetry_readings.hpp"
Alexander Hansen02c1e292024-11-15 14:30:40 +010019#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>
Alexander Hansen02c1e292024-11-15 14:30:40 +010035#include <format>
36#include <functional>
37#include <memory>
38#include <span>
39#include <string>
40#include <string_view>
41#include <utility>
42#include <vector>
43
44namespace redfish
45{
46
47Subscription::Subscription(
48 std::shared_ptr<persistent_data::UserSubscription> userSubIn,
49 const boost::urls::url_view_base& url, boost::asio::io_context& ioc) :
50 userSub{std::move(userSubIn)},
Myung Baefb546102024-10-29 10:21:26 -050051 policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
Alexander Hansen02c1e292024-11-15 14:30:40 +010052{
53 userSub->destinationUrl = url;
54 client.emplace(ioc, policy);
55 // Subscription constructor
56 policy->invalidResp = retryRespHandler;
57}
58
59Subscription::Subscription(crow::sse_socket::Connection& connIn) :
60 userSub{std::make_shared<persistent_data::UserSubscription>()},
Myung Baefb546102024-10-29 10:21:26 -050061 sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
Alexander Hansen02c1e292024-11-15 14:30:40 +010062{}
63
64// callback for subscription sendData
Myung Bae99ff0dd2025-04-22 14:38:36 -050065void Subscription::resHandler(const std::shared_ptr<Subscription>& /*self*/,
66 const crow::Response& res)
Alexander Hansen02c1e292024-11-15 14:30:40 +010067{
68 BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
69
70 if (!client)
71 {
72 BMCWEB_LOG_ERROR(
73 "Http client wasn't filled but http client callback was called.");
74 return;
75 }
76
77 if (userSub->retryPolicy != "TerminateAfterRetries")
78 {
79 return;
80 }
81 if (client->isTerminated())
82 {
Myung Baefb546102024-10-29 10:21:26 -050083 hbTimer.cancel();
Alexander Hansen02c1e292024-11-15 14:30:40 +010084 if (deleter)
85 {
86 BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
87 userSub->id);
88 deleter();
89 }
90 }
91}
92
Myung Baefb546102024-10-29 10:21:26 -050093void Subscription::sendHeartbeatEvent()
94{
95 // send the heartbeat message
96 nlohmann::json eventMessage = messages::redfishServiceFunctional();
Myung Baefb546102024-10-29 10:21:26 -050097 eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
98 eventMessage["OriginOfCondition"] =
99 std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
100 eventMessage["MemberId"] = "0";
101
102 nlohmann::json::array_t eventRecord;
103 eventRecord.emplace_back(std::move(eventMessage));
104
105 nlohmann::json msgJson;
106 msgJson["@odata.type"] = "#Event.v1_4_0.Event";
107 msgJson["Name"] = "Heartbeat";
Myung Baefb546102024-10-29 10:21:26 -0500108 msgJson["Events"] = std::move(eventRecord);
109
110 std::string strMsg =
111 msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800112
113 // Note, eventId here is always zero, because this is a a per subscription
114 // event and doesn't have an "ID"
115 uint64_t eventId = 0;
116 sendEventToSubscriber(eventId, std::move(strMsg));
Myung Baefb546102024-10-29 10:21:26 -0500117}
118
119void Subscription::scheduleNextHeartbeatEvent()
120{
121 hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
122 hbTimer.async_wait(
123 std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
124}
125
126void Subscription::heartbeatParametersChanged()
127{
128 hbTimer.cancel();
129
130 if (userSub->sendHeartbeat)
131 {
132 scheduleNextHeartbeatEvent();
133 }
134}
135
136void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
137 const boost::system::error_code& ec)
138{
139 if (ec == boost::asio::error::operation_aborted)
140 {
141 BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
142 return;
143 }
144 if (ec == boost::system::errc::operation_canceled)
145 {
146 BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
147 return;
148 }
149 if (ec)
150 {
151 BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
152 return;
153 }
154
155 std::shared_ptr<Subscription> self = weakSelf.lock();
156 if (!self)
157 {
158 BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
159 return;
160 }
161
162 // Timer expired.
163 sendHeartbeatEvent();
164
165 // reschedule heartbeat timer
166 scheduleNextHeartbeatEvent();
167}
168
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800169bool Subscription::sendEventToSubscriber(uint64_t eventId, std::string&& msg)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100170{
171 persistent_data::EventServiceConfig eventServiceConfig =
172 persistent_data::EventServiceStore::getInstance()
173 .getEventServiceConfig();
174 if (!eventServiceConfig.enabled)
175 {
176 return false;
177 }
178
179 if (client)
180 {
Ed Tanous4ac78942024-12-02 08:25:38 -0800181 boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
182 httpHeadersCopy.set(boost::beast::http::field::content_type,
183 "application/json");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100184 client->sendDataWithCallback(
185 std::move(msg), userSub->destinationUrl,
186 static_cast<ensuressl::VerifyCertificate>(
187 userSub->verifyCertificate),
Ed Tanous4ac78942024-12-02 08:25:38 -0800188 httpHeadersCopy, boost::beast::http::verb::post,
Myung Bae99ff0dd2025-04-22 14:38:36 -0500189 std::bind_front(&Subscription::resHandler, this,
190 shared_from_this()));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100191 return true;
192 }
193
194 if (sseConn != nullptr)
195 {
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800196 sseConn->sendSseEvent(std::to_string(eventId), msg);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100197 }
198 return true;
199}
200
Alexander Hansen02c1e292024-11-15 14:30:40 +0100201void Subscription::filterAndSendEventLogs(
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800202 uint64_t eventId, const std::vector<EventLogObjectsType>& eventRecords)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100203{
204 nlohmann::json::array_t logEntryArray;
205 for (const EventLogObjectsType& logEntry : eventRecords)
206 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800207 BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id,
208 logEntry.timestamp, logEntry.messageId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100209 std::vector<std::string_view> messageArgsView(
210 logEntry.messageArgs.begin(), logEntry.messageArgs.end());
211
212 nlohmann::json::object_t bmcLogEntry;
213 if (event_log::formatEventLogEntry(
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800214 eventId, logEntry.id, logEntry.messageId, messageArgsView,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100215 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
216 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800217 BMCWEB_LOG_WARNING("Read eventLog entry failed");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100218 continue;
219 }
220
221 if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
222 {
223 BMCWEB_LOG_DEBUG("Event {} did not match the filter",
224 nlohmann::json(bmcLogEntry).dump());
225 continue;
226 }
227
228 if (filter)
229 {
230 if (!memberMatches(bmcLogEntry, *filter))
231 {
232 BMCWEB_LOG_DEBUG("Filter didn't match");
233 continue;
234 }
235 }
236
237 logEntryArray.emplace_back(std::move(bmcLogEntry));
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800238 eventId++;
Alexander Hansen02c1e292024-11-15 14:30:40 +0100239 }
240
241 if (logEntryArray.empty())
242 {
243 BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
244 return;
245 }
246
247 nlohmann::json msg;
248 msg["@odata.type"] = "#Event.v1_4_0.Event";
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800249 msg["Id"] = std::to_string(eventId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100250 msg["Name"] = "Event Log";
251 msg["Events"] = std::move(logEntryArray);
252 std::string strMsg =
253 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800254 sendEventToSubscriber(eventId, std::move(strMsg));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100255}
256
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800257void Subscription::filterAndSendReports(uint64_t eventId,
258 const std::string& reportId,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100259 const telemetry::TimestampReadings& var)
260{
261 boost::urls::url mrdUri = boost::urls::format(
262 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
263
264 // Empty list means no filter. Send everything.
265 if (!userSub->metricReportDefinitions.empty())
266 {
267 if (std::ranges::find(userSub->metricReportDefinitions,
268 mrdUri.buffer()) ==
269 userSub->metricReportDefinitions.end())
270 {
271 return;
272 }
273 }
274
275 nlohmann::json msg;
276 if (!telemetry::fillReport(msg, reportId, var))
277 {
278 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
279 "Report with id {}",
280 reportId);
281 return;
282 }
283
284 // Context is set by user during Event subscription and it must be
285 // set for MetricReport response.
286 if (!userSub->customText.empty())
287 {
288 msg["Context"] = userSub->customText;
289 }
290
291 std::string strMsg =
292 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800293 sendEventToSubscriber(eventId, std::move(strMsg));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100294}
295
296void Subscription::updateRetryConfig(uint32_t retryAttempts,
297 uint32_t retryTimeoutInterval)
298{
299 if (policy == nullptr)
300 {
301 BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
302 return;
303 }
304 policy->maxRetryAttempts = retryAttempts;
305 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
306}
307
Alexander Hansen02c1e292024-11-15 14:30:40 +0100308bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
309{
310 return &thisConn == sseConn;
311}
312
313// Check used to indicate what response codes are valid as part of our retry
314// policy. 2XX is considered acceptable
315boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
316{
317 BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
318 if ((respCode < 200) || (respCode >= 300))
319 {
320 return boost::system::errc::make_error_code(
321 boost::system::errc::result_out_of_range);
322 }
323
324 // Return 0 if the response code is valid
325 return boost::system::errc::make_error_code(boost::system::errc::success);
326}
327
328} // namespace redfish