blob: a67244750683fd2d32a7f27306924876232136c7 [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{
75 std::string requestData;
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(
79 const std::string& requestData,
80 const std::function<void(bool, uint32_t, Response&)>& callback,
81 const RetryPolicyData& retryPolicy) :
Carson Labradof52c03c2022-03-23 18:50:15 +000082 requestData(requestData),
83 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
103 std::string data;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530104 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530105 std::optional<
106 boost::beast::http::response_parser<boost::beast::http::string_body>>
107 parser;
Carson Labradof52c03c2022-03-23 18:50:15 +0000108 boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000109 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530110
Carson Labradof52c03c2022-03-23 18:50:15 +0000111 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000112 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000113 crow::async_resolve::Resolver resolver;
114 boost::beast::tcp_stream conn;
115 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700116
Carson Labradof52c03c2022-03-23 18:50:15 +0000117 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530118
Sunitha Harish29a82b02021-02-18 15:54:16 +0530119 void doResolve()
120 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530121 state = ConnState::resolveInProgress;
Carson Labradof52c03c2022-03-23 18:50:15 +0000122 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
123 << std::to_string(port)
124 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530125
126 auto respHandler =
127 [self(shared_from_this())](
128 const boost::beast::error_code ec,
129 const std::vector<boost::asio::ip::tcp::endpoint>&
130 endpointList) {
Ed Tanous26f69762022-01-25 09:49:11 -0800131 if (ec || (endpointList.empty()))
Sunitha Harish29a82b02021-02-18 15:54:16 +0530132 {
133 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
134 self->state = ConnState::resolveFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000135 self->waitAndRetry();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530136 return;
137 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000138 BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
139 << std::to_string(self->port)
140 << ", id: " << std::to_string(self->connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530141 self->doConnect(endpointList);
142 };
Carson Labradof52c03c2022-03-23 18:50:15 +0000143
Sunitha Harish29a82b02021-02-18 15:54:16 +0530144 resolver.asyncResolve(host, port, std::move(respHandler));
145 }
146
147 void doConnect(
148 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530149 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530150 state = ConnState::connectInProgress;
151
Carson Labradof52c03c2022-03-23 18:50:15 +0000152 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
153 << std::to_string(port)
154 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530155
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530156 conn.expires_after(std::chrono::seconds(30));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530157 conn.async_connect(
158 endpointList, [self(shared_from_this())](
159 const boost::beast::error_code ec,
160 const boost::asio::ip::tcp::endpoint& endpoint) {
161 if (ec)
162 {
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800163 BMCWEB_LOG_ERROR << "Connect "
Carson Labradof52c03c2022-03-23 18:50:15 +0000164 << endpoint.address().to_string() << ":"
165 << std::to_string(endpoint.port())
166 << ", id: " << std::to_string(self->connId)
Sunitha Harish29a82b02021-02-18 15:54:16 +0530167 << " failed: " << ec.message();
168 self->state = ConnState::connectFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000169 self->waitAndRetry();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530170 return;
171 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000172 BMCWEB_LOG_DEBUG
173 << "Connected to: " << endpoint.address().to_string() << ":"
174 << std::to_string(endpoint.port())
175 << ", id: " << std::to_string(self->connId);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530176 self->state = ConnState::connected;
Carson Labradof52c03c2022-03-23 18:50:15 +0000177 self->sendMessage();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530178 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530179 }
180
Carson Labradof52c03c2022-03-23 18:50:15 +0000181 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530182 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530183 state = ConnState::sendInProgress;
184
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530185 req.body() = data;
186 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530187
188 // Set a timeout on the operation
189 conn.expires_after(std::chrono::seconds(30));
190
191 // Send the HTTP request to the remote host
192 boost::beast::http::async_write(
193 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530194 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530195 const std::size_t& bytesTransferred) {
196 if (ec)
197 {
198 BMCWEB_LOG_ERROR << "sendMessage() failed: "
199 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530200 self->state = ConnState::sendFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000201 self->waitAndRetry();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530202 return;
203 }
204 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
205 << bytesTransferred;
206 boost::ignore_unused(bytesTransferred);
207
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530208 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530209 });
210 }
211
212 void recvMessage()
213 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530214 state = ConnState::recvInProgress;
215
216 parser.emplace(std::piecewise_construct, std::make_tuple());
217 parser->body_limit(httpReadBodyLimit);
218
AppaRao Pulibd030d02020-03-20 03:34:29 +0530219 // Receive the HTTP response
220 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530221 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530222 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530223 const std::size_t& bytesTransferred) {
224 if (ec)
225 {
226 BMCWEB_LOG_ERROR << "recvMessage() failed: "
227 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530228 self->state = ConnState::recvFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000229 self->waitAndRetry();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530230 return;
231 }
232 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
233 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530234 BMCWEB_LOG_DEBUG << "recvMessage() data: "
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800235 << self->parser->get().body();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530236
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530237 unsigned int respCode = self->parser->get().result_int();
238 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
239 << respCode;
240
241 // 2XX response is considered to be successful
242 if ((respCode < 200) || (respCode >= 300))
243 {
244 // The listener failed to receive the Sent-Event
Sunitha Harish7adb85a2021-10-26 03:10:04 -0500245 BMCWEB_LOG_ERROR
246 << "recvMessage() Listener Failed to "
247 "receive Sent-Event. Header Response Code: "
248 << respCode;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530249 self->state = ConnState::recvFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000250 self->waitAndRetry();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530251 return;
252 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530253
Carson Labradof52c03c2022-03-23 18:50:15 +0000254 // Send is successful
255 // Reset the counter just in case this was after retrying
256 self->retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530257
258 // Keep the connection alive if server supports it
259 // Else close the connection
260 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
261 << self->parser->keep_alive();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530262
Carson Labrado039a47e2022-04-05 16:03:20 +0000263 // Copy the response into a Response object so that it can be
264 // processed by the callback function.
265 self->res.clear();
266 self->res.stringResponse = self->parser->release();
267 self->callback(self->parser->keep_alive(), self->connId,
268 self->res);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530269 });
270 }
271
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530272 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530273 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000274 if (retryCount >= retryPolicy.maxRetryAttempts)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530275 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530276 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
Carson Labradof52c03c2022-03-23 18:50:15 +0000277 BMCWEB_LOG_DEBUG << "Retry policy: "
278 << retryPolicy.retryPolicyAction;
Carson Labrado039a47e2022-04-05 16:03:20 +0000279
280 // We want to return a 502 to indicate there was an error with the
281 // external server
282 res.clear();
283 redfish::messages::operationFailed(res);
284
Carson Labradof52c03c2022-03-23 18:50:15 +0000285 if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530286 {
287 // TODO: delete subscription
288 state = ConnState::terminated;
Carson Labrado039a47e2022-04-05 16:03:20 +0000289 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530290 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000291 if (retryPolicy.retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530292 {
293 state = ConnState::suspended;
Carson Labrado039a47e2022-04-05 16:03:20 +0000294 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530295 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530296 // Reset the retrycount to zero so that client can try connecting
297 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700298 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530299 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530300 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530301
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530302 if (runningTimer)
303 {
304 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
305 return;
306 }
307 runningTimer = true;
308
309 retryCount++;
310
Carson Labradof52c03c2022-03-23 18:50:15 +0000311 BMCWEB_LOG_DEBUG << "Attempt retry after "
312 << std::to_string(
313 retryPolicy.retryIntervalSecs.count())
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530314 << " seconds. RetryCount = " << retryCount;
Carson Labradof52c03c2022-03-23 18:50:15 +0000315 timer.expires_after(retryPolicy.retryIntervalSecs);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530316 timer.async_wait(
Carson Labradof52c03c2022-03-23 18:50:15 +0000317 [self(shared_from_this())](const boost::system::error_code ec) {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530318 if (ec == boost::asio::error::operation_aborted)
319 {
320 BMCWEB_LOG_DEBUG
321 << "async_wait failed since the operation is aborted"
322 << ec.message();
323 }
324 else if (ec)
325 {
326 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
327 // Ignore the error and continue the retry loop to attempt
328 // sending the event as per the retry policy
329 }
330 self->runningTimer = false;
331
Carson Labradof52c03c2022-03-23 18:50:15 +0000332 // Let's close the connection and restart from resolve.
333 self->doCloseAndRetry();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530334 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530335 }
336
Carson Labradof52c03c2022-03-23 18:50:15 +0000337 void doClose()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530338 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000339 state = ConnState::closeInProgress;
340 boost::beast::error_code ec;
341 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
342 conn.close();
343
344 // not_connected happens sometimes so don't bother reporting it.
345 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530346 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000347 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
348 << ", id: " << std::to_string(connId)
349 << "shutdown failed: " << ec.message();
350 return;
351 }
352 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
353 << ", id: " << std::to_string(connId)
354 << " closed gracefully";
355 if ((state != ConnState::suspended) && (state != ConnState::terminated))
356 {
357 state = ConnState::closed;
358 }
359 }
360
361 void doCloseAndRetry()
362 {
363 state = ConnState::closeInProgress;
364 boost::beast::error_code ec;
365 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
366 conn.close();
367
368 // not_connected happens sometimes so don't bother reporting it.
369 if (ec && ec != boost::beast::errc::not_connected)
370 {
371 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
372 << ", id: " << std::to_string(connId)
373 << "shutdown failed: " << ec.message();
374 return;
375 }
376 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
377 << ", id: " << std::to_string(connId)
378 << " closed gracefully";
379 if ((state != ConnState::suspended) && (state != ConnState::terminated))
380 {
381 // Now let's try to resend the data
382 state = ConnState::retry;
383 this->doResolve();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530384 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530385 }
386
387 public:
Carson Labradof52c03c2022-03-23 18:50:15 +0000388 explicit ConnectionInfo(boost::asio::io_context& ioc, const std::string& id,
389 const std::string& destIP, const uint16_t destPort,
390 const std::string& destUri,
391 const boost::beast::http::fields& httpHeader,
392 const unsigned int connId) :
393 subId(id),
394 host(destIP), port(destPort), connId(connId),
Ed Tanous4da04452021-09-08 19:57:44 -0700395 req(boost::beast::http::verb::post, destUri, 11, "", httpHeader),
Carson Labradof52c03c2022-03-23 18:50:15 +0000396 conn(ioc), timer(ioc)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530397 {
Ed Tanous4da04452021-09-08 19:57:44 -0700398 req.set(boost::beast::http::field::host, host);
399 req.keep_alive(true);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530400 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000401};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530402
Carson Labradof52c03c2022-03-23 18:50:15 +0000403class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
404{
405 private:
406 boost::asio::io_context& ioc;
407 const std::string id;
408 const std::string destIP;
409 const uint16_t destPort;
410 const std::string destUri;
411 const boost::beast::http::fields httpHeader;
412 std::vector<std::shared_ptr<ConnectionInfo>> connections;
413 boost::container::devector<PendingRequest> requestQueue;
414
415 friend class HttpClient;
416
417 // Configure a connections's data, callback, and retry info in preparation
418 // to begin sending a request
419 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 Labradof52c03c2022-03-23 18:50:15 +0000428 auto req = requestQueue.front();
429 conn.retryPolicy = std::move(req.retryPolicy);
430 conn.data = std::move(req.requestData);
431 conn.callback = std::move(req.callback);
432
433 BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
434 << ":" << std::to_string(conn.port)
435 << ", id: " << std::to_string(conn.connId)
436 << ", retry policy is \"" << conn.retryPolicy.name
437 << "\"";
438
439 // We can remove the request from the queue at this point
440 requestQueue.pop_front();
441 }
442
443 // Configures a connection to use the specific retry policy.
444 inline void setConnRetryPolicy(ConnectionInfo& conn,
445 const RetryPolicyData& retryPolicy)
446 {
447 BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
448 << ", id: " << std::to_string(conn.connId)
449 << " using retry policy \"" << retryPolicy.name
450 << "\"";
451
452 conn.retryPolicy = retryPolicy;
453 }
454
455 // Gets called as part of callback after request is sent
456 // Reuses the connection if there are any requests waiting to be sent
457 // Otherwise closes the connection if it is not a keep-alive
458 void sendNext(bool keepAlive, uint32_t connId)
459 {
460 auto conn = connections[connId];
461 // Reuse the connection to send the next request in the queue
462 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530463 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000464 BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
465 << " requests remaining in queue for " << destIP
466 << ":" << std::to_string(destPort)
467 << ", reusing connnection "
468 << std::to_string(connId);
469
470 setConnProps(*conn);
471
472 if (keepAlive)
473 {
474 conn->sendMessage();
475 }
476 else
477 {
478 // Server is not keep-alive enabled so we need to close the
479 // connection and then start over from resolve
480 conn->doClose();
481 conn->doResolve();
482 }
483 return;
484 }
485
486 // No more messages to send so close the connection if necessary
487 if (keepAlive)
488 {
489 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530490 }
491 else
492 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000493 // Abort the connection since server is not keep-alive enabled
494 conn->state = ConnState::abortConnection;
495 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530496 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530497 }
498
Carson Labrado039a47e2022-04-05 16:03:20 +0000499 void sendData(std::string& data, const RetryPolicyData& retryPolicy,
500 std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530501 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000502 std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
503
504 // Callback to be called once the request has been sent
Carson Labrado039a47e2022-04-05 16:03:20 +0000505 auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
506 Response& res) {
507 // Allow provided callback to perform additional processing of the
508 // request
509 resHandler(res);
510
Carson Labradof52c03c2022-03-23 18:50:15 +0000511 // If requests remain in the queue then we want to reuse this
512 // connection to send the next request
513 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
514 if (!self)
515 {
516 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
517 return;
518 }
519
520 self->sendNext(keepAlive, connId);
521 };
522
523 // Reuse an existing connection if one is available
524 for (unsigned int i = 0; i < connections.size(); i++)
525 {
526 auto conn = connections[i];
527 if ((conn->state == ConnState::idle) ||
528 (conn->state == ConnState::initialized) ||
529 (conn->state == ConnState::closed))
530 {
531 conn->data = std::move(data);
532 conn->callback = std::move(cb);
533 conn->retryPolicy = retryPolicy;
534 setConnRetryPolicy(*conn, retryPolicy);
535 std::string commonMsg = std::to_string(i) + " from pool " +
536 destIP + ":" + std::to_string(destPort);
537
538 if (conn->state == ConnState::idle)
539 {
540 BMCWEB_LOG_DEBUG << "Grabbing idle connection "
541 << commonMsg;
542 conn->sendMessage();
543 }
544 else
545 {
546 BMCWEB_LOG_DEBUG << "Reusing existing connection "
547 << commonMsg;
548 conn->doResolve();
549 }
550 return;
551 }
552 }
553
554 // All connections in use so create a new connection or add request to
555 // the queue
556 if (connections.size() < maxPoolSize)
557 {
558 BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
559 << ":" << std::to_string(destPort);
560 auto conn = addConnection();
561 conn->data = std::move(data);
562 conn->callback = std::move(cb);
563 setConnRetryPolicy(*conn, retryPolicy);
564 conn->doResolve();
565 }
566 else if (requestQueue.size() < maxRequestQueueSize)
567 {
568 BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
569 requestQueue.emplace_back(std::move(data), std::move(cb),
570 retryPolicy);
571 }
572 else
573 {
574 BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
575 << " request queue full. Dropping request.";
576 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530577 }
578
Carson Labradof52c03c2022-03-23 18:50:15 +0000579 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530580 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000581 unsigned int newId = static_cast<unsigned int>(connections.size());
582
583 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
584 ioc, id, destIP, destPort, destUri, httpHeader, newId));
585
586 BMCWEB_LOG_DEBUG << "Added connection "
587 << std::to_string(connections.size() - 1)
588 << " to pool " << destIP << ":"
589 << std::to_string(destPort);
590
591 return ret;
592 }
593
594 public:
595 explicit ConnectionPool(boost::asio::io_context& ioc, const std::string& id,
596 const std::string& destIP, const uint16_t destPort,
597 const std::string& destUri,
598 const boost::beast::http::fields& httpHeader) :
599 ioc(ioc),
600 id(id), destIP(destIP), destPort(destPort), destUri(destUri),
601 httpHeader(httpHeader)
602 {
603 std::string clientKey = destIP + ":" + std::to_string(destPort);
604 BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
605 << std::to_string(destPort);
606
607 // Initialize the pool with a single connection
608 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530609 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530610};
611
Carson Labradof52c03c2022-03-23 18:50:15 +0000612class HttpClient
613{
614 private:
615 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
616 connectionPools;
617 boost::asio::io_context& ioc =
618 crow::connections::systemBus->get_io_context();
619 std::unordered_map<std::string, RetryPolicyData> retryInfo;
620 HttpClient() = default;
621
Carson Labrado039a47e2022-04-05 16:03:20 +0000622 // Used as a dummy callback by sendData() in order to call
623 // sendDataWithCallback()
624 static void genericResHandler(Response& res)
625 {
626 BMCWEB_LOG_DEBUG << "Response handled with return code: "
627 << std::to_string(res.resultInt());
628 };
629
Carson Labradof52c03c2022-03-23 18:50:15 +0000630 public:
631 HttpClient(const HttpClient&) = delete;
632 HttpClient& operator=(const HttpClient&) = delete;
633 HttpClient(HttpClient&&) = delete;
634 HttpClient& operator=(HttpClient&&) = delete;
635 ~HttpClient() = default;
636
637 static HttpClient& getInstance()
638 {
639 static HttpClient handler;
640 return handler;
641 }
642
Carson Labrado039a47e2022-04-05 16:03:20 +0000643 // Send a request to destIP:destPort where additional processing of the
644 // result is not required
Carson Labradof52c03c2022-03-23 18:50:15 +0000645 void sendData(std::string& data, const std::string& id,
646 const std::string& destIP, const uint16_t destPort,
647 const std::string& destUri,
648 const boost::beast::http::fields& httpHeader,
649 std::string& retryPolicyName)
650 {
Carson Labrado039a47e2022-04-05 16:03:20 +0000651 std::function<void(Response&)> cb = genericResHandler;
652 sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader,
653 retryPolicyName, cb);
654 }
655
656 // Send request to destIP:destPort and use the provided callback to
657 // handle the response
658 void sendDataWithCallback(std::string& data, const std::string& id,
659 const std::string& destIP,
660 const uint16_t destPort,
661 const std::string& destUri,
662 const boost::beast::http::fields& httpHeader,
663 std::string& retryPolicyName,
664 std::function<void(Response&)>& resHandler)
665 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000666 std::string clientKey = destIP + ":" + std::to_string(destPort);
667 // Use nullptr to avoid creating a ConnectionPool each time
668 auto result = connectionPools.try_emplace(clientKey, nullptr);
669 if (result.second)
670 {
671 // Now actually create the ConnectionPool shared_ptr since it does
672 // not already exist
673 result.first->second = std::make_shared<ConnectionPool>(
674 ioc, id, destIP, destPort, destUri, httpHeader);
675 BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
676 }
677 else
678 {
679 BMCWEB_LOG_DEBUG << "Using existing connection pool for "
680 << clientKey;
681 }
682
683 // Get the associated retry policy
684 auto policy = retryInfo.try_emplace(retryPolicyName);
685 if (policy.second)
686 {
687 BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
688 << "\" with default values";
689 policy.first->second.name = retryPolicyName;
690 }
691
692 // Send the data using either the existing connection pool or the newly
693 // created connection pool
Carson Labrado039a47e2022-04-05 16:03:20 +0000694 result.first->second->sendData(data, policy.first->second, resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000695 }
696
697 void setRetryConfig(const uint32_t retryAttempts,
698 const uint32_t retryTimeoutInterval,
699 const std::string& retryPolicyName)
700 {
701 // We need to create the retry policy if one does not already exist for
702 // the given retryPolicyName
703 auto result = retryInfo.try_emplace(retryPolicyName);
704 if (result.second)
705 {
706 BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
707 << retryPolicyName << "\"";
708 result.first->second.name = retryPolicyName;
709 }
710 else
711 {
712 BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
713 << retryPolicyName << "\"";
714 }
715
716 result.first->second.maxRetryAttempts = retryAttempts;
717 result.first->second.retryIntervalSecs =
718 std::chrono::seconds(retryTimeoutInterval);
719 }
720
721 void setRetryPolicy(const std::string& retryPolicy,
722 const std::string& retryPolicyName)
723 {
724 // We need to create the retry policy if one does not already exist for
725 // the given retryPolicyName
726 auto result = retryInfo.try_emplace(retryPolicyName);
727 if (result.second)
728 {
729 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
730 << retryPolicyName << "\"";
731 result.first->second.name = retryPolicyName;
732 }
733 else
734 {
735 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
736 << retryPolicyName << "\"";
737 }
738
739 result.first->second.retryPolicyAction = retryPolicy;
740 }
741};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530742} // namespace crow