allthingssecurity opened a new pull request, #28754: URL: https://github.com/apache/camel/pull/28754
# Description [CAMEL-26418](https://issues.apache.org/jira/browse/CAMEL-26418) (related to CAMEL-26330): the second part of `camel-chaos` from `design/chaos.adoc`, on top of the module core merged in #28716. It also takes in the six suggestions from the review of #28716. The core can fail, delay and drop sends, and fail and delay steps. This PR adds the rest of the first stage of the design: - faults at the polls of the scheduled poll consumers of the routes (`at=poll`: `error`, `delay`, `drop`), for a file, FTP or SQL route that should cope with a failing source; - `route=` with `corrupt` at the entry of a route, and `corrupt` at sends (`mode=headers`, `charset` or `truncate`); - `error status=<code>` (with `header.*` and `body=`) at sends to `http`, `https`, `undertow` and `vertx-http`. It raises the `HttpOperationFailedException` of camel-http-base, or returns the response when the endpoint does not throw on failure; - counts per route in the report (completed, failed, kept by the consumer, handled, redeliveries); - expectations (`expect=orders:100 within=2m`: met, unmet or pending) and a verdict with `lost`. ## The suggestions from #28716 1. **Clear parse errors.** These cases now get a message that names what to use instead, each with a case in `ChaosRuleParserTest`: - `route=orders error`: route= does not reach the sends and steps, so use endpoint=, component= or step=; - `step=s1 drop` and `step=s1 corrupt`: the hooks that offer these faults; - `stall` and `lifecycle`: not supported yet, followed by the list of faults; - `action=` and `when=`: not supported yet; - `status=` for netty-http: not supported yet, followed by the supported components; - a misspelt target (`rout=orders error`): this parameter is not a target; - `expect=ord*:5`: one route id, not a pattern. 2. **A version per report entry.** A rule is version 1 when it is added. `changeRule` gives the new rule the next version. The JSON entry has `version`, and the text shows `r2 v2 active: ...`. This is documented in the Rules section. Test: `ChaosReportTest.theEntriesOfAChangedRuleDifferByVersion`. 3. **Docs under "Where the faults are raised".** `endpoint=*` also fails the send to the dead letter channel, and `step=*` also matches the steps inside `onException` and `doCatch`. `ChaosStepAndErrorHandlerTest` pins both: - `endpoint=mock:*` fires 4 times for one exchange: the try, 2 redeliveries and the send to the dead letter channel; - step rules fire at a step inside `onException` and at a step inside `doCatch`. 4. **WARN in camel-main when the JAR is missing.** camel-main does not depend on camel-chaos for this. When `camel.chaos.*` options are set (from properties or Java) and chaos would be switched on (`enabled=true`, or `enabled` unset with profile `dev`; never with `prod`), camel-main looks up the plugin class `org.apache.camel.component.chaos.ChaosContextServicePlugin` by name. If it is missing, it logs one WARN that the options are ignored. Without profile `dev` or `enabled=true` chaos stays off anyway, and with the JAR the plugin says so itself. `camel run --dep` puts its JARs on the classpath while it creates the context, before this check runs. `MainChaosWithoutJarTest` (camel-main) covers seven cases: - `enabled=true`: WARN; - profile `dev`: WARN; - `enabled=true` from Java: WARN; - rules without `dev` or `enabled`: no WARN; - `enabled=false`: no WARN; - profile `prod`: no WARN; - no options: no WARN. `ChaosMainTest` checks that there is no WARN when the JAR is present. 5. **Ended rules and the pattern.** - At each add, change or remove, the snapshot that the hooks read leaves out the rules that ended and the expectations that are decided. They stay in the report. - The pending expectation of an ended rule stays and keeps counting. - Until then, the hooks check `live(now)` before matching. - The `endpoint=` pattern is normalised once per rule. The URI and the `component=` scheme are matched in place, with no new strings. - `ChaosPatternsTest` compares the new matcher with the old substring matcher on 200,000 seeded random cases. 6. **`ChaosStressTest`** now waits with `await().atMost(10, SECONDS)` instead of its own spin loop. ## Where this differs from the design - **Polls.** The design swaps the consumer's poll strategy while a rule is active. Chaos instead wraps it once per route consumer, before the consumer starts. The poll thread reads the plain field `pollStrategy`, so a swap during a poll could pair the `begin` of one strategy with the `rollback` of the other. The wrapper costs one volatile read while no poll rule fires. A poll `error` is thrown from `begin`. The wrapper passes the consumer's strategy the retry counter of a poll that failed by itself (0, 1, ...), so a strategy that retries up to a limit still stops. `pollEnrich` and `ConsumerTemplate` consumers belong to no route, and chaos does not reach them. - **Counts per route** come from a route policy (`onExchangeDone`), not from an event notifier, so each exchange counts once per route it goes through. Camel does not call route policies while the context stops. So exchanges that finish while the context drains are not counted, and expectations that are still undecided at the stop stay pending. Only the redeliveries come from an event notifier, which asks for no exchange events while there are no rules. - **`status=` for netty-http** is not supported yet. Its exception needs a Netty `HttpContent`, which I could not build and test without camel-netty-http on the test classpath. - **`corrupt`**: `charset` reads a local file as text, but skips streams, readers and remote files, since reading them would consume them. `truncate` takes an `InputStream` or a stream cache. camel-http-base (for `status=`, built by reflection) and camel-file (for the file consumer tests) are test dependencies only. No option changes, so no generated metadata. The catalog copy of the docs page is byte-identical. Size: 38 files, +4,350 / -217: about 1,700 lines of main code and 2,360 of tests. The earlier 3b on the unmerged core was +3,338; the review points added about 1,000, mostly tests. ## Cost The per-send cost is measured with the same method as #28716. It is the median ns per call of 1M calls of the send hook, with three rules that match nothing, against a normalised endpoint URI with `//` and a query (`mock://orders?retainFirst=0`). Before is the merged 3a; after is this PR: | | before | after | |---|---|---| | 3 rules that match nothing | 165 | 64 | | the same, plus 3 rules that ended by count | 353 | 62 | Most of the gain is in matching: there is no copy of the URI without its `//`, and no substring for the query. Per route call through `ProducerTemplate`, median ns of 3 runs (no JAR / chaos off / on without rules / on with three rules that match nothing): | route | before | after | |---|---|---| | empty route | 439 / 425 / 441 / 487 | 437 / 419 / 452 / 535 | | one `to` | 627 / 622 / 649 / 755 | 632 / 640 / 677 / 783 | | recipient list of two | 1866 / 1833 / 1898 / 2033 | 1870 / 1856 / 1936 / 2117 | The machine load was around 7 during these runs, so differences of 10-20 ns are noise. What this PR adds per exchange: - with chaos on and no rules: one volatile read at the begin and at the done of each route, in its route policy; - with rules: the counts per route (a map lookup and a `LongAdder` per exchange and route). That is the 30-80 ns in the last column. The poll strategy reads one volatile field per poll while no poll rule fires (plus one more at commit, until an error rule first fails a poll of that consumer). ## Tests - camel-chaos: 254 tests in 18 classes, no `Thread.sleep`. The new tests are in: - `ChaosHttpFaultTest`; - `ChaosPollFaultsTest` and `ChaosPollStrategyTest`, including a real `file:` consumer, the retry counter seen by a retrying strategy, concurrent poll runs, and suspend during a delay; - `ChaosEntryFaultsTest`; - `ChaosRouteCountsTest`; - `ChaosConsumersTest`; - the expectation, verdict and version tests in `ChaosReportTest`; - the cases of the six suggestions listed above. - camel-main: `MainChaosWithoutJarTest` (7), `ChaosConfigurationPropertiesTest` and `MainNotAutoConfiguredWarningTest` pass. - Repeats: each camel-chaos test class ran 20 times on its own, one JVM per run: 360 runs, 0 failures. `MainChaosWithoutJarTest` also ran 20 times, 0 failures. - Negative controls: 21 mutants of camel-chaos and 4 of the camel-main check (one per behaviour above, from the prod switch to the WARN conditions), each caught by at least one test. ## Open questions - An expectation stays pending for good when its rule is changed or removed first, and then `isFinal()` stays false. The design says so, because the messages may still arrive. But a tool that changes rules in a loop would never see a final report. Should an expectation that was abandoned this way stop counting against `isFinal()`, as long as it was not abandoned by the context stopping? - The WARN for a missing JAR is in camel-main only. Should the Spring Boot and Quarkus starters get the same check? - PR 4 (CLI/MCP) will show id and version, and change/remove act on the current version of an id. Is that the shape you want for the tools? # Target - [x] I checked that the commit is targeting the correct branch (Camel 4 uses the `main` branch) # Tracking - [x] If this is a large change, bug fix, or code improvement, I checked there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL-26418) filed for the change (usually before you start working on it): [CAMEL-26418](https://issues.apache.org/jira/browse/CAMEL-26418). # Apache Camel coding standards and style - [x] I checked that each commit in the pull request has a meaningful subject line and body. - [ ] I have run `mvn clean install -DskipTests` locally from root folder and I have committed all auto-generated changes. (I built the core modules, camel-main and the module with the formatter and impsort. The catalog copy of the docs page is byte-identical to its source. I did not run the full root build.) # AI-assisted contributions - [x] If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR description identifies the AI tool used. This PR was prepared with Claude Code (Claude Opus 5.5). The commit carries a `Co-Authored-By` trailer. _Claude Code on behalf of allthingssecurity_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
