blob: 34dc76f10f907de87783ff28c0996721ead0a71b [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>
Alexander Hansen02c1e292024-11-15 14:30:40 +010036#include <boost/beast/http/verb.hpp>
37#include <boost/system/errc.hpp>
38#include <boost/url/format.hpp>
39#include <boost/url/url_view_base.hpp>
40#include <nlohmann/json.hpp>
41
42#include <algorithm>
Myung Baefb546102024-10-29 10:21:26 -050043#include <chrono>
Alexander Hansen02c1e292024-11-15 14:30:40 +010044#include <cstdint>
45#include <cstdlib>
46#include <ctime>
47#include <format>
48#include <functional>
49#include <memory>
50#include <span>
51#include <string>
52#include <string_view>
53#include <utility>
54#include <vector>
55
56namespace redfish
57{
58
59Subscription::Subscription(
60 std::shared_ptr<persistent_data::UserSubscription> userSubIn,
61 const boost::urls::url_view_base& url, boost::asio::io_context& ioc) :
62 userSub{std::move(userSubIn)},
Myung Baefb546102024-10-29 10:21:26 -050063 policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
Alexander Hansen02c1e292024-11-15 14:30:40 +010064{
65 userSub->destinationUrl = url;
66 client.emplace(ioc, policy);
67 // Subscription constructor
68 policy->invalidResp = retryRespHandler;
69}
70
71Subscription::Subscription(crow::sse_socket::Connection& connIn) :
72 userSub{std::make_shared<persistent_data::UserSubscription>()},
Myung Baefb546102024-10-29 10:21:26 -050073 sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
Alexander Hansen02c1e292024-11-15 14:30:40 +010074{}
75
76// callback for subscription sendData
77void Subscription::resHandler(const std::shared_ptr<Subscription>& /*unused*/,
78 const crow::Response& res)
79{
80 BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
81
82 if (!client)
83 {
84 BMCWEB_LOG_ERROR(
85 "Http client wasn't filled but http client callback was called.");
86 return;
87 }
88
89 if (userSub->retryPolicy != "TerminateAfterRetries")
90 {
91 return;
92 }
93 if (client->isTerminated())
94 {
Myung Baefb546102024-10-29 10:21:26 -050095 hbTimer.cancel();
Alexander Hansen02c1e292024-11-15 14:30:40 +010096 if (deleter)
97 {
98 BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
99 userSub->id);
100 deleter();
101 }
102 }
103}
104
Myung Baefb546102024-10-29 10:21:26 -0500105void Subscription::sendHeartbeatEvent()
106{
107 // send the heartbeat message
108 nlohmann::json eventMessage = messages::redfishServiceFunctional();
109
110 std::string heartEventId = std::to_string(eventSeqNum);
111 eventMessage["EventId"] = heartEventId;
112 eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
113 eventMessage["OriginOfCondition"] =
114 std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
115 eventMessage["MemberId"] = "0";
116
117 nlohmann::json::array_t eventRecord;
118 eventRecord.emplace_back(std::move(eventMessage));
119
120 nlohmann::json msgJson;
121 msgJson["@odata.type"] = "#Event.v1_4_0.Event";
122 msgJson["Name"] = "Heartbeat";
123 msgJson["Id"] = heartEventId;
124 msgJson["Events"] = std::move(eventRecord);
125
126 std::string strMsg =
127 msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
128 sendEventToSubscriber(std::move(strMsg));
129 eventSeqNum++;
130}
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
Alexander Hansen02c1e292024-11-15 14:30:40 +0100182bool Subscription::sendEventToSubscriber(std::string&& msg)
183{
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 {
194 client->sendDataWithCallback(
195 std::move(msg), userSub->destinationUrl,
196 static_cast<ensuressl::VerifyCertificate>(
197 userSub->verifyCertificate),
198 userSub->httpHeaders, boost::beast::http::verb::post,
199 std::bind_front(&Subscription::resHandler, this,
200 shared_from_this()));
201 return true;
202 }
203
204 if (sseConn != nullptr)
205 {
206 eventSeqNum++;
207 sseConn->sendSseEvent(std::to_string(eventSeqNum), msg);
208 }
209 return true;
210}
211
Chandramohan Harkude81ee0e72024-12-20 19:22:12 +0530212bool Subscription::sendTestEventLog(TestEvent& testEvent)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100213{
214 nlohmann::json::array_t logEntryArray;
215 nlohmann::json& logEntryJson = logEntryArray.emplace_back();
216
Chandramohan Harkude81ee0e72024-12-20 19:22:12 +0530217 if (testEvent.eventGroupId)
218 {
219 logEntryJson["EventGroupId"] = *testEvent.eventGroupId;
220 }
221
222 if (testEvent.eventId)
223 {
224 logEntryJson["EventId"] = *testEvent.eventId;
225 }
226
227 if (testEvent.eventTimestamp)
228 {
229 logEntryJson["EventTimestamp"] = *testEvent.eventTimestamp;
230 }
231
232 if (testEvent.originOfCondition)
233 {
234 logEntryJson["OriginOfCondition"]["@odata.id"] =
235 *testEvent.originOfCondition;
236 }
237 if (testEvent.severity)
238 {
239 logEntryJson["Severity"] = *testEvent.severity;
240 }
241
242 if (testEvent.message)
243 {
244 logEntryJson["Message"] = *testEvent.message;
245 }
246
247 if (testEvent.resolution)
248 {
249 logEntryJson["Resolution"] = *testEvent.resolution;
250 }
251
252 if (testEvent.messageId)
253 {
254 logEntryJson["MessageId"] = *testEvent.messageId;
255 }
256
257 if (testEvent.messageArgs)
258 {
259 logEntryJson["MessageArgs"] = *testEvent.messageArgs;
260 }
Alexander Hansen02c1e292024-11-15 14:30:40 +0100261 // MemberId is 0 : since we are sending one event record.
262 logEntryJson["MemberId"] = "0";
Alexander Hansen02c1e292024-11-15 14:30:40 +0100263
264 nlohmann::json msg;
265 msg["@odata.type"] = "#Event.v1_4_0.Event";
266 msg["Id"] = std::to_string(eventSeqNum);
267 msg["Name"] = "Event Log";
268 msg["Events"] = logEntryArray;
269
270 std::string strMsg =
271 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
272 return sendEventToSubscriber(std::move(strMsg));
273}
274
275void Subscription::filterAndSendEventLogs(
276 const std::vector<EventLogObjectsType>& eventRecords)
277{
278 nlohmann::json::array_t logEntryArray;
279 for (const EventLogObjectsType& logEntry : eventRecords)
280 {
281 std::vector<std::string_view> messageArgsView(
282 logEntry.messageArgs.begin(), logEntry.messageArgs.end());
283
284 nlohmann::json::object_t bmcLogEntry;
285 if (event_log::formatEventLogEntry(
286 logEntry.id, logEntry.messageId, messageArgsView,
287 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
288 {
289 BMCWEB_LOG_DEBUG("Read eventLog entry failed");
290 continue;
291 }
292
293 if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
294 {
295 BMCWEB_LOG_DEBUG("Event {} did not match the filter",
296 nlohmann::json(bmcLogEntry).dump());
297 continue;
298 }
299
300 if (filter)
301 {
302 if (!memberMatches(bmcLogEntry, *filter))
303 {
304 BMCWEB_LOG_DEBUG("Filter didn't match");
305 continue;
306 }
307 }
308
309 logEntryArray.emplace_back(std::move(bmcLogEntry));
310 }
311
312 if (logEntryArray.empty())
313 {
314 BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
315 return;
316 }
317
318 nlohmann::json msg;
319 msg["@odata.type"] = "#Event.v1_4_0.Event";
320 msg["Id"] = std::to_string(eventSeqNum);
321 msg["Name"] = "Event Log";
322 msg["Events"] = std::move(logEntryArray);
323 std::string strMsg =
324 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
325 sendEventToSubscriber(std::move(strMsg));
326 eventSeqNum++;
327}
328
329void Subscription::filterAndSendReports(const std::string& reportId,
330 const telemetry::TimestampReadings& var)
331{
332 boost::urls::url mrdUri = boost::urls::format(
333 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
334
335 // Empty list means no filter. Send everything.
336 if (!userSub->metricReportDefinitions.empty())
337 {
338 if (std::ranges::find(userSub->metricReportDefinitions,
339 mrdUri.buffer()) ==
340 userSub->metricReportDefinitions.end())
341 {
342 return;
343 }
344 }
345
346 nlohmann::json msg;
347 if (!telemetry::fillReport(msg, reportId, var))
348 {
349 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
350 "Report with id {}",
351 reportId);
352 return;
353 }
354
355 // Context is set by user during Event subscription and it must be
356 // set for MetricReport response.
357 if (!userSub->customText.empty())
358 {
359 msg["Context"] = userSub->customText;
360 }
361
362 std::string strMsg =
363 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
364 sendEventToSubscriber(std::move(strMsg));
365}
366
367void Subscription::updateRetryConfig(uint32_t retryAttempts,
368 uint32_t retryTimeoutInterval)
369{
370 if (policy == nullptr)
371 {
372 BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
373 return;
374 }
375 policy->maxRetryAttempts = retryAttempts;
376 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
377}
378
379uint64_t Subscription::getEventSeqNum() const
380{
381 return eventSeqNum;
382}
383
384bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
385{
386 return &thisConn == sseConn;
387}
388
389// Check used to indicate what response codes are valid as part of our retry
390// policy. 2XX is considered acceptable
391boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
392{
393 BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
394 if ((respCode < 200) || (respCode >= 300))
395 {
396 return boost::system::errc::make_error_code(
397 boost::system::errc::result_out_of_range);
398 }
399
400 // Return 0 if the response code is valid
401 return boost::system::errc::make_error_code(boost::system::errc::success);
402}
403
404} // namespace redfish