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
Subscription(std::shared_ptr<persistent_data::UserSubscription> userSubIn,const boost::urls::url_view_base & url,boost::asio::io_context & ioc)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
Subscription(crow::sse_socket::Connection & connIn)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
resHandler(const std::shared_ptr<Subscription> &,const crow::Response & res)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
sendEventToSubscriber(std::string && msg)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
sendTestEventLog()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
filterAndSendEventLogs(const std::vector<EventLogObjectsType> & eventRecords)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
filterAndSendReports(const std::string & reportId,const telemetry::TimestampReadings & var)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
updateRetryConfig(uint32_t retryAttempts,uint32_t retryTimeoutInterval)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
getEventSeqNum() const261*02c1e29fSAlexander Hansen uint64_t Subscription::getEventSeqNum() const
262*02c1e29fSAlexander Hansen {
263*02c1e29fSAlexander Hansen return eventSeqNum;
264*02c1e29fSAlexander Hansen }
265*02c1e29fSAlexander Hansen
matchSseId(const crow::sse_socket::Connection & thisConn)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
retryRespHandler(unsigned int respCode)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