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 f380f6bc56 [Improve][e2e] Improve the stability of Kafka e2e testing 
(#10946)
f380f6bc56 is described below

commit f380f6bc566b88d0812a1de141e390a506b0e362
Author: zhiwei.niu <[email protected]>
AuthorDate: Thu May 28 22:53:06 2026 +0800

    [Improve][e2e] Improve the stability of Kafka e2e testing (#10946)
    
    Co-authored-by: Cursor <[email protected]>
---
 .../e2e/connector/kafka/KafkaFormatIT.java         |  95 ++++++++++-
 .../seatunnel/e2e/connector/kafka/KafkaIT.java     |  61 +++++--
 .../e2e/connector/kafka/KafkaKerberosIT.java       | 176 ++++++++++++---------
 .../src/test/resources/kerberos/kafka.properties   |   7 +-
 .../src/test/resources/kerberos/start.sh           |  33 +++-
 5 files changed, 272 insertions(+), 100 deletions(-)

diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaFormatIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaFormatIT.java
index 5cda636266..6db41a61d9 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaFormatIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaFormatIT.java
@@ -38,6 +38,10 @@ import 
org.apache.seatunnel.e2e.common.container.TestContainer;
 import org.apache.seatunnel.e2e.common.junit.DisabledOnContainer;
 import org.apache.seatunnel.e2e.common.junit.TestContainerExtension;
 
+import org.apache.kafka.clients.admin.AdminClient;
+import org.apache.kafka.clients.admin.AdminClientConfig;
+import org.apache.kafka.clients.admin.NewTopic;
+import org.apache.kafka.clients.admin.TopicDescription;
 import org.apache.kafka.clients.consumer.ConsumerConfig;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
 import org.apache.kafka.clients.consumer.ConsumerRecords;
@@ -76,10 +80,12 @@ import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -121,6 +127,18 @@ public class KafkaFormatIT extends TestSuiteBase 
implements TestResource {
     private static final String DEBEZIUM_DATA_PATH = 
"/debezium/debezium_data.txt";
     private static final String DEBEZIUM_KAFKA_SOURCE_TOPIC = 
"dbserver1.debezium.products";
 
+    private static final List<String> KAFKA_TOPICS =
+            Arrays.asList(
+                    CANAL_KAFKA_SOURCE_TOPIC,
+                    OGG_KAFKA_SOURCE_TOPIC,
+                    MAXWELL_KAFKA_SOURCE_TOPIC,
+                    COMPATIBLE_KAFKA_SOURCE_TOPIC,
+                    DEBEZIUM_KAFKA_SOURCE_TOPIC,
+                    CANAL_KAFKA_SINK_TOPIC,
+                    OGG_KAFKA_SINK_TOPIC,
+                    MAXWELL_KAFKA_SINK_TOPIC,
+                    DEBEZIUM_KAFKA_SINK_TOPIC);
+
     private static final String PG_SINK_TABLE1 = "sink";
     private static final String PG_SINK_TABLE2 = "sink2";
 
@@ -303,7 +321,7 @@ public class KafkaFormatIT extends TestSuiteBase implements 
TestResource {
 
     @BeforeAll
     @Override
-    public void startUp() throws ClassNotFoundException, InterruptedException, 
IOException {
+    public void startUp() throws ClassNotFoundException, IOException {
 
         LOG.info("The first stage: Starting Kafka containers...");
         createKafkaContainer();
@@ -327,15 +345,22 @@ public class KafkaFormatIT extends TestSuiteBase 
implements TestResource {
                 .atLeast(100, TimeUnit.MILLISECONDS)
                 .pollInterval(500, TimeUnit.MILLISECONDS)
                 .atMost(180, TimeUnit.SECONDS)
-                .untilAsserted(this::initKafkaConsumer);
+                .untilAsserted(this::initializeKafkaTopics);
+
+        given().ignoreExceptions()
+                .atLeast(100, TimeUnit.MILLISECONDS)
+                .pollInterval(500, TimeUnit.MILLISECONDS)
+                .atMost(180, TimeUnit.SECONDS)
+                .untilAsserted(this::waitForKafkaTopicsReady);
 
-        // local file local data send kafka
         given().ignoreExceptions()
                 .atLeast(100, TimeUnit.MILLISECONDS)
                 .pollInterval(500, TimeUnit.MILLISECONDS)
-                .atMost(3, TimeUnit.MINUTES)
-                .untilAsserted(this::initLocalDataToKafka);
-        Thread.sleep(20 * 1000);
+                .atMost(180, TimeUnit.SECONDS)
+                .untilAsserted(this::initKafkaConsumer);
+
+        // local file local data send kafka
+        initLocalDataToKafka();
     }
 
     @DisabledOnContainer(
@@ -887,6 +912,56 @@ public class KafkaFormatIT extends TestSuiteBase 
implements TestResource {
         kafkaConsumer = new KafkaConsumer<>(prop);
     }
 
+    private void initializeKafkaTopics() {
+        try (AdminClient adminClient = createKafkaAdmin()) {
+            Set<String> existingTopics = 
adminClient.listTopics().names().get(30, TimeUnit.SECONDS);
+            List<NewTopic> topicsToCreate =
+                    KAFKA_TOPICS.stream()
+                            .filter(topic -> !existingTopics.contains(topic))
+                            .map(topic -> new NewTopic(topic, 1, (short) 1))
+                            .collect(Collectors.toList());
+            if (!topicsToCreate.isEmpty()) {
+                adminClient.createTopics(topicsToCreate).all().get(60, 
TimeUnit.SECONDS);
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted while initializing Kafka 
topics", e);
+        } catch (ExecutionException | java.util.concurrent.TimeoutException e) 
{
+            throw new RuntimeException("Failed to initialize Kafka topics", e);
+        }
+    }
+
+    private void waitForKafkaTopicsReady() {
+        try (AdminClient adminClient = createKafkaAdmin()) {
+            Map<String, TopicDescription> topicDescriptions =
+                    adminClient
+                            .describeTopics(KAFKA_TOPICS)
+                            .allTopicNames()
+                            .get(30, TimeUnit.SECONDS);
+            Assertions.assertEquals(
+                    new HashSet<>(KAFKA_TOPICS).size(),
+                    topicDescriptions.size(),
+                    "Kafka topics are not ready yet");
+            KAFKA_TOPICS.forEach(
+                    topic ->
+                            Assertions.assertTrue(
+                                    topicDescriptions.containsKey(topic),
+                                    "Kafka topic is not ready yet: " + topic));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted while waiting for Kafka 
topics", e);
+        } catch (ExecutionException | java.util.concurrent.TimeoutException e) 
{
+            throw new RuntimeException("Kafka topics are not ready yet", e);
+        }
+    }
+
+    private AdminClient createKafkaAdmin() {
+        Properties props = new Properties();
+        props.put(
+                AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, 
KAFKA_CONTAINER.getBootstrapServers());
+        return AdminClient.create(props);
+    }
+
     // Example Initialize the pg sink table
     private void initializeJdbcTable() {
         try (Connection connection =
@@ -981,7 +1056,11 @@ public class KafkaFormatIT extends TestSuiteBase 
implements TestResource {
                         producer.send(record).get();
                     }
                 } catch (IOException | InterruptedException | 
ExecutionException e) {
-                    e.printStackTrace();
+                    if (e instanceof InterruptedException) {
+                        Thread.currentThread().interrupt();
+                    }
+                    throw new RuntimeException(
+                            "Failed to initialize local Kafka data for topic " 
+ kafkaTopic, e);
                 }
             }
         }
@@ -1013,7 +1092,7 @@ public class KafkaFormatIT extends TestSuiteBase 
implements TestResource {
                 LOG.info("truncate table sink");
             }
         } catch (SQLException e) {
-            e.printStackTrace();
+            throw new RuntimeException("Query Postgre sink table failed: " + 
tableName, e);
         }
         return actual;
     }
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaIT.java
index 704febe57d..6bcd103956 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaIT.java
@@ -111,6 +111,7 @@ import java.util.Properties;
 import java.util.concurrent.CompletableFuture;
 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;
 
@@ -197,7 +198,23 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                             kafkaExactlyOnceSink,
                             kafkaExactlyOnceBatchSource,
                             kafkaExactlyOnceBatchSink);
-            adminClient.createTopics(topics);
+            adminClient.createTopics(topics).all().get(60, SECONDS);
+
+            List<String> topicNames =
+                    
topics.stream().map(NewTopic::name).collect(Collectors.toList());
+            given().ignoreExceptions()
+                    .atLeast(100, TimeUnit.MILLISECONDS)
+                    .pollInterval(500, TimeUnit.MILLISECONDS)
+                    .atMost(60, SECONDS)
+                    .untilAsserted(
+                            () -> {
+                                Map<String, TopicDescription> desc =
+                                        adminClient
+                                                .describeTopics(topicNames)
+                                                .allTopicNames()
+                                                .get(30, SECONDS);
+                                Assertions.assertEquals(topicNames.size(), 
desc.size());
+                            });
         }
 
         log.info("Write 100 records to topic test_topic_source");
@@ -616,8 +633,8 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                     }
                 });
 
