blob: a6e166924070bb3784f6fccb910041482ed28d57 [file] [log] [blame]
AppaRao Pulibd030d02020-03-20 03:34:29 +05301/*
2// Copyright (c) 2020 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
Sunitha Harish29a82b02021-02-18 15:54:16 +053017#include <boost/asio/ip/address.hpp>
18#include <boost/asio/ip/basic_endpoint.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070019#include <boost/asio/steady_timer.hpp>
20#include <boost/beast/core/flat_buffer.hpp>
21#include <boost/beast/core/tcp_stream.hpp>
22#include <boost/beast/http/message.hpp>
AppaRao Pulibd030d02020-03-20 03:34:29 +053023#include <boost/beast/version.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000024#include <boost/container/devector.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053025#include <include/async_resolve.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050026
AppaRao Pulibd030d02020-03-20 03:34:29 +053027#include <cstdlib>
28#include <functional>
29#include <iostream>
30#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053031#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053032#include <string>
33
34namespace crow
35{
36
Carson Labradof52c03c2022-03-23 18:50:15 +000037// It is assumed that the BMC should be able to handle 4 parallel connections
38constexpr uint8_t maxPoolSize = 4;
39constexpr uint8_t maxRequestQueueSize = 50;
40constexpr unsigned int httpReadBodyLimit = 8192;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053041
AppaRao Pulibd030d02020-03-20 03:34:29 +053042enum class ConnState
43{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053044 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053045 resolveInProgress,
46 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053047 connectInProgress,
48 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053049 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053050 sendInProgress,
51 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053052 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053053 recvFailed,
54 idle,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053055 closeInProgress,
Ayushi Smritife44eb02020-05-15 15:24:45 +053056 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053057 suspended,
58 terminated,
59 abortConnection,
60 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053061};
62
Carson Labradof52c03c2022-03-23 18:50:15 +000063// We need to allow retry information to be set before a message has been sent
64// and a connection pool has been created
65struct RetryPolicyData
66{
67 uint32_t maxRetryAttempts = 5;
68 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
69 std::string retryPolicyAction = "TerminateAfterRetries";
70 std::string name;
71};
72
73struct PendingRequest
74{
Carson Labrado244256c2022-04-27 17:16:32 +000075 boost::beast::http::request<boost::beast::http::string_body> req;
Carson Labrado039a47e2022-04-05 16:03:20 +000076 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +000077 RetryPolicyData retryPolicy;
Carson Labrado039a47e2022-04-05 16:03:20 +000078 PendingRequest(
Carson Labrado244256c2022-04-27 17:16:32 +000079 boost::beast::http::request<boost::beast::http::string_body>&& req,
Carson Labrado039a47e2022-04-05 16:03:20 +000080 const std::function<void(bool, uint32_t, Response&)>& callback,
81 const RetryPolicyData& retryPolicy) :
Carson Labrado244256c2022-04-27 17:16:32 +000082 req(std::move(req)),
Carson Labradof52c03c2022-03-23 18:50:15 +000083 callback(callback), retryPolicy(retryPolicy)
84 {}
85};
86
87class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +053088{
89 private:
Carson Labradof52c03c2022-03-23 18:50:15 +000090 ConnState state = ConnState::initialized;
91 uint32_t retryCount = 0;
92 bool runningTimer = false;
93 std::string subId;
94 std::string host;
95 uint16_t port;
96 uint32_t connId;
97
98 // Retry policy information
99 // This should be updated before each message is sent
100 RetryPolicyData retryPolicy;
101
102 // Data buffers
AppaRao Pulibd030d02020-03-20 03:34:29 +0530103 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530104 std::optional<
105 boost::beast::http::response_parser<boost::beast::http::string_body>>
106 parser;
Carson Labradof52c03c2022-03-23 18:50:15 +0000107 boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000108 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530109
Carson Labradof52c03c2022-03-23 18:50:15 +0000110 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000111 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000112 crow::async_resolve::Resolver resolver;
113 boost::beast::tcp_stream conn;
114 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700115
Carson Labradof52c03c2022-03-23 18:50:15 +0000116 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530117
Sunitha Harish29a82b02021-02-18 15:54:16 +0530118 void doResolve()
119 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530120 state = ConnState::resolveInProgress;
Carson Labradof52c03c2022-03-23 18:50:15 +0000121 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
122 << std::to_string(port)
123 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530124
125 auto respHandler =
126 [self(shared_from_this())](
127 const boost::beast::error_code ec,
128 const std::vector<boost::asio::ip::tcp::endpoint>&
129 endpointList) {
Ed Tanous26f69762022-01-25 09:49:11 -0800130 if (ec || (endpointList.empty()))
Sunitha Harish29a82b02021-02-18 15:54:16 +0530131 {
132 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
133 self->state = ConnState::resolveFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000134 self->waitAndRetry();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530135 return;
136 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000137 BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
138 << std::to_string(self->port)
139 << ", id: " << std::to_string(self->connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530140 self->doConnect(endpointList);
141 };
Carson Labradof52c03c2022-03-23 18:50:15 +0000142
Sunitha Harish29a82b02021-02-18 15:54:16 +0530143 resolver.asyncResolve(host, port, std::move(respHandler));
144 }
145
146 void doConnect(
147 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530148 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530149 state = ConnState::connectInProgress;
150
Carson Labradof52c03c2022-03-23 18:50:15 +0000151 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
152 << std::to_string(port)
153 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530154
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530155 conn.expires_after(std::chrono::seconds(30));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530156 conn.async_connect(
157 endpointList, [self(shared_from_this())](
158 const boost::beast::error_code ec,
159 const boost::asio::ip::tcp::endpoint& endpoint) {
160 if (ec)
161 {
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800162 BMCWEB_LOG_ERROR << "Connect "
Carson Labradof52c03c2022-03-23 18:50:15 +0000163 << endpoint.address().to_string() << ":"
164 << std::to_string(endpoint.port())
165 << ", id: " << std::to_string(self->connId)
Sunitha Harish29a82b02021-02-18 15:54:16 +0530166 << " failed: " << ec.message();
167 self->state = ConnState::connectFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000168 self->waitAndRetry();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530169 return;
170 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000171 BMCWEB_LOG_DEBUG
172 << "Connected to: " << endpoint.address().to_string() << ":"
173 << std::to_string(endpoint.port())
174 << ", id: " << std::to_string(self->connId);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530175 self->state = ConnState::connected;
Carson Labradof52c03c2022-03-23 18:50:15 +0000176 self->sendMessage();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530177 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530178 }
179
Carson Labradof52c03c2022-03-23 18:50:15 +0000180 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530181 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530182 state = ConnState::sendInProgress;
183
AppaRao Pulibd030d02020-03-20 03:34:29 +0530184 // Set a timeout on the operation
185 conn.expires_after(std::chrono::seconds(30));
186
187 // Send the HTTP request to the remote host
188 boost::beast::http::async_write(
189 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530190 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530191 const std::size_t& bytesTransferred) {
192 if (ec)
193 {
194 BMCWEB_LOG_ERROR << "sendMessage() failed: "
195 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530196 self->state = ConnState::sendFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000197 self->waitAndRetry();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530198 return;
199 }
200 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
201 << bytesTransferred;
202 boost::ignore_unused(bytesTransferred);
203
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530204 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530205 });
206 }
207
208 void recvMessage()
209 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530210 state = ConnState::recvInProgress;
211
212 parser.emplace(std::piecewise_construct, std::make_tuple());
213 parser->body_limit(httpReadBodyLimit);
214
AppaRao Pulibd030d02020-03-20 03:34:29 +0530215 // Receive the HTTP response
216 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530217 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530218 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530219 const std::size_t& bytesTransferred) {
220 if (ec)
221 {
222 BMCWEB_LOG_ERROR << "recvMessage() failed: "
223 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530224 self->state = ConnState::recvFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000225 self->waitAndRetry();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530226 return;
227 }
228 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
229 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530230 BMCWEB_LOG_DEBUG << "recvMessage() data: "
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800231 << self->parser->get().body();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530232
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530233 unsigned int respCode = self->parser->get().result_int();
234 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
235 << respCode;
236
237 // 2XX response is considered to be successful
238 if ((respCode < 200) || (respCode >= 300))
239 {
240 // The listener failed to receive the Sent-Event
Sunitha Harish7adb85a2021-10-26 03:10:04 -0500241 BMCWEB_LOG_ERROR
242 << "recvMessage() Listener Failed to "
243 "receive Sent-Event. Header Response Code: "
244 << respCode;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530245 self->state = ConnState::recvFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000246 self->waitAndRetry();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530247 return;
248 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530249
Carson Labradof52c03c2022-03-23 18:50:15 +0000250 // Send is successful
251 // Reset the counter just in case this was after retrying
252 self->retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530253
254 // Keep the connection alive if server supports it
255 // Else close the connection
256 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
257 << self->parser->keep_alive();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530258
Carson Labrado039a47e2022-04-05 16:03:20 +0000259 // Copy the response into a Response object so that it can be
260 // processed by the callback function.
261 self->res.clear();
262 self->res.stringResponse = self->parser->release();
263 self->callback(self->parser->keep_alive(), self->connId,
264 self->res);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530265 });
266 }
267
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530268 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530269 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000270 if (retryCount >= retryPolicy.maxRetryAttempts)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530271 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530272 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
Carson Labradof52c03c2022-03-23 18:50:15 +0000273 BMCWEB_LOG_DEBUG << "Retry policy: "
274 << retryPolicy.retryPolicyAction;
Carson Labrado039a47e2022-04-05 16:03:20 +0000275
276 // We want to return a 502 to indicate there was an error with the
277 // external server
278 res.clear();
279 redfish::messages::operationFailed(res);
280
Carson Labradof52c03c2022-03-23 18:50:15 +0000281 if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530282 {
283 // TODO: delete subscription
284 state = ConnState::terminated;
Carson Labrado039a47e2022-04-05 16:03:20 +0000285 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530286 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000287 if (retryPolicy.retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530288 {
289 state = ConnState::suspended;
Carson Labrado039a47e2022-04-05 16:03:20 +0000290 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530291 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530292 // Reset the retrycount to zero so that client can try connecting
293 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700294 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530295 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530296 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530297
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530298 if (runningTimer)
299 {
300 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
301 return;
302 }
303 runningTimer = true;
304
305 retryCount++;
306
Carson Labradof52c03c2022-03-23 18:50:15 +0000307 BMCWEB_LOG_DEBUG << "Attempt retry after "
308 << std::to_string(
309 retryPolicy.retryIntervalSecs.count())
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530310 << " seconds. RetryCount = " << retryCount;
Carson Labradof52c03c2022-03-23 18:50:15 +0000311 timer.expires_after(retryPolicy.retryIntervalSecs);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530312 timer.async_wait(
Carson Labradof52c03c2022-03-23 18:50:15 +0000313 [self(shared_from_this())](const boost::system::error_code ec) {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530314 if (ec == boost::asio::error::operation_aborted)
315 {
316 BMCWEB_LOG_DEBUG
317 << "async_wait failed since the operation is aborted"
318 << ec.message();
319 }
320 else if (ec)
321 {
322 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
323 // Ignore the error and continue the retry loop to attempt
324 // sending the event as per the retry policy
325 }
326 self->runningTimer = false;
327
Carson Labradof52c03c2022-03-23 18:50:15 +0000328 // Let's close the connection and restart from resolve.
329 self->doCloseAndRetry();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530330 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530331 }
332
Carson Labradof52c03c2022-03-23 18:50:15 +0000333 void doClose()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530334 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000335 state = ConnState::closeInProgress;
336 boost::beast::error_code ec;
337 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
338 conn.close();
339
340 // not_connected happens sometimes so don't bother reporting it.
341 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530342 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000343 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
344 << ", id: " << std::to_string(connId)
345 << "shutdown failed: " << ec.message();
346 return;
347 }
348 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
349 << ", id: " << std::to_string(connId)
350 << " closed gracefully";
351 if ((state != ConnState::suspended) && (state != ConnState::terminated))
352 {
353 state = ConnState::closed;
354 }
355 }
356
357 void doCloseAndRetry()
358 {
359 state = ConnState::closeInProgress;
360 boost::beast::error_code ec;
361 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
362 conn.close();
363
364 // not_connected happens sometimes so don't bother reporting it.
365 if (ec && ec != boost::beast::errc::not_connected)
366 {
367 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
368 << ", id: " << std::to_string(connId)
369 << "shutdown failed: " << ec.message();
370 return;
371 }
372 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
373 << ", id: " << std::to_string(connId)
374 << " closed gracefully";
375 if ((state != ConnState::suspended) && (state != ConnState::terminated))
376 {
377 // Now let's try to resend the data
378 state = ConnState::retry;
379 this->doResolve();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530380 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530381 }
382
383 public:
Carson Labradof52c03c2022-03-23 18:50:15 +0000384 explicit ConnectionInfo(boost::asio::io_context& ioc, const std::string& id,
385 const std::string& destIP, const uint16_t destPort,
Carson Labradof52c03c2022-03-23 18:50:15 +0000386 const unsigned int connId) :
387 subId(id),
Carson Labrado244256c2022-04-27 17:16:32 +0000388 host(destIP), port(destPort), connId(connId), conn(ioc), timer(ioc)
389 {}
Carson Labradof52c03c2022-03-23 18:50:15 +0000390};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530391
Carson Labradof52c03c2022-03-23 18:50:15 +0000392class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
393{
394 private:
395 boost::asio::io_context& ioc;
396 const std::string id;
397 const std::string destIP;
398 const uint16_t destPort;
Carson Labradof52c03c2022-03-23 18:50:15 +0000399 std::vector<std::shared_ptr<ConnectionInfo>> connections;
400 boost::container::devector<PendingRequest> requestQueue;
401
402 friend class HttpClient;
403
Carson Labrado244256c2022-04-27 17:16:32 +0000404 // Configure a connections's request, callback, and retry info in
405 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000406 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530407 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000408 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530409 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000410 BMCWEB_LOG_ERROR
411 << "setConnProps() should not have been called when requestQueue is empty";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530412 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530413 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530414
Carson Labrado244256c2022-04-27 17:16:32 +0000415 auto nextReq = requestQueue.front();
416 conn.retryPolicy = std::move(nextReq.retryPolicy);
417 conn.req = std::move(nextReq.req);
418 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000419
420 BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
421 << ":" << std::to_string(conn.port)
422 << ", id: " << std::to_string(conn.connId)
423 << ", retry policy is \"" << conn.retryPolicy.name
424 << "\"";
425
426 // We can remove the request from the queue at this point
427 requestQueue.pop_front();
428 }
429
430 // Configures a connection to use the specific retry policy.
431 inline void setConnRetryPolicy(ConnectionInfo& conn,
432 const RetryPolicyData& retryPolicy)
433 {
434 BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
435 << ", id: " << std::to_string(conn.connId)
436 << " using retry policy \"" << retryPolicy.name
437 << "\"";
438
439 conn.retryPolicy = retryPolicy;
440 }
441
442 // Gets called as part of callback after request is sent
443 // Reuses the connection if there are any requests waiting to be sent
444 // Otherwise closes the connection if it is not a keep-alive
445 void sendNext(bool keepAlive, uint32_t connId)
446 {
447 auto conn = connections[connId];
448 // Reuse the connection to send the next request in the queue
449 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530450 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000451 BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
452 << " requests remaining in queue for " << destIP
453 << ":" << std::to_string(destPort)
454 << ", reusing connnection "
455 << std::to_string(connId);
456
457 setConnProps(*conn);
458
459 if (keepAlive)
460 {
461 conn->sendMessage();
462 }
463 else
464 {
465 // Server is not keep-alive enabled so we need to close the
466 // connection and then start over from resolve
467 conn->doClose();
468 conn->doResolve();
469 }
470 return;
471 }
472
473 // No more messages to send so close the connection if necessary
474 if (keepAlive)
475 {
476 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530477 }
478 else
479 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000480 // Abort the connection since server is not keep-alive enabled
481 conn->state = ConnState::abortConnection;
482 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530483 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530484 }
485
Carson Labrado244256c2022-04-27 17:16:32 +0000486 void sendData(std::string& data, const std::string& destUri,
487 const boost::beast::http::fields& httpHeader,
488 const boost::beast::http::verb verb,
489 const RetryPolicyData& retryPolicy,
Carson Labrado039a47e2022-04-05 16:03:20 +0000490 std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530491 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000492 std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
493
494 // Callback to be called once the request has been sent
Carson Labrado039a47e2022-04-05 16:03:20 +0000495 auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
496 Response& res) {
497 // Allow provided callback to perform additional processing of the
498 // request
499 resHandler(res);
500
Carson Labradof52c03c2022-03-23 18:50:15 +0000501 // If requests remain in the queue then we want to reuse this
502 // connection to send the next request
503 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
504 if (!self)
505 {
506 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
507 return;
508 }
509
510 self->sendNext(keepAlive, connId);
511 };
512
Carson Labrado244256c2022-04-27 17:16:32 +0000513 // Construct the request to be sent
514 boost::beast::http::request<boost::beast::http::string_body> thisReq(
515 verb, destUri, 11, "", httpHeader);
516 thisReq.set(boost::beast::http::field::host, destIP);
517 thisReq.keep_alive(true);
518 thisReq.body() = std::move(data);
519 thisReq.prepare_payload();
520
Carson Labradof52c03c2022-03-23 18:50:15 +0000521 // Reuse an existing connection if one is available
522 for (unsigned int i = 0; i < connections.size(); i++)
523 {
524 auto conn = connections[i];
525 if ((conn->state == ConnState::idle) ||
526 (conn->state == ConnState::initialized) ||
527 (conn->state == ConnState::closed))
528 {
Carson Labrado244256c2022-04-27 17:16:32 +0000529 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000530 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000531 setConnRetryPolicy(*conn, retryPolicy);
532 std::string commonMsg = std::to_string(i) + " from pool " +
533 destIP + ":" + std::to_string(destPort);
534
535 if (conn->state == ConnState::idle)
536 {
537 BMCWEB_LOG_DEBUG << "Grabbing idle connection "
538 << commonMsg;
539 conn->sendMessage();
540 }
541 else
542 {
543 BMCWEB_LOG_DEBUG << "Reusing existing connection "
544 << commonMsg;
545 conn->doResolve();
546 }
547 return;
548 }
549 }
550
551 // All connections in use so create a new connection or add request to
552 // the queue
553 if (connections.size() < maxPoolSize)
554 {
555 BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
556 << ":" << std::to_string(destPort);
557 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000558 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000559 conn->callback = std::move(cb);
560 setConnRetryPolicy(*conn, retryPolicy);
561 conn->doResolve();
562 }
563 else if (requestQueue.size() < maxRequestQueueSize)
564 {
565 BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
Carson Labrado244256c2022-04-27 17:16:32 +0000566 requestQueue.emplace_back(std::move(thisReq), std::move(cb),
Carson Labradof52c03c2022-03-23 18:50:15 +0000567 retryPolicy);
568 }
569 else
570 {
571 BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
572 << " request queue full. Dropping request.";
573 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530574 }
575
Carson Labradof52c03c2022-03-23 18:50:15 +0000576 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530577 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000578 unsigned int newId = static_cast<unsigned int>(connections.size());
579
Carson Labrado244256c2022-04-27 17:16:32 +0000580 auto& ret = connections.emplace_back(
581 std::make_shared<ConnectionInfo>(ioc, id, destIP, destPort, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000582
583 BMCWEB_LOG_DEBUG << "Added connection "
584 << std::to_string(connections.size() - 1)
585 << " to pool " << destIP << ":"
586 << std::to_string(destPort);
587
588 return ret;
589 }
590
591 public:
592 explicit ConnectionPool(boost::asio::io_context& ioc, const std::string& id,
Carson Labrado244256c2022-04-27 17:16:32 +0000593 const std::string& destIP,
594 const uint16_t destPort) :
Carson Labradof52c03c2022-03-23 18:50:15 +0000595 ioc(ioc),
Carson Labrado244256c2022-04-27 17:16:32 +0000596 id(id), destIP(destIP), destPort(destPort)
Carson Labradof52c03c2022-03-23 18:50:15 +0000597 {
598 std::string clientKey = destIP + ":" + std::to_string(destPort);
599 BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
600 << std::to_string(destPort);
601
602 // Initialize the pool with a single connection
603 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530604 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530605};
606
Carson Labradof52c03c2022-03-23 18:50:15 +0000607class HttpClient
608{
609 private:
610 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
611 connectionPools;
612 boost::asio::io_context& ioc =
613 crow::connections::systemBus->get_io_context();
614 std::unordered_map<std::string, RetryPolicyData> retryInfo;
615 HttpClient() = default;
616
Carson Labrado039a47e2022-04-05 16:03:20 +0000617 // Used as a dummy callback by sendData() in order to call
618 // sendDataWithCallback()
619 static void genericResHandler(Response& res)
620 {
621 BMCWEB_LOG_DEBUG << "Response handled with return code: "
622 << std::to_string(res.resultInt());
623 };
624
Carson Labradof52c03c2022-03-23 18:50:15 +0000625 public:
626 HttpClient(const HttpClient&) = delete;
627 HttpClient& operator=(const HttpClient&) = delete;
628 HttpClient(HttpClient&&) = delete;
629 HttpClient& operator=(HttpClient&&) = delete;
630 ~HttpClient() = default;
631
632 static HttpClient& getInstance()
633 {
634 static HttpClient handler;
635 return handler;
636 }
637
Carson Labrado039a47e2022-04-05 16:03:20 +0000638 // Send a request to destIP:destPort where additional processing of the
639 // result is not required
Carson Labradof52c03c2022-03-23 18:50:15 +0000640 void sendData(std::string& data, const std::string& id,
641 const std::string& destIP, const uint16_t destPort,
642 const std::string& destUri,
643 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000644 const boost::beast::http::verb verb,
645 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000646 {
Carson Labrado039a47e2022-04-05 16:03:20 +0000647 std::function<void(Response&)> cb = genericResHandler;
648 sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000649 verb, retryPolicyName, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000650 }
651
652 // Send request to destIP:destPort and use the provided callback to
653 // handle the response
654 void sendDataWithCallback(std::string& data, const std::string& id,
655 const std::string& destIP,
656 const uint16_t destPort,
657 const std::string& destUri,
658 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000659 const boost::beast::http::verb verb,
660 const std::string& retryPolicyName,
Carson Labrado039a47e2022-04-05 16:03:20 +0000661 std::function<void(Response&)>& resHandler)
662 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000663 std::string clientKey = destIP + ":" + std::to_string(destPort);
664 // Use nullptr to avoid creating a ConnectionPool each time
665 auto result = connectionPools.try_emplace(clientKey, nullptr);
666 if (result.second)
667 {
668 // Now actually create the ConnectionPool shared_ptr since it does
669 // not already exist
Carson Labrado244256c2022-04-27 17:16:32 +0000670 result.first->second =
671 std::make_shared<ConnectionPool>(ioc, id, destIP, destPort);
Carson Labradof52c03c2022-03-23 18:50:15 +0000672 BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
673 }
674 else
675 {
676 BMCWEB_LOG_DEBUG << "Using existing connection pool for "
677 << clientKey;
678 }
679
680 // Get the associated retry policy
681 auto policy = retryInfo.try_emplace(retryPolicyName);
682 if (policy.second)
683 {
684 BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
685 << "\" with default values";
686 policy.first->second.name = retryPolicyName;
687 }
688
689 // Send the data using either the existing connection pool or the newly
690 // created connection pool
Carson Labrado244256c2022-04-27 17:16:32 +0000691 result.first->second->sendData(data, destUri, httpHeader, verb,
692 policy.first->second, resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000693 }
694
695 void setRetryConfig(const uint32_t retryAttempts,
696 const uint32_t retryTimeoutInterval,
697 const std::string& retryPolicyName)
698 {
699 // We need to create the retry policy if one does not already exist for
700 // the given retryPolicyName
701 auto result = retryInfo.try_emplace(retryPolicyName);
702 if (result.second)
703 {
704 BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
705 << retryPolicyName << "\"";
706 result.first->second.name = retryPolicyName;
707 }
708 else
709 {
710 BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
711 << retryPolicyName << "\"";
712 }
713
714 result.first->second.maxRetryAttempts = retryAttempts;
715 result.first->second.retryIntervalSecs =
716 std::chrono::seconds(retryTimeoutInterval);
717 }
718
719 void setRetryPolicy(const std::string& retryPolicy,
720 const std::string& retryPolicyName)
721 {
722 // We need to create the retry policy if one does not already exist for
723 // the given retryPolicyName
724 auto result = retryInfo.try_emplace(retryPolicyName);
725 if (result.second)
726 {
727 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
728 << retryPolicyName << "\"";
729 result.first->second.name = retryPolicyName;
730 }
731 else
732 {
733 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
734 << retryPolicyName << "\"";
735 }
736
737 result.first->second.retryPolicyAction = retryPolicy;
738 }
739};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530740} // namespace crow