shashank created CAMEL-25614:
--------------------------------

             Summary: camel-kubernetes - with 
camel.vault.kubernetes.refreshEnabled=true the CamelContext is no longer 
reloaded on a secret (config map) update after the watch was closed once (410 
Gone); nothing is logged
                 Key: CAMEL-25614
                 URL: https://issues.apache.org/jira/browse/CAMEL-25614
             Project: Camel
          Issue Type: Bug
          Components: camel-kubernetes
            Reporter: shashank
            Assignee: shashank


With {{camel.vault.kubernetes.refreshEnabled=true}} 
({{camel.vault.kubernetescm.refreshEnabled=true}} for config maps) camel-main 
schedules {{SecretsReloadTriggerTask}} ({{ConfigmapsReloadTriggerTask}}) with 
{{PeriodTaskScheduler.scheduledTask}} ({{DefaultConfigurationConfigurer}} 867 / 
886 at main a7169d7c6ab6). That method submits the task once: 
{{DefaultPeriodTaskScheduler.TaskWrapper.onTimer}} (93-98)
{code:java}
// submit task only once as the task can potentially keep running (until camel 
is stopped)
if (running == null) {
    running = executorService.submit(task);
}
{code}
{{SecretsReloadTriggerTask.run}} (117-156; {{ConfigmapsReloadTriggerTask.run}} 
120-159 is the same) creates one watch, waits for {{onClose(WatcherException)}} 
and returns:
{code:java}
@Override
public void onClose(WatcherException e) {
    isWatchClosed.countDown();
}
...
// Wait till watch gets closed
isWatchClosed.await();
...
watch.close();
{code}
The Kubernetes client reconnects a watch by itself after transient errors, but 
it closes the watch for good and calls {{onClose(WatcherException)}} when the 
API server answers 410 Gone (the resource version of the watch is too old). How 
often that happens depends on the cluster: the watch asks for bookmarks, which 
keep its resource version fresh, so it is an occasional event and not a regular 
one. It is the same event that CAMEL-25273 fixed for the consumers of the 
component; the two reload tasks were not part of it, and for them one such 
close is enough.

After the first such close the task is finished and is never submitted again. 
The application keeps running with the secret values it has; a rotated secret 
is not followed by a reload, and nothing is logged, neither when the watch is 
closed nor later. A watch that cannot be created ({{watch(...)}} throws: API 
server not reachable at that moment, request refused) ends the task the same 
way; the exception stays in the {{Future}} of the submit, which nobody reads, 
so there is no log line either.

h3. Reproduction

New {{SecretsReloadTriggerTaskWatchClosedTest}} and 
{{ConfigmapsReloadTriggerTaskWatchClosedTest}} with the Kubernetes mock server 
that the module uses for its other tests. The task is scheduled the way 
camel-main does it. The first watch request is answered with a watch event 
{{ERROR}} / 410; the next one with a {{MODIFIED}} event for the configured 
secret (config map). In a second test one more watch request in between is 
answered with 403. A {{ContextReloadStrategy}} counts the reloads. On main 
these four tests fail after 20 seconds with {{the update of the secret after 
the watch was closed did not trigger a reload ==> expected: <true> but was: 
<false>}}. A third test per class stops the context while the task has a watch 
and expects the thread of the task to end; on main: {{the stopped task still 
waits for its watch to be closed ==> expected: <false> but was: <true>}} (on 
main a stopped task keeps its thread waiting for the watch).

h3. Proposed fix

{{run()}} of both tasks watches again until the task is stopped: one second 
after a closed watch, and with a delay that doubles up to 30 seconds while the 
watch cannot be created (one second at start, two seconds for the first failed 
attempt after a closed watch; the delays of {{KubernetesHelper.watchAgain}}, 
which CAMEL-25273 added for the consumers). Both cases are logged with one WARN 
line, without a stack trace (the stack trace of a watch that cannot be created 
is logged at DEBUG). {{doStop}} of the task ends the wait, so a stopped task 
does not keep its thread.

Updates made while nothing watches (between the close and the next watch) are 
still not seen; the task does not list the secrets after it watched again.

camel-kubernetes tests with the fix: 179 run, 0 failures, 6 skipped.

How it was found: a sweep of the reconnect / re-watch code of the components 
after CAMEL-25273, and a small TLA+ model of the task (submitted once, watch 
created or refused, 410, updates). For main TLC reports that the task ends 
after one 410 or one refused watch request (invariant "the task never ends 
while it is not stopped", traces of 4 and 3 states) and that "in the end the 
secrets are watched" does not hold; with the fix both hold for two 410 answers 
and two refused requests (144 states).

Affected: main (a7169d7c6ab6; tested). camel-4.23.x, camel-4.22.x, camel-4.18.x 
and camel-4.14.x have the same {{run()}} in both tasks and the same 
{{TaskWrapper.onTimer}} (read, not run).

Not looked at: the other vault refresh tasks that are scheduled with 
{{scheduledTask}}.

Duplicate check (2026-10-13): JIRA text "SecretsReloadTriggerTask" 
(CAMEL-24636, CAMEL-21924, CAMEL-21771, CAMEL-21770, CAMEL-21436: none about 
the watch), "kubernetes secret refresh watch", "kubernetes vault refresh watch 
closed" (CAMEL-24636 only); GitHub pull requests "SecretsReloadTriggerTask" 
(none), "kubernetes secret refresh watch" (nothing related).

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to