1*02c1e29fSAlexander Hansen /* 2*02c1e29fSAlexander Hansen Copyright (c) 2020 Intel Corporation 3*02c1e29fSAlexander Hansen 4*02c1e29fSAlexander Hansen Licensed under the Apache License, Version 2.0 (the "License"); 5*02c1e29fSAlexander Hansen you may not use this file except in compliance with the License. 6*02c1e29fSAlexander Hansen You may obtain a copy of the License at 7*02c1e29fSAlexander Hansen 8*02c1e29fSAlexander Hansen http://www.apache.org/licenses/LICENSE-2.0 9*02c1e29fSAlexander Hansen 10*02c1e29fSAlexander Hansen Unless required by applicable law or agreed to in writing, software 11*02c1e29fSAlexander Hansen distributed under the License is distributed on an "AS IS" BASIS, 12*02c1e29fSAlexander Hansen WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*02c1e29fSAlexander Hansen See the License for the specific language governing permissions and 14*02c1e29fSAlexander Hansen limitations under the License. 15*02c1e29fSAlexander Hansen */ 16*02c1e29fSAlexander Hansen #include "subscription.hpp" 17*02c1e29fSAlexander Hansen 18*02c1e29fSAlexander Hansen #include "event_logs_object_type.hpp" 19*02c1e29fSAlexander Hansen #include "event_matches_filter.hpp" 20*02c1e29fSAlexander Hansen #include "event_service_manager.hpp" 21*02c1e29fSAlexander Hansen #include "event_service_store.hpp" 22*02c1e29fSAlexander Hansen #include "filter_expr_executor.hpp" 23*02c1e29fSAlexander Hansen #include "generated/enums/log_entry.hpp" 24*02c1e29fSAlexander Hansen #include "http_client.hpp" 25*02c1e29fSAlexander Hansen #include "http_response.hpp" 26*02c1e29fSAlexander Hansen #include "logging.hpp" 27*02c1e29fSAlexander Hansen #include "metric_report.hpp" 28*02c1e29fSAlexander Hansen #include "server_sent_event.hpp" 29*02c1e29fSAlexander Hansen #include "ssl_key_handler.hpp" 30*02c1e29fSAlexander Hansen #include "utils/time_utils.hpp" 31*02c1e29fSAlexander Hansen 32*02c1e29fSAlexander Hansen #include <boost/asio/io_context.hpp> 33*02c1e29fSAlexander Hansen #include <boost/beast/http/verb.hpp> 34*02c1e29fSAlexander Hansen #include <boost/system/errc.hpp> 35*02c1e29fSAlexander Hansen #include <boost/url/format.hpp> 36*02c1e29fSAlexander Hansen #include <boost/url/url_view_base.hpp> 37*02c1e29fSAlexander Hansen #include <nlohmann/json.hpp> 38*02c1e29fSAlexander Hansen 39*02c1e29fSAlexander Hansen #include <algorithm> 40*02c1e29fSAlexander Hansen #include <cstdint> 41*02c1e29fSAlexander Hansen #include <cstdlib> 42*02c1e29fSAlexander Hansen #include <ctime> 43*02c1e29fSAlexander Hansen #include <format> 44*02c1e29fSAlexander Hansen #include <functional> 45*02c1e29fSAlexander Hansen #include <memory> 46*02c1e29fSAlexander Hansen #include <span> 47*02c1e29fSAlexander Hansen #include <string> 48*02c1e29fSAlexander Hansen #include <string_view> 49*02c1e29fSAlexander Hansen #include <utility> 50*02c1e29fSAlexander Hansen #include <vector> 51*02c1e29fSAlexander Hansen 52*02c1e29fSAlexander Hansen namespace redfish 53*02c1e29fSAlexander Hansen { 54*02c1e29fSAlexander Hansen 55*02c1e29fSAlexander Hansen Subscription::Subscription( 56*02c1e29fSAlexander Hansen std::shared_ptr<persistent_data::UserSubscription> userSubIn, 57*02c1e29fSAlexander Hansen const boost::urls::url_view_base& url, boost::asio::io_context& ioc) : 58*02c1e29fSAlexander Hansen userSub{std::move(userSubIn)}, 59*02c1e29fSAlexander Hansen policy(std::make_shared<crow::ConnectionPolicy>()) 60*02c1e29fSAlexander Hansen { 61*02c1e29fSAlexander Hansen userSub->destinationUrl = url; 62*02c1e29fSAlexander Hansen client.emplace(ioc, policy); 63*02c1e29fSAlexander Hansen // Subscription constructor 64*02c1e29fSAlexander Hansen policy->invalidResp = retryRespHandler; 65*02c1e29fSAlexander Hansen } 66*02c1e29fSAlexander Hansen 67*02c1e29fSAlexander Hansen Subscription::Subscription(crow::sse_socket::Connection& connIn) : 68*02c1e29fSAlexander Hansen userSub{std::make_shared<persistent_data::UserSubscription>()}, 69*02c1e29fSAlexander Hansen sseConn(&connIn) 70*02c1e29fSAlexander Hansen {} 71*02c1e29fSAlexander Hansen 72*02c1e29fSAlexander Hansen // callback for subscription sendData 73*02c1e29fSAlexander Hansen void Subscription::resHandler(const std::shared_ptr<Subscription>& /*unused*/, 74*02c1e29fSAlexander Hansen const crow::Response& res) 75*02c1e29fSAlexander Hansen { 76*02c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt()); 77*02c1e29fSAlexander Hansen 78*02c1e29fSAlexander Hansen if (!client) 79*02c1e29fSAlexander Hansen { 80*02c1e29fSAlexander Hansen BMCWEB_LOG_ERROR( 81*02c1e29fSAlexander Hansen "Http client wasn't filled but http client callback was called."); 82*02c1e29fSAlexander Hansen return; 83*02c1e29fSAlexander Hansen } 84*02c1e29fSAlexander Hansen 85*02c1e29fSAlexander Hansen if (userSub->retryPolicy != "TerminateAfterRetries") 86*02c1e29fSAlexander Hansen { 87*02c1e29fSAlexander Hansen return; 88*02c1e29fSAlexander Hansen } 89*02c1e29fSAlexander Hansen if (client->isTerminated()) 90*02c1e29fSAlexander Hansen { 91*02c1e29fSAlexander Hansen if (deleter) 92*02c1e29fSAlexander Hansen { 93*02c1e29fSAlexander Hansen BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts", 94*02c1e29fSAlexander Hansen userSub->id); 95*02c1e29fSAlexander Hansen deleter(); 96*02c1e29fSAlexander Hansen } 97*02c1e29fSAlexander Hansen } 98*02c1e29fSAlexander Hansen } 99*02c1e29fSAlexander Hansen 100*02c1e29fSAlexander Hansen bool Subscription::sendEventToSubscriber(std::string&& msg) 101*02c1e29fSAlexander Hansen { 102*02c1e29fSAlexander Hansen persistent_data::EventServiceConfig eventServiceConfig = 103*02c1e29fSAlexander Hansen persistent_data::EventServiceStore::getInstance() 104*02c1e29fSAlexander Hansen .getEventServiceConfig(); 105*02c1e29fSAlexander Hansen if (!eventServiceConfig.enabled) 106*02c1e29fSAlexander Hansen { 107*02c1e29fSAlexander Hansen return false; 108*02c1e29fSAlexander Hansen } 109*02c1e29fSAlexander Hansen 110*02c1e29fSAlexander Hansen if (client) 111*02c1e29fSAlexander Hansen { 112*02c1e29fSAlexander Hansen client->sendDataWithCallback( 113*02c1e29fSAlexander Hansen std::move(msg), userSub->destinationUrl, 114*02c1e29fSAlexander Hansen static_cast<ensuressl::VerifyCertificate>( 115*02c1e29fSAlexander Hansen userSub->verifyCertificate), 116*02c1e29fSAlexander Hansen userSub->httpHeaders, boost::beast::http::verb::post, 117*02c1e29fSAlexander Hansen std::bind_front(&Subscription::resHandler, this, 118*02c1e29fSAlexander Hansen shared_from_this())); 119*02c1e29fSAlexander Hansen return true; 120*02c1e29fSAlexander Hansen } 121*02c1e29fSAlexander Hansen 122*02c1e29fSAlexander Hansen if (sseConn != nullptr) 123*02c1e29fSAlexander Hansen { 124*02c1e29fSAlexander Hansen eventSeqNum++; 125*02c1e29fSAlexander Hansen sseConn->sendSseEvent(std::to_string(eventSeqNum), msg); 126*02c1e29fSAlexander Hansen } 127*02c1e29fSAlexander Hansen return true; 128*02c1e29fSAlexander Hansen } 129*02c1e29fSAlexander Hansen 130*02c1e29fSAlexander Hansen bool Subscription::sendTestEventLog() 131*02c1e29fSAlexander Hansen { 132*02c1e29fSAlexander Hansen nlohmann::json::array_t logEntryArray; 133*02c1e29fSAlexander Hansen nlohmann::json& logEntryJson = logEntryArray.emplace_back(); 134*02c1e29fSAlexander Hansen 135*02c1e29fSAlexander Hansen logEntryJson["EventId"] = "TestID"; 136*02c1e29fSAlexander Hansen logEntryJson["Severity"] = log_entry::EventSeverity::OK; 137*02c1e29fSAlexander Hansen logEntryJson["Message"] = "Generated test event"; 138*02c1e29fSAlexander Hansen logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog"; 139*02c1e29fSAlexander Hansen // MemberId is 0 : since we are sending one event record. 140*02c1e29fSAlexander Hansen logEntryJson["MemberId"] = "0"; 141*02c1e29fSAlexander Hansen logEntryJson["MessageArgs"] = nlohmann::json::array(); 142*02c1e29fSAlexander Hansen logEntryJson["EventTimestamp"] = 143*02c1e29fSAlexander Hansen redfish::time_utils::getDateTimeOffsetNow().first; 144*02c1e29fSAlexander Hansen logEntryJson["Context"] = userSub->customText; 145*02c1e29fSAlexander Hansen 146*02c1e29fSAlexander Hansen nlohmann::json msg; 147*02c1e29fSAlexander Hansen msg["@odata.type"] = "#Event.v1_4_0.Event"; 148*02c1e29fSAlexander Hansen msg["Id"] = std::to_string(eventSeqNum); 149*02c1e29fSAlexander Hansen msg["Name"] = "Event Log"; 150*02c1e29fSAlexander Hansen msg["Events"] = logEntryArray; 151*02c1e29fSAlexander Hansen 152*02c1e29fSAlexander Hansen std::string strMsg = 153*02c1e29fSAlexander Hansen msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 154*02c1e29fSAlexander Hansen return sendEventToSubscriber(std::move(strMsg)); 155*02c1e29fSAlexander Hansen } 156*02c1e29fSAlexander Hansen 157*02c1e29fSAlexander Hansen void Subscription::filterAndSendEventLogs( 158*02c1e29fSAlexander Hansen const std::vector<EventLogObjectsType>& eventRecords) 159*02c1e29fSAlexander Hansen { 160*02c1e29fSAlexander Hansen nlohmann::json::array_t logEntryArray; 161*02c1e29fSAlexander Hansen for (const EventLogObjectsType& logEntry : eventRecords) 162*02c1e29fSAlexander Hansen { 163*02c1e29fSAlexander Hansen std::vector<std::string_view> messageArgsView( 164*02c1e29fSAlexander Hansen logEntry.messageArgs.begin(), logEntry.messageArgs.end()); 165*02c1e29fSAlexander Hansen 166*02c1e29fSAlexander Hansen nlohmann::json::object_t bmcLogEntry; 167*02c1e29fSAlexander Hansen if (event_log::formatEventLogEntry( 168*02c1e29fSAlexander Hansen logEntry.id, logEntry.messageId, messageArgsView, 169*02c1e29fSAlexander Hansen logEntry.timestamp, userSub->customText, bmcLogEntry) != 0) 170*02c1e29fSAlexander Hansen { 171*02c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Read eventLog entry failed"); 172*02c1e29fSAlexander Hansen continue; 173*02c1e29fSAlexander Hansen } 174*02c1e29fSAlexander Hansen 175*02c1e29fSAlexander Hansen if (!eventMatchesFilter(*userSub, bmcLogEntry, "")) 176*02c1e29fSAlexander Hansen { 177*02c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Event {} did not match the filter", 178*02c1e29fSAlexander Hansen nlohmann::json(bmcLogEntry).dump()); 179*02c1e29fSAlexander Hansen continue; 180*02c1e29fSAlexander Hansen } 181*02c1e29fSAlexander Hansen 182*02c1e29fSAlexander Hansen if (filter) 183*02c1e29fSAlexander Hansen { 184*02c1e29fSAlexander Hansen if (!memberMatches(bmcLogEntry, *filter)) 185*02c1e29fSAlexander Hansen { 186*02c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Filter didn't match"); 187*02c1e29fSAlexander Hansen continue; 188*02c1e29fSAlexander Hansen } 189*02c1e29fSAlexander Hansen } 190*02c1e29fSAlexander Hansen 191*02c1e29fSAlexander Hansen logEntryArray.emplace_back(std::move(bmcLogEntry)); 192*02c1e29fSAlexander Hansen } 193*02c1e29fSAlexander Hansen 194*02c1e29fSAlexander Hansen if (logEntryArray.empty()) 195*02c1e29fSAlexander Hansen { 196*02c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 197*02c1e29fSAlexander Hansen return; 198*02c1e29fSAlexander Hansen } 199*02c1e29fSAlexander Hansen 200*02c1e29fSAlexander Hansen nlohmann::json msg; 201*02c1e29fSAlexander Hansen msg["@odata.type"] = "#Event.v1_4_0.Event"; 202*02c1e29fSAlexander Hansen msg["Id"] = std::to_string(eventSeqNum); 203*02c1e29fSAlexander Hansen msg["Name"] = "Event Log"; 204*02c1e29fSAlexander Hansen msg["Events"] = std::move(logEntryArray); 205*02c1e29fSAlexander Hansen std::string strMsg = 206*02c1e29fSAlexander Hansen msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 207*02c1e29fSAlexander Hansen sendEventToSubscriber(std::move(strMsg)); 208*02c1e29fSAlexander Hansen eventSeqNum++; 209*02c1e29fSAlexander Hansen } 210*02c1e29fSAlexander Hansen 211*02c1e29fSAlexander Hansen void Subscription::filterAndSendReports(const std::string& reportId, 212*02c1e29fSAlexander Hansen const telemetry::TimestampReadings& var) 213*02c1e29fSAlexander Hansen { 214*02c1e29fSAlexander Hansen boost::urls::url mrdUri = boost::urls::format( 215*02c1e29fSAlexander Hansen "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId); 216*02c1e29fSAlexander Hansen 217*02c1e29fSAlexander Hansen // Empty list means no filter. Send everything. 218*02c1e29fSAlexander Hansen if (!userSub->metricReportDefinitions.empty()) 219*02c1e29fSAlexander Hansen { 220*02c1e29fSAlexander Hansen if (std::ranges::find(userSub->metricReportDefinitions, 221*02c1e29fSAlexander Hansen mrdUri.buffer()) == 222*02c1e29fSAlexander Hansen userSub->metricReportDefinitions.end()) 223*02c1e29fSAlexander Hansen { 224*02c1e29fSAlexander Hansen return; 225*02c1e29fSAlexander Hansen } 226*02c1e29fSAlexander Hansen } 227*02c1e29fSAlexander Hansen 228*02c1e29fSAlexander Hansen nlohmann::json msg; 229*02c1e29fSAlexander Hansen if (!telemetry::fillReport(msg, reportId, var)) 230*02c1e29fSAlexander Hansen { 231*02c1e29fSAlexander Hansen BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus " 232*02c1e29fSAlexander Hansen "Report with id {}", 233*02c1e29fSAlexander Hansen reportId); 234*02c1e29fSAlexander Hansen return; 235*02c1e29fSAlexander Hansen } 236*02c1e29fSAlexander Hansen 237*02c1e29fSAlexander Hansen // Context is set by user during Event subscription and it must be 238*02c1e29fSAlexander Hansen // set for MetricReport response. 239*02c1e29fSAlexander Hansen if (!userSub->customText.empty()) 240*02c1e29fSAlexander Hansen { 241*02c1e29fSAlexander Hansen msg["Context"] = userSub->customText; 242*02c1e29fSAlexander Hansen } 243*02c1e29fSAlexander Hansen 244*02c1e29fSAlexander Hansen std::string strMsg = 245*02c1e29fSAlexander Hansen msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 246*02c1e29fSAlexander Hansen sendEventToSubscriber(std::move(strMsg)); 247*02c1e29fSAlexander Hansen } 248*02c1e29fSAlexander Hansen 249*02c1e29fSAlexander Hansen void Subscription::updateRetryConfig(uint32_t retryAttempts, 250*02c1e29fSAlexander Hansen uint32_t retryTimeoutInterval) 251*02c1e29fSAlexander Hansen { 252*02c1e29fSAlexander Hansen if (policy == nullptr) 253*02c1e29fSAlexander Hansen { 254*02c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set"); 255*02c1e29fSAlexander Hansen return; 256*02c1e29fSAlexander Hansen } 257*02c1e29fSAlexander Hansen policy->maxRetryAttempts = retryAttempts; 258*02c1e29fSAlexander Hansen policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval); 259*02c1e29fSAlexander Hansen } 260*02c1e29fSAlexander Hansen 261*02c1e29fSAlexander Hansen uint64_t Subscription::getEventSeqNum() const 262*02c1e29fSAlexander Hansen { 263*02c1e29fSAlexander Hansen return eventSeqNum; 264*02c1e29fSAlexander Hansen } 265*02c1e29fSAlexander Hansen 266*02c1e29fSAlexander Hansen bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn) 267*02c1e29fSAlexander Hansen { 268*02c1e29fSAlexander Hansen return &thisConn == sseConn; 269*02c1e29fSAlexander Hansen } 270*02c1e29fSAlexander Hansen 271*02c1e29fSAlexander Hansen // Check used to indicate what response codes are valid as part of our retry 272*02c1e29fSAlexander Hansen // policy. 2XX is considered acceptable 273*02c1e29fSAlexander Hansen boost::system::error_code Subscription::retryRespHandler(unsigned int respCode) 274*02c1e29fSAlexander Hansen { 275*02c1e29fSAlexander Hansen BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent"); 276*02c1e29fSAlexander Hansen if ((respCode < 200) || (respCode >= 300)) 277*02c1e29fSAlexander Hansen { 278*02c1e29fSAlexander Hansen return boost::system::errc::make_error_code( 279*02c1e29fSAlexander Hansen boost::system::errc::result_out_of_range); 280*02c1e29fSAlexander Hansen } 281*02c1e29fSAlexander Hansen 282*02c1e29fSAlexander Hansen // Return 0 if the response code is valid 283*02c1e29fSAlexander Hansen return boost::system::errc::make_error_code(boost::system::errc::success); 284*02c1e29fSAlexander Hansen } 285*02c1e29fSAlexander Hansen 286*02c1e29fSAlexander Hansen } // namespace redfish 287