-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBasePoolTest.java
More file actions
231 lines (209 loc) · 8.16 KB
/
Copy pathBasePoolTest.java
File metadata and controls
231 lines (209 loc) · 8.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/*
* Copyright (c) 2026 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
* All Rights Reserved.
*/
package io.tarantool.pool.integration;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.MultiThreadIoEventLoopGroup;
import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import org.opentest4j.AssertionFailedError;
import org.testcontainers.containers.tarantool.TarantoolContainer;
import org.testcontainers.containers.utils.TarantoolContainerClientHelper;
import io.tarantool.core.IProtoClient;
import io.tarantool.core.ManagedResource;
import io.tarantool.core.connection.ConnectionFactory;
import io.tarantool.pool.HeartbeatOpts;
import io.tarantool.pool.IProtoClientPool;
import io.tarantool.pool.IProtoClientPoolImpl;
import io.tarantool.pool.InstanceConnectionGroup;
public class BasePoolTest {
protected static final String API_USER = "api_user";
protected static final Map<String, String> CREDS =
new HashMap<String, String>() {
{
put(API_USER, "secret");
}
};
protected static final Map<String, String> ENV_MAP =
new HashMap<String, String>() {
{
put("TARANTOOL_USER_NAME", API_USER);
put("TARANTOOL_USER_PASSWORD", CREDS.get(API_USER));
}
};
protected static final Bootstrap bootstrap =
new Bootstrap()
.group(new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()))
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
protected static final int MAX_CONNECTION_COUNT = 20;
protected static final int MIN_CONNECTION_COUNT = 10;
protected static String host1;
protected static int port1;
protected static int count1;
protected static String host2;
protected static int port2;
protected static int count2;
protected static void generateCounts() {
count1 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1);
count2 = ThreadLocalRandom.current().nextInt(MIN_CONNECTION_COUNT, MAX_CONNECTION_COUNT + 1);
}
protected void execLua(TarantoolContainer<?> container, String command) {
try {
TarantoolContainerClientHelper.executeCommandDecoded(container, command);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected int getActiveConnectionsCount(TarantoolContainer<?> tt) {
try {
// box.stat.net().CONNECTIONS.current is updated asynchronously by the IProto worker;
// the loop's fiber.sleep lets it drain pending connections before we read.
String lua =
"local last = box.stat.net().CONNECTIONS.current;"
+ " for i = 1, 50 do"
+ " require('fiber').sleep(0.05);"
+ " local cur = box.stat.net().CONNECTIONS.current;"
+ " if cur == last then return cur - 1 end;"
+ " last = cur;"
+ " end;"
+ " return last - 1";
List<? extends Object> result = TarantoolContainerClientHelper.executeCommandDecoded(tt, lua);
return (Integer) result.get(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected int getActiveConnectionsCountDelta(TarantoolContainer<?> tt, int baseline) {
return getActiveConnectionsCount(tt) - baseline;
}
/**
* Retries {@link #getActiveConnectionsCount} until it equals {@code expected} — see there for why
* a single read is unreliable.
*
* @param tt the Tarantool container under test
* @param expected the expected number of active connections
*/
protected void waitForActiveConnections(TarantoolContainer<?> tt, int expected) {
try {
waitFor(
"Active connections count never reached " + expected,
Duration.ofSeconds(10),
() -> assertEquals(expected, getActiveConnectionsCount(tt)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected MeterRegistry createMetricsRegistry() {
MeterRegistry metricsRegistry = new SimpleMeterRegistry();
LongTaskTimer.builder("request.timer")
.description("Latency of requests to Tarantool")
.register(metricsRegistry);
Counter.builder("request.counter")
.description("Number of requests to Tarantool")
.register(metricsRegistry);
Counter.builder("response.success")
.description("Number of successful responses")
.register(metricsRegistry);
Counter.builder("response.errors")
.description("Number of error responses")
.register(metricsRegistry);
Counter.builder("response.ignored")
.description("Number of ignored IProto packets")
.register(metricsRegistry);
return metricsRegistry;
}
protected IProtoClientPool createPool(HeartbeatOpts heartbeatOpts) {
return createPool(heartbeatOpts, null);
}
protected IProtoClientPool createPool(
HeartbeatOpts heartbeatOpts, MeterRegistry metricsRegistry) {
ManagedResource<Timer> timerResource =
ManagedResource.owned(new HashedWheelTimer(), Timer::stop);
ConnectionFactory factory = new ConnectionFactory(bootstrap, timerResource.get());
IProtoClientPool pool =
new IProtoClientPoolImpl(
factory, timerResource, true, heartbeatOpts, null, metricsRegistry);
pool.setGroups(
Arrays.asList(
InstanceConnectionGroup.builder()
.withHost(host1)
.withPort(port1)
.withSize(count1)
.withTag("node-a")
.build(),
InstanceConnectionGroup.builder()
.withHost(host2)
.withPort(port2)
.withSize(count2)
.withTag("node-b")
.build()));
return pool;
}
@SuppressWarnings("rawtypes")
protected List<IProtoClient> getConnects(IProtoClientPool pool, String tag, int count) {
List<IProtoClient> clients = new ArrayList<>();
List<CompletableFuture<IProtoClient>> futures = new ArrayList<>();
for (int i = 0; i < count; i++) {
futures.add(pool.get(tag, i));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
for (CompletableFuture<IProtoClient> future : futures) {
clients.add(future.join());
}
return clients;
}
@SuppressWarnings("rawtypes")
protected boolean pingClients(List<IProtoClient> clients) {
List<CompletableFuture<?>> futures = new ArrayList<>();
for (IProtoClient client : clients) {
assertTrue(client.isConnected());
client.authorize(API_USER, CREDS.get(API_USER)).join();
futures.add(client.ping());
}
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
} catch (CompletionException e) {
return false;
}
return true;
}
protected void waitFor(String failMessage, Duration acceptableDuration, Runnable cb)
throws Exception {
long attempts = acceptableDuration.compareTo(Duration.ofSeconds(3)) > 0 ? 100L : 10L;
float k = (float) acceptableDuration.toMillis() / attempts / attempts;
for (int i = 1; i <= attempts; i++) {
try {
cb.run();
return;
} catch (AssertionFailedError | Exception e) {
long timeout = (long) (k * (2L * i - 1));
Thread.sleep(timeout);
}
}
fail(failMessage);
}
}