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 66000729edf0 CAMEL-26771: camel-chaos - what to try says how to feed
each route: by itself, curl, a file, or camel cmd send (#29462)
66000729edf0 is described below
commit 66000729edf0e91ec6c2785f2ae40cf9c90745ae
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 13:24:57 2026 +0100
CAMEL-26771: camel-chaos - what to try says how to feed each route: by
itself, curl, a file, or camel cmd send (#29462)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../org/apache/camel/catalog/docs/chaos.adoc | 30 +++++-
components/camel-chaos/src/main/docs/chaos.adoc | 30 +++++-
.../apache/camel/component/chaos/ChaosAdvisor.java | 97 +++++++++++++++++++
.../apache/camel/component/chaos/ChaosManager.java | 13 +++
.../camel/component/chaos/ChaosFeedTest.java | 104 +++++++++++++++++++++
.../commands/action/CamelChaosSuggestAction.java | 11 ++-
.../dsl/jbang/core/commands/ai/ToolRegistry.java | 6 +-
.../core/commands/action/CamelChaosActionTest.java | 33 +++++++
.../src/test/resources/golden/cli-help.txt | 3 +-
.../dsl/jbang/core/commands/mcp/RuntimeTools.java | 6 +-
10 files changed, 317 insertions(+), 16 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 8d6bd7a724ae..4db9c739828f 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
@@ -190,8 +190,8 @@ JSON. It only reads: a rule cannot be added, changed or
removed over HTTP.
Where to start with a route you did not write? `camel cmd chaos-suggest` asks
the running integration, and it suggests
rules per route from what the routes do, the most worth trying first. Each is
a known rule (see
-<<_the_model_of_the_rules,The model of the rules>>) with the endpoint or route
filled in, why it is worth trying, and
-the command that adds it. Nothing is added.
+<<_the_model_of_the_rules,The model of the rules>>) with the endpoint or route
filled in, why it is worth trying, the
+command that adds it, and how to feed the route with messages, so the rule has
something to act on. Nothing is added.
[source]
----
@@ -205,8 +205,27 @@ Chaos rules to try, the most worth trying first:
2. route orders: The route after a hand-off fails (lost)
route orders hands the message to route shipping over seda and does not
wait: when shipping fails, is the message lost?
camel cmd chaos-add --rule='route=shipping error rate=0.2 until=2m
expect=orders->shipping:lost<=0 within=3m'
+ feed: camel cmd send shop --endpoint=orders --body=<message> --repeat=20
--interval=200ms
----
+How to feed a route comes from its input:
+
+[cols="2,3"]
+|===
+|The input |How to feed it
+
+|`timer`, `scheduler`, `cron`, `quartz` |It feeds itself (no `feed:` line)
+|`platform-http`, `rest` |`curl` with the method and path of the route, on the
port of the HTTP server
+(`camel.server.port`, 8080 by default)
+|`jetty`, `netty-http`, `undertow`, `servlet`, `vertx-http` |`curl` to the
address in its URI
+|`file` |Write a file into its folder
+|Any other (`direct`, `seda`, a broker, a stub of a broker) |`camel cmd send`
to the route, with `--repeat` and
+`--interval`: the route id as the endpoint sends to the input of the route,
with all its options
+|===
+
+When the input is a remote system that is not a stub (see <<_with_stubs,With
stubs>>), the messages go to that real
+system: the CLI says so, and `real` is `true` in the JSON. Run with
`--stub=remote` to feed a stub instead.
+
[cols="2,3"]
|===
|What a route does |What is suggested
@@ -228,9 +247,10 @@ retry, an error?), or the polls fail for a polling
consumer; and the route stall
|===
The rules leave the query of the endpoint out: its options do not tell which
endpoint it is, and may hold a secret.
-`ChaosManager.suggest()` gives the same as JSON (`suggestions`: `route`,
`kind`, `recipe`, `title`, `rule`, `why`,
-`tried`, and `routes` for a dead letter channel that several routes share), and
-the chaos screen of the Camel TUI lists them (key `w`), where Enter types the
rule in to change or add.
+`ChaosManager.suggest()` gives the same as JSON: `suggestions`, each with
`route`, `kind`, `recipe`, `title`, `rule`,
+`why`, `tried`, `routes` for a dead letter channel that several routes share,
and `feed`. A `feed` has its `kind`
+(one of `self`, `http`, `file` and `send`), a `hint` and `real`. The chaos
screen of the Camel TUI lists the suggestions
+(key `w`), where Enter types the rule in to change or add.
=== What has been tried
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc
b/components/camel-chaos/src/main/docs/chaos.adoc
index 8d6bd7a724ae..4db9c739828f 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -190,8 +190,8 @@ JSON. It only reads: a rule cannot be added, changed or
removed over HTTP.
Where to start with a route you did not write? `camel cmd chaos-suggest` asks
the running integration, and it suggests
rules per route from what the routes do, the most worth trying first. Each is
a known rule (see
-<<_the_model_of_the_rules,The model of the rules>>) with the endpoint or route
filled in, why it is worth trying, and
-the command that adds it. Nothing is added.
+<<_the_model_of_the_rules,The model of the rules>>) with the endpoint or route
filled in, why it is worth trying, the
+command that adds it, and how to feed the route with messages, so the rule has
something to act on. Nothing is added.
[source]
----
@@ -205,8 +205,27 @@ Chaos rules to try, the most worth trying first:
2. route orders: The route after a hand-off fails (lost)
route orders hands the message to route shipping over seda and does not
wait: when shipping fails, is the message lost?
camel cmd chaos-add --rule='route=shipping error rate=0.2 until=2m
expect=orders->shipping:lost<=0 within=3m'
+ feed: camel cmd send shop --endpoint=orders --body=<message> --repeat=20
--interval=200ms
----
+How to feed a route comes from its input:
+
+[cols="2,3"]
+|===
+|The input |How to feed it
+
+|`timer`, `scheduler`, `cron`, `quartz` |It feeds itself (no `feed:` line)
+|`platform-http`, `rest` |`curl` with the method and path of the route, on the
port of the HTTP server
+(`camel.server.port`, 8080 by default)
+|`jetty`, `netty-http`, `undertow`, `servlet`, `vertx-http` |`curl` to the
address in its URI
+|`file` |Write a file into its folder
+|Any other (`direct`, `seda`, a broker, a stub of a broker) |`camel cmd send`
to the route, with `--repeat` and
+`--interval`: the route id as the endpoint sends to the input of the route,
with all its options
+|===
+
+When the input is a remote system that is not a stub (see <<_with_stubs,With
stubs>>), the messages go to that real
+system: the CLI says so, and `real` is `true` in the JSON. Run with
`--stub=remote` to feed a stub instead.
+
[cols="2,3"]
|===
|What a route does |What is suggested
@@ -228,9 +247,10 @@ retry, an error?), or the polls fail for a polling
consumer; and the route stall
|===
The rules leave the query of the endpoint out: its options do not tell which
endpoint it is, and may hold a secret.
-`ChaosManager.suggest()` gives the same as JSON (`suggestions`: `route`,
`kind`, `recipe`, `title`, `rule`, `why`,
-`tried`, and `routes` for a dead letter channel that several routes share), and
-the chaos screen of the Camel TUI lists them (key `w`), where Enter types the
rule in to change or add.
+`ChaosManager.suggest()` gives the same as JSON: `suggestions`, each with
`route`, `kind`, `recipe`, `title`, `rule`,
+`why`, `tried`, `routes` for a dead letter channel that several routes share,
and `feed`. A `feed` has its `kind`
+(one of `self`, `http`, `file` and `send`), a `hint` and `real`. The chaos
screen of the Camel TUI lists the suggestions
+(key `w`), where Enter types the rule in to change or add.
=== What has been tried
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 1fa1abd6d935..2d44cbb71ecf 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
@@ -21,6 +21,7 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -59,6 +60,13 @@ final class ChaosAdvisor {
private static final Set<String> HAND_OFF = Set.of("seda", "vm",
"disruptor", "disruptor-vm");
// the endpoints that write to the local file system: remote to Camel, but
with no connection to refuse
private static final Set<String> LOCAL_FILES = Set.of("file");
+ // the inputs that feed their route by themselves
+ private static final Set<String> SELF_FEEDING = Set.of("timer",
"scheduler", "cron", "quartz");
+ // the HTTP servers, which are fed with an HTTP request: platform-http and
rest with the port of the HTTP server of
+ // Camel, the others with the address in their URI
+ private static final Set<String> HTTP_SERVERS = Set.of("platform-http",
"rest");
+ private static final Set<String> HTTP_SERVERS_WITH_ADDRESS
+ = Set.of("jetty", "netty-http", "undertow", "servlet",
"vertx-http");
/** The kinds of faults, which the report also uses for what has been
tried. */
enum Kind {
@@ -99,6 +107,17 @@ final class ChaosAdvisor {
}
}
+ /**
+ * How to feed a route with messages (CAMEL-26771), so a rule can be tried
on it.
+ *
+ * @param kind {@code self} (the route feeds itself), {@code http}, {@code
file} or {@code send}
+ * @param hint how, such as the command to run
+ * @param real whether the messages go to the real system the route reads,
rather than a stub of it
+ * ({@code camel run --stub=remote})
+ */
+ record Feed(String kind, String hint, boolean real) {
+ }
+
/**
* A kind of fault tried in a route (CAMEL-26728): by which rules, how
often they fired there, how the exchanges
* they faulted came out (counted per rule, so a rule that fired in more
routes counts in each), and the outcomes of
@@ -219,6 +238,84 @@ final class ChaosAdvisor {
private ChaosAdvisor() {
}
+ /** How to feed the route with messages, or null when it has no input. */
+ static Feed feed(Route route) {
+ Endpoint from = route.getEndpoint();
+ if (from == null) {
+ return null;
+ }
+ String uri = from.getEndpointUri();
+ String scheme = ChaosPatterns.scheme(uri);
+ if (SELF_FEEDING.contains(scheme)) {
+ return new Feed("self", "the route feeds itself (" + scheme + ")",
false);
+ }
+ if (HTTP_SERVERS.contains(scheme)) {
+ return new Feed(
+ "http", "curl -X " + httpMethod(uri) + "
http://localhost:" + serverPort(route.getCamelContext())
+ + httpPath(uri) + " -d @<message>",
+ false);
+ }
+ if (HTTP_SERVERS_WITH_ADDRESS.contains(scheme)) {
+ String address =
ChaosPatterns.withoutSlashes(uri(from)).substring(scheme.length() + 1);
+ return new Feed("http", "curl -X POST " + address + " -d
@<message>", false);
+ }
+ if (LOCAL_FILES.contains(scheme)) {
+ String folder =
ChaosPatterns.withoutSlashes(uri(from)).substring(scheme.length() + 1);
+ return new Feed("file", "write a file into " + folder, false);
+ }
+ // the route id as the endpoint: the send console sends to the input
of the route, with all its options
+ String name = route.getCamelContext().getName();
+ return new Feed(
+ "send", "camel cmd send " + name + " --endpoint=" +
route.getRouteId()
+ + " --body=<message> --repeat=20 --interval=200ms",
+ from.isRemote());
+ }
+
+ /** The method of a platform-http or rest input: the one it restricts to,
else POST. */
+ static String httpMethod(String uri) {
+ if (uri.startsWith("rest:")) {
+ String[] parts = uri.split(":", 3);
+ return parts.length == 3 ? parts[1].toUpperCase(Locale.ROOT) :
"POST";
+ }
+ try {
+ Object method =
URISupport.parseQuery(URISupport.extractQuery(uri)).get("httpMethodRestrict");
+ if (method != null) {
+ return
method.toString().split(",")[0].trim().toUpperCase(Locale.ROOT);
+ }
+ } catch (Exception e) {
+ // no options to read: the default method
+ }
+ return "POST";
+ }
+
+ /** The path of a platform-http or rest input, such as /orders. */
+ static String httpPath(String uri) {
+ String path;
+ if (uri.startsWith("rest:")) {
+ String[] parts = uri.split(":", 3);
+ path = parts.length == 3 ? parts[2] : "";
+ } else {
+ path = uri.substring(uri.indexOf(':') + 1);
+ }
+ int query = path.indexOf('?');
+ if (query >= 0) {
+ path = path.substring(0, query);
+ }
+ while (path.startsWith("//")) {
+ path = path.substring(1);
+ }
+ return path.startsWith("/") ? path : "/" + path;
+ }
+
+ /** The port of the HTTP server of Camel (camel.server.port), 8080 by
default. */
+ private static String serverPort(CamelContext context) {
+ try {
+ return
context.resolvePropertyPlaceholders("{{camel.server.port:8080}}");
+ } catch (Exception e) {
+ return "8080";
+ }
+ }
+
/** The rules to try for the routes of the context, the most worth trying
first, at most {@link #MAX}. */
static List<Suggestion> suggest(CamelContext camelContext) {
Map<String, ChaosModel.Recipe> recipes = new LinkedHashMap<>();
diff --git
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
index 0e1c01964d7e..3f5ae8f18426 100644
---
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
+++
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -486,6 +487,7 @@ public final class ChaosManager {
for (ChaosAdvisor.RouteTried route : ChaosAdvisor.tried(all, Map.of(),
TimeSource.currentTimeMillis())) {
route.tried().forEach(k -> tried.add(route.route() + " " +
k.kind().key));
}
+ Map<String, ChaosAdvisor.Feed> feeds = new HashMap<>();
for (ChaosAdvisor.Suggestion s : ChaosAdvisor.suggest(camelContext)) {
JsonObject jo = new JsonObject();
// a dead letter channel that routes share is tried by a rule of
its kind in any of them
@@ -503,6 +505,17 @@ public final class ChaosManager {
jo.put("title", s.title());
jo.put("rule", s.rule());
jo.put("why", s.why());
+ Route route = camelContext.getRoute(s.route());
+ ChaosAdvisor.Feed feed = route != null ?
feeds.computeIfAbsent(s.route(), k -> ChaosAdvisor.feed(route)) : null;
+ if (feed != null) {
+ JsonObject f = new JsonObject();
+ f.put("kind", feed.kind());
+ f.put("hint", feed.hint());
+ if (feed.real()) {
+ f.put("real", true);
+ }
+ jo.put("feed", f);
+ }
list.add(jo);
}
JsonObject answer = new JsonObject();
diff --git
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosFeedTest.java
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosFeedTest.java
new file mode 100644
index 000000000000..3b86b093f2d9
--- /dev/null
+++
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosFeedTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.chaos;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.stub.StubComponent;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * What to try says how to feed each route with messages (CAMEL-26771): the
route feeds itself, an HTTP request, a file,
+ * or camel cmd send to the route, which goes to the real system unless its
input is a stub.
+ */
+class ChaosFeedTest extends ChaosTestSupport {
+
+ private final TestPollComponent poll = new TestPollComponent();
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+ context.getCamelContextExtension().setName("shop");
+ context.addComponent("testpoll", poll);
+ StubComponent stub = new StubComponent();
+ stub.setShadow(true);
+ // remotemq is remote by its JSON (in the test resources), served by a
stub as camel run --stub=remote does
+ context.addComponent("remotemq", stub);
+ return context;
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("timer:tick?period=100000").routeId("ticker").to("mock:tick");
+ from("direct:orders").routeId("orders").to("mock:orders");
+
from("file:target/chaos-inbox?initialDelay=100000").routeId("inbox").to("mock:inbox");
+
from("remotemq:refunds?breakOnFirstError=true").routeId("refunds").to("mock:refunds");
+
from("testpoll:payments?delay=100000&initialDelay=100000").routeId("payments").to("mock:payments");
+ }
+ };
+ }
+
+ @Test
+ void howToFeedEachRoute() {
+ assertThat(ChaosAdvisor.feed(context.getRoute("ticker")))
+ .isEqualTo(new ChaosAdvisor.Feed("self", "the route feeds
itself (timer)", false));
+ assertThat(ChaosAdvisor.feed(context.getRoute("orders")))
+ .isEqualTo(new ChaosAdvisor.Feed(
+ "send", "camel cmd send shop --endpoint=orders
--body=<message> --repeat=20 --interval=200ms",
+ false));
+ assertThat(ChaosAdvisor.feed(context.getRoute("inbox")))
+ .isEqualTo(new ChaosAdvisor.Feed("file", "write a file into
target/chaos-inbox", false));
+ // a stub of a broker is fed with camel cmd send, and the messages
stay in the stub
+ assertThat(ChaosAdvisor.feed(context.getRoute("refunds")))
+ .isEqualTo(new ChaosAdvisor.Feed(
+ "send", "camel cmd send shop --endpoint=refunds
--body=<message> --repeat=20 --interval=200ms",
+ false));
+ // a remote input that is not a stub: the messages go to the real
system
+
assertThat(ChaosAdvisor.feed(context.getRoute("payments")).real()).isTrue();
+ }
+
+ @Test
+ void theSuggestionsHaveTheFeedOfTheirRoute() {
+ JsonArray list = (JsonArray) chaos().suggest().get("suggestions");
+ JsonObject refunds = list.stream().map(JsonObject.class::cast)
+ .filter(s ->
"refunds".equals(s.getString("route"))).findFirst().orElseThrow();
+ JsonObject feed = refunds.getMap("feed");
+ assertThat(feed.getString("kind")).isEqualTo("send");
+ assertThat(feed.getString("hint")).contains("--endpoint=refunds");
+ assertThat(feed.containsKey("real")).isFalse();
+ JsonObject payments = list.stream().map(JsonObject.class::cast)
+ .filter(s ->
"payments".equals(s.getString("route"))).findFirst().orElseThrow();
+ assertThat(payments.getMap("feed").get("real")).isEqualTo(true);
+ }
+
+ @Test
+ void anHttpInputIsFedWithItsMethodAndPath() {
+
assertThat(ChaosAdvisor.httpMethod("platform-http:///orders?httpMethodRestrict=GET,POST")).isEqualTo("GET");
+
assertThat(ChaosAdvisor.httpMethod("platform-http:///orders")).isEqualTo("POST");
+
assertThat(ChaosAdvisor.httpMethod("rest:put:/orders/{id}")).isEqualTo("PUT");
+
assertThat(ChaosAdvisor.httpPath("platform-http:///orders?httpMethodRestrict=POST")).isEqualTo("/orders");
+
assertThat(ChaosAdvisor.httpPath("platform-http:orders")).isEqualTo("/orders");
+
assertThat(ChaosAdvisor.httpPath("rest:put:/orders/{id}")).isEqualTo("/orders/{id}");
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosSuggestAction.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosSuggestAction.java
index 22174ed7c2cf..551b173148c6 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosSuggestAction.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosSuggestAction.java
@@ -35,7 +35,8 @@ import picocli.CommandLine;
"hand-off to another route over seda (the next
route fails, and no message may",
"be lost between the two), an input from outside
(the route fails at its entry,",
"the polls fail, the route stalls). The most
worth trying come first, each with",
- "why and the command that adds it. Nothing is
added.",
+ "why and the command that adds it, and how to
feed the route with messages",
+ "(curl, a file, or camel cmd send), unless it
feeds itself. Nothing is added.",
"%nWithout a name or pid, the only integration
that runs, or the only one with",
"chaos switched on.",
"%nExamples:",
@@ -83,6 +84,14 @@ public class CamelChaosSuggestAction extends
CamelChaosAction {
}
sb.append("\n ").append(s.getString("why"));
sb.append("\n camel cmd chaos-add
--rule='").append(s.getString("rule")).append('\'');
+ JsonObject feed = s.getMap("feed");
+ if (feed != null && !"self".equals(feed.getString("kind"))) {
+ // how to send messages into the route, so the rule has
something to act on
+ sb.append("\n feed: ").append(feed.getString("hint"));
+ if (feed.getBooleanOrDefault("real", false)) {
+ sb.append("\n (this sends to the real system: run with
--stub=remote to feed a stub instead)");
+ }
+ }
}
return sb.toString();
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
index ba3e838d2336..6a9f5f96fc6b 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
@@ -810,8 +810,10 @@ public final class ToolRegistry {
+ "the most worth trying first, such as
an HTTP call without a circuit breaker, "
+ "a dead letter channel or a hand-off
to another route over seda. Each has the "
+ "route, the kind of fault, the rule,
why to try it, and tried=true when a rule "
- + "of that kind has already faulted the
route. Nothing is added: check a rule "
- + "with chaos_check and add it with
chaos_add.")
+ + "of that kind has already faulted the
route, and feed: how to send messages "
+ + "into the route (kind self, http,
file or send, a hint such as the command, and "
+ + "real=true when that goes to the real
system rather than a stub). Nothing is "
+ + "added: check a rule with chaos_check
and add it with chaos_add.")
.param("name", "string", CHAOS_NAME, false)
.readOnly(true).destructive(false)
.executor((ctx, args) -> {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
index 5d1554d1e4df..34d773dd25b7 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
@@ -137,6 +137,39 @@ class CamelChaosActionTest extends
ActionCommandTestSupport {
"2. routes orders, billing: The dead letter channel is
unreachable (dead letter channel)");
}
+ @Test
+ void suggestSaysHowToFeedTheRoute() throws Exception {
+ writeStatusFile(TEST_PID, "orders");
+ JsonObject stub = new JsonObject(
+ Map.of("route", "refunds", "kind", "consumer", "title", "The
route fails", "rule", "route=refunds error",
+ "why", "route refunds is called from outside (kafka)",
+ "feed", new JsonObject(
+ Map.of("kind", "send", "hint",
+ "camel cmd send orders
--endpoint=refunds --body=<message> --repeat=20 --interval=200ms"))));
+ JsonObject real = new JsonObject(
+ Map.of("route", "payments", "kind", "consumer", "title", "The
route fails", "rule", "route=payments error",
+ "why", "route payments is called from outside (jms)",
+ "feed", new JsonObject(
+ Map.of("kind", "send", "real", true, "hint",
+ "camel cmd send orders
--endpoint=payments --body=<message> --repeat=20 --interval=200ms"))));
+ JsonObject self = new JsonObject(
+ Map.of("route", "ticker", "kind", "errors", "title", "Refuses
the connection", "rule", "endpoint=jms:x error",
+ "why", "a call to jms:x", "feed",
+ new JsonObject(Map.of("kind", "self", "hint", "the
route feeds itself (timer)"))));
+ JsonObject answer = new JsonObject(Map.of("suggestions", new
JsonArray(List.of(stub, real, self))));
+
+ assertThat(callWithResponse(command(new
CamelChaosSuggestAction(main()), "orders"), answer)).isZero();
+
+ String out = printer.getOutput();
+ assertThat(printer.getLines()).contains(
+ "feed: camel cmd send orders --endpoint=refunds
--body=<message> --repeat=20 --interval=200ms",
+ "feed: camel cmd send orders --endpoint=payments
--body=<message> --repeat=20 --interval=200ms",
+ "(this sends to the real system: run with --stub=remote to
feed a stub instead)");
+ // a route that feeds itself needs no hint
+ assertThat(out).doesNotContain("feeds itself");
+ assertThat(out.split("real system", -1)).hasSize(2);
+ }
+
@Test
void suggestSaysWhenThereIsNothingToTry() throws Exception {
writeStatusFile(TEST_PID, "orders");
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
index 9d39b0ff31d1..9f7445566d67 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
@@ -811,7 +811,8 @@ breaker (the service is down for a while), a call that the
input may repeat
hand-off to another route over seda (the next route fails, and no message may
be lost between the two), an input from outside (the route fails at its entry,
the polls fail, the route stalls). The most worth trying come first, each with
-why and the command that adds it. Nothing is added.
+why and the command that adds it, and how to feed the route with messages
+(curl, a file, or camel cmd send), unless it feeds itself. Nothing is added.
Without a name or pid, the only integration that runs, or the only one with
chaos switched on.
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
index cadde20be252..d15661829100 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
@@ -1156,8 +1156,10 @@ public class RuntimeTools {
What to try (as camel cmd chaos-suggest): chaos rules
suggested per route of the Camel application \
from what the routes do, the most worth trying first, such
as an HTTP call without a circuit \
breaker, a dead letter channel or a hand-off to another
route over seda. Each has the route, the \
- kind of fault, the rule, why to try it, and tried=true when
a rule of that kind has already \
- faulted the route. Nothing is added: check a rule with
camel_runtime_chaos_check and add it with \
+ kind of fault, the rule, why to try it, tried=true when a
rule of that kind has already faulted \
+ the route, and feed: how to send messages into the route
(kind self, http, file or send, a hint \
+ such as the command, and real=true when that goes to the
real system rather than a stub). \
+ Nothing is added: check a rule with
camel_runtime_chaos_check and add it with \
camel_runtime_chaos_add.""")
public JsonObject camel_runtime_chaos_suggest(
@ToolArg(description = CHAOS_NAME_OR_PID_DESC, required = false)
String nameOrPid) {