blob: 00c9036bcbc02134f7be41563a511419fa281248 [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
79void Subscription::resHandler(const std::shared_ptr<Subscription>& /*unused*/,
80 const crow::Response& res)
81{
82 BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
83
84 if (!client)
85 {
86 BMCWEB_LOG_ERROR(
87 "Http client wasn't filled but http client callback was called.");
88 return;
89 }
90
91 if (userSub->retryPolicy != "TerminateAfterRetries")
92 {
93 return;
94 }
95 if (client->isTerminated())
96 {
Myung Baefb546102024-10-29 10:21:26 -050097 hbTimer.cancel();
Alexander Hansen02c1e292024-11-15 14:30:40 +010098 if (deleter)
99 {
100 BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
101 userSub->id);
102 deleter();
103 }
104 }
105}
106
Myung Baefb546102024-10-29 10:21:26 -0500107void Subscription::sendHeartbeatEvent()
108{
109 // send the heartbeat message
110 nlohmann::json eventMessage = messages::redfishServiceFunctional();
111
112 std::string heartEventId = std::to_string(eventSeqNum);
113 eventMessage["EventId"] = heartEventId;
114 eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
115 eventMessage["OriginOfCondition"] =
116 std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
117 eventMessage["MemberId"] = "0";
118
119 nlohmann::json::array_t eventRecord;
120 eventRecord.emplace_back(std::move(eventMessage));
121
122 nlohmann::json msgJson;
123 msgJson["@odata.type"] = "#Event.v1_4_0.Event";
124 msgJson["Name"] = "Heartbeat";
125 msgJson["Id"] = heartEventId;
126 msgJson["Events"] = std::move(eventRecord);
127
128 std::string strMsg =
129 msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
130 sendEventToSubscriber(std::move(strMsg));
131 eventSeqNum++;
132}
133
134void Subscription::scheduleNextHeartbeatEvent()
135{
136 hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
137 hbTimer.async_wait(
138 std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
139}
140
141void Subscription::heartbeatParametersChanged()
142{
143 hbTimer.cancel();
144
145 if (userSub->sendHeartbeat)
146 {
147 scheduleNextHeartbeatEvent();
148 }
149}
150
151void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
152 const boost::system::error_code& ec)
153{
154 if (ec == boost::asio::error::operation_aborted)
155 {
156 BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
157 return;
158 }
159 if (ec == boost::system::errc::operation_canceled)
160 {
161 BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
162 return;
163 }
164 if (ec)
165 {
166 BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
167 return;
168 }
169
170 std::shared_ptr<Subscription> self = weakSelf.lock();
171 if (!self)
172 {
173 BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
174 return;
175 }
176
177 // Timer expired.
178 sendHeartbeatEvent();
179
180 // reschedule heartbeat timer
181 scheduleNextHeartbeatEvent();
182}
183
Alexander Hansen02c1e292024-11-15 14:30:40 +0100184bool Subscription::sendEventToSubscriber(std::string&& msg)
185{
186 persistent_data::EventServiceConfig eventServiceConfig =
187 persistent_data::EventServiceStore::getInstance()
188 .getEventServiceConfig();
189 if (!eventServiceConfig.enabled)
190 {
191 return false;
192 }
193
194 if (client)
195 {
Ed Tanous4ac78942024-12-02 08:25:38 -0800196 boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
197 httpHeadersCopy.set(boost::beast::http::field::content_type,
198 "application/json");
Alexander Hansen02c1e292024-11-15 14:30:40 +0100199 client->sendDataWithCallback(
200 std::move(msg), userSub->destinationUrl,
201 static_cast<ensuressl::VerifyCertificate>(
202 userSub->verifyCertificate),
Ed Tanous4ac78942024-12-02 08:25:38 -0800203 httpHeadersCopy, boost::beast::http::verb::post,
Alexander Hansen02c1e292024-11-15 14:30:40 +0100204 std::bind_front(&Subscription::resHandler, this,
205 shared_from_this()));
206 return true;
207 }
208
209 if (sseConn != nullptr)
210 {
211 eventSeqNum++;
212 sseConn->sendSseEvent(std::to_string(eventSeqNum), msg);
213 }
214 return true;
215}
216
Chandramohan Harkude81ee0e72024-12-20 19:22:12 +0530217bool Subscription::sendTestEventLog(TestEvent& testEvent)
Alexander Hansen02c1e292024-11-15 14:30:40 +0100218{
219 nlohmann::json::array_t logEntryArray;
220 nlohmann::json& logEntryJson = logEntryArray.emplace_back();
221
Chandramohan Harkude81ee0e72024-12-20 19:22:12 +0530222 if (testEvent.eventGroupId)
223 {
224 logEntryJson["EventGroupId"] = *testEvent.eventGroupId;
225 }
226
227 if (testEvent.eventId)
228 {
229 logEntryJson["EventId"] = *testEvent.eventId;
230 }
231
232 if (testEvent.eventTimestamp)
233 {
234 logEntryJson["EventTimestamp"] = *testEvent.eventTimestamp;
235 }
236
237 if (testEvent.originOfCondition)
238 {
239 logEntryJson["OriginOfCondition"]["@odata.id"] =
240 *testEvent.originOfCondition;
241 }
242 if (testEvent.severity)
243 {
244 logEntryJson["Severity"] = *testEvent.severity;
245 }
246
247 if (testEvent.message)
248 {
249 logEntryJson["Message"] = *testEvent.message;
250 }
251
252 if (testEvent.resolution)
253 {
254 logEntryJson["Resolution"] = *testEvent.resolution;
255 }
256
257 if (testEvent.messageId)
258 {
259 logEntryJson["MessageId"] = *testEvent.messageId;
260 }
261
262 if (testEvent.messageArgs)
263 {
264 logEntryJson["MessageArgs"] = *testEvent.messageArgs;
265 }
Alexander Hansen02c1e292024-11-15 14:30:40 +0100266 // MemberId is 0 : since we are sending one event record.
267 logEntryJson["MemberId"] = "0";
Alexander Hansen02c1e292024-11-15 14:30:40 +0100268
269 nlohmann::json msg;
270 msg["@odata.type"] = "#Event.v1_4_0.Event";
271 msg["Id"] = std::to_string(eventSeqNum);
272 msg["Name"] = "Event Log";
273 msg["Events"] = logEntryArray;
274
275 std::string strMsg =
276 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
277 return sendEventToSubscriber(std::move(strMsg));
278}
279
280void Subscription::filterAndSendEventLogs(
281 const std::vector<EventLogObjectsType>& eventRecords)
282{
283 nlohmann::json::array_t logEntryArray;
284 for (const EventLogObjectsType& logEntry : eventRecords)
285 {
286 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 {
294 BMCWEB_LOG_DEBUG("Read eventLog entry failed");
295 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