-        // Wait for job to start and process initial data
-        Awaitility.await().pollDelay(5, SECONDS).atMost(1, MINUTES).until(() 
-> true);
+        // Wait for job to be RUNNING before changing partitions.
+        awaitJobRunning(container, jobId);
 
         try (AdminClient adminClient = createKafkaAdmin()) {
             Map<String, NewPartitions> newPartitions = new HashMap<>();
@@ -626,7 +643,22 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
             log.info("Successfully created new partition for topic: {}", 
sourceTopic);
         }
 
-        Awaitility.await().pollDelay(3, SECONDS).atMost(30, SECONDS).until(() 
-> true);
+        Awaitility.await()
+                .pollInterval(1, SECONDS)
+                .atMost(30, SECONDS)
+                .untilAsserted(
+                        () -> {
+                            try (AdminClient adminClient = createKafkaAdmin()) 
{
+                                Map<String, TopicDescription> 
topicDescriptions =
+                                        adminClient
+                                                
.describeTopics(Arrays.asList(sourceTopic))
+                                                .allTopicNames()
+                                                .get();
+                                Assertions.assertTrue(
+                                        
topicDescriptions.get(sourceTopic).partitions().size()
+                                                >= 2);
+                            }
+                        });
 
         for (int i = 0; i < 15; i++) {
             String message =
@@ -702,8 +734,7 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                     }
                 });
 
