This is an automated email from the ASF dual-hosted git repository.

gyfora pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-kubernetes-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 1eca2c5e [FLINK-28755] Fix error when switching from stateless to 
savepoint upgrade mode
1eca2c5e is described below

commit 1eca2c5e6a62bd9f1c9e752191f8a7477903d73c
Author: Gabor Somogyi <[email protected]>
AuthorDate: Wed Sep 14 09:43:42 2022 +0200

    [FLINK-28755] Fix error when switching from stateless to savepoint upgrade 
mode
---
 .../operator/config/FlinkConfigManager.java        |  16 +-
 .../deployment/AbstractJobReconciler.java          |   6 +-
 .../deployment/ApplicationReconcilerTest.java      | 150 -------
 .../ApplicationReconcilerUpgradeModeTest.java      | 457 +++++++++++++++++++++
 4 files changed, 475 insertions(+), 154 deletions(-)

diff --git 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/config/FlinkConfigManager.java
 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/config/FlinkConfigManager.java
index 49181886..5f24df7c 100644
--- 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/config/FlinkConfigManager.java
+++ 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/config/FlinkConfigManager.java
@@ -19,6 +19,7 @@
 package org.apache.flink.kubernetes.operator.config;
 
 import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ConfigOption;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.configuration.GlobalConfiguration;
 import org.apache.flink.kubernetes.operator.crd.FlinkDeployment;
@@ -53,6 +54,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 
+import static 
org.apache.flink.configuration.CheckpointingOptions.SAVEPOINT_DIRECTORY;
 import static 
org.apache.flink.kubernetes.operator.config.KubernetesOperatorConfigOptions.K8S_OP_CONF_PREFIX;
 import static 
org.apache.flink.kubernetes.operator.config.KubernetesOperatorConfigOptions.OPERATOR_DYNAMIC_CONFIG_CHECK_INTERVAL;
 import static 
org.apache.flink.kubernetes.operator.config.KubernetesOperatorConfigOptions.OPERATOR_DYNAMIC_CONFIG_ENABLED;
@@ -160,7 +162,7 @@ public class FlinkConfigManager {
                     "Cannot create observe config before first deployment, 
this indicates a bug.");
         }
         var conf = getConfig(deployment.getMetadata(), deployedSpec);
-        addOperatorConfigsFromSpec(deployment.getSpec(), conf);
+        applyConfigsFromCurrentSpec(deployment.getSpec(), conf, 
SAVEPOINT_DIRECTORY);
         return conf;
     }
 
@@ -177,6 +179,18 @@ public class FlinkConfigManager {
         }
     }
 
