15ffbc9aeSGunnar Mills // SPDX-License-Identifier: Apache-2.0 25ffbc9aeSGunnar Mills // SPDX-FileCopyrightText: Copyright OpenBMC Authors 35ffbc9aeSGunnar 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 "server_sent_event.hpp" 1702c1e29fSAlexander Hansen #include "ssl_key_handler.hpp" 18*f86cdd7dSEd Tanous #include "telemetry_readings.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 <format> 3602c1e29fSAlexander Hansen #include <functional> 3702c1e29fSAlexander Hansen #include <memory> 3802c1e29fSAlexander Hansen #include <span> 3902c1e29fSAlexander Hansen #include <string> 4002c1e29fSAlexander Hansen #include <string_view> 4102c1e29fSAlexander Hansen #include <utility> 4202c1e29fSAlexander Hansen #include <vector> 4302c1e29fSAlexander Hansen 4402c1e29fSAlexander Hansen namespace redfish 4502c1e29fSAlexander Hansen { 4602c1e29fSAlexander Hansen 4702c1e29fSAlexander Hansen Subscription::Subscription( 4802c1e29fSAlexander Hansen std::shared_ptr<persistent_data::UserSubscription> userSubIn, 4902c1e29fSAlexander Hansen const boost::urls::url_view_base& url, boost::asio::io_context& ioc) : 5002c1e29fSAlexander Hansen userSub{std::move(userSubIn)}, 51fb546105SMyung Bae policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc) 5202c1e29fSAlexander Hansen { 5302c1e29fSAlexander Hansen userSub->destinationUrl = url; 5402c1e29fSAlexander Hansen client.emplace(ioc, policy); 5502c1e29fSAlexander Hansen // Subscription constructor 5602c1e29fSAlexander Hansen policy->invalidResp = retryRespHandler; 5702c1e29fSAlexander Hansen } 5802c1e29fSAlexander Hansen 5902c1e29fSAlexander Hansen Subscription::Subscription(crow::sse_socket::Connection& connIn) : 6002c1e29fSAlexander Hansen userSub{std::make_shared<persistent_data::UserSubscription>()}, 61fb546105SMyung Bae sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context()) 6202c1e29fSAlexander Hansen {} 6302c1e29fSAlexander Hansen 6402c1e29fSAlexander Hansen // callback for subscription sendData 6599ff0ddcSMyung Bae void Subscription::resHandler(const std::shared_ptr<Subscription>& /*self*/, 6699ff0ddcSMyung Bae const crow::Response& res) 6702c1e29fSAlexander Hansen { 6802c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt()); 6902c1e29fSAlexander Hansen 7002c1e29fSAlexander Hansen if (!client) 7102c1e29fSAlexander Hansen { 7202c1e29fSAlexander Hansen BMCWEB_LOG_ERROR( 7302c1e29fSAlexander Hansen "Http client wasn't filled but http client callback was called."); 7402c1e29fSAlexander Hansen return; 7502c1e29fSAlexander Hansen } 7602c1e29fSAlexander Hansen 7702c1e29fSAlexander Hansen if (userSub->retryPolicy != "TerminateAfterRetries") 7802c1e29fSAlexander Hansen { 7902c1e29fSAlexander Hansen return; 8002c1e29fSAlexander Hansen } 8102c1e29fSAlexander Hansen if (client->isTerminated()) 8202c1e29fSAlexander Hansen { 83fb546105SMyung Bae hbTimer.cancel(); 8402c1e29fSAlexander Hansen if (deleter) 8502c1e29fSAlexander Hansen { 8602c1e29fSAlexander Hansen BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts", 8702c1e29fSAlexander Hansen userSub->id); 8802c1e29fSAlexander Hansen deleter(); 8902c1e29fSAlexander Hansen } 9002c1e29fSAlexander Hansen } 9102c1e29fSAlexander Hansen } 9202c1e29fSAlexander Hansen 93fb546105SMyung Bae void Subscription::sendHeartbeatEvent() 94fb546105SMyung Bae { 95fb546105SMyung Bae // send the heartbeat message 96fb546105SMyung Bae nlohmann::json eventMessage = messages::redfishServiceFunctional(); 97fb546105SMyung Bae eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first; 98fb546105SMyung Bae eventMessage["OriginOfCondition"] = 99fb546105SMyung Bae std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id); 100fb546105SMyung Bae eventMessage["MemberId"] = "0"; 101fb546105SMyung Bae 102fb546105SMyung Bae nlohmann::json::array_t eventRecord; 103fb546105SMyung Bae eventRecord.emplace_back(std::move(eventMessage)); 104fb546105SMyung Bae 105fb546105SMyung Bae nlohmann::json msgJson; 106fb546105SMyung Bae msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 107fb546105SMyung Bae msgJson["Name"] = "Heartbeat"; 108fb546105SMyung Bae msgJson["Events"] = std::move(eventRecord); 109fb546105SMyung Bae 110fb546105SMyung Bae std::string strMsg = 111fb546105SMyung Bae msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 1124a19a7b5SEd Tanous 1134a19a7b5SEd Tanous // Note, eventId here is always zero, because this is a a per subscription 1144a19a7b5SEd Tanous // event and doesn't have an "ID" 1154a19a7b5SEd Tanous uint64_t eventId = 0; 1164a19a7b5SEd Tanous sendEventToSubscriber(eventId, std::move(strMsg)); 117fb546105SMyung Bae } 118fb546105SMyung Bae 119fb546105SMyung Bae void Subscription::scheduleNextHeartbeatEvent() 120fb546105SMyung Bae { 121fb546105SMyung Bae hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes)); 122fb546105SMyung Bae hbTimer.async_wait( 123fb546105SMyung Bae std::bind_front(&Subscription::onHbTimeout, this, weak_from_this())); 124fb546105SMyung Bae } 125fb546105SMyung Bae 126fb546105SMyung Bae void Subscription::heartbeatParametersChanged() 127fb546105SMyung Bae { 128fb546105SMyung Bae hbTimer.cancel(); 129fb546105SMyung Bae 130fb546105SMyung Bae if (userSub->sendHeartbeat) 131fb546105SMyung Bae { 132fb546105SMyung Bae scheduleNextHeartbeatEvent(); 133fb546105SMyung Bae } 134fb546105SMyung Bae } 135fb546105SMyung Bae 136fb546105SMyung Bae void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf, 137fb546105SMyung Bae const boost::system::error_code& ec) 138fb546105SMyung Bae { 139fb546105SMyung Bae if (ec == boost::asio::error::operation_aborted) 140fb546105SMyung Bae { 141fb546105SMyung Bae BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted"); 142fb546105SMyung Bae return; 143fb546105SMyung Bae } 144fb546105SMyung Bae if (ec == boost::system::errc::operation_canceled) 145fb546105SMyung Bae { 146fb546105SMyung Bae BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled"); 147fb546105SMyung Bae return; 148fb546105SMyung Bae } 149fb546105SMyung Bae if (ec) 150fb546105SMyung Bae { 151fb546105SMyung Bae BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec); 152fb546105SMyung Bae return; 153fb546105SMyung Bae } 154fb546105SMyung Bae 155fb546105SMyung Bae std::shared_ptr<Subscription> self = weakSelf.lock(); 156fb546105SMyung Bae if (!self) 157fb546105SMyung Bae { 158fb546105SMyung Bae BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription"); 159fb546105SMyung Bae return; 160fb546105SMyung Bae } 161fb546105SMyung Bae 162fb546105SMyung Bae // Timer expired. 163fb546105SMyung Bae sendHeartbeatEvent(); 164fb546105SMyung Bae 165fb546105SMyung Bae // reschedule heartbeat timer 166fb546105SMyung Bae scheduleNextHeartbeatEvent(); 167fb546105SMyung Bae } 168fb546105SMyung Bae 1694a19a7b5SEd Tanous bool Subscription::sendEventToSubscriber(uint64_t eventId, std::string&& msg) 17002c1e29fSAlexander Hansen { 17102c1e29fSAlexander Hansen persistent_data::EventServiceConfig eventServiceConfig = 17202c1e29fSAlexander Hansen persistent_data::EventServiceStore::getInstance() 17302c1e29fSAlexander Hansen .getEventServiceConfig(); 17402c1e29fSAlexander Hansen if (!eventServiceConfig.enabled) 17502c1e29fSAlexander Hansen { 17602c1e29fSAlexander Hansen return false; 17702c1e29fSAlexander Hansen } 17802c1e29fSAlexander Hansen 17902c1e29fSAlexander Hansen if (client) 18002c1e29fSAlexander Hansen { 1814ac78946SEd Tanous boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders); 1824ac78946SEd Tanous httpHeadersCopy.set(boost::beast::http::field::content_type, 1834ac78946SEd Tanous "application/json"); 18402c1e29fSAlexander Hansen client->sendDataWithCallback( 18502c1e29fSAlexander Hansen std::move(msg), userSub->destinationUrl, 18602c1e29fSAlexander Hansen static_cast<ensuressl::VerifyCertificate>( 18702c1e29fSAlexander Hansen userSub->verifyCertificate), 1884ac78946SEd Tanous httpHeadersCopy, boost::beast::http::verb::post, 18999ff0ddcSMyung Bae std::bind_front(&Subscription::resHandler, this, 19099ff0ddcSMyung Bae shared_from_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