-        // Warm up (simple delay).
-        Awaitility.await().pollDelay(5, SECONDS).atMost(1, MINUTES).until(() 
-> true);
+        awaitJobRunning(container, jobId);
 
         // Produce 10 additional records after the job starts.
         for (int i = 0; i < 10; i++) {
@@ -762,7 +793,6 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
         // After restore, sink should advance by the 15 newly produced records 
at
         // minimum.
         Awaitility.await()
-                .pollDelay(3, SECONDS)
                 .pollInterval(2, SECONDS)
                 .atMost(5, MINUTES)
                 .until(() -> visibleCountOnP0(sinkTopic) == 
expectedSinkAfterFirstRun + 15);
@@ -802,7 +832,7 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                     }
                 });
 
-        Awaitility.await().pollDelay(5, SECONDS).atMost(1, MINUTES).until(() 
-> true);
+        awaitJobRunning(container, jobId);
 
         // Produce 10 records after job start; latest mode should consume only 
these 10
         // initially.
@@ -849,7 +879,6 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                 });
 
         Awaitility.await()
-                .pollDelay(3, SECONDS)
                 .pollInterval(2, SECONDS)
                 .atMost(5, MINUTES)
                 .until(() -> visibleCountOnP0(sinkTopic) == 
expectedSinkAfterFirstRun + 15);
@@ -888,7 +917,7 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                     }
                 });
 
-        Awaitility.await().pollDelay(5, SECONDS).atMost(1, MINUTES).until(() 
-> true);
+        awaitJobRunning(container, jobId);
 
         // Produce 10 records after job start.
         for (int i = 0; i < 10; i++) {
@@ -936,7 +965,6 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                 });
 
         Awaitility.await()
-                .pollDelay(3, SECONDS)
                 .pollInterval(2, SECONDS)
                 .atMost(5, MINUTES)
                 .until(() -> visibleCountOnP0(sinkTopic) == 
expectedSinkAfterFirstRun + 15);
@@ -976,7 +1004,7 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                     }
                 });
 
-        Awaitility.await().pollDelay(5, SECONDS).atMost(1, MINUTES).until(() 
-> true);
+        awaitJobRunning(container, jobId);
 
         // Produce 10 records after job start.
         for (int i = 0; i < 10; i++) {
@@ -1025,12 +1053,19 @@ public class KafkaIT extends TestSuiteBase implements 
TestResource {
                 });
 
         Awaitility.await()
