From 69c38c28f0359c7b659ff6f0f3f492fbbe33cab7 Mon Sep 17 00:00:00 2001 From: sahusanket Date: Tue, 5 May 2026 20:10:45 +0530 Subject: [PATCH 1/4] Check config and aritfact and skip --- .../AbstractAppLifecycleHttpHandler.java | 7 ++++ .../services/ApplicationLifecycleService.java | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/AbstractAppLifecycleHttpHandler.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/AbstractAppLifecycleHttpHandler.java index ab3dd3476ef4..6b876e6e480d 100644 --- a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/AbstractAppLifecycleHttpHandler.java +++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/AbstractAppLifecycleHttpHandler.java @@ -183,6 +183,13 @@ protected void onFinish(HttpResponder responder, File uploadedFile) { AppRequest appRequest = DECODE_GSON.fromJson(fileReader, AppRequest.class); try { + if (applicationLifecycleService.isAppAlreadyDeployed(appId, appRequest)) { + io.cdap.cdap.proto.ApplicationDetail existingApp = + applicationLifecycleService.getLatestAppDetail(appId.getAppReference()); + responder.sendJson(HttpResponseStatus.OK, GSON.toJson(new io.cdap.cdap.proto.ApplicationRecord(existingApp))); + return; + } + ApplicationWithPrograms app = applicationLifecycleService.deployApp(appId, appRequest, null, createProgramTerminator(), skipMarkingLatest); responder.sendJson(HttpResponseStatus.OK, GSON.toJson(getApplicationRecord(app))); diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ApplicationLifecycleService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ApplicationLifecycleService.java index 22eef1f32806..1cb0093c6c44 100644 --- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ApplicationLifecycleService.java +++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ApplicationLifecycleService.java @@ -741,6 +741,43 @@ private ApplicationId updateApplicationByArtifact(ApplicationId appId, allowedArtifactScopes, allowSnapshot, ownerAdmin.getOwner(appId), appSpec); } + /** + * Checks if the application already exists with the exact same artifact and + * configuration. + */ + public boolean isAppAlreadyDeployed(ApplicationId appId, AppRequest appRequest) throws Exception { + ApplicationMeta appMeta = store.getLatest(appId.getAppReference()); + if (appMeta == null || appMeta.getSpec() == null) { + return false; + } + + ArtifactSummary requestedArtifact = appRequest.getArtifact(); + if (requestedArtifact == null) { + return false; + } + + ArtifactId currentArtifactId = appMeta.getSpec().getArtifactId(); + if (!currentArtifactId.getName().equals(requestedArtifact.getName()) || + !currentArtifactId.getVersion().getVersion().equals(requestedArtifact.getVersion()) || + !currentArtifactId.getScope().equals(requestedArtifact.getScope())) { + return false; + } + + Object config = appRequest.getConfig(); + String requestedConfigStr = config == null ? null + : config instanceof String ? (String) config : GSON.toJson(config); + String currentConfigStr = appMeta.getSpec().getConfiguration(); + + String normRequestedConfig = requestedConfigStr == null ? "" : requestedConfigStr.trim(); + String normCurrentConfig = currentConfigStr == null ? "" : currentConfigStr.trim(); + + if (!normRequestedConfig.equals(normCurrentConfig)) { + return false; + } + + return true; + } + /** * Updates an application config by applying given update actions. The app should know how to * apply these actions to its config. From b5aefb3a65102cef2dfdea86b16e53e4469cde90 Mon Sep 17 00:00:00 2001 From: sahusanket Date: Wed, 6 May 2026 15:23:35 +0530 Subject: [PATCH 2/4] optimistic lock --- .../io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cdap-storage-ext-spanner/src/main/java/io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java b/cdap-storage-ext-spanner/src/main/java/io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java index fcd72a506c51..752c18283b41 100644 --- a/cdap-storage-ext-spanner/src/main/java/io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java +++ b/cdap-storage-ext-spanner/src/main/java/io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java @@ -17,6 +17,7 @@ package io.cdap.cdap.storage.spanner; import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.Options; import com.google.cloud.spanner.SpannerException; import io.cdap.cdap.spi.data.transaction.TransactionException; import io.cdap.cdap.spi.data.transaction.TransactionRunner; @@ -36,7 +37,7 @@ public SpannerTransactionRunner(SpannerStructuredTableAdmin admin) { @Override public void run(TxRunnable runnable) throws TransactionException { try { - admin.getDatabaseClient().readWriteTransaction().allowNestedTransaction().run(context -> { + admin.getDatabaseClient().readWriteTransaction(Options.optimisticLock()).allowNestedTransaction().run(context -> { runnable.run(new SpannerStructuredTableContext(context, admin)); return null; }); From de3476401815bbf788a1e32b6385e020b4849d16 Mon Sep 17 00:00:00 2001 From: sahusanket Date: Wed, 6 May 2026 17:45:45 +0530 Subject: [PATCH 3/4] support for version range --- .../handlers/AbstractAppLifecycleHttpHandler.java | 1 + .../app/services/ApplicationLifecycleService.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/AbstractAppLifecycleHttpHandler.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/AbstractAppLifecycleHttpHandler.java index 6b876e6e480d..14fbf55312e7 100644 --- a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/AbstractAppLifecycleHttpHandler.java +++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/AbstractAppLifecycleHttpHandler.java @@ -184,6 +184,7 @@ protected void onFinish(HttpResponder responder, File uploadedFile) { try { if (applicationLifecycleService.isAppAlreadyDeployed(appId, appRequest)) { + LOG.warn("Application {} is already deployed",appId ); io.cdap.cdap.proto.ApplicationDetail existingApp = applicationLifecycleService.getLatestAppDetail(appId.getAppReference()); responder.sendJson(HttpResponseStatus.OK, GSON.toJson(new io.cdap.cdap.proto.ApplicationRecord(existingApp))); diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ApplicationLifecycleService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ApplicationLifecycleService.java index 1cb0093c6c44..4262a3ac6455 100644 --- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ApplicationLifecycleService.java +++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ApplicationLifecycleService.java @@ -758,11 +758,20 @@ public boolean isAppAlreadyDeployed(ApplicationId appId, AppRequest appReques ArtifactId currentArtifactId = appMeta.getSpec().getArtifactId(); if (!currentArtifactId.getName().equals(requestedArtifact.getName()) || - !currentArtifactId.getVersion().getVersion().equals(requestedArtifact.getVersion()) || !currentArtifactId.getScope().equals(requestedArtifact.getScope())) { return false; } + try { + io.cdap.cdap.api.artifact.ArtifactVersionRange range = io.cdap.cdap.api.artifact.ArtifactVersionRange + .parse(requestedArtifact.getVersion()); + if (!range.versionIsInRange(currentArtifactId.getVersion())) { + return false; + } + } catch (Exception e) { + return false; + } + Object config = appRequest.getConfig(); String requestedConfigStr = config == null ? null : config instanceof String ? (String) config : GSON.toJson(config); From 34b452a95840f9906f6148d750a766502af40323 Mon Sep 17 00:00:00 2001 From: sahusanket Date: Mon, 11 May 2026 20:59:14 +0530 Subject: [PATCH 4/4] comment --- .../io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java | 1 + 1 file changed, 1 insertion(+) diff --git a/cdap-storage-ext-spanner/src/main/java/io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java b/cdap-storage-ext-spanner/src/main/java/io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java index 752c18283b41..8aa033b4bd7b 100644 --- a/cdap-storage-ext-spanner/src/main/java/io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java +++ b/cdap-storage-ext-spanner/src/main/java/io/cdap/cdap/storage/spanner/SpannerTransactionRunner.java @@ -37,6 +37,7 @@ public SpannerTransactionRunner(SpannerStructuredTableAdmin admin) { @Override public void run(TxRunnable runnable) throws TransactionException { try { + // enforce lock admin.getDatabaseClient().readWriteTransaction(Options.optimisticLock()).allowNestedTransaction().run(context -> { runnable.run(new SpannerStructuredTableContext(context, admin)); return null;