1bd030d0aSAppaRao Puli /* 2bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation 3bd030d0aSAppaRao Puli // 4bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License"); 5bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License. 6bd030d0aSAppaRao Puli // You may obtain a copy of the License at 7bd030d0aSAppaRao Puli // 8bd030d0aSAppaRao Puli // http://www.apache.org/licenses/LICENSE-2.0 9bd030d0aSAppaRao Puli // 10bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software 11bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS, 12bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and 14bd030d0aSAppaRao Puli // limitations under the License. 15bd030d0aSAppaRao Puli */ 16bd030d0aSAppaRao Puli #pragma once 1729a82b08SSunitha Harish #include <boost/asio/ip/address.hpp> 1829a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp> 19d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp> 20d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp> 21d43cd0caSEd Tanous #include <boost/beast/core/tcp_stream.hpp> 22d43cd0caSEd Tanous #include <boost/beast/http/message.hpp> 23bd030d0aSAppaRao Puli #include <boost/beast/version.hpp> 24f52c03c1SCarson Labrado #include <boost/container/devector.hpp> 2529a82b08SSunitha Harish #include <include/async_resolve.hpp> 261214b7e7SGunnar Mills 27bd030d0aSAppaRao Puli #include <cstdlib> 28bd030d0aSAppaRao Puli #include <functional> 29bd030d0aSAppaRao Puli #include <iostream> 30bd030d0aSAppaRao Puli #include <memory> 312a5689a7SAppaRao Puli #include <queue> 32bd030d0aSAppaRao Puli #include <string> 33bd030d0aSAppaRao Puli 34bd030d0aSAppaRao Puli namespace crow 35bd030d0aSAppaRao Puli { 36bd030d0aSAppaRao Puli 37f52c03c1SCarson Labrado // It is assumed that the BMC should be able to handle 4 parallel connections 38f52c03c1SCarson Labrado constexpr uint8_t maxPoolSize = 4; 39f52c03c1SCarson Labrado constexpr uint8_t maxRequestQueueSize = 50; 40f52c03c1SCarson Labrado constexpr unsigned int httpReadBodyLimit = 8192; 412a5689a7SAppaRao Puli 42bd030d0aSAppaRao Puli enum class ConnState 43bd030d0aSAppaRao Puli { 442a5689a7SAppaRao Puli initialized, 4529a82b08SSunitha Harish resolveInProgress, 4629a82b08SSunitha Harish resolveFailed, 472a5689a7SAppaRao Puli connectInProgress, 482a5689a7SAppaRao Puli connectFailed, 49bd030d0aSAppaRao Puli connected, 502a5689a7SAppaRao Puli sendInProgress, 512a5689a7SAppaRao Puli sendFailed, 526eaa1d2fSSunitha Harish recvInProgress, 532a5689a7SAppaRao Puli recvFailed, 542a5689a7SAppaRao Puli idle, 556eaa1d2fSSunitha Harish closeInProgress, 56fe44eb0bSAyushi Smriti closed, 576eaa1d2fSSunitha Harish suspended, 586eaa1d2fSSunitha Harish terminated, 596eaa1d2fSSunitha Harish abortConnection, 606eaa1d2fSSunitha Harish retry 61bd030d0aSAppaRao Puli }; 62bd030d0aSAppaRao Puli 63f52c03c1SCarson Labrado // We need to allow retry information to be set before a message has been sent 64f52c03c1SCarson Labrado // and a connection pool has been created 65f52c03c1SCarson Labrado struct RetryPolicyData 66f52c03c1SCarson Labrado { 67f52c03c1SCarson Labrado uint32_t maxRetryAttempts = 5; 68f52c03c1SCarson Labrado std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0); 69f52c03c1SCarson Labrado std::string retryPolicyAction = "TerminateAfterRetries"; 70f52c03c1SCarson Labrado std::string name; 71f52c03c1SCarson Labrado }; 72f52c03c1SCarson Labrado 73f52c03c1SCarson Labrado struct PendingRequest 74f52c03c1SCarson Labrado { 75244256ccSCarson Labrado boost::beast::http::request<boost::beast::http::string_body> req; 76039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 77f52c03c1SCarson Labrado RetryPolicyData retryPolicy; 78039a47e3SCarson Labrado PendingRequest( 79244256ccSCarson Labrado boost::beast::http::request<boost::beast::http::string_body>&& req, 80039a47e3SCarson Labrado const std::function<void(bool, uint32_t, Response&)>& callback, 81f52c03c1SCarson Labrado const RetryPolicyData& retryPolicy) : 82244256ccSCarson Labrado req(std::move(req)), 83f52c03c1SCarson Labrado callback(callback), retryPolicy(retryPolicy) 84f52c03c1SCarson Labrado {} 85f52c03c1SCarson Labrado }; 86f52c03c1SCarson Labrado 87f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo> 88bd030d0aSAppaRao Puli { 89bd030d0aSAppaRao Puli private: 90f52c03c1SCarson Labrado ConnState state = ConnState::initialized; 91f52c03c1SCarson Labrado uint32_t retryCount = 0; 92f52c03c1SCarson Labrado bool runningTimer = false; 93f52c03c1SCarson Labrado std::string subId; 94f52c03c1SCarson Labrado std::string host; 95f52c03c1SCarson Labrado uint16_t port; 96f52c03c1SCarson Labrado uint32_t connId; 97f52c03c1SCarson Labrado 98f52c03c1SCarson Labrado // Retry policy information 99f52c03c1SCarson Labrado // This should be updated before each message is sent 100f52c03c1SCarson Labrado RetryPolicyData retryPolicy; 101f52c03c1SCarson Labrado 102f52c03c1SCarson Labrado // Data buffers 103bd030d0aSAppaRao Puli boost::beast::http::request<boost::beast::http::string_body> req; 1046eaa1d2fSSunitha Harish std::optional< 1056eaa1d2fSSunitha Harish boost::beast::http::response_parser<boost::beast::http::string_body>> 1066eaa1d2fSSunitha Harish parser; 107f52c03c1SCarson Labrado boost::beast::flat_static_buffer<httpReadBodyLimit> buffer; 108039a47e3SCarson Labrado Response res; 1096eaa1d2fSSunitha Harish 110f52c03c1SCarson Labrado // Ascync callables 111039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 112f52c03c1SCarson Labrado crow::async_resolve::Resolver resolver; 113f52c03c1SCarson Labrado boost::beast::tcp_stream conn; 114f52c03c1SCarson Labrado boost::asio::steady_timer timer; 11584b35604SEd Tanous 116f52c03c1SCarson Labrado friend class ConnectionPool; 117bd030d0aSAppaRao Puli 11829a82b08SSunitha Harish void doResolve() 11929a82b08SSunitha Harish { 12029a82b08SSunitha Harish state = ConnState::resolveInProgress; 121f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" 122f52c03c1SCarson Labrado << std::to_string(port) 123f52c03c1SCarson Labrado << ", id: " << std::to_string(connId); 12429a82b08SSunitha Harish 12529a82b08SSunitha Harish auto respHandler = 12629a82b08SSunitha Harish [self(shared_from_this())]( 12729a82b08SSunitha Harish const boost::beast::error_code ec, 12829a82b08SSunitha Harish const std::vector<boost::asio::ip::tcp::endpoint>& 12929a82b08SSunitha Harish endpointList) { 13026f6976fSEd Tanous if (ec || (endpointList.empty())) 13129a82b08SSunitha Harish { 13229a82b08SSunitha Harish BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message(); 13329a82b08SSunitha Harish self->state = ConnState::resolveFailed; 134f52c03c1SCarson Labrado self->waitAndRetry(); 13529a82b08SSunitha Harish return; 13629a82b08SSunitha Harish } 137f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":" 138f52c03c1SCarson Labrado << std::to_string(self->port) 139f52c03c1SCarson Labrado << ", id: " << std::to_string(self->connId); 14029a82b08SSunitha Harish self->doConnect(endpointList); 14129a82b08SSunitha Harish }; 142f52c03c1SCarson Labrado 14329a82b08SSunitha Harish resolver.asyncResolve(host, port, std::move(respHandler)); 14429a82b08SSunitha Harish } 14529a82b08SSunitha Harish 14629a82b08SSunitha Harish void doConnect( 14729a82b08SSunitha Harish const std::vector<boost::asio::ip::tcp::endpoint>& endpointList) 148bd030d0aSAppaRao Puli { 1492a5689a7SAppaRao Puli state = ConnState::connectInProgress; 1502a5689a7SAppaRao Puli 151f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" 152f52c03c1SCarson Labrado << std::to_string(port) 153f52c03c1SCarson Labrado << ", id: " << std::to_string(connId); 154b00dcc27SEd Tanous 15529a82b08SSunitha Harish conn.expires_after(std::chrono::seconds(30)); 156002d39b4SEd Tanous conn.async_connect(endpointList, 157002d39b4SEd Tanous [self(shared_from_this())]( 15829a82b08SSunitha Harish const boost::beast::error_code ec, 15929a82b08SSunitha Harish const boost::asio::ip::tcp::endpoint& endpoint) { 1602a5689a7SAppaRao Puli if (ec) 1612a5689a7SAppaRao Puli { 162002d39b4SEd Tanous BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string() 163002d39b4SEd Tanous << ":" << std::to_string(endpoint.port()) 164f52c03c1SCarson Labrado << ", id: " << std::to_string(self->connId) 1652a5689a7SAppaRao Puli << " failed: " << ec.message(); 1662a5689a7SAppaRao Puli self->state = ConnState::connectFailed; 167f52c03c1SCarson Labrado self->waitAndRetry(); 1682a5689a7SAppaRao Puli return; 1692a5689a7SAppaRao Puli } 170f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG 171f52c03c1SCarson Labrado << "Connected to: " << endpoint.address().to_string() << ":" 172f52c03c1SCarson Labrado << std::to_string(endpoint.port()) 173f52c03c1SCarson Labrado << ", id: " << std::to_string(self->connId); 1746eaa1d2fSSunitha Harish self->state = ConnState::connected; 175f52c03c1SCarson Labrado self->sendMessage(); 1762a5689a7SAppaRao Puli }); 1772a5689a7SAppaRao Puli } 1782a5689a7SAppaRao Puli 179f52c03c1SCarson Labrado void sendMessage() 1802a5689a7SAppaRao Puli { 1812a5689a7SAppaRao Puli state = ConnState::sendInProgress; 1822a5689a7SAppaRao Puli 183bd030d0aSAppaRao Puli // Set a timeout on the operation 184bd030d0aSAppaRao Puli conn.expires_after(std::chrono::seconds(30)); 185bd030d0aSAppaRao Puli 186bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 187bd030d0aSAppaRao Puli boost::beast::http::async_write( 188bd030d0aSAppaRao Puli conn, req, 1892a5689a7SAppaRao Puli [self(shared_from_this())](const boost::beast::error_code& ec, 190bd030d0aSAppaRao Puli const std::size_t& bytesTransferred) { 191bd030d0aSAppaRao Puli if (ec) 192bd030d0aSAppaRao Puli { 193002d39b4SEd Tanous BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message(); 1942a5689a7SAppaRao Puli self->state = ConnState::sendFailed; 195f52c03c1SCarson Labrado self->waitAndRetry(); 196bd030d0aSAppaRao Puli return; 197bd030d0aSAppaRao Puli } 198bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " 199bd030d0aSAppaRao Puli << bytesTransferred; 200bd030d0aSAppaRao Puli boost::ignore_unused(bytesTransferred); 201bd030d0aSAppaRao Puli 2022a5689a7SAppaRao Puli self->recvMessage(); 203bd030d0aSAppaRao Puli }); 204bd030d0aSAppaRao Puli } 205bd030d0aSAppaRao Puli 206bd030d0aSAppaRao Puli void recvMessage() 207bd030d0aSAppaRao Puli { 2086eaa1d2fSSunitha Harish state = ConnState::recvInProgress; 2096eaa1d2fSSunitha Harish 2106eaa1d2fSSunitha Harish parser.emplace(std::piecewise_construct, std::make_tuple()); 2116eaa1d2fSSunitha Harish parser->body_limit(httpReadBodyLimit); 2126eaa1d2fSSunitha Harish 213bd030d0aSAppaRao Puli // Receive the HTTP response 214bd030d0aSAppaRao Puli boost::beast::http::async_read( 2156eaa1d2fSSunitha Harish conn, buffer, *parser, 2162a5689a7SAppaRao Puli [self(shared_from_this())](const boost::beast::error_code& ec, 217bd030d0aSAppaRao Puli const std::size_t& bytesTransferred) { 218bd030d0aSAppaRao Puli if (ec) 219bd030d0aSAppaRao Puli { 220002d39b4SEd Tanous BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message(); 2212a5689a7SAppaRao Puli self->state = ConnState::recvFailed; 222f52c03c1SCarson Labrado self->waitAndRetry(); 223bd030d0aSAppaRao Puli return; 224bd030d0aSAppaRao Puli } 225bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " 226bd030d0aSAppaRao Puli << bytesTransferred; 2276eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG << "recvMessage() data: " 2288cc8edecSEd Tanous << self->parser->get().body(); 229bd030d0aSAppaRao Puli 2306eaa1d2fSSunitha Harish unsigned int respCode = self->parser->get().result_int(); 2316eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " 2326eaa1d2fSSunitha Harish << respCode; 2336eaa1d2fSSunitha Harish 2346eaa1d2fSSunitha Harish // 2XX response is considered to be successful 2356eaa1d2fSSunitha Harish if ((respCode < 200) || (respCode >= 300)) 2366eaa1d2fSSunitha Harish { 2376eaa1d2fSSunitha Harish // The listener failed to receive the Sent-Event 238002d39b4SEd Tanous BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " 2397adb85acSSunitha Harish "receive Sent-Event. Header Response Code: " 2407adb85acSSunitha Harish << respCode; 2416eaa1d2fSSunitha Harish self->state = ConnState::recvFailed; 242f52c03c1SCarson Labrado self->waitAndRetry(); 2436eaa1d2fSSunitha Harish return; 2446eaa1d2fSSunitha Harish } 245bd030d0aSAppaRao Puli 246f52c03c1SCarson Labrado // Send is successful 247f52c03c1SCarson Labrado // Reset the counter just in case this was after retrying 248f52c03c1SCarson Labrado self->retryCount = 0; 2496eaa1d2fSSunitha Harish 2506eaa1d2fSSunitha Harish // Keep the connection alive if server supports it 2516eaa1d2fSSunitha Harish // Else close the connection 2526eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " 2536eaa1d2fSSunitha Harish << self->parser->keep_alive(); 2546eaa1d2fSSunitha Harish 255039a47e3SCarson Labrado // Copy the response into a Response object so that it can be 256039a47e3SCarson Labrado // processed by the callback function. 257039a47e3SCarson Labrado self->res.clear(); 258039a47e3SCarson Labrado self->res.stringResponse = self->parser->release(); 259002d39b4SEd Tanous self->callback(self->parser->keep_alive(), self->connId, self->res); 260bd030d0aSAppaRao Puli }); 261bd030d0aSAppaRao Puli } 262bd030d0aSAppaRao Puli 2636eaa1d2fSSunitha Harish void waitAndRetry() 264bd030d0aSAppaRao Puli { 265f52c03c1SCarson Labrado if (retryCount >= retryPolicy.maxRetryAttempts) 2662a5689a7SAppaRao Puli { 2676eaa1d2fSSunitha Harish BMCWEB_LOG_ERROR << "Maximum number of retries reached."; 268f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Retry policy: " 269f52c03c1SCarson Labrado << retryPolicy.retryPolicyAction; 270039a47e3SCarson Labrado 271039a47e3SCarson Labrado // We want to return a 502 to indicate there was an error with the 272039a47e3SCarson Labrado // external server 273039a47e3SCarson Labrado res.clear(); 274039a47e3SCarson Labrado redfish::messages::operationFailed(res); 275039a47e3SCarson Labrado 276f52c03c1SCarson Labrado if (retryPolicy.retryPolicyAction == "TerminateAfterRetries") 277fe44eb0bSAyushi Smriti { 278fe44eb0bSAyushi Smriti // TODO: delete subscription 279fe44eb0bSAyushi Smriti state = ConnState::terminated; 280039a47e3SCarson Labrado callback(false, connId, res); 281fe44eb0bSAyushi Smriti } 282f52c03c1SCarson Labrado if (retryPolicy.retryPolicyAction == "SuspendRetries") 283fe44eb0bSAyushi Smriti { 2842a5689a7SAppaRao Puli state = ConnState::suspended; 285039a47e3SCarson Labrado callback(false, connId, res); 2862a5689a7SAppaRao Puli } 2876eaa1d2fSSunitha Harish // Reset the retrycount to zero so that client can try connecting 2886eaa1d2fSSunitha Harish // again if needed 289fe44eb0bSAyushi Smriti retryCount = 0; 2902a5689a7SAppaRao Puli return; 2912a5689a7SAppaRao Puli } 2922a5689a7SAppaRao Puli 293fe44eb0bSAyushi Smriti if (runningTimer) 294fe44eb0bSAyushi Smriti { 295fe44eb0bSAyushi Smriti BMCWEB_LOG_DEBUG << "Retry timer is already running."; 296fe44eb0bSAyushi Smriti return; 297fe44eb0bSAyushi Smriti } 298fe44eb0bSAyushi Smriti runningTimer = true; 299fe44eb0bSAyushi Smriti 3002a5689a7SAppaRao Puli retryCount++; 301fe44eb0bSAyushi Smriti 302f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Attempt retry after " 303f52c03c1SCarson Labrado << std::to_string( 304f52c03c1SCarson Labrado retryPolicy.retryIntervalSecs.count()) 305fe44eb0bSAyushi Smriti << " seconds. RetryCount = " << retryCount; 306f52c03c1SCarson Labrado timer.expires_after(retryPolicy.retryIntervalSecs); 307cb13a392SEd Tanous timer.async_wait( 308f52c03c1SCarson Labrado [self(shared_from_this())](const boost::system::error_code ec) { 3096eaa1d2fSSunitha Harish if (ec == boost::asio::error::operation_aborted) 3106eaa1d2fSSunitha Harish { 3116eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG 3126eaa1d2fSSunitha Harish << "async_wait failed since the operation is aborted" 3136eaa1d2fSSunitha Harish << ec.message(); 3146eaa1d2fSSunitha Harish } 3156eaa1d2fSSunitha Harish else if (ec) 3166eaa1d2fSSunitha Harish { 3176eaa1d2fSSunitha Harish BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message(); 3186eaa1d2fSSunitha Harish // Ignore the error and continue the retry loop to attempt 3196eaa1d2fSSunitha Harish // sending the event as per the retry policy 3206eaa1d2fSSunitha Harish } 321fe44eb0bSAyushi Smriti self->runningTimer = false; 3226eaa1d2fSSunitha Harish 323f52c03c1SCarson Labrado // Let's close the connection and restart from resolve. 324f52c03c1SCarson Labrado self->doCloseAndRetry(); 325fe44eb0bSAyushi Smriti }); 3262a5689a7SAppaRao Puli } 3272a5689a7SAppaRao Puli 328f52c03c1SCarson Labrado void doClose() 329fe44eb0bSAyushi Smriti { 330f52c03c1SCarson Labrado state = ConnState::closeInProgress; 331f52c03c1SCarson Labrado boost::beast::error_code ec; 332f52c03c1SCarson Labrado conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 333f52c03c1SCarson Labrado conn.close(); 334f52c03c1SCarson Labrado 335f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 336f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 3372a5689a7SAppaRao Puli { 338f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << host << ":" << std::to_string(port) 339f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 340f52c03c1SCarson Labrado << "shutdown failed: " << ec.message(); 3416eaa1d2fSSunitha Harish return; 3426eaa1d2fSSunitha Harish } 343f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port) 344f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 345f52c03c1SCarson Labrado << " closed gracefully"; 346f52c03c1SCarson Labrado if ((state != ConnState::suspended) && (state != ConnState::terminated)) 347f52c03c1SCarson Labrado { 348f52c03c1SCarson Labrado state = ConnState::closed; 3496eaa1d2fSSunitha Harish } 3506eaa1d2fSSunitha Harish } 351f52c03c1SCarson Labrado 352f52c03c1SCarson Labrado void doCloseAndRetry() 35392a74e56SAppaRao Puli { 354f52c03c1SCarson Labrado state = ConnState::closeInProgress; 355f52c03c1SCarson Labrado boost::beast::error_code ec; 356f52c03c1SCarson Labrado conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 357f52c03c1SCarson Labrado conn.close(); 358f52c03c1SCarson Labrado 359f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 360f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 36192a74e56SAppaRao Puli { 362f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << host << ":" << std::to_string(port) 363f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 364f52c03c1SCarson Labrado << "shutdown failed: " << ec.message(); 3656eaa1d2fSSunitha Harish return; 3666eaa1d2fSSunitha Harish } 367f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port) 368f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 369f52c03c1SCarson Labrado << " closed gracefully"; 370f52c03c1SCarson Labrado if ((state != ConnState::suspended) && (state != ConnState::terminated)) 3716eaa1d2fSSunitha Harish { 372f52c03c1SCarson Labrado // Now let's try to resend the data 373f52c03c1SCarson Labrado state = ConnState::retry; 374f52c03c1SCarson Labrado this->doResolve(); 3752a5689a7SAppaRao Puli } 376bd030d0aSAppaRao Puli } 377bd030d0aSAppaRao Puli 378bd030d0aSAppaRao Puli public: 379f52c03c1SCarson Labrado explicit ConnectionInfo(boost::asio::io_context& ioc, const std::string& id, 380f52c03c1SCarson Labrado const std::string& destIP, const uint16_t destPort, 381f52c03c1SCarson Labrado const unsigned int connId) : 382f52c03c1SCarson Labrado subId(id), 383244256ccSCarson Labrado host(destIP), port(destPort), connId(connId), conn(ioc), timer(ioc) 384244256ccSCarson Labrado {} 385f52c03c1SCarson Labrado }; 386bd030d0aSAppaRao Puli 387f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool> 388bd030d0aSAppaRao Puli { 389f52c03c1SCarson Labrado private: 390f52c03c1SCarson Labrado boost::asio::io_context& ioc; 391f52c03c1SCarson Labrado const std::string id; 392f52c03c1SCarson Labrado const std::string destIP; 393f52c03c1SCarson Labrado const uint16_t destPort; 394f52c03c1SCarson Labrado std::vector<std::shared_ptr<ConnectionInfo>> connections; 395f52c03c1SCarson Labrado boost::container::devector<PendingRequest> requestQueue; 396f52c03c1SCarson Labrado 397f52c03c1SCarson Labrado friend class HttpClient; 398f52c03c1SCarson Labrado 399244256ccSCarson Labrado // Configure a connections's request, callback, and retry info in 400244256ccSCarson Labrado // preparation to begin sending the request 401f52c03c1SCarson Labrado void setConnProps(ConnectionInfo& conn) 402bd030d0aSAppaRao Puli { 403f52c03c1SCarson Labrado if (requestQueue.empty()) 404f52c03c1SCarson Labrado { 405f52c03c1SCarson Labrado BMCWEB_LOG_ERROR 406f52c03c1SCarson Labrado << "setConnProps() should not have been called when requestQueue is empty"; 407bd030d0aSAppaRao Puli return; 408bd030d0aSAppaRao Puli } 409bd030d0aSAppaRao Puli 410244256ccSCarson Labrado auto nextReq = requestQueue.front(); 411244256ccSCarson Labrado conn.retryPolicy = std::move(nextReq.retryPolicy); 412244256ccSCarson Labrado conn.req = std::move(nextReq.req); 413244256ccSCarson Labrado conn.callback = std::move(nextReq.callback); 414f52c03c1SCarson Labrado 415f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host 416f52c03c1SCarson Labrado << ":" << std::to_string(conn.port) 417f52c03c1SCarson Labrado << ", id: " << std::to_string(conn.connId) 418f52c03c1SCarson Labrado << ", retry policy is \"" << conn.retryPolicy.name 419f52c03c1SCarson Labrado << "\""; 420f52c03c1SCarson Labrado 421f52c03c1SCarson Labrado // We can remove the request from the queue at this point 422f52c03c1SCarson Labrado requestQueue.pop_front(); 423f52c03c1SCarson Labrado } 424f52c03c1SCarson Labrado 425f52c03c1SCarson Labrado // Configures a connection to use the specific retry policy. 426f52c03c1SCarson Labrado inline void setConnRetryPolicy(ConnectionInfo& conn, 427f52c03c1SCarson Labrado const RetryPolicyData& retryPolicy) 4282a5689a7SAppaRao Puli { 429f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort) 430f52c03c1SCarson Labrado << ", id: " << std::to_string(conn.connId) 431f52c03c1SCarson Labrado << " using retry policy \"" << retryPolicy.name 432f52c03c1SCarson Labrado << "\""; 433f52c03c1SCarson Labrado 434f52c03c1SCarson Labrado conn.retryPolicy = retryPolicy; 435f52c03c1SCarson Labrado } 436f52c03c1SCarson Labrado 437f52c03c1SCarson Labrado // Gets called as part of callback after request is sent 438f52c03c1SCarson Labrado // Reuses the connection if there are any requests waiting to be sent 439f52c03c1SCarson Labrado // Otherwise closes the connection if it is not a keep-alive 440f52c03c1SCarson Labrado void sendNext(bool keepAlive, uint32_t connId) 441f52c03c1SCarson Labrado { 442f52c03c1SCarson Labrado auto conn = connections[connId]; 443f52c03c1SCarson Labrado // Reuse the connection to send the next request in the queue 444f52c03c1SCarson Labrado if (!requestQueue.empty()) 445f52c03c1SCarson Labrado { 446f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size()) 447f52c03c1SCarson Labrado << " requests remaining in queue for " << destIP 448f52c03c1SCarson Labrado << ":" << std::to_string(destPort) 449f52c03c1SCarson Labrado << ", reusing connnection " 450f52c03c1SCarson Labrado << std::to_string(connId); 451f52c03c1SCarson Labrado 452f52c03c1SCarson Labrado setConnProps(*conn); 453f52c03c1SCarson Labrado 454f52c03c1SCarson Labrado if (keepAlive) 455f52c03c1SCarson Labrado { 456f52c03c1SCarson Labrado conn->sendMessage(); 4572a5689a7SAppaRao Puli } 4582a5689a7SAppaRao Puli else 4592a5689a7SAppaRao Puli { 460f52c03c1SCarson Labrado // Server is not keep-alive enabled so we need to close the 461f52c03c1SCarson Labrado // connection and then start over from resolve 462f52c03c1SCarson Labrado conn->doClose(); 463f52c03c1SCarson Labrado conn->doResolve(); 464f52c03c1SCarson Labrado } 465f52c03c1SCarson Labrado return; 466f52c03c1SCarson Labrado } 467f52c03c1SCarson Labrado 468f52c03c1SCarson Labrado // No more messages to send so close the connection if necessary 469f52c03c1SCarson Labrado if (keepAlive) 470f52c03c1SCarson Labrado { 471f52c03c1SCarson Labrado conn->state = ConnState::idle; 472f52c03c1SCarson Labrado } 473f52c03c1SCarson Labrado else 474f52c03c1SCarson Labrado { 475f52c03c1SCarson Labrado // Abort the connection since server is not keep-alive enabled 476f52c03c1SCarson Labrado conn->state = ConnState::abortConnection; 477f52c03c1SCarson Labrado conn->doClose(); 4782a5689a7SAppaRao Puli } 479bd030d0aSAppaRao Puli } 480bd030d0aSAppaRao Puli 481244256ccSCarson Labrado void sendData(std::string& data, const std::string& destUri, 482244256ccSCarson Labrado const boost::beast::http::fields& httpHeader, 483244256ccSCarson Labrado const boost::beast::http::verb verb, 484244256ccSCarson Labrado const RetryPolicyData& retryPolicy, 485039a47e3SCarson Labrado std::function<void(Response&)>& resHandler) 486fe44eb0bSAyushi Smriti { 487f52c03c1SCarson Labrado std::weak_ptr<ConnectionPool> weakSelf = weak_from_this(); 488f52c03c1SCarson Labrado 489f52c03c1SCarson Labrado // Callback to be called once the request has been sent 490039a47e3SCarson Labrado auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId, 491039a47e3SCarson Labrado Response& res) { 492039a47e3SCarson Labrado // Allow provided callback to perform additional processing of the 493039a47e3SCarson Labrado // request 494039a47e3SCarson Labrado resHandler(res); 495039a47e3SCarson Labrado 496f52c03c1SCarson Labrado // If requests remain in the queue then we want to reuse this 497f52c03c1SCarson Labrado // connection to send the next request 498f52c03c1SCarson Labrado std::shared_ptr<ConnectionPool> self = weakSelf.lock(); 499f52c03c1SCarson Labrado if (!self) 500f52c03c1SCarson Labrado { 501f52c03c1SCarson Labrado BMCWEB_LOG_CRITICAL << self << " Failed to capture connection"; 502f52c03c1SCarson Labrado return; 503fe44eb0bSAyushi Smriti } 504fe44eb0bSAyushi Smriti 505f52c03c1SCarson Labrado self->sendNext(keepAlive, connId); 506f52c03c1SCarson Labrado }; 507f52c03c1SCarson Labrado 508244256ccSCarson Labrado // Construct the request to be sent 509244256ccSCarson Labrado boost::beast::http::request<boost::beast::http::string_body> thisReq( 510244256ccSCarson Labrado verb, destUri, 11, "", httpHeader); 511244256ccSCarson Labrado thisReq.set(boost::beast::http::field::host, destIP); 512244256ccSCarson Labrado thisReq.keep_alive(true); 513244256ccSCarson Labrado thisReq.body() = std::move(data); 514244256ccSCarson Labrado thisReq.prepare_payload(); 515244256ccSCarson Labrado 516f52c03c1SCarson Labrado // Reuse an existing connection if one is available 517f52c03c1SCarson Labrado for (unsigned int i = 0; i < connections.size(); i++) 518fe44eb0bSAyushi Smriti { 519f52c03c1SCarson Labrado auto conn = connections[i]; 520f52c03c1SCarson Labrado if ((conn->state == ConnState::idle) || 521f52c03c1SCarson Labrado (conn->state == ConnState::initialized) || 522f52c03c1SCarson Labrado (conn->state == ConnState::closed)) 523f52c03c1SCarson Labrado { 524244256ccSCarson Labrado conn->req = std::move(thisReq); 525f52c03c1SCarson Labrado conn->callback = std::move(cb); 526f52c03c1SCarson Labrado setConnRetryPolicy(*conn, retryPolicy); 527f52c03c1SCarson Labrado std::string commonMsg = std::to_string(i) + " from pool " + 528f52c03c1SCarson Labrado destIP + ":" + std::to_string(destPort); 529f52c03c1SCarson Labrado 530f52c03c1SCarson Labrado if (conn->state == ConnState::idle) 531f52c03c1SCarson Labrado { 532f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Grabbing idle connection " 533f52c03c1SCarson Labrado << commonMsg; 534f52c03c1SCarson Labrado conn->sendMessage(); 535f52c03c1SCarson Labrado } 536f52c03c1SCarson Labrado else 537f52c03c1SCarson Labrado { 538f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Reusing existing connection " 539f52c03c1SCarson Labrado << commonMsg; 540f52c03c1SCarson Labrado conn->doResolve(); 541f52c03c1SCarson Labrado } 542f52c03c1SCarson Labrado return; 543f52c03c1SCarson Labrado } 544f52c03c1SCarson Labrado } 545f52c03c1SCarson Labrado 546f52c03c1SCarson Labrado // All connections in use so create a new connection or add request to 547f52c03c1SCarson Labrado // the queue 548f52c03c1SCarson Labrado if (connections.size() < maxPoolSize) 549f52c03c1SCarson Labrado { 550f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP 551f52c03c1SCarson Labrado << ":" << std::to_string(destPort); 552f52c03c1SCarson Labrado auto conn = addConnection(); 553244256ccSCarson Labrado conn->req = std::move(thisReq); 554f52c03c1SCarson Labrado conn->callback = std::move(cb); 555f52c03c1SCarson Labrado setConnRetryPolicy(*conn, retryPolicy); 556f52c03c1SCarson Labrado conn->doResolve(); 557f52c03c1SCarson Labrado } 558f52c03c1SCarson Labrado else if (requestQueue.size() < maxRequestQueueSize) 559f52c03c1SCarson Labrado { 560f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue."; 561244256ccSCarson Labrado requestQueue.emplace_back(std::move(thisReq), std::move(cb), 562f52c03c1SCarson Labrado retryPolicy); 563f52c03c1SCarson Labrado } 564f52c03c1SCarson Labrado else 565f52c03c1SCarson Labrado { 566f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort) 567f52c03c1SCarson Labrado << " request queue full. Dropping request."; 568f52c03c1SCarson Labrado } 569f52c03c1SCarson Labrado } 570f52c03c1SCarson Labrado 571f52c03c1SCarson Labrado std::shared_ptr<ConnectionInfo>& addConnection() 572f52c03c1SCarson Labrado { 573f52c03c1SCarson Labrado unsigned int newId = static_cast<unsigned int>(connections.size()); 574f52c03c1SCarson Labrado 575244256ccSCarson Labrado auto& ret = connections.emplace_back( 576244256ccSCarson Labrado std::make_shared<ConnectionInfo>(ioc, id, destIP, destPort, newId)); 577f52c03c1SCarson Labrado 578f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Added connection " 579f52c03c1SCarson Labrado << std::to_string(connections.size() - 1) 580f52c03c1SCarson Labrado << " to pool " << destIP << ":" 581f52c03c1SCarson Labrado << std::to_string(destPort); 582f52c03c1SCarson Labrado 583f52c03c1SCarson Labrado return ret; 584f52c03c1SCarson Labrado } 585f52c03c1SCarson Labrado 586f52c03c1SCarson Labrado public: 587f52c03c1SCarson Labrado explicit ConnectionPool(boost::asio::io_context& ioc, const std::string& id, 588244256ccSCarson Labrado const std::string& destIP, 589244256ccSCarson Labrado const uint16_t destPort) : 590f52c03c1SCarson Labrado ioc(ioc), 591244256ccSCarson Labrado id(id), destIP(destIP), destPort(destPort) 592f52c03c1SCarson Labrado { 593f52c03c1SCarson Labrado std::string clientKey = destIP + ":" + std::to_string(destPort); 594f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":" 595f52c03c1SCarson Labrado << std::to_string(destPort); 596f52c03c1SCarson Labrado 597f52c03c1SCarson Labrado // Initialize the pool with a single connection 598f52c03c1SCarson Labrado addConnection(); 599fe44eb0bSAyushi Smriti } 600bd030d0aSAppaRao Puli }; 601bd030d0aSAppaRao Puli 602f52c03c1SCarson Labrado class HttpClient 603f52c03c1SCarson Labrado { 604f52c03c1SCarson Labrado private: 605f52c03c1SCarson Labrado std::unordered_map<std::string, std::shared_ptr<ConnectionPool>> 606f52c03c1SCarson Labrado connectionPools; 607f52c03c1SCarson Labrado boost::asio::io_context& ioc = 608f52c03c1SCarson Labrado crow::connections::systemBus->get_io_context(); 609f52c03c1SCarson Labrado std::unordered_map<std::string, RetryPolicyData> retryInfo; 610f52c03c1SCarson Labrado HttpClient() = default; 611f52c03c1SCarson Labrado 612039a47e3SCarson Labrado // Used as a dummy callback by sendData() in order to call 613039a47e3SCarson Labrado // sendDataWithCallback() 614039a47e3SCarson Labrado static void genericResHandler(Response& res) 615039a47e3SCarson Labrado { 616039a47e3SCarson Labrado BMCWEB_LOG_DEBUG << "Response handled with return code: " 617039a47e3SCarson Labrado << std::to_string(res.resultInt()); 618*4ee8e211SEd Tanous } 619039a47e3SCarson Labrado 620f52c03c1SCarson Labrado public: 621f52c03c1SCarson Labrado HttpClient(const HttpClient&) = delete; 622f52c03c1SCarson Labrado HttpClient& operator=(const HttpClient&) = delete; 623f52c03c1SCarson Labrado HttpClient(HttpClient&&) = delete; 624f52c03c1SCarson Labrado HttpClient& operator=(HttpClient&&) = delete; 625f52c03c1SCarson Labrado ~HttpClient() = default; 626f52c03c1SCarson Labrado 627f52c03c1SCarson Labrado static HttpClient& getInstance() 628f52c03c1SCarson Labrado { 629f52c03c1SCarson Labrado static HttpClient handler; 630f52c03c1SCarson Labrado return handler; 631f52c03c1SCarson Labrado } 632f52c03c1SCarson Labrado 633039a47e3SCarson Labrado // Send a request to destIP:destPort where additional processing of the 634039a47e3SCarson Labrado // result is not required 635f52c03c1SCarson Labrado void sendData(std::string& data, const std::string& id, 636f52c03c1SCarson Labrado const std::string& destIP, const uint16_t destPort, 637f52c03c1SCarson Labrado const std::string& destUri, 638f52c03c1SCarson Labrado const boost::beast::http::fields& httpHeader, 639244256ccSCarson Labrado const boost::beast::http::verb verb, 640244256ccSCarson Labrado const std::string& retryPolicyName) 641f52c03c1SCarson Labrado { 642039a47e3SCarson Labrado std::function<void(Response&)> cb = genericResHandler; 643039a47e3SCarson Labrado sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader, 644244256ccSCarson Labrado verb, retryPolicyName, cb); 645039a47e3SCarson Labrado } 646039a47e3SCarson Labrado 647039a47e3SCarson Labrado // Send request to destIP:destPort and use the provided callback to 648039a47e3SCarson Labrado // handle the response 649039a47e3SCarson Labrado void sendDataWithCallback(std::string& data, const std::string& id, 650039a47e3SCarson Labrado const std::string& destIP, 651039a47e3SCarson Labrado const uint16_t destPort, 652039a47e3SCarson Labrado const std::string& destUri, 653039a47e3SCarson Labrado const boost::beast::http::fields& httpHeader, 654244256ccSCarson Labrado const boost::beast::http::verb verb, 655244256ccSCarson Labrado const std::string& retryPolicyName, 656039a47e3SCarson Labrado std::function<void(Response&)>& resHandler) 657039a47e3SCarson Labrado { 658f52c03c1SCarson Labrado std::string clientKey = destIP + ":" + std::to_string(destPort); 659f52c03c1SCarson Labrado // Use nullptr to avoid creating a ConnectionPool each time 660f52c03c1SCarson Labrado auto result = connectionPools.try_emplace(clientKey, nullptr); 661f52c03c1SCarson Labrado if (result.second) 662f52c03c1SCarson Labrado { 663f52c03c1SCarson Labrado // Now actually create the ConnectionPool shared_ptr since it does 664f52c03c1SCarson Labrado // not already exist 665244256ccSCarson Labrado result.first->second = 666244256ccSCarson Labrado std::make_shared<ConnectionPool>(ioc, id, destIP, destPort); 667f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey; 668f52c03c1SCarson Labrado } 669f52c03c1SCarson Labrado else 670f52c03c1SCarson Labrado { 671f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Using existing connection pool for " 672f52c03c1SCarson Labrado << clientKey; 673f52c03c1SCarson Labrado } 674f52c03c1SCarson Labrado 675f52c03c1SCarson Labrado // Get the associated retry policy 676f52c03c1SCarson Labrado auto policy = retryInfo.try_emplace(retryPolicyName); 677f52c03c1SCarson Labrado if (policy.second) 678f52c03c1SCarson Labrado { 679f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName 680f52c03c1SCarson Labrado << "\" with default values"; 681f52c03c1SCarson Labrado policy.first->second.name = retryPolicyName; 682f52c03c1SCarson Labrado } 683f52c03c1SCarson Labrado 684f52c03c1SCarson Labrado // Send the data using either the existing connection pool or the newly 685f52c03c1SCarson Labrado // created connection pool 686244256ccSCarson Labrado result.first->second->sendData(data, destUri, httpHeader, verb, 687244256ccSCarson Labrado policy.first->second, resHandler); 688f52c03c1SCarson Labrado } 689f52c03c1SCarson Labrado 690f52c03c1SCarson Labrado void setRetryConfig(const uint32_t retryAttempts, 691f52c03c1SCarson Labrado const uint32_t retryTimeoutInterval, 692f52c03c1SCarson Labrado const std::string& retryPolicyName) 693f52c03c1SCarson Labrado { 694f52c03c1SCarson Labrado // We need to create the retry policy if one does not already exist for 695f52c03c1SCarson Labrado // the given retryPolicyName 696f52c03c1SCarson Labrado auto result = retryInfo.try_emplace(retryPolicyName); 697f52c03c1SCarson Labrado if (result.second) 698f52c03c1SCarson Labrado { 699f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \"" 700f52c03c1SCarson Labrado << retryPolicyName << "\""; 701f52c03c1SCarson Labrado result.first->second.name = retryPolicyName; 702f52c03c1SCarson Labrado } 703f52c03c1SCarson Labrado else 704f52c03c1SCarson Labrado { 705f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \"" 706f52c03c1SCarson Labrado << retryPolicyName << "\""; 707f52c03c1SCarson Labrado } 708f52c03c1SCarson Labrado 709f52c03c1SCarson Labrado result.first->second.maxRetryAttempts = retryAttempts; 710f52c03c1SCarson Labrado result.first->second.retryIntervalSecs = 711f52c03c1SCarson Labrado std::chrono::seconds(retryTimeoutInterval); 712f52c03c1SCarson Labrado } 713f52c03c1SCarson Labrado 714f52c03c1SCarson Labrado void setRetryPolicy(const std::string& retryPolicy, 715f52c03c1SCarson Labrado const std::string& retryPolicyName) 716f52c03c1SCarson Labrado { 717f52c03c1SCarson Labrado // We need to create the retry policy if one does not already exist for 718f52c03c1SCarson Labrado // the given retryPolicyName 719f52c03c1SCarson Labrado auto result = retryInfo.try_emplace(retryPolicyName); 720f52c03c1SCarson Labrado if (result.second) 721f52c03c1SCarson Labrado { 722f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \"" 723f52c03c1SCarson Labrado << retryPolicyName << "\""; 724f52c03c1SCarson Labrado result.first->second.name = retryPolicyName; 725f52c03c1SCarson Labrado } 726f52c03c1SCarson Labrado else 727f52c03c1SCarson Labrado { 728f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \"" 729f52c03c1SCarson Labrado << retryPolicyName << "\""; 730f52c03c1SCarson Labrado } 731f52c03c1SCarson Labrado 732f52c03c1SCarson Labrado result.first->second.retryPolicyAction = retryPolicy; 733f52c03c1SCarson Labrado } 734f52c03c1SCarson Labrado }; 735bd030d0aSAppaRao Puli } // namespace crow 736