-                .pollDelay(3, SECONDS)
                 .pollInterval(2, SECONDS)
                 .atMost(5, MINUTES)
                 .until(() -> visibleCountOnP0(sinkTopic) == 
expectedSinkAfterFirstRun + 15 - 11);
     }
 
+    private void awaitJobRunning(TestContainer container, String jobId) {
+        Awaitility.await()
+                .pollInterval(2, SECONDS)
+                .atMost(1, MINUTES)
+                .untilAsserted(
+                        () -> Assertions.assertEquals("RUNNING", 
container.getJobStatus(jobId)));
+    }
+
     /**
      * Get visible record count on partition-0: endOffset - beginningOffset 
(exclusive upper bound).
      */
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaKerberosIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaKerberosIT.java
index 435602ffb6..9d24670b78 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaKerberosIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/java/org/apache/seatunnel/e2e/connector/kafka/KafkaKerberosIT.java
@@ -38,6 +38,7 @@ import 
org.apache.seatunnel.e2e.common.junit.DisabledOnContainer;
 import org.apache.seatunnel.e2e.common.util.ContainerUtil;
 import org.apache.seatunnel.format.text.TextSerializationSchema;
 
+import org.apache.kafka.clients.admin.AdminClientConfig;
 import org.apache.kafka.clients.consumer.ConsumerConfig;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
 import org.apache.kafka.clients.consumer.ConsumerRecords;
@@ -57,6 +58,7 @@ import org.junit.jupiter.api.TestTemplate;
 import org.testcontainers.containers.Container;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.containers.wait.strategy.Wait;
 import org.testcontainers.lifecycle.Startables;
 import org.testcontainers.shaded.org.awaitility.Awaitility;
 import org.testcontainers.utility.DockerImageName;
