diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice')
12 files changed, 2847 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..5cb5c538c --- /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,163 @@ +From d5ade2d2032d7bb0c17c92e957c2a4f6e77af2ee Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +Date: Mon, 6 Dec 2021 19:49:01 +0000 +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: I43e68eeffb8d69c289dd306c1c7cafc87ad766a0 +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + http/http_client.hpp | 32 ++++++++++++++++--- + .../include/event_service_manager.hpp | 26 +++++++++------ + redfish-core/lib/event_service.hpp | 1 + + 3 files changed, 45 insertions(+), 14 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index 5f352d3..f0152db 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -188,6 +188,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; +@@ -379,15 +390,17 @@ 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 boost::beast::http::fields& httpHeader) : ++ const std::string& destUri) : + conn(ioc), +- timer(ioc), +- req(boost::beast::http::verb::post, destUri, 11, "", httpHeader), +- subId(id), host(destIP), port(destPort) ++ timer(ioc), req(boost::beast::http::verb::post, destUri, 11), subId(id), ++ host(destIP), port(destPort) + { ++ // 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) +@@ -408,6 +421,15 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + } + ++ void setHeaders(const boost::beast::http::fields& httpHeaders) ++ { ++ // Set custom headers ++ for (const auto& header : httpHeaders) ++ { ++ req.set(header.name(), header.value()); ++ } ++ } ++ + void setRetryConfig(const uint32_t retryAttempts, + const uint32_t retryTimeoutInterval) + { +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index e879f9e..75380dc 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -370,7 +370,10 @@ class Subscription : public persistent_data::UserSubscription + eventSeqNum(1), + host(inHost), port(inPort), path(inPath), uriProto(inUriProto) + { +- // Subscription constructor ++ // create the HttpClient connection ++ conn = std::make_shared<crow::HttpClient>( ++ crow::connections::systemBus->get_io_context(), id, host, port, ++ path); + } + + Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) : +@@ -391,17 +394,12 @@ class Subscription : public persistent_data::UserSubscription + return false; + } + +- if (conn == nullptr) ++ if (conn != nullptr) + { +- // create the HttpClient connection +- conn = std::make_shared<crow::HttpClient>( +- crow::connections::systemBus->get_io_context(), id, host, port, +- path, httpHeaders); ++ conn->sendData(msg); ++ eventSeqNum++; + } + +- conn->sendData(msg); +- eventSeqNum++; +- + if (sseConn != nullptr) + { + sseConn->sendData(eventSeqNum, msg); +@@ -548,6 +546,14 @@ class Subscription : public persistent_data::UserSubscription + } + } + ++ void updatehttpHeaders() ++ { ++ if (conn != nullptr) ++ { ++ conn->setHeaders(httpHeaders); ++ } ++ } ++ + uint64_t getEventSeqNum() const + { + return eventSeqNum; +@@ -661,6 +667,7 @@ class EventServiceManager + // Update retry configuration. + subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); + subValue->updateRetryPolicy(); ++ subValue->updatehttpHeaders(); + } + } + +@@ -915,6 +922,7 @@ class EventServiceManager + // Update retry configuration. + subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); + subValue->updateRetryPolicy(); ++ subValue->updatehttpHeaders(); + + return id; + } +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 9ce2f05..0903874 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -619,6 +619,7 @@ inline void requestRoutesEventDestination(App& app) + } + } + subValue->httpHeaders = fields; ++ subValue->updatehttpHeaders(); + } + + if (retryPolicy) +-- +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..9f51d23d3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch @@ -0,0 +1,448 @@ +From 0dc82b56bae36aee8ebc1923e909e3ce74f6fdbc Mon Sep 17 00:00:00 2001 +From: AppaRao Puli <apparao.puli@linux.intel.com> +Date: Mon, 6 Dec 2021 21:39:05 +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: I480085344ba7bed6ec0d94876eda1d252e51cb45 +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_client.hpp | 309 ++++++++++++------ + .../include/event_service_manager.hpp | 2 +- + 2 files changed, 204 insertions(+), 107 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index e69d397..7328eae 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 <boost/circular_buffer.hpp> + #include <include/async_resolve.hpp> +@@ -44,6 +45,8 @@ enum class ConnState + resolveFailed, + connectInProgress, + connectFailed, ++ handshakeInProgress, ++ handshakeFailed, + connected, + sendInProgress, + sendFailed, +@@ -62,7 +65,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; +@@ -110,25 +115,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.address().to_string() +- << " 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.address().to_string(); ++ ++ BMCWEB_LOG_DEBUG << "SSL Handshake successfull"; + self->state = ConnState::connected; + self->handleConnState(); + }); +@@ -141,124 +173,182 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + 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; + +- parser.emplace(std::piecewise_construct, std::make_tuple()); +- parser->body_limit(httpReadBodyLimit); ++ 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(); + +- // 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) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "recvMessage() failed: " +- << ec.message(); +- self->state = ConnState::recvFailed; +- self->handleConnState(); +- return; +- } +- BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " +- << bytesTransferred; +- BMCWEB_LOG_DEBUG << "recvMessage() data: " +- << self->parser->get().body(); ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } + +- // 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; +- } ++ 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; ++ 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. Header Response Code: " +- << respCode; +- self->state = ConnState::recvFailed; +- self->handleConnState(); +- return; +- } ++ // 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; ++ // 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; ++ } + +- // 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(); +- }); +- } ++ 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; +- 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) ++ // Set the timeout on the tcp stream socket for the async operation ++ conn.expires_after(std::chrono::seconds(30)); ++ if (sslConn) + { +- BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); +- return; ++ sslConn->async_shutdown([self = shared_from_this()]( ++ const boost::system::error_code ec) { ++ if (ec) ++ { ++ // 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(); ++ } ++ } ++ else ++ { ++ BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; ++ } ++ self->conn.close(); ++ ++ if ((self->state != ConnState::suspended) && ++ (self->state != ConnState::terminated)) ++ { ++ self->state = ConnState::closed; ++ self->handleConnState(); ++ } ++ }); + } +- 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 +420,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 +447,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: +@@ -392,7 +484,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), subId(id), + host(destIP), port(destPort) +@@ -403,6 +496,10 @@ 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) +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index bc97219..64cb43a 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -372,7 +372,7 @@ class Subscription : public persistent_data::UserSubscription + // create the HttpClient connection + 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.25.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..bce9fc939 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch @@ -0,0 +1,471 @@ +From 6134487ccc46ace1943184f40c42184e3aa85881 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 | 282 +++++++++++++++++++++++++++++++++++++ + 4 files changed, 365 insertions(+), 5 deletions(-) + create mode 100644 http/server_sent_event.hpp + +diff --git a/http/http_connection.hpp b/http/http_connection.hpp +index a27ec26..882a7a6 100644 +--- a/http/http_connection.hpp ++++ b/http/http_connection.hpp +@@ -374,11 +374,13 @@ class Connection : + self->completeRequest(thisRes); + }); + +- 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 3c2a3f9..679dab2 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>; + +@@ -173,8 +178,8 @@ struct Response + + private: + bool completed = false; +- std::function<void(Response&)> completeRequestHandler; + std::function<bool()> isAliveHelper; ++ std::function<void(Response&)> 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 250cd33..552e1cf 100644 +--- a/http/routing.hpp ++++ b/http/routing.hpp +@@ -7,6 +7,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" +@@ -407,6 +408,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 + { +@@ -419,6 +482,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,282 @@ ++#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.setCompleteRequestHandler(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.setCompleteRequestHandler([this, self(shared_from_this())]( ++ crow::Response& thisRes) { ++ boost::ignore_unused(thisRes); ++ 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.setCompleteRequestHandler(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..40b46e74a --- /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,677 @@ +From 30088cc01b8068419311b171dc51c8ff11fbc185 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: Nitin Wankhade <nitinx.arunrao.wankhade@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 | 111 +++++-- + redfish-core/include/server_sent_events.hpp | 286 ------------------ + redfish-core/lib/event_service.hpp | 8 +- + src/webserver_main.cpp | 2 + + 6 files changed, 165 insertions(+), 319 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 9986e07..0b7b341 100644 +--- a/http/http_connection.hpp ++++ b/http/http_connection.hpp +@@ -379,7 +379,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 64cb43a..c64bb59 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -23,6 +23,7 @@ + #include <sys/inotify.h> + + #include <boost/asio/io_context.hpp> ++#include <boost/beast/core/span.hpp> + #include <boost/container/flat_map.hpp> + #include <dbus_utility.hpp> + #include <error_messages.hpp> +@@ -30,9 +31,10 @@ + #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> +@@ -48,9 +50,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 registries + { + inline std::span<const MessageEntry> +@@ -70,24 +90,6 @@ inline std::span<const MessageEntry> + } + return {openbmc::registry}; + } +-} // namespace 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 registries +-{ + static const Message* + getMsgFromRegistry(const std::string& messageKey, + const std::span<const MessageEntry>& registry) +@@ -375,11 +377,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; + +@@ -396,13 +396,14 @@ class Subscription : public persistent_data::UserSubscription + if (conn != nullptr) + { + conn->sendData(msg); +- eventSeqNum++; + } + + if (sseConn != nullptr) + { +- sseConn->sendData(eventSeqNum, msg); ++ sseConn->sendEvent(std::to_string(eventSeqNum), msg); + } ++ ++ eventSeqNum++; + return true; + } + +@@ -558,14 +559,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 +@@ -923,6 +949,8 @@ class EventServiceManager + subValue->updateRetryPolicy(); + subValue->updatehttpHeaders(); + ++ // Set Subscription ID for back trace ++ subValue->setSubscriptionId(id); + return id; + } + +@@ -947,11 +975,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 13840d3..0000000 +--- a/redfish-core/include/server_sent_events.hpp ++++ /dev/null +@@ -1,286 +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/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{SseConnState::startInit}; +- int retryCount{0}; +- int maxRetryAttempts{5}; +- +- 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; +- } +- } +- } +- +- 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) +- { +- 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 c7392bd..2181346 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -40,8 +40,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/") +@@ -54,6 +52,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", +@@ -92,9 +92,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 6bdce98..d8b4f4e 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 @@ static int run() + #endif + + #ifdef BMCWEB_ENABLE_REDFISH ++ redfish::eventservice_sse::requestRoutes(app); + redfish::requestRoutes(app); + redfish::RedfishService redfish(app); + +-- +2.25.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..816456cec --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch @@ -0,0 +1,326 @@ +From e207fca58bf0d05b160ea6735f4f576737d7a9cb 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 | 172 +++++++++++++++++- + 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 | 27 +++ + 5 files changed, 209 insertions(+), 9 deletions(-) + +diff --git a/include/eventservice_sse.hpp b/include/eventservice_sse.hpp +index 14daf00..5847766 100644 +--- a/include/eventservice_sse.hpp ++++ b/include/eventservice_sse.hpp +@@ -21,18 +21,182 @@ static bool createSubscription(std::shared_ptr<crow::SseConnection>& conn, + res.end(); + return false; + } ++#ifdef NEW_BOOST_URL ++ BMCWEB_LOG_DEBUG << "Request query param size: " ++ << req.urlView.params().size(); ++ ++ // EventService SSE supports only "$filter" query param. ++ if (req.urlView.params().size() > 1) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ std::string eventFormatType; ++ std::string queryFilters; ++ if (req.urlView.params().size()) ++ { ++ boost::urls::params_view::iterator it = ++ req.urlView.params().find("$filter"); ++ if (it == req.urlView.params().end()) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ queryFilters = std::string((*it).value); ++ } ++ else ++ { ++ eventFormatType = "Event"; ++ } ++#else + 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::query_params_view::iterator it = ++ req.urlParams.find("$filter"); ++ if (it == req.urlParams.end()) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ queryFilters = it->value(); ++ } ++ else ++ { ++ eventFormatType = "Event"; ++ } ++#endif ++ ++ 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::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ ++ // RegsitryPrefix and messageIds are mutuly exclusive as per redfish ++ // specification. ++ if (!regPrefixes.empty() && !msgIds.empty()) ++ { ++ messages::propertyValueConflict(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 std::span<const redfish::registries::MessageEntry> ++ registry = ++ redfish::registries::getRegistryFromPrefix(it); ++ ++ if (std::any_of( ++ registry.begin(), registry.end(), ++ [&id](const redfish::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 b3b2305..9b3ee49 100644 +--- a/redfish-core/include/error_messages.hpp ++++ b/redfish-core/include/error_messages.hpp +@@ -980,6 +980,15 @@ nlohmann::json invalidUpload(std::string_view arg1, std::string_view arg2); + void invalidUpload(crow::Response& res, std::string_view arg1, + std::string_view 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 c64bb59..e03dd9b 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -57,6 +57,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 2181346..e9ee78f 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -24,11 +24,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 381b8a9..cad145a 100644 +--- a/redfish-core/src/error_messages.cpp ++++ b/redfish-core/src/error_messages.cpp +@@ -1668,6 +1668,33 @@ nlohmann::json invalidUpload(std::string_view arg1, std::string_view arg2) + {"MessageSeverity", "Warning"}, + {"Resolution", "None."}}; + } ++ ++/** ++ * @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.25.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..3818b99ee --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0007-EventService-Log-events-for-subscription-actions.patch @@ -0,0 +1,133 @@ +From ec3349511d66a7136a054c1a4e7a6f24ea3b6722 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> +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@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 f2b7803..5483a74 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> +@@ -782,7 +783,7 @@ class EventServiceManager + } + } + +- void updateSubscriptionData() const ++ void persistSubscriptionData() + { + persistent_data::EventServiceStore::getInstance() + .eventServiceConfig.enabled = serviceEnabled; +@@ -829,7 +830,7 @@ class EventServiceManager + + if (updateConfig) + { +- updateSubscriptionData(); ++ persistSubscriptionData(); + } + + if (updateRetryCfg) +@@ -941,7 +942,7 @@ class EventServiceManager + + if (updateFile) + { +- updateSubscriptionData(); ++ persistSubscriptionData(); + } + + #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES +@@ -957,6 +958,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; + } + +@@ -981,7 +989,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); + } + } + +@@ -1003,6 +1018,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 ab85796..91ce22c 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -630,7 +630,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..65bfafdcc --- /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 a22fcc269c475b0ff8a80a6aa2c837b247eca907 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> +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + redfish-core/lib/event_service.hpp | 27 +++++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 88ebd01..373c873 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -22,6 +22,10 @@ + + #include <span> + ++#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 = { +@@ -229,6 +233,12 @@ inline void requestRoutesEventDestinationCollection(App& app) + return; + } + ++ if (destUrl.size() > MAX_DESTINATION_SIZE) ++ { ++ messages::propertySizeExceeded(asyncResp->res, "Destination"); ++ return; ++ } ++ + if (regPrefixes && msgIds) + { + if (!regPrefixes->empty() && !msgIds->empty()) +@@ -336,13 +346,30 @@ 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 (const nlohmann::json& headerChunk : *headers) + { ++ std::string hdr{headerChunk.dump( ++ -1, ' ', true, ++ nlohmann::json::error_handler_t::replace)}; ++ cumulativeLen += hdr.length(); ++ if (cumulativeLen > MAX_HEADER_SIZE) ++ { ++ messages::propertySizeExceeded(asyncResp->res, ++ "HttpHeaders"); ++ return; ++ } + for (const auto& item : headerChunk.items()) + { + const std::string* value = +-- +2.25.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..ede279868 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0010-Remove-Terminated-Event-Subscriptions.patch @@ -0,0 +1,267 @@ +From fc4b7e964f5f26e7af2eb19f120b1762b70b8179 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@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 | 48 +++++++++++++++---- + .../include/event_service_manager.hpp | 36 ++++++++++++++ + 2 files changed, 75 insertions(+), 9 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index 7328eae..db99faa 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -56,6 +56,8 @@ enum class ConnState + closeInProgress, + closed, + suspended, ++ terminate, ++ terminateInProgress, + terminated, + abortConnection, + retry +@@ -290,7 +292,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)); +@@ -320,8 +329,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(); +@@ -343,8 +355,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(); +@@ -367,8 +382,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") + { +@@ -424,6 +438,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; +@@ -440,11 +455,16 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + break; + } + case ConnState::suspended: +- case ConnState::terminated: ++ case ConnState::terminate: + { + doClose(); + break; + } ++ case ConnState::terminated: ++ { ++ BMCWEB_LOG_DEBUG << "Connection Terminated"; ++ break; ++ } + case ConnState::resolveFailed: + case ConnState::connectFailed: + case ConnState::handshakeFailed: +@@ -504,7 +524,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; + } +@@ -520,6 +541,15 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + } + ++ bool isTerminated() ++ { ++ if (state == ConnState::terminated) ++ { ++ return true; ++ } ++ return false; ++ } ++ + void setHeaders(const boost::beast::http::fields& httpHeaders) + { + // Set custom headers +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 5483a74..92abaa5 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -590,6 +590,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; +@@ -845,6 +853,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; +@@ -879,6 +903,7 @@ class EventServiceManager + + std::shared_ptr<Subscription> getSubscription(const std::string& id) + { ++ deleteTerminatedSubcriptions(); + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { +@@ -970,6 +995,7 @@ class EventServiceManager + + bool isSubscriptionExist(const std::string& id) + { ++ deleteTerminatedSubcriptions(); + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { +@@ -1032,6 +1058,7 @@ class EventServiceManager + + size_t getNumberOfSubscriptions() + { ++ deleteTerminatedSubcriptions(); + return subscriptionsMap.size(); + } + +@@ -1048,6 +1075,7 @@ class EventServiceManager + + std::vector<std::string> getAllIDs() + { ++ deleteTerminatedSubcriptions(); + std::vector<std::string> idList; + for (const auto& it : subscriptionsMap) + { +@@ -1058,6 +1086,7 @@ class EventServiceManager + + bool isDestinationExist(const std::string& destUrl) + { ++ deleteTerminatedSubcriptions(); + for (const auto& it : subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +@@ -1072,6 +1101,7 @@ class EventServiceManager + + bool sendTestEventLog() + { ++ deleteTerminatedSubcriptions(); + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +@@ -1108,6 +1138,8 @@ class EventServiceManager + } + eventRecord.push_back(eventMessage); + ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +@@ -1151,6 +1183,8 @@ class EventServiceManager + } + void sendBroadcastMsg(const std::string& broadcastMsg) + { ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr<Subscription> entry = it.second; +@@ -1263,6 +1297,8 @@ class EventServiceManager + return; + } + ++ deleteTerminatedSubcriptions(); ++ + 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/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..87f0a4fd9 --- /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 ff562320d23e1c1e075689a636505f22eb4890d4 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 745eeb6..5575765 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -369,6 +369,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."; +@@ -395,11 +401,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 5d71c63..f97909c 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -853,18 +853,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/0012-Add-support-for-deleting-terminated-subscriptions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch new file mode 100644 index 000000000..61503904c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch @@ -0,0 +1,66 @@ +From b132263ef2f3bb3fd2766154c00b7b00c1e49fb6 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Date: Tue, 7 Dec 2021 09:48:07 +0100 +Subject: [PATCH] Add support for deleting terminated subscriptions + +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. + +This commit adds this check for metric reports. + +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: I3cb0af5bc24411cddcdb3d1d9de25e8e9144106c +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + redfish-core/include/event_service_manager.hpp | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index c68b707..6a93fc0 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -1486,7 +1486,7 @@ class EventServiceManager + } + + #endif +- static void getReadingsForReport(sdbusplus::message::message& msg) ++ void getReadingsForReport(sdbusplus::message::message& msg) + { + if (msg.is_method_error()) + { +@@ -1525,6 +1525,8 @@ class EventServiceManager + return; + } + ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : + EventServiceManager::getInstance().subscriptionsMap) + { +@@ -1560,7 +1562,10 @@ class EventServiceManager + "arg0=xyz.openbmc_project.Telemetry.Report"; + + matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>( +- *crow::connections::systemBus, matchStr, getReadingsForReport); ++ *crow::connections::systemBus, matchStr, ++ [this](sdbusplus::message::message& msg) { ++ getReadingsForReport(msg); ++ }); + } + }; + +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0013-event-service-fix-added-Context-field-to-response.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0013-event-service-fix-added-Context-field-to-response.patch new file mode 100644 index 000000000..019ccbe64 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0013-event-service-fix-added-Context-field-to-response.patch @@ -0,0 +1,33 @@ +From ce9b52791e76d73050f053f8fc607c6e1eb5d8c4 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Date: Thu, 16 Dec 2021 10:46:55 +0100 +Subject: [PATCH] event service fix, added Context field to response + +Tested: + - Context field is present + - No regression detected + +Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +--- + redfish-core/include/event_service_manager.hpp | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 881d2db..1ba9f21 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -530,6 +530,11 @@ class Subscription : public persistent_data::UserSubscription + return; + } + ++ if (!customText.empty()) ++ { ++ msg["Context"] = customText; ++ } ++ + this->sendEvent( + msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); + } +-- +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..617d6f3e8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README @@ -0,0 +1,37 @@ +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 + + - 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 + + - Add support for deleting terminated subscriptions + file://eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch + + - event service fix added Context field to response + file://eventservice/0013-event-service-fix-added-Context-field-to-response.patch |