This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch chaos-explore-small-findings in repository https://gitbox.apache.org/repos/asf/camel.git
commit bfef819070d3910a52b0b546d0de64a1966a1584 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Nov 11 10:46:52 2026 +0100 chore: camel-chaos - Times out counts as tried once it ran, and let there be chaos remembers the budget and the picks A rule that fails after a wait (after=) is a timeout: kind slow, as the suggestions offer it, not errors. The Chaos screen (L) opens with the budget and the rules picked the last time, while the TUI runs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/catalog/docs/chaos.adoc | 6 ++-- components/camel-chaos/src/main/docs/chaos.adoc | 6 ++-- .../apache/camel/component/chaos/ChaosAdvisor.java | 3 +- .../camel/component/chaos/ChaosTriedTest.java | 3 ++ .../dsl/jbang/core/commands/tui/ChaosControl.java | 13 +++++++-- .../core/commands/tui/ChaosExploreDialog.java | 32 ++++++++++++++++++++++ .../jbang/core/commands/tui/ChaosExploreTest.java | 30 ++++++++++++++++++++ 7 files changed, 84 insertions(+), 9 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 3cf17107f73d..2a245ccca8a8 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 @@ -324,9 +324,9 @@ $ camel get chaos --report shipping dead letter channel not tried yet ---- -* The kinds are `errors` (a send or a step fails), `slow` (a delay), `lost` (a send is dropped), `corrupt`, `duplicate`, -`consumer` (the polls, or the entry of the route), `lifecycle` (stall and lifecycle) and `dead letter channel` -(`at=dlq`). A rule has one kind, from its fault and where it acts. +* The kinds are `errors` (a send or a step fails), `slow` (a delay, or an error that waits first: a timeout), `lost` +(a send is dropped), `corrupt`, `duplicate`, `consumer` (the polls, or the entry of the route), `lifecycle` (stall and +lifecycle) and `dead letter channel` (`at=dlq`). A rule has one kind, from its fault and where it acts. * A kind is tried in a route when a rule of that kind fired in it: a rule counts its faults per route, so a rule that reaches several routes is tried in each. A rule that expects no message lost between two routes (`expect=orders->shipping:lost<=0`) also tries `lost` for the first route, as the hand-off suggestion fails the next diff --git a/components/camel-chaos/src/main/docs/chaos.adoc b/components/camel-chaos/src/main/docs/chaos.adoc index 3cf17107f73d..2a245ccca8a8 100644 --- a/components/camel-chaos/src/main/docs/chaos.adoc +++ b/components/camel-chaos/src/main/docs/chaos.adoc @@ -324,9 +324,9 @@ $ camel get chaos --report shipping dead letter channel not tried yet ---- -* The kinds are `errors` (a send or a step fails), `slow` (a delay), `lost` (a send is dropped), `corrupt`, `duplicate`, -`consumer` (the polls, or the entry of the route), `lifecycle` (stall and lifecycle) and `dead letter channel` -(`at=dlq`). A rule has one kind, from its fault and where it acts. +* The kinds are `errors` (a send or a step fails), `slow` (a delay, or an error that waits first: a timeout), `lost` +(a send is dropped), `corrupt`, `duplicate`, `consumer` (the polls, or the entry of the route), `lifecycle` (stall and +lifecycle) and `dead letter channel` (`at=dlq`). A rule has one kind, from its fault and where it acts. * A kind is tried in a route when a rule of that kind fired in it: a rule counts its faults per route, so a rule that reaches several routes is tried in each. A rule that expects no message lost between two routes (`expect=orders->shipping:lost<=0`) also tries `lost` for the first route, as the hand-off suggestion fails the next diff --git a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java index 4f65002691ee..6c5db1c6c0a5 100644 --- a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java +++ b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java @@ -179,7 +179,8 @@ final class ChaosAdvisor { case DROP -> consumer ? Kind.CONSUMER : Kind.LOST; case CORRUPT -> Kind.CORRUPT; case DUPLICATE -> Kind.DUPLICATE; - default -> consumer ? Kind.CONSUMER : Kind.ERRORS; + // an error that waits first (after=) is a call that times out, which the suggestions offer as slow + default -> consumer ? Kind.CONSUMER : rule.after > 0 ? Kind.SLOW : Kind.ERRORS; }; } diff --git a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java index 92bd4826ff56..2db55f43e568 100644 --- a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java +++ b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java @@ -168,6 +168,9 @@ class ChaosTriedTest extends ChaosTestSupport { void theKindOfARule() { assertThat(kind("endpoint=x error")).isEqualTo(ChaosAdvisor.Kind.ERRORS); assertThat(kind("endpoint=x delay duration=1s")).isEqualTo(ChaosAdvisor.Kind.SLOW); + // Times out: the suggestion of an HTTP call is slow, so trying it marks it tried + assertThat(kind("endpoint=x error after=5s exception=java.net.SocketTimeoutException")) + .isEqualTo(ChaosAdvisor.Kind.SLOW); assertThat(kind("endpoint=x drop")).isEqualTo(ChaosAdvisor.Kind.LOST); assertThat(kind("endpoint=x drop at=poll")).isEqualTo(ChaosAdvisor.Kind.CONSUMER); assertThat(kind("endpoint=x error at=poll")).isEqualTo(ChaosAdvisor.Kind.CONSUMER); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java index 93d290500f57..be7e1eb6def5 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java @@ -23,6 +23,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Supplier; @@ -96,6 +97,8 @@ final class ChaosControl { // the exploration started here (L), and whether one was just started, for the screen to show it private final ChaosExploreRun explorer; private boolean exploreStarted; + // the budget and the picks of the dialog per integration, as it was closed last, while the TUI runs + private final Map<String, ChaosExploreDialog.Picks> lastPicks = new ConcurrentHashMap<>(); // set by the background task that sends the action, read when the screen is drawn private volatile Step step = Step.NONE; private Command command; @@ -404,7 +407,9 @@ final class ChaosControl { : reply.getString("error")); return; } - exploreDialog = new ChaosExploreDialog(of, reply); + ChaosExploreDialog dialog = new ChaosExploreDialog(of, reply); + dialog.restore(lastPicks.get(of)); + exploreDialog = dialog; }; if (sender != null) { task.run(); @@ -422,8 +427,12 @@ final class ChaosControl { return true; } switch (dialog.handleKey(ke)) { - case CLOSED -> close(); + case CLOSED -> { + lastPicks.put(dialog.name(), dialog.picks()); + close(); + } case START -> { + lastPicks.put(dialog.name(), dialog.picks()); explorer.start(pid, dialog.picked(), dialog.budget(), dialog.budgetMillis()); exploreStarted = true; close(); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreDialog.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreDialog.java index 6d634cc4044b..bfa08c75fb5c 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreDialog.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreDialog.java @@ -95,6 +95,10 @@ final class ChaosExploreDialog { } } + /** The budget and the rules picked, to open the dialog with them again. */ + record Picks(String budget, Set<String> rules) { + } + /** The budgets to pick from: an exploration by hand is short, the long ones are for camel cmd chaos-explore. */ static final List<String> BUDGETS = List.of("2m", "5m", "10m", "15m", "30m"); // the shortest time a rule is tried, so its faults fire and the routes have messages @@ -175,6 +179,34 @@ final class ChaosExploreDialog { return items; } + String name() { + return name; + } + + /** The budget and the rules picked now. */ + Picks picks() { + return new Picks(budget(), picked().stream().map(Item::rule).collect(Collectors.toSet())); + } + + /** + * Opens with the budget and the picks of last time, to try them again (after a change to the routes): the rules of + * last time that the integration still suggests, else the ones picked by itself. + */ + void restore(Picks last) { + if (last == null) { + return; + } + int index = BUDGETS.indexOf(last.budget()); + if (index >= 0) { + budget = index; + } + if (items.stream().anyMatch(item -> last.rules().contains(item.rule()))) { + for (int i = 0; i < items.size(); i++) { + picked.set(i, last.rules().contains(items.get(i).rule())); + } + } + } + /** The picked rules, in the order of the list. */ List<Item> picked() { List<Item> answer = new ArrayList<>(); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreTest.java index 8fa1af8c414c..f52700e8436f 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreTest.java @@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -150,6 +151,35 @@ class ChaosExploreTest { .isEqualTo(ChaosExploreDialog.Outcome.NONE); } + @Test + void theDialogOpensWithTheBudgetAndThePicksOfLastTime() { + key('L'); + // the first rule unpicked, refunds picked, and 5 minutes + key(' '); + press(KeyCode.DOWN); + press(KeyCode.DOWN); + key(' '); + press(KeyCode.LEFT); + press(KeyCode.ESCAPE); + assertThat(tab.isTextInputActive()).isFalse(); + + key('L'); + assertThat(TuiTestHelper.renderToString(tab, 160, 40)).contains("5m", "3 rules, 1m40s each", + "[ ] Returns an error (HTTP status)", "[x] The route after a hand-off fails", "[x] The route fails", + "[x] An outage while the dead letter channel fails"); + } + + @Test + void thePicksOfLastTimeThatAreNoLongerSuggestedAreNot() throws Exception { + ChaosExploreDialog dialog = dialog(); + dialog.restore(new ChaosExploreDialog.Picks("30m", Set.of("route=gone error"))); + assertThat(dialog.budget()).isEqualTo("30m"); + // picked by itself, as none of last time is suggested + assertThat(dialog.picked()).extracting(ChaosExploreDialog.Item::title) + .containsExactly("Returns an error (HTTP status)", "The route after a hand-off fails", + "An outage while the dead letter channel fails"); + } + @Test void eachRuleRunsForItsShare() { assertThat(ChaosExploreRun.forShare(
