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 c6050f5b76 [Fix][Zeta] Set SeaTunnel home in cluster startup script 
(#10786)
c6050f5b76 is described below

commit c6050f5b7681cd27eafc3c6343c4f5a2553c2c38
Author: David Zollo <[email protected]>
AuthorDate: Tue Jul 21 22:46:44 2026 +0800

    [Fix][Zeta] Set SeaTunnel home in cluster startup script (#10786)
    
    Co-authored-by: github-actions[bot] 
<github-actions[bot]@users.noreply.github.com>
    Co-authored-by: DanielLeens <[email protected]>
---
 .../src/main/bin/seatunnel-cluster.cmd             |   7 +
 .../src/main/bin/seatunnel-cluster.sh              |   6 +
 .../seatunnel/SeaTunnelClusterScriptTest.java      | 193 +++++++++++++++++++++
 3 files changed, 206 insertions(+)

diff --git 
a/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.cmd 
b/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.cmd
index cd17ce43e5..cc19fe9bc6 100644
--- a/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.cmd
+++ b/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.cmd
@@ -47,6 +47,13 @@ for %%I in (%*) do (
 set "JAVA_OPTS=%JvmOption%"
 set "SEATUNNEL_CONFIG=%CONF_DIR%\seatunnel.yaml"
 
+rem Anchor cluster-side connector discovery to the distribution when no 
explicit home is provided.
+if not defined SEATUNNEL_HOME (
+    set "SEATUNNEL_HOME=%APP_DIR%"
+)
+
+rem Publish the effective SeaTunnel home for both Java-property and 
environment-style lookups.
+set "JAVA_OPTS=!JAVA_OPTS! -Dseatunnel.home=!SEATUNNEL_HOME! 
-DSEATUNNEL_HOME=!SEATUNNEL_HOME!"
 set "JAVA_OPTS=!JAVA_OPTS! 
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
 set "JAVA_OPTS=!JAVA_OPTS! -Dlog4j2.isThreadContextMapInheritable=true"
 set "JAVA_OPTS=!JAVA_OPTS! -DAsyncLogger.ThreadNameStrategy=UNCACHED"
diff --git a/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh 
b/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh
index 8eff7127fa..2a91905026 100755
--- a/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh
+++ b/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh
@@ -48,6 +48,10 @@ if [ -f "${CONF_DIR}/seatunnel-env.sh" ]; then
     . "${CONF_DIR}/seatunnel-env.sh"
 fi
 
+if [ -z "${SEATUNNEL_HOME:-}" ]; then
+    SEATUNNEL_HOME=${APP_DIR}
+fi
+
 if [ $# == 0 ]
 then
     args=""
@@ -57,6 +61,8 @@ fi
 
 set +u
 
+JAVA_OPTS="${JAVA_OPTS} -Dseatunnel.home=${SEATUNNEL_HOME} 
-DSEATUNNEL_HOME=${SEATUNNEL_HOME}"
+
 if [ -z $SEATUNNEL_CONFIG ]; then
     SEATUNNEL_CONFIG=${CONF_DIR}/seatunnel.yaml
 fi
diff --git 
a/seatunnel-core/seatunnel-starter/src/test/java/org/apache/seatunnel/core/starter/seatunnel/SeaTunnelClusterScriptTest.java
 
b/seatunnel-core/seatunnel-starter/src/test/java/org/apache/seatunnel/core/starter/seatunnel/SeaTunnelClusterScriptTest.java
new file mode 100644
index 0000000000..48739a8fcc
--- /dev/null
+++ 
b/seatunnel-core/seatunnel-starter/src/test/java/org/apache/seatunnel/core/starter/seatunnel/SeaTunnelClusterScriptTest.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.core.starter.seatunnel;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+
+@EnabledOnOs({OS.LINUX, OS.MAC})
+public class SeaTunnelClusterScriptTest {
+
+    @TempDir private Path temporaryDirectory;
+
+    /**
+     * Verifies that cluster startup publishes the distribution home to Java 
system properties.
+     *
+     * <p>The server uses these properties to resolve connector metadata and 
connector jars when
+     * jobs are submitted through cluster-side paths such as REST.
+     */
+    @Test
+    public void testClusterScriptPassesSeatunnelHomeToServerJvm() throws 
Exception {
+        Path appDirectory = createMinimalDistribution();
+        List<String> arguments = runClusterScript(appDirectory, null);
+
+        Assertions.assertTrue(
+                arguments.contains("-Dseatunnel.home=" + appDirectory),
+                "seatunnel.home should default to the script distribution 
directory");
+        Assertions.assertTrue(
+                arguments.contains("-DSEATUNNEL_HOME=" + appDirectory),
+                "SEATUNNEL_HOME should default to the script distribution 
directory");
+    }
+
+    /**
+     * Verifies that an externally configured SeaTunnel home is forwarded 
without being overwritten
+     * by the script fallback.
+     */
+    @Test
+    public void testClusterScriptPassesCustomSeatunnelHomeToServerJvm() throws 
Exception {
+        Path appDirectory = createMinimalDistribution();
+        Path customSeaTunnelHome = 
temporaryDirectory.resolve("custom-seatunnel-home");
+        Files.createDirectories(customSeaTunnelHome);
+        List<String> arguments = runClusterScript(appDirectory, 
customSeaTunnelHome);
+
+        Assertions.assertTrue(
+                arguments.contains("-Dseatunnel.home=" + customSeaTunnelHome),
+                "seatunnel.home should use the externally configured SeaTunnel 
home");
+        Assertions.assertTrue(
+                arguments.contains("-DSEATUNNEL_HOME=" + customSeaTunnelHome),
+                "SEATUNNEL_HOME should use the externally configured SeaTunnel 
home");
+    }
+
+    /**
+     * Verifies that the Windows cluster script keeps custom SeaTunnel home 
values and forwards the
+     * effective value before later JVM option appends.
+     */
+    @Test
+    public void testWindowsClusterScriptPassesSeatunnelHomeToServerJvm() 
throws Exception {
+        String script =
+                new String(Files.readAllBytes(locateWindowsClusterScript()), 
StandardCharsets.UTF_8)
+                        .replace("\r\n", "\n");
+
+        int fallbackIndex = script.indexOf("if not defined SEATUNNEL_HOME");
+        int fallbackValueIndex = script.indexOf("set 
\"SEATUNNEL_HOME=%APP_DIR%\"");
+        int seaTunnelHomeJvmOptionIndex =
+                script.indexOf(
+                        "set \"JAVA_OPTS=!JAVA_OPTS! 
-Dseatunnel.home=!SEATUNNEL_HOME! "
+                                + "-DSEATUNNEL_HOME=!SEATUNNEL_HOME!\"");
+        int log4jJvmOptionIndex =
+                script.indexOf(
+                        "set \"JAVA_OPTS=!JAVA_OPTS! "
+                                + 
"-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector\"");
+
+        Assertions.assertTrue(
+                fallbackIndex >= 0,
+                "Windows cluster script should only fallback when 
SEATUNNEL_HOME is undefined");
+        Assertions.assertTrue(
+                fallbackValueIndex > fallbackIndex,
+                "Windows cluster script should fallback to APP_DIR as the 
distribution home");
+        Assertions.assertTrue(
+                seaTunnelHomeJvmOptionIndex > fallbackValueIndex,
+                "Windows cluster script should pass the effective SeaTunnel 
home to the JVM");
+        Assertions.assertTrue(
+                seaTunnelHomeJvmOptionIndex < log4jJvmOptionIndex,
+                "SeaTunnel home JVM options should be appended before later 
JVM options");
+    }
+
+    private List<String> runClusterScript(Path appDirectory, Path 
seaTunnelHome) throws Exception {
+        Path capturedArguments = temporaryDirectory.resolve("java-args.txt");
+        Path fakeJavaDirectory = createFakeJava(capturedArguments);
+        ProcessBuilder processBuilder =
+                new ProcessBuilder(
+                        "/bin/bash",
+                        
appDirectory.resolve("bin").resolve("seatunnel-cluster.sh").toString());
+        
processBuilder.directory(temporaryDirectory.resolve("working-dir").toFile());
+        Map<String, String> environment = processBuilder.environment();
+        environment.put(
+                "PATH",
+                fakeJavaDirectory
+                        + System.getProperty("path.separator")
+                        + environment.getOrDefault("PATH", ""));
+        environment.put("CAPTURE_FILE", capturedArguments.toString());
+        if (seaTunnelHome == null) {
+            environment.remove("SEATUNNEL_HOME");
+        } else {
+            environment.put("SEATUNNEL_HOME", seaTunnelHome.toString());
+        }
+        environment.remove("JAVA_OPTS");
+        environment.remove("JvmOption");
+        environment.remove("SEATUNNEL_CONFIG");
+        environment.remove("HAZELCAST_CONFIG");
+
+        Process process = processBuilder.start();
+        int exitCode = process.waitFor();
+
+        Assertions.assertEquals(0, exitCode);
+        return Files.readAllLines(capturedArguments, StandardCharsets.UTF_8);
+    }
+
+    private Path createMinimalDistribution() throws Exception {
+        Path appDirectory = temporaryDirectory.resolve("apache-seatunnel");
+        Path binDirectory = appDirectory.resolve("bin");
+        Path configDirectory = appDirectory.resolve("config");
+        Files.createDirectories(binDirectory);
+        Files.createDirectories(configDirectory);
+        Files.createDirectories(appDirectory.resolve("starter"));
+        Files.createDirectories(appDirectory.resolve("lib"));
+        Files.createDirectories(temporaryDirectory.resolve("working-dir"));
+
+        Files.copy(locateClusterScript(), 
binDirectory.resolve("seatunnel-cluster.sh"));
+        Files.createFile(configDirectory.resolve("hazelcast.yaml"));
+        Files.createFile(configDirectory.resolve("seatunnel.yaml"));
+        Files.createFile(configDirectory.resolve("jvm_options"));
+        Files.createFile(configDirectory.resolve("jvm_master_options"));
+        Files.createFile(configDirectory.resolve("jvm_worker_options"));
+        
Files.createFile(appDirectory.resolve("starter").resolve("seatunnel-starter.jar"));
+        return appDirectory;
+    }
+
+    private Path locateClusterScript() {
+        Path modulePath = Paths.get("src/main/bin/seatunnel-cluster.sh");
+        if (Files.exists(modulePath)) {
+            return modulePath;
+        }
+        return 
Paths.get("seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh");
+    }
+
+    private Path locateWindowsClusterScript() {
+        Path modulePath = Paths.get("src/main/bin/seatunnel-cluster.cmd");
+        if (Files.exists(modulePath)) {
+            return modulePath;
+        }
+        return 
Paths.get("seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.cmd");
+    }
+
+    private Path createFakeJava(Path capturedArguments) throws Exception {
+        Path fakeJavaDirectory = temporaryDirectory.resolve("fake-java-bin");
+        Files.createDirectories(fakeJavaDirectory);
+        Path fakeJava = fakeJavaDirectory.resolve("java");
+        Files.write(
+                fakeJava,
+                ("#!/bin/sh\n"
+                                + "for arg in \"$@\"; do\n"
+                                + "  printf '%s\\n' \"$arg\"\n"
+                                + "done > \"$CAPTURE_FILE\"\n")
+                        .getBytes(StandardCharsets.UTF_8));
+        Assertions.assertTrue(fakeJava.toFile().setExecutable(true));
+        return fakeJavaDirectory;
+    }
+}

Reply via email to