This is an automated email from the ASF dual-hosted git repository.
fanjia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/seatunnel-web.git
The following commit(s) were added to refs/heads/main by this push:
new cf1f6c9b [Bug][Seatunnel-web] Job update API fails when tasks are
added or removed (#249)
cf1f6c9b is described below
commit cf1f6c9b5f75d5a398e4c9601465976863e84052
Author: Mohammad Arshad <[email protected]>
AuthorDate: Tue Dec 31 11:26:24 2024 +0530
[Bug][Seatunnel-web] Job update API fails when tasks are added or removed
(#249)
---
.../seatunnel/app/service/impl/JobServiceImpl.java | 37 ++++++---
.../seatunnel/app/test/JobControllerTest.java | 94 ++++++++++++++++++++++
.../seatunnel/app/utils/JobTestingUtils.java | 34 ++++++--
3 files changed, 147 insertions(+), 18 deletions(-)
diff --git
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java
index 7ac97cc3..8a0499b0 100644
---
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java
+++
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java
@@ -47,6 +47,9 @@ import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
@Service
public class JobServiceImpl implements IJobService {
@@ -61,14 +64,31 @@ public class JobServiceImpl implements IJobService {
throws JsonProcessingException {
JobReq jobDefinition =
getJobDefinition(jobCreateRequest.getJobConfig());
long jobId = jobService.createJob(userId, jobDefinition);
+ createTasks(userId, jobCreateRequest, jobId);
+ return jobId;
+ }
+
+ private void createTasks(int userId, JobCreateReq jobCreateRequest, long
jobId)
+ throws JsonProcessingException {
List<PluginConfig> pluginConfig = jobCreateRequest.getPluginConfigs();
+ Set<String> edgeIds =
+ jobCreateRequest.getJobDAG().getEdges().stream()
+ .flatMap(
+ edge ->
+ Stream.of(
+ edge.getInputPluginId(),
edge.getTargetPluginId()))
+ .collect(Collectors.toSet());
Map<String, String> pluginNameVsPluginId = new HashMap<>();
if (pluginConfig != null) {
for (PluginConfig config : pluginConfig) {
- String pluginId =
String.valueOf(CodeGenerateUtils.getInstance().genCode());
- config.setPluginId(pluginId);
+ String pluginIdKey =
+ edgeIds.contains(config.getName())
+ ? config.getName()
+ : config.getPluginId();
+ String newPluginId =
String.valueOf(CodeGenerateUtils.getInstance().genCode());
+ config.setPluginId(newPluginId);
jobTaskService.saveSingleTask(jobId, config);
- pluginNameVsPluginId.put(config.getName(), pluginId);
+ pluginNameVsPluginId.put(pluginIdKey, newPluginId);
}
}
jobConfigService.updateJobConfig(userId, jobId,
jobCreateRequest.getJobConfig());
@@ -80,7 +100,6 @@ public class JobServiceImpl implements IJobService {
edge.setTargetPluginId(pluginNameVsPluginId.get(edge.getTargetPluginId()));
}
jobTaskService.saveJobDAG(jobId, jobDAG);
- return jobId;
}
private JobReq getJobDefinition(JobConfig jobConfig) {
@@ -118,14 +137,8 @@ public class JobServiceImpl implements IJobService {
@Override
public void updateJob(Integer userId, long jobVersionId, JobCreateReq
jobCreateReq)
throws JsonProcessingException {
- jobConfigService.updateJobConfig(userId, jobVersionId,
jobCreateReq.getJobConfig());
- List<PluginConfig> pluginConfigs = jobCreateReq.getPluginConfigs();
- if (pluginConfigs != null) {
- for (PluginConfig pluginConfig : pluginConfigs) {
- jobTaskService.saveSingleTask(jobVersionId, pluginConfig);
- }
- }
- jobTaskService.saveJobDAG(jobVersionId, jobCreateReq.getJobDAG());
+ jobTaskService.deleteTaskByVersionId(jobVersionId);
+ createTasks(userId, jobCreateReq, jobVersionId);
}
@Override
diff --git
a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobControllerTest.java
b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobControllerTest.java
index b8725b41..d3c0c8a3 100644
---
a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobControllerTest.java
+++
b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobControllerTest.java
@@ -20,6 +20,8 @@ import org.apache.seatunnel.app.common.Result;
import org.apache.seatunnel.app.common.SeaTunnelWebCluster;
import org.apache.seatunnel.app.controller.JobControllerWrapper;
import org.apache.seatunnel.app.controller.JobExecutorControllerWrapper;
+import org.apache.seatunnel.app.domain.request.connector.SceneMode;
+import org.apache.seatunnel.app.domain.request.job.Edge;
import org.apache.seatunnel.app.domain.request.job.JobConfig;
import org.apache.seatunnel.app.domain.request.job.JobCreateReq;
import org.apache.seatunnel.app.domain.request.job.JobDAG;
@@ -29,6 +31,7 @@ import org.apache.seatunnel.app.domain.response.job.JobRes;
import
org.apache.seatunnel.app.domain.response.metrics.JobPipelineDetailMetricsRes;
import org.apache.seatunnel.app.utils.JobTestingUtils;
import org.apache.seatunnel.common.constants.JobMode;
+import org.apache.seatunnel.common.constants.PluginType;
import org.apache.seatunnel.engine.core.job.JobStatus;
import org.apache.seatunnel.server.common.SeatunnelErrorEnum;
@@ -36,7 +39,11 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -187,6 +194,93 @@ public class JobControllerTest {
assertEquals(SeatunnelErrorEnum.ERROR_CONFIG.getCode(),
jobUpdateResult.getCode());
}
+ @Test
+ public void testUpdateJob_AddNewTask() {
+ String jobName = "updateJob_single_api_add_task" + uniqueId;
+ JobCreateReq jobCreateReq =
+ JobTestingUtils.populateJobCreateReqFromFile(
+ jobName, "fake_source_create_4" + uniqueId,
"console_create_4" + uniqueId);
+
+ Result<Long> job = jobControllerWrapper.createJob(jobCreateReq);
+ assertTrue(job.isSuccess());
+ JobTestingUtils.executeJobAndVerifySuccess(job.getData());
+
+ Result<JobRes> getJobResponse =
jobControllerWrapper.getJob(job.getData());
+ assertTrue(getJobResponse.isSuccess());
+ JobRes jobRes = getJobResponse.getData();
+
+ JobCreateReq jobUpdateReq = convertJobResToJobCreateReq(jobRes);
+ jobUpdateReq.getPluginConfigs().add(getCopyTransformPlugin());
+
+ List<Edge> edges = new ArrayList<>();
+ edges.add(new Edge("source-fake-source", "transform-replace"));
+ edges.add(new Edge("transform-replace", "transform-copy"));
+ edges.add(new Edge("transform-copy", "sink-console"));
+ JobDAG jobDAG = new JobDAG(edges);
+ jobUpdateReq.setJobDAG(jobDAG);
+ String jobName2 = "updateJob_single_api_add_task_up" + uniqueId;
+ jobUpdateReq.getJobConfig().setName(jobName2);
+ jobUpdateReq.getJobConfig().setDescription(jobName2 + " description");
+
+ Result<Void> jobUpdateResult =
jobControllerWrapper.updateJob(job.getData(), jobUpdateReq);
+ assertTrue(jobUpdateResult.isSuccess());
+ JobTestingUtils.executeJobAndVerifySuccess(job.getData());
+ }
+
+ @Test
+ public void testUpdateJob_RemoveTask() {
+ String jobName = "updateJob_single_api_remove_task" + uniqueId;
+ JobCreateReq jobCreateReq =
+ JobTestingUtils.populateJobCreateReqFromFile(
+ jobName, "fake_source_create_5" + uniqueId,
"console_create_5" + uniqueId);
+
+ Result<Long> job = jobControllerWrapper.createJob(jobCreateReq);
+ assertTrue(job.isSuccess());
+ JobTestingUtils.executeJobAndVerifySuccess(job.getData());
+
+ Result<JobRes> getJobResponse =
jobControllerWrapper.getJob(job.getData());
+ assertTrue(getJobResponse.isSuccess());
+ JobRes jobRes = getJobResponse.getData();
+
+ JobCreateReq jobUpdateReq = convertJobResToJobCreateReq(jobRes);
+ jobUpdateReq
+ .getPluginConfigs()
+ .removeIf(pluginConfig ->
"transform-replace".equals(pluginConfig.getName()));
+
+ List<Edge> edges = new ArrayList<>();
+ edges.add(new Edge("source-fake-source", "sink-console"));
+ JobDAG jobDAG = new JobDAG(edges);
+ jobUpdateReq.setJobDAG(jobDAG);
+
+ Result<Void> jobUpdateResult =
jobControllerWrapper.updateJob(job.getData(), jobUpdateReq);
+ assertTrue(jobUpdateResult.isSuccess());
+ JobTestingUtils.executeJobAndVerifySuccess(job.getData());
+ }
+
+ private PluginConfig getCopyTransformPlugin() {
+ String transPluginId = "copy" + System.currentTimeMillis();
+ Map<String, Object> transOptions = new HashMap<>();
+ transOptions.put(
+ "copyList",
+ Arrays.asList(
+ new HashMap<String, String>() {
+ {
+ put("sourceFieldName", "name");
+ put("targetFieldName", "name_copy");
+ }
+ }));
+ return PluginConfig.builder()
+ .pluginId(transPluginId)
+ .name("transform-copy")
+ .type(PluginType.TRANSFORM)
+ .connectorType("Copy")
+ .transformOptions(transOptions)
+ .outputSchema(null)
+ .sceneMode(SceneMode.SINGLE_TABLE)
+ .config("{\"query\":\"\"}")
+ .build();
+ }
+
private JobCreateReq convertJobResToJobCreateReq(JobRes jobRes) {
JobCreateReq jobCreateReq = new JobCreateReq();
diff --git
a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/utils/JobTestingUtils.java
b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/utils/JobTestingUtils.java
index 6ca28f2d..82f265a4 100644
---
a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/utils/JobTestingUtils.java
+++
b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/utils/JobTestingUtils.java
@@ -20,6 +20,7 @@ import org.apache.seatunnel.app.common.Result;
import org.apache.seatunnel.app.controller.JobConfigControllerWrapper;
import org.apache.seatunnel.app.controller.JobControllerWrapper;
import org.apache.seatunnel.app.controller.JobDefinitionControllerWrapper;
+import org.apache.seatunnel.app.controller.JobExecutorControllerWrapper;
import org.apache.seatunnel.app.controller.JobMetricsControllerWrapper;
import org.apache.seatunnel.app.controller.JobTaskControllerWrapper;
import
org.apache.seatunnel.app.controller.SeatunnelDatasourceControllerWrapper;
@@ -31,6 +32,7 @@ import
org.apache.seatunnel.app.domain.request.job.PluginConfig;
import org.apache.seatunnel.app.domain.response.job.JobTaskCheckRes;
import
org.apache.seatunnel.app.domain.response.metrics.JobPipelineDetailMetricsRes;
import org.apache.seatunnel.common.utils.JsonUtils;
+import org.apache.seatunnel.engine.core.job.JobStatus;
import java.io.IOException;
import java.nio.file.Files;
@@ -39,20 +41,23 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class JobTestingUtils {
- private static JobMetricsControllerWrapper jobMetricsControllerWrapper =
+ private static final JobMetricsControllerWrapper
jobMetricsControllerWrapper =
new JobMetricsControllerWrapper();
- private static JobConfigControllerWrapper jobConfigControllerWrapper =
+ private static final JobConfigControllerWrapper jobConfigControllerWrapper
=
new JobConfigControllerWrapper();
- private static JobDefinitionControllerWrapper
jobDefinitionControllerWrapper =
+ private static final JobDefinitionControllerWrapper
jobDefinitionControllerWrapper =
new JobDefinitionControllerWrapper();
- private static JobTaskControllerWrapper jobTaskControllerWrapper =
+ private static final JobTaskControllerWrapper jobTaskControllerWrapper =
new JobTaskControllerWrapper();
- private static SeatunnelDatasourceControllerWrapper
seatunnelDatasourceControllerWrapper =
+ private static final SeatunnelDatasourceControllerWrapper
seatunnelDatasourceControllerWrapper =
new SeatunnelDatasourceControllerWrapper();
- private static JobControllerWrapper jobControllerWrapper = new
JobControllerWrapper();
+ private static final JobControllerWrapper jobControllerWrapper = new
JobControllerWrapper();
+ private static final JobExecutorControllerWrapper
jobExecutorControllerWrapper =
+ new JobExecutorControllerWrapper();
private static final long TIMEOUT = 60; // 1 minute
private static final long INTERVAL = 2; // 1 second
@@ -208,4 +213,21 @@ public class JobTestingUtils {
assertTrue(jobCreateResult.isSuccess());
return jobCreateResult.getData();
}
+
+ public static void executeJobAndVerifySuccess(long jobVersionId) {
+ executeJobAndVerifySuccess(jobVersionId, 5, 5);
+ }
+
+ public static void executeJobAndVerifySuccess(
+ long jobVersionId, long expectedReadRowCount, long
expectedWriteRowCount) {
+ Result<Long> result =
jobExecutorControllerWrapper.jobExecutor(jobVersionId);
+ assertTrue(result.isSuccess());
+ assertTrue(result.getData() > 0);
+ Result<List<JobPipelineDetailMetricsRes>> listResult =
+ JobTestingUtils.waitForJobCompletion(result.getData());
+ assertEquals(1, listResult.getData().size());
+ assertEquals(JobStatus.FINISHED,
listResult.getData().get(0).getStatus());
+ assertEquals(expectedReadRowCount,
listResult.getData().get(0).getReadRowCount());
+ assertEquals(expectedWriteRowCount,
listResult.getData().get(0).getWriteRowCount());
+ }
}