blob: 1ca8a5efc68ba36811b1ae05cd2660d75a1617fb [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();
110
111 std::string heartEventId = std::to_string(eventSeqNum);
112 eventMessage["EventId"] = heartEventId;
113 eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
114 eventMessage["OriginOfCondition"] =
115 std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
116 eventMessage["MemberId"] = "0";
117
118 nlohmann::json::array_t eventRecord;
119 eventRecord.emplace_back(std::move(eventMessage));
120
121 nlohmann::json msgJson;
122 msgJson["@odata.type"] = "#Event.v1_4_0.Event";
123 msgJson["Name"] = "Heartbeat";
124 msgJson["Id"] = heartEventId;
125 msgJson["Events"] = std::move(eventRecord);
126
127 std::string strMsg =
128 msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
129 sendEventToSubscriber(std::move(strMsg));
130 eventSeqNum++;
131}
132
133void Subscription::scheduleNextHeartbeatEvent()
134{
135 hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
136 hbTimer.async_wait(
137 std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
138}
139
140void Subscription::heartbeatParametersChanged()
141{
142 hbTimer.cancel();
143
144 if (userSub->sendHeartbeat)
145 {
146 scheduleNextHeartbeatEvent();
147 }
148}
149
150void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
151 const boost::system::error_code& ec)
152{
153 if (ec == boost::asio::error::operation_aborted)
154 {
155 BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
156 return;
157 }
158 if (ec == boost::system::errc::operation_canceled)
159 {
160 BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
161 return;
162 }
163 if (ec)
164 {
165 BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
166 return;
167 }
168
169 std::shared_ptr<Subscription> self = weakSelf.lock();
170 if (!self)
171 {
172 BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
173 return;
174 }
175
176 // Timer expired.
177 sendHeartbeatEvent();
178
179 // reschedule heartbeat timer
180 scheduleNextHeartbeatEvent();
181}
182
Alexander Hansen02c1e292024-11-15 14:30:40 +0100183bool Subscription::sendEventToSubscriber(std::string&& msg)
184{
185 persistent_data::EventServiceConfig eventServiceConfig =
186 persistent_data::EventServiceStore::getInstance()
187 .getEventServiceConfig();
188 if (!eventServiceConfig.enabled)
189 {
190 return false;
191 }
192
193 if (client)
194 {
Ed Tanous4ac78942024-12-02 08:25:38 -0800195 boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
196 httpHeadersCopy.set(boost::beast::http::field::content_type,
197 "application/json");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100198 client->sendDataWithCallback(
199 std::move(msg), userSub->destinationUrl,
200 static_cast<ensuressl::VerifyCertificate>(
201 userSub->verifyCertificate),
Ed Tanous4ac78942024-12-02 08:25:38 -0800202 httpHeadersCopy, boost::beast::http::verb::post,
Alexander Hansenf2656d12025-01-13 15:14:29 +0100203 std::bind_front(&Subscription::resHandler, this));
Alexander Hansen02c1e292024-11-15 14:30:40 +0100204 return true;
205 }
206
207 if (sseConn != nullptr)
208 {
209 eventSeqNum++;
210 sseConn->sendSseEvent(std::to_string(eventSeqNum), msg);
211 }
212 return true;
213}
214
Chandramohan Harkude81ee0e72024-12-20 19:22:12 +0530215bool Subscription::sendTestEventLog(TestEvent& testEvent)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100216{
217 nlohmann::json::array_t logEntryArray;
218 nlohmann::json& logEntryJson = logEntryArray.emplace_back();
219
Chandramohan Harkude81ee0e72024-12-20 19:22:12 +0530220 if (testEvent.eventGroupId)
221 {
222 logEntryJson["EventGroupId"] = *testEvent.eventGroupId;
223 }
224
225 if (testEvent.eventId)
226 {
227 logEntryJson["EventId"] = *testEvent.eventId;
228 }
229
230 if (testEvent.eventTimestamp)
231 {
232 logEntryJson["EventTimestamp"] = *testEvent.eventTimestamp;
233 }
234
235 if (testEvent.originOfCondition)
236 {
237 logEntryJson["OriginOfCondition"]["@odata.id"] =
238 *testEvent.originOfCondition;
239 }
240 if (testEvent.severity)
241 {
242 logEntryJson["Severity"] = *testEvent.severity;
243 }
244
245 if (testEvent.message)
246 {
247 logEntryJson["Message"] = *testEvent.message;
248 }
249
250 if (testEvent.resolution)
251 {
252 logEntryJson["Resolution"] = *testEvent.resolution;
253 }
254
255 if (testEvent.messageId)
256 {
257 logEntryJson["MessageId"] = *testEvent.messageId;
258 }
259
260 if (testEvent.messageArgs)
261 {
262 logEntryJson["MessageArgs"] = *testEvent.messageArgs;
263 }
Alexander Hansen02c1e292024-11-15 14:30:40 +0100264 // MemberId is 0 : since we are sending one event record.
265 logEntryJson["MemberId"] = "0";
Alexander Hansen02c1e292024-11-15 14:30:40 +0100266
267 nlohmann::json msg;
268 msg["@odata.type"] = "#Event.v1_4_0.Event";
269 msg["Id"] = std::to_string(eventSeqNum);
270 msg["Name"] = "Event Log";
271 msg["Events"] = logEntryArray;
272
273 std::string strMsg =
274 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
275 return sendEventToSubscriber(std::move(strMsg));
276}
277
278void Subscription::filterAndSendEventLogs(
279 const std::vector<EventLogObjectsType>& eventRecords)
280{
281 nlohmann::json::array_t logEntryArray;
282 for (const EventLogObjectsType& logEntry : eventRecords)
283 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800284 BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id,
285 logEntry.timestamp, logEntry.messageId);
Alexander Hansen02c1e292024-11-15 14:30:40 +0100286 std::vector<std::string_view> messageArgsView(
287 logEntry.messageArgs.begin(), logEntry.messageArgs.end());
288
289 nlohmann::json::object_t bmcLogEntry;
290 if (event_log::formatEventLogEntry(
291 logEntry.id, logEntry.messageId, messageArgsView,
292 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
293 {
Igor Kanyuka0309c212025-01-10 03:38:25 -0800294 BMCWEB_LOG_WARNING("Read eventLog entry failed");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100295 continue;
296 }
297
298 if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
299 {
300 BMCWEB_LOG_DEBUG("Event {} did not match the filter",
301 nlohmann::json(bmcLogEntry).dump());
302 continue;
303 }
304
305 if (filter)
306 {
307 if (!memberMatches(bmcLogEntry, *filter))
308 {
309 BMCWEB_LOG_DEBUG("Filter didn't match");
310 continue;
311 }
312 }
313
314 logEntryArray.emplace_back(std::move(bmcLogEntry));
315 }
316
317 if (logEntryArray.empty())
318 {
319 BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
320 return;
321 }
322
323 nlohmann::json msg;
324 msg["@odata.type"] = "#Event.v1_4_0.Event";
325 msg["Id"] = std::to_string(eventSeqNum);
326 msg["Name"] = "Event Log";
327 msg["Events"] = std::move(logEntryArray);
328 std::string strMsg =
329 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
330 sendEventToSubscriber(std::move(strMsg));
331 eventSeqNum++;
332}
333
334void Subscription::filterAndSendReports(const std::string& reportId,
335 const telemetry::TimestampReadings& var)
336{
337 boost::urls::url mrdUri = boost::urls::format(
338 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
339
340 // Empty list means no filter. Send everything.
341 if (!userSub->metricReportDefinitions.empty())
342 {
343 if (std::ranges::find(userSub->metricReportDefinitions,
344 mrdUri.buffer()) ==
345 userSub->metricReportDefinitions.end())
346 {
347 return;
348 }
349 }
350
351 nlohmann::json msg;
352 if (!telemetry::fillReport(msg, reportId, var))
353 {
354 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
355 "Report with id {}",
356 reportId);
357 return;
358 }
359
360 // Context is set by user during Event subscription and it must be
361 // set for MetricReport response.
362 if (!userSub->customText.empty())
363 {
364 msg["Context"] = userSub->customText;
365 }
366
367 std::string strMsg =
368 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
369 sendEventToSubscriber(std::move(strMsg));
370}
371
372void Subscription::updateRetryConfig(uint32_t retryAttempts,
373 uint32_t retryTimeoutInterval)
374{
375 if (policy == nullptr)
376 {
377 BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
378 return;
379 }
380 policy->maxRetryAttempts = retryAttempts;
381 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
382}
383
384uint64_t Subscription::getEventSeqNum() const
385{
386 return eventSeqNum;
387}
388
389bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
390{
391 return &thisConn == sseConn;
392}
393
394// Check used to indicate what response codes are valid as part of our retry
395// policy. 2XX is considered acceptable
396boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
397{
398 BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
399 if ((respCode < 200) || (respCode >= 300))
400 {
401 return boost::system::errc::make_error_code(
402 boost::system::errc::result_out_of_range);
403 }
404
405 // Return 0 if the response code is valid
406 return boost::system::errc::make_error_code(boost::system::errc::success);
407}
408
409} // namespace redfish