This is an automated email from the ASF dual-hosted git repository.
yuqi4733 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 a59e1cffba [#8639]feat(core): Add the job template alteration in the
core part (#8775)
a59e1cffba is described below
commit a59e1cffba34841eb9077a35a2a42b46d11f2289
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 10 09:58:04 2025 +0800
[#8639]feat(core): Add the job template alteration in the core part (#8775)
### What changes were proposed in this pull request?
Add the core part for job template alteration.
### Why are the changes needed?
This is a part of work for job template alteration.
Fix: #8639
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
New UTs to test code.
---------
Co-authored-by: Jerry Shao <[email protected]>
Co-authored-by: Jerry Shao <[email protected]>
---
.../apache/gravitino/job/JobTemplateChange.java | 39 ----
.../gravitino/api/job/job_template_change.py | 49 ++---
.../java/org/apache/gravitino/job/JobManager.java | 169 ++++++++++++++++
.../gravitino/job/JobOperationDispatcher.java | 14 ++
.../gravitino/storage/relational/JDBCBackend.java | 2 +
.../relational/mapper/JobTemplateMetaMapper.java | 5 +
.../mapper/JobTemplateMetaSQLProviderFactory.java | 6 +
.../base/JobTemplateMetaBaseSQLProvider.java | 21 ++
.../storage/relational/po/JobTemplatePO.java | 25 +++
.../relational/service/JobTemplateMetaService.java | 83 ++++++--
.../org/apache/gravitino/job/TestJobManager.java | 215 +++++++++++++++++++++
.../service/TestJobTemplateMetaService.java | 69 +++++++
12 files changed, 614 insertions(+), 83 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/job/JobTemplateChange.java
b/api/src/main/java/org/apache/gravitino/job/JobTemplateChange.java
index 88d9f58d0f..da9078302e 100644
--- a/api/src/main/java/org/apache/gravitino/job/JobTemplateChange.java
+++ b/api/src/main/java/org/apache/gravitino/job/JobTemplateChange.java
@@ -18,14 +18,9 @@
*/
package org.apache.gravitino.job;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import org.apache.commons.lang3.StringUtils;
/**
* The interface for job template changes. A job template change is an
operation that modifies a job
@@ -378,18 +373,6 @@ public interface JobTemplateChange {
this.newCustomFields = newCustomFields;
return self();
}
-
- /** Validates the builder fields before building the TemplateUpdate
instance. */
- protected void validate() {
- Preconditions.checkArgument(
- StringUtils.isNoneBlank(newExecutable), "Executable cannot be null
or blank");
- this.newArguments =
- newArguments == null ? Collections.emptyList() :
ImmutableList.copyOf(newArguments);
- this.newEnvironments =
- newEnvironments == null ? Collections.emptyMap() :
ImmutableMap.copyOf(newEnvironments);
- this.newCustomFields =
- newCustomFields == null ? Collections.emptyMap() :
ImmutableMap.copyOf(newCustomFields);
- }
}
}
@@ -470,7 +453,6 @@ public interface JobTemplateChange {
*/
@Override
public ShellTemplateUpdate build() {
- validate();
return new ShellTemplateUpdate(this);
}
@@ -483,14 +465,6 @@ public interface JobTemplateChange {
protected Builder self() {
return this;
}
-
- /** Validates the builder fields before building the ShellTemplateUpdate
instance. */
- @Override
- protected void validate() {
- super.validate();
- this.newScripts =
- newScripts == null ? Collections.emptyList() :
ImmutableList.copyOf(newScripts);
- }
}
}
@@ -671,7 +645,6 @@ public interface JobTemplateChange {
*/
@Override
public SparkTemplateUpdate build() {
- validate();
return new SparkTemplateUpdate(this);
}
@@ -684,18 +657,6 @@ public interface JobTemplateChange {
protected Builder self() {
return this;
}
-
- /** Validates the builder fields before building the SparkTemplateUpdate
instance. */
- @Override
- protected void validate() {
- super.validate();
- this.newJars = newJars == null ? Collections.emptyList() :
ImmutableList.copyOf(newJars);
- this.newFiles = newFiles == null ? Collections.emptyList() :
ImmutableList.copyOf(newFiles);
- this.newArchives =
- newArchives == null ? Collections.emptyList() :
ImmutableList.copyOf(newArchives);
- this.newConfigs =
- newConfigs == null ? Collections.emptyMap() :
ImmutableMap.copyOf(newConfigs);
- }
}
}
}
diff --git a/clients/client-python/gravitino/api/job/job_template_change.py
b/clients/client-python/gravitino/api/job/job_template_change.py
index 1c3c1c50db..cef6a6f2e7 100644
--- a/clients/client-python/gravitino/api/job/job_template_change.py
+++ b/clients/client-python/gravitino/api/job/job_template_change.py
@@ -16,7 +16,6 @@
# under the License.
from abc import ABC
from typing import List, Dict, Optional, Any
-from copy import deepcopy
class JobTemplateChange:
@@ -184,7 +183,7 @@ class TemplateUpdate(ABC):
def __init__(
self,
- new_executable: str,
+ new_executable: Optional[str] = None,
new_arguments: Optional[List[str]] = None,
new_environments: Optional[Dict[str, str]] = None,
new_custom_fields: Optional[Dict[str, str]] = None,
@@ -198,20 +197,12 @@ class TemplateUpdate(ABC):
new_environments: The new environments of the job template.
new_custom_fields: The new custom fields of the job template.
"""
- if not new_executable or not new_executable.strip():
- raise ValueError("Executable cannot be null or blank")
self._new_executable = new_executable
- self._new_arguments = (
- deepcopy(new_arguments) if new_arguments is not None else []
- )
- self._new_environments = (
- deepcopy(new_environments) if new_environments is not None else {}
- )
- self._new_custom_fields = (
- deepcopy(new_custom_fields) if new_custom_fields is not None else
{}
- )
+ self._new_arguments = new_arguments
+ self._new_environments = new_environments
+ self._new_custom_fields = new_custom_fields
- def get_new_executable(self) -> str:
+ def get_new_executable(self) -> Optional[str]:
"""
Get the new executable of the job template.
@@ -220,7 +211,7 @@ class TemplateUpdate(ABC):
"""
return self._new_executable
- def get_new_arguments(self) -> List[str]:
+ def get_new_arguments(self) -> Optional[List[str]]:
"""
Get the new arguments of the job template.
@@ -229,7 +220,7 @@ class TemplateUpdate(ABC):
"""
return self._new_arguments
- def get_new_environments(self) -> Dict[str, str]:
+ def get_new_environments(self) -> Optional[Dict[str, str]]:
"""
Get the new environments of the job template.
@@ -238,7 +229,7 @@ class TemplateUpdate(ABC):
"""
return self._new_environments
- def get_new_custom_fields(self) -> Dict[str, str]:
+ def get_new_custom_fields(self) -> Optional[Dict[str, str]]:
"""
Get the new custom fields of the job template.
@@ -275,7 +266,7 @@ class ShellTemplateUpdate(TemplateUpdate):
def __init__(
self,
- new_executable: str,
+ new_executable: Optional[str] = None,
new_arguments: Optional[List[str]] = None,
new_environments: Optional[Dict[str, str]] = None,
new_custom_fields: Optional[Dict[str, str]] = None,
@@ -294,9 +285,9 @@ class ShellTemplateUpdate(TemplateUpdate):
super().__init__(
new_executable, new_arguments, new_environments, new_custom_fields
)
- self._new_scripts = deepcopy(new_scripts) if new_scripts is not None
else []
+ self._new_scripts = new_scripts
- def get_new_scripts(self) -> List[str]:
+ def get_new_scripts(self) -> Optional[List[str]]:
"""
Get the new scripts of the shell job template.
@@ -323,7 +314,7 @@ class SparkTemplateUpdate(TemplateUpdate):
def __init__(
self,
- new_executable: str,
+ new_executable: Optional[str] = None,
new_arguments: Optional[List[str]] = None,
new_environments: Optional[Dict[str, str]] = None,
new_custom_fields: Optional[Dict[str, str]] = None,
@@ -351,10 +342,10 @@ class SparkTemplateUpdate(TemplateUpdate):
new_executable, new_arguments, new_environments, new_custom_fields
)
self._new_class_name = new_class_name
- self._new_jars = deepcopy(new_jars) if new_jars is not None else []
- self._new_files = deepcopy(new_files) if new_files is not None else []
- self._new_archives = deepcopy(new_archives) if new_archives is not
None else []
- self._new_configs = deepcopy(new_configs) if new_configs is not None
else {}
+ self._new_jars = new_jars
+ self._new_files = new_files
+ self._new_archives = new_archives
+ self._new_configs = new_configs
def get_new_class_name(self) -> Optional[str]:
"""
@@ -365,7 +356,7 @@ class SparkTemplateUpdate(TemplateUpdate):
"""
return self._new_class_name
- def get_new_jars(self) -> List[str]:
+ def get_new_jars(self) -> Optional[List[str]]:
"""
Get the new jars of the spark job template.
@@ -374,7 +365,7 @@ class SparkTemplateUpdate(TemplateUpdate):
"""
return self._new_jars
- def get_new_files(self) -> List[str]:
+ def get_new_files(self) -> Optional[List[str]]:
"""
Get the new files of the spark job template.
@@ -383,7 +374,7 @@ class SparkTemplateUpdate(TemplateUpdate):
"""
return self._new_files
- def get_new_archives(self) -> List[str]:
+ def get_new_archives(self) -> Optional[List[str]]:
"""
Get the new archives of the spark job template.
@@ -392,7 +383,7 @@ class SparkTemplateUpdate(TemplateUpdate):
"""
return self._new_archives
- def get_new_configs(self) -> Dict[str, str]:
+ def get_new_configs(self) -> Optional[Dict[str, str]]:
"""
Get the new configs of the spark job template.
diff --git a/core/src/main/java/org/apache/gravitino/job/JobManager.java
b/core/src/main/java/org/apache/gravitino/job/JobManager.java
index af02d1f655..61033b763d 100644
--- a/core/src/main/java/org/apache/gravitino/job/JobManager.java
+++ b/core/src/main/java/org/apache/gravitino/job/JobManager.java
@@ -29,6 +29,7 @@ import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.time.Instant;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -300,6 +301,53 @@ public class JobManager implements JobOperationDispatcher {
});
}
+ @Override
+ public JobTemplateEntity alterJobTemplate(
+ String metalake, String jobTemplateName, JobTemplateChange... changes)
+ throws NoSuchJobTemplateException, IllegalArgumentException {
+ checkMetalake(NameIdentifierUtil.ofMetalake(metalake), entityStore);
+
+ Optional<String> newName =
+ Arrays.stream(changes)
+ .filter(c -> c instanceof JobTemplateChange.RenameJobTemplate)
+ .map(c -> ((JobTemplateChange.RenameJobTemplate) c).getNewName())
+ .reduce((first, second) -> second);
+
+ NameIdentifier jobTemplateIdent =
NameIdentifierUtil.ofJobTemplate(metalake, jobTemplateName);
+ return TreeLockUtils.doWithTreeLock(
+ jobTemplateIdent,
+ LockType.READ, // Use READ lock because the update method in
JobTemplateMetaService will
+ // handle the update transactionally and update with a new version
number. So we don't
+ // have to use a WRITE lock here.
+ () -> {
+ try {
+ return entityStore.update(
+ jobTemplateIdent,
+ JobTemplateEntity.class,
+ Entity.EntityType.JOB_TEMPLATE,
+ jobTemplateEntity ->
+ updateJobTemplateEntity(jobTemplateIdent,
jobTemplateEntity, changes));
+ } catch (NoSuchEntityException e) {
+ throw new NoSuchJobTemplateException(
+ "Job template with name %s under metalake %s does not exist,
this could be due to"
+ + " the job template not existing or updated concurrently.
For the latter case"
+ + " please retry the operation.",
+ jobTemplateName, metalake);
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ } catch (EntityAlreadyExistsException e) {
+ // If the EntityAlreadyExistsException is thrown, it means the new
name already exists.
+ // So there should be a rename change, and the new name should be
present.
+ throw new RuntimeException(
+ String.format(
+ "Failed to rename job template from %s to %s under
metalake %s, the new name "
+ + "already exists",
+ jobTemplateName, newName, metalake),
+ e);
+ }
+ });
+ }
+
@Override
public List<JobEntity> listJobs(String metalake, Optional<String>
jobTemplateName)
throws NoSuchJobTemplateException {
@@ -764,4 +812,125 @@ public class JobManager implements JobOperationDispatcher
{
throw new RuntimeException("Failed to list in-use metalakes", e);
}
}
+
+ @VisibleForTesting
+ JobTemplateEntity updateJobTemplateEntity(
+ NameIdentifier jobTemplateIdent,
+ JobTemplateEntity jobTemplateEntity,
+ JobTemplateChange... changes) {
+ String newName = jobTemplateEntity.name();
+ String newComment = jobTemplateEntity.comment();
+ JobTemplateEntity.Builder newTemplateBuilder = JobTemplateEntity.builder();
+ JobTemplateEntity.TemplateContent.TemplateContentBuilder
newTemplateContentBuilder =
+ JobTemplateEntity.TemplateContent.builder()
+ .withJobType(jobTemplateEntity.templateContent().jobType())
+ .withExecutable(jobTemplateEntity.templateContent().executable())
+ .withArguments(jobTemplateEntity.templateContent().arguments())
+
.withEnvironments(jobTemplateEntity.templateContent().environments())
+
.withCustomFields(jobTemplateEntity.templateContent().customFields())
+ .withScripts(jobTemplateEntity.templateContent().scripts())
+ .withClassName(jobTemplateEntity.templateContent().className())
+ .withJars(jobTemplateEntity.templateContent().jars())
+ .withFiles(jobTemplateEntity.templateContent().files())
+ .withArchives(jobTemplateEntity.templateContent().archives())
+ .withConfigs(jobTemplateEntity.templateContent().configs());
+
+ for (JobTemplateChange change : changes) {
+ if (change instanceof JobTemplateChange.RenameJobTemplate) {
+ newName = ((JobTemplateChange.RenameJobTemplate) change).getNewName();
+
+ } else if (change instanceof JobTemplateChange.UpdateJobTemplateComment)
{
+ newComment = ((JobTemplateChange.UpdateJobTemplateComment)
change).getNewComment();
+
+ } else if (change instanceof JobTemplateChange.UpdateJobTemplate) {
+ JobTemplateEntity.TemplateContent oldTemplateContent =
jobTemplateEntity.templateContent();
+ JobTemplateChange.TemplateUpdate templateUpdate =
+ ((JobTemplateChange.UpdateJobTemplate) change).getTemplateUpdate();
+ newTemplateContentBuilder
+ .withJobType(oldTemplateContent.jobType())
+ .withExecutable(
+ updatedValue(
+ oldTemplateContent.executable(),
+ Optional.ofNullable(templateUpdate.getNewExecutable())))
+ .withArguments(
+ updatedValue(
+ oldTemplateContent.arguments(),
+ Optional.ofNullable(templateUpdate.getNewArguments())))
+ .withEnvironments(
+ updatedValue(
+ oldTemplateContent.environments(),
+ Optional.ofNullable(templateUpdate.getNewEnvironments())))
+ .withCustomFields(
+ updatedValue(
+ oldTemplateContent.customFields(),
+ Optional.ofNullable(templateUpdate.getNewCustomFields())));
+
+ if (templateUpdate instanceof JobTemplateChange.ShellTemplateUpdate) {
+ Preconditions.checkArgument(
+ jobTemplateEntity.templateContent().jobType() ==
JobTemplate.JobType.SHELL,
+ "Job template %s is not a shell job template, cannot update to
shell template",
+ jobTemplateIdent.name());
+
+ JobTemplateChange.ShellTemplateUpdate shellUpdate =
+ (JobTemplateChange.ShellTemplateUpdate) templateUpdate;
+ newTemplateContentBuilder.withScripts(
+ updatedValue(
+ oldTemplateContent.scripts(),
Optional.ofNullable(shellUpdate.getNewScripts())));
+
+ } else if (templateUpdate instanceof
JobTemplateChange.SparkTemplateUpdate) {
+ Preconditions.checkArgument(
+ jobTemplateEntity.templateContent().jobType() ==
JobTemplate.JobType.SPARK,
+ "Job template %s is not a spark job template, cannot update to
spark template",
+ jobTemplateIdent.name());
+
+ JobTemplateChange.SparkTemplateUpdate sparkUpdate =
+ (JobTemplateChange.SparkTemplateUpdate) templateUpdate;
+ newTemplateContentBuilder
+ .withClassName(
+ updatedValue(
+ oldTemplateContent.className(),
+ Optional.ofNullable(sparkUpdate.getNewClassName())))
+ .withJars(
+ updatedValue(
+ oldTemplateContent.jars(),
Optional.ofNullable(sparkUpdate.getNewJars())))
+ .withFiles(
+ updatedValue(
+ oldTemplateContent.files(),
Optional.ofNullable(sparkUpdate.getNewFiles())))
+ .withArchives(
+ updatedValue(
+ oldTemplateContent.archives(),
+ Optional.ofNullable(sparkUpdate.getNewArchives())))
+ .withConfigs(
+ updatedValue(
+ oldTemplateContent.configs(),
+ Optional.ofNullable(sparkUpdate.getNewConfigs())));
+
+ } else {
+ throw new IllegalArgumentException("Unsupported template update: " +
templateUpdate);
+ }
+
+ } else {
+ throw new IllegalArgumentException("Unsupported job template change: "
+ change);
+ }
+ }
+
+ return newTemplateBuilder
+ .withId(jobTemplateEntity.id())
+ .withName(newName)
+ .withComment(newComment)
+ .withNamespace(jobTemplateIdent.namespace())
+ .withTemplateContent(newTemplateContentBuilder.build())
+ .withAuditInfo(
+ AuditInfo.builder()
+ .withCreator(jobTemplateEntity.auditInfo().creator())
+ .withCreateTime(jobTemplateEntity.auditInfo().createTime())
+
.withLastModifier(PrincipalUtils.getCurrentPrincipal().getName())
+ .withLastModifiedTime(Instant.now())
+ .build())
+ .build();
+ }
+
+ private <T> T updatedValue(T currentValue, Optional<T> newValue) {
+ return newValue.orElse(currentValue);
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/job/JobOperationDispatcher.java
b/core/src/main/java/org/apache/gravitino/job/JobOperationDispatcher.java
index 464b430847..795ae6aeae 100644
--- a/core/src/main/java/org/apache/gravitino/job/JobOperationDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/job/JobOperationDispatcher.java
@@ -73,6 +73,20 @@ public interface JobOperationDispatcher extends Closeable {
*/
boolean deleteJobTemplate(String metalake, String jobTemplateName) throws
InUseException;
+ /**
+ * Alters a job template by applying the specified changes in the specified
metalake.
+ *
+ * @param metalake the name of the metalake
+ * @param jobTemplateName the name of the job template to alter
+ * @param changes the changes to apply to the job template
+ * @return the updated job template entity after applying the changes
+ * @throws NoSuchJobTemplateException if no job template with the specified
name exists
+ * @throws IllegalArgumentException if any of the changes cannot be applied
to the job template.
+ */
+ JobTemplateEntity alterJobTemplate(
+ String metalake, String jobTemplateName, JobTemplateChange... changes)
+ throws NoSuchJobTemplateException, IllegalArgumentException;
+
/**
* List all the jobs. If the jobTemplateName is provided, it will list the
jobs associated with
* that job template, if not, it will list all the jobs in the specified
metalake.
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index 3aa7402a18..e44cb9b2ae 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -238,6 +238,8 @@ public class JDBCBackend implements RelationalBackend {
return (E)
ModelVersionMetaService.getInstance().updateModelVersion(ident, updater);
case POLICY:
return (E) PolicyMetaService.getInstance().updatePolicy(ident,
updater);
+ case JOB_TEMPLATE:
+ return (E)
JobTemplateMetaService.getInstance().updateJobTemplate(ident, updater);
default:
throw new UnsupportedEntityTypeException(
"Unsupported entity type: %s for update operation", entityType);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
index c902880908..19af2cc44a 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
@@ -65,4 +65,9 @@ public interface JobTemplateMetaMapper {
method = "deleteJobTemplateMetasByLegacyTimeline")
Integer deleteJobTemplateMetasByLegacyTimeline(
@Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit);
+
+ @UpdateProvider(type = JobTemplateMetaSQLProviderFactory.class, method =
"updateJobTemplateMeta")
+ Integer updateJobTemplateMeta(
+ @Param("newJobTemplateMeta") JobTemplatePO newJobTemplatePO,
+ @Param("oldJobTemplateMeta") JobTemplatePO oldJobTemplatePO);
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
index 64de88dc88..4dc6908e0d 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
@@ -87,4 +87,10 @@ public class JobTemplateMetaSQLProviderFactory {
@Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit)
{
return
getProvider().deleteJobTemplateMetasByLegacyTimeline(legacyTimeline, limit);
}
+
+ public static String updateJobTemplateMeta(
+ @Param("newJobTemplateMeta") JobTemplatePO newJobTemplatePO,
+ @Param("oldJobTemplateMeta") JobTemplatePO oldJobTemplatePO) {
+ return getProvider().updateJobTemplateMeta(newJobTemplatePO,
oldJobTemplatePO);
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
index 43111d3efd..3f6a1082b5 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
@@ -120,4 +120,25 @@ public class JobTemplateMetaBaseSQLProvider {
+ JobTemplateMetaMapper.TABLE_NAME
+ " WHERE deleted_at < #{legacyTimeline} AND deleted_at > 0 LIMIT
#{limit}";
}
+
+ public String updateJobTemplateMeta(
+ @Param("newJobTemplateMeta") JobTemplatePO newJobTemplatePO,
+ @Param("oldJobTemplateMeta") JobTemplatePO oldJobTemplatePO) {
+ return "UPDATE "
+ + JobTemplateMetaMapper.TABLE_NAME
+ + " SET job_template_name = #{newJobTemplateMeta.jobTemplateName},"
+ + " metalake_id = #{newJobTemplateMeta.metalakeId},"
+ + " job_template_comment = #{newJobTemplateMeta.jobTemplateComment},"
+ + " job_template_content = #{newJobTemplateMeta.jobTemplateContent},"
+ + " audit_info = #{newJobTemplateMeta.auditInfo},"
+ + " current_version = #{newJobTemplateMeta.currentVersion},"
+ + " last_version = #{newJobTemplateMeta.lastVersion},"
+ + " deleted_at = #{newJobTemplateMeta.deletedAt}"
+ + " WHERE job_template_id = #{oldJobTemplateMeta.jobTemplateId}"
+ + " AND job_template_name = #{oldJobTemplateMeta.jobTemplateName}"
+ + " AND metalake_id = #{oldJobTemplateMeta.metalakeId}"
+ + " AND current_version = #{oldJobTemplateMeta.currentVersion}"
+ + " AND last_version = #{oldJobTemplateMeta.lastVersion}"
+ + " AND deleted_at = 0";
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/po/JobTemplatePO.java
b/core/src/main/java/org/apache/gravitino/storage/relational/po/JobTemplatePO.java
index 6efd8fb141..f997ad5df9 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/po/JobTemplatePO.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/po/JobTemplatePO.java
@@ -111,6 +111,31 @@ public class JobTemplatePO {
}
}
+ public static JobTemplatePO updateJobTemplatePO(
+ JobTemplatePO oldJobTemplatePO,
+ JobTemplateEntity newJobTemplateEntity,
+ JobTemplatePOBuilder builder) {
+ try {
+ Long lastVersion = oldJobTemplatePO.lastVersion() + 1;
+ Long currentVersion = lastVersion;
+
+ return builder
+ .withJobTemplateId(newJobTemplateEntity.id())
+ .withJobTemplateName(newJobTemplateEntity.name())
+ .withJobTemplateComment(newJobTemplateEntity.comment())
+ .withJobTemplateContent(
+
JsonUtils.anyFieldMapper().writeValueAsString(newJobTemplateEntity.templateContent()))
+ .withAuditInfo(
+
JsonUtils.anyFieldMapper().writeValueAsString(newJobTemplateEntity.auditInfo()))
+ .withCurrentVersion(currentVersion)
+ .withLastVersion(lastVersion)
+ .withDeletedAt(DEFAULT_DELETED_AT)
+ .build();
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Failed to serialize job template entity", e);
+ }
+ }
+
public static JobTemplateEntity fromJobTemplatePO(
JobTemplatePO jobTemplatePO, Namespace namespace) {
try {
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/JobTemplateMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/JobTemplateMetaService.java
index a042d8ba70..203773f371 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/JobTemplateMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/JobTemplateMetaService.java
@@ -18,12 +18,16 @@
*/
package org.apache.gravitino.storage.relational.service;
+import com.google.common.base.Preconditions;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.exceptions.NoSuchEntityException;
@@ -59,21 +63,7 @@ public class JobTemplateMetaService {
}
public JobTemplateEntity getJobTemplateByIdentifier(NameIdentifier
jobTemplateIdent) {
- String metalakeName = jobTemplateIdent.namespace().level(0);
- String jobTemplateName = jobTemplateIdent.name();
-
- JobTemplatePO jobTemplatePO =
- SessionUtils.getWithoutCommit(
- JobTemplateMetaMapper.class,
- mapper ->
mapper.selectJobTemplatePOByMetalakeAndName(metalakeName, jobTemplateName));
-
- if (jobTemplatePO == null) {
- throw new NoSuchEntityException(
- NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
- Entity.EntityType.JOB_TEMPLATE.name().toLowerCase(Locale.ROOT),
- jobTemplateName);
- }
-
+ JobTemplatePO jobTemplatePO = getJobTemplatePO(jobTemplateIdent);
return JobTemplatePO.fromJobTemplatePO(jobTemplatePO,
jobTemplateIdent.namespace());
}
@@ -130,4 +120,67 @@ public class JobTemplateMetaService {
JobTemplateMetaMapper.class,
mapper ->
mapper.deleteJobTemplateMetasByLegacyTimeline(legacyTimeline, limit));
}
+
+ public <E extends Entity & HasIdentifier> JobTemplateEntity
updateJobTemplate(
+ NameIdentifier jobTemplateIdent, Function<E, E> updater) throws
IOException {
+ JobTemplatePO oldJobTemplatePO = getJobTemplatePO(jobTemplateIdent);
+ JobTemplateEntity oldJobTemplateEntity =
+ JobTemplatePO.fromJobTemplatePO(oldJobTemplatePO,
jobTemplateIdent.namespace());
+ JobTemplateEntity newJobTemplateEntity =
+ (JobTemplateEntity) updater.apply((E) oldJobTemplateEntity);
+ Preconditions.checkArgument(
+ Objects.equals(oldJobTemplateEntity.id(), newJobTemplateEntity.id()),
+ "The updated job templated id: %s is not equal to the old one: %s,
which is unexpected",
+ newJobTemplateEntity.id(),
+ oldJobTemplateEntity.id());
+
+ JobTemplatePO.JobTemplatePOBuilder newBuilder =
+ JobTemplatePO.builder().withMetalakeId(oldJobTemplatePO.metalakeId());
+ JobTemplatePO newJobTemplatePO =
+ JobTemplatePO.updateJobTemplatePO(oldJobTemplatePO,
newJobTemplateEntity, newBuilder);
+
+ Integer result;
+ try {
+ result =
+ SessionUtils.doWithCommitAndFetchResult(
+ JobTemplateMetaMapper.class,
+ mapper -> mapper.updateJobTemplateMeta(newJobTemplatePO,
oldJobTemplatePO));
+ } catch (RuntimeException e) {
+ ExceptionUtils.checkSQLException(
+ e, Entity.EntityType.JOB_TEMPLATE, oldJobTemplateEntity.name());
+ throw e;
+ }
+
+ if (result == null || result == 0) {
+ throw new NoSuchEntityException(
+ NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+ Entity.EntityType.JOB_TEMPLATE.name().toLowerCase(Locale.ROOT),
+ oldJobTemplateEntity.name());
+ } else if (result > 1) {
+ throw new IOException(
+ String.format(
+ "Failed to update job template: %s, because more than one rows
are updated: %d",
+ oldJobTemplateEntity.name(), result));
+ } else {
+ return newJobTemplateEntity;
+ }
+ }
+
+ private JobTemplatePO getJobTemplatePO(NameIdentifier jobTemplateIdent) {
+ String metalakeName = jobTemplateIdent.namespace().level(0);
+ String jobTemplateName = jobTemplateIdent.name();
+
+ JobTemplatePO jobTemplatePO =
+ SessionUtils.getWithoutCommit(
+ JobTemplateMetaMapper.class,
+ mapper ->
mapper.selectJobTemplatePOByMetalakeAndName(metalakeName, jobTemplateName));
+
+ if (jobTemplatePO == null) {
+ throw new NoSuchEntityException(
+ NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+ Entity.EntityType.JOB_TEMPLATE.name().toLowerCase(Locale.ROOT),
+ jobTemplateName);
+ }
+ return jobTemplatePO;
+ }
}
diff --git a/core/src/test/java/org/apache/gravitino/job/TestJobManager.java
b/core/src/test/java/org/apache/gravitino/job/TestJobManager.java
index a95cec8466..674e68cf9a 100644
--- a/core/src/test/java/org/apache/gravitino/job/TestJobManager.java
+++ b/core/src/test/java/org/apache/gravitino/job/TestJobManager.java
@@ -595,6 +595,221 @@ public class TestJobManager {
});
}
+ @Test
+ public void testUpdateShellJobTemplateEntity() {
+ String jobTemplateName = "old_shell_job";
+ String jobTemplateComment = "An old shell job template";
+ JobTemplateEntity oldJobTemplateEntity =
+ newShellJobTemplateEntity(jobTemplateName, jobTemplateComment);
+
+ // Update name and comment
+ String newJobTemplateName = "new_shell_job";
+ String newJobTemplateComment = "A new shell job template";
+ JobTemplateChange rename = JobTemplateChange.rename(newJobTemplateName);
+ JobTemplateChange updateComment =
JobTemplateChange.updateComment(newJobTemplateComment);
+
+ JobTemplateEntity newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
rename, updateComment);
+
+ Assertions.assertEquals(oldJobTemplateEntity.id(),
newJobTemplateEntity.id());
+ Assertions.assertEquals(newJobTemplateName, newJobTemplateEntity.name());
+ Assertions.assertEquals(oldJobTemplateEntity.namespace(),
newJobTemplateEntity.namespace());
+ Assertions.assertEquals(newJobTemplateComment,
newJobTemplateEntity.comment());
+ Assertions.assertEquals(
+ oldJobTemplateEntity.templateContent(),
newJobTemplateEntity.templateContent());
+
+ // Update the executable of the shell job template
+ JobTemplateChange updateShellTemplate =
+ JobTemplateChange.updateTemplate(
+
JobTemplateChange.ShellTemplateUpdate.builder().withNewExecutable("/bin/ls").build());
+
+ newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
updateShellTemplate);
+ Assertions.assertEquals(oldJobTemplateEntity.id(),
newJobTemplateEntity.id());
+ Assertions.assertEquals(oldJobTemplateEntity.name(),
newJobTemplateEntity.name());
+ Assertions.assertEquals(oldJobTemplateEntity.namespace(),
newJobTemplateEntity.namespace());
+ Assertions.assertEquals(oldJobTemplateEntity.comment(),
newJobTemplateEntity.comment());
+ Assertions.assertNotEquals(
+ oldJobTemplateEntity.templateContent(),
newJobTemplateEntity.templateContent());
+ JobTemplateEntity.TemplateContent oldContent =
oldJobTemplateEntity.templateContent();
+ JobTemplateEntity.TemplateContent newContent =
newJobTemplateEntity.templateContent();
+ Assertions.assertEquals(oldContent.jobType(), newContent.jobType());
+ Assertions.assertEquals("/bin/ls", newContent.executable());
+ Assertions.assertEquals(oldContent.arguments(), newContent.arguments());
+ Assertions.assertEquals(oldContent.environments(),
newContent.environments());
+ Assertions.assertEquals(oldContent.customFields(),
newContent.customFields());
+
+ // Update the arguments, environments, custom fields of the shell job
template
+ JobTemplateChange updateShellTemplate2 =
+ JobTemplateChange.updateTemplate(
+ JobTemplateChange.ShellTemplateUpdate.builder()
+ .withNewArguments(ImmutableList.of("arg1", "arg2"))
+ .withNewEnvironments(Collections.singletonMap("env1",
"value1"))
+ .withNewCustomFields(Collections.singletonMap("field1",
"value1"))
+ .build());
+ newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
updateShellTemplate2);
+
+ JobTemplateEntity.TemplateContent newContent2 =
newJobTemplateEntity.templateContent();
+ Assertions.assertEquals(oldContent.jobType(), newContent2.jobType());
+ Assertions.assertEquals(oldContent.executable(), newContent2.executable());
+ Assertions.assertEquals(ImmutableList.of("arg1", "arg2"),
newContent2.arguments());
+ Assertions.assertEquals(Collections.singletonMap("env1", "value1"),
newContent2.environments());
+ Assertions.assertEquals(
+ Collections.singletonMap("field1", "value1"),
newContent2.customFields());
+ Assertions.assertEquals(oldContent.scripts(), newContent2.scripts());
+
+ // Update the scripts of the shell job template
+ JobTemplateChange updateShellTemplate3 =
+ JobTemplateChange.updateTemplate(
+ JobTemplateChange.ShellTemplateUpdate.builder()
+ .withNewScripts(ImmutableList.of("echo Hello", "echo World"))
+ .build());
+ newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
updateShellTemplate3);
+
+ JobTemplateEntity.TemplateContent newContent3 =
newJobTemplateEntity.templateContent();
+ Assertions.assertEquals(oldContent.jobType(), newContent3.jobType());
+ Assertions.assertEquals(oldContent.executable(), newContent3.executable());
+ Assertions.assertEquals(oldContent.arguments(), newContent3.arguments());
+ Assertions.assertEquals(oldContent.environments(),
newContent3.environments());
+ Assertions.assertEquals(oldContent.customFields(),
newContent3.customFields());
+ Assertions.assertEquals(ImmutableList.of("echo Hello", "echo World"),
newContent3.scripts());
+
+ // Update with no changes
+ JobTemplateChange noChange =
+
JobTemplateChange.updateTemplate(JobTemplateChange.ShellTemplateUpdate.builder().build());
+ newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
noChange);
+ Assertions.assertEquals(
+ oldJobTemplateEntity.templateContent(),
newJobTemplateEntity.templateContent());
+
+ // Update job template with SparkJobTemplateChange should throw
IllegalArgumentException
+ JobTemplateChange invalidChange =
+
JobTemplateChange.updateTemplate(JobTemplateChange.SparkTemplateUpdate.builder().build());
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
invalidChange));
+ }
+
+ @Test
+ public void testUpdateSparkJobTemplateEntity() {
+ String jobTemplateName = "old_spark_job";
+ String jobTemplateComment = "An old spark job template";
+ JobTemplateEntity oldJobTemplateEntity =
+ newSparkJobTemplateEntity(jobTemplateName, jobTemplateComment);
+
+ // Update the executable and class name of the spark job template
+ JobTemplateChange updateSparkTemplate =
+ JobTemplateChange.updateTemplate(
+ JobTemplateChange.SparkTemplateUpdate.builder()
+ .withNewExecutable("file:/new/path/to/spark-examples.jar")
+ .withNewClassName("org.apache.spark.examples.SparkWordCount")
+ .build());
+ JobTemplateEntity newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
updateSparkTemplate);
+ Assertions.assertEquals(oldJobTemplateEntity.id(),
newJobTemplateEntity.id());
+ Assertions.assertEquals(oldJobTemplateEntity.name(),
newJobTemplateEntity.name());
+ Assertions.assertEquals(oldJobTemplateEntity.namespace(),
newJobTemplateEntity.namespace());
+ Assertions.assertEquals(oldJobTemplateEntity.comment(),
newJobTemplateEntity.comment());
+ Assertions.assertNotEquals(
+ oldJobTemplateEntity.templateContent(),
newJobTemplateEntity.templateContent());
+ JobTemplateEntity.TemplateContent oldContent =
oldJobTemplateEntity.templateContent();
+ JobTemplateEntity.TemplateContent newContent =
newJobTemplateEntity.templateContent();
+ Assertions.assertEquals(oldContent.jobType(), newContent.jobType());
+ Assertions.assertEquals("file:/new/path/to/spark-examples.jar",
newContent.executable());
+ Assertions.assertEquals("org.apache.spark.examples.SparkWordCount",
newContent.className());
+ Assertions.assertEquals(oldContent.arguments(), newContent.arguments());
+ Assertions.assertEquals(oldContent.environments(),
newContent.environments());
+ Assertions.assertEquals(oldContent.customFields(),
newContent.customFields());
+ Assertions.assertEquals(oldContent.jars(), newContent.jars());
+ Assertions.assertEquals(oldContent.files(), newContent.files());
+ Assertions.assertEquals(oldContent.archives(), newContent.archives());
+ Assertions.assertEquals(oldContent.configs(), newContent.configs());
+
+ // Update the arguments, environments, custom fields of the spark job
template
+ JobTemplateChange updateSparkTemplate2 =
+ JobTemplateChange.updateTemplate(
+ JobTemplateChange.SparkTemplateUpdate.builder()
+ .withNewArguments(ImmutableList.of("arg1", "arg2"))
+ .withNewEnvironments(Collections.singletonMap("env1",
"value1"))
+ .withNewCustomFields(Collections.singletonMap("field1",
"value1"))
+ .build());
+ newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
updateSparkTemplate2);
+ JobTemplateEntity.TemplateContent newContent2 =
newJobTemplateEntity.templateContent();
+ Assertions.assertEquals(oldContent.jobType(), newContent2.jobType());
+ Assertions.assertEquals(oldContent.executable(), newContent2.executable());
+ Assertions.assertEquals(oldContent.className(), newContent2.className());
+ Assertions.assertEquals(ImmutableList.of("arg1", "arg2"),
newContent2.arguments());
+ Assertions.assertEquals(Collections.singletonMap("env1", "value1"),
newContent2.environments());
+ Assertions.assertEquals(
+ Collections.singletonMap("field1", "value1"),
newContent2.customFields());
+ Assertions.assertEquals(oldContent.jars(), newContent2.jars());
+ Assertions.assertEquals(oldContent.files(), newContent2.files());
+ Assertions.assertEquals(oldContent.archives(), newContent2.archives());
+ Assertions.assertEquals(oldContent.configs(), newContent2.configs());
+
+ // Update the jars, files, archives, configs of the spark job template
+ JobTemplateChange updateSparkTemplate3 =
+ JobTemplateChange.updateTemplate(
+ JobTemplateChange.SparkTemplateUpdate.builder()
+ .withNewJars(ImmutableList.of("file:/new/path/to/jar1 ",
"file:/new/path/to/jar2"))
+ .withNewFiles(
+ ImmutableList.of("file:/new/path/to/file1",
"file:/new/path/to/file2"))
+ .withNewArchives(
+ ImmutableList.of("file:/new/path/to/archive1",
"file:/new/path/to/archive2"))
+
.withNewConfigs(Collections.singletonMap("spark.executor.memory", "4g"))
+ .build());
+ newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
updateSparkTemplate3);
+ JobTemplateEntity.TemplateContent newContent3 =
newJobTemplateEntity.templateContent();
+ Assertions.assertEquals(oldContent.jobType(), newContent3.jobType());
+ Assertions.assertEquals(oldContent.executable(), newContent3.executable());
+ Assertions.assertEquals(oldContent.className(), newContent3.className());
+ Assertions.assertEquals(oldContent.arguments(), newContent3.arguments());
+ Assertions.assertEquals(oldContent.environments(),
newContent3.environments());
+ Assertions.assertEquals(oldContent.customFields(),
newContent3.customFields());
+ Assertions.assertEquals(
+ ImmutableList.of("file:/new/path/to/jar1 ", "file:/new/path/to/jar2"),
newContent3.jars());
+ Assertions.assertEquals(
+ ImmutableList.of("file:/new/path/to/file1", "file:/new/path/to/file2"),
+ newContent3.files());
+ Assertions.assertEquals(
+ ImmutableList.of("file:/new/path/to/archive1",
"file:/new/path/to/archive2"),
+ newContent3.archives());
+ Assertions.assertEquals(
+ Collections.singletonMap("spark.executor.memory", "4g"),
newContent3.configs());
+
+ // Update with no changes
+ JobTemplateChange noChange =
+
JobTemplateChange.updateTemplate(JobTemplateChange.SparkTemplateUpdate.builder().build());
+ newJobTemplateEntity =
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
noChange);
+ Assertions.assertEquals(
+ oldJobTemplateEntity.templateContent(),
newJobTemplateEntity.templateContent());
+
+ // Update job template with ShellJobTemplateChange should throw
IllegalArgumentException
+ JobTemplateChange invalidChange =
+
JobTemplateChange.updateTemplate(JobTemplateChange.ShellTemplateUpdate.builder().build());
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ jobManager.updateJobTemplateEntity(
+ oldJobTemplateEntity.nameIdentifier(), oldJobTemplateEntity,
invalidChange));
+ }
+
private static JobTemplateEntity newShellJobTemplateEntity(String name,
String comment) {
ShellJobTemplate shellJobTemplate =
ShellJobTemplate.builder()
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobTemplateMetaService.java
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobTemplateMetaService.java
index 2eb21c7620..70f1bf0864 100644
---
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobTemplateMetaService.java
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobTemplateMetaService.java
@@ -209,6 +209,75 @@ public class TestJobTemplateMetaService extends
TestJDBCBackend {
Assertions.assertEquals(0, jobs.size());
}
+ @Test
+ public void testUpdateJobTemplate() throws IOException {
+ BaseMetalake metalake =
+ createBaseMakeLake(RandomIdGenerator.INSTANCE.nextId(), METALAKE_NAME,
AUDIT_INFO);
+ backend.insert(metalake, false);
+
+ JobTemplateMetaService jobTemplateMetaService =
JobTemplateMetaService.getInstance();
+
+ JobTemplateEntity jobTemplateEntity =
+ newShellJobTemplateEntity("updatable_template", "An updatable job
template", METALAKE_NAME);
+ jobTemplateMetaService.insertJobTemplate(jobTemplateEntity, false);
+
+ // Update the job template's comment
+ JobTemplateEntity updatedJobTemplateEntity =
+ JobTemplateEntity.builder()
+ .withId(jobTemplateEntity.id())
+ .withName("updated_template")
+ .withNamespace(jobTemplateEntity.namespace())
+ .withTemplateContent(jobTemplateEntity.templateContent())
+ .withComment("Updated comment for the job template")
+ .withAuditInfo(jobTemplateEntity.auditInfo())
+ .build();
+
+ jobTemplateMetaService.updateJobTemplate(
+ jobTemplateEntity.nameIdentifier(), e -> updatedJobTemplateEntity);
+
+ // Verify the update
+ JobTemplateEntity fetchedJobTemplate =
+ jobTemplateMetaService.getJobTemplateByIdentifier(
+ NameIdentifierUtil.ofJobTemplate(METALAKE_NAME,
"updated_template"));
+
+ Assertions.assertEquals(updatedJobTemplateEntity, fetchedJobTemplate);
+
+ // Verify that the old name no longer exists
+ Assertions.assertThrows(
+ NoSuchEntityException.class,
+ () ->
+ jobTemplateMetaService.getJobTemplateByIdentifier(
+ NameIdentifierUtil.ofJobTemplate(METALAKE_NAME,
"updatable_template")));
+
+ // Test update an non-existent job template
+ Assertions.assertThrows(
+ NoSuchEntityException.class,
+ () ->
+ jobTemplateMetaService.updateJobTemplate(
+ NameIdentifierUtil.ofJobTemplate(METALAKE_NAME,
"non_existent_template"),
+ e -> updatedJobTemplateEntity));
+
+ // Test update to a name that already exists
+ JobTemplateEntity anotherJobTemplateEntity =
+ newSparkJobTemplateEntity("existing_template", "An existing job
template", METALAKE_NAME);
+ jobTemplateMetaService.insertJobTemplate(anotherJobTemplateEntity, false);
+ JobTemplateEntity duplicateNameJobTemplateEntity =
+ JobTemplateEntity.builder()
+ .withId(updatedJobTemplateEntity.id())
+ .withName("existing_template")
+ .withNamespace(updatedJobTemplateEntity.namespace())
+ .withTemplateContent(updatedJobTemplateEntity.templateContent())
+ .withComment("Trying to update to an existing name")
+ .withAuditInfo(updatedJobTemplateEntity.auditInfo())
+ .build();
+
+ Assertions.assertThrows(
+ EntityAlreadyExistsException.class,
+ () ->
+ jobTemplateMetaService.updateJobTemplate(
+ updatedJobTemplateEntity.nameIdentifier(), e ->
duplicateNameJobTemplateEntity));
+ }
+
static JobTemplateEntity newShellJobTemplateEntity(String name, String
comment, String metalake) {
ShellJobTemplate shellJobTemplate =
ShellJobTemplate.builder()