@@ -70,6 +72,7 @@ import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Arrays;
+import java.util.Base64;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -94,7 +97,6 @@ public class KafkaKerberosIT extends TestSuiteBase implements 
TestResource {
     // The hostname is uniformly set to lowercase letters to prevent errors 
during Kerberos
     // authentication
     private static final String KAFKA_HOST = "kafkacluster";
-    private static final String BOOTSTRAP_SERVERS = KAFKA_HOST + ":9092";
 
     private KafkaProducer<byte[], byte[]> producer;
 
@@ -156,7 +158,6 @@ public class KafkaKerberosIT extends TestSuiteBase 
implements TestResource {
                         .withLogConsumer(
                                 new Slf4jLogConsumer(
                                         
DockerLoggerFactory.getLogger(KERBEROS_IMAGE_NAME)));
-        kerberosContainer.setPortBindings(Arrays.asList("88/udp:88/udp", 
"749:749"));
         Startables.deepStart(Stream.of(kerberosContainer)).join();
         log.info("Kerberos just started");
 
@@ -164,10 +165,12 @@ public class KafkaKerberosIT extends TestSuiteBase 
implements TestResource {
                 "bash",
                 "-c",
                 "kadmin.local -q \"addprinc -randkey 
kafka/[email protected]\"");
+        kerberosContainer.execInContainer(
+                "bash", "-c", "kadmin.local -q \"addprinc -randkey 
kafka/[email protected]\"");
         kerberosContainer.execInContainer(
                 "bash",
                 "-c",
-                "kadmin.local -q \"xst -k /tmp/kafka.keytab 
kafka/[email protected]\"");
+                "kadmin.local -q \"xst -k /tmp/kafka.keytab 
kafka/[email protected] kafka/[email protected]\"");
 
         // test.keytab verify unprivileged keytab usage
         kerberosContainer.execInContainer(
@@ -202,81 +205,91 @@ public class KafkaKerberosIT extends TestSuiteBase 
implements TestResource {
                         .withFileSystemBind(
                                 
ContainerUtil.getResourcesFile("/kerberos/krb5.conf").getPath(),
                                 "/etc/krb5.conf")
-                        .withExposedPorts(9092, 2181)
-                        .withFileSystemBind(
-                                
ContainerUtil.getResourcesFile("/kerberos/kafka.properties")
-                                        .getPath(),
-                                "/etc/kafka/kafka.properties")
+                        .withExposedPorts(9093, 2181)
                         .withFileSystemBind("/tmp/kafka.keytab", 
"/tmp/kafka.keytab")
                         .withLogConsumer(
                                 new Slf4jLogConsumer(
                                         
DockerLoggerFactory.getLogger(KAFKA_IMAGE_NAME)))
+                        .waitingFor(
+                                Wait.forLogMessage(".*ZooKeeper is ready.*", 1)
+                                        
.withStartupTimeout(Duration.ofMinutes(2)))
                         .withCommand(
                                 "bash",
                                 "-c",
                                 FileUtils.readFileToStr(
                                         
ContainerUtil.getResourcesFile("/kerberos/start.sh")
                                                 .toPath()));
-        kafkaContainer.setPortBindings(Arrays.asList("9092:9092", 
"2181:2181"));
         Startables.deepStart(Stream.of(kafkaContainer)).join();
-        log.info("Kafka container started");
-
-        // Add Hosts, local connection kerberos kafka use
-        appendToHosts("127.0.0.1", "kafkacluster");
+        log.info("Kafka container started, ZooKeeper is ready");
+
+        int externalMappedPort = kafkaContainer.getMappedPort(9093);
+        String originalProps =
+                FileUtils.readFileToStr(
+                        
ContainerUtil.getResourcesFile("/kerberos/kafka.properties").toPath());
+        String patchedProps =
+                originalProps.replace(
+                        "EXTERNAL://localhost:9093", "EXTERNAL://localhost:" + 
externalMappedPort);
+        String propsBase64 = 
Base64.getEncoder().encodeToString(patchedProps.getBytes("UTF-8"));
+        kafkaContainer.execInContainer(
+                "bash",
+                "-c",
+                "echo '"
+                        + propsBase64
+                        + "' | base64 -d > /etc/kafka/kafka.properties"
+                        + " && touch /tmp/start_kafka");
+        log.info(
+                "Kafka config written with external port {}, signaling broker 
start",
+                externalMappedPort);
 
         Awaitility.given()
                 .ignoreExceptions()
                 .atLeast(100, TimeUnit.MILLISECONDS)
                 .pollInterval(500, TimeUnit.MILLISECONDS)
                 .atMost(180, TimeUnit.SECONDS)
-                .untilAsserted(this::initKafkaProducer);
+                .untilAsserted(this::waitForKafkaKerberosReady);
+        initKafkaProducer();
     }
 
     private void initKafkaProducer() {
-        Properties props = new Properties();
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
+        Properties props = createKafkaClientSecurityProperties();
         props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, 
ByteArraySerializer.class);
         props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, 
ByteArraySerializer.class);
-        props.put("security.protocol", "SASL_PLAINTEXT");
-        props.put("sasl.mechanism", "GSSAPI");
-        props.put("sasl.kerberos.service.name", "kafka");
         producer = new KafkaProducer<>(props);
     }
 
     private Properties kafkaConsumerConfig() {
-        Properties props = new Properties();
-        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
+        Properties props = createKafkaClientSecurityProperties();
         props.put(ConsumerConfig.GROUP_ID_CONFIG, 
"seatunnel-kafka-sink-group");
         props.put(
                 ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,
                 OffsetResetStrategy.EARLIEST.toString().toLowerCase());
         props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, 
StringDeserializer.class);
         props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, 
StringDeserializer.class);
-        props.put("security.protocol", "SASL_PLAINTEXT");
-        props.put("sasl.mechanism", "GSSAPI");
-        props.put("sasl.kerberos.service.name", "kafka");
         return props;
     }
 
-    private static void appendToHosts(String ip, String hostname) {
-        try {
-            String entry = String.format("%s %s", ip, hostname);
-            ProcessBuilder processBuilder =
-                    new ProcessBuilder("sudo", "sh", "-c", "echo '" + entry + 
"' >> /etc/hosts");
-            processBuilder.redirectErrorStream(true);
-
-            Process process = processBuilder.start();
-
-            int exitCode = process.waitFor();
-            if (exitCode == 0) {
-                log.info("Successfully added to /etc/hosts: {}", entry);
-            } else {
-                log.error("Failed to add to /etc/hosts: {}", entry);
-            }
-        } catch (Exception e) {
-            log.error("Failed to add to /etc/hosts: {}", e.getMessage());
-            throw new RuntimeException(e);
-        }
+    private Properties createKafkaClientSecurityProperties() {
+        Properties props = new Properties();
+        props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, 
getBootstrapServers());
+        return props;
+    }
+
+    private void waitForKafkaKerberosReady() {
+        String logs = kafkaContainer.getLogs();
+        Assertions.assertTrue(
+                logs.contains("started (kafka.server.KafkaServer)"),
+                "Kafka kerberos broker has not fully started");
+    }
+
+    static boolean containsKafkaAuthenticationFailure(String logs) {
+        return logs != null
+                && logs.contains("Failed authentication with")
+                && (logs.contains("during SASL handshake")
+                        || logs.contains("SaslAuthenticationException"));
+    }
+
+    private String getBootstrapServers() {
+        return kafkaContainer.getHost() + ":" + 
kafkaContainer.getMappedPort(9093);
     }
 
     @TestTemplate
