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 5bcc06f60 [FLINK-41050] Canary health checks revert canary metadata 
changes and restart a healthy operator (#1227)
5bcc06f60 is described below

commit 5bcc06f60b5bc5a0d510cf19e413a1d8cd9938a5
Author: Dennis-Mircea Ciupitu <[email protected]>
AuthorDate: Thu Oct 29 09:27:01 2026 +0200

    [FLINK-41050] Canary health checks revert canary metadata changes and 
restart a healthy operator (#1227)
---
 .../flink/kubernetes/operator/FlinkOperator.java   |   4 +
 .../operator/health/CanaryResourceManager.java     | 159 +++++++++---
 .../operator/health/CanaryResourceManagerTest.java | 276 +++++++++++++++++++++
 .../operator/health/HealthProbeTest.java           |  18 +-
 4 files changed, 411 insertions(+), 46 deletions(-)

diff --git 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/FlinkOperator.java
 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/FlinkOperator.java
index b37aefdfd..8a5af0e6b 100644
--- 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/FlinkOperator.java
+++ 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/FlinkOperator.java
@@ -85,6 +85,7 @@ public class FlinkOperator {
     private final FlinkConfigManager configManager;
     private final Set<FlinkResourceValidator> validators;
     @VisibleForTesting final Set<RegisteredController<?>> 
registeredControllers = new HashSet<>();
+    private final Set<CanaryResourceManager<?>> canaryResourceManagers = new 
HashSet<>();
     private final KubernetesOperatorMetricGroup metricGroup;
     private final Collection<FlinkResourceListener> listeners;
     private final OperatorHealthService operatorHealthService;
@@ -147,6 +148,7 @@ public class FlinkOperator {
                         controller.changeNamespaces(namespaces);
                     }
                 });
+        canaryResourceManagers.forEach(manager -> 
manager.retainNamespaces(namespaces));
     }
 
     private void overrideOperatorConfigs(ConfigurationServiceOverrider 
overrider) {
@@ -198,6 +200,7 @@ public class FlinkOperator {
         var observerFactory = new 
FlinkDeploymentObserverFactory(eventRecorder);
         var canaryResourceManager = new 
CanaryResourceManager<FlinkDeployment>(configManager);
         
HealthProbe.INSTANCE.registerCanaryResourceManager(canaryResourceManager);
+        canaryResourceManagers.add(canaryResourceManager);
 
         var controller =
                 new FlinkDeploymentController(
@@ -222,6 +225,7 @@ public class FlinkOperator {
         var observer = new FlinkSessionJobObserver(eventRecorder);
         var canaryResourceManager = new 
CanaryResourceManager<FlinkSessionJob>(configManager);
         
HealthProbe.INSTANCE.registerCanaryResourceManager(canaryResourceManager);
+        canaryResourceManagers.add(canaryResourceManager);
 
         var controller =
                 new FlinkSessionJobController(
diff --git 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/health/CanaryResourceManager.java
 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/health/CanaryResourceManager.java
index f141239e0..e3eccc0b1 100644
--- 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/health/CanaryResourceManager.java
+++ 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/health/CanaryResourceManager.java
@@ -21,21 +21,30 @@ import org.apache.flink.annotation.VisibleForTesting;
 import org.apache.flink.kubernetes.operator.api.AbstractFlinkResource;
 import org.apache.flink.kubernetes.operator.config.FlinkConfigManager;
 import 
org.apache.flink.kubernetes.operator.config.KubernetesOperatorConfigOptions;
-import org.apache.flink.kubernetes.operator.reconciler.ReconciliationUtils;
 
 import io.fabric8.kubernetes.api.model.HasMetadata;
 import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientException;
+import io.javaoperatorsdk.operator.api.reconciler.Constants;
 import io.javaoperatorsdk.operator.processing.event.ResourceID;
 import lombok.RequiredArgsConstructor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-
-/** Logic encapsulating canary tests. */
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tracks the canary resources of one kind, labeled with {@link 
#CANARY_LABEL}, to verify that the
+ * operator still picks up and reconciles spec changes. Each canary spec is 
periodically updated by
+ * bumping its restart nonce, and the canary is reported unhealthy when the 
update is not reconciled
+ * within the canary resource timeout, or when the update itself keeps 
failing. Canaries are never
+ * deployed, their reconciliation stops at this class.
+ */
 @RequiredArgsConstructor
 public class CanaryResourceManager<CR extends AbstractFlinkResource<?, ?>> {
 
@@ -43,6 +52,8 @@ public class CanaryResourceManager<CR extends 
AbstractFlinkResource<?, ?>> {
 
     public static final String CANARY_LABEL = "flink.apache.org/canary";
 
+    @VisibleForTesting static final int MAX_FAILED_UPDATES = 3;
+
     private final ConcurrentHashMap<ResourceID, CanaryResourceState> 
canaryResources =
             new ConcurrentHashMap<>();
 
@@ -55,28 +66,36 @@ public class CanaryResourceManager<CR extends 
AbstractFlinkResource<?, ?>> {
     }
 
     public boolean handleCanaryResourceReconciliation(CR resource, 
KubernetesClient client) {
+        var resourceId = ResourceID.fromResource(resource);
         if (!isCanaryResource(resource)) {
+            if (canaryResources.remove(resourceId) != null) {
+                LOG.info(
+                        "Reconciled {} without the canary label. Stopping 
health checks",
+                        resourceId);
+            }
             return false;
         }
 
-        var resourceId = ResourceID.fromResource(resource);
-
         LOG.info("Reconciling canary resource");
 
-        canaryResources.compute(
-                resourceId,
-                (id, previousState) -> {
-                    boolean firstReconcile = false;
-                    if (previousState == null) {
-                        firstReconcile = true;
-                        previousState = new CanaryResourceState();
-                    }
-                    previousState.onReconcile(resource);
-                    if (firstReconcile) {
-                        updateSpecAndScheduleHealthCheck(resourceId, 
previousState, client);
-                    }
-                    return previousState;
-                });
+        var firstReconcile = new AtomicBoolean(false);
+        var state =
+                canaryResources.compute(
+                        resourceId,
+                        (id, previousState) -> {
+                            if (previousState == null) {
+                                firstReconcile.set(true);
+                                previousState = new CanaryResourceState();
+                                previousState.bumpedGeneration =
+                                        resource.getMetadata().getGeneration();
+                            }
+                            previousState.onReconcile(resource);
+                            return previousState;
+                        });
+
+        if (firstReconcile.get()) {
+            updateSpecAndScheduleHealthCheck(resourceId, state, client);
+        }
 
         return true;
     }
@@ -92,6 +111,23 @@ public class CanaryResourceManager<CR extends 
AbstractFlinkResource<?, ?>> {
         return true;
     }
 
+    public void retainNamespaces(Set<String> namespaces) {
+        // The operator switched to watching all namespaces
+        if (namespaces.contains(Constants.WATCH_ALL_NAMESPACES)) {
+            return;
+        }
+
+        for (var resourceId : canaryResources.keySet()) {
+            var watched = 
resourceId.getNamespace().map(namespaces::contains).orElse(true);
+            if (!watched && canaryResources.remove(resourceId) != null) {
+                LOG.info(
+                        "Namespace of canary resource {} is no longer watched. 
Stopping health checks",
+                        resourceId);
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
     private void updateSpecAndScheduleHealthCheck(
             ResourceID resourceID, CanaryResourceState crs, KubernetesClient 
client) {
         var canaryTimeout =
@@ -99,18 +135,59 @@ public class CanaryResourceManager<CR extends 
AbstractFlinkResource<?, ?>> {
                         .getDefaultConfig()
                         
.get(KubernetesOperatorConfigOptions.CANARY_RESOURCE_TIMEOUT);
 
-        Long restartNonce = crs.resource.getSpec().getRestartNonce();
-        crs.resource.getSpec().setRestartNonce(restartNonce == null ? 1L : 
restartNonce + 1);
-        crs.previousGeneration = crs.resource.getMetadata().getGeneration();
+        Long newGeneration = null;
+        var deleted = false;
+        var labelRemoved = false;
+        try {
+            var canaryResource =
+                    client.resources((Class<CR>) crs.resource.getClass())
+                            
.inNamespace(resourceID.getNamespace().orElse(null))
+                            .withName(resourceID.getName());
+            var latest = canaryResource.get();
+            if (latest == null) {
+                deleted = true;
+            } else if (!isCanaryResource(latest)) {
+                labelRemoved = true;
+            } else {
+                var restartNonce = latest.getSpec().getRestartNonce();
+                latest.getSpec().setRestartNonce(restartNonce == null ? 1L : 
restartNonce + 1);
+                newGeneration = 
client.resource(latest).update().getMetadata().getGeneration();
+            }
+        } catch (KubernetesClientException e) {
+            if (e.getCode() == 404) {
+                deleted = true;
+            } else {
+                LOG.warn("Could not bump canary resource {}, will retry", 
resourceID, e);
+            }
+        } catch (Exception e) {
+            LOG.warn("Could not bump canary resource {}, will retry", 
resourceID, e);
+        }
 
-        LOG.info("Scheduling canary check for {} in {}s", resourceID, 
canaryTimeout.toSeconds());
+        if (deleted) {
+            LOG.info(
+                    "Canary resource {} was deleted without cleanup. Stopping 
health checks",
+                    resourceID);
+            canaryResources.remove(resourceID, crs);
+            return;
+        }
 
-        try {
-            client.resource(ReconciliationUtils.clone(crs.resource)).replace();
-        } catch (Throwable t) {
-            LOG.warn("Could not bump canary deployment, it may have been 
deleted", t);
+        if (labelRemoved) {
+            LOG.info(
+                    "Canary label removed from {}, skipping the spec bump. 
Stopping health checks",
+                    resourceID);
+            canaryResources.remove(resourceID, crs);
+            return;
         }
 
+        if (newGeneration == null) {
+            crs.failedBumps++;
+        } else {
+            crs.bumpedGeneration = newGeneration;
+            crs.failedBumps = 0;
+        }
+
+        LOG.info("Scheduling canary check for {} in {}s", resourceID, 
canaryTimeout.toSeconds());
+
         executorService.schedule(
                 () -> checkHealth(resourceID, client),
                 canaryTimeout.toMillis(),
@@ -125,20 +202,31 @@ public class CanaryResourceManager<CR extends 
AbstractFlinkResource<?, ?>> {
             return;
         }
 
-        // We did not reconcile since the last spec update within deadline
-        if (crs.canaryReconciledSinceUpdate()) {
+        if (crs.failedBumps >= MAX_FAILED_UPDATES) {
+            LOG.error(
+                    "Canary deployment {} spec could not be updated in {} 
consecutive attempts",
+                    resourceID,
+                    crs.failedBumps);
+            crs.isHealthy = false;
+        } else if (crs.canaryReconciledSinceUpdate() && crs.failedBumps > 0) {
+            LOG.warn(
+                    "Canary deployment {} healthy, verified against its last 
successful spec update. The last {} spec update attempts failed",
+                    resourceID,
+                    crs.failedBumps);
+            crs.isHealthy = true;
+        } else if (crs.canaryReconciledSinceUpdate()) {
             LOG.info("Canary deployment healthy");
             crs.isHealthy = true;
         } else {
+            // We did not reconcile since the last spec update within deadline
             LOG.error(
-                    "Canary deployment {} latest spec not reconciled. Expected 
generation larger than {}, received {}",
+                    "Canary deployment {} latest spec not reconciled. Expected 
generation at least {}, received {}",
                     resourceID,
-                    crs.previousGeneration,
+                    crs.bumpedGeneration,
                     crs.resource.getMetadata().getGeneration());
             crs.isHealthy = false;
         }
 
-        // Update spec and reschedule health check
         updateSpecAndScheduleHealthCheck(resourceID, crs, client);
     }
 
@@ -157,9 +245,8 @@ public class CanaryResourceManager<CR extends 
AbstractFlinkResource<?, ?>> {
 
     private class CanaryResourceState {
         CR resource;
-
-        long previousGeneration;
-
+        long bumpedGeneration;
+        int failedBumps;
         boolean isHealthy = true;
 
         void onReconcile(CR cr) {
@@ -167,7 +254,7 @@ public class CanaryResourceManager<CR extends 
AbstractFlinkResource<?, ?>> {
         }
 
         boolean canaryReconciledSinceUpdate() {
-            return resource.getMetadata().getGeneration() > previousGeneration;
+            return resource.getMetadata().getGeneration() >= bumpedGeneration;
         }
     }
 }
diff --git 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/health/CanaryResourceManagerTest.java
 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/health/CanaryResourceManagerTest.java
new file mode 100644
index 000000000..5d55f3ab6
--- /dev/null
+++ 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/health/CanaryResourceManagerTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.health;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.kubernetes.operator.TestUtils;
+import org.apache.flink.kubernetes.operator.api.FlinkDeployment;
+import org.apache.flink.kubernetes.operator.config.FlinkConfigManager;
+
+import io.fabric8.kubernetes.api.model.GenericKubernetesResourceBuilder;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import io.javaoperatorsdk.operator.api.reconciler.Constants;
+import io.javaoperatorsdk.operator.processing.event.ResourceID;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/** Tests for {@link CanaryResourceManager}. */
+@EnableKubernetesMockClient(crud = true)
+public class CanaryResourceManagerTest {
+
+    KubernetesClient client;
+    KubernetesMockServer mockServer;
+
+    private CanaryResourceManager<FlinkDeployment> canaryManager;
+    private ResourceID canaryId;
+
+    @BeforeAll
+    public static void setAutoTryKubeConfig() {
+        
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, 
"false");
+    }
+
+    @BeforeEach
+    public void setup() {
+        canaryManager = new CanaryResourceManager<>(new FlinkConfigManager(new 
Configuration()));
+        var canary = TestUtils.createCanaryDeployment();
+        client.resource(canary).create();
+        canaryId = ResourceID.fromResource(canary);
+    }
+
+    @Test
+    public void testHealthDependsOnReconcilingTheBump() {
+        reconcileLatest();
+        assertEquals(1, canaryManager.getNumberOfActiveCanaries());
+        assertTrue(canaryManager.allCanariesHealthy());
+
+        canaryManager.checkHealth(canaryId, client);
+        assertFalse(canaryManager.allCanariesHealthy());
+
+        reconcileLatest();
+        canaryManager.checkHealth(canaryId, client);
+        assertTrue(canaryManager.allCanariesHealthy());
+    }
+
+    @Test
+    public void testBumpPreservesConcurrentMetadataChanges() {
+        reconcileLatest();
+        var nonce = latest().getSpec().getRestartNonce();
+
+        client.resource(latest())
+                .edit(
+                        cr -> {
+                            var labels = new 
HashMap<>(cr.getMetadata().getLabels());
+                            labels.put("test", "label");
+                            cr.getMetadata().setLabels(labels);
+                            return cr;
+                        });
+
+        canaryManager.checkHealth(canaryId, client);
+
+        var canary = latest();
+        assertEquals("label", canary.getMetadata().getLabels().get("test"));
+        assertEquals(nonce + 1, canary.getSpec().getRestartNonce());
+    }
+
+    @Test
+    public void testCanaryWithoutSpec() throws InterruptedException {
+        var context =
+                new ResourceDefinitionContext.Builder()
+                        .withGroup("flink.apache.org")
+                        .withVersion("v1beta1")
+                        .withKind("FlinkDeployment")
+                        .withPlural("flinkdeployments")
+                        .withNamespaced(true)
+                        .build();
+        client.genericKubernetesResources(context)
+                .inNamespace("default")
+                .resource(
+                        new GenericKubernetesResourceBuilder()
+                                .withApiVersion("flink.apache.org/v1beta1")
+                                .withKind("FlinkDeployment")
+                                .withNewMetadata()
+                                .withName("no-spec")
+                                .withNamespace("default")
+                                
.withLabels(Map.of(CanaryResourceManager.CANARY_LABEL, "true"))
+                                .endMetadata()
+                                .build())
+                .create();
+        var noSpec =
+                
client.resources(FlinkDeployment.class).inNamespace("default").withName("no-spec");
+
+        
assertTrue(canaryManager.handleCanaryResourceReconciliation(noSpec.get(), 
client));
+
+        var lastRequest = mockServer.getLastRequest();
+        assertEquals("PUT", lastRequest.getMethod());
+        assertEquals(1L, noSpec.get().getSpec().getRestartNonce());
+    }
+
+    @Test
+    public void testFailedBumpsReportUnhealthyAfterMaxAttempts() {
+        reconcileLatest();
+        reconcileLatest();
+        try (var unreachableClient = unreachableClient()) {
+            canaryManager.checkHealth(canaryId, unreachableClient);
+            assertTrue(canaryManager.allCanariesHealthy());
+
+            for (int i = 1; i < CanaryResourceManager.MAX_FAILED_UPDATES; i++) 
{
+                canaryManager.checkHealth(canaryId, unreachableClient);
+                assertTrue(canaryManager.allCanariesHealthy());
+            }
+
+            canaryManager.checkHealth(canaryId, unreachableClient);
+            assertFalse(canaryManager.allCanariesHealthy());
+            assertEquals(1, canaryManager.getNumberOfActiveCanaries());
+        }
+
+        canaryManager.checkHealth(canaryId, client);
+        reconcileLatest();
+        canaryManager.checkHealth(canaryId, client);
+        assertTrue(canaryManager.allCanariesHealthy());
+    }
+
+    @Test
+    public void testLateReconcileRecoversWhileBumpsFail() {
+        reconcileLatest();
+        try (var unreachableClient = unreachableClient()) {
+            canaryManager.checkHealth(canaryId, unreachableClient);
+            assertFalse(canaryManager.allCanariesHealthy());
+
+            reconcileLatest();
+            canaryManager.checkHealth(canaryId, unreachableClient);
+            assertTrue(canaryManager.allCanariesHealthy());
+        }
+    }
+
+    @Test
+    public void testFailedFirstBump() {
+        try (var unreachableClient = unreachableClient()) {
+            assertTrue(
+                    canaryManager.handleCanaryResourceReconciliation(latest(), 
unreachableClient));
+            canaryManager.checkHealth(canaryId, unreachableClient);
+            assertTrue(canaryManager.allCanariesHealthy());
+        }
+    }
+
+    @Test
+    public void testDeletedWithoutCleanupStopsHealthChecks() {
+        reconcileLatest();
+
+        client.resource(latest()).delete();
+
+        canaryManager.checkHealth(canaryId, client);
+        assertEquals(0, canaryManager.getNumberOfActiveCanaries());
+        assertTrue(canaryManager.allCanariesHealthy());
+    }
+
+    @Test
+    public void testCanaryLabelRemoved() {
+        reconcileLatest();
+        var nonce = latest().getSpec().getRestartNonce();
+
+        client.resource(latest())
+                .edit(
+                        cr -> {
+                            
cr.getMetadata().getLabels().remove(CanaryResourceManager.CANARY_LABEL);
+                            return cr;
+                        });
+
+        canaryManager.checkHealth(canaryId, client);
+        assertEquals(0, canaryManager.getNumberOfActiveCanaries());
+        assertEquals(nonce, latest().getSpec().getRestartNonce());
+    }
+
+    @Test
+    public void testNamespaceNoLongerWatched() {
+        var otherCanary = TestUtils.createCanaryDeployment();
+        otherCanary.getMetadata().setNamespace("other");
+        client.resource(otherCanary).create();
+        reconcileLatest();
+        
assertTrue(canaryManager.handleCanaryResourceReconciliation(otherCanary, 
client));
+        assertEquals(2, canaryManager.getNumberOfActiveCanaries());
+
+        canaryManager.retainNamespaces(Set.of(Constants.WATCH_ALL_NAMESPACES));
+        assertEquals(2, canaryManager.getNumberOfActiveCanaries());
+
+        var nonce = latest().getSpec().getRestartNonce();
+        canaryManager.retainNamespaces(Set.of("other"));
+        assertEquals(1, canaryManager.getNumberOfActiveCanaries());
+
+        canaryManager.checkHealth(canaryId, client);
+        assertEquals(1, canaryManager.getNumberOfActiveCanaries());
+        assertEquals(nonce, latest().getSpec().getRestartNonce());
+        assertTrue(canaryManager.allCanariesHealthy());
+    }
+
+    @Test
+    public void testReconcilingFormerCanaryStopsTracking() {
+        reconcileLatest();
+        assertEquals(1, canaryManager.getNumberOfActiveCanaries());
+
+        var formerCanary = latest();
+        
formerCanary.getMetadata().getLabels().remove(CanaryResourceManager.CANARY_LABEL);
+        
assertFalse(canaryManager.handleCanaryResourceReconciliation(formerCanary, 
client));
+        assertEquals(0, canaryManager.getNumberOfActiveCanaries());
+    }
+
+    @Test
+    public void testDeletion() {
+        reconcileLatest();
+        assertTrue(canaryManager.handleCanaryResourceDeletion(latest()));
+        assertEquals(0, canaryManager.getNumberOfActiveCanaries());
+
+        canaryManager.checkHealth(canaryId, client);
+        assertEquals(0, canaryManager.getNumberOfActiveCanaries());
+    }
+
+    private void reconcileLatest() {
+        assertTrue(canaryManager.handleCanaryResourceReconciliation(latest(), 
client));
+    }
+
+    private static KubernetesClient unreachableClient() {
+        return new KubernetesClientBuilder()
+                .withConfig(
+                        new ConfigBuilder()
+                                .withMasterUrl("http://localhost:1";)
+                                .withRequestRetryBackoffLimit(0)
+                                .build())
+                .build();
+    }
+
+    private FlinkDeployment latest() {
+        return client.resources(FlinkDeployment.class)
+                .inNamespace(canaryId.getNamespace().orElseThrow())
+                .withName(canaryId.getName())
+                .get();
+    }
+}
diff --git 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/health/HealthProbeTest.java
 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/health/HealthProbeTest.java
index 6ce4f16e2..654476b1b 100644
--- 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/health/HealthProbeTest.java
+++ 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/health/HealthProbeTest.java
@@ -23,7 +23,6 @@ import org.apache.flink.kubernetes.operator.TestUtils;
 import org.apache.flink.kubernetes.operator.api.FlinkDeployment;
 import org.apache.flink.kubernetes.operator.config.FlinkConfigManager;
 import 
org.apache.flink.kubernetes.operator.config.KubernetesOperatorConfigOptions;
-import org.apache.flink.kubernetes.operator.reconciler.ReconciliationUtils;
 import org.apache.flink.util.NetUtils;
 
 import io.fabric8.kubernetes.client.Config;
@@ -188,25 +187,24 @@ public class HealthProbeTest {
         // No canary resources
         assertTrue(HealthProbe.INSTANCE.isHealthy());
 
-        var canary = TestUtils.createCanaryDeployment();
-        
canaryManager.handleCanaryResourceReconciliation(ReconciliationUtils.clone(canary),
 client);
+        var canary = 
client.resource(TestUtils.createCanaryDeployment()).create();
+        var canaryId = ResourceID.fromResource(canary);
+        
canaryManager.handleCanaryResourceReconciliation(client.resource(canary).get(), 
client);
 
         // Canary resource healthy before health check
         assertTrue(HealthProbe.INSTANCE.isHealthy());
 
         // No reconciliation before health check
-        canaryManager.checkHealth(ResourceID.fromResource(canary), client);
+        canaryManager.checkHealth(canaryId, client);
         assertFalse(HealthProbe.INSTANCE.isHealthy());
 
         // Healthy again
-        canary.getMetadata().setGeneration(2L);
-        
canaryManager.handleCanaryResourceReconciliation(ReconciliationUtils.clone(canary),
 client);
-        canaryManager.checkHealth(ResourceID.fromResource(canary), client);
+        
canaryManager.handleCanaryResourceReconciliation(client.resource(canary).get(), 
client);
+        canaryManager.checkHealth(canaryId, client);
         assertTrue(HealthProbe.INSTANCE.isHealthy());
 
-        canary.getMetadata().setGeneration(3L);
-        
canaryManager.handleCanaryResourceReconciliation(ReconciliationUtils.clone(canary),
 client);
-        canaryManager.checkHealth(ResourceID.fromResource(canary), client);
+        
canaryManager.handleCanaryResourceReconciliation(client.resource(canary).get(), 
client);
+        canaryManager.checkHealth(canaryId, client);
         assertTrue(HealthProbe.INSTANCE.isHealthy());
     }
 

Reply via email to