Skip to content

Commit 858df28

Browse files
authored
[Test] Preserve quorum during session seed fallback recovery (#18958)
1 parent 10749a2 commit 858df28

1 file changed

Lines changed: 63 additions & 20 deletions

File tree

‎integration-test/src/test/java/org/apache/iotdb/session/it/SessionSeedNodeFallbackIT.java‎

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.iotdb.it.framework.IoTDBTestRunner;
3131
import org.apache.iotdb.itbase.category.ClusterIT;
3232
import org.apache.iotdb.rpc.IoTDBConnectionException;
33+
import org.apache.iotdb.rpc.StatementExecutionException;
3334
import org.apache.iotdb.rpc.UrlUtils;
3435
import org.apache.iotdb.session.NodesSupplier;
3536
import org.apache.iotdb.session.Session;
@@ -63,6 +64,8 @@
6364
@RunWith(IoTDBTestRunner.class)
6465
@Category(ClusterIT.class)
6566
public class SessionSeedNodeFallbackIT {
67+
private static final int RECOVERY_CONNECTION_TIMEOUT_MS = 30_000;
68+
6669
@Before
6770
public void setUp() {
6871
EnvFactory.getEnv()
@@ -137,32 +140,41 @@ public ScheduledFuture<?> scheduleAtFixedRate(
137140
return super.scheduleAtFixedRate(command, 1, 1, TimeUnit.DAYS);
138141
}
139142
};
140-
NodesSupplier business = createSupplier(executor);
141-
NodesSupplier background = createSupplier(executor);
142-
try (Session session = new Session.Builder().nodeUrls(urls()).enableAutoFetch(false).build()) {
143-
Set<TEndPoint> discovered = new HashSet<>(Arrays.asList(endpoint(1), endpoint(2)));
144-
awaitSnapshot(business, discovered);
145-
awaitSnapshot(background, discovered);
146-
session.open(false, 1000, new ConcurrentHashMap<>(), business);
143+
// Node 2 keeps a Ratis majority alive for login queries, but is unknown to these clients until
144+
// discovery recovers. Initialize a controlled stale snapshot without running discovery first.
145+
List<TEndPoint> discovered = Collections.singletonList(endpoint(1));
146+
List<TEndPoint> seeds = Arrays.asList(endpoint(0), endpoint(1));
147+
NodesSupplier business = createSupplier(executor, discovered, seeds);
148+
NodesSupplier background = createSupplier(executor, discovered, seeds);
149+
List<String> seedUrls =
150+
seeds.stream().map(UrlUtils::convertTEndPointIpv4AndIpv6Url).collect(Collectors.toList());
151+
try (Session session =
152+
new Session.Builder().nodeUrls(seedUrls).enableAutoFetch(false).build()) {
153+
assertEquals(discovered, business.get());
154+
assertEquals(discovered, background.get());
155+
session.open(false, RECOVERY_CONNECTION_TIMEOUT_MS, new ConcurrentHashMap<>(), business);
156+
assertEquals(endpoint(1), connectedEndpoint(session));
147157

148158
EnvFactory.getEnv().startDataNode(0);
149159
awaitStatus(0, NodeStatus.Running);
160+
awaitLogin(0);
150161
EnvFactory.getEnv().getDataNodeWrapper(1).stopForcibly();
151162
awaitStatus(1, NodeStatus.Unknown);
152-
EnvFactory.getEnv().shutdownDataNode(2);
153-
awaitStatus(2, EnvUtils.getNodeStatusAfterLocalStop());
154-
assertFalse(business.get().contains(endpoint(0)));
155-
assertFalse(background.get().contains(endpoint(0)));
163+
assertEquals(discovered, business.get());
164+
assertEquals(discovered, background.get());
156165

157-
// Both discovered endpoints are dead. Only the initial seed omitted by discovery is live.
166+
// Every discovered endpoint is dead. Node 0 is the only reachable initial seed.
167+
Set<TEndPoint> recoveredNodes = new HashSet<>(Arrays.asList(endpoint(0), endpoint(2)));
158168
try (SessionDataSet result = session.executeQueryStatement("SHOW AVAILABLE URLS")) {
159-
assertEquals(Collections.singleton(endpoint(0)), readEndpoints(result));
169+
assertEquals(recoveredNodes, readEndpoints(result));
160170
}
161-
awaitSnapshot(background, Collections.singleton(endpoint(0)));
162-
assertEquals(3, background.getSeedNodes().size());
171+
assertEquals(endpoint(0), connectedEndpoint(session));
172+
awaitSnapshot(background, recoveredNodes);
173+
assertEquals(seeds, background.getSeedNodes());
163174
// Wait for the immediate refresh requested by business reconnection, not a 60-second poll.
164-
executor.submit(() -> {}).get(10, TimeUnit.SECONDS);
165-
assertEquals(Collections.singletonList(endpoint(0)), business.get());
175+
executor.submit(() -> {}).get(60, TimeUnit.SECONDS);
176+
assertEquals(recoveredNodes, new HashSet<>(business.get()));
177+
assertEquals(seeds, business.getSeedNodes());
166178
} finally {
167179
business.close();
168180
background.close();
@@ -191,17 +203,24 @@ private NodesSupplier supplier(Object owner) throws Exception {
191203
return (NodesSupplier) field.get(owner);
192204
}
193205

194-
private NodesSupplier createSupplier(ScheduledThreadPoolExecutor executor) {
195-
List<TEndPoint> seeds = Arrays.asList(endpoint(0), endpoint(1), endpoint(2));
206+
private TEndPoint connectedEndpoint(Session session) throws Exception {
207+
Field field = Session.class.getDeclaredField("defaultEndPoint");
208+
field.setAccessible(true);
209+
return (TEndPoint) field.get(session);
210+
}
211+
212+
private NodesSupplier createSupplier(
213+
ScheduledThreadPoolExecutor executor, List<TEndPoint> discovered, List<TEndPoint> seeds) {
196214
return NodesSupplier.createNodeSupplier(
215+
discovered,
197216
seeds,
198217
executor,
199218
SessionConfig.DEFAULT_USER,
200219
SessionConfig.DEFAULT_PASSWORD,
201220
ZoneId.of("UTC"),
202221
SessionConfig.DEFAULT_INITIAL_BUFFER_CAPACITY,
203222
SessionConfig.DEFAULT_MAX_FRAME_SIZE,
204-
1000,
223+
RECOVERY_CONNECTION_TIMEOUT_MS,
205224
false,
206225
null,
207226
null,
@@ -240,6 +259,30 @@ private void awaitStatus(int index, NodeStatus status) {
240259
Collections.singletonList(status));
241260
}
242261

262+
private void awaitLogin(int index) {
263+
TEndPoint recovered = endpoint(index);
264+
Awaitility.await()
265+
.atMost(60, TimeUnit.SECONDS)
266+
.pollInterval(100, TimeUnit.MILLISECONDS)
267+
.untilAsserted(
268+
() -> {
269+
// Probe the recovered seed independently so the tested snapshots remain stale.
270+
try (Session probe =
271+
new Session.Builder()
272+
.host(recovered.getIp())
273+
.port(recovered.getPort())
274+
.enableAutoFetch(false)
275+
.build()) {
276+
probe.open(false, RECOVERY_CONNECTION_TIMEOUT_MS);
277+
try (SessionDataSet result = probe.executeQueryStatement("SHOW AVAILABLE URLS")) {
278+
assertTrue(readEndpoints(result).contains(recovered));
279+
}
280+
} catch (IoTDBConnectionException | StatementExecutionException e) {
281+
throw new AssertionError("Seed is not ready for session queries: " + recovered, e);
282+
}
283+
});
284+
}
285+
243286
private void awaitSnapshot(NodesSupplier current, Set<TEndPoint> expected) {
244287
Awaitility.await()
245288
.atMost(30, TimeUnit.SECONDS)

0 commit comments

Comments
 (0)