Add forwarding timeout option (#1462)

Fixes #1454.
This commit is contained in:
Sergey Kuznetsov
2024-06-14 16:53:08 +01:00
committed by GitHub
parent 437ea7bf98
commit 1334bd05d9
18 changed files with 319 additions and 53 deletions

View File

@@ -195,6 +195,13 @@ TEST_F(ConfigTest, Array)
ASSERT_TRUE(exp.empty());
}
TEST_F(ConfigTest, toMilliseconds)
{
EXPECT_EQ(Config::toMilliseconds(0.0f).count(), 0);
EXPECT_EQ(Config::toMilliseconds(0.123f).count(), 123);
EXPECT_EQ(Config::toMilliseconds(3.45f).count(), 3450);
}
/**
* @brief Simple custom data type with json parsing support
*/

View File

@@ -29,6 +29,7 @@
#include <algorithm>
#include <chrono>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@@ -37,7 +38,7 @@ using namespace etl::impl;
struct ForwardingSourceTests : SyncAsioContextTest {
TestWsServer server_{ctx, "0.0.0.0", 11114};
ForwardingSource forwardingSource{"127.0.0.1", "11114", std::chrono::milliseconds{1}};
ForwardingSource forwardingSource{"127.0.0.1", "11114", std::chrono::milliseconds{1}, std::chrono::milliseconds{1}};
};
TEST_F(ForwardingSourceTests, ConnectionFailed)
@@ -101,6 +102,19 @@ TEST_F(ForwardingSourceOperationsTests, ReadFailed)
});
}
TEST_F(ForwardingSourceOperationsTests, ReadTimeout)
{
TestWsConnectionPtr connection;
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
connection = std::make_unique<TestWsConnection>(serverConnection(yield));
});
runSpawn([&](boost::asio::yield_context yield) {
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
EXPECT_FALSE(result);
});
}
TEST_F(ForwardingSourceOperationsTests, ParseFailed)
{
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {

View File

@@ -66,15 +66,40 @@ struct LoadBalancerConstructorTests : util::prometheus::WithPrometheus, MockBack
backend,
subscriptionManager_,
networkManager_,
[this](auto&&... args) -> SourcePtr {
return sourceFactory_.makeSourceMock(std::forward<decltype(args)>(args)...);
}
[this](auto&&... args) -> SourcePtr { return sourceFactory_(std::forward<decltype(args)>(args)...); }
);
}
};
TEST_F(LoadBalancerConstructorTests, construct)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
makeLoadBalancer();
}
TEST_F(LoadBalancerConstructorTests, forwardingTimeoutPassedToSourceFactory)
{
auto const forwardingTimeout = 10;
configJson_.as_object()["forwarding"] = boost::json::object{{"timeout", float{forwardingTimeout}}};
EXPECT_CALL(
sourceFactory_,
makeSource(
testing::_,
testing::_,
testing::_,
testing::_,
testing::_,
std::chrono::steady_clock::duration{std::chrono::seconds{forwardingTimeout}},
testing::_,
testing::_,
testing::_
)
)
.Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
@@ -84,6 +109,7 @@ TEST_F(LoadBalancerConstructorTests, construct)
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0Fails)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(1);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(std::nullopt));
EXPECT_CALL(sourceFactory_.sourceAt(0), toString);
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
@@ -91,6 +117,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0Fails)
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0ReturnsError)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(1);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled)
.WillOnce(Return(boost::json::object{{"error", "some error"}}));
EXPECT_CALL(sourceFactory_.sourceAt(0), toString);
@@ -99,6 +126,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0ReturnsError)
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source1Fails)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(std::nullopt));
EXPECT_CALL(sourceFactory_.sourceAt(1), toString);
@@ -110,6 +138,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_DifferentNetworkID)
auto const source1Json = boost::json::parse(R"({"result": {"info": {"network_id": 0}}})");
auto const source2Json = boost::json::parse(R"({"result": {"info": {"network_id": 1}}})");
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(source1Json.as_object()));
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(source2Json.as_object()));
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
@@ -117,6 +146,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_DifferentNetworkID)
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source1FailsButAllowNoEtlIsTrue)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(std::nullopt));
@@ -131,6 +161,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_DifferentNetworkIDButAllowNoE
{
auto const source1Json = boost::json::parse(R"({"result": {"info": {"network_id": 0}}})");
auto const source2Json = boost::json::parse(R"({"result": {"info": {"network_id": 1}}})");
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(source1Json.as_object()));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(source2Json.as_object()));
@@ -152,6 +183,7 @@ TEST_F(LoadBalancerConstructorDeathTest, numMarkersSpecifiedInConfigIsInvalid)
struct LoadBalancerOnConnectHookTests : LoadBalancerConstructorTests {
LoadBalancerOnConnectHookTests()
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
@@ -280,8 +312,9 @@ TEST_F(LoadBalancerOnConnectHookTests, bothSourcesDisconnectAndConnectBack)
struct LoadBalancer3SourcesTests : LoadBalancerConstructorTests {
LoadBalancer3SourcesTests()
{
sourceFactory_ = StrictMockSourceFactory{3};
sourceFactory_.setSourcesNumber(3);
configJson_.as_object()["etl_sources"] = {"source1", "source2", "source3"};
EXPECT_CALL(sourceFactory_, makeSource).Times(3);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
@@ -381,6 +414,7 @@ TEST_F(LoadBalancerLoadInitialLedgerCustomNumMarkersTests, loadInitialLedger)
{
configJson_.as_object()["num_markers"] = numMarkers_;
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
@@ -484,6 +518,7 @@ struct LoadBalancerForwardToRippledTests : LoadBalancerConstructorTests, SyncAsi
TEST_F(LoadBalancerForwardToRippledTests, forward)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_CALL(
sourceFactory_.sourceAt(0),
@@ -498,6 +533,7 @@ TEST_F(LoadBalancerForwardToRippledTests, forward)
TEST_F(LoadBalancerForwardToRippledTests, forwardWithXUserHeader)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_CALL(
sourceFactory_.sourceAt(0),
@@ -512,6 +548,7 @@ TEST_F(LoadBalancerForwardToRippledTests, forwardWithXUserHeader)
TEST_F(LoadBalancerForwardToRippledTests, source0Fails)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_CALL(
sourceFactory_.sourceAt(0),
@@ -531,6 +568,7 @@ TEST_F(LoadBalancerForwardToRippledTests, source0Fails)
TEST_F(LoadBalancerForwardToRippledTests, bothSourcesFail)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_CALL(
sourceFactory_.sourceAt(0),
@@ -550,7 +588,8 @@ TEST_F(LoadBalancerForwardToRippledTests, bothSourcesFail)
TEST_F(LoadBalancerForwardToRippledTests, forwardingCacheEnabled)
{
configJson_.as_object()["forwarding_cache_timeout"] = 10.;
configJson_.as_object()["forwarding"] = boost::json::object{{"cache_timeout", 10.}};
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
auto const request = boost::json::object{{"command", "server_info"}};
@@ -569,13 +608,15 @@ TEST_F(LoadBalancerForwardToRippledTests, forwardingCacheEnabled)
TEST_F(LoadBalancerForwardToRippledTests, forwardingCacheDisabledOnLedgerClosedHookCalled)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_NO_THROW(sourceFactory_.callbacksAt(0).onLedgerClosed());
}
TEST_F(LoadBalancerForwardToRippledTests, onLedgerClosedHookInvalidatesCache)
{
configJson_.as_object()["forwarding_cache_timeout"] = 10.;
configJson_.as_object()["forwarding"] = boost::json::object{{"cache_timeout", 10.}};
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
auto const request = boost::json::object{{"command", "server_info"}};

View File

@@ -22,6 +22,7 @@
#include "util/requests/Types.hpp"
#include "util/requests/WsConnection.hpp"
#include <boost/asio/error.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/beast/http/field.hpp>
#include <gtest/gtest.h>
@@ -29,6 +30,7 @@
#include <chrono>
#include <cstddef>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <thread>
@@ -119,6 +121,75 @@ TEST_P(WsConnectionTests, SendAndReceive)
});
}
TEST_F(WsConnectionTests, ReadTimeout)
{
TestWsConnectionPtr serverConnection;
asio::spawn(ctx, [&](asio::yield_context yield) {
serverConnection = std::make_unique<TestWsConnection>(unwrap(server.acceptConnection(yield)));
});
runSpawn([&](asio::yield_context yield) {
auto connection = unwrap(builder.plainConnect(yield));
auto message = connection->read(yield, std::chrono::milliseconds{1});
ASSERT_FALSE(message.has_value());
ASSERT_TRUE(message.error().errorCode().has_value());
EXPECT_EQ(message.error().errorCode().value().value(), asio::error::timed_out);
});
}
TEST_F(WsConnectionTests, ReadWithTimeoutWorksFine)
{
asio::spawn(ctx, [&](asio::yield_context yield) {
auto serverConnection = unwrap(server.acceptConnection(yield));
auto maybeError = serverConnection.send("hello", yield);
EXPECT_FALSE(maybeError.has_value()) << *maybeError;
});
runSpawn([&](asio::yield_context yield) {
auto connection = unwrap(builder.plainConnect(yield));
auto message = connection->read(yield, std::chrono::seconds{1});
ASSERT_TRUE(message.has_value()) << message.error().message();
EXPECT_EQ(message.value(), "hello");
});
}
TEST_F(WsConnectionTests, WriteTimeout)
{
TestWsConnectionPtr serverConnection;
asio::spawn(ctx, [&](asio::yield_context yield) {
serverConnection = std::make_unique<TestWsConnection>(unwrap(server.acceptConnection(yield)));
});
runSpawn([&](asio::yield_context yield) {
auto connection = unwrap(builder.plainConnect(yield));
std::optional<RequestError> error;
// Write is success even if the other side is not reading.
// It seems we need to fill some socket buffer before the timeout occurs.
while (not error.has_value()) {
error = connection->write("hello", yield, std::chrono::milliseconds{1});
}
ASSERT_TRUE(error.has_value());
EXPECT_EQ(error->errorCode().value().value(), asio::error::timed_out);
});
}
TEST_F(WsConnectionTests, WriteWithTimeoutWorksFine)
{
asio::spawn(ctx, [&](asio::yield_context yield) {
auto serverConnection = unwrap(server.acceptConnection(yield));
auto message = serverConnection.receive(yield);
ASSERT_TRUE(message.has_value());
EXPECT_EQ(message, "hello");
});
runSpawn([&](asio::yield_context yield) {
auto connection = unwrap(builder.plainConnect(yield));
auto error = connection->write("hello", yield, std::chrono::seconds{1});
ASSERT_FALSE(error.has_value()) << error->message();
});
}
TEST_F(WsConnectionTests, TrySslUsePlain)
{
asio::spawn(ctx, [&](asio::yield_context yield) {
@@ -148,7 +219,7 @@ TEST_F(WsConnectionTests, TrySslUsePlain)
});
}
TEST_F(WsConnectionTests, Timeout)
TEST_F(WsConnectionTests, ConnectionTimeout)
{
builder.setConnectionTimeout(std::chrono::milliseconds{1});
runSpawn([&](asio::yield_context yield) {
@@ -213,9 +284,9 @@ TEST_F(WsConnectionTests, CloseConnection)
TEST_F(WsConnectionTests, CloseConnectionTimeout)
{
TestWsConnectionPtr serverConnection;
asio::spawn(ctx, [&](asio::yield_context yield) {
auto serverConnection = unwrap(server.acceptConnection(yield));
std::this_thread::sleep_for(std::chrono::milliseconds{10});
auto serverConnection = std::make_unique<TestWsConnection>(unwrap(server.acceptConnection(yield)));
});
runSpawn([&](asio::yield_context yield) {