This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26771-chaos-feed in repository https://gitbox.apache.org/repos/asf/camel.git
commit f33bb0a9111257acf304805b013b0546a76d9270 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 9 11:32:29 2026 +0100 CAMEL-26771: camel-chaos - what to try says how to feed each route: by itself, curl, a file, or camel cmd send Step 3 of CAMEL-26760: each suggestion of chaos-suggest has a feed, how to send messages into its route so the rule has something to act on: a timer, scheduler, cron or quartz route feeds itself; platform-http and rest are fed with curl (method, path and camel.server.port), the other HTTP servers with curl to their address; a file route with a file in its folder; any other route with camel cmd send <name> --endpoint=<route id> --repeat=20 --interval=200ms (CAMEL-26767), which says when that goes to a real system rather than a stub. In the JSON (feed: kind, hint, real), the CLI (feed: line) and the AI and MCP tool descriptions. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/catalog/docs/chaos.adoc | 26 +++++- components/camel-chaos/src/main/docs/chaos.adoc | 26 +++++- .../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, 313 insertions(+), 12 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 269010b70abb..5c30f3de5787 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 @@ -229,7 +248,8 @@ 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 +`tried`, `routes` for a dead letter channel that several routes share, and `feed` with `kind` (`self`, `http`, `file` +or `send`), `hint` and `real`), and the chaos screen of the Camel TUI lists them (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 269010b70abb..5c30f3de5787 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 @@ -229,7 +248,8 @@ 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 +`tried`, `routes` for a dead letter channel that several routes share, and `feed` with `kind` (`self`, `http`, `file` +or `send`), `hint` and `real`), and the chaos screen of the Camel TUI lists them (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 b10c2dcffd8c..38b6702eb44c 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) {
