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 18*b80ba2e4SAlexander Hansen #include "event_log.hpp" 1902c1e29fSAlexander Hansen #include "event_logs_object_type.hpp" 2002c1e29fSAlexander Hansen #include "event_matches_filter.hpp" 2102c1e29fSAlexander Hansen #include "event_service_store.hpp" 2202c1e29fSAlexander Hansen #include "filter_expr_executor.hpp" 2302c1e29fSAlexander Hansen #include "generated/enums/log_entry.hpp" 2402c1e29fSAlexander Hansen #include "http_client.hpp" 2502c1e29fSAlexander Hansen #include "http_response.hpp" 2602c1e29fSAlexander Hansen #include "logging.hpp" 2702c1e29fSAlexander Hansen #include "metric_report.hpp" 2802c1e29fSAlexander Hansen #include "server_sent_event.hpp" 2902c1e29fSAlexander Hansen #include "ssl_key_handler.hpp" 3002c1e29fSAlexander Hansen #include "utils/time_utils.hpp" 3102c1e29fSAlexander Hansen 3202c1e29fSAlexander Hansen #include <boost/asio/io_context.hpp> 3302c1e29fSAlexander Hansen #include <boost/beast/http/verb.hpp> 3402c1e29fSAlexander Hansen #include <boost/system/errc.hpp> 3502c1e29fSAlexander Hansen #include <boost/url/format.hpp> 3602c1e29fSAlexander Hansen #include <boost/url/url_view_base.hpp> 3702c1e29fSAlexander Hansen #include <nlohmann/json.hpp> 3802c1e29fSAlexander Hansen 3902c1e29fSAlexander Hansen #include <algorithm> 4002c1e29fSAlexander Hansen #include <cstdint> 4102c1e29fSAlexander Hansen #include <cstdlib> 4202c1e29fSAlexander Hansen #include <ctime> 4302c1e29fSAlexander Hansen #include <format> 4402c1e29fSAlexander Hansen #include <functional> 4502c1e29fSAlexander Hansen #include <memory> 4602c1e29fSAlexander Hansen #include <span> 4702c1e29fSAlexander Hansen #include <string> 4802c1e29fSAlexander Hansen #include <string_view> 4902c1e29fSAlexander Hansen #include <utility> 5002c1e29fSAlexander Hansen #include <vector> 5102c1e29fSAlexander Hansen 5202c1e29fSAlexander Hansen namespace redfish 5302c1e29fSAlexander Hansen { 5402c1e29fSAlexander Hansen 5502c1e29fSAlexander Hansen Subscription::Subscription( 5602c1e29fSAlexander Hansen std::shared_ptr<persistent_data::UserSubscription> userSubIn, 5702c1e29fSAlexander Hansen const boost::urls::url_view_base& url, boost::asio::io_context& ioc) : 5802c1e29fSAlexander Hansen userSub{std::move(userSubIn)}, 5902c1e29fSAlexander Hansen policy(std::make_shared<crow::ConnectionPolicy>()) 6002c1e29fSAlexander Hansen { 6102c1e29fSAlexander Hansen userSub->destinationUrl = url; 6202c1e29fSAlexander Hansen client.emplace(ioc, policy); 6302c1e29fSAlexander Hansen // Subscription constructor 6402c1e29fSAlexander Hansen policy->invalidResp = retryRespHandler; 6502c1e29fSAlexander Hansen } 6602c1e29fSAlexander Hansen 6702c1e29fSAlexander Hansen Subscription::Subscription(crow::sse_socket::Connection& connIn) : 6802c1e29fSAlexander Hansen userSub{std::make_shared<persistent_data::UserSubscription>()}, 6902c1e29fSAlexander Hansen sseConn(&connIn) 7002c1e29fSAlexander Hansen {} 7102c1e29fSAlexander Hansen 7202c1e29fSAlexander Hansen // callback for subscription sendData 7302c1e29fSAlexander Hansen void Subscription::resHandler(const std::shared_ptr<Subscription>& /*unused*/, 7402c1e29fSAlexander Hansen const crow::Response& res) 7502c1e29fSAlexander Hansen { 7602c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt()); 7702c1e29fSAlexander Hansen 7802c1e29fSAlexander Hansen if (!client) 7902c1e29fSAlexander Hansen { 8002c1e29fSAlexander Hansen BMCWEB_LOG_ERROR( 8102c1e29fSAlexander Hansen "Http client wasn't filled but http client callback was called."); 8202c1e29fSAlexander Hansen return; 8302c1e29fSAlexander Hansen } 8402c1e29fSAlexander Hansen 8502c1e29fSAlexander Hansen if (userSub->retryPolicy != "TerminateAfterRetries") 8602c1e29fSAlexander Hansen { 8702c1e29fSAlexander Hansen return; 8802c1e29fSAlexander Hansen } 8902c1e29fSAlexander Hansen if (client->isTerminated()) 9002c1e29fSAlexander Hansen { 9102c1e29fSAlexander Hansen if (deleter) 9202c1e29fSAlexander Hansen { 9302c1e29fSAlexander Hansen BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts", 9402c1e29fSAlexander Hansen userSub->id); 9502c1e29fSAlexander Hansen deleter(); 9602c1e29fSAlexander Hansen } 9702c1e29fSAlexander Hansen } 9802c1e29fSAlexander Hansen } 9902c1e29fSAlexander Hansen 10002c1e29fSAlexander Hansen bool Subscription::sendEventToSubscriber(std::string&& msg) 10102c1e29fSAlexander Hansen { 10202c1e29fSAlexander Hansen persistent_data::EventServiceConfig eventServiceConfig = 10302c1e29fSAlexander Hansen persistent_data::EventServiceStore::getInstance() 10402c1e29fSAlexander Hansen .getEventServiceConfig(); 10502c1e29fSAlexander Hansen if (!eventServiceConfig.enabled) 10602c1e29fSAlexander Hansen { 10702c1e29fSAlexander Hansen return false; 10802c1e29fSAlexander Hansen } 10902c1e29fSAlexander Hansen 11002c1e29fSAlexander Hansen if (client) 11102c1e29fSAlexander Hansen { 11202c1e29fSAlexander Hansen client->sendDataWithCallback( 11302c1e29fSAlexander Hansen std::move(msg), userSub->destinationUrl, 11402c1e29fSAlexander Hansen static_cast<ensuressl::VerifyCertificate>( 11502c1e29fSAlexander Hansen userSub->verifyCertificate), 11602c1e29fSAlexander Hansen userSub->httpHeaders, boost::beast::http::verb::post, 11702c1e29fSAlexander Hansen std::bind_front(&Subscription::resHandler, this, 11802c1e29fSAlexander Hansen shared_from_this())); 11902c1e29fSAlexander Hansen return true; 12002c1e29fSAlexander Hansen } 12102c1e29fSAlexander Hansen 12202c1e29fSAlexander Hansen if (sseConn != nullptr) 12302c1e29fSAlexander Hansen { 12402c1e29fSAlexander Hansen eventSeqNum++; 12502c1e29fSAlexander Hansen sseConn->sendSseEvent(std::to_string(eventSeqNum), msg); 12602c1e29fSAlexander Hansen } 12702c1e29fSAlexander Hansen return true; 12802c1e29fSAlexander Hansen } 12902c1e29fSAlexander Hansen 13002c1e29fSAlexander Hansen bool Subscription::sendTestEventLog() 13102c1e29fSAlexander Hansen { 13202c1e29fSAlexander Hansen nlohmann::json::array_t logEntryArray; 13302c1e29fSAlexander Hansen nlohmann::json& logEntryJson = logEntryArray.emplace_back(); 13402c1e29fSAlexander Hansen 13502c1e29fSAlexander Hansen logEntryJson["EventId"] = "TestID"; 13602c1e29fSAlexander Hansen logEntryJson["Severity"] = log_entry::EventSeverity::OK; 13702c1e29fSAlexander Hansen logEntryJson["Message"] = "Generated test event"; 13802c1e29fSAlexander Hansen logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog"; 13902c1e29fSAlexander Hansen // MemberId is 0 : since we are sending one event record. 14002c1e29fSAlexander Hansen logEntryJson["MemberId"] = "0"; 14102c1e29fSAlexander Hansen logEntryJson["MessageArgs"] = nlohmann::json::array(); 14202c1e29fSAlexander Hansen logEntryJson["EventTimestamp"] = 14302c1e29fSAlexander Hansen redfish::time_utils::getDateTimeOffsetNow().first; 14402c1e29fSAlexander Hansen logEntryJson["Context"] = userSub->customText; 14502c1e29fSAlexander Hansen 14602c1e29fSAlexander Hansen nlohmann::json msg; 14702c1e29fSAlexander Hansen msg["@odata.type"] = "#Event.v1_4_0.Event"; 14802c1e29fSAlexander Hansen msg["Id"] = std::to_string(eventSeqNum); 14902c1e29fSAlexander Hansen msg["Name"] = "Event Log"; 15002c1e29fSAlexander Hansen msg["Events"] = logEntryArray; 15102c1e29fSAlexander Hansen 15202c1e29fSAlexander Hansen std::string strMsg = 15302c1e29fSAlexander Hansen msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 15402c1e29fSAlexander Hansen return sendEventToSubscriber(std::move(strMsg)); 15502c1e29fSAlexander Hansen } 15602c1e29fSAlexander Hansen 15702c1e29fSAlexander Hansen void Subscription::filterAndSendEventLogs( 15802c1e29fSAlexander Hansen const std::vector<EventLogObjectsType>& eventRecords) 15902c1e29fSAlexander Hansen { 16002c1e29fSAlexander Hansen nlohmann::json::array_t logEntryArray; 16102c1e29fSAlexander Hansen for (const EventLogObjectsType& logEntry : eventRecords) 16202c1e29fSAlexander Hansen { 16302c1e29fSAlexander Hansen std::vector<std::string_view> messageArgsView( 16402c1e29fSAlexander Hansen logEntry.messageArgs.begin(), logEntry.messageArgs.end()); 16502c1e29fSAlexander Hansen 16602c1e29fSAlexander Hansen nlohmann::json::object_t bmcLogEntry; 16702c1e29fSAlexander Hansen if (event_log::formatEventLogEntry( 16802c1e29fSAlexander Hansen logEntry.id, logEntry.messageId, messageArgsView, 16902c1e29fSAlexander Hansen logEntry.timestamp, userSub->customText, bmcLogEntry) != 0) 17002c1e29fSAlexander Hansen { 17102c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Read eventLog entry failed"); 17202c1e29fSAlexander Hansen continue; 17302c1e29fSAlexander Hansen } 17402c1e29fSAlexander Hansen 17502c1e29fSAlexander Hansen if (!eventMatchesFilter(*userSub, bmcLogEntry, "")) 17602c1e29fSAlexander Hansen { 17702c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Event {} did not match the filter", 17802c1e29fSAlexander Hansen nlohmann::json(bmcLogEntry).dump()); 17902c1e29fSAlexander Hansen continue; 18002c1e29fSAlexander Hansen } 18102c1e29fSAlexander Hansen 18202c1e29fSAlexander Hansen if (filter) 18302c1e29fSAlexander Hansen { 18402c1e29fSAlexander Hansen if (!memberMatches(bmcLogEntry, *filter)) 18502c1e29fSAlexander Hansen { 18602c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Filter didn't match"); 18702c1e29fSAlexander Hansen continue; 18802c1e29fSAlexander Hansen } 18902c1e29fSAlexander Hansen } 19002c1e29fSAlexander Hansen 19102c1e29fSAlexander Hansen logEntryArray.emplace_back(std::move(bmcLogEntry)); 19202c1e29fSAlexander Hansen } 19302c1e29fSAlexander Hansen 19402c1e29fSAlexander Hansen if (logEntryArray.empty()) 19502c1e29fSAlexander Hansen { 19602c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 19702c1e29fSAlexander Hansen return; 19802c1e29fSAlexander Hansen } 19902c1e29fSAlexander Hansen 20002c1e29fSAlexander Hansen nlohmann::json msg; 20102c1e29fSAlexander Hansen msg["@odata.type"] = "#Event.v1_4_0.Event"; 20202c1e29fSAlexander Hansen msg["Id"] = std::to_string(eventSeqNum); 20302c1e29fSAlexander Hansen msg["Name"] = "Event Log"; 20402c1e29fSAlexander Hansen msg["Events"] = std::move(logEntryArray); 20502c1e29fSAlexander Hansen std::string strMsg = 20602c1e29fSAlexander Hansen msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 20702c1e29fSAlexander Hansen sendEventToSubscriber(std::move(strMsg)); 20802c1e29fSAlexander Hansen eventSeqNum++; 20902c1e29fSAlexander Hansen } 21002c1e29fSAlexander Hansen 21102c1e29fSAlexander Hansen void Subscription::filterAndSendReports(const std::string& reportId, 21202c1e29fSAlexander Hansen const telemetry::TimestampReadings& var) 21302c1e29fSAlexander Hansen { 21402c1e29fSAlexander Hansen boost::urls::url mrdUri = boost::urls::format( 21502c1e29fSAlexander Hansen "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId); 21602c1e29fSAlexander Hansen 21702c1e29fSAlexander Hansen // Empty list means no filter. Send everything. 21802c1e29fSAlexander Hansen if (!userSub->metricReportDefinitions.empty()) 21902c1e29fSAlexander Hansen { 22002c1e29fSAlexander Hansen if (std::ranges::find(userSub->metricReportDefinitions, 22102c1e29fSAlexander Hansen mrdUri.buffer()) == 22202c1e29fSAlexander Hansen userSub->metricReportDefinitions.end()) 22302c1e29fSAlexander Hansen { 22402c1e29fSAlexander Hansen return; 22502c1e29fSAlexander Hansen } 22602c1e29fSAlexander Hansen } 22702c1e29fSAlexander Hansen 22802c1e29fSAlexander Hansen nlohmann::json msg; 22902c1e29fSAlexander Hansen if (!telemetry::fillReport(msg, reportId, var)) 23002c1e29fSAlexander Hansen { 23102c1e29fSAlexander Hansen BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus " 23202c1e29fSAlexander Hansen "Report with id {}", 23302c1e29fSAlexander Hansen reportId); 23402c1e29fSAlexander Hansen return; 23502c1e29fSAlexander Hansen } 23602c1e29fSAlexander Hansen 23702c1e29fSAlexander Hansen // Context is set by user during Event subscription and it must be 23802c1e29fSAlexander Hansen // set for MetricReport response. 23902c1e29fSAlexander Hansen if (!userSub->customText.empty()) 24002c1e29fSAlexander Hansen { 24102c1e29fSAlexander Hansen msg["Context"] = userSub->customText; 24202c1e29fSAlexander Hansen } 24302c1e29fSAlexander Hansen 24402c1e29fSAlexander Hansen std::string strMsg = 24502c1e29fSAlexander Hansen msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 24602c1e29fSAlexander Hansen sendEventToSubscriber(std::move(strMsg)); 24702c1e29fSAlexander Hansen } 24802c1e29fSAlexander Hansen 24902c1e29fSAlexander Hansen void Subscription::updateRetryConfig(uint32_t retryAttempts, 25002c1e29fSAlexander Hansen uint32_t retryTimeoutInterval) 25102c1e29fSAlexander Hansen { 25202c1e29fSAlexander Hansen if (policy == nullptr) 25302c1e29fSAlexander Hansen { 25402c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set"); 25502c1e29fSAlexander Hansen return; 25602c1e29fSAlexander Hansen } 25702c1e29fSAlexander Hansen policy->maxRetryAttempts = retryAttempts; 25802c1e29fSAlexander Hansen policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval); 25902c1e29fSAlexander Hansen } 26002c1e29fSAlexander Hansen 26102c1e29fSAlexander Hansen uint64_t Subscription::getEventSeqNum() const 26202c1e29fSAlexander Hansen { 26302c1e29fSAlexander Hansen return eventSeqNum; 26402c1e29fSAlexander Hansen } 26502c1e29fSAlexander Hansen 26602c1e29fSAlexander Hansen bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn) 26702c1e29fSAlexander Hansen { 26802c1e29fSAlexander Hansen return &thisConn == sseConn; 26902c1e29fSAlexander Hansen } 27002c1e29fSAlexander Hansen 27102c1e29fSAlexander Hansen // Check used to indicate what response codes are valid as part of our retry 27202c1e29fSAlexander Hansen // policy. 2XX is considered acceptable 27302c1e29fSAlexander Hansen boost::system::error_code Subscription::retryRespHandler(unsigned int respCode) 27402c1e29fSAlexander Hansen { 27502c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent"); 27602c1e29fSAlexander Hansen if ((respCode < 200) || (respCode >= 300)) 27702c1e29fSAlexander Hansen { 27802c1e29fSAlexander Hansen return boost::system::errc::make_error_code( 27902c1e29fSAlexander Hansen boost::system::errc::result_out_of_range); 28002c1e29fSAlexander Hansen } 28102c1e29fSAlexander Hansen 28202c1e29fSAlexander Hansen // Return 0 if the response code is valid 28302c1e29fSAlexander Hansen return boost::system::errc::make_error_code(boost::system::errc::success); 28402c1e29fSAlexander Hansen } 28502c1e29fSAlexander Hansen 28602c1e29fSAlexander Hansen } // namespace redfish 287