blob: 571b5a9465e43dad6d0e4ba4b39de788c844efb1 [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;
Carson Labrado4d942722022-06-22 22:16:10 +000040constexpr unsigned int httpReadBodyLimit = 16384;
41constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053042
AppaRao Pulibd030d02020-03-20 03:34:29 +053043enum class ConnState
44{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053045 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053046 resolveInProgress,
47 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053048 connectInProgress,
49 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053050 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053051 sendInProgress,
52 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053053 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053054 recvFailed,
55 idle,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053056 closeInProgress,
Ayushi Smritife44eb02020-05-15 15:24:45 +053057 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053058 suspended,
59 terminated,
60 abortConnection,
61 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053062};
63
Carson Labradoa7a80292022-06-01 16:01:52 +000064static inline boost::system::error_code
65 defaultRetryHandler(unsigned int respCode)
66{
67 // As a default, assume 200X is alright
68 BMCWEB_LOG_DEBUG << "Using default check for response code validity";
69 if ((respCode < 200) || (respCode >= 300))
70 {
71 return boost::system::errc::make_error_code(
72 boost::system::errc::result_out_of_range);
73 }
74
75 // Return 0 if the response code is valid
76 return boost::system::errc::make_error_code(boost::system::errc::success);
77};
78
Carson Labradof52c03c2022-03-23 18:50:15 +000079// We need to allow retry information to be set before a message has been sent
80// and a connection pool has been created
81struct RetryPolicyData
82{
83 uint32_t maxRetryAttempts = 5;
84 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
85 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradoa7a80292022-06-01 16:01:52 +000086 std::function<boost::system::error_code(unsigned int respCode)>
87 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +000088};
89
90struct PendingRequest
91{
Carson Labrado244256c2022-04-27 17:16:32 +000092 boost::beast::http::request<boost::beast::http::string_body> req;
Carson Labrado039a47e2022-04-05 16:03:20 +000093 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +000094 RetryPolicyData retryPolicy;
Carson Labrado039a47e2022-04-05 16:03:20 +000095 PendingRequest(
Carson Labrado244256c2022-04-27 17:16:32 +000096 boost::beast::http::request<boost::beast::http::string_body>&& req,
Carson Labrado039a47e2022-04-05 16:03:20 +000097 const std::function<void(bool, uint32_t, Response&)>& callback,
98 const RetryPolicyData& retryPolicy) :
Carson Labrado244256c2022-04-27 17:16:32 +000099 req(std::move(req)),
Carson Labradof52c03c2022-03-23 18:50:15 +0000100 callback(callback), retryPolicy(retryPolicy)
101 {}
102};
103
104class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530105{
106 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000107 ConnState state = ConnState::initialized;
108 uint32_t retryCount = 0;
109 bool runningTimer = false;
110 std::string subId;
111 std::string host;
112 uint16_t port;
113 uint32_t connId;
114
115 // Retry policy information
116 // This should be updated before each message is sent
117 RetryPolicyData retryPolicy;
118
119 // Data buffers
AppaRao Pulibd030d02020-03-20 03:34:29 +0530120 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530121 std::optional<
122 boost::beast::http::response_parser<boost::beast::http::string_body>>
123 parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000124 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000125 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530126
Carson Labradof52c03c2022-03-23 18:50:15 +0000127 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000128 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000129 crow::async_resolve::Resolver resolver;
130 boost::beast::tcp_stream conn;
131 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700132
Carson Labradof52c03c2022-03-23 18:50:15 +0000133 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530134
Sunitha Harish29a82b02021-02-18 15:54:16 +0530135 void doResolve()
136 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530137 state = ConnState::resolveInProgress;
Carson Labradof52c03c2022-03-23 18:50:15 +0000138 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
139 << std::to_string(port)
140 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530141
142 auto respHandler =
143 [self(shared_from_this())](
144 const boost::beast::error_code ec,
145 const std::vector<boost::asio::ip::tcp::endpoint>&
146 endpointList) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700147 if (ec || (endpointList.empty()))
148 {
149 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
150 self->state = ConnState::resolveFailed;
151 self->waitAndRetry();
152 return;
153 }
154 BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
155 << std::to_string(self->port)
156 << ", id: " << std::to_string(self->connId);
157 self->doConnect(endpointList);
158 };
Carson Labradof52c03c2022-03-23 18:50:15 +0000159
Sunitha Harish29a82b02021-02-18 15:54:16 +0530160 resolver.asyncResolve(host, port, std::move(respHandler));
161 }
162
163 void doConnect(
164 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530165 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530166 state = ConnState::connectInProgress;
167
Carson Labradof52c03c2022-03-23 18:50:15 +0000168 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
169 << std::to_string(port)
170 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530171
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530172 conn.expires_after(std::chrono::seconds(30));
Ed Tanous002d39b2022-05-31 08:59:27 -0700173 conn.async_connect(endpointList,
174 [self(shared_from_this())](
175 const boost::beast::error_code ec,
176 const boost::asio::ip::tcp::endpoint& endpoint) {
177 if (ec)
178 {
179 BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
180 << ":" << std::to_string(endpoint.port())
181 << ", id: " << std::to_string(self->connId)
182 << " failed: " << ec.message();
183 self->state = ConnState::connectFailed;
184 self->waitAndRetry();
185 return;
186 }
187 BMCWEB_LOG_DEBUG
188 << "Connected to: " << endpoint.address().to_string() << ":"
189 << std::to_string(endpoint.port())
190 << ", id: " << std::to_string(self->connId);
191 self->state = ConnState::connected;
192 self->sendMessage();
193 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530194 }
195
Carson Labradof52c03c2022-03-23 18:50:15 +0000196 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530197 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530198 state = ConnState::sendInProgress;
199
AppaRao Pulibd030d02020-03-20 03:34:29 +0530200 // Set a timeout on the operation
201 conn.expires_after(std::chrono::seconds(30));
202
203 // Send the HTTP request to the remote host
204 boost::beast::http::async_write(
205 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530206 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530207 const std::size_t& bytesTransferred) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700208 if (ec)
209 {
210 BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
211 self->state = ConnState::sendFailed;
212 self->waitAndRetry();
213 return;
214 }
215 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
216 << bytesTransferred;
217 boost::ignore_unused(bytesTransferred);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530218
Ed Tanous002d39b2022-05-31 08:59:27 -0700219 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530220 });
221 }
222
223 void recvMessage()
224 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530225 state = ConnState::recvInProgress;
226
227 parser.emplace(std::piecewise_construct, std::make_tuple());
228 parser->body_limit(httpReadBodyLimit);
229
AppaRao Pulibd030d02020-03-20 03:34:29 +0530230 // Receive the HTTP response
231 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530232 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530233 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530234 const std::size_t& bytesTransferred) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700235 if (ec)
236 {
237 BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
238 self->state = ConnState::recvFailed;
239 self->waitAndRetry();
240 return;
241 }
242 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
243 << bytesTransferred;
244 BMCWEB_LOG_DEBUG << "recvMessage() data: "
245 << self->parser->get().body();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530246
Ed Tanous002d39b2022-05-31 08:59:27 -0700247 unsigned int respCode = self->parser->get().result_int();
248 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
249 << respCode;
250
Carson Labradoa7a80292022-06-01 16:01:52 +0000251 // Make sure the received response code is valid as defined by
252 // the associated retry policy
253 if (self->retryPolicy.invalidResp(respCode))
Ed Tanous002d39b2022-05-31 08:59:27 -0700254 {
255 // The listener failed to receive the Sent-Event
256 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
257 "receive Sent-Event. Header Response Code: "
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530258 << respCode;
Ed Tanous002d39b2022-05-31 08:59:27 -0700259 self->state = ConnState::recvFailed;
260 self->waitAndRetry();
261 return;
262 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530263
Ed Tanous002d39b2022-05-31 08:59:27 -0700264 // Send is successful
265 // Reset the counter just in case this was after retrying
266 self->retryCount = 0;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530267
Ed Tanous002d39b2022-05-31 08:59:27 -0700268 // Keep the connection alive if server supports it
269 // Else close the connection
270 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
271 << self->parser->keep_alive();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530272
Ed Tanous002d39b2022-05-31 08:59:27 -0700273 // Copy the response into a Response object so that it can be
274 // processed by the callback function.
275 self->res.clear();
276 self->res.stringResponse = self->parser->release();
277 self->callback(self->parser->keep_alive(), self->connId, self->res);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530278 });
279 }
280
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530281 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530282 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000283 if (retryCount >= retryPolicy.maxRetryAttempts)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530284 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530285 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
Carson Labradof52c03c2022-03-23 18:50:15 +0000286 BMCWEB_LOG_DEBUG << "Retry policy: "
287 << retryPolicy.retryPolicyAction;
Carson Labrado039a47e2022-04-05 16:03:20 +0000288
289 // We want to return a 502 to indicate there was an error with the
290 // external server
291 res.clear();
292 redfish::messages::operationFailed(res);
293
Carson Labradof52c03c2022-03-23 18:50:15 +0000294 if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530295 {
296 // TODO: delete subscription
297 state = ConnState::terminated;
Carson Labrado039a47e2022-04-05 16:03:20 +0000298 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530299 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000300 if (retryPolicy.retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530301 {
302 state = ConnState::suspended;
Carson Labrado039a47e2022-04-05 16:03:20 +0000303 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530304 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530305 // Reset the retrycount to zero so that client can try connecting
306 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700307 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530308 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530309 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530310
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530311 if (runningTimer)
312 {
313 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
314 return;
315 }
316 runningTimer = true;
317
318 retryCount++;
319
Carson Labradof52c03c2022-03-23 18:50:15 +0000320 BMCWEB_LOG_DEBUG << "Attempt retry after "
321 << std::to_string(
322 retryPolicy.retryIntervalSecs.count())
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530323 << " seconds. RetryCount = " << retryCount;
Carson Labradof52c03c2022-03-23 18:50:15 +0000324 timer.expires_after(retryPolicy.retryIntervalSecs);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530325 timer.async_wait(
Carson Labradof52c03c2022-03-23 18:50:15 +0000326 [self(shared_from_this())](const boost::system::error_code ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700327 if (ec == boost::asio::error::operation_aborted)
328 {
329 BMCWEB_LOG_DEBUG
330 << "async_wait failed since the operation is aborted"
331 << ec.message();
332 }
333 else if (ec)
334 {
335 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
336 // Ignore the error and continue the retry loop to attempt
337 // sending the event as per the retry policy
338 }
339 self->runningTimer = false;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530340
Ed Tanous002d39b2022-05-31 08:59:27 -0700341 // Let's close the connection and restart from resolve.
342 self->doCloseAndRetry();
343 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530344 }
345
Carson Labradof52c03c2022-03-23 18:50:15 +0000346 void doClose()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530347 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000348 state = ConnState::closeInProgress;
349 boost::beast::error_code ec;
350 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
351 conn.close();
352
353 // not_connected happens sometimes so don't bother reporting it.
354 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530355 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000356 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
357 << ", id: " << std::to_string(connId)
358 << "shutdown failed: " << ec.message();
359 return;
360 }
361 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
362 << ", id: " << std::to_string(connId)
363 << " closed gracefully";
364 if ((state != ConnState::suspended) && (state != ConnState::terminated))
365 {
366 state = ConnState::closed;
367 }
368 }
369
370 void doCloseAndRetry()
371 {
372 state = ConnState::closeInProgress;
373 boost::beast::error_code ec;
374 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
375 conn.close();
376
377 // not_connected happens sometimes so don't bother reporting it.
378 if (ec && ec != boost::beast::errc::not_connected)
379 {
380 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
381 << ", id: " << std::to_string(connId)
382 << "shutdown failed: " << ec.message();
383 return;
384 }
385 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
386 << ", id: " << std::to_string(connId)
387 << " closed gracefully";
388 if ((state != ConnState::suspended) && (state != ConnState::terminated))
389 {
390 // Now let's try to resend the data
391 state = ConnState::retry;
392 this->doResolve();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530393 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530394 }
395
396 public:
Carson Labradof52c03c2022-03-23 18:50:15 +0000397 explicit ConnectionInfo(boost::asio::io_context& ioc, const std::string& id,
398 const std::string& destIP, const uint16_t destPort,
Carson Labradof52c03c2022-03-23 18:50:15 +0000399 const unsigned int connId) :
400 subId(id),
Carson Labrado244256c2022-04-27 17:16:32 +0000401 host(destIP), port(destPort), connId(connId), conn(ioc), timer(ioc)
402 {}
Carson Labradof52c03c2022-03-23 18:50:15 +0000403};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530404
Carson Labradof52c03c2022-03-23 18:50:15 +0000405class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
406{
407 private:
408 boost::asio::io_context& ioc;
409 const std::string id;
410 const std::string destIP;
411 const uint16_t destPort;
Carson Labradof52c03c2022-03-23 18:50:15 +0000412 std::vector<std::shared_ptr<ConnectionInfo>> connections;
413 boost::container::devector<PendingRequest> requestQueue;
414
415 friend class HttpClient;
416
Carson Labrado244256c2022-04-27 17:16:32 +0000417 // Configure a connections's request, callback, and retry info in
418 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000419 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530420 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000421 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530422 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000423 BMCWEB_LOG_ERROR
424 << "setConnProps() should not have been called when requestQueue is empty";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530425 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530426 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530427
Carson Labrado244256c2022-04-27 17:16:32 +0000428 auto nextReq = requestQueue.front();
429 conn.retryPolicy = std::move(nextReq.retryPolicy);
430 conn.req = std::move(nextReq.req);
431 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000432
433 BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
434 << ":" << std::to_string(conn.port)
Carson Labradoa7a80292022-06-01 16:01:52 +0000435 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000436
437 // We can remove the request from the queue at this point
438 requestQueue.pop_front();
439 }
440
441 // Configures a connection to use the specific retry policy.
442 inline void setConnRetryPolicy(ConnectionInfo& conn,
443 const RetryPolicyData& retryPolicy)
444 {
445 BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
Carson Labradoa7a80292022-06-01 16:01:52 +0000446 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000447
448 conn.retryPolicy = retryPolicy;
449 }
450
451 // Gets called as part of callback after request is sent
452 // Reuses the connection if there are any requests waiting to be sent
453 // Otherwise closes the connection if it is not a keep-alive
454 void sendNext(bool keepAlive, uint32_t connId)
455 {
456 auto conn = connections[connId];
457 // Reuse the connection to send the next request in the queue
458 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530459 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000460 BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
461 << " requests remaining in queue for " << destIP
462 << ":" << std::to_string(destPort)
463 << ", reusing connnection "
464 << std::to_string(connId);
465
466 setConnProps(*conn);
467
468 if (keepAlive)
469 {
470 conn->sendMessage();
471 }
472 else
473 {
474 // Server is not keep-alive enabled so we need to close the
475 // connection and then start over from resolve
476 conn->doClose();
477 conn->doResolve();
478 }
479 return;
480 }
481
482 // No more messages to send so close the connection if necessary
483 if (keepAlive)
484 {
485 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530486 }
487 else
488 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000489 // Abort the connection since server is not keep-alive enabled
490 conn->state = ConnState::abortConnection;
491 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530492 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530493 }
494
Carson Labrado244256c2022-04-27 17:16:32 +0000495 void sendData(std::string& data, const std::string& destUri,
496 const boost::beast::http::fields& httpHeader,
497 const boost::beast::http::verb verb,
498 const RetryPolicyData& retryPolicy,
Carson Labrado039a47e2022-04-05 16:03:20 +0000499 std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530500 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000501 std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
502
503 // Callback to be called once the request has been sent
Carson Labrado039a47e2022-04-05 16:03:20 +0000504 auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
505 Response& res) {
506 // Allow provided callback to perform additional processing of the
507 // request
508 resHandler(res);
509
Carson Labradof52c03c2022-03-23 18:50:15 +0000510 // If requests remain in the queue then we want to reuse this
511 // connection to send the next request
512 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
513 if (!self)
514 {
515 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
516 return;
517 }
518
519 self->sendNext(keepAlive, connId);
520 };
521
Carson Labrado244256c2022-04-27 17:16:32 +0000522 // Construct the request to be sent
523 boost::beast::http::request<boost::beast::http::string_body> thisReq(
524 verb, destUri, 11, "", httpHeader);
525 thisReq.set(boost::beast::http::field::host, destIP);
526 thisReq.keep_alive(true);
527 thisReq.body() = std::move(data);
528 thisReq.prepare_payload();
529
Carson Labradof52c03c2022-03-23 18:50:15 +0000530 // Reuse an existing connection if one is available
531 for (unsigned int i = 0; i < connections.size(); i++)
532 {
533 auto conn = connections[i];
534 if ((conn->state == ConnState::idle) ||
535 (conn->state == ConnState::initialized) ||
536 (conn->state == ConnState::closed))
537 {
Carson Labrado244256c2022-04-27 17:16:32 +0000538 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000539 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000540 setConnRetryPolicy(*conn, retryPolicy);
541 std::string commonMsg = std::to_string(i) + " from pool " +
542 destIP + ":" + std::to_string(destPort);
543
544 if (conn->state == ConnState::idle)
545 {
546 BMCWEB_LOG_DEBUG << "Grabbing idle connection "
547 << commonMsg;
548 conn->sendMessage();
549 }
550 else
551 {
552 BMCWEB_LOG_DEBUG << "Reusing existing connection "
553 << commonMsg;
554 conn->doResolve();
555 }
556 return;
557 }
558 }
559
560 // All connections in use so create a new connection or add request to
561 // the queue
562 if (connections.size() < maxPoolSize)
563 {
564 BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
565 << ":" << std::to_string(destPort);
566 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000567 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000568 conn->callback = std::move(cb);
569 setConnRetryPolicy(*conn, retryPolicy);
570 conn->doResolve();
571 }
572 else if (requestQueue.size() < maxRequestQueueSize)
573 {
574 BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
Carson Labrado244256c2022-04-27 17:16:32 +0000575 requestQueue.emplace_back(std::move(thisReq), std::move(cb),
Carson Labradof52c03c2022-03-23 18:50:15 +0000576 retryPolicy);
577 }
578 else
579 {
580 BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
581 << " request queue full. Dropping request.";
582 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530583 }
584
Carson Labradof52c03c2022-03-23 18:50:15 +0000585 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530586 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000587 unsigned int newId = static_cast<unsigned int>(connections.size());
588
Carson Labrado244256c2022-04-27 17:16:32 +0000589 auto& ret = connections.emplace_back(
590 std::make_shared<ConnectionInfo>(ioc, id, destIP, destPort, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000591
592 BMCWEB_LOG_DEBUG << "Added connection "
593 << std::to_string(connections.size() - 1)
594 << " to pool " << destIP << ":"
595 << std::to_string(destPort);
596
597 return ret;
598 }
599
600 public:
601 explicit ConnectionPool(boost::asio::io_context& ioc, const std::string& id,
Carson Labrado244256c2022-04-27 17:16:32 +0000602 const std::string& destIP,
603 const uint16_t destPort) :
Carson Labradof52c03c2022-03-23 18:50:15 +0000604 ioc(ioc),
Carson Labrado244256c2022-04-27 17:16:32 +0000605 id(id), destIP(destIP), destPort(destPort)
Carson Labradof52c03c2022-03-23 18:50:15 +0000606 {
607 std::string clientKey = destIP + ":" + std::to_string(destPort);
608 BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
609 << std::to_string(destPort);
610
611 // Initialize the pool with a single connection
612 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530613 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530614};
615
Carson Labradof52c03c2022-03-23 18:50:15 +0000616class HttpClient
617{
618 private:
619 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
620 connectionPools;
621 boost::asio::io_context& ioc =
622 crow::connections::systemBus->get_io_context();
623 std::unordered_map<std::string, RetryPolicyData> retryInfo;
624 HttpClient() = default;
625
Carson Labrado039a47e2022-04-05 16:03:20 +0000626 // Used as a dummy callback by sendData() in order to call
627 // sendDataWithCallback()
628 static void genericResHandler(Response& res)
629 {
630 BMCWEB_LOG_DEBUG << "Response handled with return code: "
631 << std::to_string(res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700632 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000633
Carson Labradof52c03c2022-03-23 18:50:15 +0000634 public:
635 HttpClient(const HttpClient&) = delete;
636 HttpClient& operator=(const HttpClient&) = delete;
637 HttpClient(HttpClient&&) = delete;
638 HttpClient& operator=(HttpClient&&) = delete;
639 ~HttpClient() = default;
640
641 static HttpClient& getInstance()
642 {
643 static HttpClient handler;
644 return handler;
645 }
646
Carson Labrado039a47e2022-04-05 16:03:20 +0000647 // Send a request to destIP:destPort where additional processing of the
648 // result is not required
Carson Labradof52c03c2022-03-23 18:50:15 +0000649 void sendData(std::string& data, const std::string& id,
650 const std::string& destIP, const uint16_t destPort,
651 const std::string& destUri,
652 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000653 const boost::beast::http::verb verb,
654 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000655 {
Carson Labrado039a47e2022-04-05 16:03:20 +0000656 std::function<void(Response&)> cb = genericResHandler;
657 sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000658 verb, retryPolicyName, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000659 }
660
661 // Send request to destIP:destPort and use the provided callback to
662 // handle the response
663 void sendDataWithCallback(std::string& data, const std::string& id,
664 const std::string& destIP,
665 const uint16_t destPort,
666 const std::string& destUri,
667 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000668 const boost::beast::http::verb verb,
669 const std::string& retryPolicyName,
Carson Labrado039a47e2022-04-05 16:03:20 +0000670 std::function<void(Response&)>& resHandler)
671 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000672 std::string clientKey = destIP + ":" + std::to_string(destPort);
673 // Use nullptr to avoid creating a ConnectionPool each time
674 auto result = connectionPools.try_emplace(clientKey, nullptr);
675 if (result.second)
676 {
677 // Now actually create the ConnectionPool shared_ptr since it does
678 // not already exist
Carson Labrado244256c2022-04-27 17:16:32 +0000679 result.first->second =
680 std::make_shared<ConnectionPool>(ioc, id, destIP, destPort);
Carson Labradof52c03c2022-03-23 18:50:15 +0000681 BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
682 }
683 else
684 {
685 BMCWEB_LOG_DEBUG << "Using existing connection pool for "
686 << clientKey;
687 }
688
689 // Get the associated retry policy
690 auto policy = retryInfo.try_emplace(retryPolicyName);
691 if (policy.second)
692 {
693 BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
694 << "\" with default values";
Carson Labradof52c03c2022-03-23 18:50:15 +0000695 }
696
697 // Send the data using either the existing connection pool or the newly
698 // created connection pool
Carson Labrado244256c2022-04-27 17:16:32 +0000699 result.first->second->sendData(data, destUri, httpHeader, verb,
700 policy.first->second, resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000701 }
702
Carson Labradoa7a80292022-06-01 16:01:52 +0000703 void setRetryConfig(
704 const uint32_t retryAttempts, const uint32_t retryTimeoutInterval,
705 const std::function<boost::system::error_code(unsigned int respCode)>&
706 invalidResp,
707 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000708 {
709 // We need to create the retry policy if one does not already exist for
710 // the given retryPolicyName
711 auto result = retryInfo.try_emplace(retryPolicyName);
712 if (result.second)
713 {
714 BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
715 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000716 }
717 else
718 {
719 BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
720 << retryPolicyName << "\"";
721 }
722
723 result.first->second.maxRetryAttempts = retryAttempts;
724 result.first->second.retryIntervalSecs =
725 std::chrono::seconds(retryTimeoutInterval);
Carson Labradoa7a80292022-06-01 16:01:52 +0000726 result.first->second.invalidResp = invalidResp;
Carson Labradof52c03c2022-03-23 18:50:15 +0000727 }
728
729 void setRetryPolicy(const std::string& retryPolicy,
730 const std::string& retryPolicyName)
731 {
732 // We need to create the retry policy if one does not already exist for
733 // the given retryPolicyName
734 auto result = retryInfo.try_emplace(retryPolicyName);
735 if (result.second)
736 {
737 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
738 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000739 }
740 else
741 {
742 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
743 << retryPolicyName << "\"";
744 }
745
746 result.first->second.retryPolicyAction = retryPolicy;
747 }
748};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530749} // namespace crow