blob: f35a8e902dc71a41fdd6a35a8f97fd2ceef84424 [file] [log] [blame]
Alexander Hansen02c1e292024-11-15 14:30:40 +01001/*
2Copyright (c) 2020 Intel Corporation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16#include "subscription.hpp"
17
Myung Baefb546102024-10-29 10:21:26 -050018#include "dbus_singleton.hpp"
Alexander Hansenb80ba2e2024-11-18 12:24:35 +010019#include "event_log.hpp"
Alexander Hansen02c1e292024-11-15 14:30:40 +010020#include "event_logs_object_type.hpp"
21#include "event_matches_filter.hpp"
Alexander Hansen02c1e292024-11-15 14:30:40 +010022#include "event_service_store.hpp"
23#include "filter_expr_executor.hpp"
Myung Baefb546102024-10-29 10:21:26 -050024#include "heartbeat_messages.hpp"
Alexander Hansen02c1e292024-11-15 14:30:40 +010025#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
Myung Baefb546102024-10-29 10:21:26 -050033#include <boost/asio/error.hpp>
Alexander Hansen02c1e292024-11-15 14:30:40 +010034#include <boost/asio/io_context.hpp>
Myung Baefb546102024-10-29 10:21:26 -050035#include <boost/asio/steady_timer.hpp>
Ed Tanous4ac78942024-12-02 08:25:38 -080036#include <boost/beast/http/field.hpp>
37#include <boost/beast/http/fields.hpp>
Alexander Hansen02c1e292024-11-15 14:30:40 +010038#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>
Myung Baefb546102024-10-29 10:21:26 -050045#include <chrono>
Alexander Hansen02c1e292024-11-15 14:30:40 +010046#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
58namespace redfish
59{
60
61Subscription::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)},
Myung Baefb546102024-10-29 10:21:26 -050065 policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
Alexander Hansen02c1e292024-11-15 14:30:40 +010066{
67 userSub->destinationUrl = url;
68 client.emplace(ioc, policy);
69 // Subscription constructor
70 policy->invalidResp = retryRespHandler;
71}
72
73Subscription::Subscription(crow::sse_socket::Connection& connIn) :
74 userSub{std::make_shared<persistent_data::UserSubscription>()},
Myung Baefb546102024-10-29 10:21:26 -050075 sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
Alexander Hansen02c1e292024-11-15 14:30:40 +010076{}
77
78// callback for subscription sendData
Alexander Hansenf2656d12025-01-13 15:14:29 +010079void Subscription::resHandler(const crow::Response& res)
Alexander Hansen02c1e292024-11-15 14:30:40 +010080{
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 {
Myung Baefb546102024-10-29 10:21:26 -050096 hbTimer.cancel();
Alexander Hansen02c1e292024-11-15 14:30:40 +010097 if (deleter)
98 {
99 BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
100 userSub->id);
101 deleter();
102 }
103 }
104}
105
Myung Baefb546102024-10-29 10:21:26 -0500106void Subscription::sendHeartbeatEvent()
107{
108 // send the heartbeat message
109 nlohmann::json eventMessage = messages::redfishServiceFunctional();
Myung Baefb546102024-10-29 10:21:26 -0500110 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";
Myung Baefb546102024-10-29 10:21:26 -0500121 msgJson["Events"] = std::move(eventRecord);
122
123 std::string strMsg =
124 msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800125
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));
Myung Baefb546102024-10-29 10:21:26 -0500130}
131
132void 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
139void Subscription::heartbeatParametersChanged()
140{
141 hbTimer.cancel();
142
143 if (userSub->sendHeartbeat)
144 {
145 scheduleNextHeartbeatEvent();
146 }
147}
148
149void 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
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800182bool Subscription::sendEventToSubscriber(uint64_t eventId, std::string&& msg)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100183{
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 {
Ed Tanous4ac78942024-12-02 08:25:38 -0800194 boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
195 httpHeadersCopy.set(boost::beast::http::field::content_type,
196 "application/json");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100197 client->sendDataWithCallback(
198 std::move(msg), userSub->destinationUrl,
199 static_cast<ensuressl::VerifyCertificate>(
200 userSub->verifyCertificate),
Ed Tanous4ac78942024-12-02 08:25:38 -0800201 httpHeadersCopy, boost::beast::http::verb::post,
Alexander Hansenf2656d12025-01-13 15:14:29 +0100202 std::bind_front(&Subscription::resHandler, this));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100203 return true;
204 }
205
206 if (sseConn != nullptr)
207 {
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800208 sseConn->sendSseEvent(std::to_string(eventId), msg);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100209 }
210 return true;
211}
212
Alexander Hansen02c1e292024-11-15 14:30:40 +0100213void Subscription::filterAndSendEventLogs(
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800214 uint64_t eventId, const std::vector<EventLogObjectsType>& eventRecords)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100215{
216 nlohmann::json::array_t logEntryArray;
217 for (const EventLogObjectsType& logEntry : eventRecords)
218 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800219 BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id,
220 logEntry.timestamp, logEntry.messageId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100221 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(
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800226 eventId, logEntry.id, logEntry.messageId, messageArgsView,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100227 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
228 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800229 BMCWEB_LOG_WARNING("Read eventLog entry failed");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100230 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));
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800250 eventId++;
Alexander Hansen02c1e292024-11-15 14:30:40 +0100251 }
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";
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800261 msg["Id"] = std::to_string(eventId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100262 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);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800266 sendEventToSubscriber(eventId, std::move(strMsg));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100267}
268
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800269void Subscription::filterAndSendReports(uint64_t eventId,
270 const std::string& reportId,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100271 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);
Ed Tanous4a19a7b2025-01-27 10:44:15 -0800305 sendEventToSubscriber(eventId, std::move(strMsg));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100306}
307
308void 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
Alexander Hansen02c1e292024-11-15 14:30:40 +0100320bool 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
327boost::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