+    @SuppressWarnings("unchecked")
+    private void applyConfigsFromCurrentSpec(
+            AbstractFlinkSpec spec, Configuration conf, ConfigOption... 
configOptions) {
+        addOperatorConfigsFromSpec(spec, conf);
+        if (spec.getFlinkConfiguration() != null) {
+            var deployConfig = 
Configuration.fromMap(spec.getFlinkConfiguration());
+            for (ConfigOption configOption : configOptions) {
+                deployConfig.getOptional(configOption).ifPresent(v -> 
conf.set(configOption, v));
+            }
+        }
+    }
+
     public Configuration getSessionJobConfig(
             FlinkDeployment deployment, FlinkSessionJobSpec sessionJobSpec) {
         Configuration sessionJobConfig = getObserveConfig(deployment);
diff --git 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/reconciler/deployment/AbstractJobReconciler.java
 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/reconciler/deployment/AbstractJobReconciler.java
index 4f850abe..11a9ac74 100644
--- 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/reconciler/deployment/AbstractJobReconciler.java
+++ 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/reconciler/deployment/AbstractJobReconciler.java
@@ -154,9 +154,6 @@ public abstract class AbstractJobReconciler<
             CR resource, Configuration deployConfig, Configuration 
observeConfig) {
         var status = resource.getStatus();
         var upgradeMode = resource.getSpec().getJob().getUpgradeMode();
-        var changedToLastStateWithoutHa =
-                
ReconciliationUtils.isUpgradeModeChangedToLastStateAndHADisabledPreviously(
-                        resource, observeConfig);
 
         if (upgradeMode == UpgradeMode.STATELESS) {
             LOG.info("Stateless job, ready for upgrade");
@@ -171,6 +168,9 @@ public abstract class AbstractJobReconciler<
 
         if (ReconciliationUtils.isJobRunning(status)) {
             LOG.info("Job is in running state, ready for upgrade with {}", 
upgradeMode);
+            var changedToLastStateWithoutHa =
+                    
ReconciliationUtils.isUpgradeModeChangedToLastStateAndHADisabledPreviously(
+                            resource, observeConfig);
             if (changedToLastStateWithoutHa) {
                 LOG.info(
                         "Using savepoint upgrade mode when switching to 
last-state without HA previously enabled");
diff --git 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/reconciler/deployment/ApplicationReconcilerTest.java
 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/reconciler/deployment/ApplicationReconcilerTest.java
index b8f5e9af..b102cdb1 100644
--- 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/reconciler/deployment/ApplicationReconcilerTest.java
+++ 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/reconciler/deployment/ApplicationReconcilerTest.java
@@ -189,78 +189,6 @@ public class ApplicationReconcilerTest {
         assertEquals("finished_sp", runningJobs.get(0).f0);
     }
 
-    @ParameterizedTest
-    @EnumSource(UpgradeMode.class)
-    public void testUpgradeBeforeReachingStableSpec(UpgradeMode upgradeMode) 
throws Exception {
-        flinkService.setHaDataAvailable(false);
-
-        final FlinkDeployment deployment = TestUtils.buildApplicationCluster();
-
-        reconciler.reconcile(deployment, context);
-        assertEquals(
-                JobManagerDeploymentStatus.DEPLOYING,
-                deployment.getStatus().getJobManagerDeploymentStatus());
-
-        // Ready for spec changes, the reconciliation should be performed
-        final String newImage = "new-image-1";
-        deployment.getSpec().getJob().setUpgradeMode(upgradeMode);
-        deployment.getSpec().setImage(newImage);
-        reconciler.reconcile(deployment, context);
-        if (!UpgradeMode.STATELESS.equals(upgradeMode)) {
-            
assertNull(deployment.getStatus().getReconciliationStatus().getLastReconciledSpec());
-            assertEquals(
-                    ReconciliationState.UPGRADING,
-                    
deployment.getStatus().getReconciliationStatus().getState());
-            reconciler.reconcile(deployment, context);
-        }
-        assertEquals(
-                newImage,
-                deployment
-                        .getStatus()
-                        .getReconciliationStatus()
-                        .deserializeLastReconciledSpec()
-                        .getImage());
-    }
-
-    @Test
-    public void testUpgradeModeChangeFromSavepointToLastState() throws 
Exception {
-        final String expectedSavepointPath = "savepoint_0";
-        final FlinkDeployment deployment = TestUtils.buildApplicationCluster();
-
-        reconciler.reconcile(deployment, context);
-        var runningJobs = flinkService.listJobs();
-        verifyAndSetRunningJobsToStatus(deployment, runningJobs);
-
-        // Suspend FlinkDeployment with savepoint upgrade mode
-        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.SAVEPOINT);
-        deployment.getSpec().getJob().setState(JobState.SUSPENDED);
-        deployment.getSpec().setImage("new-image-1");
-
-        reconciler.reconcile(deployment, context);
-        assertEquals(0, flinkService.getRunningCount());
-        assertEquals(
-                org.apache.flink.api.common.JobStatus.FINISHED.name(),
-                deployment.getStatus().getJobStatus().getState());
-
-        assertEquals(
-                expectedSavepointPath,
-                deployment
-                        .getStatus()
-                        .getJobStatus()
-                        .getSavepointInfo()
-                        .getLastSavepoint()
-                        .getLocation());
-
-        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.LAST_STATE);
-        deployment.getSpec().getJob().setState(JobState.RUNNING);
-        deployment.getSpec().setImage("new-image-2");
-
-        reconciler.reconcile(deployment, context);
-        runningJobs = flinkService.listJobs();
-        assertEquals(1, flinkService.getRunningCount());
-        assertEquals(expectedSavepointPath, runningJobs.get(0).f0);
-    }
-
     @Test
     public void triggerSavepoint() throws Exception {
         FlinkDeployment deployment = TestUtils.buildApplicationCluster();
@@ -358,84 +286,6 @@ public class ApplicationReconcilerTest {
         
assertTrue(SavepointUtils.savepointInProgress(spDeployment.getStatus().getJobStatus()));
     }
 
-    @Test
-    public void 
testUpgradeModeChangedToLastStateShouldTriggerSavepointWhileHADisabled()
-            throws Exception {
-        flinkService.setHaDataAvailable(false);
-
-        final FlinkDeployment deployment = TestUtils.buildApplicationCluster();
-        
deployment.getSpec().getFlinkConfiguration().remove(HighAvailabilityOptions.HA_MODE.key());
-
-        reconciler.reconcile(deployment, context);
-        assertEquals(
-                JobManagerDeploymentStatus.DEPLOYING,
-                deployment.getStatus().getJobManagerDeploymentStatus());
-
-        // Not ready for spec changes, the reconciliation is not performed
-        final String newImage = "new-image-1";
-        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.LAST_STATE);
-        deployment.getSpec().setImage(newImage);
-        reconciler.reconcile(deployment, context);
-        reconciler.reconcile(deployment, context);
-        assertNull(flinkService.listJobs().get(0).f0);
-        assertNotEquals(
-                newImage,
-                deployment
-                        .getStatus()
-                        .getReconciliationStatus()
-                        .deserializeLastReconciledSpec()
-                        .getImage());
-
-        // Ready for spec changes, the reconciliation should be performed
-        verifyAndSetRunningJobsToStatus(deployment, flinkService.listJobs());
-        reconciler.reconcile(deployment, context);
-        reconciler.reconcile(deployment, context);
-        assertEquals(
-                newImage,
-                deployment
-                        .getStatus()
-                        .getReconciliationStatus()
-                        .deserializeLastReconciledSpec()
-                        .getImage());
-        // Upgrade mode changes from stateless to last-state should trigger a 
savepoint
-        final String expectedSavepointPath = "savepoint_0";
-        var runningJobs = flinkService.listJobs();
-        assertEquals(expectedSavepointPath, runningJobs.get(0).f0);
-    }
-
-    @Test
-    public void 
testUpgradeModeChangedToLastStateShouldNotTriggerSavepointWhileHAEnabled()
-            throws Exception {
-        final FlinkDeployment deployment = TestUtils.buildApplicationCluster();
-
-        reconciler.reconcile(deployment, context);
-        assertNotEquals(
-                UpgradeMode.LAST_STATE,
-                deployment
-                        .getStatus()
-                        .getReconciliationStatus()
-                        .deserializeLastReconciledSpec()
-                        .getJob()
-                        .getUpgradeMode());
-
-        final String newImage = "new-image-1";
-        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.LAST_STATE);
-        deployment.getSpec().setImage(newImage);
-        verifyAndSetRunningJobsToStatus(deployment, flinkService.listJobs());
-        reconciler.reconcile(deployment, context);
-        reconciler.reconcile(deployment, context);
-        assertEquals(
-                newImage,
-                deployment
-                        .getStatus()
-                        .getReconciliationStatus()
-                        .deserializeLastReconciledSpec()
-                        .getImage());
-        // Upgrade mode changes from stateless to last-state while HA enabled 
previously should not
-        // trigger a savepoint
-        assertNull(flinkService.listJobs().get(0).f0);
-    }
-
     @Test
     public void triggerRestart() throws Exception {
         FlinkDeployment deployment = TestUtils.buildApplicationCluster();
diff --git 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/reconciler/deployment/ApplicationReconcilerUpgradeModeTest.java
 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/reconciler/deployment/ApplicationReconcilerUpgradeModeTest.java
new file mode 100644
index 00000000..5f79fc3e
--- /dev/null
+++ 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/reconciler/deployment/ApplicationReconcilerUpgradeModeTest.java
@@ -0,0 +1,457 @@
+/*
+ * 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.flink.kubernetes.operator.reconciler.deployment;
+
+import org.apache.flink.api.java.tuple.Tuple3;
+import org.apache.flink.configuration.CheckpointingOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.HighAvailabilityOptions;
+import org.apache.flink.kubernetes.operator.TestUtils;
+import org.apache.flink.kubernetes.operator.TestingFlinkService;
+import org.apache.flink.kubernetes.operator.TestingStatusRecorder;
+import org.apache.flink.kubernetes.operator.config.FlinkConfigManager;
+import org.apache.flink.kubernetes.operator.crd.FlinkDeployment;
+import org.apache.flink.kubernetes.operator.crd.spec.FlinkVersion;
+import org.apache.flink.kubernetes.operator.crd.spec.JobState;
+import org.apache.flink.kubernetes.operator.crd.spec.UpgradeMode;
+import org.apache.flink.kubernetes.operator.crd.status.FlinkDeploymentStatus;
+import 
org.apache.flink.kubernetes.operator.crd.status.JobManagerDeploymentStatus;
+import org.apache.flink.kubernetes.operator.crd.status.JobStatus;
+import org.apache.flink.kubernetes.operator.crd.status.ReconciliationState;
+import org.apache.flink.kubernetes.operator.crd.status.Savepoint;
+import org.apache.flink.kubernetes.operator.crd.status.SavepointTriggerType;
+import 
org.apache.flink.kubernetes.operator.exception.DeploymentFailedException;
+import org.apache.flink.kubernetes.operator.reconciler.ReconciliationUtils;
+import org.apache.flink.kubernetes.operator.utils.EventRecorder;
+import org.apache.flink.runtime.client.JobStatusMessage;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/** @link JobStatusObserver unit tests */
+@EnableKubernetesMockClient(crud = true)
+public class ApplicationReconcilerUpgradeModeTest {
+
+    private KubernetesClient kubernetesClient;
+    private final FlinkConfigManager configManager = new 
FlinkConfigManager(new Configuration());
+    private TestingFlinkService flinkService;
+    private ApplicationReconciler reconciler;
+    private Context<FlinkDeployment> context;
+
+    @BeforeEach
+    public void before() {
+        
kubernetesClient.resource(TestUtils.buildApplicationCluster()).createOrReplace();
+        var eventRecorder = new EventRecorder(kubernetesClient, (r, e) -> {});
+        var statusRecoder = new TestingStatusRecorder<FlinkDeployment, 
FlinkDeploymentStatus>();
+        flinkService = new TestingFlinkService(kubernetesClient);
+        context = flinkService.getContext();
+        reconciler =
+                new ApplicationReconciler(
+                        kubernetesClient,
+                        flinkService,
+                        configManager,
+                        eventRecorder,
+                        statusRecoder);
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromStatelessToStateless(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToStateless(flinkVersion, UpgradeMode.STATELESS);
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromSavepointToStateless(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToStateless(flinkVersion, UpgradeMode.SAVEPOINT);
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromLastStateToStateless(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToStateless(flinkVersion, UpgradeMode.LAST_STATE);
+    }
+
+    private void testUpgradeToStateless(FlinkVersion flinkVersion, UpgradeMode 
fromUpgradeMode)
+            throws Exception {
+        FlinkDeployment deployment = buildApplicationCluster(flinkVersion, 
fromUpgradeMode);
+
+        reconciler.reconcile(deployment, context);
+        var runningJobs = flinkService.listJobs();
+        verifyAndSetRunningJobsToStatus(deployment, runningJobs);
+
+        FlinkDeployment modifiedDeployment =
+                cloneDeploymentWithUpgradeMode(deployment, 
UpgradeMode.STATELESS);
+
+        reconciler.reconcile(modifiedDeployment, context);
+        assertEquals(0, flinkService.getRunningCount());
+
+        reconciler.reconcile(modifiedDeployment, context);
+        runningJobs = flinkService.listJobs();
+        assertEquals(1, flinkService.getRunningCount());
+        assertNull(runningJobs.get(0).f0);
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromStatelessToSavepoint(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToSavepoint(flinkVersion, UpgradeMode.STATELESS);
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromSavepointToSavepoint(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToSavepoint(flinkVersion, UpgradeMode.SAVEPOINT);
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromLastStateToSavepoint(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToSavepoint(flinkVersion, UpgradeMode.LAST_STATE);
+    }
+
+    private void testUpgradeToSavepoint(FlinkVersion flinkVersion, UpgradeMode 
fromUpgradeMode)
+            throws Exception {
+        FlinkDeployment deployment = buildApplicationCluster(flinkVersion, 
fromUpgradeMode);
+
+        reconciler.reconcile(deployment, context);
+        var runningJobs = flinkService.listJobs();
+        verifyAndSetRunningJobsToStatus(deployment, runningJobs);
+
+        FlinkDeployment modifiedDeployment =
+                cloneDeploymentWithUpgradeMode(deployment, 
UpgradeMode.SAVEPOINT);
+        modifiedDeployment
+                .getSpec()
+                .getFlinkConfiguration()
+                .put(CheckpointingOptions.SAVEPOINT_DIRECTORY.key(), 
"test-savepoint-dir");
+
+        reconciler.reconcile(modifiedDeployment, context);
+        assertEquals(0, flinkService.getRunningCount());
+
+        reconciler.reconcile(modifiedDeployment, context);
+        runningJobs = flinkService.listJobs();
+        assertEquals(1, flinkService.getRunningCount());
+        assertEquals("savepoint_0", runningJobs.get(0).f0);
+        assertEquals(
+                SavepointTriggerType.UPGRADE,
+                modifiedDeployment
+                        .getStatus()
+                        .getJobStatus()
+                        .getSavepointInfo()
+                        .getLastSavepoint()
+                        .getTriggerType());
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromStatelessToLastState(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToLastState(flinkVersion, UpgradeMode.STATELESS);
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromSavepointToLastState(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToLastState(flinkVersion, UpgradeMode.SAVEPOINT);
+    }
+
+    @ParameterizedTest
+    @EnumSource(FlinkVersion.class)
+    public void testUpgradeFromLastStateToLastState(FlinkVersion flinkVersion) 
throws Exception {
+        testUpgradeToLastState(flinkVersion, UpgradeMode.LAST_STATE);
+    }
+
+    private void testUpgradeToLastState(FlinkVersion flinkVersion, UpgradeMode 
fromUpgradeMode)
+            throws Exception {
+        FlinkDeployment deployment = buildApplicationCluster(flinkVersion, 
fromUpgradeMode);
+
+        reconciler.reconcile(deployment, context);
+        var runningJobs = flinkService.listJobs();
+        verifyAndSetRunningJobsToStatus(deployment, runningJobs);
+
+        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.LAST_STATE);
+        deployment.getSpec().setRestartNonce(100L);
+        deployment
+                .getStatus()
+                .getReconciliationStatus()
+                .setLastStableSpec(
+                        
deployment.getStatus().getReconciliationStatus().getLastReconciledSpec());
+        flinkService.setHaDataAvailable(false);
+        deployment.getStatus().getJobStatus().setState("RECONCILING");
+
+        Assertions.assertThrows(
+                DeploymentFailedException.class,
+                () -> {
+                    deployment
+                            .getStatus()
+                            
.setJobManagerDeploymentStatus(JobManagerDeploymentStatus.MISSING);
+                    reconciler.reconcile(deployment, context);
+                    fail();
+                });
+
+        Assertions.assertThrows(
+                DeploymentFailedException.class,
+                () -> {
+                    deployment
+                            .getStatus()
+                            
.setJobManagerDeploymentStatus(JobManagerDeploymentStatus.ERROR);
+                    reconciler.reconcile(deployment, context);
+                });
+
+        flinkService.clear();
+        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.LAST_STATE);
+        deployment.getSpec().setRestartNonce(200L);
+        flinkService.setHaDataAvailable(false);
+        deployment
+                .getStatus()
+                .getJobStatus()
+                .getSavepointInfo()
+                .setLastSavepoint(Savepoint.of("finished_sp", 
SavepointTriggerType.UPGRADE));
+        deployment.getStatus().getJobStatus().setState("FINISHED");
+        
deployment.getStatus().setJobManagerDeploymentStatus(JobManagerDeploymentStatus.READY);
+        deployment
+                .getSpec()
+                .getFlinkConfiguration()
+                .put(CheckpointingOptions.SAVEPOINT_DIRECTORY.key(), 
"test-savepoint-dir");
+        reconciler.reconcile(deployment, context);
+        reconciler.reconcile(deployment, context);
+
+        assertEquals(1, flinkService.getRunningCount());
+        assertEquals("finished_sp", runningJobs.get(0).f0);
+    }
+
+    private FlinkDeployment cloneDeploymentWithUpgradeMode(
+            FlinkDeployment deployment, UpgradeMode upgradeMode) {
+        FlinkDeployment result = ReconciliationUtils.clone(deployment);
+
+        result.getSpec().getJob().setUpgradeMode(upgradeMode);
+        result.getSpec().getFlinkConfiguration().put("new", "conf");
+
+        return result;
+    }
+
+    @ParameterizedTest
+    @EnumSource(UpgradeMode.class)
+    public void testUpgradeBeforeReachingStableSpec(UpgradeMode upgradeMode) 
throws Exception {
+        flinkService.setHaDataAvailable(false);
+
+        final FlinkDeployment deployment = TestUtils.buildApplicationCluster();
+
+        reconciler.reconcile(deployment, context);
+        assertEquals(
+                JobManagerDeploymentStatus.DEPLOYING,
+                deployment.getStatus().getJobManagerDeploymentStatus());
+
+        // Ready for spec changes, the reconciliation should be performed
+        final String newImage = "new-image-1";
+        deployment.getSpec().getJob().setUpgradeMode(upgradeMode);
+        deployment.getSpec().setImage(newImage);
+        reconciler.reconcile(deployment, context);
+        if (!UpgradeMode.STATELESS.equals(upgradeMode)) {
+            
assertNull(deployment.getStatus().getReconciliationStatus().getLastReconciledSpec());
+            assertEquals(
+                    ReconciliationState.UPGRADING,
+                    
deployment.getStatus().getReconciliationStatus().getState());
+            reconciler.reconcile(deployment, context);
+        }
+        assertEquals(
+                newImage,
+                deployment
+                        .getStatus()
+                        .getReconciliationStatus()
+                        .deserializeLastReconciledSpec()
+                        .getImage());
+    }
+
+    @Test
+    public void testUpgradeModeChangeFromSavepointToLastState() throws 
Exception {
+        final String expectedSavepointPath = "savepoint_0";
+        final FlinkDeployment deployment = TestUtils.buildApplicationCluster();
+
+        reconciler.reconcile(deployment, context);
+        var runningJobs = flinkService.listJobs();
+        verifyAndSetRunningJobsToStatus(deployment, runningJobs);
+
+        // Suspend FlinkDeployment with savepoint upgrade mode
+        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.SAVEPOINT);
+        deployment.getSpec().getJob().setState(JobState.SUSPENDED);
+        deployment.getSpec().setImage("new-image-1");
+
+        reconciler.reconcile(deployment, context);
+        assertEquals(0, flinkService.getRunningCount());
+        assertEquals(
+                org.apache.flink.api.common.JobStatus.FINISHED.name(),
+                deployment.getStatus().getJobStatus().getState());
+
+        assertEquals(
+                expectedSavepointPath,
+                deployment
+                        .getStatus()
+                        .getJobStatus()
+                        .getSavepointInfo()
+                        .getLastSavepoint()
+                        .getLocation());
+
+        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.LAST_STATE);
+        deployment.getSpec().getJob().setState(JobState.RUNNING);
+        deployment.getSpec().setImage("new-image-2");
+
+        reconciler.reconcile(deployment, context);
+        runningJobs = flinkService.listJobs();
+        assertEquals(1, flinkService.getRunningCount());
+        assertEquals(expectedSavepointPath, runningJobs.get(0).f0);
+    }
+
+    @Test
+    public void 
testUpgradeModeChangedToLastStateShouldTriggerSavepointWhileHADisabled()
+            throws Exception {
+        flinkService.setHaDataAvailable(false);
+
+        final FlinkDeployment deployment = TestUtils.buildApplicationCluster();
+        
deployment.getSpec().getFlinkConfiguration().remove(HighAvailabilityOptions.HA_MODE.key());
+
+        reconciler.reconcile(deployment, context);
+        assertEquals(
+                JobManagerDeploymentStatus.DEPLOYING,
+                deployment.getStatus().getJobManagerDeploymentStatus());
+
+        // Not ready for spec changes, the reconciliation is not performed
+        final String newImage = "new-image-1";
+        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.LAST_STATE);
+        deployment.getSpec().setImage(newImage);
+        reconciler.reconcile(deployment, context);
+        reconciler.reconcile(deployment, context);
+        assertNull(flinkService.listJobs().get(0).f0);
+        assertNotEquals(
+                newImage,
+                deployment
+                        .getStatus()
+                        .getReconciliationStatus()
+                        .deserializeLastReconciledSpec()
+                        .getImage());
+
+        // Ready for spec changes, the reconciliation should be performed
+        verifyAndSetRunningJobsToStatus(deployment, flinkService.listJobs());
+        reconciler.reconcile(deployment, context);
+        reconciler.reconcile(deployment, context);
+        assertEquals(
+                newImage,
+                deployment
+                        .getStatus()
+                        .getReconciliationStatus()
+                        .deserializeLastReconciledSpec()
+                        .getImage());
+        // Upgrade mode changes from stateless to last-state should trigger a 
savepoint
+        final String expectedSavepointPath = "savepoint_0";
+        var runningJobs = flinkService.listJobs();
+        assertEquals(expectedSavepointPath, runningJobs.get(0).f0);
+    }
+
+    @Test
+    public void 
testUpgradeModeChangedToLastStateShouldNotTriggerSavepointWhileHAEnabled()
+            throws Exception {
+        final FlinkDeployment deployment = TestUtils.buildApplicationCluster();
+
+        reconciler.reconcile(deployment, context);
+        assertNotEquals(
+                UpgradeMode.LAST_STATE,
+                deployment
+                        .getStatus()
+                        .getReconciliationStatus()
+                        .deserializeLastReconciledSpec()
+                        .getJob()
+                        .getUpgradeMode());
+
+        final String newImage = "new-image-1";
+        deployment.getSpec().getJob().setUpgradeMode(UpgradeMode.LAST_STATE);
+        deployment.getSpec().setImage(newImage);
+        verifyAndSetRunningJobsToStatus(deployment, flinkService.listJobs());
+        reconciler.reconcile(deployment, context);
+        reconciler.reconcile(deployment, context);
+        assertEquals(
+                newImage,
+                deployment
+                        .getStatus()
+                        .getReconciliationStatus()
+                        .deserializeLastReconciledSpec()
+                        .getImage());
+        // Upgrade mode changes from stateless to last-state while HA enabled 
previously should not
+        // trigger a savepoint
+        assertNull(flinkService.listJobs().get(0).f0);
+    }
+
+    public static FlinkDeployment buildApplicationCluster(
+            FlinkVersion flinkVersion, UpgradeMode upgradeMode) {
+        FlinkDeployment deployment = 
TestUtils.buildApplicationCluster(flinkVersion);
+        deployment.getSpec().getJob().setUpgradeMode(upgradeMode);
+        Map<String, String> conf = 
deployment.getSpec().getFlinkConfiguration();
+
+        switch (upgradeMode) {
+            case STATELESS:
+                conf.remove(HighAvailabilityOptions.HA_MODE.key());
+                conf.remove(HighAvailabilityOptions.HA_STORAGE_PATH.key());
+                conf.remove(CheckpointingOptions.SAVEPOINT_DIRECTORY.key());
+                conf.remove(CheckpointingOptions.CHECKPOINTS_DIRECTORY.key());
+                break;
+
+            case SAVEPOINT:
+                conf.remove(HighAvailabilityOptions.HA_MODE.key());
+                conf.remove(HighAvailabilityOptions.HA_STORAGE_PATH.key());
+                break;
+
+            case LAST_STATE:
+                conf.remove(CheckpointingOptions.SAVEPOINT_DIRECTORY.key());
+                break;
+
+            default:
+                throw new RuntimeException("Unsupported upgrade mode " + 
upgradeMode);
+        }
+
+        return deployment;
+    }
+
+    private void verifyAndSetRunningJobsToStatus(
+            FlinkDeployment deployment,
+            List<Tuple3<String, JobStatusMessage, Configuration>> runningJobs) 
{
+        assertEquals(1, runningJobs.size());
+        assertNull(runningJobs.get(0).f0);
+        deployment
+                .getStatus()
+                .setJobStatus(
+                        new JobStatus()
+                                .toBuilder()
+                                
.jobId(runningJobs.get(0).f1.getJobId().toHexString())
+                                .jobName(runningJobs.get(0).f1.getJobName())
+                                .state("RUNNING")
+                                .build());
+        
deployment.getStatus().setJobManagerDeploymentStatus(JobManagerDeploymentStatus.READY);
+    }
+}

Reply via email to