102c1e29fSAlexander Hansen /* 202c1e29fSAlexander Hansen Copyright (c) 2020 Intel Corporation 302c1e29fSAlexander Hansen 402c1e29fSAlexander Hansen Licensed under the Apache License, Version 2.0 (the "License"); 502c1e29fSAlexander Hansen you may not use this file except in compliance with the License. 602c1e29fSAlexander Hansen You may obtain a copy of the License at 702c1e29fSAlexander Hansen 802c1e29fSAlexander Hansen http://www.apache.org/licenses/LICENSE-2.0 902c1e29fSAlexander Hansen 1002c1e29fSAlexander Hansen Unless required by applicable law or agreed to in writing, software 1102c1e29fSAlexander Hansen distributed under the License is distributed on an "AS IS" BASIS, 1202c1e29fSAlexander Hansen WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1302c1e29fSAlexander Hansen See the License for the specific language governing permissions and 1402c1e29fSAlexander Hansen limitations under the License. 1502c1e29fSAlexander Hansen */ 1602c1e29fSAlexander Hansen #include "subscription.hpp" 1702c1e29fSAlexander Hansen 18fb546105SMyung Bae #include "dbus_singleton.hpp" 19b80ba2e4SAlexander Hansen #include "event_log.hpp" 2002c1e29fSAlexander Hansen #include "event_logs_object_type.hpp" 2102c1e29fSAlexander Hansen #include "event_matches_filter.hpp" 2202c1e29fSAlexander Hansen #include "event_service_store.hpp" 2302c1e29fSAlexander Hansen #include "filter_expr_executor.hpp" 24fb546105SMyung Bae #include "heartbeat_messages.hpp" 2502c1e29fSAlexander Hansen #include "http_client.hpp" 2602c1e29fSAlexander Hansen #include "http_response.hpp" 2702c1e29fSAlexander Hansen #include "logging.hpp" 2802c1e29fSAlexander Hansen #include "metric_report.hpp" 2902c1e29fSAlexander Hansen #include "server_sent_event.hpp" 3002c1e29fSAlexander Hansen #include "ssl_key_handler.hpp" 3102c1e29fSAlexander Hansen #include "utils/time_utils.hpp" 3202c1e29fSAlexander Hansen 33fb546105SMyung Bae #include <boost/asio/error.hpp> 3402c1e29fSAlexander Hansen #include <boost/asio/io_context.hpp> 35fb546105SMyung Bae #include <boost/asio/steady_timer.hpp> 364ac78946SEd Tanous #include <boost/beast/http/field.hpp> 374ac78946SEd Tanous #include <boost/beast/http/fields.hpp> 3802c1e29fSAlexander Hansen #include <boost/beast/http/verb.hpp> 3902c1e29fSAlexander Hansen #include <boost/system/errc.hpp> 4002c1e29fSAlexander Hansen #include <boost/url/format.hpp> 4102c1e29fSAlexander Hansen #include <boost/url/url_view_base.hpp> 4202c1e29fSAlexander Hansen #include <nlohmann/json.hpp> 4302c1e29fSAlexander Hansen 4402c1e29fSAlexander Hansen #include <algorithm> 45fb546105SMyung Bae #include <chrono> 4602c1e29fSAlexander Hansen #include <cstdint> 4702c1e29fSAlexander Hansen #include <cstdlib> 4802c1e29fSAlexander Hansen #include <ctime> 4902c1e29fSAlexander Hansen #include <format> 5002c1e29fSAlexander Hansen #include <functional> 5102c1e29fSAlexander Hansen #include <memory> 5202c1e29fSAlexander Hansen #include <span> 5302c1e29fSAlexander Hansen #include <string> 5402c1e29fSAlexander Hansen #include <string_view> 5502c1e29fSAlexander Hansen #include <utility> 5602c1e29fSAlexander Hansen #include <vector> 5702c1e29fSAlexander Hansen 5802c1e29fSAlexander Hansen namespace redfish 5902c1e29fSAlexander Hansen { 6002c1e29fSAlexander Hansen 6102c1e29fSAlexander Hansen Subscription::Subscription( 6202c1e29fSAlexander Hansen std::shared_ptr<persistent_data::UserSubscription> userSubIn, 6302c1e29fSAlexander Hansen const boost::urls::url_view_base& url, boost::asio::io_context& ioc) : 6402c1e29fSAlexander Hansen userSub{std::move(userSubIn)}, 65fb546105SMyung Bae policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc) 6602c1e29fSAlexander Hansen { 6702c1e29fSAlexander Hansen userSub->destinationUrl = url; 6802c1e29fSAlexander Hansen client.emplace(ioc, policy); 6902c1e29fSAlexander Hansen // Subscription constructor 7002c1e29fSAlexander Hansen policy->invalidResp = retryRespHandler; 7102c1e29fSAlexander Hansen } 7202c1e29fSAlexander Hansen 7302c1e29fSAlexander Hansen Subscription::Subscription(crow::sse_socket::Connection& connIn) : 7402c1e29fSAlexander Hansen userSub{std::make_shared<persistent_data::UserSubscription>()}, 75fb546105SMyung Bae sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context()) 7602c1e29fSAlexander Hansen {} 7702c1e29fSAlexander Hansen 7802c1e29fSAlexander Hansen // callback for subscription sendData 79f2656d1bSAlexander Hansen void Subscription::resHandler(const crow::Response& res) 8002c1e29fSAlexander Hansen { 8102c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt()); 8202c1e29fSAlexander Hansen 8302c1e29fSAlexander Hansen if (!client) 8402c1e29fSAlexander Hansen { 8502c1e29fSAlexander Hansen BMCWEB_LOG_ERROR( 8602c1e29fSAlexander Hansen "Http client wasn't filled but http client callback was called."); 8702c1e29fSAlexander Hansen return; 8802c1e29fSAlexander Hansen } 8902c1e29fSAlexander Hansen 9002c1e29fSAlexander Hansen if (userSub->retryPolicy != "TerminateAfterRetries") 9102c1e29fSAlexander Hansen { 9202c1e29fSAlexander Hansen return; 9302c1e29fSAlexander Hansen } 9402c1e29fSAlexander Hansen if (client->isTerminated()) 9502c1e29fSAlexander Hansen { 96fb546105SMyung Bae hbTimer.cancel(); 9702c1e29fSAlexander Hansen if (deleter) 9802c1e29fSAlexander Hansen { 9902c1e29fSAlexander Hansen BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts", 10002c1e29fSAlexander Hansen userSub->id); 10102c1e29fSAlexander Hansen deleter(); 10202c1e29fSAlexander Hansen } 10302c1e29fSAlexander Hansen } 10402c1e29fSAlexander Hansen } 10502c1e29fSAlexander Hansen 106fb546105SMyung Bae void Subscription::sendHeartbeatEvent() 107fb546105SMyung Bae { 108fb546105SMyung Bae // send the heartbeat message 109fb546105SMyung Bae nlohmann::json eventMessage = messages::redfishServiceFunctional(); 110fb546105SMyung Bae eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first; 111fb546105SMyung Bae eventMessage["OriginOfCondition"] = 112fb546105SMyung Bae std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id); 113fb546105SMyung Bae eventMessage["MemberId"] = "0"; 114fb546105SMyung Bae 115fb546105SMyung Bae nlohmann::json::array_t eventRecord; 116fb546105SMyung Bae eventRecord.emplace_back(std::move(eventMessage)); 117fb546105SMyung Bae 118fb546105SMyung Bae nlohmann::json msgJson; 119fb546105SMyung Bae msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 120fb546105SMyung Bae msgJson["Name"] = "Heartbeat"; 121fb546105SMyung Bae msgJson["Events"] = std::move(eventRecord); 122fb546105SMyung Bae 123fb546105SMyung Bae std::string strMsg = 124fb546105SMyung Bae msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 125*4a19a7b5SEd Tanous 126*4a19a7b5SEd Tanous // Note, eventId here is always zero, because this is a a per subscription 127*4a19a7b5SEd Tanous // event and doesn't have an "ID" 128*4a19a7b5SEd Tanous uint64_t eventId = 0; 129*4a19a7b5SEd Tanous sendEventToSubscriber(eventId, std::move(strMsg)); 130fb546105SMyung Bae } 131fb546105SMyung Bae 132fb546105SMyung Bae void Subscription::scheduleNextHeartbeatEvent() 133fb546105SMyung Bae { 134fb546105SMyung Bae hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes)); 135fb546105SMyung Bae hbTimer.async_wait( 136fb546105SMyung Bae std::bind_front(&Subscription::onHbTimeout, this, weak_from_this())); 137fb546105SMyung Bae } 138fb546105SMyung Bae 139fb546105SMyung Bae void Subscription::heartbeatParametersChanged() 140fb546105SMyung Bae { 141fb546105SMyung Bae hbTimer.cancel(); 142fb546105SMyung Bae 143fb546105SMyung Bae if (userSub->sendHeartbeat) 144fb546105SMyung Bae { 145fb546105SMyung Bae scheduleNextHeartbeatEvent(); 146fb546105SMyung Bae } 147fb546105SMyung Bae } 148fb546105SMyung Bae 149fb546105SMyung Bae void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf, 150fb546105SMyung Bae const boost::system::error_code& ec) 151fb546105SMyung Bae { 152fb546105SMyung Bae if (ec == boost::asio::error::operation_aborted) 153fb546105SMyung Bae { 154fb546105SMyung Bae BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted"); 155fb546105SMyung Bae return; 156fb546105SMyung Bae } 157fb546105SMyung Bae if (ec == boost::system::errc::operation_canceled) 158fb546105SMyung Bae { 159fb546105SMyung Bae BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled"); 160fb546105SMyung Bae return; 161fb546105SMyung Bae } 162fb546105SMyung Bae if (ec) 163fb546105SMyung Bae { 164fb546105SMyung Bae BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec); 165fb546105SMyung Bae return; 166fb546105SMyung Bae } 167fb546105SMyung Bae 168fb546105SMyung Bae std::shared_ptr<Subscription> self = weakSelf.lock(); 169fb546105SMyung Bae if (!self) 170fb546105SMyung Bae { 171fb546105SMyung Bae BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription"); 172fb546105SMyung Bae return; 173fb546105SMyung Bae } 174fb546105SMyung Bae 175fb546105SMyung Bae // Timer expired. 176fb546105SMyung Bae sendHeartbeatEvent(); 177fb546105SMyung Bae 178fb546105SMyung Bae // reschedule heartbeat timer 179fb546105SMyung Bae scheduleNextHeartbeatEvent(); 180fb546105SMyung Bae } 181fb546105SMyung Bae 182*4a19a7b5SEd Tanous bool Subscription::sendEventToSubscriber(uint64_t eventId, std::string&& msg) 18302c1e29fSAlexander Hansen { 18402c1e29fSAlexander Hansen persistent_data::EventServiceConfig eventServiceConfig = 18502c1e29fSAlexander Hansen persistent_data::EventServiceStore::getInstance() 18602c1e29fSAlexander Hansen .getEventServiceConfig(); 18702c1e29fSAlexander Hansen if (!eventServiceConfig.enabled) 18802c1e29fSAlexander Hansen { 18902c1e29fSAlexander Hansen return false; 19002c1e29fSAlexander Hansen } 19102c1e29fSAlexander Hansen 19202c1e29fSAlexander Hansen if (client) 19302c1e29fSAlexander Hansen { 1944ac78946SEd Tanous boost::beast::http::fields httpHeadersCopy(userSub->httpHeaders); 1954ac78946SEd Tanous httpHeadersCopy.set(boost::beast::http::field::content_type, 1964ac78946SEd Tanous "application/json"); 19702c1e29fSAlexander Hansen client->sendDataWithCallback( 19802c1e29fSAlexander Hansen std::move(msg), userSub->destinationUrl, 19902c1e29fSAlexander Hansen static_cast<ensuressl::VerifyCertificate>( 20002c1e29fSAlexander Hansen userSub->verifyCertificate), 2014ac78946SEd Tanous httpHeadersCopy, boost::beast::http::verb::post, 202f2656d1bSAlexander Hansen std::bind_front(&Subscription::resHandler, this)); 20302c1e29fSAlexander Hansen return true; 20402c1e29fSAlexander Hansen } 20502c1e29fSAlexander Hansen 20602c1e29fSAlexander Hansen if (sseConn != nullptr) 20702c1e29fSAlexander Hansen { 208*4a19a7b5SEd Tanous sseConn->sendSseEvent(std::to_string(eventId), msg); 20902c1e29fSAlexander Hansen } 21002c1e29fSAlexander Hansen return true; 21102c1e29fSAlexander Hansen } 21202c1e29fSAlexander Hansen 21302c1e29fSAlexander Hansen void Subscription::filterAndSendEventLogs( 214*4a19a7b5SEd Tanous uint64_t eventId, const std::vector<EventLogObjectsType>& eventRecords) 21502c1e29fSAlexander Hansen { 21602c1e29fSAlexander Hansen nlohmann::json::array_t logEntryArray; 21702c1e29fSAlexander Hansen for (const EventLogObjectsType& logEntry : eventRecords) 21802c1e29fSAlexander Hansen { 2190309c216SIgor Kanyuka BMCWEB_LOG_DEBUG("Processing logEntry: {}, {} '{}'", logEntry.id, 2200309c216SIgor Kanyuka logEntry.timestamp, logEntry.messageId); 22102c1e29fSAlexander Hansen std::vector<std::string_view> messageArgsView( 22202c1e29fSAlexander Hansen logEntry.messageArgs.begin(), logEntry.messageArgs.end()); 22302c1e29fSAlexander Hansen 22402c1e29fSAlexander Hansen nlohmann::json::object_t bmcLogEntry; 22502c1e29fSAlexander Hansen if (event_log::formatEventLogEntry( 226*4a19a7b5SEd Tanous eventId, logEntry.id, logEntry.messageId, messageArgsView, 22702c1e29fSAlexander Hansen logEntry.timestamp, userSub->customText, bmcLogEntry) != 0) 22802c1e29fSAlexander Hansen { 2290309c216SIgor Kanyuka BMCWEB_LOG_WARNING("Read eventLog entry failed"); 23002c1e29fSAlexander Hansen continue; 23102c1e29fSAlexander Hansen } 23202c1e29fSAlexander Hansen 23302c1e29fSAlexander Hansen if (!eventMatchesFilter(*userSub, bmcLogEntry, "")) 23402c1e29fSAlexander Hansen { 23502c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Event {} did not match the filter", 23602c1e29fSAlexander Hansen nlohmann::json(bmcLogEntry).dump()); 23702c1e29fSAlexander Hansen continue; 23802c1e29fSAlexander Hansen } 23902c1e29fSAlexander Hansen 24002c1e29fSAlexander Hansen if (filter) 24102c1e29fSAlexander Hansen { 24202c1e29fSAlexander Hansen if (!memberMatches(bmcLogEntry, *filter)) 24302c1e29fSAlexander Hansen { 24402c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Filter didn't match"); 24502c1e29fSAlexander Hansen continue; 24602c1e29fSAlexander Hansen } 24702c1e29fSAlexander Hansen } 24802c1e29fSAlexander Hansen 24902c1e29fSAlexander Hansen logEntryArray.emplace_back(std::move(bmcLogEntry)); 250*4a19a7b5SEd Tanous eventId++; 25102c1e29fSAlexander Hansen } 25202c1e29fSAlexander Hansen 25302c1e29fSAlexander Hansen if (logEntryArray.empty()) 25402c1e29fSAlexander Hansen { 25502c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 25602c1e29fSAlexander Hansen return; 25702c1e29fSAlexander Hansen } 25802c1e29fSAlexander Hansen 25902c1e29fSAlexander Hansen nlohmann::json msg; 26002c1e29fSAlexander Hansen msg["@odata.type"] = "#Event.v1_4_0.Event"; 261*4a19a7b5SEd Tanous msg["Id"] = std::to_string(eventId); 26202c1e29fSAlexander Hansen msg["Name"] = "Event Log"; 26302c1e29fSAlexander Hansen msg["Events"] = std::move(logEntryArray); 26402c1e29fSAlexander Hansen std::string strMsg = 26502c1e29fSAlexander Hansen msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 266*4a19a7b5SEd Tanous sendEventToSubscriber(eventId, std::move(strMsg)); 26702c1e29fSAlexander Hansen } 26802c1e29fSAlexander Hansen 269*4a19a7b5SEd Tanous void Subscription::filterAndSendReports(uint64_t eventId, 270*4a19a7b5SEd Tanous const std::string& reportId, 27102c1e29fSAlexander Hansen const telemetry::TimestampReadings& var) 27202c1e29fSAlexander Hansen { 27302c1e29fSAlexander Hansen boost::urls::url mrdUri = boost::urls::format( 27402c1e29fSAlexander Hansen "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId); 27502c1e29fSAlexander Hansen 27602c1e29fSAlexander Hansen // Empty list means no filter. Send everything. 27702c1e29fSAlexander Hansen if (!userSub->metricReportDefinitions.empty()) 27802c1e29fSAlexander Hansen { 27902c1e29fSAlexander Hansen if (std::ranges::find(userSub->metricReportDefinitions, 28002c1e29fSAlexander Hansen mrdUri.buffer()) == 28102c1e29fSAlexander Hansen userSub->metricReportDefinitions.end()) 28202c1e29fSAlexander Hansen { 28302c1e29fSAlexander Hansen return; 28402c1e29fSAlexander Hansen } 28502c1e29fSAlexander Hansen } 28602c1e29fSAlexander Hansen 28702c1e29fSAlexander Hansen nlohmann::json msg; 28802c1e29fSAlexander Hansen if (!telemetry::fillReport(msg, reportId, var)) 28902c1e29fSAlexander Hansen { 29002c1e29fSAlexander Hansen BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus " 29102c1e29fSAlexander Hansen "Report with id {}", 29202c1e29fSAlexander Hansen reportId); 29302c1e29fSAlexander Hansen return; 29402c1e29fSAlexander Hansen } 29502c1e29fSAlexander Hansen 29602c1e29fSAlexander Hansen // Context is set by user during Event subscription and it must be 29702c1e29fSAlexander Hansen // set for MetricReport response. 29802c1e29fSAlexander Hansen if (!userSub->customText.empty()) 29902c1e29fSAlexander Hansen { 30002c1e29fSAlexander Hansen msg["Context"] = userSub->customText; 30102c1e29fSAlexander Hansen } 30202c1e29fSAlexander Hansen 30302c1e29fSAlexander Hansen std::string strMsg = 30402c1e29fSAlexander Hansen msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 305*4a19a7b5SEd Tanous sendEventToSubscriber(eventId, std::move(strMsg)); 30602c1e29fSAlexander Hansen } 30702c1e29fSAlexander Hansen 30802c1e29fSAlexander Hansen void Subscription::updateRetryConfig(uint32_t retryAttempts, 30902c1e29fSAlexander Hansen uint32_t retryTimeoutInterval) 31002c1e29fSAlexander Hansen { 31102c1e29fSAlexander Hansen if (policy == nullptr) 31202c1e29fSAlexander Hansen { 31302c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set"); 31402c1e29fSAlexander Hansen return; 31502c1e29fSAlexander Hansen } 31602c1e29fSAlexander Hansen policy->maxRetryAttempts = retryAttempts; 31702c1e29fSAlexander Hansen policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval); 31802c1e29fSAlexander Hansen } 31902c1e29fSAlexander Hansen 32002c1e29fSAlexander Hansen bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn) 32102c1e29fSAlexander Hansen { 32202c1e29fSAlexander Hansen return &thisConn == sseConn; 32302c1e29fSAlexander Hansen } 32402c1e29fSAlexander Hansen 32502c1e29fSAlexander Hansen // Check used to indicate what response codes are valid as part of our retry 32602c1e29fSAlexander Hansen // policy. 2XX is considered acceptable 32702c1e29fSAlexander Hansen boost::system::error_code Subscription::retryRespHandler(unsigned int respCode) 32802c1e29fSAlexander Hansen { 32902c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent"); 33002c1e29fSAlexander Hansen if ((respCode < 200) || (respCode >= 300)) 33102c1e29fSAlexander Hansen { 33202c1e29fSAlexander Hansen return boost::system::errc::make_error_code( 33302c1e29fSAlexander Hansen boost::system::errc::result_out_of_range); 33402c1e29fSAlexander Hansen } 33502c1e29fSAlexander Hansen 33602c1e29fSAlexander Hansen // Return 0 if the response code is valid 33702c1e29fSAlexander Hansen return boost::system::errc::make_error_code(boost::system::errc::success); 33802c1e29fSAlexander Hansen } 33902c1e29fSAlexander Hansen 34002c1e29fSAlexander Hansen } // namespace redfish 341