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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 688db726024e CAMEL-25857: camel-kubernetes - watch consumers try again 
when their first watch cannot be created (#28380)
688db726024e is described below

commit 688db726024ed267d7a437521a8058bfbc52b5f1
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 24 19:13:41 2026 +0530

    CAMEL-25857: camel-kubernetes - watch consumers try again when their first 
watch cannot be created (#28380)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../component/kubernetes/KubernetesHelper.java     | 32 +++++++-
 .../config_maps/KubernetesConfigMapsConsumer.java  |  2 +-
 .../KubernetesCustomResourcesConsumer.java         |  2 +-
 .../deployments/KubernetesDeploymentsConsumer.java |  2 +-
 .../events/KubernetesEventsConsumer.java           |  2 +-
 .../kubernetes/hpa/KubernetesHPAConsumer.java      |  2 +-
 .../namespaces/KubernetesNamespacesConsumer.java   |  2 +-
 .../kubernetes/nodes/KubernetesNodesConsumer.java  |  2 +-
 .../kubernetes/pods/KubernetesPodsConsumer.java    |  2 +-
 .../KubernetesReplicationControllersConsumer.java  |  2 +-
 .../services/KubernetesServicesConsumer.java       |  2 +-
 .../OpenshiftDeploymentConfigsConsumer.java        |  2 +-
 .../KubernetesPodsConsumerFirstWatchFailsTest.java | 96 ++++++++++++++++++++++
 13 files changed, 138 insertions(+), 12 deletions(-)

diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/KubernetesHelper.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/KubernetesHelper.java
index c647781cfb2b..db7e3af04f26 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/KubernetesHelper.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/KubernetesHelper.java
@@ -104,6 +104,35 @@ public final class KubernetesHelper {
      */
     static final long WATCH_AGAIN_MAX_DELAY_MILLIS = 30000;
 
+    /**
+     * Creates the first watch of a consumer on its executor. If it fails (the 
API server cannot be reached, answers 403
+     * because the RBAC rules of the service account are not there yet, ...), 
it is tried again as in
+     * {@link #watchAgain(ServiceSupport, ExecutorService, Runnable)}, until 
it succeeds or the consumer is stopped.
+     *
+     * @param consumer the consumer of the watch
+     * @param executor the executor of the consumer
+     * @param task     the task that creates the watch of the consumer
+     */
+    public static void watch(ServiceSupport consumer, ExecutorService 
executor, Runnable task) {
+        executor.submit(() -> {
+            if (!consumer.isRunAllowed()) {
+                return;
+            }
+            try {
+                task.run();
+            } catch (Exception e) {
+                if (Thread.currentThread().isInterrupted() || 
!consumer.isRunAllowed()) {
+                    LOG.debug("Failed to watch for {} as it is stopping", 
consumer, e);
+                    return;
+                }
+                // tried again until it succeeds: one line per attempt, the 
stack trace at DEBUG
+                LOG.warn("Failed to watch for {}, trying again in {} ms: {}", 
consumer, WATCH_AGAIN_DELAY_MILLIS, e.toString());
+                LOG.debug("Failed to watch for {}", consumer, e);
+                watchAgain(consumer, executor, task, WATCH_AGAIN_DELAY_MILLIS);
+            }
+        });
+    }
+
     /**
      * Watches again when the Kubernetes client closed the watch of a consumer 
with an error. The client reconnects a
      * watch by itself after transient errors, and only closes it with an 
exception when it gives up: when the API
@@ -149,7 +178,8 @@ public final class KubernetesHelper {
                     }
                     long nextDelayMillis = Math.min(delayMillis * 2, 
WATCH_AGAIN_MAX_DELAY_MILLIS);
                     LOG.warn("Failed to watch again for {}, trying again in {} 
ms: {}", consumer, nextDelayMillis,
-                            e.getMessage(), e);
+                            e.toString());
+                    LOG.debug("Failed to watch again for {}", consumer, e);
                     watchAgain(consumer, executor, task, nextDelayMillis);
                 }
             });
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/config_maps/KubernetesConfigMapsConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/config_maps/KubernetesConfigMapsConsumer.java
index 6fd6452f461c..155ead5d3604 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/config_maps/KubernetesConfigMapsConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/config_maps/KubernetesConfigMapsConsumer.java
@@ -60,7 +60,7 @@ public class KubernetesConfigMapsConsumer extends 
DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         configMapWatcher = new ConfigMapsConsumerTask();
-        executor.submit(configMapWatcher);
+        KubernetesHelper.watch(this, executor, configMapWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/customresources/KubernetesCustomResourcesConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/customresources/KubernetesCustomResourcesConsumer.java
index a429a2a12c4e..726b1a2fb113 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/customresources/KubernetesCustomResourcesConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/customresources/KubernetesCustomResourcesConsumer.java
@@ -59,7 +59,7 @@ public class KubernetesCustomResourcesConsumer extends 
DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         customResourcesWatcher = new CustomResourcesConsumerTask();
-        executor.submit(customResourcesWatcher);
+        KubernetesHelper.watch(this, executor, customResourcesWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/deployments/KubernetesDeploymentsConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/deployments/KubernetesDeploymentsConsumer.java
index 8a5ab50196df..1bde5a4ae972 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/deployments/KubernetesDeploymentsConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/deployments/KubernetesDeploymentsConsumer.java
@@ -60,7 +60,7 @@ public class KubernetesDeploymentsConsumer extends 
DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         deploymentsWatcher = new DeploymentsConsumerTask();
-        executor.submit(deploymentsWatcher);
+        KubernetesHelper.watch(this, executor, deploymentsWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/events/KubernetesEventsConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/events/KubernetesEventsConsumer.java
index 44e6474eed92..75016bac50dd 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/events/KubernetesEventsConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/events/KubernetesEventsConsumer.java
@@ -60,7 +60,7 @@ public class KubernetesEventsConsumer extends DefaultConsumer 
{
         executor = getEndpoint().createExecutor(this);
 
         eventWatcher = new EventsConsumerTask();
-        executor.submit(eventWatcher);
+        KubernetesHelper.watch(this, executor, eventWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/hpa/KubernetesHPAConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/hpa/KubernetesHPAConsumer.java
index e6b069f2e46b..9460f4ff83cc 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/hpa/KubernetesHPAConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/hpa/KubernetesHPAConsumer.java
@@ -60,7 +60,7 @@ public class KubernetesHPAConsumer extends DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         hpasWatcher = new HpaConsumerTask();
-        executor.submit(hpasWatcher);
+        KubernetesHelper.watch(this, executor, hpasWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/namespaces/KubernetesNamespacesConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/namespaces/KubernetesNamespacesConsumer.java
index 300380459b72..e63153b182f6 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/namespaces/KubernetesNamespacesConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/namespaces/KubernetesNamespacesConsumer.java
@@ -59,7 +59,7 @@ public class KubernetesNamespacesConsumer extends 
DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         nsWatcher = new NamespacesConsumerTask();
-        executor.submit(nsWatcher);
+        KubernetesHelper.watch(this, executor, nsWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/nodes/KubernetesNodesConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/nodes/KubernetesNodesConsumer.java
index e75b8b366c78..9fa8a3751967 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/nodes/KubernetesNodesConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/nodes/KubernetesNodesConsumer.java
@@ -59,7 +59,7 @@ public class KubernetesNodesConsumer extends DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         nodesWatcher = new NodesConsumerTask();
-        executor.submit(nodesWatcher);
+        KubernetesHelper.watch(this, executor, nodesWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/pods/KubernetesPodsConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/pods/KubernetesPodsConsumer.java
index 2754f18dd0e9..6854b435ac0f 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/pods/KubernetesPodsConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/pods/KubernetesPodsConsumer.java
@@ -60,7 +60,7 @@ public class KubernetesPodsConsumer extends DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         podsWatcher = new PodsConsumerTask();
-        executor.submit(podsWatcher);
+        KubernetesHelper.watch(this, executor, podsWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/replication_controllers/KubernetesReplicationControllersConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/replication_controllers/KubernetesReplicationControllersConsumer.java
index 1549971544f0..fd40601606e7 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/replication_controllers/KubernetesReplicationControllersConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/replication_controllers/KubernetesReplicationControllersConsumer.java
@@ -60,7 +60,7 @@ public class KubernetesReplicationControllersConsumer extends 
DefaultConsumer {
 
         executor = getEndpoint().createExecutor(this);
         rcWatcher = new ReplicationControllersConsumerTask();
-        executor.submit(rcWatcher);
+        KubernetesHelper.watch(this, executor, rcWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/services/KubernetesServicesConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/services/KubernetesServicesConsumer.java
index 4a088ec31f53..9ebce834eb02 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/services/KubernetesServicesConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/kubernetes/services/KubernetesServicesConsumer.java
@@ -60,7 +60,7 @@ public class KubernetesServicesConsumer extends 
DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         servicesWatcher = new ServicesConsumerTask();
-        executor.submit(servicesWatcher);
+        KubernetesHelper.watch(this, executor, servicesWatcher);
 
     }
 
diff --git 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/openshift/deploymentconfigs/OpenshiftDeploymentConfigsConsumer.java
 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/openshift/deploymentconfigs/OpenshiftDeploymentConfigsConsumer.java
index 6ac0f8fc9a53..589f2665a7f1 100644
--- 
a/components/camel-kubernetes/src/main/java/org/apache/camel/component/openshift/deploymentconfigs/OpenshiftDeploymentConfigsConsumer.java
+++ 
b/components/camel-kubernetes/src/main/java/org/apache/camel/component/openshift/deploymentconfigs/OpenshiftDeploymentConfigsConsumer.java
@@ -61,7 +61,7 @@ public class OpenshiftDeploymentConfigsConsumer extends 
DefaultConsumer {
         executor = getEndpoint().createExecutor(this);
 
         deploymentsWatcher = new DeploymentsConfigConsumerTask();
-        executor.submit(deploymentsWatcher);
+        KubernetesHelper.watch(this, executor, deploymentsWatcher);
     }
 
     @Override
diff --git 
a/components/camel-kubernetes/src/test/java/org/apache/camel/component/kubernetes/consumer/KubernetesPodsConsumerFirstWatchFailsTest.java
 
b/components/camel-kubernetes/src/test/java/org/apache/camel/component/kubernetes/consumer/KubernetesPodsConsumerFirstWatchFailsTest.java
new file mode 100644
index 000000000000..2997047f507e
--- /dev/null
+++ 
b/components/camel-kubernetes/src/test/java/org/apache/camel/component/kubernetes/consumer/KubernetesPodsConsumerFirstWatchFailsTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.camel.component.kubernetes.consumer;
+
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodBuilder;
+import io.fabric8.kubernetes.api.model.StatusBuilder;
+import io.fabric8.kubernetes.api.model.WatchEvent;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.NamespacedKubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.kubernetes.KubernetesTestSupport;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * When the first watch of a consumer cannot be created (the API server cannot 
be reached, or answers 403 because the
+ * RBAC rules of the service account are not there yet), the consumer must try 
again, as it does when the client closes
+ * a watch with an error.
+ */
+@EnableKubernetesMockClient
+class KubernetesPodsConsumerFirstWatchFailsTest extends KubernetesTestSupport {
+
+    private static final String WATCH_PATH = 
"/api/v1/namespaces/test/pods?allowWatchBookmarks=true&watch=true";
+
+    KubernetesMockServer server;
+    NamespacedKubernetesClient client;
+
+    @BindToRegistry("kubernetesClient")
+    public KubernetesClient getClient() {
+        return client;
+    }
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    void testWatchAgainWhenTheFirstWatchFails() throws Exception {
+        // the first watch fails
+        server.expect().withPath(WATCH_PATH)
+                .andReturn(403, new 
StatusBuilder().withCode(403).withReason("Forbidden").withMessage("forbidden").build())
+                .once();
+        server.expect().withPath(WATCH_PATH)
+                .andUpgradeToWebSocket()
+                .open()
+                .waitFor(10)
+                .andEmit(new WatchEvent(
+                        new 
PodBuilder().withNewMetadata().withName("pod1").withNamespace("test")
+                                
.withResourceVersion("2").endMetadata().build(),
+                        "ADDED"))
+                .done()
+                .once();
+
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(1);
+        mock.expectedMessagesMatches(e -> "pod1".equals(
+                e.getMessage().getBody(Pod.class).getMetadata().getName()));
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("kubernetes-pods://kubernetes?kubernetesClient=#kubernetesClient&namespace=test").routeId("pods")
+                        .to("mock:result");
+            }
+        });
+        context.start();
+
+        // watched again after 1 second
+        mock.setResultWaitTime(10000);
+        mock.assertIsSatisfied();
+        assertEquals(2, server.getRequestCount());
+        
assertTrue(context.getRouteController().getRouteStatus("pods").isStarted());
+    }
+}

Reply via email to