diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice')
11 files changed, 2892 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch new file mode 100644 index 000000000..52135e255 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch @@ -0,0 +1,121 @@ +From 6ff897d2b5513f15445f18aae16d8439ed94f377 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +Date: Mon, 11 Oct 2021 18:41:27 +0530 +Subject: [PATCH] Add unmerged changes for http retry support + +The http retry support added upstream as a single patch was slpit into +3 patches, but only 2 of them was merged. +This commit pulls in the differentail changes required to complete the +entire http retry support. and also allow for other subsequent patches +to be appplied easily. + +Change-Id: Id8ccd991b7ffc505196b1a92b23e1cd51e00bc89 +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + http/http_client.hpp | 44 +++++++++++-------- + .../include/event_service_manager.hpp | 2 +- + 2 files changed, 27 insertions(+), 19 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index ab20eb0..aad1cce 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -68,7 +68,6 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + std::optional< + boost::beast::http::response_parser<boost::beast::http::string_body>> + parser; +- std::vector<std::pair<std::string, std::string>> headers; + boost::circular_buffer_space_optimized<std::string> requestDataQueue{}; + + ConnState state; +@@ -137,18 +136,6 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + + BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; + +- req.version(static_cast<int>(11)); // HTTP 1.1 +- req.target(uri); +- req.method(boost::beast::http::verb::post); +- +- // Set headers +- for (const auto& [key, value] : headers) +- { +- req.set(key, value); +- } +- req.set(boost::beast::http::field::host, host); +- req.keep_alive(true); +- + req.body() = data; + req.prepare_payload(); + +@@ -204,6 +191,17 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + BMCWEB_LOG_DEBUG << "recvMessage() data: " + << self->parser->get(); + ++ // Check if the response and header are received ++ if (!self->parser->is_done()) ++ { ++ // The parser failed to receive the response ++ BMCWEB_LOG_ERROR ++ << "recvMessage() parser failed to receive response"; ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } ++ + unsigned int respCode = self->parser->get().result_int(); + BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " + << respCode; +@@ -398,11 +396,17 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + const std::string& destIP, const std::string& destPort, + const std::string& destUri) : + conn(ioc), +- timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri), +- retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0), ++ timer(ioc), req(boost::beast::http::verb::post, destUri, 11), ++ state(ConnState::initialized), subId(id), host(destIP), port(destPort), ++ uri(destUri), retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0), + retryPolicyAction("TerminateAfterRetries"), runningTimer(false) + { +- state = ConnState::initialized; ++ // Set the request header ++ req.set(boost::beast::http::field::host, host); ++ req.set(boost::beast::http::field::content_type, "application/json"); ++ req.keep_alive(true); ++ ++ requestDataQueue.set_capacity(maxRequestQueueSize); + } + + void sendData(const std::string& data) +@@ -425,10 +429,14 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + return; + } + +- void setHeaders( ++ void addHeaders( + const std::vector<std::pair<std::string, std::string>>& httpHeaders) + { +- headers = httpHeaders; ++ // Set custom headers ++ for (const auto& [key, value] : httpHeaders) ++ { ++ req.set(key, value); ++ } + } + + void setRetryConfig(const uint32_t retryAttempts, +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 8042803..0a63b8c 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -412,7 +412,7 @@ class Subscription : public persistent_data::UserSubscription + reqHeaders.emplace_back(std::pair(key, val)); + } + } +- conn->setHeaders(reqHeaders); ++ conn->addHeaders(reqHeaders); + conn->sendData(msg); + this->eventSeqNum++; + } +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch new file mode 100644 index 000000000..aeeafc421 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch @@ -0,0 +1,453 @@ +From 3f2ad28e6e124249cde3df50c9e18c283fbcbf3e Mon Sep 17 00:00:00 2001 +From: AppaRao Puli <apparao.puli@linux.intel.com> +Date: Mon, 22 Feb 2021 17:07:47 +0000 +Subject: [PATCH] EventService: https client support + +Add https client support for push style eventing. Using this BMC can +push the event logs/telemetry data to event listener over secure http +channel. + +Tested: + - Created subscription with https destination url. Using + SubmitTestEvent action set the event and can see event on event + listener. + - Validator passed. + +Change-Id: I44c3918b39baa2eb5fddda9d635f99aa280a422a +Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com> +--- + http/http_client.hpp | 307 ++++++++++++------ + .../include/event_service_manager.hpp | 2 +- + 2 files changed, 202 insertions(+), 107 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index aad1cce..5e7ff47 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -20,6 +20,7 @@ + #include <boost/beast/core/flat_buffer.hpp> + #include <boost/beast/core/tcp_stream.hpp> + #include <boost/beast/http/message.hpp> ++#include <boost/beast/ssl/ssl_stream.hpp> + #include <boost/beast/version.hpp> + #include <include/async_resolve.hpp> + +@@ -43,6 +44,8 @@ enum class ConnState + resolveFailed, + connectInProgress, + connectFailed, ++ handshakeInProgress, ++ handshakeFailed, + connected, + sendInProgress, + sendFailed, +@@ -61,7 +64,9 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + { + private: + crow::async_resolve::Resolver resolver; ++ boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client}; + boost::beast::tcp_stream conn; ++ std::optional<boost::beast::ssl_stream<boost::beast::tcp_stream&>> sslConn; + boost::asio::steady_timer timer; + boost::beast::flat_static_buffer<httpReadBodyLimit> buffer; + boost::beast::http::request<boost::beast::http::string_body> req; +@@ -108,23 +113,52 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + const std::vector<boost::asio::ip::tcp::endpoint>& endpointList) + { + state = ConnState::connectInProgress; ++ sslConn.emplace(conn, ctx); + + BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port; ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code ec, ++ const boost::asio::ip::tcp::endpoint& endpoint) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Connect " << endpoint ++ << " failed: " << ec.message(); ++ self->state = ConnState::connectFailed; ++ self->handleConnState(); ++ return; ++ } + ++ BMCWEB_LOG_DEBUG << "Connected to: " << endpoint; ++ if (self->sslConn) ++ { ++ self->performHandshake(); ++ } ++ else ++ { ++ self->handleConnState(); ++ } ++ }; + conn.expires_after(std::chrono::seconds(30)); +- conn.async_connect( +- endpointList, [self(shared_from_this())]( +- const boost::beast::error_code ec, +- const boost::asio::ip::tcp::endpoint& endpoint) { ++ conn.async_connect(endpointList, std::move(respHandler)); ++ } ++ ++ void performHandshake() ++ { ++ state = ConnState::handshakeInProgress; ++ ++ sslConn->async_handshake( ++ boost::asio::ssl::stream_base::client, ++ [self(shared_from_this())](const boost::beast::error_code ec) { + if (ec) + { +- BMCWEB_LOG_ERROR << "Connect " << endpoint +- << " failed: " << ec.message(); +- self->state = ConnState::connectFailed; ++ BMCWEB_LOG_ERROR << "SSL handshake failed: " ++ << ec.message(); ++ self->state = ConnState::handshakeFailed; + self->handleConnState(); + return; + } +- BMCWEB_LOG_DEBUG << "Connected to: " << endpoint; ++ ++ BMCWEB_LOG_DEBUG << "SSL Handshake successfull"; + self->state = ConnState::connected; + self->handleConnState(); + }); +@@ -132,132 +166,187 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + + void sendMessage(const std::string& data) + { +- state = ConnState::sendInProgress; +- + BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; ++ state = ConnState::sendInProgress; + + req.body() = data; + req.prepare_payload(); + +- // Set a timeout on the operation +- conn.expires_after(std::chrono::seconds(30)); ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code ec, ++ const std::size_t& bytesTransferred) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message(); ++ self->state = ConnState::sendFailed; ++ self->handleConnState(); ++ return; ++ } + +- // Send the HTTP request to the remote host +- boost::beast::http::async_write( +- conn, req, +- [self(shared_from_this())](const boost::beast::error_code& ec, +- const std::size_t& bytesTransferred) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "sendMessage() failed: " +- << ec.message(); +- self->state = ConnState::sendFailed; +- self->handleConnState(); +- return; +- } +- BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " +- << bytesTransferred; +- boost::ignore_unused(bytesTransferred); ++ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " ++ << bytesTransferred; ++ boost::ignore_unused(bytesTransferred); ++ self->recvMessage(); ++ }; + +- self->recvMessage(); +- }); ++ // Set a timeout on the operation ++ conn.expires_after(std::chrono::seconds(30)); ++ if (sslConn) ++ { ++ boost::beast::http::async_write(*sslConn, req, ++ std::move(respHandler)); ++ } ++ else ++ { ++ boost::beast::http::async_write(conn, req, std::move(respHandler)); ++ } + } +- + void recvMessage() + { + state = ConnState::recvInProgress; + ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code ec, ++ const std::size_t& bytesTransferred) { ++ if (ec && ec != boost::asio::ssl::error::stream_truncated) ++ { ++ BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message(); ++ ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } ++ ++ BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " ++ << bytesTransferred; ++ boost::ignore_unused(bytesTransferred); ++ ++ // Check if the response and header are received ++ if (!self->parser->is_done()) ++ { ++ // The parser failed to receive the response ++ BMCWEB_LOG_ERROR ++ << "recvMessage() parser failed to receive response"; ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } ++ ++ unsigned int respCode = self->parser->get().result_int(); ++ BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " ++ << respCode; ++ ++ // 2XX response is considered to be successful ++ if ((respCode < 200) || (respCode >= 300)) ++ { ++ // The listener failed to receive the Sent-Event ++ BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " ++ "receive Sent-Event"; ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } ++ ++ // Send is successful, Lets remove data from queue ++ // check for next request data in queue. ++ if (!self->requestDataQueue.empty()) ++ { ++ self->requestDataQueue.pop_front(); ++ } ++ self->state = ConnState::idle; ++ // Keep the connection alive if server supports it ++ // Else close the connection ++ BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " ++ << self->parser->keep_alive(); ++ if (!self->parser->keep_alive()) ++ { ++ // Abort the connection since server is not keep-alive enabled ++ self->state = ConnState::abortConnection; ++ } ++ ++ // Returns ownership of the parsed message ++ self->parser->release(); ++ ++ self->handleConnState(); ++ }; + parser.emplace(std::piecewise_construct, std::make_tuple()); + parser->body_limit(httpReadBodyLimit); + + // Check only for the response header + parser->skip(true); ++ conn.expires_after(std::chrono::seconds(30)); ++ if (sslConn) ++ { ++ boost::beast::http::async_read(*sslConn, buffer, *parser, ++ std::move(respHandler)); ++ } ++ else ++ { ++ boost::beast::http::async_read(conn, buffer, *parser, ++ std::move(respHandler)); ++ } ++ } ++ void doClose() ++ { ++ state = ConnState::closeInProgress; + +- // Receive the HTTP response +- boost::beast::http::async_read( +- conn, buffer, *parser, +- [self(shared_from_this())](const boost::beast::error_code& ec, +- const std::size_t& bytesTransferred) { ++ // Set the timeout on the tcp stream socket for the async operation ++ conn.expires_after(std::chrono::seconds(30)); ++ if (sslConn) ++ { ++ sslConn->async_shutdown([self = shared_from_this()]( ++ const boost::system::error_code ec) { + if (ec) + { +- BMCWEB_LOG_ERROR << "recvMessage() failed: " +- << ec.message(); +- self->state = ConnState::recvFailed; +- self->handleConnState(); +- return; ++ // Many https server closes connection abruptly ++ // i.e witnout close_notify. More details are at ++ // https://github.com/boostorg/beast/issues/824 ++ if (ec == boost::asio::ssl::error::stream_truncated) ++ { ++ BMCWEB_LOG_INFO << "doClose(): Connection " ++ "closed by server. "; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "doClose() failed: " ++ << ec.message(); ++ } + } +- BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " +- << bytesTransferred; +- BMCWEB_LOG_DEBUG << "recvMessage() data: " +- << self->parser->get(); +- +- // Check if the response and header are received +- if (!self->parser->is_done()) ++ else + { +- // The parser failed to receive the response +- BMCWEB_LOG_ERROR +- << "recvMessage() parser failed to receive response"; +- self->state = ConnState::recvFailed; +- self->handleConnState(); +- return; ++ BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; + } ++ self->conn.close(); + +- unsigned int respCode = self->parser->get().result_int(); +- BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " +- << respCode; +- +- // 2XX response is considered to be successful +- if ((respCode < 200) || (respCode >= 300)) ++ if ((self->state != ConnState::suspended) && ++ (self->state != ConnState::terminated)) + { +- // The listener failed to receive the Sent-Event +- BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " +- "receive Sent-Event"; +- self->state = ConnState::recvFailed; ++ self->state = ConnState::closed; + self->handleConnState(); +- return; + } +- +- // Send is successful, Lets remove data from queue +- // check for next request data in queue. +- if (!self->requestDataQueue.empty()) +- { +- self->requestDataQueue.pop_front(); +- } +- self->state = ConnState::idle; +- +- // Keep the connection alive if server supports it +- // Else close the connection +- BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " +- << self->parser->keep_alive(); +- if (!self->parser->keep_alive()) +- { +- // Abort the connection since server is not keep-alive +- // enabled +- self->state = ConnState::abortConnection; +- } +- +- self->handleConnState(); + }); +- } +- +- void doClose() +- { +- state = ConnState::closeInProgress; +- boost::beast::error_code ec; +- conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); +- conn.close(); +- +- // not_connected happens sometimes so don't bother reporting it. +- if (ec && ec != boost::beast::errc::not_connected) +- { +- BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); +- return; + } +- BMCWEB_LOG_DEBUG << "Connection closed gracefully"; +- if ((state != ConnState::suspended) && (state != ConnState::terminated)) ++ else + { +- state = ConnState::closed; +- handleConnState(); ++ boost::beast::error_code ec; ++ conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ++ ec); ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "doClose() failed: " << ec.message(); ++ } ++ else ++ { ++ BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; ++ } ++ conn.close(); ++ ++ if ((state != ConnState::suspended) && ++ (state != ConnState::terminated)) ++ { ++ state = ConnState::closed; ++ handleConnState(); ++ } + } + } + +@@ -330,6 +419,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + { + case ConnState::resolveInProgress: + case ConnState::connectInProgress: ++ case ConnState::handshakeInProgress: + case ConnState::sendInProgress: + case ConnState::recvInProgress: + case ConnState::closeInProgress: +@@ -356,6 +446,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + case ConnState::resolveFailed: + case ConnState::connectFailed: ++ case ConnState::handshakeFailed: + case ConnState::sendFailed: + case ConnState::recvFailed: + case ConnState::retry: +@@ -394,7 +485,8 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + public: + explicit HttpClient(boost::asio::io_context& ioc, const std::string& id, + const std::string& destIP, const std::string& destPort, +- const std::string& destUri) : ++ const std::string& destUri, ++ const std::string& uriProto) : + conn(ioc), + timer(ioc), req(boost::beast::http::verb::post, destUri, 11), + state(ConnState::initialized), subId(id), host(destIP), port(destPort), +@@ -407,8 +499,11 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + req.keep_alive(true); + + requestDataQueue.set_capacity(maxRequestQueueSize); ++ if (uriProto == "https") ++ { ++ sslConn.emplace(conn, ctx); ++ } + } +- + void sendData(const std::string& data) + { + if ((state == ConnState::suspended) || (state == ConnState::terminated)) +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 08d0b98..f1ce0c0 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -385,7 +385,7 @@ class Subscription : public persistent_data::UserSubscription + { + conn = std::make_shared<crow::HttpClient>( + crow::connections::systemBus->get_io_context(), id, host, port, +- path); ++ path, uriProto); + } + + Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) : +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch new file mode 100644 index 000000000..ea521a7e4 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch @@ -0,0 +1,468 @@ +From d7a2660f200c38e74bfcbfe55b8da1b8bed08833 Mon Sep 17 00:00:00 2001 +From: AppaRao Puli <apparao.puli@linux.intel.com> +Date: Fri, 12 Mar 2021 18:53:25 +0000 +Subject: [PATCH] Add Server-Sent-Events support + +Server-Sent Events is a standard describing how servers can +initiate data transmission towards clients once an initial +client connection has been established. Unlike websockets +(which are bidirectional), Server-Sent Events are +unidirectional and commonly used to send message updates or +continuous data streams to a browser client. + +This is base patch for adding Server-Sent events support to +bmcweb. Redfish eventservice SSE style subscription uses +this and will be loaded on top of this commit. + +Tested: + - Tested using follow-up patch on top which adds + support for Redfish EventService SSE style subscription + and observed events are getting sent periodically. + +Change-Id: I36956565cbba30c2007852c9471f477f6d1736e9 +Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com> +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + http/http_connection.hpp | 10 +- + http/http_response.hpp | 7 +- + http/routing.hpp | 71 ++++++++++ + http/server_sent_event.hpp | 279 +++++++++++++++++++++++++++++++++++++ + 4 files changed, 362 insertions(+), 5 deletions(-) + create mode 100644 http/server_sent_event.hpp + +diff --git a/http/http_connection.hpp b/http/http_connection.hpp +index 8e53afa..a1bbfce 100644 +--- a/http/http_connection.hpp ++++ b/http/http_connection.hpp +@@ -378,11 +378,13 @@ class Connection : + [self] { self->completeRequest(); }); + }); + +- if (thisReq.isUpgrade() && +- boost::iequals( +- thisReq.getHeaderValue(boost::beast::http::field::upgrade), +- "websocket")) ++ if ((thisReq.isUpgrade() && ++ boost::iequals( ++ thisReq.getHeaderValue(boost::beast::http::field::upgrade), ++ "websocket")) || ++ (req->url == "/sse")) + { ++ BMCWEB_LOG_DEBUG << "Request: " << this << " is getting upgraded"; + handler->handleUpgrade(thisReq, res, std::move(adaptor)); + // delete lambda with self shared_ptr + // to enable connection destruction +diff --git a/http/http_response.hpp b/http/http_response.hpp +index a983d4a..07b0265 100644 +--- a/http/http_response.hpp ++++ b/http/http_response.hpp +@@ -15,10 +15,15 @@ namespace crow + template <typename Adaptor, typename Handler> + class Connection; + ++template <typename Adaptor> ++class SseConnectionImpl; ++ + struct Response + { + template <typename Adaptor, typename Handler> + friend class crow::Connection; ++ template <typename Adaptor> ++ friend class crow::SseConnectionImpl; + using response_type = + boost::beast::http::response<boost::beast::http::string_body>; + +@@ -143,8 +148,8 @@ struct Response + + private: + bool completed{}; +- std::function<void()> completeRequestHandler; + std::function<bool()> isAliveHelper; ++ std::function<void()> completeRequestHandler; + + // In case of a JSON object, set the Content-Type header + void jsonMode() +diff --git a/http/routing.hpp b/http/routing.hpp +index 5d9c8e3..bfff107 100644 +--- a/http/routing.hpp ++++ b/http/routing.hpp +@@ -6,6 +6,7 @@ + #include "http_response.hpp" + #include "logging.hpp" + #include "privileges.hpp" ++#include "server_sent_event.hpp" + #include "sessions.hpp" + #include "utility.hpp" + #include "websocket.hpp" +@@ -398,6 +399,68 @@ class WebSocketRule : public BaseRule + std::function<void(crow::websocket::Connection&)> errorHandler; + }; + ++class SseSocketRule : public BaseRule ++{ ++ using self_t = SseSocketRule; ++ ++ public: ++ SseSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) ++ {} ++ ++ void validate() override ++ {} ++ ++ void handle(const Request&, ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const RoutingParams&) override ++ { ++ asyncResp->res.result(boost::beast::http::status::not_found); ++ } ++ ++ void handleUpgrade(const Request& req, Response&, ++ boost::asio::ip::tcp::socket&& adaptor) override ++ { ++ std::shared_ptr<crow::SseConnectionImpl<boost::asio::ip::tcp::socket>> ++ myConnection = std::make_shared< ++ crow::SseConnectionImpl<boost::asio::ip::tcp::socket>>( ++ req, std::move(adaptor), openHandler, closeHandler); ++ myConnection->start(); ++ } ++#ifdef BMCWEB_ENABLE_SSL ++ void handleUpgrade(const Request& req, Response&, ++ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& ++ adaptor) override ++ { ++ std::shared_ptr<crow::SseConnectionImpl< ++ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> ++ myConnection = std::make_shared<crow::SseConnectionImpl< ++ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( ++ req, std::move(adaptor), openHandler, closeHandler); ++ myConnection->start(); ++ } ++#endif ++ ++ template <typename Func> ++ self_t& onopen(Func f) ++ { ++ openHandler = f; ++ return *this; ++ } ++ ++ template <typename Func> ++ self_t& onclose(Func f) ++ { ++ closeHandler = f; ++ return *this; ++ } ++ ++ private: ++ std::function<void(std::shared_ptr<crow::SseConnection>&, ++ const crow::Request&, crow::Response&)> ++ openHandler; ++ std::function<void(std::shared_ptr<crow::SseConnection>&)> closeHandler; ++}; ++ + template <typename T> + struct RuleParameterTraits + { +@@ -410,6 +473,14 @@ struct RuleParameterTraits + return *p; + } + ++ SseSocketRule& serverSentEvent() ++ { ++ self_t* self = static_cast<self_t*>(this); ++ SseSocketRule* p = new SseSocketRule(self->rule); ++ self->ruleToUpgrade.reset(p); ++ return *p; ++ } ++ + self_t& name(const std::string_view name) noexcept + { + self_t* self = static_cast<self_t*>(this); +diff --git a/http/server_sent_event.hpp b/http/server_sent_event.hpp +new file mode 100644 +index 0000000..41d18ed +--- /dev/null ++++ b/http/server_sent_event.hpp +@@ -0,0 +1,279 @@ ++#pragma once ++#include "http_request.hpp" ++ ++#include <boost/algorithm/string/predicate.hpp> ++#include <boost/asio/buffer.hpp> ++#include <boost/beast/http/buffer_body.hpp> ++#include <boost/beast/websocket.hpp> ++ ++#include <array> ++#include <functional> ++ ++#ifdef BMCWEB_ENABLE_SSL ++#include <boost/beast/websocket/ssl.hpp> ++#endif ++ ++namespace crow ++{ ++ ++struct SseConnection : std::enable_shared_from_this<SseConnection> ++{ ++ public: ++ SseConnection(const crow::Request& reqIn) : req(reqIn) ++ {} ++ virtual ~SseConnection() = default; ++ ++ virtual boost::asio::io_context& getIoContext() = 0; ++ virtual void sendSSEHeader() = 0; ++ virtual void completeRequest() = 0; ++ virtual void close(const std::string_view msg = "quit") = 0; ++ virtual void sendEvent(const std::string_view id, ++ const std::string_view msg) = 0; ++ ++ crow::Request req; ++ crow::Response res; ++}; ++ ++template <typename Adaptor> ++class SseConnectionImpl : public SseConnection ++{ ++ public: ++ SseConnectionImpl( ++ const crow::Request& reqIn, Adaptor adaptorIn, ++ std::function<void(std::shared_ptr<SseConnection>&, ++ const crow::Request&, crow::Response&)> ++ openHandler, ++ std::function<void(std::shared_ptr<SseConnection>&)> closeHandler) : ++ SseConnection(reqIn), ++ adaptor(std::move(adaptorIn)), openHandler(std::move(openHandler)), ++ closeHandler(std::move(closeHandler)) ++ { ++ BMCWEB_LOG_DEBUG << "SseConnectionImpl: SSE constructor " << this; ++ } ++ ++ ~SseConnectionImpl() override ++ { ++ res.completeRequestHandler = nullptr; ++ BMCWEB_LOG_DEBUG << "SseConnectionImpl: SSE destructor " << this; ++ } ++ ++ boost::asio::io_context& getIoContext() override ++ { ++ return static_cast<boost::asio::io_context&>( ++ adaptor.get_executor().context()); ++ } ++ ++ void start() ++ { ++ // Register for completion callback. ++ res.completeRequestHandler = [this, self(shared_from_this())] { ++ boost::asio::post(this->adaptor.get_executor(), ++ [self] { self->completeRequest(); }); ++ }; ++ ++ if (openHandler) ++ { ++ std::shared_ptr<SseConnection> self = this->shared_from_this(); ++ openHandler(self, req, res); ++ } ++ } ++ ++ void close(const std::string_view msg) override ++ { ++ BMCWEB_LOG_DEBUG << "Closing SSE connection " << this << " - " << msg; ++ boost::beast::get_lowest_layer(adaptor).close(); ++ ++ // send notification to handler for cleanup ++ if (closeHandler) ++ { ++ std::shared_ptr<SseConnection> self = this->shared_from_this(); ++ closeHandler(self); ++ } ++ } ++ ++ void sendSSEHeader() override ++ { ++ BMCWEB_LOG_DEBUG << "Starting SSE connection"; ++ using BodyType = boost::beast::http::buffer_body; ++ auto response = ++ std::make_shared<boost::beast::http::response<BodyType>>( ++ boost::beast::http::status::ok, 11); ++ auto serializer = ++ std::make_shared<boost::beast::http::response_serializer<BodyType>>( ++ *response); ++ ++ response->set(boost::beast::http::field::server, "bmcweb"); ++ response->set(boost::beast::http::field::content_type, ++ "text/event-stream"); ++ response->body().data = nullptr; ++ response->body().size = 0; ++ response->body().more = true; ++ ++ boost::beast::http::async_write_header( ++ adaptor, *serializer, ++ [this, self(shared_from_this()), response, serializer]( ++ const boost::beast::error_code& ec, const std::size_t&) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Error sending header" << ec; ++ close("async_write_header failed"); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "SSE header sent - Connection established"; ++ ++ // SSE stream header sent, So lets setup monitor. ++ // Any read data on this stream will be error in case of SSE. ++ setupRead(); ++ }); ++ } ++ ++ void setupRead() ++ { ++ adaptor.async_read_some( ++ outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()), ++ [this](const boost::system::error_code& ec, std::size_t bytesRead) { ++ BMCWEB_LOG_DEBUG << "async_read_some: Read " << bytesRead ++ << " bytes"; ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Read error: " << ec; ++ } ++ outputBuffer.commit(bytesRead); ++ outputBuffer.consume(bytesRead); ++ ++ // After establishing SSE stream, Reading data on this ++ // stream means client is disobeys the SSE protocol. ++ // Read the data to avoid buffer attacks and close connection. ++ close("Close SSE connection"); ++ return; ++ }); ++ } ++ ++ void doWrite() ++ { ++ if (doingWrite) ++ { ++ return; ++ } ++ if (inputBuffer.size() == 0) ++ { ++ BMCWEB_LOG_DEBUG << "inputBuffer is empty... Bailing out"; ++ return; ++ } ++ doingWrite = true; ++ ++ adaptor.async_write_some( ++ inputBuffer.data(), [this, self(shared_from_this())]( ++ boost::beast::error_code ec, ++ const std::size_t& bytesTransferred) { ++ doingWrite = false; ++ inputBuffer.consume(bytesTransferred); ++ ++ if (ec == boost::asio::error::eof) ++ { ++ BMCWEB_LOG_ERROR << "async_write_some() SSE stream closed"; ++ close("SSE stream closed"); ++ return; ++ } ++ ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "async_write_some() failed: " ++ << ec.message(); ++ close("async_write_some failed"); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "async_write_some() bytes transferred: " ++ << bytesTransferred; ++ ++ doWrite(); ++ }); ++ } ++ ++ void completeRequest() override ++ { ++ BMCWEB_LOG_DEBUG << "SSE completeRequest() handler"; ++ if (res.body().empty() && !res.jsonValue.empty()) ++ { ++ res.addHeader("Content-Type", "application/json"); ++ res.body() = res.jsonValue.dump( ++ 2, ' ', true, nlohmann::json::error_handler_t::replace); ++ } ++ ++ res.preparePayload(); ++ auto serializer = ++ std::make_shared<boost::beast::http::response_serializer< ++ boost::beast::http::string_body>>(*res.stringResponse); ++ ++ boost::beast::http::async_write( ++ adaptor, *serializer, ++ [this, self(shared_from_this()), ++ serializer](const boost::system::error_code& ec, ++ std::size_t bytesTransferred) { ++ BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred ++ << " bytes"; ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG << this << " from async_write failed"; ++ return; ++ } ++ res.clear(); ++ ++ BMCWEB_LOG_DEBUG << this ++ << " Closing SSE connection - Request invalid"; ++ close("Request invalid"); ++ }); ++ ++ // delete lambda with self shared_ptr ++ // to enable connection destruction ++ res.completeRequestHandler = nullptr; ++ } ++ ++ void sendEvent(const std::string_view id, ++ const std::string_view msg) override ++ { ++ if (msg.empty()) ++ { ++ BMCWEB_LOG_DEBUG << "Empty data, bailing out."; ++ return; ++ } ++ ++ std::string rawData; ++ if (!id.empty()) ++ { ++ rawData += "id: "; ++ rawData.append(id.begin(), id.end()); ++ rawData += "\n"; ++ } ++ ++ rawData += "data: "; ++ for (char character : msg) ++ { ++ rawData += character; ++ if (character == '\n') ++ { ++ rawData += "data: "; ++ } ++ } ++ rawData += "\n\n"; ++ ++ boost::asio::buffer_copy(inputBuffer.prepare(rawData.size()), ++ boost::asio::buffer(rawData)); ++ inputBuffer.commit(rawData.size()); ++ ++ doWrite(); ++ } ++ ++ private: ++ Adaptor adaptor; ++ ++ boost::beast::flat_static_buffer<1024U * 8U> outputBuffer; ++ boost::beast::flat_static_buffer<1024U * 64U> inputBuffer; ++ bool doingWrite = false; ++ ++ std::function<void(std::shared_ptr<SseConnection>&, const crow::Request&, ++ crow::Response&)> ++ openHandler; ++ std::function<void(std::shared_ptr<SseConnection>&)> closeHandler; ++}; ++} // namespace crow +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch new file mode 100644 index 000000000..ee69081ef --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch @@ -0,0 +1,679 @@ +From 799e47842e179f7c752712004f0e96d3219eee11 Mon Sep 17 00:00:00 2001 +From: AppaRao Puli <apparao.puli@linux.intel.com> +Date: Tue, 16 Mar 2021 15:37:24 +0000 +Subject: [PATCH] Add SSE style subscription support to eventservice + +This commit adds the SSE style eventservice subscription +style event. Using this, end user can subscribe for +Redfish event logs using GET on SSE usri from +browser. +URI: /redfish/v1/EventService/Subscriptions/SSE + +Tested: + - From Browser did GET on above SSE URI and + generated some Redfish event logs(power cycle) + and saw redfish event logs streaming on browser. + - After SSE registration, Check Subscription collections + and GET on individual subscription and saw desired + response. + - Ran RedfishValidation and its passed. + +Change-Id: I7f4b7a34974080739c4ba968ed570489af0474de +Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com> +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + http/http_connection.hpp | 2 +- + include/eventservice_sse.hpp | 75 +++++ + .../include/event_service_manager.hpp | 109 +++++-- + redfish-core/include/server_sent_events.hpp | 290 ------------------ + redfish-core/lib/event_service.hpp | 8 +- + src/webserver_main.cpp | 2 + + 6 files changed, 164 insertions(+), 322 deletions(-) + create mode 100644 include/eventservice_sse.hpp + delete mode 100644 redfish-core/include/server_sent_events.hpp + +diff --git a/http/http_connection.hpp b/http/http_connection.hpp +index a1bbfce..2d08501 100644 +--- a/http/http_connection.hpp ++++ b/http/http_connection.hpp +@@ -382,7 +382,7 @@ class Connection : + boost::iequals( + thisReq.getHeaderValue(boost::beast::http::field::upgrade), + "websocket")) || +- (req->url == "/sse")) ++ (req->url == "/redfish/v1/EventService/Subscriptions/SSE")) + { + BMCWEB_LOG_DEBUG << "Request: " << this << " is getting upgraded"; + handler->handleUpgrade(thisReq, res, std::move(adaptor)); +diff --git a/include/eventservice_sse.hpp b/include/eventservice_sse.hpp +new file mode 100644 +index 0000000..14daf00 +--- /dev/null ++++ b/include/eventservice_sse.hpp +@@ -0,0 +1,75 @@ ++#pragma once ++ ++#include <app.hpp> ++#include <event_service_manager.hpp> ++ ++namespace redfish ++{ ++namespace eventservice_sse ++{ ++ ++static bool createSubscription(std::shared_ptr<crow::SseConnection>& conn, ++ const crow::Request& req, crow::Response& res) ++{ ++ if ((EventServiceManager::getInstance().getNumberOfSubscriptions() >= ++ maxNoOfSubscriptions) || ++ EventServiceManager::getInstance().getNumberOfSSESubscriptions() >= ++ maxNoOfSSESubscriptions) ++ { ++ BMCWEB_LOG_ERROR << "Max SSE subscriptions reached"; ++ messages::eventSubscriptionLimitExceeded(res); ++ res.end(); ++ return false; ++ } ++ BMCWEB_LOG_DEBUG << "Request query param size: " << req.urlParams.size(); ++ ++ std::shared_ptr<redfish::Subscription> subValue = ++ std::make_shared<redfish::Subscription>(std::move(conn)); ++ ++ // GET on this URI means, Its SSE subscriptionType. ++ subValue->subscriptionType = redfish::subscriptionTypeSSE; ++ ++ // TODO: parse $filter query params and fill config. ++ subValue->protocol = "Redfish"; ++ subValue->retryPolicy = "TerminateAfterRetries"; ++ subValue->eventFormatType = "Event"; ++ ++ std::string id = ++ redfish::EventServiceManager::getInstance().addSubscription(subValue, ++ false); ++ if (id.empty()) ++ { ++ messages::internalError(res); ++ res.end(); ++ return false; ++ } ++ ++ return true; ++} ++ ++static void deleteSubscription(std::shared_ptr<crow::SseConnection>& conn) ++{ ++ redfish::EventServiceManager::getInstance().deleteSubscription(conn); ++} ++ ++inline void requestRoutes(App& app) ++{ ++ BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/SSE") ++ .privileges({{"ConfigureComponents", "ConfigureManager"}}) ++ .serverSentEvent() ++ .onopen([](std::shared_ptr<crow::SseConnection>& conn, ++ const crow::Request& req, crow::Response& res) { ++ BMCWEB_LOG_DEBUG << "Connection " << conn << " opened."; ++ if (createSubscription(conn, req, res)) ++ { ++ // All success, lets send SSE haader ++ conn->sendSSEHeader(); ++ } ++ }) ++ .onclose([](std::shared_ptr<crow::SseConnection>& conn) { ++ BMCWEB_LOG_DEBUG << "Connection " << conn << " closed"; ++ deleteSubscription(conn); ++ }); ++} ++} // namespace eventservice_sse ++} // namespace redfish +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 3f398d7..dd833ce 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -22,15 +22,17 @@ + #include <sys/inotify.h> + + #include <boost/asio/io_context.hpp> ++#include <boost/beast/core/span.hpp> + #include <boost/container/flat_map.hpp> + #include <error_messages.hpp> + #include <event_service_store.hpp> + #include <http_client.hpp> + #include <persistent_data.hpp> + #include <random.hpp> +-#include <server_sent_events.hpp> ++#include <server_sent_event.hpp> + #include <utils/json_utils.hpp> + ++#include <algorithm> + #include <cstdlib> + #include <ctime> + #include <fstream> +@@ -46,9 +48,27 @@ using ReadingsObjType = + static constexpr const char* eventFormatType = "Event"; + static constexpr const char* metricReportFormatType = "MetricReport"; + ++static constexpr const char* subscriptionTypeSSE = "SSE"; + static constexpr const char* eventServiceFile = + "/var/lib/bmcweb/eventservice_config.json"; + ++static constexpr const uint8_t maxNoOfSubscriptions = 20; ++static constexpr const uint8_t maxNoOfSSESubscriptions = 10; ++ ++#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES ++static std::optional<boost::asio::posix::stream_descriptor> inotifyConn; ++static constexpr const char* redfishEventLogDir = "/var/log"; ++static constexpr const char* redfishEventLogFile = "/var/log/redfish"; ++static constexpr const size_t iEventSize = sizeof(inotify_event); ++static int inotifyFd = -1; ++static int dirWatchDesc = -1; ++static int fileWatchDesc = -1; ++ ++// <ID, timestamp, RedfishLogId, registryPrefix, MessageId, MessageArgs> ++using EventLogObjectsType = ++ std::tuple<std::string, std::string, std::string, std::string, std::string, ++ std::vector<std::string>>; ++ + namespace message_registries + { + inline boost::beast::span<const MessageEntry> +@@ -68,24 +88,6 @@ inline boost::beast::span<const MessageEntry> + } + return boost::beast::span<const MessageEntry>(openbmc::registry); + } +-} // namespace message_registries +- +-#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES +-static std::optional<boost::asio::posix::stream_descriptor> inotifyConn; +-static constexpr const char* redfishEventLogDir = "/var/log"; +-static constexpr const char* redfishEventLogFile = "/var/log/redfish"; +-static constexpr const size_t iEventSize = sizeof(inotify_event); +-static int inotifyFd = -1; +-static int dirWatchDesc = -1; +-static int fileWatchDesc = -1; +- +-// <ID, timestamp, RedfishLogId, registryPrefix, MessageId, MessageArgs> +-using EventLogObjectsType = +- std::tuple<std::string, std::string, std::string, std::string, std::string, +- std::vector<std::string>>; +- +-namespace message_registries +-{ + static const Message* + getMsgFromRegistry(const std::string& messageKey, + const boost::beast::span<const MessageEntry>& registry) +@@ -388,11 +390,9 @@ class Subscription : public persistent_data::UserSubscription + path, uriProto); + } + +- Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) : +- eventSeqNum(1) +- { +- sseConn = std::make_shared<crow::ServerSentEvents>(adaptor); +- } ++ Subscription(const std::shared_ptr<crow::SseConnection>& adaptor) : ++ sseConn(adaptor), eventSeqNum(1) ++ {} + + ~Subscription() = default; + +@@ -417,7 +417,7 @@ class Subscription : public persistent_data::UserSubscription + + if (sseConn != nullptr) + { +- sseConn->sendData(eventSeqNum, msg); ++ sseConn->sendEvent(std::to_string(eventSeqNum), msg); + } + } + +@@ -508,6 +508,7 @@ class Subscription : public persistent_data::UserSubscription + + this->sendEvent( + msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); ++ this->eventSeqNum++; + } + #endif + +@@ -578,14 +579,39 @@ class Subscription : public persistent_data::UserSubscription + return eventSeqNum; + } + ++ void setSubscriptionId(const std::string& id) ++ { ++ BMCWEB_LOG_DEBUG << "Subscription ID: " << id; ++ subId = id; ++ } ++ ++ std::string getSubscriptionId() ++ { ++ return subId; ++ } ++ ++ std::optional<std::string> ++ getSubscriptionId(const std::shared_ptr<crow::SseConnection>& connPtr) ++ { ++ if (sseConn != nullptr && connPtr == sseConn) ++ { ++ BMCWEB_LOG_DEBUG << __FUNCTION__ ++ << " conn matched, subId: " << subId; ++ return subId; ++ } ++ ++ return std::nullopt; ++ } ++ + private: ++ std::shared_ptr<crow::SseConnection> sseConn = nullptr; + uint64_t eventSeqNum; + std::string host; + std::string port; + std::string path; + std::string uriProto; + std::shared_ptr<crow::HttpClient> conn = nullptr; +- std::shared_ptr<crow::ServerSentEvents> sseConn = nullptr; ++ std::string subId; + }; + + class EventServiceManager +@@ -942,6 +968,8 @@ class EventServiceManager + subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); + subValue->updateRetryPolicy(); + ++ // Set Subscription ID for back trace ++ subValue->setSubscriptionId(id); + return id; + } + +@@ -970,11 +998,40 @@ class EventServiceManager + } + } + ++ void deleteSubscription(const std::shared_ptr<crow::SseConnection>& connPtr) ++ { ++ for (const auto& it : this->subscriptionsMap) ++ { ++ std::shared_ptr<Subscription> entry = it.second; ++ if (entry->subscriptionType == subscriptionTypeSSE) ++ { ++ std::optional<std::string> id = ++ entry->getSubscriptionId(connPtr); ++ if (id) ++ { ++ deleteSubscription(*id); ++ return; ++ } ++ } ++ } ++ } ++ + size_t getNumberOfSubscriptions() + { + return subscriptionsMap.size(); + } + ++ size_t getNumberOfSSESubscriptions() const ++ { ++ auto count = std::count_if( ++ subscriptionsMap.begin(), subscriptionsMap.end(), ++ [this](const std::pair<std::string, std::shared_ptr<Subscription>>& ++ entry) { ++ return (entry.second->subscriptionType == subscriptionTypeSSE); ++ }); ++ return static_cast<size_t>(count); ++ } ++ + std::vector<std::string> getAllIDs() + { + std::vector<std::string> idList; +diff --git a/redfish-core/include/server_sent_events.hpp b/redfish-core/include/server_sent_events.hpp +deleted file mode 100644 +index 7613d7b..0000000 +--- a/redfish-core/include/server_sent_events.hpp ++++ /dev/null +@@ -1,290 +0,0 @@ +- +-/* +-// 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. +-*/ +-#pragma once +- +-#include <boost/asio/strand.hpp> +-#include <boost/beast/core/span.hpp> +-#include <boost/beast/http/buffer_body.hpp> +-#include <boost/beast/http/message.hpp> +-#include <boost/beast/version.hpp> +- +-#include <cstdlib> +-#include <functional> +-#include <iostream> +-#include <memory> +-#include <queue> +-#include <string> +- +-namespace crow +-{ +- +-static constexpr uint8_t maxReqQueueSize = 50; +- +-enum class SseConnState +-{ +- startInit, +- initInProgress, +- initialized, +- initFailed, +- sendInProgress, +- sendFailed, +- idle, +- suspended, +- closed +-}; +- +-class ServerSentEvents : public std::enable_shared_from_this<ServerSentEvents> +-{ +- private: +- std::shared_ptr<boost::beast::tcp_stream> sseConn; +- std::queue<std::pair<uint64_t, std::string>> requestDataQueue; +- std::string outBuffer; +- SseConnState state; +- int retryCount; +- int maxRetryAttempts; +- +- void sendEvent(const std::string& id, const std::string& msg) +- { +- if (msg.empty()) +- { +- BMCWEB_LOG_DEBUG << "Empty data, bailing out."; +- return; +- } +- +- if (state == SseConnState::sendInProgress) +- { +- return; +- } +- state = SseConnState::sendInProgress; +- +- if (!id.empty()) +- { +- outBuffer += "id: "; +- outBuffer.append(id.begin(), id.end()); +- outBuffer += "\n"; +- } +- +- outBuffer += "data: "; +- for (char character : msg) +- { +- outBuffer += character; +- if (character == '\n') +- { +- outBuffer += "data: "; +- } +- } +- outBuffer += "\n\n"; +- +- doWrite(); +- } +- +- void doWrite() +- { +- if (outBuffer.empty()) +- { +- BMCWEB_LOG_DEBUG << "All data sent successfully."; +- // Send is successful, Lets remove data from queue +- // check for next request data in queue. +- requestDataQueue.pop(); +- state = SseConnState::idle; +- checkQueue(); +- return; +- } +- +- sseConn->async_write_some( +- boost::asio::buffer(outBuffer.data(), outBuffer.size()), +- [self(shared_from_this())]( +- boost::beast::error_code ec, +- [[maybe_unused]] const std::size_t& bytesTransferred) { +- self->outBuffer.erase(0, bytesTransferred); +- +- if (ec == boost::asio::error::eof) +- { +- // Send is successful, Lets remove data from queue +- // check for next request data in queue. +- self->requestDataQueue.pop(); +- self->state = SseConnState::idle; +- self->checkQueue(); +- return; +- } +- +- if (ec) +- { +- BMCWEB_LOG_ERROR << "async_write_some() failed: " +- << ec.message(); +- self->state = SseConnState::sendFailed; +- self->checkQueue(); +- return; +- } +- BMCWEB_LOG_DEBUG << "async_write_some() bytes transferred: " +- << bytesTransferred; +- +- self->doWrite(); +- }); +- } +- +- void startSSE() +- { +- if (state == SseConnState::initInProgress) +- { +- return; +- } +- state = SseConnState::initInProgress; +- +- BMCWEB_LOG_DEBUG << "starting SSE connection "; +- using BodyType = boost::beast::http::buffer_body; +- auto response = +- std::make_shared<boost::beast::http::response<BodyType>>( +- boost::beast::http::status::ok, 11); +- auto serializer = +- std::make_shared<boost::beast::http::response_serializer<BodyType>>( +- *response); +- +- // TODO: Add hostname in http header. +- response->set(boost::beast::http::field::server, "iBMC"); +- response->set(boost::beast::http::field::content_type, +- "text/event-stream"); +- response->body().data = nullptr; +- response->body().size = 0; +- response->body().more = true; +- +- boost::beast::http::async_write_header( +- *sseConn, *serializer, +- [this, response, +- serializer](const boost::beast::error_code& ec, +- [[maybe_unused]] const std::size_t& bytesTransferred) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Error sending header" << ec; +- state = SseConnState::initFailed; +- checkQueue(); +- return; +- } +- +- BMCWEB_LOG_DEBUG << "startSSE Header sent."; +- state = SseConnState::initialized; +- checkQueue(); +- }); +- } +- +- void checkQueue(const bool newRecord = false) +- { +- if (requestDataQueue.empty()) +- { +- BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n"; +- return; +- } +- +- if (retryCount >= maxRetryAttempts) +- { +- BMCWEB_LOG_ERROR << "Maximum number of retries is reached."; +- +- // Clear queue. +- while (!requestDataQueue.empty()) +- { +- requestDataQueue.pop(); +- } +- +- // TODO: Take 'DeliveryRetryPolicy' action. +- // For now, doing 'SuspendRetries' action. +- state = SseConnState::suspended; +- return; +- } +- +- if ((state == SseConnState::initFailed) || +- (state == SseConnState::sendFailed)) +- { +- if (newRecord) +- { +- // We are already running async wait and retry. +- // Since record is added to queue, it gets the +- // turn in FIFO. +- return; +- } +- +- retryCount++; +- // TODO: Perform async wait for retryTimeoutInterval before proceed. +- } +- else +- { +- // reset retry count. +- retryCount = 0; +- } +- +- switch (state) +- { +- case SseConnState::initInProgress: +- case SseConnState::sendInProgress: +- case SseConnState::suspended: +- case SseConnState::startInit: +- case SseConnState::closed: +- // do nothing +- break; +- case SseConnState::initFailed: +- { +- startSSE(); +- break; +- } +- case SseConnState::initialized: +- case SseConnState::idle: +- case SseConnState::sendFailed: +- { +- std::pair<uint64_t, std::string> reqData = +- requestDataQueue.front(); +- sendEvent(std::to_string(reqData.first), reqData.second); +- break; +- } +- } +- +- return; +- } +- +- public: +- ServerSentEvents(const ServerSentEvents&) = delete; +- ServerSentEvents& operator=(const ServerSentEvents&) = delete; +- ServerSentEvents(ServerSentEvents&&) = delete; +- ServerSentEvents& operator=(ServerSentEvents&&) = delete; +- +- ServerSentEvents(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) : +- sseConn(adaptor), state(SseConnState::startInit), retryCount(0), +- maxRetryAttempts(5) +- { +- startSSE(); +- } +- +- ~ServerSentEvents() = default; +- +- void sendData(const uint64_t& id, const std::string& data) +- { +- if (state == SseConnState::suspended) +- { +- return; +- } +- +- if (requestDataQueue.size() <= maxReqQueueSize) +- { +- requestDataQueue.push(std::pair(id, data)); +- checkQueue(true); +- } +- else +- { +- BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data."; +- } +- } +-}; +- +-} // namespace crow +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 8609862..249e594 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -37,8 +37,6 @@ static constexpr const std::array<const char*, 1> supportedResourceTypes = { + "Task"}; + #endif + +-static constexpr const uint8_t maxNoOfSubscriptions = 20; +- + inline void requestRoutesEventService(App& app) + { + BMCWEB_ROUTE(app, "/redfish/v1/EventService/") +@@ -50,6 +48,8 @@ inline void requestRoutesEventService(App& app) + {"@odata.type", "#EventService.v1_5_0.EventService"}, + {"Id", "EventService"}, + {"Name", "Event Service"}, ++ {"ServerSentEventUri", ++ "/redfish/v1/EventService/Subscriptions/SSE"}, + {"Subscriptions", + {{"@odata.id", "/redfish/v1/EventService/Subscriptions"}}}, + {"Actions", +@@ -90,9 +90,7 @@ inline void requestRoutesEventService(App& app) + .privileges(redfish::privileges::patchEventService) + .methods(boost::beast::http::verb::patch)( + [](const crow::Request& req, +- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) +- +- { ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { + std::optional<bool> serviceEnabled; + std::optional<uint32_t> retryAttemps; + std::optional<uint32_t> retryInterval; +diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp +index bf98aae..53745d8 100644 +--- a/src/webserver_main.cpp ++++ b/src/webserver_main.cpp +@@ -6,6 +6,7 @@ + #include <cors_preflight.hpp> + #include <dbus_monitor.hpp> + #include <dbus_singleton.hpp> ++#include <eventservice_sse.hpp> + #include <google/google_service_root.hpp> + #include <hostname_monitor.hpp> + #include <ibm/management_console_rest.hpp> +@@ -83,6 +84,7 @@ int main(int /*argc*/, char** /*argv*/) + #endif + + #ifdef BMCWEB_ENABLE_REDFISH ++ redfish::eventservice_sse::requestRoutes(app); + redfish::requestRoutes(app); + redfish::RedfishService redfish(app); + +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch new file mode 100644 index 000000000..3914cc81a --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch @@ -0,0 +1,296 @@ +From 769f0e20d0a7e786d7091ffb7ee57d35204dfa28 Mon Sep 17 00:00:00 2001 +From: AppaRao Puli <apparao.puli@linux.intel.com> +Date: Wed, 17 Mar 2021 01:16:50 +0000 +Subject: [PATCH] Add EventService SSE filter support + +This commit implements the Event Service SSE stream +filters support. As per redfish specification: +The SSE streams have these formats: + - Metric report SSE stream + - Event message SSE stream + +To reduce the amount of data, service supports $filter +query parameter in SSE URI. +Below properties support as filter criteria: + - EventFormatType( Event & MetricReport) + - MessageId + - RegistryPrefix + - MetricReportDefinition + +For more details, refer Redfish specification section 13.5.2 + +Tested: + Created SSE stream with different filters and observed + desired events on SSE stream client(browser), some examples + - To get all Redfish events, + URI: /redfish/v1/EventService/Subscriptions/SSE?$filter=(EventFormatType%20eq%20Event) + - To get Redfish events with RegistryPrefix "OpenBMC" + URI: /redfish/v1/EventService/Subscriptions/SSE?$filter=(RegistryPrefix%20eq%20OpenBMC) + - To get only DC power of Events, + URI: /redfish/v1/EventService/Subscriptions/SSE?$filter=(EventFormatType%20eq%20Event)%20and%20(MessageId%20eq%20DCPowerOff) + +Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com> +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +Change-Id: I55c6f53bb5e57aa1f2d1601f1a16525a33b13bd2 +--- + include/eventservice_sse.hpp | 145 +++++++++++++++++- + redfish-core/include/error_messages.hpp | 9 ++ + .../include/event_service_manager.hpp | 5 + + redfish-core/lib/event_service.hpp | 5 - + redfish-core/src/error_messages.cpp | 26 ++++ + 5 files changed, 181 insertions(+), 9 deletions(-) + +diff --git a/include/eventservice_sse.hpp b/include/eventservice_sse.hpp +index 14daf00..fed7fec 100644 +--- a/include/eventservice_sse.hpp ++++ b/include/eventservice_sse.hpp +@@ -23,16 +23,153 @@ static bool createSubscription(std::shared_ptr<crow::SseConnection>& conn, + } + BMCWEB_LOG_DEBUG << "Request query param size: " << req.urlParams.size(); + ++ // EventService SSE supports only "$filter" query param. ++ if (req.urlParams.size() > 1) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ std::string eventFormatType; ++ std::string queryFilters; ++ if (req.urlParams.size()) ++ { ++ boost::urls::url_view::params_type::iterator it = ++ req.urlParams.find("$filter"); ++ if (it == req.urlParams.end()) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ queryFilters = it->value(); ++ } ++ else ++ { ++ eventFormatType = "Event"; ++ } ++ ++ std::vector<std::string> msgIds; ++ std::vector<std::string> regPrefixes; ++ std::vector<std::string> mrdsArray; ++ if (!queryFilters.empty()) ++ { ++ // Reading from query params. ++ bool status = readSSEQueryParams(queryFilters, eventFormatType, msgIds, ++ regPrefixes, mrdsArray); ++ if (!status) ++ { ++ messages::invalidObject(res, queryFilters); ++ res.end(); ++ return false; ++ } ++ ++ // RegsitryPrefix and messageIds are mutuly exclusive as per redfish ++ // specification. ++ if (regPrefixes.size() && msgIds.size()) ++ { ++ messages::mutualExclusiveProperties(res, "RegistryPrefix", ++ "MessageId"); ++ res.end(); ++ return false; ++ } ++ ++ if (!eventFormatType.empty()) ++ { ++ if (std::find(supportedEvtFormatTypes.begin(), ++ supportedEvtFormatTypes.end(), ++ eventFormatType) == supportedEvtFormatTypes.end()) ++ { ++ messages::propertyValueNotInList(res, eventFormatType, ++ "EventFormatType"); ++ res.end(); ++ return false; ++ } ++ } ++ else ++ { ++ // If nothing specified, using default "Event" ++ eventFormatType = "Event"; ++ } ++ ++ if (!regPrefixes.empty()) ++ { ++ for (const std::string& it : regPrefixes) ++ { ++ if (std::find(supportedRegPrefixes.begin(), ++ supportedRegPrefixes.end(), ++ it) == supportedRegPrefixes.end()) ++ { ++ messages::propertyValueNotInList(res, it, "RegistryPrefix"); ++ res.end(); ++ return false; ++ } ++ } ++ } ++ ++ if (!msgIds.empty()) ++ { ++ std::vector<std::string> registryPrefix; ++ ++ // If no registry prefixes are mentioned, consider all supported ++ // prefixes to validate message ID ++ if (regPrefixes.empty()) ++ { ++ registryPrefix.assign(supportedRegPrefixes.begin(), ++ supportedRegPrefixes.end()); ++ } ++ else ++ { ++ registryPrefix = regPrefixes; ++ } ++ ++ for (const std::string& id : msgIds) ++ { ++ bool validId = false; ++ ++ // Check for Message ID in each of the selected Registry ++ for (const std::string& it : registryPrefix) ++ { ++ const boost::beast::span< ++ const redfish::message_registries::MessageEntry> ++ registry = ++ redfish::message_registries::getRegistryFromPrefix( ++ it); ++ ++ if (std::any_of( ++ registry.cbegin(), registry.cend(), ++ [&id]( ++ const redfish::message_registries::MessageEntry& ++ messageEntry) { ++ return !id.compare(messageEntry.first); ++ })) ++ { ++ validId = true; ++ break; ++ } ++ } ++ ++ if (!validId) ++ { ++ messages::propertyValueNotInList(res, id, "MessageIds"); ++ res.end(); ++ return false; ++ } ++ } ++ } ++ } ++ + std::shared_ptr<redfish::Subscription> subValue = + std::make_shared<redfish::Subscription>(std::move(conn)); + + // GET on this URI means, Its SSE subscriptionType. +- subValue->subscriptionType = redfish::subscriptionTypeSSE; +- +- // TODO: parse $filter query params and fill config. ++ subValue->subscriptionType = subscriptionTypeSSE; + subValue->protocol = "Redfish"; + subValue->retryPolicy = "TerminateAfterRetries"; +- subValue->eventFormatType = "Event"; ++ subValue->eventFormatType = eventFormatType; ++ subValue->registryMsgIds = msgIds; ++ subValue->registryPrefixes = regPrefixes; ++ subValue->metricReportDefinitions = mrdsArray; + + std::string id = + redfish::EventServiceManager::getInstance().addSubscription(subValue, +diff --git a/redfish-core/include/error_messages.hpp b/redfish-core/include/error_messages.hpp +index 3d11cc4..90084e3 100644 +--- a/redfish-core/include/error_messages.hpp ++++ b/redfish-core/include/error_messages.hpp +@@ -971,6 +971,15 @@ nlohmann::json mutualExclusiveProperties(const std::string& arg1, + void mutualExclusiveProperties(crow::Response& res, const std::string& arg1, + const std::string& arg2); + ++/** ++ * @brief Formats InvalidQueryFilter message into JSON ++ * Message body: "The requested URL contains the invalid query filters" ++ * ++ * @returns Message InvalidQueryFilter formatted to JSON */ ++nlohmann::json invalidQueryFilter(); ++ ++void invalidQueryFilter(crow::Response& res); ++ + } // namespace messages + + } // namespace redfish +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index dd833ce..861f4cb 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -55,6 +55,11 @@ static constexpr const char* eventServiceFile = + static constexpr const uint8_t maxNoOfSubscriptions = 20; + static constexpr const uint8_t maxNoOfSSESubscriptions = 10; + ++static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = { ++ eventFormatType, metricReportFormatType}; ++static constexpr const std::array<const char*, 2> supportedRegPrefixes = { ++ "OpenBMC", "TaskEvent"}; ++ + #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES + static std::optional<boost::asio::posix::stream_descriptor> inotifyConn; + static constexpr const char* redfishEventLogDir = "/var/log"; +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 249e594..6f01707 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -21,11 +21,6 @@ + + namespace redfish + { +- +-static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = { +- eventFormatType, metricReportFormatType}; +-static constexpr const std::array<const char*, 3> supportedRegPrefixes = { +- "Base", "OpenBMC", "TaskEvent"}; + static constexpr const std::array<const char*, 3> supportedRetryPolicies = { + "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; + +diff --git a/redfish-core/src/error_messages.cpp b/redfish-core/src/error_messages.cpp +index 9c28e8f..2394398 100644 +--- a/redfish-core/src/error_messages.cpp ++++ b/redfish-core/src/error_messages.cpp +@@ -2173,6 +2173,32 @@ void mutualExclusiveProperties(crow::Response& res, const std::string& arg1, + addMessageToErrorJson(res.jsonValue, mutualExclusiveProperties(arg1, arg2)); + } + ++/** ++ * @internal ++ * @brief Formats InvalidQueryFilter into JSON ++ * ++ * See header file for more information ++ * @endinternal ++ */ ++nlohmann::json invalidQueryFilter() ++{ ++ return nlohmann::json{ ++ {"@odata.type", "#Message.v1_0_0.Message"}, ++ {"MessageId", "Base.1.5.0.InvalidQueryFilter"}, ++ {"Message", "The requested url contains the invalid query filter."}, ++ {"MessageArgs", nlohmann::json::array()}, ++ {"Severity", "Warning"}, ++ {"Resolution", ++ "Ensure the correct query filter is specified in requested url " ++ "and resubmit the request."}}; ++} ++ ++void invalidQueryFilter(crow::Response& res) ++{ ++ res.result(boost::beast::http::status::bad_request); ++ addMessageToErrorJson(res.jsonValue, invalidQueryFilter()); ++} ++ + } // namespace messages + + } // namespace redfish +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0007-EventService-Log-events-for-subscription-actions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0007-EventService-Log-events-for-subscription-actions.patch new file mode 100644 index 000000000..3be65ee2a --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0007-EventService-Log-events-for-subscription-actions.patch @@ -0,0 +1,132 @@ +From b8eb53886106e44e3668857b13f8642d2ad3cfbf Mon Sep 17 00:00:00 2001 +From: AppaRao Puli <apparao.puli@linux.intel.com> +Date: Fri, 27 Aug 2021 16:02:01 +0000 +Subject: [PATCH] EventService: Log events for subscription actions + +Log redfish event for below 3 actions + - Add new subscription + - Update existing subscription properties + - Delete existing subscription +in order to notify the subscribed clients on the subscription related +information. + +Modified method name accordingly to indicate the clear purpose and +added updateSubscription method with subscription id param +to log event for subscription update. + +Tested: + - Performed all the above actions and verified the redfish event + messages are logged. + +Change-Id: I3745fa6357bd215379781a9818d9acc02a853d79 +Signed-off-by: AppaRao Puli <apparao.puli@intel.com> +Signed-off-by: Ayushi Smriti <smriti.ayushi@intel.com> +--- + .../include/event_service_manager.hpp | 35 ++++++++++++++++--- + redfish-core/lib/event_service.hpp | 2 +- + 2 files changed, 32 insertions(+), 5 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index c3e7f61..e9bdbfa 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -21,6 +21,7 @@ + #include "registries/task_event_message_registry.hpp" + + #include <sys/inotify.h> ++#include <systemd/sd-journal.h> + + #include <boost/asio/io_context.hpp> + #include <boost/beast/core/span.hpp> +@@ -788,7 +789,7 @@ class EventServiceManager + } + } + +- void updateSubscriptionData() ++ void persistSubscriptionData() + { + persistent_data::EventServiceStore::getInstance() + .eventServiceConfig.enabled = serviceEnabled; +@@ -835,7 +836,7 @@ class EventServiceManager + + if (updateConfig) + { +- updateSubscriptionData(); ++ persistSubscriptionData(); + } + + if (updateRetryCfg) +@@ -947,7 +948,7 @@ class EventServiceManager + + if (updateFile) + { +- updateSubscriptionData(); ++ persistSubscriptionData(); + } + + #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES +@@ -962,6 +963,13 @@ class EventServiceManager + + // Set Subscription ID for back trace + subValue->setSubscriptionId(id); ++ ++ /* Log event for subscription addition */ ++ sd_journal_send("MESSAGE=Event subscription added(Id: %s)", id.c_str(), ++ "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.EventSubscriptionAdded", ++ "REDFISH_MESSAGE_ARGS=%s", id.c_str(), NULL); ++ + return id; + } + +@@ -986,7 +994,14 @@ class EventServiceManager + persistent_data::EventServiceStore::getInstance() + .subscriptionsConfigMap.erase(obj2); + updateNoOfSubscribersCount(); +- updateSubscriptionData(); ++ ++ persistSubscriptionData(); ++ /* Log event for subscription delete. */ ++ sd_journal_send("MESSAGE=Event subscription removed.(Id = %s)", ++ id.c_str(), "PRIORITY=%i", LOG_INFO, ++ "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.EventSubscriptionRemoved", ++ "REDFISH_MESSAGE_ARGS=%s", id.c_str(), NULL); + } + } + +@@ -1008,6 +1023,18 @@ class EventServiceManager + } + } + ++ void updateSubscription(const std::string& id) ++ { ++ persistSubscriptionData(); ++ ++ /* Log event for subscription update. */ ++ sd_journal_send("MESSAGE=Event subscription updated.(Id = %s)", ++ id.c_str(), "PRIORITY=%i", LOG_INFO, ++ "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.EventSubscriptionUpdated", ++ "REDFISH_MESSAGE_ARGS=%s", id.c_str(), NULL); ++ } ++ + size_t getNumberOfSubscriptions() + { + return subscriptionsMap.size(); +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 9def549..6a8421f 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -617,7 +617,7 @@ inline void requestRoutesEventDestination(App& app) + subValue->updateRetryPolicy(); + } + +- EventServiceManager::getInstance().updateSubscriptionData(); ++ EventServiceManager::getInstance().updateSubscription(param); + }); + BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") + // The below privilege is wrong, it should be ConfigureManager OR +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch new file mode 100644 index 000000000..84ceb4ba8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch @@ -0,0 +1,85 @@ +From 05fdea2bb8e486b058d137a067ce1f5c885d2a96 Mon Sep 17 00:00:00 2001 +From: Nitin Wankhade <nitinx.arunrao.wankhade@intel.com> +Date: Mon, 28 Jun 2021 19:59:57 +0000 +Subject: [PATCH] Add checks on Event Subscription input parameters + +There is no check on the size of input parameters(Context, +Destination and Header) during Event Subscription.This +creates out of memory situation. +This commit checks for the size of input parameters and +rejects if it is exceeding the input size limits. + +Tested + - Validated using POST on Event Subscription. + - When Context, Destination and Headers were too long, + received a error message denoting the same. + +Change-Id: Iec2cd766c0e137b72706fc2da468d4fefd8fbaae +Signed-off-by: Nitin Wankhade <nitinx.arunrao.wankhade@intel.com> +--- + redfish-core/lib/event_service.hpp | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 52b01e5..f8a1671 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -19,6 +19,10 @@ + #include <app.hpp> + #include <registries/privilege_registry.hpp> + ++#define MAX_CONTEXT_SIZE 256 ++#define MAX_DESTINATION_SIZE 1024 ++#define MAX_HEADER_SIZE 8096 ++ + namespace redfish + { + static constexpr const std::array<const char*, 3> supportedRetryPolicies = { +@@ -220,6 +224,12 @@ inline void requestRoutesEventDestinationCollection(App& app) + return; + } + ++ if (destUrl.size() > MAX_DESTINATION_SIZE) ++ { ++ messages::propertySizeExceeded(asyncResp->res, "Destination"); ++ return; ++ } ++ + if (regPrefixes && msgIds) + { + if (regPrefixes->size() && msgIds->size()) +@@ -330,11 +340,31 @@ inline void requestRoutesEventDestinationCollection(App& app) + + if (context) + { ++ if (context->size() > MAX_CONTEXT_SIZE) ++ { ++ messages::propertySizeExceeded(asyncResp->res, "Context"); ++ return; ++ } + subValue->customText = *context; + } + + if (headers) + { ++ size_t cumulativeLen = 0; ++ ++ for (nlohmann::json& itr : *headers) ++ { ++ std::string hdr{itr.dump( ++ -1, ' ', true, nlohmann::json::error_handler_t::replace)}; ++ cumulativeLen += hdr.length(); ++ ++ if (cumulativeLen > MAX_HEADER_SIZE) ++ { ++ messages::propertySizeExceeded(asyncResp->res, ++ "HttpHeaders"); ++ return; ++ } ++ } + subValue->httpHeaders = *headers; + } + +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0009-Restructure-Redifsh-EventLog-Transmit-code-flow.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0009-Restructure-Redifsh-EventLog-Transmit-code-flow.patch new file mode 100644 index 000000000..d1fe475f5 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0009-Restructure-Redifsh-EventLog-Transmit-code-flow.patch @@ -0,0 +1,225 @@ +From 542505dff60e3921b00b51acae882e207d46f1a6 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Date: Wed, 14 Jul 2021 14:13:11 +0000 +Subject: [PATCH] Restructure Redfish EventLog Transmit code flow + +In the current implementation: + 1. When Event service is disabled and enabled back after a while, + all the logs during this time span between disable to enable + are dumped to the Event listener. + 2. When two events occur very close (in terms of microseconds) + and they trigger two different iNotify events, the listener + receives both of these events with the same Event ID. + +This occurs as the last log time stamp read from redfish file +and previous time stamp used to generate Event ID's are not +being updated continuously. + +This commit fixes this issue by tweaking the logic to continuously +update the time stamp values (even during when Event Service is +disabled), and also replaces multiple string operations with file +operations. i.e. Instead of looping through the entire Redfish file +until last time stamp read is reached, this fix makes use of +fseek to get to the last read position. + +Tested: + - Subscribed to an event and successfully received Event Logs. + - No Event Logs were received when Event Service was disabled. + - No Dump of past Events after Event Service was enabled. + - Redfish Validator passed + +Change-Id: I87136bee78076b1b3219930813702b3b9d20c157 +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + .../include/event_service_manager.hpp | 108 ++++++++++++------ + 1 file changed, 76 insertions(+), 32 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index e9bdbfa..5c4de70 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -134,15 +134,10 @@ static const Message* formatMessage(const std::string_view& messageID) + + namespace event_log + { +-inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID, +- const bool firstEntry = true) ++inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID) + { + static time_t prevTs = 0; + static int index = 0; +- if (firstEntry) +- { +- prevTs = 0; +- } + + // Get the entry timestamp + std::time_t curTs = 0; +@@ -621,6 +616,7 @@ class EventServiceManager + } + + std::string lastEventTStr; ++ std::streampos redfishLogFilePosition{0}; + size_t noOfEventLogSubscribers{0}; + size_t noOfMetricReportSubscribers{0}; + std::shared_ptr<sdbusplus::bus::match::match> matchTelemetryMonitor; +@@ -1163,7 +1159,22 @@ class EventServiceManager + #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES + void cacheLastEventTimestamp() + { +- lastEventTStr.clear(); ++ // Control comes here when : ++ // 1. Subscription is added and lastEventTStr is empty ++ // 2. lastEventTStr is empty ++ // 3. When a new Redfish file is created ++ ++ if (!lastEventTStr.empty()) ++ { ++ // Control would be here when Redfish file is created. ++ // Reset File Position as new file is created ++ redfishLogFilePosition = 0; ++ return; ++ } ++ ++ // Open the redfish file and read till the last record to get the ++ // last event's time stamp. ++ + std::ifstream logStream(redfishEventLogFile); + if (!logStream.good()) + { +@@ -1171,27 +1182,44 @@ class EventServiceManager + return; + } + std::string logEntry; ++ std::string prev_logEntry; + while (std::getline(logStream, logEntry)) + { +- size_t space = logEntry.find_first_of(' '); +- if (space == std::string::npos) +- { +- // Shouldn't enter here but lets skip it. +- BMCWEB_LOG_DEBUG << "Invalid log entry found."; +- continue; +- } +- lastEventTStr = logEntry.substr(0, space); ++ prev_logEntry = logEntry; ++ redfishLogFilePosition = logStream.tellg(); ++ } ++ ++ if (prev_logEntry.empty()) ++ { ++ BMCWEB_LOG_ERROR ++ << "Last Event Time Stamp Caching Failed : No Records"; ++ redfishLogFilePosition = 0; ++ return; ++ } ++ ++ size_t space = prev_logEntry.find_first_of(' '); ++ if (space == std::string::npos) ++ { ++ // Shouldn't enter here but lets skip it. ++ BMCWEB_LOG_DEBUG << "Invalid log entry found."; ++ BMCWEB_LOG_ERROR << "Last Event Time Stamp Caching Failed"; ++ return; + } ++ lastEventTStr = prev_logEntry.substr(0, space); + BMCWEB_LOG_DEBUG << "Last Event time stamp set: " << lastEventTStr; ++ BMCWEB_LOG_DEBUG << "Next Log Position : " << redfishLogFilePosition; + } + + void readEventLogsFromFile() + { +- if (!serviceEnabled || !noOfEventLogSubscribers) ++ if (lastEventTStr.empty()) + { +- BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; +- return; ++ // Shouldn't ideally enter here. ++ // Last event Time stamp would be set by now. ++ // Just incase of any failures before. ++ cacheLastEventTimestamp(); + } ++ + std::ifstream logStream(redfishEventLogFile); + if (!logStream.good()) + { +@@ -1201,27 +1229,21 @@ class EventServiceManager + + std::vector<EventLogObjectsType> eventRecords; + +- bool startLogCollection = false; +- bool firstEntry = true; +- + std::string logEntry; ++ ++ // Get the read pointer to the next log to be read. ++ logStream.seekg(redfishLogFilePosition); ++ + while (std::getline(logStream, logEntry)) + { +- if (!startLogCollection && !lastEventTStr.empty()) +- { +- if (boost::starts_with(logEntry, lastEventTStr)) +- { +- startLogCollection = true; +- } +- continue; +- } ++ // Update Pointer position ++ redfishLogFilePosition = logStream.tellg(); + + std::string idStr; +- if (!event_log::getUniqueEntryID(logEntry, idStr, firstEntry)) ++ if (!event_log::getUniqueEntryID(logEntry, idStr)) + { + continue; + } +- firstEntry = false; + + std::string timestamp; + std::string messageID; +@@ -1233,6 +1255,16 @@ class EventServiceManager + continue; + } + ++ lastEventTStr = timestamp; ++ ++ if (!serviceEnabled || !noOfEventLogSubscribers) ++ { ++ // If Service is not enabled, no need to compute ++ // the remaining items below. ++ // But, Loop must continue to keep track of Timestamp ++ continue; ++ } ++ + std::string registryName; + std::string messageKey; + event_log::getRegistryAndMessageKey(messageID, registryName, +@@ -1242,11 +1274,23 @@ class EventServiceManager + continue; + } + +- lastEventTStr = timestamp; + eventRecords.emplace_back(idStr, timestamp, messageID, registryName, + messageKey, messageArgs); + } + ++ if (!serviceEnabled || !noOfEventLogSubscribers) ++ { ++ BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; ++ return; ++ } ++ ++ if (eventRecords.empty()) ++ { ++ // No Records to send ++ BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; ++ return; ++ } ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0010-Remove-Terminated-Event-Subscriptions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0010-Remove-Terminated-Event-Subscriptions.patch new file mode 100644 index 000000000..9af5a066b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0010-Remove-Terminated-Event-Subscriptions.patch @@ -0,0 +1,258 @@ +From f665ba085bb2310f008b7534f827fb401ad973c2 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Date: Tue, 12 Oct 2021 08:19:51 +0000 +Subject: [PATCH] Delete/Remove Terminated Event Subscription(s) + +Added functionality to delete/remove event subscription(s) which are +configured to Terminate after retries. + +Currently, when an Event is subscribed with Retry Policy as +"TerminateAfterRetries", the state of the connection is set to +"Terminated" after retrying, but the Subscription is not removed. +This commit adds the functionality to detect terminated connection and +remove the respective subscription. + +Tested: + - Created a Subscription with + DeliveryRetryPolicy: "TerminateAfterRetries" + - Received Events successfully on Event listener + - Once the Event listener was stopped, the Subscription was + removed/deleted after retries. + +Change-Id: If447acb2db74fb29a5d1cfe6194b77cda82bc8a1 +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + http/http_client.hpp | 43 +++++++++++++++---- + .../include/event_service_manager.hpp | 36 ++++++++++++++++ + 2 files changed, 70 insertions(+), 9 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index 5e7ff47..54ae2c3 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -55,6 +55,8 @@ enum class ConnState + closeInProgress, + closed, + suspended, ++ terminate, ++ terminateInProgress, + terminated, + abortConnection, + retry +@@ -288,7 +290,14 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + void doClose() + { +- state = ConnState::closeInProgress; ++ if (state == ConnState::terminate) ++ { ++ state = ConnState::terminateInProgress; ++ } ++ else if (state != ConnState::suspended) ++ { ++ state = ConnState::closeInProgress; ++ } + + // Set the timeout on the tcp stream socket for the async operation + conn.expires_after(std::chrono::seconds(30)); +@@ -318,8 +327,11 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + self->conn.close(); + +- if ((self->state != ConnState::suspended) && +- (self->state != ConnState::terminated)) ++ if (self->state == ConnState::terminateInProgress) ++ { ++ self->state = ConnState::terminated; ++ } ++ else if (self->state == ConnState::closeInProgress) + { + self->state = ConnState::closed; + self->handleConnState(); +@@ -341,8 +353,11 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + conn.close(); + +- if ((state != ConnState::suspended) && +- (state != ConnState::terminated)) ++ if (state == ConnState::terminateInProgress) ++ { ++ state = ConnState::terminated; ++ } ++ else if (state == ConnState::closeInProgress) + { + state = ConnState::closed; + handleConnState(); +@@ -365,8 +380,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction; + if (retryPolicyAction == "TerminateAfterRetries") + { +- // TODO: delete subscription +- state = ConnState::terminated; ++ state = ConnState::terminate; + } + if (retryPolicyAction == "SuspendRetries") + { +@@ -423,6 +437,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + case ConnState::sendInProgress: + case ConnState::recvInProgress: + case ConnState::closeInProgress: ++ case ConnState::terminateInProgress: + { + BMCWEB_LOG_DEBUG << "Async operation is already in progress"; + break; +@@ -439,7 +454,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + break; + } + case ConnState::suspended: +- case ConnState::terminated: ++ case ConnState::terminate: + { + doClose(); + break; +@@ -506,7 +521,8 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + void sendData(const std::string& data) + { +- if ((state == ConnState::suspended) || (state == ConnState::terminated)) ++ if ((state == ConnState::terminate) || ++ (state == ConnState::terminated) || (state == ConnState::suspended)) + { + return; + } +@@ -524,6 +540,15 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + return; + } + ++ bool isTerminated() ++ { ++ if (state == ConnState::terminated) ++ { ++ return true; ++ } ++ return false; ++ } ++ + void addHeaders( + const std::vector<std::pair<std::string, std::string>>& httpHeaders) + { +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 6f60a31..363adb0 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -591,6 +591,14 @@ class Subscription : public persistent_data::UserSubscription + return std::nullopt; + } + ++ bool isTerminated() ++ { ++ if (conn != nullptr) ++ return conn->isTerminated(); ++ ++ return false; ++ } ++ + private: + std::shared_ptr<crow::SseConnection> sseConn = nullptr; + uint64_t eventSeqNum; +@@ -847,6 +855,22 @@ class EventServiceManager + } + } + ++ void deleteTerminatedSubcriptions() ++ { ++ boost::container::flat_map<std::string, ++ std::shared_ptr<Subscription>>::iterator it = ++ subscriptionsMap.begin(); ++ while (it != subscriptionsMap.end()) ++ { ++ std::shared_ptr<Subscription> entry = it->second; ++ if (entry->isTerminated()) ++ { ++ subscriptionsMap.erase(it); ++ } ++ it++; ++ } ++ } ++ + void updateNoOfSubscribersCount() + { + size_t eventLogSubCount = 0; +@@ -881,6 +905,7 @@ class EventServiceManager + + std::shared_ptr<Subscription> getSubscription(const std::string& id) + { ++ deleteTerminatedSubcriptions(); + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { +@@ -971,6 +996,7 @@ class EventServiceManager + + bool isSubscriptionExist(const std::string& id) + { ++ deleteTerminatedSubcriptions(); + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { +@@ -1033,6 +1059,7 @@ class EventServiceManager + + size_t getNumberOfSubscriptions() + { ++ deleteTerminatedSubcriptions(); + return subscriptionsMap.size(); + } + +@@ -1049,6 +1076,7 @@ class EventServiceManager + + std::vector<std::string> getAllIDs() + { ++ deleteTerminatedSubcriptions(); + std::vector<std::string> idList; + for (const auto& it : subscriptionsMap) + { +@@ -1059,6 +1087,7 @@ class EventServiceManager + + bool isDestinationExist(const std::string& destUrl) + { ++ deleteTerminatedSubcriptions(); + for (const auto& it : subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +@@ -1073,6 +1102,7 @@ class EventServiceManager + + void sendTestEventLog() + { ++ deleteTerminatedSubcriptions(); + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +@@ -1100,6 +1130,8 @@ class EventServiceManager + } + eventRecord.push_back(eventMessage); + ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +@@ -1143,6 +1175,8 @@ class EventServiceManager + } + void sendBroadcastMsg(const std::string& broadcastMsg) + { ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +@@ -1291,6 +1325,8 @@ class EventServiceManager + return; + } + ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch new file mode 100644 index 000000000..585f7bf09 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch @@ -0,0 +1,141 @@ +From 5b87bb61b58e92a8c5af37a7959347747409a65c Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +Date: Thu, 14 Oct 2021 02:56:11 +0530 +Subject: [PATCH] Fix bmcweb crash while deleting terminated subscriptions + +This commit fixes bmcweb crash while deleting the terminated +subscriptions. In the earlier implementation, detection of subscription +to be deleted and the deletion(erase) was happening in the same loop. +Due to this, if the Subscription to be deleted is the last one in the +list, the loop will enter into infinite loop. The fix is to keep the +detection and deletion loop separate. +Also, this commit adds code to : + - Delete from persistent storage + - Add journal entry for deleted entry + - update number of subcribers and update persistent storage. + +Apart from this, this commit also moves the retry timer check to the top +to avoid multiple calls to close when the retry count is 3 and timer is +running. + +Tested: + - Checked journal logs to confirm each retry is actually spanned to be + 30 secs + - Verified Journal entry for deleted subscription after retires. + - Verified Event service functionality by making three subscriptions: + retry for ever, terminate after retires and suspend after retries. + +Change-Id: I425a6c749923ce86c457a36394deb0fbbee232db +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + http/http_client.hpp | 11 ++-- + .../include/event_service_manager.hpp | 59 ++++++++++++++++--- + 2 files changed, 58 insertions(+), 12 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index 54ae2c3..162cb09 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -367,6 +367,12 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + + void waitAndRetry() + { ++ if (runningTimer) ++ { ++ BMCWEB_LOG_DEBUG << "Retry timer is already running."; ++ return; ++ } ++ + if (retryCount >= maxRetryAttempts) + { + BMCWEB_LOG_ERROR << "Maximum number of retries reached."; +@@ -393,11 +399,6 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + return; + } + +- if (runningTimer) +- { +- BMCWEB_LOG_DEBUG << "Retry timer is already running."; +- return; +- } + runningTimer = true; + + retryCount++; +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 363adb0..7af7a4d 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -857,18 +857,63 @@ class EventServiceManager + + void deleteTerminatedSubcriptions() + { +- boost::container::flat_map<std::string, +- std::shared_ptr<Subscription>>::iterator it = +- subscriptionsMap.begin(); +- while (it != subscriptionsMap.end()) ++ BMCWEB_LOG_ERROR << "Map size Before Delete : " ++ << subscriptionsMap.size(); ++ ++ std::vector<std::string> deleteIds; ++ ++ // Determine Subscription ID's to be deleted. ++ for (const auto& it : subscriptionsMap) + { +- std::shared_ptr<Subscription> entry = it->second; ++ std::shared_ptr<Subscription> entry = it.second; + if (entry->isTerminated()) + { +- subscriptionsMap.erase(it); ++ deleteIds.emplace_back(it.first); ++ } ++ } ++ ++ // Delete the Terminated Subcriptions ++ for (std::string& id : deleteIds) ++ { ++ auto map1 = subscriptionsMap.find(id); ++ if (map1 != subscriptionsMap.end()) ++ { ++ subscriptionsMap.erase(map1); ++ auto map2 = persistent_data::EventServiceStore::getInstance() ++ .subscriptionsConfigMap.find(id); ++ if (map2 != persistent_data::EventServiceStore::getInstance() ++ .subscriptionsConfigMap.end()) ++ { ++ persistent_data::EventServiceStore::getInstance() ++ .subscriptionsConfigMap.erase(map2); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "Couldn't find ID: " << id ++ << " in subscriptionsConfigMap"; ++ } ++ ++ /* Log event for subscription delete. */ ++ sd_journal_send("MESSAGE=Event subscription removed.(Id = %s)", ++ id.c_str(), "PRIORITY=%i", LOG_INFO, ++ "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.EventSubscriptionRemoved", ++ "REDFISH_MESSAGE_ARGS=%s", id.c_str(), NULL); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "Couldn't find ID: " << id ++ << " in subscriptionsMap"; + } +- it++; + } ++ if (deleteIds.size()) ++ { ++ updateNoOfSubscribersCount(); ++ persistSubscriptionData(); ++ } ++ ++ BMCWEB_LOG_ERROR << "Map size After Delete : " ++ << subscriptionsMap.size(); + } + + void updateNoOfSubscribersCount() +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README new file mode 100644 index 000000000..c09967456 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README @@ -0,0 +1,34 @@ +Eventservice specific patches: Temporary pulling down +the upstream patches. These will be remove as soon as +thee gets merged upstream. + +Upstream revision information: + - EventService : Add unmerged changes for http retry support (Downstream patch) + file://eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch + + - EventService: https client support + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/31735/40 (Rebased on latest bmcweb) + + - Add Server-Sent-Events support + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41258/9 + + - Add SSE style subscription support to eventservice + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41319/10 + + - Add EventService SSE filter support + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41349/7 (Modified boost::urls::query_params_view to boost::urls::url_view::params_type) + + - EventService Log events for subscription actions (Downstream patch) + file://eventservice/0007-EventService-Log-events-for-subscription-actions.patch + + - Add checks on Event-Subscription input parameters (Downstream patch) + file://eventservice//0008-Add-checks-on-Event-Subscription-input-parameters.patch + + - Restructure Redifsh EventLog Transmit code flow + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44449/3 + + - Remove Terminated Event Subscriptions (Downstream patch) + file://eventservice/0010-Remove-Terminated-Event-Subscriptions.patch + + - Fix bmcweb crash while deleting terminated subscriptions (Downstream patch) + file://eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch |