blob: a28c1c42459fec9f20d3d60f3aa1bc91ee820a41 [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 Labradoa7a80292022-06-01 16:01:52 +000063static inline boost::system::error_code
64 defaultRetryHandler(unsigned int respCode)
65{
66 // As a default, assume 200X is alright
67 BMCWEB_LOG_DEBUG << "Using default check for response code validity";
68 if ((respCode < 200) || (respCode >= 300))
69 {
70 return boost::system::errc::make_error_code(
71 boost::system::errc::result_out_of_range);
72 }
73
74 // Return 0 if the response code is valid
75 return boost::system::errc::make_error_code(boost::system::errc::success);
76};
77
Carson Labradof52c03c2022-03-23 18:50:15 +000078// We need to allow retry information to be set before a message has been sent
79// and a connection pool has been created
80struct RetryPolicyData
81{
82 uint32_t maxRetryAttempts = 5;
83 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
84 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradoa7a80292022-06-01 16:01:52 +000085 std::function<boost::system::error_code(unsigned int respCode)>
86 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +000087};
88
89struct PendingRequest
90{
Carson Labrado244256c2022-04-27 17:16:32 +000091 boost::beast::http::request<boost::beast::http::string_body> req;
Carson Labrado039a47e2022-04-05 16:03:20 +000092 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +000093 RetryPolicyData retryPolicy;
Carson Labrado039a47e2022-04-05 16:03:20 +000094 PendingRequest(
Carson Labrado244256c2022-04-27 17:16:32 +000095 boost::beast::http::request<boost::beast::http::string_body>&& req,
Carson Labrado039a47e2022-04-05 16:03:20 +000096 const std::function<void(bool, uint32_t, Response&)>& callback,
97 const RetryPolicyData& retryPolicy) :
Carson Labrado244256c2022-04-27 17:16:32 +000098 req(std::move(req)),
Carson Labradof52c03c2022-03-23 18:50:15 +000099 callback(callback), retryPolicy(retryPolicy)
100 {}
101};
102
103class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530104{
105 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000106 ConnState state = ConnState::initialized;
107 uint32_t retryCount = 0;
108 bool runningTimer = false;
109 std::string subId;
110 std::string host;
111 uint16_t port;
112 uint32_t connId;
113
114 // Retry policy information
115 // This should be updated before each message is sent
116 RetryPolicyData retryPolicy;
117
118 // Data buffers
AppaRao Pulibd030d02020-03-20 03:34:29 +0530119 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530120 std::optional<
121 boost::beast::http::response_parser<boost::beast::http::string_body>>
122 parser;
Carson Labradof52c03c2022-03-23 18:50:15 +0000123 boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000124 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530125
Carson Labradof52c03c2022-03-23 18:50:15 +0000126 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000127 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000128 crow::async_resolve::Resolver resolver;
129 boost::beast::tcp_stream conn;
130 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700131
Carson Labradof52c03c2022-03-23 18:50:15 +0000132 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530133
Sunitha Harish29a82b02021-02-18 15:54:16 +0530134 void doResolve()
135 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530136 state = ConnState::resolveInProgress;
Carson Labradof52c03c2022-03-23 18:50:15 +0000137 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
138 << std::to_string(port)
139 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530140
141 auto respHandler =
142 [self(shared_from_this())](
143 const boost::beast::error_code ec,
144 const std::vector<boost::asio::ip::tcp::endpoint>&
145 endpointList) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700146 if (ec || (endpointList.empty()))
147 {
148 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
149 self->state = ConnState::resolveFailed;
150 self->waitAndRetry();
151 return;
152 }
153 BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
154 << std::to_string(self->port)
155 << ", id: " << std::to_string(self->connId);
156 self->doConnect(endpointList);
157 };
Carson Labradof52c03c2022-03-23 18:50:15 +0000158
Sunitha Harish29a82b02021-02-18 15:54:16 +0530159 resolver.asyncResolve(host, port, std::move(respHandler));
160 }
161
162 void doConnect(
163 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530164 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530165 state = ConnState::connectInProgress;
166
Carson Labradof52c03c2022-03-23 18:50:15 +0000167 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
168 << std::to_string(port)
169 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530170
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530171 conn.expires_after(std::chrono::seconds(30));
Ed Tanous002d39b2022-05-31 08:59:27 -0700172 conn.async_connect(endpointList,
173 [self(shared_from_this())](
174 const boost::beast::error_code ec,
175 const boost::asio::ip::tcp::endpoint& endpoint) {
176 if (ec)
177 {
178 BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
179 << ":" << std::to_string(endpoint.port())
180 << ", id: " << std::to_string(self->connId)
181 << " failed: " << ec.message();
182 self->state = ConnState::connectFailed;
183 self->waitAndRetry();
184 return;
185 }
186 BMCWEB_LOG_DEBUG
187 << "Connected to: " << endpoint.address().to_string() << ":"
188 << std::to_string(endpoint.port())
189 << ", id: " << std::to_string(self->connId);
190 self->state = ConnState::connected;
191 self->sendMessage();
192 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530193 }
194
Carson Labradof52c03c2022-03-23 18:50:15 +0000195 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530196 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530197 state = ConnState::sendInProgress;
198
AppaRao Pulibd030d02020-03-20 03:34:29 +0530199 // Set a timeout on the operation
200 conn.expires_after(std::chrono::seconds(30));
201
202 // Send the HTTP request to the remote host
203 boost::beast::http::async_write(
204 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530205 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530206 const std::size_t& bytesTransferred) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700207 if (ec)
208 {
209 BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
210 self->state = ConnState::sendFailed;
211 self->waitAndRetry();
212 return;
213 }
214 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
215 << bytesTransferred;
216 boost::ignore_unused(bytesTransferred);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530217
Ed Tanous002d39b2022-05-31 08:59:27 -0700218 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530219 });
220 }
221
222 void recvMessage()
223 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530224 state = ConnState::recvInProgress;
225
226 parser.emplace(std::piecewise_construct, std::make_tuple());
227 parser->body_limit(httpReadBodyLimit);
228
AppaRao Pulibd030d02020-03-20 03:34:29 +0530229 // Receive the HTTP response
230 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530231 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530232 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530233 const std::size_t& bytesTransferred) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700234 if (ec)
235 {
236 BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
237 self->state = ConnState::recvFailed;
238 self->waitAndRetry();
239 return;
240 }
241 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
242 << bytesTransferred;
243 BMCWEB_LOG_DEBUG << "recvMessage() data: "
244 << self->parser->get().body();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530245
Ed Tanous002d39b2022-05-31 08:59:27 -0700246 unsigned int respCode = self->parser->get().result_int();
247 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
248 << respCode;
249
Carson Labradoa7a80292022-06-01 16:01:52 +0000250 // Make sure the received response code is valid as defined by
251 // the associated retry policy
252 if (self->retryPolicy.invalidResp(respCode))
Ed Tanous002d39b2022-05-31 08:59:27 -0700253 {
254 // The listener failed to receive the Sent-Event
255 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
256 "receive Sent-Event. Header Response Code: "
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530257 << respCode;
Ed Tanous002d39b2022-05-31 08:59:27 -0700258 self->state = ConnState::recvFailed;
259 self->waitAndRetry();
260 return;
261 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530262
Ed Tanous002d39b2022-05-31 08:59:27 -0700263 // Send is successful
264 // Reset the counter just in case this was after retrying
265 self->retryCount = 0;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530266
Ed Tanous002d39b2022-05-31 08:59:27 -0700267 // Keep the connection alive if server supports it
268 // Else close the connection
269 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
270 << self->parser->keep_alive();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530271
Ed Tanous002d39b2022-05-31 08:59:27 -0700272 // Copy the response into a Response object so that it can be
273 // processed by the callback function.
274 self->res.clear();
275 self->res.stringResponse = self->parser->release();
276 self->callback(self->parser->keep_alive(), self->connId, self->res);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530277 });
278 }
279
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530280 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530281 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000282 if (retryCount >= retryPolicy.maxRetryAttempts)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530283 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530284 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
Carson Labradof52c03c2022-03-23 18:50:15 +0000285 BMCWEB_LOG_DEBUG << "Retry policy: "
286 << retryPolicy.retryPolicyAction;
Carson Labrado039a47e2022-04-05 16:03:20 +0000287
288 // We want to return a 502 to indicate there was an error with the
289 // external server
290 res.clear();
291 redfish::messages::operationFailed(res);
292
Carson Labradof52c03c2022-03-23 18:50:15 +0000293 if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530294 {
295 // TODO: delete subscription
296 state = ConnState::terminated;
Carson Labrado039a47e2022-04-05 16:03:20 +0000297 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530298 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000299 if (retryPolicy.retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530300 {
301 state = ConnState::suspended;
Carson Labrado039a47e2022-04-05 16:03:20 +0000302 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530303 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530304 // Reset the retrycount to zero so that client can try connecting
305 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700306 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530307 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530308 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530309
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530310 if (runningTimer)
311 {
312 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
313 return;
314 }
315 runningTimer = true;
316
317 retryCount++;
318
Carson Labradof52c03c2022-03-23 18:50:15 +0000319 BMCWEB_LOG_DEBUG << "Attempt retry after "
320 << std::to_string(
321 retryPolicy.retryIntervalSecs.count())
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530322 << " seconds. RetryCount = " << retryCount;
Carson Labradof52c03c2022-03-23 18:50:15 +0000323 timer.expires_after(retryPolicy.retryIntervalSecs);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530324 timer.async_wait(
Carson Labradof52c03c2022-03-23 18:50:15 +0000325 [self(shared_from_this())](const boost::system::error_code ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700326 if (ec == boost::asio::error::operation_aborted)
327 {
328 BMCWEB_LOG_DEBUG
329 << "async_wait failed since the operation is aborted"
330 << ec.message();
331 }
332 else if (ec)
333 {
334 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
335 // Ignore the error and continue the retry loop to attempt
336 // sending the event as per the retry policy
337 }
338 self->runningTimer = false;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530339
Ed Tanous002d39b2022-05-31 08:59:27 -0700340 // Let's close the connection and restart from resolve.
341 self->doCloseAndRetry();
342 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530343 }
344
Carson Labradof52c03c2022-03-23 18:50:15 +0000345 void doClose()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530346 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000347 state = ConnState::closeInProgress;
348 boost::beast::error_code ec;
349 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
350 conn.close();
351
352 // not_connected happens sometimes so don't bother reporting it.
353 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530354 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000355 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
356 << ", id: " << std::to_string(connId)
357 << "shutdown failed: " << ec.message();
358 return;
359 }
360 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
361 << ", id: " << std::to_string(connId)
362 << " closed gracefully";
363 if ((state != ConnState::suspended) && (state != ConnState::terminated))
364 {
365 state = ConnState::closed;
366 }
367 }
368
369 void doCloseAndRetry()
370 {
371 state = ConnState::closeInProgress;
372 boost::beast::error_code ec;
373 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
374 conn.close();
375
376 // not_connected happens sometimes so don't bother reporting it.
377 if (ec && ec != boost::beast::errc::not_connected)
378 {
379 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
380 << ", id: " << std::to_string(connId)
381 << "shutdown failed: " << ec.message();
382 return;
383 }
384 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
385 << ", id: " << std::to_string(connId)
386 << " closed gracefully";
387 if ((state != ConnState::suspended) && (state != ConnState::terminated))
388 {
389 // Now let's try to resend the data
390 state = ConnState::retry;
391 this->doResolve();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530392 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530393 }
394
395 public:
Carson Labradof52c03c2022-03-23 18:50:15 +0000396 explicit ConnectionInfo(boost::asio::io_context& ioc, const std::string& id,
397 const std::string& destIP, const uint16_t destPort,
Carson Labradof52c03c2022-03-23 18:50:15 +0000398 const unsigned int connId) :
399 subId(id),
Carson Labrado244256c2022-04-27 17:16:32 +0000400 host(destIP), port(destPort), connId(connId), conn(ioc), timer(ioc)
401 {}
Carson Labradof52c03c2022-03-23 18:50:15 +0000402};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530403
Carson Labradof52c03c2022-03-23 18:50:15 +0000404class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
405{
406 private:
407 boost::asio::io_context& ioc;
408 const std::string id;
409 const std::string destIP;
410 const uint16_t destPort;
Carson Labradof52c03c2022-03-23 18:50:15 +0000411 std::vector<std::shared_ptr<ConnectionInfo>> connections;
412 boost::container::devector<PendingRequest> requestQueue;
413
414 friend class HttpClient;
415
Carson Labrado244256c2022-04-27 17:16:32 +0000416 // Configure a connections's request, callback, and retry info in
417 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000418 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530419 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000420 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530421 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000422 BMCWEB_LOG_ERROR
423 << "setConnProps() should not have been called when requestQueue is empty";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530424 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530425 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530426
Carson Labrado244256c2022-04-27 17:16:32 +0000427 auto nextReq = requestQueue.front();
428 conn.retryPolicy = std::move(nextReq.retryPolicy);
429 conn.req = std::move(nextReq.req);
430 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000431
432 BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
433 << ":" << std::to_string(conn.port)
Carson Labradoa7a80292022-06-01 16:01:52 +0000434 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000435
436 // We can remove the request from the queue at this point
437 requestQueue.pop_front();
438 }
439
440 // Configures a connection to use the specific retry policy.
441 inline void setConnRetryPolicy(ConnectionInfo& conn,
442 const RetryPolicyData& retryPolicy)
443 {
444 BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
Carson Labradoa7a80292022-06-01 16:01:52 +0000445 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000446
447 conn.retryPolicy = retryPolicy;
448 }
449
450 // Gets called as part of callback after request is sent
451 // Reuses the connection if there are any requests waiting to be sent
452 // Otherwise closes the connection if it is not a keep-alive
453 void sendNext(bool keepAlive, uint32_t connId)
454 {
455 auto conn = connections[connId];
456 // Reuse the connection to send the next request in the queue
457 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530458 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000459 BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
460 << " requests remaining in queue for " << destIP
461 << ":" << std::to_string(destPort)
462 << ", reusing connnection "
463 << std::to_string(connId);
464
465 setConnProps(*conn);
466
467 if (keepAlive)
468 {
469 conn->sendMessage();
470 }
471 else
472 {
473 // Server is not keep-alive enabled so we need to close the
474 // connection and then start over from resolve
475 conn->doClose();
476 conn->doResolve();
477 }
478 return;
479 }
480
481 // No more messages to send so close the connection if necessary
482 if (keepAlive)
483 {
484 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530485 }
486 else
487 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000488 // Abort the connection since server is not keep-alive enabled
489 conn->state = ConnState::abortConnection;
490 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530491 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530492 }
493
Carson Labrado244256c2022-04-27 17:16:32 +0000494 void sendData(std::string& data, const std::string& destUri,
495 const boost::beast::http::fields& httpHeader,
496 const boost::beast::http::verb verb,
497 const RetryPolicyData& retryPolicy,
Carson Labrado039a47e2022-04-05 16:03:20 +0000498 std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530499 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000500 std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
501
502 // Callback to be called once the request has been sent
Carson Labrado039a47e2022-04-05 16:03:20 +0000503 auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
504 Response& res) {
505 // Allow provided callback to perform additional processing of the
506 // request
507 resHandler(res);
508
Carson Labradof52c03c2022-03-23 18:50:15 +0000509 // If requests remain in the queue then we want to reuse this
510 // connection to send the next request
511 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
512 if (!self)
513 {
514 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
515 return;
516 }
517
518 self->sendNext(keepAlive, connId);
519 };
520
Carson Labrado244256c2022-04-27 17:16:32 +0000521 // Construct the request to be sent
522 boost::beast::http::request<boost::beast::http::string_body> thisReq(
523 verb, destUri, 11, "", httpHeader);
524 thisReq.set(boost::beast::http::field::host, destIP);
525 thisReq.keep_alive(true);
526 thisReq.body() = std::move(data);
527 thisReq.prepare_payload();
528
Carson Labradof52c03c2022-03-23 18:50:15 +0000529 // Reuse an existing connection if one is available
530 for (unsigned int i = 0; i < connections.size(); i++)
531 {
532 auto conn = connections[i];
533 if ((conn->state == ConnState::idle) ||
534 (conn->state == ConnState::initialized) ||
535 (conn->state == ConnState::closed))
536 {
Carson Labrado244256c2022-04-27 17:16:32 +0000537 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000538 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000539 setConnRetryPolicy(*conn, retryPolicy);
540 std::string commonMsg = std::to_string(i) + " from pool " +
541 destIP + ":" + std::to_string(destPort);
542
543 if (conn->state == ConnState::idle)
544 {
545 BMCWEB_LOG_DEBUG << "Grabbing idle connection "
546 << commonMsg;
547 conn->sendMessage();
548 }
549 else
550 {
551 BMCWEB_LOG_DEBUG << "Reusing existing connection "
552 << commonMsg;
553 conn->doResolve();
554 }
555 return;
556 }
557 }
558
559 // All connections in use so create a new connection or add request to
560 // the queue
561 if (connections.size() < maxPoolSize)
562 {
563 BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
564 << ":" << std::to_string(destPort);
565 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000566 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000567 conn->callback = std::move(cb);
568 setConnRetryPolicy(*conn, retryPolicy);
569 conn->doResolve();
570 }
571 else if (requestQueue.size() < maxRequestQueueSize)
572 {
573 BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
Carson Labrado244256c2022-04-27 17:16:32 +0000574 requestQueue.emplace_back(std::move(thisReq), std::move(cb),
Carson Labradof52c03c2022-03-23 18:50:15 +0000575 retryPolicy);
576 }
577 else
578 {
579 BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
580 << " request queue full. Dropping request.";
581 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530582 }
583
Carson Labradof52c03c2022-03-23 18:50:15 +0000584 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530585 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000586 unsigned int newId = static_cast<unsigned int>(connections.size());
587
Carson Labrado244256c2022-04-27 17:16:32 +0000588 auto& ret = connections.emplace_back(
589 std::make_shared<ConnectionInfo>(ioc, id, destIP, destPort, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000590
591 BMCWEB_LOG_DEBUG << "Added connection "
592 << std::to_string(connections.size() - 1)
593 << " to pool " << destIP << ":"
594 << std::to_string(destPort);
595
596 return ret;
597 }
598
599 public:
600 explicit ConnectionPool(boost::asio::io_context& ioc, const std::string& id,
Carson Labrado244256c2022-04-27 17:16:32 +0000601 const std::string& destIP,
602 const uint16_t destPort) :
Carson Labradof52c03c2022-03-23 18:50:15 +0000603 ioc(ioc),
Carson Labrado244256c2022-04-27 17:16:32 +0000604 id(id), destIP(destIP), destPort(destPort)
Carson Labradof52c03c2022-03-23 18:50:15 +0000605 {
606 std::string clientKey = destIP + ":" + std::to_string(destPort);
607 BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
608 << std::to_string(destPort);
609
610 // Initialize the pool with a single connection
611 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530612 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530613};
614
Carson Labradof52c03c2022-03-23 18:50:15 +0000615class HttpClient
616{
617 private:
618 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
619 connectionPools;
620 boost::asio::io_context& ioc =
621 crow::connections::systemBus->get_io_context();
622 std::unordered_map<std::string, RetryPolicyData> retryInfo;
623 HttpClient() = default;
624
Carson Labrado039a47e2022-04-05 16:03:20 +0000625 // Used as a dummy callback by sendData() in order to call
626 // sendDataWithCallback()
627 static void genericResHandler(Response& res)
628 {
629 BMCWEB_LOG_DEBUG << "Response handled with return code: "
630 << std::to_string(res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700631 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000632
Carson Labradof52c03c2022-03-23 18:50:15 +0000633 public:
634 HttpClient(const HttpClient&) = delete;
635 HttpClient& operator=(const HttpClient&) = delete;
636 HttpClient(HttpClient&&) = delete;
637 HttpClient& operator=(HttpClient&&) = delete;
638 ~HttpClient() = default;
639
640 static HttpClient& getInstance()
641 {
642 static HttpClient handler;
643 return handler;
644 }
645
Carson Labrado039a47e2022-04-05 16:03:20 +0000646 // Send a request to destIP:destPort where additional processing of the
647 // result is not required
Carson Labradof52c03c2022-03-23 18:50:15 +0000648 void sendData(std::string& data, const std::string& id,
649 const std::string& destIP, const uint16_t destPort,
650 const std::string& destUri,
651 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000652 const boost::beast::http::verb verb,
653 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000654 {
Carson Labrado039a47e2022-04-05 16:03:20 +0000655 std::function<void(Response&)> cb = genericResHandler;
656 sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000657 verb, retryPolicyName, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000658 }
659
660 // Send request to destIP:destPort and use the provided callback to
661 // handle the response
662 void sendDataWithCallback(std::string& data, const std::string& id,
663 const std::string& destIP,
664 const uint16_t destPort,
665 const std::string& destUri,
666 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000667 const boost::beast::http::verb verb,
668 const std::string& retryPolicyName,
Carson Labrado039a47e2022-04-05 16:03:20 +0000669 std::function<void(Response&)>& resHandler)
670 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000671 std::string clientKey = destIP + ":" + std::to_string(destPort);
672 // Use nullptr to avoid creating a ConnectionPool each time
673 auto result = connectionPools.try_emplace(clientKey, nullptr);
674 if (result.second)
675 {
676 // Now actually create the ConnectionPool shared_ptr since it does
677 // not already exist
Carson Labrado244256c2022-04-27 17:16:32 +0000678 result.first->second =
679 std::make_shared<ConnectionPool>(ioc, id, destIP, destPort);
Carson Labradof52c03c2022-03-23 18:50:15 +0000680 BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
681 }
682 else
683 {
684 BMCWEB_LOG_DEBUG << "Using existing connection pool for "
685 << clientKey;
686 }
687
688 // Get the associated retry policy
689 auto policy = retryInfo.try_emplace(retryPolicyName);
690 if (policy.second)
691 {
692 BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
693 << "\" with default values";
Carson Labradof52c03c2022-03-23 18:50:15 +0000694 }
695
696 // Send the data using either the existing connection pool or the newly
697 // created connection pool
Carson Labrado244256c2022-04-27 17:16:32 +0000698 result.first->second->sendData(data, destUri, httpHeader, verb,
699 policy.first->second, resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000700 }
701
Carson Labradoa7a80292022-06-01 16:01:52 +0000702 void setRetryConfig(
703 const uint32_t retryAttempts, const uint32_t retryTimeoutInterval,
704 const std::function<boost::system::error_code(unsigned int respCode)>&
705 invalidResp,
706 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000707 {
708 // We need to create the retry policy if one does not already exist for
709 // the given retryPolicyName
710 auto result = retryInfo.try_emplace(retryPolicyName);
711 if (result.second)
712 {
713 BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
714 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000715 }
716 else
717 {
718 BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
719 << retryPolicyName << "\"";
720 }
721
722 result.first->second.maxRetryAttempts = retryAttempts;
723 result.first->second.retryIntervalSecs =
724 std::chrono::seconds(retryTimeoutInterval);
Carson Labradoa7a80292022-06-01 16:01:52 +0000725 result.first->second.invalidResp = invalidResp;
Carson Labradof52c03c2022-03-23 18:50:15 +0000726 }
727
728 void setRetryPolicy(const std::string& retryPolicy,
729 const std::string& retryPolicyName)
730 {
731 // We need to create the retry policy if one does not already exist for
732 // the given retryPolicyName
733 auto result = retryInfo.try_emplace(retryPolicyName);
734 if (result.second)
735 {
736 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
737 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000738 }
739 else
740 {
741 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
742 << retryPolicyName << "\"";
743 }
744
745 result.first->second.retryPolicyAction = retryPolicy;
746 }
747};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530748} // namespace crow