blob: 821077acb0dd8b74eb14fb82ef06bfaccb6d0e55 [file] [log] [blame]
/*
Copyright (c) 2020 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "subscription.hpp"
#include "dbus_singleton.hpp"
#include "event_log.hpp"
#include "event_logs_object_type.hpp"
#include "event_matches_filter.hpp"
#include "event_service_store.hpp"
#include "filter_expr_executor.hpp"
#include "generated/enums/log_entry.hpp"
#include "heartbeat_messages.hpp"
#include "http_client.hpp"
#include "http_response.hpp"
#include "logging.hpp"
#include "metric_report.hpp"
#include "server_sent_event.hpp"
#include "ssl_key_handler.hpp"
#include "utils/time_utils.hpp"
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/beast/http/verb.hpp>
#include <boost/system/errc.hpp>
#include <boost/url/format.hpp>
#include <boost/url/url_view_base.hpp>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <format>
#include <functional>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace redfish
{
Subscription::Subscription(
std::shared_ptr<persistent_data::UserSubscription> userSubIn,
const boost::urls::url_view_base& url, boost::asio::io_context& ioc) :
userSub{std::move(userSubIn)},
policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
{
userSub->destinationUrl = url;
client.emplace(ioc, policy);
// Subscription constructor
policy->invalidResp = retryRespHandler;
}
Subscription::Subscription(crow::sse_socket::Connection& connIn) :
userSub{std::make_shared<persistent_data::UserSubscription>()},
sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
{}
// callback for subscription sendData
void Subscription::resHandler(const std::shared_ptr<Subscription>& /*unused*/,
const crow::Response& res)
{
BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
if (!client)
{
BMCWEB_LOG_ERROR(
"Http client wasn't filled but http client callback was called.");
return;
}
if (userSub->retryPolicy != "TerminateAfterRetries")
{
return;
}
if (client->isTerminated())
{
hbTimer.cancel();
if (deleter)
{
BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
userSub->id);
deleter();
}
}
}
void Subscription::sendHeartbeatEvent()
{
// send the heartbeat message
nlohmann::json eventMessage = messages::redfishServiceFunctional();
std::string heartEventId = std::to_string(eventSeqNum);
eventMessage["EventId"] = heartEventId;
eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
eventMessage["OriginOfCondition"] =
std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
eventMessage["MemberId"] = "0";
nlohmann::json::array_t eventRecord;
eventRecord.emplace_back(std::move(eventMessage));
nlohmann::json msgJson;
msgJson["@odata.type"] = "#Event.v1_4_0.Event";
msgJson["Name"] = "Heartbeat";
msgJson["Id"] = heartEventId;
msgJson["Events"] = std::move(eventRecord);
std::string strMsg =
msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
sendEventToSubscriber(std::move(strMsg));
eventSeqNum++;
}
void Subscription::scheduleNextHeartbeatEvent()
{
hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
hbTimer.async_wait(
std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
}
void Subscription::heartbeatParametersChanged()
{
hbTimer.cancel();
if (userSub->sendHeartbeat)
{
scheduleNextHeartbeatEvent();
}
}
void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
const boost::system::error_code& ec)
{
if (ec == boost::asio::error::operation_aborted)
{
BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
return;
}
if (ec == boost::system::errc::operation_canceled)
{
BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
return;
}
if (ec)
{
BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
return;
}
std::shared_ptr<Subscription> self = weakSelf.lock();
if (!self)
{
BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
return;
}
// Timer expired.
sendHeartbeatEvent();
// reschedule heartbeat timer
scheduleNextHeartbeatEvent();
}
bool Subscription::sendEventToSubscriber(std::string&& msg)
{
persistent_data::EventServiceConfig eventServiceConfig =
persistent_data::EventServiceStore::getInstance()
.getEventServiceConfig();
if (!eventServiceConfig.enabled)
{
return false;
}
if (client)
{
client->sendDataWithCallback(
std::move(msg), userSub->destinationUrl,
static_cast<ensuressl::VerifyCertificate>(
userSub->verifyCertificate),
userSub->httpHeaders, boost::beast::http::verb::post,
std::bind_front(&Subscription::resHandler, this,
shared_from_this()));
return true;
}
if (sseConn != nullptr)
{
eventSeqNum++;
sseConn->sendSseEvent(std::to_string(eventSeqNum), msg);
}
return true;
}
bool Subscription::sendTestEventLog()
{
nlohmann::json::array_t logEntryArray;
nlohmann::json& logEntryJson = logEntryArray.emplace_back();
logEntryJson["EventId"] = "TestID";
logEntryJson["Severity"] = log_entry::EventSeverity::OK;
logEntryJson["Message"] = "Generated test event";
logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog";
// MemberId is 0 : since we are sending one event record.
logEntryJson["MemberId"] = "0";
logEntryJson["MessageArgs"] = nlohmann::json::array();
logEntryJson["EventTimestamp"] =
redfish::time_utils::getDateTimeOffsetNow().first;
logEntryJson["Context"] = userSub->customText;
nlohmann::json msg;
msg["@odata.type"] = "#Event.v1_4_0.Event";
msg["Id"] = std::to_string(eventSeqNum);
msg["Name"] = "Event Log";
msg["Events"] = logEntryArray;
std::string strMsg =
msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
return sendEventToSubscriber(std::move(strMsg));
}
void Subscription::filterAndSendEventLogs(
const std::vector<EventLogObjectsType>& eventRecords)
{
nlohmann::json::array_t logEntryArray;
for (const EventLogObjectsType& logEntry : eventRecords)
{
std::vector<std::string_view> messageArgsView(
logEntry.messageArgs.begin(), logEntry.messageArgs.end());
nlohmann::json::object_t bmcLogEntry;
if (event_log::formatEventLogEntry(
logEntry.id, logEntry.messageId, messageArgsView,
logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
{
BMCWEB_LOG_DEBUG("Read eventLog entry failed");
continue;
}
if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
{
BMCWEB_LOG_DEBUG("Event {} did not match the filter",
nlohmann::json(bmcLogEntry).dump());
continue;
}
if (filter)
{
if (!memberMatches(bmcLogEntry, *filter))
{
BMCWEB_LOG_DEBUG("Filter didn't match");
continue;
}
}
logEntryArray.emplace_back(std::move(bmcLogEntry));
}
if (logEntryArray.empty())
{
BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
return;
}
nlohmann::json msg;
msg["@odata.type"] = "#Event.v1_4_0.Event";
msg["Id"] = std::to_string(eventSeqNum);
msg["Name"] = "Event Log";
msg["Events"] = std::move(logEntryArray);
std::string strMsg =
msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
sendEventToSubscriber(std::move(strMsg));
eventSeqNum++;
}
void Subscription::filterAndSendReports(const std::string& reportId,
const telemetry::TimestampReadings& var)
{
boost::urls::url mrdUri = boost::urls::format(
"/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
// Empty list means no filter. Send everything.
if (!userSub->metricReportDefinitions.empty())
{
if (std::ranges::find(userSub->metricReportDefinitions,
mrdUri.buffer()) ==
userSub->metricReportDefinitions.end())
{
return;
}
}
nlohmann::json msg;
if (!telemetry::fillReport(msg, reportId, var))
{
BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
"Report with id {}",
reportId);
return;
}
// Context is set by user during Event subscription and it must be
// set for MetricReport response.
if (!userSub->customText.empty())
{
msg["Context"] = userSub->customText;
}
std::string strMsg =
msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
sendEventToSubscriber(std::move(strMsg));
}
void Subscription::updateRetryConfig(uint32_t retryAttempts,
uint32_t retryTimeoutInterval)
{
if (policy == nullptr)
{
BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
return;
}
policy->maxRetryAttempts = retryAttempts;
policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
}
uint64_t Subscription::getEventSeqNum() const
{
return eventSeqNum;
}
bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
{
return &thisConn == sseConn;
}
// Check used to indicate what response codes are valid as part of our retry
// policy. 2XX is considered acceptable
boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
{
BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
if ((respCode < 200) || (respCode >= 300))
{
return boost::system::errc::make_error_code(
boost::system::errc::result_out_of_range);
}
// Return 0 if the response code is valid
return boost::system::errc::make_error_code(boost::system::errc::success);
}
} // namespace redfish