This is an automated email from the ASF dual-hosted git repository.
davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 7b7a4d30b7 [Fix][E2E] Recreate Fluss test tables that never get a
bucket leader (#12852)
7b7a4d30b7 is described below
commit 7b7a4d30b7fe4af1202ff33f59a62aa9669dea56
Author: Goutam Adwant <[email protected]>
AuthorDate: Wed Oct 21 06:22:23 2026 -0700
[Fix][E2E] Recreate Fluss test tables that never get a bucket leader
(#12852)
---
.../e2e/connector/fluss/FlussTestBase.java | 74 ++++++++++++++++++++--
1 file changed, 67 insertions(+), 7 deletions(-)
diff --git
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-fluss-e2e/src/test/java/org/apache/seatunnel/e2e/connector/fluss/FlussTestBase.java
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-fluss-e2e/src/test/java/org/apache/seatunnel/e2e/connector/fluss/FlussTestBase.java
index 6cb22c7489..b87453c9d8 100644
---
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-fluss-e2e/src/test/java/org/apache/seatunnel/e2e/connector/fluss/FlussTestBase.java
+++
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-fluss-e2e/src/test/java/org/apache/seatunnel/e2e/connector/fluss/FlussTestBase.java
@@ -19,8 +19,10 @@ package org.apache.seatunnel.e2e.connector.fluss;
import org.apache.seatunnel.e2e.common.TestResource;
import org.apache.seatunnel.e2e.common.TestSuiteBase;
+import org.awaitility.core.ConditionTimeoutException;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Timeout;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.images.builder.Transferable;
@@ -30,10 +32,12 @@ import org.testcontainers.utility.DockerLoggerFactory;
import com.alibaba.fluss.client.Connection;
import com.alibaba.fluss.client.ConnectionFactory;
import com.alibaba.fluss.client.admin.Admin;
+import com.alibaba.fluss.client.admin.OffsetSpec;
import com.alibaba.fluss.config.Configuration;
import com.alibaba.fluss.metadata.DatabaseDescriptor;
import com.alibaba.fluss.metadata.Schema;
import com.alibaba.fluss.metadata.TableDescriptor;
+import com.alibaba.fluss.metadata.TableInfo;
import com.alibaba.fluss.metadata.TablePath;
import com.alibaba.fluss.types.DataTypes;
import com.github.dockerjava.api.command.InspectContainerResponse;
@@ -41,12 +45,18 @@ import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.Socket;
+import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.awaitility.Awaitility.given;
+// A Fluss table without a bucket leader makes the SeaTunnel job wait forever.
Fail the case instead
+// of blocking the whole CI job; the slowest case waits at most about 8
minutes when healthy.
+@Timeout(value = 15, unit = TimeUnit.MINUTES)
@Slf4j
public abstract class FlussTestBase extends TestSuiteBase implements
TestResource {
@@ -61,6 +71,7 @@ public abstract class FlussTestBase extends TestSuiteBase
implements TestResourc
protected static final int FLUSS_TABLET_PORT = 9124;
protected static final int FLUSS_COORDINATOR_LOCAL_PORT = 8123;
protected static final int FLUSS_TABLET_LOCAL_PORT = 8124;
+ private static final int TABLE_CREATE_ATTEMPTS = 3;
private GenericContainer<?> zookeeperServer;
private GenericContainer<?> coordinatorServer;
@@ -159,16 +170,20 @@ public abstract class FlussTestBase extends TestSuiteBase
implements TestResourc
}
private void initializeConnection() throws ExecutionException,
InterruptedException {
+ flussConnection = ConnectionFactory.createConnection(flussConfig());
+ // Perform a real admin RPC so the readiness await only passes once
the tablet server
+ // actually answers, not merely when the (lazy) client object is
constructed.
+ flussConnection.getAdmin().listDatabases().get();
+ }
+
+ private Configuration flussConfig() {
Configuration flussConfig = new Configuration();
flussConfig.setString(
"bootstrap.servers",
coordinatorServer.getHost()
+ ":"
+
coordinatorServer.getMappedPort(FLUSS_COORDINATOR_LOCAL_PORT));
- flussConnection = ConnectionFactory.createConnection(flussConfig);
- // Perform a real admin RPC so the readiness await only passes once
the tablet server
- // actually answers, not merely when the (lazy) client object is
constructed.
- flussConnection.getAdmin().listDatabases().get();
+ return flussConfig;
}
protected void createDb(Connection connection, String dbName)
@@ -201,12 +216,57 @@ public abstract class FlussTestBase extends TestSuiteBase
implements TestResourc
protected void createTable(
Connection connection, String dbName, String tableName, Schema
schema)
- throws ExecutionException, InterruptedException {
+ throws Exception {
Admin admin = connection.getAdmin();
TableDescriptor tableDescriptor =
TableDescriptor.builder().schema(schema).build();
TablePath tablePath = TablePath.of(dbName, tableName);
- admin.dropTable(tablePath, true).get();
- admin.createTable(tablePath, tableDescriptor, true).get(); // blocking
call
+ // Fluss 0.7.0 can miss a table creation event (apache/fluss#1018).
The table is then
+ // registered but its buckets never get a leader, so recreate it.
+ ConditionTimeoutException lastFailure = null;
+ for (int attempt = 1; attempt <= TABLE_CREATE_ATTEMPTS; attempt++) {
+ admin.dropTable(tablePath, true).get();
+ admin.createTable(tablePath, tableDescriptor, true).get(); //
blocking call
+ try {
+ waitForBucketLeaders(tablePath);
+ return;
+ } catch (ConditionTimeoutException e) {
+ lastFailure = e;
+ log.warn(
+ "Fluss table {} has no bucket leader (attempt {} of
{})",
+ tablePath,
+ attempt,
+ TABLE_CREATE_ATTEMPTS,
+ e);
+ }
+ }
+ throw new IllegalStateException(
+ String.format(
+ "Fluss table %s has no bucket leader after %d
attempts, see apache/fluss#1018",
+ tablePath, TABLE_CREATE_ATTEMPTS),
+ lastFailure);
+ }
+
+ private void waitForBucketLeaders(TablePath tablePath) throws Exception {
+ // Use a new connection so no metadata cached for an earlier table at
this path is used.
+ try (Connection connection =
ConnectionFactory.createConnection(flussConfig())) {
+ Admin admin = connection.getAdmin();
+ TableInfo tableInfo = admin.getTableInfo(tablePath).get();
+ List<Integer> buckets =
+ IntStream.range(0, tableInfo.getNumBuckets())
+ .boxed()
+ .collect(Collectors.toList());
+ // Listing offsets fails while a bucket has no leader.
+ given().ignoreExceptions()
+ .await()
+ .atMost(30, TimeUnit.SECONDS)
+ .pollInterval(1, TimeUnit.SECONDS)
+ .untilAsserted(
+ () ->
+ admin.listOffsets(
+ tablePath, buckets, new
OffsetSpec.LatestSpec())
+ .all()
+ .get(10, TimeUnit.SECONDS));
+ }
}
protected static boolean checkPort(String host, int port, int timeoutMs)
throws IOException {