@@ -285,19 +298,39 @@ public class KafkaKerberosIT extends TestSuiteBase 
implements TestResource {
         container.copyFileToContainer("/kerberos/krb5.conf", "/etc/krb5.conf");
         container.copyAbsolutePathToContainer("/tmp/test.keytab", 
"/tmp/kafka.keytab");
 
-        Container.ExecResult execResult =
-                
container.executeJob("/kerberos/kafka_sink_fake_to_kafka_kerberos.conf");
-        Assertions.assertEquals(1, execResult.getExitCode());
+        int logOffsetBeforeJob = container.getServerLogs().length();
+
+        String jobId = "654321";
+        CompletableFuture.runAsync(
+                () -> {
+                    try {
+                        container.executeJob(
+                                
"/kerberos/kafka_sink_fake_to_kafka_kerberos.conf", jobId);
+                    } catch (IOException | InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+
+        Awaitility.given()
+                .ignoreExceptions()
+                .atLeast(100, TimeUnit.MILLISECONDS)
+                .pollInterval(500, TimeUnit.MILLISECONDS)
+                .atMost(180, TimeUnit.SECONDS)
+                .untilAsserted(
+                        () -> Assertions.assertEquals("FAILED", 
container.getJobStatus(jobId)));
+
+        String newLogs = 
container.getServerLogs().substring(logOffsetBeforeJob);
         Assertions.assertTrue(
-                execResult
-                        .getStderr()
-                        .contains(
-                                "Could not login: the client is being asked 
for a password, but the Kafka client code does not currently support obtaining 
a password from the user."));
+                newLogs.contains(
+                        "Could not login: the client is being asked for a 
password, but the Kafka client code does not currently support obtaining a 
password from the user."),
+                "Expected Kerberos login failure with wrong keytab");
     }
 
     @TestTemplate
     public void testNotKerberosConfig(TestContainer container)
             throws IOException, InterruptedException {
+        int brokerLogOffsetBeforeJob = kafkaContainer.getLogs().length();
+
         String jobId = "123456";
         CompletableFuture.runAsync(
                 () -> {
@@ -307,22 +340,27 @@ public class KafkaKerberosIT extends TestSuiteBase 
implements TestResource {
                         throw new RuntimeException(e);
                     }
                 });
-        // step 1. Verify whether Kafka has authentication failure logs
+
         Awaitility.given()
+                .ignoreExceptions()
                 .atLeast(100, TimeUnit.MILLISECONDS)
                 .pollInterval(500, TimeUnit.MILLISECONDS)
                 .atMost(60, TimeUnit.SECONDS)
                 .untilAsserted(
-                        () ->
-                                Assertions.assertTrue(
-                                        kafkaContainer
-                                                .execInContainer(
-                                                        "bash",
-                                                        "-c",
-                                                        "tail 
/var/log/kafka/server.log")
-                                                .getStdout()
-                                                .matches(
-                                                        "(?s).*Failed 
authentication with /.*? \\(Unexpected Kafka request of type METADATA during 
SASL handshake.*")));
+                        () -> Assertions.assertEquals("RUNNING", 
container.getJobStatus(jobId)));
+
+        Awaitility.given()
+                .ignoreExceptions()
+                .pollInterval(1, TimeUnit.SECONDS)
+                .atMost(30, TimeUnit.SECONDS)
+                .untilAsserted(
+                        () -> {
+                            String newBrokerLogs =
+                                    
kafkaContainer.getLogs().substring(brokerLogOffsetBeforeJob);
+                            Assertions.assertTrue(
+                                    
containsKafkaAuthenticationFailure(newBrokerLogs),
+                                    "Expected broker to log authentication 
failure for non-Kerberos PLAINTEXT client");
+                        });
 
         container.cancelJob(jobId);
 
@@ -332,19 +370,6 @@ public class KafkaKerberosIT extends TestSuiteBase 
implements TestResource {
                             String jobStatus = 
container.getJobStatus(String.valueOf(jobId));
                             Assertions.assertEquals("CANCELED", jobStatus);
                         });
-
-        // step 2. Verify that the program outputs retry logs
-        Awaitility.given()
-                .atLeast(100, TimeUnit.MILLISECONDS)
-                .pollInterval(500, TimeUnit.MILLISECONDS)
-                .atMost(60, TimeUnit.SECONDS)
-                .untilAsserted(
-                        () ->
-                                Assertions.assertTrue(
-                                        container
-                                                .getServerLogs()
-                                                .contains(
-                                                        "Cancelled in-flight 
INIT_PRODUCER_ID request with correlation id")));
     }
 
     @TestTemplate
@@ -451,5 +476,8 @@ public class KafkaKerberosIT extends TestSuiteBase 
implements TestResource {
         if (kafkaContainer != null) {
             kafkaContainer.close();
         }
+        if (kerberosContainer != null) {
+            kerberosContainer.close();
+        }
     }
 }
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/resources/kerberos/kafka.properties
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/resources/kerberos/kafka.properties
index affde507cb..75455b60f4 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/resources/kerberos/kafka.properties
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/resources/kerberos/kafka.properties
@@ -17,9 +17,10 @@
 
################################################################################
 
 zookeeper.connect=kafkacluster:2181
-listeners=SASL_PLAINTEXT://0.0.0.0:9092
-advertised.listeners=SASL_PLAINTEXT://kafkacluster:9092
-security.inter.broker.protocol=SASL_PLAINTEXT
+listeners=INTERNAL://0.0.0.0:9092,EXTERNAL://0.0.0.0:9093
+advertised.listeners=INTERNAL://kafkacluster:9092,EXTERNAL://localhost:9093
+listener.security.protocol.map=INTERNAL:SASL_PLAINTEXT,EXTERNAL:PLAINTEXT
+inter.broker.listener.name=INTERNAL
 sasl.mechanism.inter.broker.protocol=GSSAPI
 sasl.enabled.mechanisms=GSSAPI
 sasl.kerberos.service.name=kafka
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/resources/kerberos/start.sh
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/resources/kerberos/start.sh
index 74ce841733..522ca3c100 100755
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/resources/kerberos/start.sh
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-kafka-e2e/src/test/resources/kerberos/start.sh
@@ -26,7 +26,23 @@ nohup java -Xmx512M -Xms512M -server \
     -Dsun.security.krb5.debug=true 
org.apache.zookeeper.server.quorum.QuorumPeerMain \
     /etc/kafka/zookeeper.properties &
 
-sleep 5
+ZK_READY=false
+for i in $(seq 1 30); do
+    if nc -z localhost 2181 2>/dev/null; then
+        echo "ZooKeeper is ready after ${i}s"
+        ZK_READY=true
+        break
+    fi
+    sleep 1
+done
+if [ "$ZK_READY" != "true" ]; then
+    echo "ERROR: ZooKeeper failed to start within 30s" >&2
+    exit 1
+fi
+
+echo "Waiting for Kafka config..."
+while [ ! -f /tmp/start_kafka ]; do sleep 0.1; done
+echo "Kafka config received, starting broker..."
 
 nohup java -Xmx1G -Xms1G -server \
     -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 
\
@@ -39,6 +55,19 @@ nohup java -Xmx1G -Xms1G -server \
     -Dsun.security.krb5.debug=true 
-Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf \
     -Djava.security.krb5.conf=/etc/krb5.conf kafka.Kafka 
/etc/kafka/kafka.properties &
 
-sleep 5
+KAFKA_READY=false
+for i in $(seq 1 60); do
+    if grep -q "started (kafka.server.KafkaServer)" /var/log/kafka/server.log 
2>/dev/null; then
+        echo "Kafka broker is ready after ${i}s"
+        KAFKA_READY=true
+        break
+    fi
+    sleep 1
+done
+if [ "$KAFKA_READY" != "true" ]; then
+    echo "ERROR: Kafka broker failed to start within 60s" >&2
+    cat /var/log/kafka/server.log >&2 2>/dev/null
+    exit 1
+fi
 
 tail -f /var/log/kafka/server.log
\ No newline at end of file


Reply via email to