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 f01ee9f6b7 [Fix][Core] Fix Spark cluster config lookup (#11366)
f01ee9f6b7 is described below
commit f01ee9f6b7eb324be198dfc741a43349f09d0627
Author: Jast <[email protected]>
AuthorDate: Tue Jul 21 22:51:52 2026 +0800
[Fix][Core] Fix Spark cluster config lookup (#11366)
---
.../spark/command/SparkConfValidateCommand.java | 9 +-
.../spark/command/SparkTaskExecuteCommand.java | 51 +++++++-
.../spark/command/SparkTaskExecuteCommandTest.java | 134 +++++++++++++++++++++
3 files changed, 191 insertions(+), 3 deletions(-)
diff --git
a/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/command/SparkConfValidateCommand.java
b/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/command/SparkConfValidateCommand.java
index 9da9679703..c198dff3d0 100644
---
a/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/command/SparkConfValidateCommand.java
+++
b/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/command/SparkConfValidateCommand.java
@@ -20,12 +20,15 @@ package org.apache.seatunnel.core.starter.spark.command;
import org.apache.seatunnel.core.starter.command.Command;
import org.apache.seatunnel.core.starter.exception.ConfigCheckException;
import org.apache.seatunnel.core.starter.spark.args.SparkCommandArgs;
-import org.apache.seatunnel.core.starter.utils.FileUtils;
+
+import org.apache.spark.SparkFiles;
import lombok.extern.slf4j.Slf4j;
import java.nio.file.Path;
+import static
org.apache.seatunnel.core.starter.utils.FileUtils.checkConfigExist;
+
/** Use to validate the configuration of the SeaTunnel API. */
@Slf4j
public class SparkConfValidateCommand implements Command<SparkCommandArgs> {
@@ -38,7 +41,9 @@ public class SparkConfValidateCommand implements
Command<SparkCommandArgs> {
@Override
public void execute() throws ConfigCheckException {
- Path configPath = FileUtils.getConfigPath(sparkCommandArgs);
+ Path configPath =
+ SparkTaskExecuteCommand.resolveConfigPath(sparkCommandArgs,
SparkFiles::get);
+ checkConfigExist(configPath);
// TODO: validate the config by new api
}
}
diff --git
a/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/command/SparkTaskExecuteCommand.java
b/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/command/SparkTaskExecuteCommand.java
index 67fef2ac4c..33ba4db28f 100644
---
a/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/command/SparkTaskExecuteCommand.java
+++
b/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/command/SparkTaskExecuteCommand.java
@@ -23,6 +23,7 @@ import
org.apache.seatunnel.shade.com.typesafe.config.ConfigValueFactory;
import org.apache.seatunnel.api.metalake.MetalakeConfigUtils;
import org.apache.seatunnel.common.Constants;
+import org.apache.seatunnel.common.config.DeployMode;
import org.apache.seatunnel.core.starter.command.Command;
import org.apache.seatunnel.core.starter.exception.CommandExecuteException;
import org.apache.seatunnel.core.starter.spark.args.SparkCommandArgs;
@@ -30,9 +31,14 @@ import
org.apache.seatunnel.core.starter.spark.execution.SparkExecution;
import org.apache.seatunnel.core.starter.utils.ConfigBuilder;
import org.apache.seatunnel.core.starter.utils.FileUtils;
+import org.apache.spark.SparkFiles;
+
import lombok.extern.slf4j.Slf4j;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Function;
import static
org.apache.seatunnel.core.starter.utils.FileUtils.checkConfigExist;
@@ -47,7 +53,7 @@ public class SparkTaskExecuteCommand implements
Command<SparkCommandArgs> {
@Override
public void execute() throws CommandExecuteException {
- Path configFile = FileUtils.getConfigPath(sparkCommandArgs);
+ Path configFile = resolveConfigPath(sparkCommandArgs, SparkFiles::get);
checkConfigExist(configFile);
Config config =
MetalakeConfigUtils.getMetalakeConfig(
@@ -65,4 +71,47 @@ public class SparkTaskExecuteCommand implements
Command<SparkCommandArgs> {
throw new CommandExecuteException("Run SeaTunnel on spark failed",
e);
}
}
+
+ static Path resolveConfigPath(
+ SparkCommandArgs sparkCommandArgs, Function<String, String>
sparkFilesResolver) {
+ Path configFile = FileUtils.getConfigPath(sparkCommandArgs);
+ if (!DeployMode.CLUSTER.equals(sparkCommandArgs.getDeployMode())) {
+ log.info("Resolved SeaTunnel config file: {}",
configFile.toAbsolutePath());
+ return configFile;
+ }
+
+ Path fileName = configFile.getFileName();
+ if (fileName == null) {
+ log.info("Resolved SeaTunnel config file: {}",
configFile.toAbsolutePath());
+ return configFile;
+ }
+
+ String sparkFilePath;
+ try {
+ sparkFilePath = sparkFilesResolver.apply(fileName.toString());
+ } catch (RuntimeException e) {
+ log.debug("Failed to resolve SeaTunnel config file from
SparkFiles", e);
+ log.info("Resolved SeaTunnel config file: {}",
configFile.toAbsolutePath());
+ return configFile;
+ }
+ if (sparkFilePath == null) {
+ log.info("Resolved SeaTunnel config file: {}",
configFile.toAbsolutePath());
+ return configFile;
+ }
+
+ Path shippedConfigFile = Paths.get(sparkFilePath);
+ if (Files.exists(shippedConfigFile)) {
+ log.info("Resolved SeaTunnel config file: {}",
shippedConfigFile.toAbsolutePath());
+ return shippedConfigFile;
+ }
+ if (Files.exists(configFile)) {
+ log.info("Resolved SeaTunnel config file: {}",
configFile.toAbsolutePath());
+ return configFile;
+ }
+ log.warn(
+ "SeaTunnel config file was not found in working directory {}
or SparkFiles path {}",
+ configFile.toAbsolutePath(),
+ shippedConfigFile.toAbsolutePath());
+ return configFile;
+ }
}
diff --git
a/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/test/java/org/apache/seatunnel/core/starter/spark/command/SparkTaskExecuteCommandTest.java
b/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/test/java/org/apache/seatunnel/core/starter/spark/command/SparkTaskExecuteCommandTest.java
new file mode 100644
index 0000000000..74e3e686ad
--- /dev/null
+++
b/seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/test/java/org/apache/seatunnel/core/starter/spark/command/SparkTaskExecuteCommandTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.spark.command;
+
+import org.apache.seatunnel.common.config.DeployMode;
+import org.apache.seatunnel.core.starter.spark.args.SparkCommandArgs;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+class SparkTaskExecuteCommandTest {
+
+ @TempDir private Path tempDir;
+
+ @Test
+ void resolveClusterConfigFromSparkFilesWhenLocalBasenameMissing() throws
IOException {
+ Path shippedConfig =
Files.createFile(tempDir.resolve("v2.batch.config"));
+ SparkCommandArgs args = new SparkCommandArgs();
+ args.setDeployMode(DeployMode.CLUSTER);
+ args.setConfigFile(configFilePath("v2.batch.config"));
+
+ Path resolvedConfig =
+ SparkTaskExecuteCommand.resolveConfigPath(
+ args, fileName ->
tempDir.resolve(fileName).toString());
+
+ Assertions.assertEquals(shippedConfig, resolvedConfig);
+ }
+
+ @Test
+ void keepClientConfigPathWithoutCallingSparkFiles() throws IOException {
+ Path clientConfig = Files.createFile(tempDir.resolve("client.conf"));
+ SparkCommandArgs args = new SparkCommandArgs();
+ args.setDeployMode(DeployMode.CLIENT);
+ args.setConfigFile(clientConfig.toString());
+ AtomicBoolean sparkFilesCalled = new AtomicBoolean(false);
+
+ Path resolvedConfig =
+ SparkTaskExecuteCommand.resolveConfigPath(
+ args,
+ fileName -> {
+ sparkFilesCalled.set(true);
+ return tempDir.resolve(fileName).toString();
+ });
+
+ Assertions.assertEquals(clientConfig, resolvedConfig);
+ Assertions.assertFalse(sparkFilesCalled.get());
+ }
+
+ @Test
+ void keepClusterConfigPathWhenSparkFilesReturnsNull() {
+ SparkCommandArgs args = new SparkCommandArgs();
+ args.setDeployMode(DeployMode.CLUSTER);
+ args.setConfigFile(configFilePath("missing.conf"));
+
+ Path resolvedConfig = SparkTaskExecuteCommand.resolveConfigPath(args,
fileName -> null);
+
+ Assertions.assertEquals(Paths.get("missing.conf"), resolvedConfig);
+ }
+
+ @Test
+ void keepClusterConfigPathWhenSparkFilesThrowsException() {
+ SparkCommandArgs args = new SparkCommandArgs();
+ args.setDeployMode(DeployMode.CLUSTER);
+ args.setConfigFile(configFilePath("missing.conf"));
+
+ Path resolvedConfig =
+ SparkTaskExecuteCommand.resolveConfigPath(
+ args,
+ fileName -> {
+ throw new NullPointerException("SparkEnv is not
initialized");
+ });
+
+ Assertions.assertEquals(Paths.get("missing.conf"), resolvedConfig);
+ }
+
+ @Test
+ void keepClusterConfigPathWhenSparkFilesPathDoesNotExist() {
+ SparkCommandArgs args = new SparkCommandArgs();
+ args.setDeployMode(DeployMode.CLUSTER);
+ args.setConfigFile(configFilePath("missing.conf"));
+
+ Path resolvedConfig =
+ SparkTaskExecuteCommand.resolveConfigPath(
+ args, fileName ->
tempDir.resolve(fileName).toString());
+
+ Assertions.assertEquals(Paths.get("missing.conf"), resolvedConfig);
+ }
+
+ @Test
+ void preferSparkFilesConfigWhenLocalBasenameExists() throws IOException {
+ Path localConfig = Paths.get("spark-local-basename-exists.conf");
+ Path shippedConfig =
Files.createFile(tempDir.resolve(localConfig.getFileName()));
+ Files.createFile(localConfig);
+ try {
+ SparkCommandArgs args = new SparkCommandArgs();
+ args.setDeployMode(DeployMode.CLUSTER);
+
args.setConfigFile(configFilePath(localConfig.getFileName().toString()));
+
+ Path resolvedConfig =
+ SparkTaskExecuteCommand.resolveConfigPath(
+ args, fileName ->
tempDir.resolve(fileName).toString());
+
+ Assertions.assertEquals(shippedConfig, resolvedConfig);
+ } finally {
+ Files.deleteIfExists(localConfig);
+ }
+ }
+
+ private String configFilePath(String fileName) {
+ return Paths.get("opt", "seatunnel", "config", fileName).toString();
+ }
+}