xref: /openbmc/bmcweb/features/redfish/src/subscription.cpp (revision 5ffbc9ae424658c15c2b6d93b86b752f09d2b6dc)
1*5ffbc9aeSGunnar Mills // SPDX-License-Identifier: Apache-2.0
2*5ffbc9aeSGunnar Mills // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3*5ffbc9aeSGunnar Mills // SPDX-FileCopyrightText: Copyright 2020 Intel Corporation
402c1e29fSAlexander Hansen #include "subscription.hpp"
502c1e29fSAlexander Hansen 
6fb546105SMyung Bae #include "dbus_singleton.hpp"
7b80ba2e4SAlexander Hansen #include "event_log.hpp"
802c1e29fSAlexander Hansen #include "event_logs_object_type.hpp"
902c1e29fSAlexander Hansen #include "event_matches_filter.hpp"
1002c1e29fSAlexander Hansen #include "event_service_store.hpp"
1102c1e29fSAlexander Hansen #include "filter_expr_executor.hpp"
12fb546105SMyung Bae #include "heartbeat_messages.hpp"
1302c1e29fSAlexander Hansen #include "http_client.hpp"
1402c1e29fSAlexander Hansen #include "http_response.hpp"
1502c1e29fSAlexander Hansen #include "logging.hpp"
1602c1e29fSAlexander Hansen #include "metric_report.hpp"
1702c1e29fSAlexander Hansen #include "server_sent_event.hpp"
1802c1e29fSAlexander Hansen #include "ssl_key_handler.hpp"
1902c1e29fSAlexander Hansen #include "utils/time_utils.hpp"
2002c1e29fSAlexander Hansen 
21fb546105SMyung Bae #include <boost/asio/error.hpp>
2202c1e29fSAlexander Hansen #include <boost/asio/io_context.hpp>
23fb546105SMyung Bae #include <boost/asio/steady_timer.hpp>
244ac78946SEd Tanous #include <boost/beast/http/field.hpp>
254ac78946SEd Tanous #include <boost/beast/http/fields.hpp>
2602c1e29fSAlexander Hansen #include <boost/beast/http/verb.hpp>
2702c1e29fSAlexander Hansen #include <boost/system/errc.hpp>
2802c1e29fSAlexander Hansen #include <boost/url/format.hpp>
2902c1e29fSAlexander Hansen #include <boost/url/url_view_base.hpp>
3002c1e29fSAlexander Hansen #include <nlohmann/json.hpp>
3102c1e29fSAlexander Hansen 
3202c1e29fSAlexander Hansen #include <algorithm>
33fb546105SMyung Bae #include <chrono>
3402c1e29fSAlexander Hansen #include <cstdint>
3502c1e29fSAlexander Hansen #include <cstdlib>
3602c1e29fSAlexander Hansen #include <ctime>
3702c1e29fSAlexander Hansen #include <format>
3802c1e29fSAlexander Hansen #include <functional>
3902c1e29fSAlexander Hansen #include <memory>
4002c1e29fSAlexander Hansen #include <span>
4102c1e29fSAlexander Hansen #include <string>
4202c1e29fSAlexander Hansen #include <string_view>
4302c1e29fSAlexander Hansen #include <utility>
4402c1e29fSAlexander Hansen #include <vector>
4502c1e29fSAlexander Hansen 
4602c1e29fSAlexander Hansen namespace redfish
4702c1e29fSAlexander Hansen {
4802c1e29fSAlexander Hansen 
4902c1e29fSAlexander Hansen Subscription::Subscription(
5002c1e29fSAlexander Hansen     std::shared_ptr<persistent_data::UserSubscription> userSubIn,
5102c1e29fSAlexander Hansen     const boost::urls::url_view_base& url, boost::asio::io_context& ioc) :
5202c1e29fSAlexander Hansen     userSub{std::move(userSubIn)},
53fb546105SMyung Bae     policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
5402c1e29fSAlexander Hansen {
5502c1e29fSAlexander Hansen     userSub->destinationUrl = url;
5602c1e29fSAlexander Hansen     client.emplace(ioc, policy);
5702c1e29fSAlexander Hansen     // Subscription constructor
5802c1e29fSAlexander Hansen     policy->invalidResp = retryRespHandler;
5902c1e29fSAlexander Hansen }
6002c1e29fSAlexander Hansen 
6102c1e29fSAlexander Hansen Subscription::Subscription(crow::sse_socket::Connection& connIn) :
6202c1e29fSAlexander Hansen     userSub{std::make_shared<persistent_data::UserSubscription>()},
63fb546105SMyung Bae     sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
6402c1e29fSAlexander Hansen {}
6502c1e29fSAlexander Hansen 
6602c1e29fSAlexander Hansen // callback for subscription sendData
67f2656d1bSAlexander Hansen void Subscription::resHandler(const crow::Response& res)
6802c1e29fSAlexander Hansen {
6902c1e29fSAlexander Hansen     BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
7002c1e29fSAlexander Hansen 
7102c1e29fSAlexander Hansen     if (!client)
7202c1e29fSAlexander Hansen     {
7302c1e29fSAlexander Hansen         BMCWEB_LOG_ERROR(
7402c1e29fSAlexander Hansen             "Http client wasn't filled but http client callback was called.");
7502c1e29fSAlexander Hansen         return;
7602c1e29fSAlexander Hansen     }
7702c1e29fSAlexander Hansen 
7802c1e29fSAlexander Hansen     if (userSub->retryPolicy != "TerminateAfterRetries")
7902c1e29fSAlexander Hansen     {
8002c1e29fSAlexander Hansen         return;
8102c1e29fSAlexander Hansen     }
8202c1e29fSAlexander Hansen     if (client->isTerminated())
8302c1e29fSAlexander Hansen     {
84fb546105SMyung Bae         hbTimer.cancel();
8502c1e29fSAlexander Hansen         if (deleter)
8602c1e29fSAlexander Hansen         {
8702c1e29fSAlexander Hansen             BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
8802c1e29fSAlexander Hansen                             userSub->id);
8902c1e29fSAlexander Hansen             deleter();
9002c1e29fSAlexander Hansen         }
9102c1e29fSAlexander Hansen     }
9202c1e29fSAlexander Hansen }
9302c1e29fSAlexander Hansen 
94fb546105SMyung Bae void Subscription::sendHeartbeatEvent()
95fb546105SMyung Bae {
96fb546105SMyung Bae     // send the heartbeat message
97fb546105SMyung Bae     nlohmann::json eventMessage = messages::redfishServiceFunctional();
98fb546105SMyung Bae     eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
99fb546105SMyung Bae     eventMessage["OriginOfCondition"] =
100fb546105SMyung Bae         std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
101fb546105SMyung Bae     eventMessage["MemberId"] = "0";
102fb546105SMyung Bae 
103fb546105SMyung Bae     nlohmann::json::array_t eventRecord;
104fb546105SMyung Bae     eventRecord.emplace_back(std::move(eventMessage));
105fb546105SMyung Bae 
106fb546105SMyung Bae     nlohmann::json msgJson;
107fb546105SMyung Bae     msgJson["@odata.type"] = "#Event.v1_4_0.Event";
108fb546105SMyung Bae     msgJson["Name"] = "Heartbeat";
109fb546105SMyung Bae     msgJson["Events"] = std::move(eventRecord);
110fb546105SMyung Bae 
111fb546105SMyung Bae     std::string strMsg =
112fb546105SMyung Bae         msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
1134a19a7b5SEd Tanous 
1144a19a7b5SEd Tanous     // Note, eventId here is always zero, because this is a a per subscription
1154a19a7b5SEd Tanous     // event and doesn't have an "ID"
1164a19a7b5SEd Tanous     uint64_t eventId = 0;
1174a19a7b5SEd Tanous     sendEventToSubscriber(eventId, std::move(strMsg));
118fb546105SMyung Bae }
119fb546105SMyung Bae 
120fb546105SMyung Bae void Subscription::scheduleNextHeartbeatEvent()
121fb546105SMyung Bae {
122fb546105SMyung Bae     hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
123fb546105SMyung Bae     hbTimer.async_wait(
124fb546105SMyung Bae         std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
125fb546105SMyung Bae }
126fb546105SMyung Bae 
127fb546105SMyung Bae void Subscription::heartbeatParametersChanged()
128fb546105SMyung Bae {
129fb546105SMyung Bae     hbTimer.cancel();
130fb546105SMyung Bae 
131fb546105SMyung Bae     if (userSub->sendHeartbeat)
132fb546105SMyung Bae     {
133fb546105SMyung Bae         scheduleNextHeartbeatEvent();
134fb546105SMyung Bae     }
135fb546105SMyung Bae }
136fb546105SMyung Bae 
137fb546105SMyung Bae void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
138fb546105SMyung Bae                                const boost::system::error_code& ec)
139fb546105SMyung Bae {
140fb546105SMyung Bae     if (ec == boost::asio::error::operation_aborted)
141fb546105SMyung Bae     {
142fb546105SMyung Bae         BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
143fb546105SMyung Bae         return;
144fb546105SMyung Bae     }
145fb546105SMyung Bae     if (ec == boost::system::errc::operation_canceled)
146fb546105SMyung Bae     {
147fb546105SMyung Bae         BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
148fb546105SMyung Bae         return;
149fb546105SMyung Bae     }
150fb546105SMyung Bae     if (ec)
151fb546105SMyung Bae     {
152fb546105SMyung Bae         BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
153fb546105SMyung Bae         return;
154fb546105SMyung Bae     }
155fb546105SMyung Bae 
156fb546105SMyung Bae     std::shared_ptr<Subscription> self = weakSelf.lock();
157fb546105SMyung Bae     if (!self)
158fb546105SMyung Bae     {
159fb546105SMyung Bae         BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
160fb546105SMyung Bae         return;
161fb546105SMyung Bae     }
162fb546105SMyung Bae 
163fb546105SMyung Bae     // Timer expired.
164fb546105SMyung Bae     sendHeartbeatEvent();
165fb546105SMyung Bae 
166fb546105SMyung Bae     // reschedule heartbeat timer
167fb546105SMyung Bae     scheduleNextHeartbeatEvent();
168fb546105SMyung Bae }
169fb546105SMyung Bae 
1704a19a7b5SEd Tanous bool Subscription::sendEventToSubscriber(uint64_t eventId, std::string&& msg)
17102c1e29fSAlexander Hansen {
17202c1e29fSAlexander Hansen     persistent_data::EventServiceConfig eventServiceConfig =
17302c1e29fSAlexander Hansen         persistent_data::EventServiceStore::getInstance()
17402c1e29fSAlexander Hansen             .getEventServiceConfig();
17502c1e29fSAlexander Hansen     if (!eventServiceConfig.enabled)
17602c1e29fSAlexander Hansen     {
17702c1e29fSAlexander Hansen         return false;
17802c1e29fSAlexander Hansen     }
17902c1e29fSAlexander Hansen 
18002c1e29fSAlexander Hansen     if (client)
18102c1e29fSAlexander Hansen     {
1824ac78946SEd Tanous         boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders);
1834ac78946SEd Tanous         httpHeadersCopy.set(boost::beast::http::field::content_type,
1844ac78946SEd Tanous                             "application/json");
18502c1e29fSAlexander Hansen         client->sendDataWithCallback(
18602c1e29fSAlexander Hansen             std::move(msg), userSub->destinationUrl,
18702c1e29fSAlexander Hansen             static_cast<ensuressl::VerifyCertificate>(
18802c1e29fSAlexander Hansen                 userSub->verifyCertificate),
1894ac78946SEd Tanous             httpHeadersCopy, boost::beast::http::verb::post,
190f2656d1bSAlexander Hansen             std::bind_front(&Subscription::resHandler, this));
19102c1e29fSAlexander Hansen         return true;
19202c1e29fSAlexander Hansen     }
19302c1e29fSAlexander Hansen 
19402c1e29fSAlexander Hansen     if (sseConn != nullptr)
19502c1e29fSAlexander Hansen     {
1964a19a7b5SEd Tanous         sseConn->sendSseEvent(std::to_string(eventId), msg);
19702c1e29fSAlexander Hansen     }
19802c1e29fSAlexander Hansen     return true;
19902c1e29fSAlexander Hansen }
20002c1e29fSAlexander Hansen 
20102c1e29fSAlexander Hansen void Subscription::filterAndSendEventLogs(
2024a19a7b5SEd Tanous     uint64_t eventId, const std::vector<EventLogObjectsType>& eventRecords)
20302c1e29fSAlexander Hansen {
20402c1e29fSAlexander Hansen     nlohmann::json::array_t logEntryArray;
20502c1e29fSAlexander Hansen     for (const EventLogObjectsType& logEntry : eventRecords)
20602c1e29fSAlexander Hansen     {
2070309c216SIgor Kanyuka         BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id,
2080309c216SIgor Kanyuka                          logEntry.timestamp, logEntry.messageId);
20902c1e29fSAlexander Hansen         std::vector<std::string_view> messageArgsView(
21002c1e29fSAlexander Hansen             logEntry.messageArgs.begin(), logEntry.messageArgs.end());
21102c1e29fSAlexander Hansen 
21202c1e29fSAlexander Hansen         nlohmann::json::object_t bmcLogEntry;
21302c1e29fSAlexander Hansen         if (event_log::formatEventLogEntry(
2144a19a7b5SEd Tanous                 eventId, logEntry.id, logEntry.messageId, messageArgsView,
21502c1e29fSAlexander Hansen                 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
21602c1e29fSAlexander Hansen         {
2170309c216SIgor Kanyuka             BMCWEB_LOG_WARNING("Read eventLog entry failed");
21802c1e29fSAlexander Hansen             continue;
21902c1e29fSAlexander Hansen         }
22002c1e29fSAlexander Hansen 
22102c1e29fSAlexander Hansen         if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
22202c1e29fSAlexander Hansen         {
22302c1e29fSAlexander Hansen             BMCWEB_LOG_DEBUG("Event {} did not match the filter",
22402c1e29fSAlexander Hansen                              nlohmann::json(bmcLogEntry).dump());
22502c1e29fSAlexander Hansen             continue;
22602c1e29fSAlexander Hansen         }
22702c1e29fSAlexander Hansen 
22802c1e29fSAlexander Hansen         if (filter)
22902c1e29fSAlexander Hansen         {
23002c1e29fSAlexander Hansen             if (!memberMatches(bmcLogEntry, *filter))
23102c1e29fSAlexander Hansen             {
23202c1e29fSAlexander Hansen                 BMCWEB_LOG_DEBUG("Filter didn't match");
23302c1e29fSAlexander Hansen                 continue;
23402c1e29fSAlexander Hansen             }
23502c1e29fSAlexander Hansen         }
23602c1e29fSAlexander Hansen 
23702c1e29fSAlexander Hansen         logEntryArray.emplace_back(std::move(bmcLogEntry));
2384a19a7b5SEd Tanous         eventId++;
23902c1e29fSAlexander Hansen     }
24002c1e29fSAlexander Hansen 
24102c1e29fSAlexander Hansen     if (logEntryArray.empty())
24202c1e29fSAlexander Hansen     {
24302c1e29fSAlexander Hansen         BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
24402c1e29fSAlexander Hansen         return;
24502c1e29fSAlexander Hansen     }
24602c1e29fSAlexander Hansen 
24702c1e29fSAlexander Hansen     nlohmann::json msg;
24802c1e29fSAlexander Hansen     msg["@odata.type"] = "#Event.v1_4_0.Event";
2494a19a7b5SEd Tanous     msg["Id"] = std::to_string(eventId);
25002c1e29fSAlexander Hansen     msg["Name"] = "Event Log";
25102c1e29fSAlexander Hansen     msg["Events"] = std::move(logEntryArray);
25202c1e29fSAlexander Hansen     std::string strMsg =
25302c1e29fSAlexander Hansen         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
2544a19a7b5SEd Tanous     sendEventToSubscriber(eventId, std::move(strMsg));
25502c1e29fSAlexander Hansen }
25602c1e29fSAlexander Hansen 
2574a19a7b5SEd Tanous void Subscription::filterAndSendReports(uint64_t eventId,
2584a19a7b5SEd Tanous                                         const std::string& reportId,
25902c1e29fSAlexander Hansen                                         const telemetry::TimestampReadings& var)
26002c1e29fSAlexander Hansen {
26102c1e29fSAlexander Hansen     boost::urls::url mrdUri = boost::urls::format(
26202c1e29fSAlexander Hansen         "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
26302c1e29fSAlexander Hansen 
26402c1e29fSAlexander Hansen     // Empty list means no filter. Send everything.
26502c1e29fSAlexander Hansen     if (!userSub->metricReportDefinitions.empty())
26602c1e29fSAlexander Hansen     {
26702c1e29fSAlexander Hansen         if (std::ranges::find(userSub->metricReportDefinitions,
26802c1e29fSAlexander Hansen                               mrdUri.buffer()) ==
26902c1e29fSAlexander Hansen             userSub->metricReportDefinitions.end())
27002c1e29fSAlexander Hansen         {
27102c1e29fSAlexander Hansen             return;
27202c1e29fSAlexander Hansen         }
27302c1e29fSAlexander Hansen     }
27402c1e29fSAlexander Hansen 
27502c1e29fSAlexander Hansen     nlohmann::json msg;
27602c1e29fSAlexander Hansen     if (!telemetry::fillReport(msg, reportId, var))
27702c1e29fSAlexander Hansen     {
27802c1e29fSAlexander Hansen         BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
27902c1e29fSAlexander Hansen                          "Report with id {}",
28002c1e29fSAlexander Hansen                          reportId);
28102c1e29fSAlexander Hansen         return;
28202c1e29fSAlexander Hansen     }
28302c1e29fSAlexander Hansen 
28402c1e29fSAlexander Hansen     // Context is set by user during Event subscription and it must be
28502c1e29fSAlexander Hansen     // set for MetricReport response.
28602c1e29fSAlexander Hansen     if (!userSub->customText.empty())
28702c1e29fSAlexander Hansen     {
28802c1e29fSAlexander Hansen         msg["Context"] = userSub->customText;
28902c1e29fSAlexander Hansen     }
29002c1e29fSAlexander Hansen 
29102c1e29fSAlexander Hansen     std::string strMsg =
29202c1e29fSAlexander Hansen         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
2934a19a7b5SEd Tanous     sendEventToSubscriber(eventId, std::move(strMsg));
29402c1e29fSAlexander Hansen }
29502c1e29fSAlexander Hansen 
29602c1e29fSAlexander Hansen void Subscription::updateRetryConfig(uint32_t retryAttempts,
29702c1e29fSAlexander Hansen                                      uint32_t retryTimeoutInterval)
29802c1e29fSAlexander Hansen {
29902c1e29fSAlexander Hansen     if (policy == nullptr)
30002c1e29fSAlexander Hansen     {
30102c1e29fSAlexander Hansen         BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
30202c1e29fSAlexander Hansen         return;
30302c1e29fSAlexander Hansen     }
30402c1e29fSAlexander Hansen     policy->maxRetryAttempts = retryAttempts;
30502c1e29fSAlexander Hansen     policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
30602c1e29fSAlexander Hansen }
30702c1e29fSAlexander Hansen 
30802c1e29fSAlexander Hansen bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
30902c1e29fSAlexander Hansen {
31002c1e29fSAlexander Hansen     return &thisConn == sseConn;
31102c1e29fSAlexander Hansen }
31202c1e29fSAlexander Hansen 
31302c1e29fSAlexander Hansen // Check used to indicate what response codes are valid as part of our retry
31402c1e29fSAlexander Hansen // policy.  2XX is considered acceptable
31502c1e29fSAlexander Hansen boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
31602c1e29fSAlexander Hansen {
31702c1e29fSAlexander Hansen     BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
31802c1e29fSAlexander Hansen     if ((respCode < 200) || (respCode >= 300))
31902c1e29fSAlexander Hansen     {
32002c1e29fSAlexander Hansen         return boost::system::errc::make_error_code(
32102c1e29fSAlexander Hansen             boost::system::errc::result_out_of_range);
32202c1e29fSAlexander Hansen     }
32302c1e29fSAlexander Hansen 
32402c1e29fSAlexander Hansen     // Return 0 if the response code is valid
32502c1e29fSAlexander Hansen     return boost::system::errc::make_error_code(boost::system::errc::success);
32602c1e29fSAlexander Hansen }
32702c1e29fSAlexander Hansen 
32802c1e29fSAlexander Hansen } // namespace redfish
329