Skip to content

Commit 1df6b83

Browse files
[Feature][Zeta]Add jar path precheck when job submit on master (#7976)
1 parent fe03f0f commit 1df6b83

File tree

9 files changed

+169
-13
lines changed

9 files changed

+169
-13
lines changed

seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-2/src/test/resources/jdbc_starrocks_dialect.conf

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
env {
1919
parallelism = 1
2020
job.mode = "BATCH"
21-
jars = "file:///tmp/jars/mysql-connector-java-8.0.16.jar"
2221
}
2322

2423
source {

seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-2/src/test/resources/jdbc_starrocks_source_to_sink.conf

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
env {
1919
parallelism = 1
2020
job.mode = "BATCH"
21-
jars = "file:///tmp/jars/mysql-connector-java-8.0.16.jar"
2221
}
2322

2423
source {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.seatunnel.engine.common.exception;
18+
19+
import org.apache.seatunnel.common.exception.SeaTunnelErrorCode;
20+
21+
public enum ClassLoaderErrorCode implements SeaTunnelErrorCode {
22+
NOT_FOUND_JAR("NOT-FOUND-JAR", "Jar package not found");
23+
24+
private final String code;
25+
private final String description;
26+
27+
ClassLoaderErrorCode(String code, String description) {
28+
this.code = code;
29+
this.description = description;
30+
}
31+
32+
@Override
33+
public String getCode() {
34+
return code;
35+
}
36+
37+
@Override
38+
public String getDescription() {
39+
return description;
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.seatunnel.engine.common.exception;
19+
20+
import org.apache.seatunnel.common.exception.ExceptionParamsUtil;
21+
import org.apache.seatunnel.common.exception.SeaTunnelErrorCode;
22+
23+
import java.util.HashMap;
24+
25+
public class ClassLoaderException extends SeaTunnelEngineException {
26+
27+
public ClassLoaderException(SeaTunnelErrorCode seaTunnelErrorCode, String errorMessage) {
28+
super(seaTunnelErrorCode.getErrorMessage() + " - " + errorMessage);
29+
ExceptionParamsUtil.assertParamsMatchWithDescription(
30+
seaTunnelErrorCode.getDescription(), new HashMap<>());
31+
}
32+
}

seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/classloader/DefaultClassLoaderService.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,21 @@
1717

1818
package org.apache.seatunnel.engine.core.classloader;
1919

20+
import org.apache.seatunnel.engine.common.exception.ClassLoaderErrorCode;
21+
import org.apache.seatunnel.engine.common.exception.ClassLoaderException;
2022
import org.apache.seatunnel.engine.common.loader.SeaTunnelChildFirstClassLoader;
2123

2224
import com.google.common.annotations.VisibleForTesting;
25+
import com.hazelcast.spi.impl.NodeEngine;
26+
import com.hazelcast.spi.impl.NodeEngineImpl;
27+
import lombok.SneakyThrows;
2328
import lombok.extern.slf4j.Slf4j;
2429

30+
import java.io.File;
2531
import java.net.URL;
2632
import java.util.Collection;
2733
import java.util.Map;
34+
import java.util.Objects;
2835
import java.util.Optional;
2936
import java.util.concurrent.ConcurrentHashMap;
3037
import java.util.concurrent.atomic.AtomicInteger;
@@ -34,14 +41,17 @@ public class DefaultClassLoaderService implements ClassLoaderService {
3441
private final boolean cacheMode;
3542
private final Map<Long, Map<String, ClassLoader>> classLoaderCache;
3643
private final Map<Long, Map<String, AtomicInteger>> classLoaderReferenceCount;
44+
private final NodeEngine nodeEngine;
3745

38-
public DefaultClassLoaderService(boolean cacheMode) {
46+
public DefaultClassLoaderService(boolean cacheMode, NodeEngine nodeEngine) {
3947
this.cacheMode = cacheMode;
48+
this.nodeEngine = nodeEngine;
4049
classLoaderCache = new ConcurrentHashMap<>();
4150
classLoaderReferenceCount = new ConcurrentHashMap<>();
4251
log.info("start classloader service" + (cacheMode ? " with cache mode" : ""));
4352
}
4453

54+
@SneakyThrows
4555
@Override
4656
public synchronized ClassLoader getClassLoader(long jobId, Collection<URL> jars) {
4757
log.debug("Get classloader for job {} with jars {}", jobId, jars);
@@ -59,6 +69,24 @@ public synchronized ClassLoader getClassLoader(long jobId, Collection<URL> jars)
5969
classLoaderReferenceCount.get(jobId).get(key).incrementAndGet();
6070
return classLoaderMap.get(key);
6171
} else {
72+
if (Objects.nonNull(nodeEngine)) {
73+
for (URL jar : jars) {
74+
File file = new File(jar.toURI().getPath());
75+
if (!file.exists()) {
76+
String host =
77+
((NodeEngineImpl) nodeEngine).getNode().getThisAddress().getHost();
78+
throw new ClassLoaderException(
79+
ClassLoaderErrorCode.NOT_FOUND_JAR,
80+
"The jar file "
81+
+ jar
82+
+ " can not be found in node "
83+
+ host
84+
+ ", please ensure that the deployment paths of SeaTunnel on different nodes are consistent.");
85+
}
86+
}
87+
} else {
88+
log.debug("Run the test class without file checking");
89+
}
6290
ClassLoader classLoader = new SeaTunnelChildFirstClassLoader(jars);
6391
log.info("Create classloader for job {} with jars {}", jobId, jars);
6492
classLoaderMap.put(key, classLoader);

seatunnel-engine/seatunnel-engine-core/src/test/java/org/apache/seatunnel/engine/core/classloader/AbstractClassLoaderServiceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public abstract class AbstractClassLoaderServiceTest {
3838

3939
@BeforeEach
4040
void setUp() {
41-
classLoaderService = new DefaultClassLoaderService(cacheMode());
41+
classLoaderService = new DefaultClassLoaderService(cacheMode(), null);
4242
}
4343

4444
@Test

seatunnel-engine/seatunnel-engine-core/src/test/java/org/apache/seatunnel/engine/core/classloader/ClassLoaderServiceTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,19 @@
1717

1818
package org.apache.seatunnel.engine.core.classloader;
1919

20+
import org.apache.seatunnel.engine.common.exception.ClassLoaderException;
21+
2022
import org.junit.jupiter.api.Assertions;
2123
import org.junit.jupiter.api.Test;
24+
import org.mockito.Mockito;
2225

2326
import com.google.common.collect.Lists;
27+
import com.hazelcast.cluster.Address;
28+
import com.hazelcast.instance.impl.Node;
29+
import com.hazelcast.spi.impl.NodeEngineImpl;
2430

31+
import java.io.File;
32+
import java.io.IOException;
2533
import java.net.MalformedURLException;
2634
import java.net.URL;
2735

@@ -100,4 +108,45 @@ void testRecycleClassLoaderFromThread() throws MalformedURLException, Interrupte
100108
Thread.sleep(2000);
101109
Assertions.assertFalse(thread.isAlive());
102110
}
111+
112+
@Test
113+
void testPreCheckJar() throws IOException {
114+
115+
// Mocking Node and NodeEngineImpl for testing
116+
Node mockNode = Mockito.mock(Node.class);
117+
Mockito.when(mockNode.getThisAddress()).thenReturn(new Address("localhost", 5801));
118+
NodeEngineImpl mockNodeEngine = Mockito.mock(NodeEngineImpl.class);
119+
Mockito.when(mockNodeEngine.getNode()).thenReturn(mockNode);
120+
// Creating DefaultClassLoaderService object for testing
121+
DefaultClassLoaderService defaultClassLoaderService =
122+
new DefaultClassLoaderService(cacheMode(), mockNodeEngine);
123+
// Test case to check ClassLoaderException when file is not found
124+
Assertions.assertThrows(
125+
ClassLoaderException.class,
126+
() -> {
127+
try {
128+
defaultClassLoaderService.getClassLoader(
129+
3L, Lists.newArrayList(new URL("file:/fake.jar")));
130+
} catch (ClassLoaderException e) {
131+
Assertions.assertTrue(
132+
e.getMessage()
133+
.contains(
134+
"The jar file file:/fake.jar can not be found in node localhost, please ensure that the deployment paths of SeaTunnel on different nodes are consistent."));
135+
throw e;
136+
}
137+
});
138+
139+
// Creating a temporary jar file for testing
140+
File tempJar = File.createTempFile("console", ".jar");
141+
String tempJarPath = tempJar.toURI().toURL().toString();
142+
143+
// Test case to check successful class loader creation with existing jar file
144+
Assertions.assertDoesNotThrow(
145+
() ->
146+
defaultClassLoaderService.getClassLoader(
147+
3L, Lists.newArrayList(new URL(tempJarPath))));
148+
149+
// Deleting the temporary jar file after test
150+
tempJar.delete();
151+
}
103152
}

seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/SeaTunnelServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public void init(NodeEngine engine, Properties hzProperties) {
122122

123123
classLoaderService =
124124
new DefaultClassLoaderService(
125-
seaTunnelConfig.getEngineConfig().isClassloaderCacheMode());
125+
seaTunnelConfig.getEngineConfig().isClassloaderCacheMode(), nodeEngine);
126126

127127
eventService = new EventService(nodeEngine);
128128

seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/TaskExecutionServiceTest.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
import com.hazelcast.internal.serialization.Data;
4444
import lombok.NonNull;
4545

46+
import java.io.File;
4647
import java.io.IOException;
47-
import java.net.MalformedURLException;
4848
import java.net.URL;
4949
import java.net.URLClassLoader;
5050
import java.util.ArrayList;
@@ -167,7 +167,12 @@ public void testFinish() {
167167
}
168168

169169
@Test
170-
public void testClassloaderSplit() throws MalformedURLException {
170+
public void testClassloaderSplit() throws IOException {
171+
File console = File.createTempFile("console", ".jar");
172+
File fake = File.createTempFile("fake", ".jar");
173+
String consoleFile = console.toURI().toURL().toString();
174+
String fakeFile = fake.toURI().toURL().toString();
175+
171176
TaskExecutionService taskExecutionService = server.getTaskExecutionService();
172177

173178
long sleepTime = 300;
@@ -190,8 +195,8 @@ public void testClassloaderSplit() throws MalformedURLException {
190195
nodeEngine.getSerializationService().toData(testTask1),
191196
nodeEngine.getSerializationService().toData(testTask2)),
192197
Arrays.asList(
193-
Collections.singleton(new URL("file://fake.jar")),
194-
Collections.singleton(new URL("file://console.jar"))),
198+
Collections.singleton(new URL(fakeFile)),
199+
Collections.singleton(new URL(consoleFile))),
195200
Arrays.asList(emptySet(), emptySet()));
196201

197202
Data data = nodeEngine.getSerializationService().toData(taskGroupImmutableInformation);
@@ -203,24 +208,27 @@ public void testClassloaderSplit() throws MalformedURLException {
203208
TaskGroupContext taskGroupContext =
204209
taskExecutionService.getActiveExecutionContext(location);
205210
Assertions.assertIterableEquals(
206-
Collections.singleton(new URL("file://fake.jar")),
211+
Collections.singleton(new URL(fakeFile)),
207212
taskGroupContext.getJars().get(testTask1.getTaskID()));
208213
Assertions.assertIterableEquals(
209-
Collections.singleton(new URL("file://console.jar")),
214+
Collections.singleton(new URL(consoleFile)),
210215
taskGroupContext.getJars().get(testTask2.getTaskID()));
211216

212217
Assertions.assertIterableEquals(
213-
Collections.singletonList(new URL("file://fake.jar")),
218+
Collections.singletonList(new URL(fakeFile)),
214219
Arrays.asList(
215220
((URLClassLoader) taskGroupContext.getClassLoader(testTask1.getTaskID()))
216221
.getURLs()));
217222
Assertions.assertIterableEquals(
218-
Collections.singletonList(new URL("file://console.jar")),
223+
Collections.singletonList(new URL(consoleFile)),
219224
Arrays.asList(
220225
((URLClassLoader) taskGroupContext.getClassLoader(testTask2.getTaskID()))
221226
.getURLs()));
222227

223228
taskExecutionService.cancelTaskGroup(location);
229+
230+
fake.delete();
231+
console.delete();
224232
}
225233

226234
/** Test task execution time is the same as the timer timeout */

0 commit comments

Comments
 (0)