From 81bdc43c7fa5b03a7f6749ce89946dd029aee817 Mon Sep 17 00:00:00 2001 From: Tobias Soloschenko Date: Mon, 17 Jul 2023 07:17:00 +0200 Subject: [PATCH] feature: UnexpectedTaskExecutionException for CTR --- .../TaskLauncherTasklet.java | 12 +- .../UnexpectedTaskExecutionException.java | 199 ++++++++++++++++++ .../TaskLauncherTaskletTests.java | 16 +- 3 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 spring-cloud-dataflow-composed-task-runner/src/main/java/org/springframework/cloud/dataflow/composedtaskrunner/support/UnexpectedTaskExecutionException.java diff --git a/spring-cloud-dataflow-composed-task-runner/src/main/java/org/springframework/cloud/dataflow/composedtaskrunner/TaskLauncherTasklet.java b/spring-cloud-dataflow-composed-task-runner/src/main/java/org/springframework/cloud/dataflow/composedtaskrunner/TaskLauncherTasklet.java index bb32a2f05a..9c7035e0d6 100644 --- a/spring-cloud-dataflow-composed-task-runner/src/main/java/org/springframework/cloud/dataflow/composedtaskrunner/TaskLauncherTasklet.java +++ b/spring-cloud-dataflow-composed-task-runner/src/main/java/org/springframework/cloud/dataflow/composedtaskrunner/TaskLauncherTasklet.java @@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory; import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.UnexpectedJobExecutionException; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.item.ExecutionContext; @@ -40,6 +39,7 @@ import org.springframework.cloud.dataflow.composedtaskrunner.properties.ComposedTaskProperties; import org.springframework.cloud.dataflow.composedtaskrunner.support.ComposedTaskException; import org.springframework.cloud.dataflow.composedtaskrunner.support.TaskExecutionTimeoutException; +import org.springframework.cloud.dataflow.composedtaskrunner.support.UnexpectedTaskExecutionException; import org.springframework.cloud.dataflow.rest.client.DataFlowOperations; import org.springframework.cloud.dataflow.rest.client.DataFlowTemplate; import org.springframework.cloud.dataflow.rest.client.TaskOperations; @@ -234,10 +234,12 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon TaskExecution taskExecution = this.taskExplorer.getTaskExecution(this.executionId); if (taskExecution != null && taskExecution.getEndTime() != null) { if (taskExecution.getExitCode() == null) { - throw new UnexpectedJobExecutionException("Task returned a null exit code."); - } else if (taskExecution.getExitCode() != 0) { - throw new UnexpectedJobExecutionException("Task returned a non zero exit code."); - } else { + throw new UnexpectedTaskExecutionException("Task returned a null exit code.", taskExecution); + } + else if (taskExecution.getExitCode() != 0) { + throw new UnexpectedTaskExecutionException("Task returned a non zero exit code.", taskExecution); + } + else { return RepeatStatus.FINISHED; } } diff --git a/spring-cloud-dataflow-composed-task-runner/src/main/java/org/springframework/cloud/dataflow/composedtaskrunner/support/UnexpectedTaskExecutionException.java b/spring-cloud-dataflow-composed-task-runner/src/main/java/org/springframework/cloud/dataflow/composedtaskrunner/support/UnexpectedTaskExecutionException.java new file mode 100644 index 0000000000..e60deb3a74 --- /dev/null +++ b/spring-cloud-dataflow-composed-task-runner/src/main/java/org/springframework/cloud/dataflow/composedtaskrunner/support/UnexpectedTaskExecutionException.java @@ -0,0 +1,199 @@ +/* + * Copyright 2017-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.cloud.dataflow.composedtaskrunner.support; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.boot.ExitCodeGenerator; +import org.springframework.cloud.task.repository.TaskExecution; + +/** + * Creates a {@link UnexpectedTaskExecutionException} which extends {@link UnsupportedOperationException}, but + * also contains the exitCode as information. + * + * @author Tobias Soloschenko + */ +public class UnexpectedTaskExecutionException extends UnexpectedJobExecutionException implements ExitCodeGenerator { + + private static final long serialVersionUID = 3494615323781500165L; + + /** + * The unique id associated with the task execution. + */ + private long executionId; + + /** + * The parent task execution id. + */ + private Long parentExecutionId; + + /** + * The recorded exit code for the task. + */ + private Integer exitCode = -1; + + /** + * User defined name for the task. + */ + private String taskName; + + /** + * Time of when the task was started. + */ + private Date startTime; + + /** + * Timestamp of when the task was completed/terminated. + */ + private Date endTime; + + /** + * Message returned from the task or stacktrace. + */ + private String exitMessage; + + /** + * Id assigned to the task by the platform. + */ + private String externalExecutionId; + + /** + * Error information available upon the failure of a task. + */ + private String errorMessage; + + /** + * The arguments that were used for this task execution. + */ + private ArrayList arguments; + + /** + * Constructs an UnexpectedTaskExecutionException with the specified + * detail message. + * + * @param message the detail message + */ + public UnexpectedTaskExecutionException(String message) { + super(message); + } + + /** + * Constructs an UnexpectedTaskExecutionException with the specified + * detail message, cause and exitCode. + * + * @param message the detail message + * @param cause the cause which leads to this exception + */ + public UnexpectedTaskExecutionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an UnexpectedTaskExecutionException with the specified + * detail message and taskExecution. + * + * @param message the detail message + * @param taskExecution the taskExecution of the task + */ + public UnexpectedTaskExecutionException(String message, TaskExecution taskExecution) { + this(message); + assignTaskExecutionFields(taskExecution); + } + + /** + * Constructs an UnexpectedTaskExecutionException with the specified + * detail message, cause and taskExecution. + * + * @param message the detail message + * @param cause the cause which leads to this exception + * @param taskExecution the taskExecution of the task + */ + public UnexpectedTaskExecutionException(String message, Throwable cause, TaskExecution taskExecution) { + this(message, cause); + assignTaskExecutionFields(taskExecution); + } + + /** + * Assigns the task execution fields to this exception. + * + * @param taskExecution the task execution of which the fields should be assigned to this exception + */ + private void assignTaskExecutionFields(TaskExecution taskExecution) { + if(taskExecution != null) { + executionId = taskExecution.getExecutionId(); + parentExecutionId = taskExecution.getParentExecutionId(); + exitCode = taskExecution.getExitCode(); + taskName = taskExecution.getTaskName(); + startTime = taskExecution.getStartTime(); + endTime = taskExecution.getEndTime(); + externalExecutionId = taskExecution.getExternalExecutionId(); + errorMessage = taskExecution.getErrorMessage(); + exitMessage = taskExecution.getExitMessage(); + arguments = new ArrayList<>(taskExecution.getArguments()); + } + } + + public long getExecutionId() { + return this.executionId; + } + + /** + * Returns the exit code of the task. + * + * @return the exit code or -1 if the exit code couldn't be determined + */ + @Override + public int getExitCode() { + return this.exitCode; + } + + public String getTaskName() { + return this.taskName; + } + + public Date getStartTime() { + return (this.startTime != null) ? (Date) this.startTime.clone() : null; + } + + public Date getEndTime() { + return (this.endTime != null) ? (Date) this.endTime.clone() : null; + } + + public String getExitMessage() { + return this.exitMessage; + } + + public List getArguments() { + return this.arguments; + } + + public String getErrorMessage() { + return this.errorMessage; + } + + public String getExternalExecutionId() { + return this.externalExecutionId; + } + + public Long getParentExecutionId() { + return this.parentExecutionId; + } + +} \ No newline at end of file diff --git a/spring-cloud-dataflow-composed-task-runner/src/test/java/org/springframework/cloud/dataflow/composedtaskrunner/TaskLauncherTaskletTests.java b/spring-cloud-dataflow-composed-task-runner/src/test/java/org/springframework/cloud/dataflow/composedtaskrunner/TaskLauncherTaskletTests.java index 874eb464b0..f95e3cbd63 100644 --- a/spring-cloud-dataflow-composed-task-runner/src/test/java/org/springframework/cloud/dataflow/composedtaskrunner/TaskLauncherTaskletTests.java +++ b/spring-cloud-dataflow-composed-task-runner/src/test/java/org/springframework/cloud/dataflow/composedtaskrunner/TaskLauncherTaskletTests.java @@ -40,7 +40,6 @@ import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.scope.context.StepContext; @@ -52,6 +51,7 @@ import org.springframework.cloud.dataflow.composedtaskrunner.properties.ComposedTaskProperties; import org.springframework.cloud.dataflow.composedtaskrunner.support.ComposedTaskException; import org.springframework.cloud.dataflow.composedtaskrunner.support.TaskExecutionTimeoutException; +import org.springframework.cloud.dataflow.composedtaskrunner.support.UnexpectedTaskExecutionException; import org.springframework.cloud.dataflow.core.database.support.MultiSchemaTaskExecutionDaoFactoryBean; import org.springframework.cloud.dataflow.rest.client.DataFlowClientException; import org.springframework.cloud.dataflow.rest.client.DataFlowOperations; @@ -318,10 +318,14 @@ public void testTaskLauncherTaskletFailure() { mockReturnValForTaskExecution(1L); TaskLauncherTasklet taskLauncherTasklet = getTaskExecutionTasklet(); ChunkContext chunkContext = chunkContext(); - createCompleteTaskExecution(1); - Throwable exception = assertThrows(UnexpectedJobExecutionException.class, + createCompleteTaskExecution(1, "This is the exit message of the task itself."); + UnexpectedTaskExecutionException exception = assertThrows(UnexpectedTaskExecutionException.class, () -> execute(taskLauncherTasklet, null, chunkContext)); Assertions.assertThat(exception.getMessage()).isEqualTo("Task returned a non zero exit code."); + Assertions.assertThat(exception.getMessage()).isEqualTo("Task returned a non zero exit code."); + Assertions.assertThat(exception.getExitCode()).isEqualTo(1); + Assertions.assertThat(exception.getExitMessage()).isEqualTo("This is the exit message of the task itself."); + Assertions.assertThat(exception.getEndTime()).isNotNull(); } private RepeatStatus execute(TaskLauncherTasklet taskLauncherTasklet, StepContribution contribution, @@ -341,7 +345,7 @@ public void testTaskLauncherTaskletNullResult() { TaskLauncherTasklet taskLauncherTasklet = getTaskExecutionTasklet(); ChunkContext chunkContext = chunkContext(); getCompleteTaskExecutionWithNull(); - Throwable exception = assertThrows(UnexpectedJobExecutionException.class, + Throwable exception = assertThrows(UnexpectedTaskExecutionException.class, () -> execute(taskLauncherTasklet, null, chunkContext)); Assertions.assertThat(exception.getMessage()).isEqualTo("Task returned a null exit code."); } @@ -455,10 +459,10 @@ public void testTaskOperationsConfiguredWithMissingUsername() { } fail("Expected an IllegalArgumentException to be thrown"); } - private void createCompleteTaskExecution(int exitCode) { + private void createCompleteTaskExecution(int exitCode, String... message) { TaskExecution taskExecution = this.taskRepository.createTaskExecution(); this.taskRepository.completeTaskExecution(taskExecution.getExecutionId(), - exitCode, new Date(), ""); + exitCode, new Date(), message != null && message.length > 0 ? message[0] : ""); } private void createAndStartCompleteTaskExecution(int exitCode, JobExecution jobExecution) {