This is an automated email from the ASF dual-hosted git repository.
jshao pushed a commit to branch branch-1.0
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.0 by this push:
new 6dc8135b26 [#8640] feat(server): Add server-side REST interface for
job template alteration (#8790)
6dc8135b26 is described below
commit 6dc8135b26a233c32640572b305999d5dbb5861c
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Oct 11 19:19:34 2025 +0800
[#8640] feat(server): Add server-side REST interface for job template
alteration (#8790)
### What changes were proposed in this pull request?
This PR adds the server-side REST interface for job template alteration.
### Why are the changes needed?
This is a part of work to support job template alteration.
Fix: #8640
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
UTs added.
Co-authored-by: Jerry Shao <[email protected]>
---
.../gravitino/dto/job/ShellTemplateUpdateDTO.java | 60 ++++
.../gravitino/dto/job/SparkTemplateUpdateDTO.java | 81 ++++++
.../gravitino/dto/job/TemplateUpdateDTO.java | 77 +++++
.../dto/requests/JobTemplateUpdateRequest.java | 163 +++++++++++
.../dto/requests/JobTemplateUpdatesRequest.java | 60 ++++
.../gravitino/dto/job/TestTemplateUpdateDTO.java | 320 +++++++++++++++++++++
.../requests/TestJobTemplateUpdatesRequest.java | 53 ++++
.../gravitino/server/web/rest/JobOperations.java | 38 +++
.../server/web/rest/TestJobOperations.java | 129 +++++++++
9 files changed, 981 insertions(+)
diff --git
a/common/src/main/java/org/apache/gravitino/dto/job/ShellTemplateUpdateDTO.java
b/common/src/main/java/org/apache/gravitino/dto/job/ShellTemplateUpdateDTO.java
new file mode 100644
index 0000000000..23574c6e36
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/job/ShellTemplateUpdateDTO.java
@@ -0,0 +1,60 @@
+/*
+ * 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.gravitino.dto.job;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import javax.annotation.Nullable;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.experimental.SuperBuilder;
+import org.apache.gravitino.job.JobTemplateChange;
+
+/** DTO for updating a Shell Job Template. */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@SuperBuilder(setterPrefix = "with")
+@ToString(callSuper = true)
+public class ShellTemplateUpdateDTO extends TemplateUpdateDTO {
+
+ @Nullable
+ @JsonProperty("newScripts")
+ private List<String> newScripts;
+
+ /**
+ * The default constructor for Jackson. This constructor is required for
deserialization of the
+ * DTO.
+ */
+ private ShellTemplateUpdateDTO() {
+ // Default constructor for Jackson
+ super();
+ }
+
+ @Override
+ public JobTemplateChange.TemplateUpdate toTemplateUpdate() {
+ return JobTemplateChange.ShellTemplateUpdate.builder()
+ .withNewExecutable(getNewExecutable())
+ .withNewArguments(getNewArguments())
+ .withNewEnvironments(getNewEnvironments())
+ .withNewCustomFields(getNewCustomFields())
+ .withNewScripts(newScripts)
+ .build();
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/job/SparkTemplateUpdateDTO.java
b/common/src/main/java/org/apache/gravitino/dto/job/SparkTemplateUpdateDTO.java
new file mode 100644
index 0000000000..5ef5027331
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/job/SparkTemplateUpdateDTO.java
@@ -0,0 +1,81 @@
+/*
+ * 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.gravitino.dto.job;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.experimental.SuperBuilder;
+import org.apache.gravitino.job.JobTemplateChange;
+
+/** DTO for updating a Spark Job Template. */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+@SuperBuilder(setterPrefix = "with")
+@ToString(callSuper = true)
+public class SparkTemplateUpdateDTO extends TemplateUpdateDTO {
+
+ @Nullable
+ @JsonProperty("newClassName")
+ private String newClassName;
+
+ @Nullable
+ @JsonProperty("newJars")
+ private List<String> newJars;
+
+ @Nullable
+ @JsonProperty("newFiles")
+ private List<String> newFiles;
+
+ @Nullable
+ @JsonProperty("newArchives")
+ private List<String> newArchives;
+
+ @Nullable
+ @JsonProperty("newConfigs")
+ private Map<String, String> newConfigs;
+
+ /**
+ * The default constructor for Jackson. This constructor is required for
deserialization of the
+ * DTO.
+ */
+ private SparkTemplateUpdateDTO() {
+ // Default constructor for Jackson
+ super();
+ }
+
+ @Override
+ public JobTemplateChange.TemplateUpdate toTemplateUpdate() {
+ return JobTemplateChange.SparkTemplateUpdate.builder()
+ .withNewExecutable(getNewExecutable())
+ .withNewArguments(getNewArguments())
+ .withNewEnvironments(getNewEnvironments())
+ .withNewCustomFields(getNewCustomFields())
+ .withNewClassName(newClassName)
+ .withNewJars(newJars)
+ .withNewFiles(newFiles)
+ .withNewArchives(newArchives)
+ .withNewConfigs(newConfigs)
+ .build();
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/job/TemplateUpdateDTO.java
b/common/src/main/java/org/apache/gravitino/dto/job/TemplateUpdateDTO.java
new file mode 100644
index 0000000000..8cda83393b
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/job/TemplateUpdateDTO.java
@@ -0,0 +1,77 @@
+/*
+ * 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.gravitino.dto.job;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.experimental.SuperBuilder;
+import org.apache.gravitino.job.JobTemplateChange;
+
+/** DTO for updating a Job Template. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = ShellTemplateUpdateDTO.class, name = "shell"),
+ @JsonSubTypes.Type(value = SparkTemplateUpdateDTO.class, name = "spark")
+})
+@Getter
+@EqualsAndHashCode
+@SuperBuilder(setterPrefix = "with")
+@ToString
+public abstract class TemplateUpdateDTO {
+
+ @Nullable
+ @JsonProperty("newExecutable")
+ private String newExecutable;
+
+ @Nullable
+ @JsonProperty("newArguments")
+ private List<String> newArguments;
+
+ @Nullable
+ @JsonProperty("newEnvironments")
+ private Map<String, String> newEnvironments;
+
+ @Nullable
+ @JsonProperty("newCustomFields")
+ private Map<String, String> newCustomFields;
+
+ /**
+ * The default constructor for Jackson. This constructor is required for
deserialization of the
+ * DTO.
+ */
+ protected TemplateUpdateDTO() {
+ // Default constructor for Jackson
+ }
+
+ /**
+ * Converts this DTO to a TemplateUpdate object.
+ *
+ * @return the corresponding TemplateUpdate object
+ */
+ public abstract JobTemplateChange.TemplateUpdate toTemplateUpdate();
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/JobTemplateUpdateRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/JobTemplateUpdateRequest.java
new file mode 100644
index 0000000000..5b513c6260
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/JobTemplateUpdateRequest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.gravitino.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.job.TemplateUpdateDTO;
+import org.apache.gravitino.job.JobTemplateChange;
+import org.apache.gravitino.rest.RESTRequest;
+
+/**
+ * Represents a request to update a job template. This can include renaming
the template, updating
+ * its comment, or changing its content.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
+@JsonSubTypes({
+ @JsonSubTypes.Type(
+ value = JobTemplateUpdateRequest.RenameJobTemplateRequest.class,
+ name = "rename"),
+ @JsonSubTypes.Type(
+ value = JobTemplateUpdateRequest.UpdateJobTemplateCommentRequest.class,
+ name = "updateComment"),
+ @JsonSubTypes.Type(
+ value = JobTemplateUpdateRequest.UpdateJobTemplateContentRequest.class,
+ name = "updateTemplate")
+})
+public interface JobTemplateUpdateRequest extends RESTRequest {
+
+ /**
+ * Get the job template change that is requested.
+ *
+ * @return the job template change
+ */
+ JobTemplateChange jobTemplateChange();
+
+ /** The request to rename a job template. */
+ @EqualsAndHashCode
+ @ToString
+ class RenameJobTemplateRequest implements JobTemplateUpdateRequest {
+
+ @Getter
+ @JsonProperty("newName")
+ private final String newName;
+
+ /**
+ * Constructor for RenameJobTemplateRequest.
+ *
+ * @param newName the new name for the job template
+ */
+ public RenameJobTemplateRequest(String newName) {
+ this.newName = newName;
+ }
+
+ /** Default constructor for Jackson. */
+ private RenameJobTemplateRequest() {
+ this(null);
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(newName), "\"newName\" is required and cannot
be empty");
+ }
+
+ @Override
+ public JobTemplateChange jobTemplateChange() {
+ return JobTemplateChange.rename(newName);
+ }
+ }
+
+ /** The request to update the comment of a job template. */
+ @EqualsAndHashCode
+ @ToString
+ class UpdateJobTemplateCommentRequest implements JobTemplateUpdateRequest {
+
+ @Getter
+ @JsonProperty("newComment")
+ private final String newComment;
+
+ /**
+ * Constructor for UpdateJobTemplateCommentRequest.
+ *
+ * @param newComment the new comment for the job template
+ */
+ public UpdateJobTemplateCommentRequest(String newComment) {
+ this.newComment = newComment;
+ }
+
+ /** Default constructor for Jackson. */
+ private UpdateJobTemplateCommentRequest() {
+ this(null);
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ // No specific validation needed for comment, it can be null or empty
+ }
+
+ @Override
+ public JobTemplateChange jobTemplateChange() {
+ return JobTemplateChange.updateComment(newComment);
+ }
+ }
+
+ /** The request to update the content of a job template. */
+ @EqualsAndHashCode
+ @ToString
+ class UpdateJobTemplateContentRequest implements JobTemplateUpdateRequest {
+
+ @Getter
+ @JsonProperty("newTemplate")
+ private final TemplateUpdateDTO newTemplate;
+
+ /**
+ * Constructor for UpdateJobTemplateContentRequest.
+ *
+ * @param newTemplate the new template content for the job template
+ */
+ public UpdateJobTemplateContentRequest(TemplateUpdateDTO newTemplate) {
+ this.newTemplate = newTemplate;
+ }
+
+ /** Default constructor for Jackson. */
+ private UpdateJobTemplateContentRequest() {
+ this(null);
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ Preconditions.checkArgument(
+ newTemplate != null, "\"newTemplate\" is required and cannot be
null");
+ }
+
+ @Override
+ public JobTemplateChange jobTemplateChange() {
+ return JobTemplateChange.updateTemplate(newTemplate.toTemplateUpdate());
+ }
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/JobTemplateUpdatesRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/JobTemplateUpdatesRequest.java
new file mode 100644
index 0000000000..fc3b12c5d6
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/JobTemplateUpdatesRequest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.gravitino.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to update a job template with series of updates. */
+@Getter
+@EqualsAndHashCode
+@ToString
+public class JobTemplateUpdatesRequest implements RESTRequest {
+
+ @JsonProperty("updates")
+ private final List<JobTemplateUpdateRequest> updates;
+
+ /**
+ * Creates a new JobTemplateUpdatesRequest.
+ *
+ * @param updates The updates to apply to the job template.
+ */
+ public JobTemplateUpdatesRequest(List<JobTemplateUpdateRequest> updates) {
+ this.updates = updates;
+ }
+
+ /** This is the constructor that is used by Jackson deserializer */
+ private JobTemplateUpdatesRequest() {
+ this(null);
+ }
+
+ /**
+ * Validates the request.
+ *
+ * @throws IllegalArgumentException If the request is invalid, this
exception is thrown.
+ */
+ @Override
+ public void validate() throws IllegalArgumentException {
+ updates.forEach(RESTRequest::validate);
+ }
+}
diff --git
a/common/src/test/java/org/apache/gravitino/dto/job/TestTemplateUpdateDTO.java
b/common/src/test/java/org/apache/gravitino/dto/job/TestTemplateUpdateDTO.java
new file mode 100644
index 0000000000..16235f0b45
--- /dev/null
+++
b/common/src/test/java/org/apache/gravitino/dto/job/TestTemplateUpdateDTO.java
@@ -0,0 +1,320 @@
+/*
+ * 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.gravitino.dto.job;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestTemplateUpdateDTO {
+
+ @Test
+ public void testShellTemplateUpdateDTO() throws JsonProcessingException {
+ ShellTemplateUpdateDTO shellTemplateUpdateDTO =
+ ShellTemplateUpdateDTO.builder()
+ .withNewExecutable("/bin/bash")
+ .withNewArguments(ImmutableList.of("-c", "echo Hello World"))
+ .withNewEnvironments(ImmutableMap.of("ENV_VAR", "value"))
+ .withNewCustomFields(ImmutableMap.of("customKey", "customValue"))
+ .withNewScripts(ImmutableList.of("/path/to/script1.sh",
"/path/to/script2.sh"))
+ .build();
+
+ String serJson =
JsonUtils.objectMapper().writeValueAsString(shellTemplateUpdateDTO);
+ ShellTemplateUpdateDTO deserDTO =
+ JsonUtils.objectMapper().readValue(serJson,
ShellTemplateUpdateDTO.class);
+ Assertions.assertEquals(shellTemplateUpdateDTO, deserDTO);
+
+ shellTemplateUpdateDTO =
+ ShellTemplateUpdateDTO.builder()
+ .withNewEnvironments(ImmutableMap.of("ENV_VAR", "value"))
+ .withNewCustomFields(ImmutableMap.of("customKey", "customValue"))
+ .withNewScripts(ImmutableList.of("/path/to/script1.sh",
"/path/to/script2.sh"))
+ .build();
+
+ serJson =
JsonUtils.objectMapper().writeValueAsString(shellTemplateUpdateDTO);
+ deserDTO = JsonUtils.objectMapper().readValue(serJson,
ShellTemplateUpdateDTO.class);
+ Assertions.assertNull(deserDTO.getNewExecutable());
+ Assertions.assertNull(deserDTO.getNewArguments());
+ Assertions.assertEquals(shellTemplateUpdateDTO, deserDTO);
+
+ shellTemplateUpdateDTO =
+ ShellTemplateUpdateDTO.builder()
+ .withNewScripts(ImmutableList.of("/path/to/script1.sh",
"/path/to/script2.sh"))
+ .build();
+
+ serJson =
JsonUtils.objectMapper().writeValueAsString(shellTemplateUpdateDTO);
+ deserDTO = JsonUtils.objectMapper().readValue(serJson,
ShellTemplateUpdateDTO.class);
+ Assertions.assertNull(deserDTO.getNewEnvironments());
+ Assertions.assertNull(deserDTO.getNewCustomFields());
+ Assertions.assertEquals(shellTemplateUpdateDTO, deserDTO);
+
+ shellTemplateUpdateDTO = ShellTemplateUpdateDTO.builder().build();
+ serJson =
JsonUtils.objectMapper().writeValueAsString(shellTemplateUpdateDTO);
+ deserDTO = JsonUtils.objectMapper().readValue(serJson,
ShellTemplateUpdateDTO.class);
+ Assertions.assertNull(deserDTO.getNewScripts());
+ Assertions.assertEquals(shellTemplateUpdateDTO, deserDTO);
+ }
+
+ @Test
+ public void testSparkTemplateUpdateDTO() throws JsonProcessingException {
+ SparkTemplateUpdateDTO sparkTemplateUpdateDTO =
+ SparkTemplateUpdateDTO.builder()
+ .withNewExecutable("spark-submit")
+ .withNewArguments(ImmutableList.of("--class", "org.example.Main"))
+ .withNewEnvironments(ImmutableMap.of("SPARK_ENV", "prod"))
+ .withNewCustomFields(ImmutableMap.of("customKey", "customValue"))
+ .withNewClassName("org.example.Main")
+ .withNewJars(ImmutableList.of("/path/to/jar1.jar",
"/path/to/jar2.jar"))
+ .withNewFiles(ImmutableList.of("/path/to/file1.txt",
"/path/to/file2.txt"))
+ .withNewArchives(ImmutableList.of("/path/to/archive1.zip",
"/path/to/archive2.zip"))
+ .withNewConfigs(ImmutableMap.of("spark.executor.memory", "4g"))
+ .build();
+
+ String serJson =
JsonUtils.objectMapper().writeValueAsString(sparkTemplateUpdateDTO);
+ SparkTemplateUpdateDTO deserDTO =
+ JsonUtils.objectMapper().readValue(serJson,
SparkTemplateUpdateDTO.class);
+ Assertions.assertEquals(sparkTemplateUpdateDTO, deserDTO);
+
+ sparkTemplateUpdateDTO =
+ SparkTemplateUpdateDTO.builder()
+ .withNewEnvironments(ImmutableMap.of("SPARK_ENV", "prod"))
+ .withNewCustomFields(ImmutableMap.of("customKey", "customValue"))
+ .withNewClassName("org.example.Main")
+ .withNewJars(ImmutableList.of("/path/to/jar1.jar",
"/path/to/jar2.jar"))
+ .withNewFiles(ImmutableList.of("/path/to/file1.txt",
"/path/to/file2.txt"))
+ .withNewArchives(ImmutableList.of("/path/to/archive1.zip",
"/path/to/archive2.zip"))
+ .withNewConfigs(ImmutableMap.of("spark.executor.memory", "4g"))
+ .build();
+
+ serJson =
JsonUtils.objectMapper().writeValueAsString(sparkTemplateUpdateDTO);
+ deserDTO = JsonUtils.objectMapper().readValue(serJson,
SparkTemplateUpdateDTO.class);
+ Assertions.assertNull(deserDTO.getNewExecutable());
+ Assertions.assertNull(deserDTO.getNewArguments());
+ Assertions.assertEquals(sparkTemplateUpdateDTO, deserDTO);
+
+ sparkTemplateUpdateDTO =
+ SparkTemplateUpdateDTO.builder()
+ .withNewClassName("org.example.Main")
+ .withNewJars(ImmutableList.of("/path/to/jar1.jar",
"/path/to/jar2.jar"))
+ .withNewFiles(ImmutableList.of("/path/to/file1.txt",
"/path/to/file2.txt"))
+ .withNewArchives(ImmutableList.of("/path/to/archive1.zip",
"/path/to/archive2.zip"))
+ .withNewConfigs(ImmutableMap.of("spark.executor.memory", "4g"))
+ .build();
+
+ serJson =
JsonUtils.objectMapper().writeValueAsString(sparkTemplateUpdateDTO);
+ deserDTO = JsonUtils.objectMapper().readValue(serJson,
SparkTemplateUpdateDTO.class);
+ Assertions.assertNull(deserDTO.getNewEnvironments());
+ Assertions.assertNull(deserDTO.getNewCustomFields());
+ Assertions.assertEquals(sparkTemplateUpdateDTO, deserDTO);
+
+ sparkTemplateUpdateDTO =
+ SparkTemplateUpdateDTO.builder()
+ .withNewConfigs(ImmutableMap.of("spark.executor.memory", "4g"))
+ .build();
+
+ serJson =
JsonUtils.objectMapper().writeValueAsString(sparkTemplateUpdateDTO);
+ deserDTO = JsonUtils.objectMapper().readValue(serJson,
SparkTemplateUpdateDTO.class);
+ Assertions.assertNull(deserDTO.getNewClassName());
+ Assertions.assertNull(deserDTO.getNewJars());
+ Assertions.assertNull(deserDTO.getNewFiles());
+ Assertions.assertNull(deserDTO.getNewArchives());
+ Assertions.assertEquals(sparkTemplateUpdateDTO, deserDTO);
+
+ sparkTemplateUpdateDTO = SparkTemplateUpdateDTO.builder().build();
+ serJson =
JsonUtils.objectMapper().writeValueAsString(sparkTemplateUpdateDTO);
+ deserDTO = JsonUtils.objectMapper().readValue(serJson,
SparkTemplateUpdateDTO.class);
+ Assertions.assertNull(deserDTO.getNewConfigs());
+ Assertions.assertEquals(sparkTemplateUpdateDTO, deserDTO);
+ }
+
+ @Test
+ public void testDeserializeShellTemplateUpdate() throws
JsonProcessingException {
+ String json =
+ "{"
+ + "\"@type\":\"shell\","
+ + "\"newExecutable\":\"/bin/bash\","
+ + "\"newArguments\":[\"-c\",\"echo Hello World\"],"
+ + "\"newEnvironments\":{\"ENV_VAR\":\"value\"},"
+ + "\"newCustomFields\":{\"customKey\":\"customValue\"},"
+ +
"\"newScripts\":[\"/path/to/script1.sh\",\"/path/to/script2.sh\"]"
+ + "}";
+
+ TemplateUpdateDTO dto = JsonUtils.objectMapper().readValue(json,
TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(ShellTemplateUpdateDTO.class, dto);
+ ShellTemplateUpdateDTO shellDto = (ShellTemplateUpdateDTO) dto;
+ Assertions.assertEquals("/bin/bash", shellDto.getNewExecutable());
+ Assertions.assertEquals(ImmutableList.of("-c", "echo Hello World"),
shellDto.getNewArguments());
+ Assertions.assertEquals(ImmutableMap.of("ENV_VAR", "value"),
shellDto.getNewEnvironments());
+ Assertions.assertEquals(
+ ImmutableMap.of("customKey", "customValue"),
shellDto.getNewCustomFields());
+ Assertions.assertEquals(
+ ImmutableList.of("/path/to/script1.sh", "/path/to/script2.sh"),
shellDto.getNewScripts());
+
+ json =
+ "{"
+ + "\"@type\":\"shell\","
+ + "\"newEnvironments\":{\"ENV_VAR\":\"value\"},"
+ + "\"newCustomFields\":{\"customKey\":\"customValue\"},"
+ +
"\"newScripts\":[\"/path/to/script1.sh\",\"/path/to/script2.sh\"]"
+ + "}";
+
+ dto = JsonUtils.objectMapper().readValue(json, TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(ShellTemplateUpdateDTO.class, dto);
+ shellDto = (ShellTemplateUpdateDTO) dto;
+ Assertions.assertNull(shellDto.getNewExecutable());
+ Assertions.assertNull(shellDto.getNewArguments());
+
+ json =
+ "{"
+ + "\"@type\":\"shell\","
+ +
"\"newScripts\":[\"/path/to/script1.sh\",\"/path/to/script2.sh\"]"
+ + "}";
+
+ dto = JsonUtils.objectMapper().readValue(json, TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(ShellTemplateUpdateDTO.class, dto);
+ shellDto = (ShellTemplateUpdateDTO) dto;
+ Assertions.assertNull(shellDto.getNewEnvironments());
+ Assertions.assertNull(shellDto.getNewCustomFields());
+ Assertions.assertEquals(
+ ImmutableList.of("/path/to/script1.sh", "/path/to/script2.sh"),
shellDto.getNewScripts());
+
+ json = "{" + "\"@type\":\"shell\"" + "}";
+
+ dto = JsonUtils.objectMapper().readValue(json, TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(ShellTemplateUpdateDTO.class, dto);
+ shellDto = (ShellTemplateUpdateDTO) dto;
+ Assertions.assertNull(shellDto.getNewScripts());
+ }
+
+ @Test
+ public void testDeserializeSparkTemplateUpdate() throws
JsonProcessingException {
+ String json =
+ "{"
+ + "\"@type\":\"spark\","
+ + "\"newExecutable\":\"spark-submit\","
+ + "\"newArguments\":[\"--class\",\"org.example.Main\"],"
+ + "\"newEnvironments\":{\"SPARK_ENV\":\"prod\"},"
+ + "\"newCustomFields\":{\"customKey\":\"customValue\"},"
+ + "\"newClassName\":\"org.example.Main\","
+ + "\"newJars\":[\"/path/to/jar1.jar\",\"/path/to/jar2.jar\"],"
+ + "\"newFiles\":[\"/path/to/file1.txt\",\"/path/to/file2.txt\"],"
+ +
"\"newArchives\":[\"/path/to/archive1.zip\",\"/path/to/archive2.zip\"],"
+ + "\"newConfigs\":{\"spark.executor.memory\":\"4g\"}"
+ + "}";
+
+ TemplateUpdateDTO dto = JsonUtils.objectMapper().readValue(json,
TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(SparkTemplateUpdateDTO.class, dto);
+ SparkTemplateUpdateDTO sparkDto = (SparkTemplateUpdateDTO) dto;
+ Assertions.assertEquals("spark-submit", sparkDto.getNewExecutable());
+ Assertions.assertEquals(
+ ImmutableList.of("--class", "org.example.Main"),
sparkDto.getNewArguments());
+ Assertions.assertEquals(ImmutableMap.of("SPARK_ENV", "prod"),
sparkDto.getNewEnvironments());
+ Assertions.assertEquals(
+ ImmutableMap.of("customKey", "customValue"),
sparkDto.getNewCustomFields());
+ Assertions.assertEquals("org.example.Main", sparkDto.getNewClassName());
+ Assertions.assertEquals(
+ ImmutableList.of("/path/to/jar1.jar", "/path/to/jar2.jar"),
sparkDto.getNewJars());
+ Assertions.assertEquals(
+ ImmutableList.of("/path/to/file1.txt", "/path/to/file2.txt"),
sparkDto.getNewFiles());
+ Assertions.assertEquals(
+ ImmutableList.of("/path/to/archive1.zip", "/path/to/archive2.zip"),
+ sparkDto.getNewArchives());
+ Assertions.assertEquals(
+ ImmutableMap.of("spark.executor.memory", "4g"),
sparkDto.getNewConfigs());
+
+ json =
+ "{"
+ + "\"@type\":\"spark\","
+ + "\"newEnvironments\":{\"SPARK_ENV\":\"prod\"},"
+ + "\"newCustomFields\":{\"customKey\":\"customValue\"},"
+ + "\"newClassName\":\"org.example.Main\","
+ + "\"newJars\":[\"/path/to/jar1.jar\",\"/path/to/jar2.jar\"],"
+ + "\"newFiles\":[\"/path/to/file1.txt\",\"/path/to/file2.txt\"],"
+ +
"\"newArchives\":[\"/path/to/archive1.zip\",\"/path/to/archive2.zip\"],"
+ + "\"newConfigs\":{\"spark.executor.memory\":\"4g\"}"
+ + "}";
+
+ dto = JsonUtils.objectMapper().readValue(json, TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(SparkTemplateUpdateDTO.class, dto);
+ sparkDto = (SparkTemplateUpdateDTO) dto;
+ Assertions.assertNull(sparkDto.getNewExecutable());
+ Assertions.assertNull(sparkDto.getNewArguments());
+ Assertions.assertEquals(ImmutableMap.of("SPARK_ENV", "prod"),
sparkDto.getNewEnvironments());
+ Assertions.assertEquals(
+ ImmutableMap.of("customKey", "customValue"),
sparkDto.getNewCustomFields());
+ Assertions.assertEquals("org.example.Main", sparkDto.getNewClassName());
+ Assertions.assertEquals(
+ ImmutableList.of("/path/to/jar1.jar", "/path/to/jar2.jar"),
sparkDto.getNewJars());
+ Assertions.assertEquals(
+ ImmutableList.of("/path/to/file1.txt", "/path/to/file2.txt"),
sparkDto.getNewFiles());
+ Assertions.assertEquals(
+ ImmutableList.of("/path/to/archive1.zip", "/path/to/archive2.zip"),
+ sparkDto.getNewArchives());
+ Assertions.assertEquals(
+ ImmutableMap.of("spark.executor.memory", "4g"),
sparkDto.getNewConfigs());
+
+ json =
+ "{"
+ + "\"@type\":\"spark\","
+ + "\"newClassName\":\"org.example.Main\","
+ + "\"newJars\":[\"/path/to/jar1.jar\",\"/path/to/jar2.jar\"],"
+ + "\"newFiles\":[\"/path/to/file1.txt\",\"/path/to/file2.txt\"],"
+ +
"\"newArchives\":[\"/path/to/archive1.zip\",\"/path/to/archive2.zip\"],"
+ + "\"newConfigs\":{\"spark.executor.memory\":\"4g\"}"
+ + "}";
+
+ dto = JsonUtils.objectMapper().readValue(json, TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(SparkTemplateUpdateDTO.class, dto);
+ sparkDto = (SparkTemplateUpdateDTO) dto;
+ Assertions.assertNull(sparkDto.getNewExecutable());
+ Assertions.assertNull(sparkDto.getNewArguments());
+ Assertions.assertNull(sparkDto.getNewEnvironments());
+ Assertions.assertNull(sparkDto.getNewCustomFields());
+
+ json = "{" + "\"@type\":\"spark\"," +
"\"newConfigs\":{\"spark.executor.memory\":\"4g\"}" + "}";
+
+ dto = JsonUtils.objectMapper().readValue(json, TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(SparkTemplateUpdateDTO.class, dto);
+ sparkDto = (SparkTemplateUpdateDTO) dto;
+ Assertions.assertNull(sparkDto.getNewClassName());
+ Assertions.assertNull(sparkDto.getNewJars());
+ Assertions.assertNull(sparkDto.getNewFiles());
+ Assertions.assertNull(sparkDto.getNewArchives());
+ Assertions.assertEquals(
+ ImmutableMap.of("spark.executor.memory", "4g"),
sparkDto.getNewConfigs());
+
+ json = "{" + "\"@type\":\"spark\"" + "}";
+
+ dto = JsonUtils.objectMapper().readValue(json, TemplateUpdateDTO.class);
+ Assertions.assertInstanceOf(SparkTemplateUpdateDTO.class, dto);
+ sparkDto = (SparkTemplateUpdateDTO) dto;
+ Assertions.assertNull(sparkDto.getNewConfigs());
+ Assertions.assertNull(sparkDto.getNewClassName());
+ Assertions.assertNull(sparkDto.getNewJars());
+ Assertions.assertNull(sparkDto.getNewFiles());
+ Assertions.assertNull(sparkDto.getNewArchives());
+ Assertions.assertNull(sparkDto.getNewExecutable());
+ Assertions.assertNull(sparkDto.getNewArguments());
+ Assertions.assertNull(sparkDto.getNewEnvironments());
+ Assertions.assertNull(sparkDto.getNewCustomFields());
+ }
+}
diff --git
a/common/src/test/java/org/apache/gravitino/dto/requests/TestJobTemplateUpdatesRequest.java
b/common/src/test/java/org/apache/gravitino/dto/requests/TestJobTemplateUpdatesRequest.java
new file mode 100644
index 0000000000..7a7a3b82df
--- /dev/null
+++
b/common/src/test/java/org/apache/gravitino/dto/requests/TestJobTemplateUpdatesRequest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.gravitino.dto.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableList;
+import org.apache.gravitino.dto.job.ShellTemplateUpdateDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestJobTemplateUpdatesRequest {
+
+ @Test
+ public void testSerDeRequest() throws JsonProcessingException {
+ JobTemplateUpdateRequest renameReq =
+ new
JobTemplateUpdateRequest.RenameJobTemplateRequest("new_template_name");
+ JobTemplateUpdateRequest updateCommentReq =
+ new JobTemplateUpdateRequest.UpdateJobTemplateCommentRequest("Updated
comment");
+ JobTemplateUpdateRequest updateContentReq =
+ new JobTemplateUpdateRequest.UpdateJobTemplateContentRequest(
+
ShellTemplateUpdateDTO.builder().withNewExecutable("/bin/bash").build());
+
+ JobTemplateUpdatesRequest request =
+ new JobTemplateUpdatesRequest(
+ ImmutableList.of(renameReq, updateCommentReq, updateContentReq));
+
+ String json = JsonUtils.objectMapper().writeValueAsString(request);
+ JobTemplateUpdatesRequest deserializedRequest =
+ JsonUtils.objectMapper().readValue(json,
JobTemplateUpdatesRequest.class);
+
+ Assertions.assertEquals(request, deserializedRequest);
+
Assertions.assertTrue(deserializedRequest.getUpdates().contains(renameReq));
+
Assertions.assertTrue(deserializedRequest.getUpdates().contains(updateCommentReq));
+
Assertions.assertTrue(deserializedRequest.getUpdates().contains(updateContentReq));
+ }
+}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/JobOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/JobOperations.java
index 54ea3c1365..ca5bc551e6 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/JobOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/JobOperations.java
@@ -33,6 +33,7 @@ import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -46,6 +47,8 @@ import org.apache.gravitino.dto.job.ShellJobTemplateDTO;
import org.apache.gravitino.dto.job.SparkJobTemplateDTO;
import org.apache.gravitino.dto.requests.JobRunRequest;
import org.apache.gravitino.dto.requests.JobTemplateRegisterRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdateRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdatesRequest;
import org.apache.gravitino.dto.responses.BaseResponse;
import org.apache.gravitino.dto.responses.DropResponse;
import org.apache.gravitino.dto.responses.JobListResponse;
@@ -55,6 +58,7 @@ import org.apache.gravitino.dto.responses.JobTemplateResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
import org.apache.gravitino.dto.util.DTOConverters;
import org.apache.gravitino.job.JobOperationDispatcher;
+import org.apache.gravitino.job.JobTemplateChange;
import org.apache.gravitino.meta.AuditInfo;
import org.apache.gravitino.meta.JobEntity;
import org.apache.gravitino.meta.JobTemplateEntity;
@@ -202,6 +206,40 @@ public class JobOperations {
}
}
+ @PUT
+ @Path("templates/{name}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "alter-job-template." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "alter-job-template", absolute = true)
+ public Response alterJobTemplate(
+ @PathParam("metalake") String metalake,
+ @PathParam("name") String jobTemplateName,
+ JobTemplateUpdatesRequest request) {
+ LOG.info(
+ "Received request to alter job template: {} in metalake: {}",
jobTemplateName, metalake);
+
+ try {
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ request.validate();
+ JobTemplateChange[] changes =
+ request.getUpdates().stream()
+ .map(JobTemplateUpdateRequest::jobTemplateChange)
+ .toArray(JobTemplateChange[]::new);
+
+ JobTemplateEntity updatedEntity =
+ jobOperationDispatcher.alterJobTemplate(metalake,
jobTemplateName, changes);
+ Response response = Utils.ok(new
JobTemplateResponse(toDTO(updatedEntity)));
+ LOG.info("Job template {} in metalake {} is altered",
jobTemplateName, metalake);
+ return response;
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handleJobTemplateException(
+ OperationType.ALTER, jobTemplateName, metalake, e);
+ }
+ }
+
@GET
@Path("runs")
@Produces("application/vnd.gravitino.v1+json")
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestJobOperations.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestJobOperations.java
index 56f4be73a3..452608c151 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestJobOperations.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestJobOperations.java
@@ -39,8 +39,11 @@ import javax.ws.rs.core.Response;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.dto.job.JobTemplateDTO;
+import org.apache.gravitino.dto.job.ShellTemplateUpdateDTO;
import org.apache.gravitino.dto.requests.JobRunRequest;
import org.apache.gravitino.dto.requests.JobTemplateRegisterRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdateRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdatesRequest;
import org.apache.gravitino.dto.responses.BaseResponse;
import org.apache.gravitino.dto.responses.DropResponse;
import org.apache.gravitino.dto.responses.ErrorConstants;
@@ -58,6 +61,7 @@ import
org.apache.gravitino.exceptions.NoSuchJobTemplateException;
import org.apache.gravitino.exceptions.NoSuchMetalakeException;
import org.apache.gravitino.job.JobHandle;
import org.apache.gravitino.job.JobOperationDispatcher;
+import org.apache.gravitino.job.JobTemplateChange;
import org.apache.gravitino.job.ShellJobTemplate;
import org.apache.gravitino.job.SparkJobTemplate;
import org.apache.gravitino.meta.AuditInfo;
@@ -498,6 +502,131 @@ public class TestJobOperations extends JerseyTest {
Assertions.assertEquals(InUseException.class.getSimpleName(),
errorResp4.getType());
}
+ @Test
+ public void testAlterJobTemplate() {
+ String templateName = "shell_template_1";
+ JobTemplateEntity template = newShellJobTemplateEntity(templateName,
"Updated comment");
+ JobTemplateUpdateRequest renameReq =
+ new JobTemplateUpdateRequest.RenameJobTemplateRequest(templateName);
+ JobTemplateUpdateRequest updateCommentReq =
+ new JobTemplateUpdateRequest.UpdateJobTemplateCommentRequest("Updated
comment");
+ JobTemplateUpdateRequest updateContentReq =
+ new JobTemplateUpdateRequest.UpdateJobTemplateContentRequest(
+ ShellTemplateUpdateDTO.builder().build());
+ JobTemplateUpdatesRequest req =
+ new JobTemplateUpdatesRequest(
+ Lists.newArrayList(renameReq, updateCommentReq, updateContentReq));
+ JobTemplateChange[] changes =
+ req.getUpdates().stream()
+ .map(JobTemplateUpdateRequest::jobTemplateChange)
+ .toArray(JobTemplateChange[]::new);
+
+ when(jobOperationDispatcher.alterJobTemplate(metalake, templateName,
changes))
+ .thenReturn(template);
+
+ Response resp =
+ target(jobTemplatePath())
+ .path(templateName)
+ .request(APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(req, APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(APPLICATION_JSON_TYPE, resp.getMediaType());
+
+ JobTemplateResponse jobTemplateResp =
resp.readEntity(JobTemplateResponse.class);
+ Assertions.assertEquals(0, jobTemplateResp.getCode());
+ Assertions.assertEquals(JobOperations.toDTO(template),
jobTemplateResp.getJobTemplate());
+
+ // Test throw NoSuchMetalakeException
+ doThrow(new NoSuchMetalakeException("mock error"))
+ .when(jobOperationDispatcher)
+ .alterJobTemplate(any(), any(), any());
+
+ Response resp2 =
+ target(jobTemplatePath())
+ .path(templateName)
+ .request(APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(req, APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp2.getStatus());
+
+ ErrorResponse errorResp = resp2.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResp.getCode());
+ Assertions.assertEquals(NoSuchMetalakeException.class.getSimpleName(),
errorResp.getType());
+
+ // Test throw MetalakeNotInUseException
+ doThrow(new MetalakeNotInUseException("mock error"))
+ .when(jobOperationDispatcher)
+ .alterJobTemplate(any(), any(), any());
+
+ Response resp3 =
+ target(jobTemplatePath())
+ .path(templateName)
+ .request(APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(req, APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
resp3.getStatus());
+
+ ErrorResponse errorResp2 = resp3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_IN_USE_CODE,
errorResp2.getCode());
+ Assertions.assertEquals(MetalakeNotInUseException.class.getSimpleName(),
errorResp2.getType());
+
+ // Test throw IllegalArgumentException
+ doThrow(new IllegalArgumentException("mock error"))
+ .when(jobOperationDispatcher)
+ .alterJobTemplate(any(), any(), any());
+
+ Response resp4 =
+ target(jobTemplatePath())
+ .path(templateName)
+ .request(APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(req, APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(),
resp4.getStatus());
+ ErrorResponse errorResp3 = resp4.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.ILLEGAL_ARGUMENTS_CODE,
errorResp3.getCode());
+ Assertions.assertEquals(IllegalArgumentException.class.getSimpleName(),
errorResp3.getType());
+
+ // Test throw NoSuchJobTemplateException
+ doThrow(new NoSuchJobTemplateException("mock error"))
+ .when(jobOperationDispatcher)
+ .alterJobTemplate(any(), any(), any());
+
+ Response resp5 =
+ target(jobTemplatePath())
+ .path(templateName)
+ .request(APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(req, APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp5.getStatus());
+ ErrorResponse errorResp4 = resp5.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResp4.getCode());
+ Assertions.assertEquals(NoSuchJobTemplateException.class.getSimpleName(),
errorResp4.getType());
+
+ // Test throw RuntimeException
+ doThrow(new RuntimeException("mock error"))
+ .when(jobOperationDispatcher)
+ .alterJobTemplate(any(), any(), any());
+
+ Response resp6 =
+ target(jobTemplatePath())
+ .path(templateName)
+ .request(APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(req, APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp6.getStatus());
+ ErrorResponse errorResp5 = resp6.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResp5.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp5.getType());
+ }
+
@Test
public void testListJobs() {
String templateName = "shell_template_1";