xref: /openbmc/bmcweb/features/redfish/src/subscription.cpp (revision b80ba2e4b0aad093f27d05fabc1cee1fac650d57)
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