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 dc73c347e3c0 CAMEL-26789: camel-chaos - an experiment fails when its 
rules lost messages at the dead letter channel (#29512)
dc73c347e3c0 is described below

commit dc73c347e3c0a836b9736019654856a3c31ad447
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 23:50:36 2026 +0100

    CAMEL-26789: camel-chaos - an experiment fails when its rules lost messages 
at the dead letter channel (#29512)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/docs/chaos.adoc       |  5 +++-
 components/camel-chaos/src/main/docs/chaos.adoc    |  5 +++-
 .../camel/component/chaos/ChaosExperiment.java     | 27 ++++++++++++++++++----
 .../camel/component/chaos/ChaosExperimentTest.java | 26 +++++++++++++++++++++
 .../dsl/jbang/core/commands/process/ListChaos.java |  5 ++++
 .../jbang/core/commands/process/ListChaosTest.java |  5 +++-
 6 files changed, 66 insertions(+), 7 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
index 66d44391258b..6713e09b3b2d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
@@ -387,7 +387,10 @@ runs.
 When it ends, camel-chaos reads what came of it and then removes its rules: 
per route the difference of the counts, the
 faults of each rule, the state of each expectation, and a verdict:
 
-* `FAIL`: an expectation is unmet.
+* `FAIL`: an expectation is unmet, or a rule of the experiment failed or 
dropped the send of a dead letter channel.
+The dead letter channel then handles the message without delivering it, so no 
expectation of a route sees it: the
+experiment counts it as lost (`lostAtDeadLetterChannel`, and per rule 
`lostAtDeadLetterChannelPerRule`), as the
+report does.
 * `PASS`: all expectations are met.
 * `PENDING`: it is still open and an expectation is not decided yet.
 * `INCONCLUSIVE`: it ended before an expectation was decided, so the messages 
may still have come.
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc 
b/components/camel-chaos/src/main/docs/chaos.adoc
index 66d44391258b..6713e09b3b2d 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -387,7 +387,10 @@ runs.
 When it ends, camel-chaos reads what came of it and then removes its rules: 
per route the difference of the counts, the
 faults of each rule, the state of each expectation, and a verdict:
 
-* `FAIL`: an expectation is unmet.
+* `FAIL`: an expectation is unmet, or a rule of the experiment failed or 
dropped the send of a dead letter channel.
+The dead letter channel then handles the message without delivering it, so no 
expectation of a route sees it: the
+experiment counts it as lost (`lostAtDeadLetterChannel`, and per rule 
`lostAtDeadLetterChannelPerRule`), as the
+report does.
 * `PASS`: all expectations are met.
 * `PENDING`: it is still open and an expectation is not decided yet.
 * `INCONCLUSIVE`: it ended before an expectation was decided, so the messages 
may still have come.
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExperiment.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExperiment.java
index fe2a5bd5c87b..33f6d676dc53 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExperiment.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExperiment.java
@@ -33,9 +33,11 @@ import org.apache.camel.util.json.JsonObject;
  * the difference of the counts, the faults of each rule and the states of the 
expectations are kept in the journal,
  * with a verdict.
  * <p/>
- * The verdict is {@code FAIL} when an expectation is unmet and {@code PASS} 
when all are met. While it runs and an
- * expectation is not decided it is {@code PENDING}, and an experiment that 
ended before an expectation was decided is
- * {@code INCONCLUSIVE}: the messages may still have come. The manager guards 
it with its lock.
+ * The verdict is {@code FAIL} when an expectation is unmet, or a rule of it 
failed or dropped the send of a dead letter
+ * channel, which then handled the message without delivering it (lost, as the 
report counts it, CAMEL-26789), and
+ * {@code PASS} when all are met. While it runs and an expectation is not 
decided it is {@code PENDING}, and an
+ * experiment that ended before an expectation was decided is {@code 
INCONCLUSIVE}: the messages may still have come.
+ * The manager guards it with its lock.
  */
 final class ChaosExperiment {
 
@@ -135,6 +137,9 @@ final class ChaosExperiment {
         jo.put("rules", new JsonArray(rules));
         boolean unmet = false;
         boolean undecided = false;
+        // the messages its rules kept from a dead letter channel: handled 
there, so no expectation of a route sees them
+        long lost = 0;
+        JsonObject lostAtDlq = new JsonObject();
         JsonArray texts = new JsonArray();
         JsonObject faults = new JsonObject();
         JsonArray expectations = new JsonArray();
@@ -149,6 +154,11 @@ final class ChaosExperiment {
                 fired += r.firedAt(hook);
             }
             faults.put(id, fired);
+            long kept = r.lostAtDlqCount();
+            if (kept > 0) {
+                lostAtDlq.put(id, kept);
+                lost += kept;
+            }
             Expectation e = r.expectation;
             if (e != null) {
                 Expectation.Reading reading = e.read(now);
@@ -168,7 +178,11 @@ final class ChaosExperiment {
         jo.put("ruleTexts", texts);
         jo.put("faults", faults);
         jo.put("expectations", expectations);
-        jo.put("verdict", unmet ? FAIL : !undecided ? PASS : ended ? 
INCONCLUSIVE : PENDING);
+        if (lost > 0) {
+            jo.put("lostAtDeadLetterChannel", lost);
+            jo.put("lostAtDeadLetterChannelPerRule", lostAtDlq);
+        }
+        jo.put("verdict", unmet || lost > 0 ? FAIL : !undecided ? PASS : ended 
? INCONCLUSIVE : PENDING);
         JsonArray routes = new JsonArray();
         counts.forEach((route, c) -> {
             long[] now0 = read(c);
@@ -225,6 +239,11 @@ final class ChaosExperiment {
             sb.append("\n        expect 
").append(x.getString("expect")).append(": ").append(x.getString("state"))
                     .append(", ").append(x.getString("value"));
         }
+        long lost = jo.getLongOrDefault("lostAtDeadLetterChannel", 0);
+        if (lost > 0) {
+            sb.append("\n        lost at the dead letter channel: 
").append(lost)
+                    .append(lost == 1 ? " message" : " messages").append(", 
handled there without being delivered");
+        }
         StringJoiner sj = new StringJoiner(", ");
         for (Object o : jo.getCollection("routes")) {
             JsonObject r = (JsonObject) o;
diff --git 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosExperimentTest.java
 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosExperimentTest.java
index 7b58a415864f..aa4ff153c363 100644
--- 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosExperimentTest.java
+++ 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosExperimentTest.java
@@ -52,6 +52,11 @@ class ChaosExperimentTest extends ChaosTestSupport {
             @Override
             public void configure() {
                 from("direct:orders").routeId("orders").to("mock:shipped");
+                // a dead letter channel that parks what could not be charged
+                from("direct:billing").routeId("billing")
+                        .errorHandler(deadLetterChannel("direct:parked"))
+                        .to("mock:charged");
+                from("direct:parked").routeId("parked").to("mock:parked");
             }
         };
     }
@@ -135,6 +140,27 @@ class ChaosExperimentTest extends ChaosTestSupport {
                 .containsExactly("r1", "r2");
     }
 
+    @Test
+    void messagesLostAtTheDeadLetterChannelFailTheExperiment() {
+        chaos().startExploration("10m", null);
+        // the service is down and the dead letter channel fails too: the dead 
letter channel handles each message it
+        // could not deliver, so billing does not fail, and only the count of 
the rule sees the loss (CAMEL-26789)
+        chaos().beginExperiment("nothing is lost when billing and parking 
fail",
+                "endpoint=mock:charged error expect=billing:failed<=0 
within=1m; endpoint=direct:parked error at=dlq");
+        for (int i = 0; i < 3; i++) {
+            template.send("direct:billing", e -> 
e.getMessage().setBody("bill"));
+        }
+
+        Map<String, Object> ended = chaos().endExperiment(null);
+
+        assertThat(ended).containsEntry("verdict", 
"FAIL").containsEntry("lostAtDeadLetterChannel", 3L);
+        assertThat(((JsonObject) 
ended.get("lostAtDeadLetterChannelPerRule")).getLong("r2")).isEqualTo(3);
+        // the expectation alone sees nothing wrong: nothing failed in billing
+        assertThat(first(new JsonObject(ended), 
"expectations").getLong("actual")).isZero();
+        assertThat(chaos().report().toText()).contains("1 FAIL  nothing is 
lost when billing and parking fail",
+                "lost at the dead letter channel: 3 messages, handled there 
without being delivered");
+    }
+
     @Test
     void oneAtATimeWithAnExpectationAndInAnExploration() {
         String rule = "endpoint=mock:shipped error expect=orders:failed<=0 
within=1m";
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
index ed5f4bc11332..4d26bf1f35c1 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
@@ -389,6 +389,11 @@ public class ListChaos extends ProcessWatchCommand {
                           + x.getString("value"));
             }
         }
+        long lost = experiment.getLongOrDefault("lostAtDeadLetterChannel", 0);
+        if (lost > 0) {
+            lines.add(more + "lost at the dead letter channel: " + lost + 
(lost == 1 ? " message" : " messages")
+                      + ", handled there without being delivered");
+        }
         List<Object> routes = experiment.getCollection("routes");
         if (routes != null && !routes.isEmpty()) {
             lines.add(more + "routes: " + routeCounts(routes));
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaosTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaosTest.java
index 6763a6dfd9f9..6c06f3e46c9c 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaosTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaosTest.java
@@ -220,6 +220,7 @@ class ListChaosTest extends ProcessCommandTestSupport {
         during.put("failed", 9L);
         experiment.put("routes", new JsonArray(List.of(during)));
         experiment.put("note", "orders has no error handler");
+        experiment.put("lostAtDeadLetterChannel", 58L);
         exploration.put("experiments", new JsonArray(List.of(experiment)));
         ((JsonObject) chaos.get("report")).put("exploration", exploration);
         writeStatus(chaos);
@@ -230,7 +231,9 @@ class ListChaosTest extends ProcessCommandTestSupport {
         assertThat(out).contains("exploration: running, 6m12s left of 10m0s, 
focus: orders",
                 "rules tried: r1, r2", "baseline (before the first rule): 
orders 30 completed, 0 failed",
                 "experiments:", "1 FAIL  an order never fails (r1, r2, ended 
after 1m4s)",
-                "expect orders:failed<=0: unmet, 9 failed", "routes: orders 41 
completed, 9 failed",
+                "expect orders:failed<=0: unmet, 9 failed",
+                "lost at the dead letter channel: 58 messages, handled there 
without being delivered",
+                "routes: orders 41 completed, 9 failed",
                 "note: orders has no error handler");
     }
 

Reply via email to