blob: 5d6d629ea8289c5819a6e87d71c0a98683613c4a [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>
Alexander Hansen02c1e292024-11-15 14:30:40 +010025#include <boost/beast/http/verb.hpp>
26#include <boost/system/errc.hpp>
27#include <boost/url/format.hpp>
28#include <boost/url/url_view_base.hpp>
29#include <nlohmann/json.hpp>
30
31#include <algorithm>
Myung Baefb546102024-10-29 10:21:26 -050032#include <chrono>
Alexander Hansen02c1e292024-11-15 14:30:40 +010033#include <cstdint>
Alexander Hansen02c1e292024-11-15 14:30:40 +010034#include <format>
35#include <functional>
36#include <memory>
37#include <span>
38#include <string>
39#include <string_view>
40#include <utility>
41#include <vector>
42
43namespace redfish
44{
45
46Subscription::Subscription(
47 std::shared_ptr<persistent_data::UserSubscription> userSubIn,
48 const boost::urls::url_view_base& url, boost::asio::io_context& ioc) :
49 userSub{std::move(userSubIn)},
Myung Baefb546102024-10-29 10:21:26 -050050 policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
Alexander Hansen02c1e292024-11-15 14:30:40 +010051{
52 userSub->destinationUrl = url;
53 client.emplace(ioc, policy);
54 // Subscription constructor
55 policy->invalidResp = retryRespHandler;
56}
57
58Subscription::Subscription(crow::sse_socket::Connection& connIn) :
59 userSub{std::make_shared<persistent_data::UserSubscription>()},
Myung Baefb546102024-10-29 10:21:26 -050060 sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
Alexander Hansen02c1e292024-11-15 14:30:40 +010061{}
62
63// callback for subscription sendData
Myung Bae99ff0dd2025-04-22 14:38:36 -050064void Subscription::resHandler(const std::shared_ptr<Subscription>& /*self*/,
65 const crow::Response& res)
Alexander Hansen02c1e292024-11-15 14:30:40 +010066{
67 BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
68
69 if (!client)
70 {
71 BMCWEB_LOG_ERROR(
72 "Http client wasn't filled but http client callback was called.");
73 return;
74 }
75
76 if (userSub->retryPolicy != "TerminateAfterRetries")
77 {
78 return;
79 }
80 if (client->isTerminated())
81 {
Myung Baefb546102024-10-29 10:21:26 -050082 hbTimer.cancel();
Alexander Hansen02c1e292024-11-15 14:30:40 +010083 if (deleter)
84 {
85 BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
86 userSub->id);
87 deleter();
88 }
89 }
90}
91
Myung Baefb546102024-10-29 10:21:26 -050092void Subscription::sendHeartbeatEvent()
93{
94 // send the heartbeat message
95 nlohmann::json eventMessage = messages::redfishServiceFunctional();
Myung Baefb546102024-10-29 10:21:26 -050096 eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
97 eventMessage["OriginOfCondition"] =
98 std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
99 eventMessage["MemberId"] = "0";
100
101 nlohmann::json::array_t eventRecord;
102 eventRecord.emplace_back(std::move(eventMessage));
103
104 nlohmann::json msgJson;
105 msgJson["@odata.type"] = "#Event.v1_4_0.Event";
106 msgJson["Name"] = "Heartbeat";
Myung Baefb546102024-10-29 10:21:26 -0500107 msgJson["Events"] = std::move(eventRecord);
108
109 std::string strMsg =
110 msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800111
112 // Note, eventId here is always zero, because this is a a per subscription
113 // event and doesn't have an "ID"
114 uint64_t eventId = 0;
115 sendEventToSubscriber(eventId, std::move(strMsg));
Myung Baefb546102024-10-29 10:21:26 -0500116}
117
118void Subscription::scheduleNextHeartbeatEvent()
119{
120 hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
121 hbTimer.async_wait(
122 std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
123}
124
125void Subscription::heartbeatParametersChanged()
126{
127 hbTimer.cancel();
128
129 if (userSub->sendHeartbeat)
130 {
131 scheduleNextHeartbeatEvent();
132 }
133}
134
135void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
136 const boost::system::error_code& ec)
137{
138 if (ec == boost::asio::error::operation_aborted)
139 {
140 BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
141 return;
142 }
143 if (ec == boost::system::errc::operation_canceled)
144 {
145 BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
146 return;
147 }
148 if (ec)
149 {
150 BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
151 return;
152 }
153
154 std::shared_ptr<Subscription> self = weakSelf.lock();
155 if (!self)
156 {
157 BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
158 return;
159 }
160
161 // Timer expired.
162 sendHeartbeatEvent();
163
164 // reschedule heartbeat timer
165 scheduleNextHeartbeatEvent();
166}
167
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800168bool Subscription::sendEventToSubscriber(uint64_t eventId, std::string&& msg)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100169{
170 persistent_data::EventServiceConfig eventServiceConfig =
171 persistent_data::EventServiceStore::getInstance()
172 .getEventServiceConfig();
173 if (!eventServiceConfig.enabled)
174 {
175 return false;
176 }
177
178 if (client)
179 {
Ed Tanous4ac78942024-12-02 08:25:38 -0800180 boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
181 httpHeadersCopy.set(boost::beast::http::field::content_type,
182 "application/json");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100183 client->sendDataWithCallback(
184 std::move(msg), userSub->destinationUrl,
185 static_cast<ensuressl::VerifyCertificate>(
186 userSub->verifyCertificate),
Ed Tanous4ac78942024-12-02 08:25:38 -0800187 httpHeadersCopy, boost::beast::http::verb::post,
Myung Bae99ff0dd2025-04-22 14:38:36 -0500188 std::bind_front(&Subscription::resHandler, this,
189 shared_from_this()));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100190 return true;
191 }
192
193 if (sseConn != nullptr)
194 {
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800195 sseConn->sendSseEvent(std::to_string(eventId), msg);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100196 }
197 return true;
198}
199
Alexander Hansen02c1e292024-11-15 14:30:40 +0100200void Subscription::filterAndSendEventLogs(
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800201 uint64_t eventId, const std::vector<EventLogObjectsType>& eventRecords)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100202{
203 nlohmann::json::array_t logEntryArray;
204 for (const EventLogObjectsType& logEntry : eventRecords)
205 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800206 BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id,
207 logEntry.timestamp, logEntry.messageId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100208 std::vector<std::string_view> messageArgsView(
209 logEntry.messageArgs.begin(), logEntry.messageArgs.end());
210
211 nlohmann::json::object_t bmcLogEntry;
212 if (event_log::formatEventLogEntry(
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800213 eventId, logEntry.id, logEntry.messageId, messageArgsView,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100214 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
215 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800216 BMCWEB_LOG_WARNING("Read eventLog entry failed");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100217 continue;
218 }
219
220 if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
221 {
222 BMCWEB_LOG_DEBUG("Event {} did not match the filter",
223 nlohmann::json(bmcLogEntry).dump());
224 continue;
225 }
226
227 if (filter)
228 {
229 if (!memberMatches(bmcLogEntry, *filter))
230 {
231 BMCWEB_LOG_DEBUG("Filter didn't match");
232 continue;
233 }
234 }
235
236 logEntryArray.emplace_back(std::move(bmcLogEntry));
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800237 eventId++;
Alexander Hansen02c1e292024-11-15 14:30:40 +0100238 }
239
240 if (logEntryArray.empty())
241 {
242 BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
243 return;
244 }
245
246 nlohmann::json msg;
247 msg["@odata.type"] = "#Event.v1_4_0.Event";
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800248 msg["Id"] = std::to_string(eventId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100249 msg["Name"] = "Event Log";
250 msg["Events"] = std::move(logEntryArray);
251 std::string strMsg =
252 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800253 sendEventToSubscriber(eventId, std::move(strMsg));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100254}
255
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800256void Subscription::filterAndSendReports(uint64_t eventId,
257 const std::string& reportId,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100258 const telemetry::TimestampReadings& var)
259{
260 boost::urls::url mrdUri = boost::urls::format(
261 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
262
263 // Empty list means no filter. Send everything.
264 if (!userSub->metricReportDefinitions.empty())
265 {
266 if (std::ranges::find(userSub->metricReportDefinitions,
267 mrdUri.buffer()) ==
268 userSub->metricReportDefinitions.end())
269 {
270 return;
271 }
272 }
273
274 nlohmann::json msg;
275 if (!telemetry::fillReport(msg, reportId, var))
276 {
277 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
278 "Report with id {}",
279 reportId);
280 return;
281 }
282
283 // Context is set by user during Event subscription and it must be
284 // set for MetricReport response.
285 if (!userSub->customText.empty())
286 {
287 msg["Context"] = userSub->customText;
288 }
289
290 std::string strMsg =
291 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800292 sendEventToSubscriber(eventId, std::move(strMsg));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100293}
294
295void Subscription::updateRetryConfig(uint32_t retryAttempts,
296 uint32_t retryTimeoutInterval)
297{
298 if (policy == nullptr)
299 {
300 BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
301 return;
302 }
303 policy->maxRetryAttempts = retryAttempts;
304 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
305}
306
Alexander Hansen02c1e292024-11-15 14:30:40 +0100307bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
308{
309 return &thisConn == sseConn;
310}
311
312// Check used to indicate what response codes are valid as part of our retry
313// policy. 2XX is considered acceptable
314boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
315{
316 BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
317 if ((respCode < 200) || (respCode >= 300))
318 {
319 return boost::system::errc::make_error_code(
320 boost::system::errc::result_out_of_range);
321 }
322
323 // Return 0 if the response code is valid
324 return boost::system::errc::make_error_code(boost::system::errc::success);
325}
326
327} // namespace redfish