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 ab3dd3476ef..14fbf55312e 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,14 @@ protected void onFinish(HttpResponder responder, File uploadedFile) { AppRequest appRequest = DECODE_GSON.fromJson(fileReader, AppRequest.class); 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))); + 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 22eef1f3280..4262a3ac645 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,52 @@ 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.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); + 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. 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 fcd72a506c5..8aa033b4bd7 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,8 @@ public SpannerTransactionRunner(SpannerStructuredTableAdmin admin) { @Override public void run(TxRunnable runnable) throws TransactionException { try { - admin.getDatabaseClient().readWriteTransaction().allowNestedTransaction().run(context -> { + // enforce lock + admin.getDatabaseClient().readWriteTransaction(Options.optimisticLock()).allowNestedTransaction().run(context -> { runnable.run(new SpannerStructuredTableContext(context, admin)); return null; });