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-jbang-examples.git
The following commit(s) were added to refs/heads/main by this push:
new 2dad588 fail-well/chaos - chaos rules break the shop on purpose, and
a scenario gates the build (#134)
2dad588 is described below
commit 2dad588d3d8a0a3677cb8337226b0dc1c405eadf
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 10 16:34:34 2026 +0100
fail-well/chaos - chaos rules break the shop on purpose, and a scenario
gates the build (#134)
* fail-well/chaos - chaos rules break the shop on purpose, and a scenario
gates the build
The shop charges an order every second at a payment service over HTTP and
hands
it to shipping over seda. Chaos rules added while it runs make the payment
service answer 503, go down for 10 seconds, fail shipping and fail the dead
letter channel too, and the README says what each one shows in the log, in
camel get chaos and in the Camel TUI. chaos/payment-outage.chaos replays a
bad
half minute with a seed and three expectations; with --max-seconds its
verdict
is the exit code.
Needs Camel 4.24 (camel-chaos is new there), so the CI entry comes with the
move
of the examples to Camel 4.24.0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* fail-well/chaos - the scenario is in test/chaos, which camel run leaves
alone (CAMEL-26761)
The catalog generator takes the scenarios of test/chaos as files of the
example, as camel run --chaos=test/chaos/payment-outage.chaos needs them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* fail-well/chaos - a refunds route from Kafka (stubbed) with a gap on
purpose, for an AI to find: let there be chaos (CAMEL-26760)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---------
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.gitignore | 2 +-
README.md | 3 +-
camel-jbang-example-catalog.json | 51 +++++
fail-well/README.md | 3 +-
fail-well/chaos/README.md | 245 ++++++++++++++++++++++++
fail-well/chaos/application.properties | 29 +++
fail-well/chaos/chaos.camel.yaml | 63 ++++++
fail-well/chaos/metadata.json | 40 ++++
fail-well/chaos/parked.camel.yaml | 18 ++
fail-well/chaos/refunds.camel.yaml | 25 +++
fail-well/chaos/test/chaos.citrus.it.yaml | 42 ++++
fail-well/chaos/test/chaos/payment-outage.chaos | 11 ++
generate-catalog.sh | 10 +-
13 files changed, 538 insertions(+), 4 deletions(-)
diff --git a/.gitignore b/.gitignore
index db62dd3..c16bf84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,7 @@ camel-runner.jar
.citrus-jbang/
# written by the csv-to-json example at runtime
outbox/
-# written by the error-handling example at runtime
+# written by the error-handling and chaos examples at runtime
parked/
# written by the file-processing example at runtime
inbox/
diff --git a/README.md b/README.md
index 9822da3..2061360 100644
--- a/README.md
+++ b/README.md
@@ -80,12 +80,13 @@ The routing patterns: content-based router, splitter,
aggregator, filter and mul
### [Fail well](fail-well/)
-Retries, a dead letter channel, and a circuit breaker in front of a flaky
service.
+Retries, a dead letter channel, and a circuit breaker in front of a flaky
service, then chaos rules that break the routes on purpose to prove it.
| Example | What you will see | Needs |
|---|---|---|
| [Error handling](fail-well/error-handling/) | The three orders go to a
payment provider: the first is charged at once, the second gets no answer twice
and is charged on the third attempt after two retries logged as warnings, and
the third is declined, logged as such and parked as a file for manual review. |
nothing |
| [Circuit breaker](fail-well/circuit-breaker/) | A stock check calls the
supplier every second; the supplier goes down for nine calls, the breaker opens
after two failures in its window of four, answers from the fallback without
calling the supplier while open, tries it again after five seconds and closes
once a call succeeds; each line logs the breaker state and whether the supplier
failed or was not called. | nothing |
+| [Chaos](fail-well/chaos/) | The shop charges an order every second at a
payment service over HTTP and hands it to shipping; chaos rules added while it
runs make the payment service answer 503, go down for 10 seconds and fail
shipping, the retries recover most orders and the dead letter channel parks the
rest, and a scenario file replays a bad half minute with a seed and ends with
the verdict PASS as its exit code; a refunds route from Kafka (stubbed) has a
gap on purpose, for an AI to [...]
### [Connect without a service](connect/)
diff --git a/camel-jbang-example-catalog.json b/camel-jbang-example-catalog.json
index 261b291..ce96df4 100644
--- a/camel-jbang-example-catalog.json
+++ b/camel-jbang-example-catalog.json
@@ -913,6 +913,57 @@
],
"order": 1
},
+ {
+ "name": "fail-well/chaos",
+ "title": "Chaos",
+ "description": "The shop charges an order every second at a payment
service over HTTP and hands it to shipping; chaos rules added while it runs
make the payment service answer 503, go down for 10 seconds and fail shipping,
the retries recover most orders and the dead letter channel parks the rest, and
a scenario file replays a bad half minute with a seed and ends with the verdict
PASS as its exit code; a refunds route from Kafka (stubbed) has a gap on
purpose, for an AI to find w [...]
+ "level": "fail-well",
+ "teaches": {
+ "components": [
+ "timer",
+ "kafka",
+ "http",
+ "platform-http",
+ "seda",
+ "direct",
+ "file",
+ "log"
+ ],
+ "eips": [
+ "errorHandler",
+ "deadLetterChannel",
+ "redeliveryPolicy",
+ "setVariable",
+ "setBody",
+ "removeHeaders"
+ ],
+ "languages": [
+ "simple"
+ ]
+ },
+ "tags": [
+ "shop",
+ "chaos",
+ "resilience",
+ "ai",
+ "error-handling",
+ "dead-letter",
+ "tui"
+ ],
+ "bundled": true,
+ "requiresDocker": false,
+ "hasCitrusTests": true,
+ "files": [
+ "README.md",
+ "application.properties",
+ "chaos.camel.yaml",
+ "parked.camel.yaml",
+ "refunds.camel.yaml",
+ "test/chaos/payment-outage.chaos"
+ ],
+ "needs": "Camel 4.24 or newer",
+ "order": 3
+ },
{
"name": "fail-well/circuit-breaker",
"title": "Circuit breaker",
diff --git a/fail-well/README.md b/fail-well/README.md
index 8e50e0f..8e34dff 100644
--- a/fail-well/README.md
+++ b/fail-well/README.md
@@ -1,12 +1,13 @@
# Fail well
<!-- group:start -->
-Retries, a dead letter channel, and a circuit breaker in front of a flaky
service.
+Retries, a dead letter channel, and a circuit breaker in front of a flaky
service, then chaos rules that break the routes on purpose to prove it.
| Example | What you will see | Needs |
|---|---|---|
| [Error handling](error-handling/) | The three orders go to a payment
provider: the first is charged at once, the second gets no answer twice and is
charged on the third attempt after two retries logged as warnings, and the
third is declined, logged as such and parked as a file for manual review. |
nothing |
| [Circuit breaker](circuit-breaker/) | A stock check calls the supplier every
second; the supplier goes down for nine calls, the breaker opens after two
failures in its window of four, answers from the fallback without calling the
supplier while open, tries it again after five seconds and closes once a call
succeeds; each line logs the breaker state and whether the supplier failed or
was not called. | nothing |
+| [Chaos](chaos/) | The shop charges an order every second at a payment
service over HTTP and hands it to shipping; chaos rules added while it runs
make the payment service answer 503, go down for 10 seconds and fail shipping,
the retries recover most orders and the dead letter channel parks the rest, and
a scenario file replays a bad half minute with a seed and ends with the verdict
PASS as its exit code; a refunds route from Kafka (stubbed) has a gap on
purpose, for an AI to find with [...]
Start with [Error handling](error-handling/); the examples read best in the
order above, each one building on what the one before it set up.
diff --git a/fail-well/chaos/README.md b/fail-well/chaos/README.md
new file mode 100644
index 0000000..ac1d20c
--- /dev/null
+++ b/fail-well/chaos/README.md
@@ -0,0 +1,245 @@
+# Chaos
+
+The shop takes an order every second, charges it at the payment service over
HTTP and hands it to shipping. Nothing
+in the routes fails on its own: you break things on purpose while it runs,
with chaos rules, and watch whether the
+retries, the dead letter channel and the hand-off to shipping do what they
should. A scenario file then replays a bad
+half minute at the payment service, and its verdict is the exit code, so it
can gate a build.
+
+Chaos is new in Camel 4.24
([camel-chaos](https://camel.apache.org/components/next/others/chaos.html)),
and it is for
+development only: it is on with profile `dev`, the default of `camel run`, and
never with profile `prod`.
+
+## What you will see
+
+Without rules, every order is charged and shipped:
+
+```text
+INFO ... chaos.camel.yaml:35 : Order ORD-1 charged: {"payment": "PAY-30456"}
+INFO ... chaos.camel.yaml:45 : Order ORD-1 shipped
+```
+
+With a rule that makes the payment service answer 503 to one call in four, the
error handler retries, and the order
+is charged on the second or third attempt; the few that fail three times in a
row are parked:
+
+```text
+WARN ... DeadLetterChannel : Failed delivery for (...) at orders[to1]
chaos.camel.yaml:33. On delivery attempt: 0 caught:
org.apache.camel.http.base.HttpOperationFailedException: HTTP operation failed
invoking http://localhost:8080/payment with statusCode: 503
+INFO ... chaos.camel.yaml:35 : Order ORD-45 charged: {"payment": "PAY-27475"}
+...
+WARN ... DeadLetterChannel : Failed delivery for (...) at orders[to1]
chaos.camel.yaml:33. On delivery attempt: 2 caught:
org.apache.camel.http.base.HttpOperationFailedException: HTTP operation failed
invoking http://localhost:8080/payment with statusCode: 503
+INFO ... parked.camel.yaml:12 : Order ORD-47 parked for manual review: HTTP
operation failed invoking http://localhost:8080/payment with statusCode: 503
+```
+
+and the scenario ends with its verdict:
+
+```text
+Chaos verdict: PASS (3 expectations met)
+```
+
+## Install Camel CLI
+
+Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as
described in the
+[root README](../../README.md#install-the-camel-cli); `camel --version`
confirms the install. This example needs
+Camel 4.24 or newer.
+
+## Run it
+
+```shell
+camel run * --chaos
+```
+
+`--chaos` adds camel-chaos to the integration. Leave it running, and give the
commands below in another terminal.
+Stop it with `ctrl` + `c`: when rules were added, the chaos report is logged
as the application stops.
+
+## How it works
+
+- `chaos.camel.yaml` is the shop. Route `orders` creates an order every second
(`order.period`), charges it with a
+ `POST` to `http://localhost:8080/payment` and sends it to `seda:shipping`, a
hand-off to route `shipping` that does
+ not wait for it. The `errorHandler` of the file, a dead letter channel to
`direct:parked` with two retries half a
+ second apart, applies to both routes. Route `payment` is the payment
service, a `platform-http` route of the same
+ application, so the example needs nothing else to run; it has
`noErrorHandler`, so a failure there is the caller's
+ to deal with.
+- `parked.camel.yaml` writes an order that could not be charged or shipped to
`parked/<order id>.json`. It is in a
+ file of its own, with an error handler of its own, as in [Error
handling](../error-handling/).
+- `refunds.camel.yaml` pays refunds back at the payment service. They arrive
on the Kafka topic `refunds`; the shop
+ needs no broker, as `camel.jbang.stub=kafka` in `application.properties`
stubs Kafka, and a refund is sent with
+ `camel cmd send --endpoint=kafka:refunds --body='{"refundId": "R-1"}'`. The
route has only the default error
+ handler, on purpose: see [Let there be chaos](#let-there-be-chaos).
+- `test/chaos/payment-outage.chaos` is a scenario: chaos rules in a file, one
per line, with a seed, so the same
+ experiment runs again.
+- A chaos rule is one line: what it targets (an `endpoint=`, a `route=`, a
`component=` or a `step=`), the fault
+ (`error`, `delay`, `drop`, `duplicate`, `corrupt`, `stall`), how often
(`rate=`) and for how long (`until=`), and
+ optionally what must still hold (`expect=`). A rule ends by itself, at the
latest after 10 minutes.
+
+## Break it step by step
+
+1. Ask the integration what is worth trying. Chaos reads the routes and
suggests rules, the most worth trying first:
+
+ ```shell
+ camel cmd chaos-suggest
+ ```
+
+ ```text
+ Chaos rules to try, the most worth trying first:
+
+ 1. route orders: Returns an error (HTTP status) (errors)
+ an HTTP call to http://localhost:8080/payment without a circuit breaker:
what does the route do when the service answers 503?
+ camel cmd chaos-add --rule='endpoint=http://localhost:8080/payment error
status=503 rate=0.25 until=10m'
+
+ 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'
+ ...
+ ```
+
+2. Make the payment service answer 503 to one call in four, the first
suggestion:
+
+ ```shell
+ camel cmd chaos-add --rule='endpoint=http://localhost:8080/payment error
status=503 rate=0.25 until=10m'
+ ```
+
+ ```text
+ Added chaos rule r1: fail 25% of the sends to http://localhost:8080/payment
with HTTP status 503, for 10 minutes
+ ```
+
+ The log shows the retries, and once in a while an order parked in
`parked/`. `camel get chaos` shows how the rule
+ is doing: its observed rate, and the effect of its faults (`60 recovered, 4
handled`: recovered by a retry, or
+ handled by the dead letter channel). Before you add a rule, `chaos-add
--dry-run` says what it would hit, without
+ adding it.
+
+3. Take the payment service down for 10 seconds. A suspended `platform-http`
route answers 503, so the HTTP client
+ retries once by itself, then the error handler twice, and the orders of
those seconds are parked:
+
+ ```shell
+ camel cmd chaos-add --rule='route=payment stall duration=10s'
+ ```
+
+4. Fail route `shipping` one message in five, with an expectation that no
order charged is lost on the way:
+
+ ```shell
+ camel cmd chaos-add --rule='route=shipping error rate=0.2 until=1m
expect=orders->shipping:lost<=0 within=90s'
+ ```
+
+ The dead letter channel of the file covers `shipping` too: a retry ships
the order, or else it is parked, so
+ nothing is lost; when the expectation is decided, `camel get chaos
--report` says `met, 0 lost`.
+
+5. Now the last safety net: fail the dead letter channel too, while the
payment service is down:
+
+ ```shell
+ camel cmd chaos-add --rule='endpoint=http://localhost:8080/payment error
status=503 until=10s' --rule='endpoint=direct:parked error at=dlq until=10s'
+ ```
+
+ The orders of those seconds are neither charged nor parked. The log has
only a WARN for each
+ (`New exception occurred during processing by the
DeadLetterChannel[direct:parked]`), and the report counts them
+ as lost, with the verdict FAIL:
+
+ ```text
+ chaos (pid 58473): FAIL
+ lost: 9
+ FAIL: rule r5 failed or dropped 9 sends of the dead letter channel, which
handled them without delivering them: 9 lost
+ ```
+
+6. Read the report: per rule its matches, faults and effect, per route the
exchanges completed, failed, handled by
+ the error handler and redelivered, the recent faults, and per route which
kinds of fault have been tried and which
+ not yet:
+
+ ```shell
+ camel get chaos --report
+ ```
+
+ `camel cmd chaos-clear --reset` removes the rules and starts the report
again, for the next experiment.
+
+## Watch it in the Camel TUI
+
+```shell
+camel tui
+```
+
+- The chaos screen (`Chaos` under `0 More`) lists the rules with their live
rate, faults per second and effect, and
+ the report of the selected rule. `a` adds a rule from a form of known rules,
`w` lists what to try (the
+ suggestions), `e` changes, `x` removes and `c` clears a rule, `R` starts a
new experiment, and `s` saves the rules
+ as a scenario file.
+- `4 Timeline` shows per second the faults of each rule, and what each route
did: completed, recovered, handled,
+ failed, and the seconds route `payment` was down.
+- `t` on a rule asks the AI panel to make a test of it, with the seed, so it
fails the same messages again.
+
+## A scenario as a gate
+
+`test/chaos/payment-outage.chaos` is a bad half minute at the payment service,
with an expectation on each rule:
+
+```text
+seed=42
+
+# one call in four fails for 20 seconds: the redeliveries recover them, and
the shop still takes 20 orders
+endpoint=http://localhost:8080/payment error status=503 rate=0.25 until=20s
expect=orders:20 within=25s
+# shipping fails one message in five for 15 seconds: its redeliveries still
ship every order charged
+route=shipping error rate=0.2 until=15s expect=orders->shipping:lost<=0
within=20s
+# then the payment service is down for 10 seconds: every order is charged or
parked, none fails
+route=payment stall duration=10s from=+20s expect=orders:failed<=0 within=15s
+```
+
+Run the shop with it for 45 seconds:
+
+```shell
+camel run * --chaos=test/chaos/payment-outage.chaos --max-seconds=45
+```
+
+The report is logged when it stops, and the verdict is the exit code: 0 for
PASS, 1 for FAIL, so a build can run it.
+`seed=42` makes the rules fail the same calls each time. `camel cmd chaos-load
test/chaos/payment-outage.chaos` loads the
+scenario into a shop that runs already.
+
+## Let there be chaos
+
+Instead of choosing the rules yourself, let an AI explore for a time budget.
It reads the suggestions, says what it
+expects (a hypothesis), runs an experiment (rules with expectations, and
messages for the route it is about), reads the
+verdict and chooses the next one. It needs an LLM; a local model with Ollama
is enough:
+
+```shell
+camel cmd chaos-explore --budget=10m
+```
+
+Route `refunds` has a gap on purpose: with only the default error handler, a
refund the payment service answers with
+503 fails, and is neither retried nor parked, while route `orders` recovers
from the same fault. An exploration finds
+it as a FAIL, and often the loss of step 5 as well: the orders lost when the
dead letter channel fails too.
+
+Each experiment ends with a verdict: PASS, FAIL, or INCONCLUSIVE when it
cannot tell (the faults never fired, or the
+route had no messages). The exploration saves its report, and a scenario of
each experiment that failed, in
+`test/chaos`, so `camel cmd chaos-load` replays it. In the Camel TUI, `l` on
the chaos screen shows the exploration:
+the budget as a strip of time with the experiments, and the selected one in
detail.
+
+## Try changing
+
+- Add `endpoint=direct:parked error at=dlq until=10s from=+20s` to the
scenario: the parked orders are lost, and the
+ verdict is FAIL, exit code 1.
+- Give route `refunds` an `errorHandler` with redeliveries, and let the AI
explore again: its refunds experiments
+ pass now.
+- Set `maximumRedeliveries` to 0 in `chaos.camel.yaml` and run the scenario:
the first rule parks the orders instead
+ of recovering them.
+- Wrap the payment call in a `circuitBreaker` (see [Circuit
breaker](../circuit-breaker/)) and ask
+ `camel cmd chaos-suggest` again: it suggests an outage, to see the breaker
open and the fallback answer.
+- Slow the payment service down, with an expectation on the time of an order:
+ `endpoint=http://localhost:8080/payment delay duration=100ms..2s rate=0.5
until=1m expect=orders:meanTime<=1s within=90s`.
+- Make the payment service charge twice:
`endpoint=http://localhost:8080/payment duplicate rate=0.1` sends one call
+ in ten again. Is your payment service idempotent?
+- Fail only some orders: `when="${variable.orderId} ends with '7'"` on any
rule fires only for the orders whose
+ number ends with 7.
+
+## Integration testing
+
+The example comes with a test in the [Citrus](https://citrusframework.org/)
YAML DSL,
+`test/chaos.citrus.it.yaml`, which the Camel CLI runs:
+
+```shell
+camel test run test/chaos.citrus.it.yaml
+```
+
+The test starts the shop with the scenario and verifies that the payment
service goes down and comes back, and that
+orders are still shipped.
+
+## Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/fail-well/chaos/application.properties
b/fail-well/chaos/application.properties
new file mode 100644
index 0000000..3c21428
--- /dev/null
+++ b/fail-well/chaos/application.properties
@@ -0,0 +1,29 @@
+# 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.
+
+
+# a new order every second
+order.period=1000
+# the port of the payment service, which the shop calls on localhost
+camel.server.port=8080
+
+# the folder the parked route writes holds data, not sources: with camel run
--source-dir in dev mode,
+# a change in it does not reload the routes
+camel.jbang.dataDir=parked
+
+# route refunds reads Kafka: stub it, so the example needs no broker; a refund
is sent with camel cmd send
+camel.jbang.stub=kafka
diff --git a/fail-well/chaos/chaos.camel.yaml b/fail-well/chaos/chaos.camel.yaml
new file mode 100644
index 0000000..2d2ce5e
--- /dev/null
+++ b/fail-well/chaos/chaos.camel.yaml
@@ -0,0 +1,63 @@
+# The web shop takes an order every second, charges it at the payment service
over HTTP and hands it to
+# shipping. Nothing here fails on its own: the faults come from the chaos
rules you add while it runs.
+- errorHandler:
+ deadLetterChannel:
+ deadLetterUri: direct:parked
+ redeliveryPolicy:
+ maximumRedeliveries: 2
+ redeliveryDelay: 500
+ retryAttemptedLogLevel: WARN
+
+- route:
+ id: orders
+ from:
+ uri: timer
+ parameters:
+ timerName: orders
+ period: "{{order.period}}"
+ includeMetadata: true
+ steps:
+ - setVariable:
+ name: orderId
+ expression:
+ simple:
+ expression: "ORD-${header.CamelTimerCounter}"
+ - removeHeaders:
+ pattern: "Camel*"
+ - setBody:
+ expression:
+ simple:
+ expression: |-
+ {"orderId": "${variable.orderId}", "customer":
"C-${random(100,999)}", "country": "DK",
+ "lines": [{"sku": "CAMEL-TSHIRT", "qty": ${random(1,3)},
"price": 19.95}], "status": "paid"}
+ - to:
+ uri: "http://localhost:{{camel.server.port}}/payment"
+ - log:
+ message: "Order ${variable.orderId} charged: ${body}"
+ - to:
+ uri: seda:shipping
+
+- route:
+ id: shipping
+ from:
+ uri: seda:shipping
+ steps:
+ - log:
+ message: "Order ${variable.orderId} shipped"
+
+# The payment service the shop calls. In a real shop it runs elsewhere; here
it is a route of
+# the same application, so the example needs nothing else to run.
+- route:
+ id: payment
+ errorHandler:
+ noErrorHandler: {}
+ from:
+ uri: platform-http
+ parameters:
+ path: /payment
+ httpMethodRestrict: POST
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "{\"payment\": \"PAY-${random(10000,99999)}\"}"
diff --git a/fail-well/chaos/metadata.json b/fail-well/chaos/metadata.json
new file mode 100644
index 0000000..b707a84
--- /dev/null
+++ b/fail-well/chaos/metadata.json
@@ -0,0 +1,40 @@
+{
+ "title": "Chaos",
+ "description": "The shop charges an order every second at a payment
service over HTTP and hands it to shipping; chaos rules added while it runs
make the payment service answer 503, go down for 10 seconds and fail shipping,
the retries recover most orders and the dead letter channel parks the rest, and
a scenario file replays a bad half minute with a seed and ends with the verdict
PASS as its exit code; a refunds route from Kafka (stubbed) has a gap on
purpose, for an AI to find with [...]
+ "level": "fail-well",
+ "order": 3,
+ "teaches": {
+ "components": [
+ "timer",
+ "kafka",
+ "http",
+ "platform-http",
+ "seda",
+ "direct",
+ "file",
+ "log"
+ ],
+ "eips": [
+ "errorHandler",
+ "deadLetterChannel",
+ "redeliveryPolicy",
+ "setVariable",
+ "setBody",
+ "removeHeaders"
+ ],
+ "languages": [
+ "simple"
+ ]
+ },
+ "tags": [
+ "shop",
+ "chaos",
+ "resilience",
+ "ai",
+ "error-handling",
+ "dead-letter",
+ "tui"
+ ],
+ "needs": "Camel 4.24 or newer",
+ "bundled": true
+}
diff --git a/fail-well/chaos/parked.camel.yaml
b/fail-well/chaos/parked.camel.yaml
new file mode 100644
index 0000000..f1574b2
--- /dev/null
+++ b/fail-well/chaos/parked.camel.yaml
@@ -0,0 +1,18 @@
+# The orders the shop could not charge or ship are written as files for
someone to look at.
+# The route has an error handler of its own, so a failure in parking an order
is logged
+# instead of being sent back here.
+- route:
+ id: parked
+ errorHandler:
+ deadLetterChannel:
+ deadLetterUri: "log:parking-failed?level=ERROR"
+ from:
+ uri: direct:parked
+ steps:
+ - log:
+ message: "Order ${variable.orderId} parked for manual review:
${exception.message}"
+ - to:
+ uri: file
+ parameters:
+ directoryName: parked
+ fileName: "${variable.orderId}.json"
diff --git a/fail-well/chaos/refunds.camel.yaml
b/fail-well/chaos/refunds.camel.yaml
new file mode 100644
index 0000000..351da9a
--- /dev/null
+++ b/fail-well/chaos/refunds.camel.yaml
@@ -0,0 +1,25 @@
+# Refunds arrive on Kafka from the returns desk, and the shop pays them back
at the payment service.
+# It is a route of its own: a failed refund is logged, without the dead letter
channel of the shop.
+- route:
+ id: refunds
+ errorHandler:
+ defaultErrorHandler: {}
+ from:
+ uri: kafka:refunds
+ parameters:
+ brokers: localhost:9092
+ groupId: shop
+ steps:
+ - setVariable:
+ name: refundId
+ expression:
+ jsonpath:
+ expression: "$.refundId"
+ - log:
+ message: "Refund ${variable.refundId} received"
+ - removeHeaders:
+ pattern: "*"
+ - to:
+ uri: "http://localhost:{{camel.server.port}}/payment"
+ - log:
+ message: "Refund ${variable.refundId} paid back: ${body}"
diff --git a/fail-well/chaos/test/chaos.citrus.it.yaml
b/fail-well/chaos/test/chaos.citrus.it.yaml
new file mode 100644
index 0000000..627510b
--- /dev/null
+++ b/fail-well/chaos/test/chaos.citrus.it.yaml
@@ -0,0 +1,42 @@
+name: chaos-test
+description: The chaos example runs its scenario, takes the payment service
down and brings it back, and still ships orders
+actions:
+ - camel:
+ cli:
+ run:
+ integration:
+ name: "chaos"
+ file: "../chaos.camel.yaml"
+ systemProperties:
+ file: "../application.properties"
+ resources:
+ - "parked.camel.yaml"
+ - "test/chaos/payment-outage.chaos"
+ args:
+ - "--chaos=test/chaos/payment-outage.chaos"
+ - camel:
+ cli:
+ verify:
+ integration: "chaos"
+ logMessage: "Order ORD-1 shipped"
+ - camel:
+ cli:
+ verify:
+ integration: "chaos"
+ logMessage: "Chaos rule r3 suspended route payment for 10s"
+ maxAttempts: 30
+ delayBetweenAttempts: 1000
+ - camel:
+ cli:
+ verify:
+ integration: "chaos"
+ logMessage: "Chaos rule r3 resumed route payment"
+ maxAttempts: 20
+ delayBetweenAttempts: 1000
+ - camel:
+ cli:
+ verify:
+ integration: "chaos"
+ logMessage: "Order ORD-35 shipped"
+ maxAttempts: 20
+ delayBetweenAttempts: 1000
diff --git a/fail-well/chaos/test/chaos/payment-outage.chaos
b/fail-well/chaos/test/chaos/payment-outage.chaos
new file mode 100644
index 0000000..28ba946
--- /dev/null
+++ b/fail-well/chaos/test/chaos/payment-outage.chaos
@@ -0,0 +1,11 @@
+# A bad half minute at the payment service: for 20 seconds it answers 503 to
one call in four,
+# then it is down for 10 seconds. The shop must keep taking orders, ship every
order it charged,
+# and park the orders it could not charge instead of losing them.
+seed=42
+
+# one call in four fails for 20 seconds: the redeliveries recover them, and
the shop still takes 20 orders
+endpoint=http://localhost:8080/payment error status=503 rate=0.25 until=20s
expect=orders:20 within=25s
+# shipping fails one message in five for 15 seconds: its redeliveries still
ship every order charged
+route=shipping error rate=0.2 until=15s expect=orders->shipping:lost<=0
within=20s
+# then the payment service is down for 10 seconds: every order is charged or
parked, none fails
+route=payment stall duration=10s from=+20s expect=orders:failed<=0 within=15s
diff --git a/generate-catalog.sh b/generate-catalog.sh
index 9fa9660..840921f 100755
--- a/generate-catalog.sh
+++ b/generate-catalog.sh
@@ -93,6 +93,14 @@ for dirpath, dirnames, filenames in
sorted(os.walk(repo_root)):
rel = os.path.relpath(os.path.join(sub_dirpath, f), dirpath)
if os.path.relpath(os.path.join(sub_dirpath, f), repo_root)
not in ignored:
files.append(rel)
+ # the chaos scenarios of test/chaos are files the example runs with (camel
run --chaos=test/chaos/name.chaos)
+ chaos_dir = os.path.join(dirpath, "test", "chaos")
+ for sub_dirpath, sub_dirnames, sub_filenames in os.walk(chaos_dir):
+ sub_dirnames[:] = [d for d in sub_dirnames if not d.startswith(".")]
+ for f in sub_filenames:
+ rel = os.path.relpath(os.path.join(sub_dirpath, f), dirpath)
+ if not f.startswith(".") and
os.path.relpath(os.path.join(sub_dirpath, f), repo_root) not in ignored:
+ files.append(rel)
files.sort()
requires_docker = ("compose.yaml" in filenames or "docker-compose.yaml" in
filenames
@@ -146,7 +154,7 @@ GROUPS = [
("run", "Run", "Running Camel: timers and cron schedules, a bean in a
route, properties and profiles."),
("transform", "Transform and map", "JSON, XML and CSV in and out,
field-by-field mapping, Groovy and XSLT."),
("route", "Route", "The routing patterns: content-based router, splitter,
aggregator, filter and multicast."),
- ("fail-well", "Fail well", "Retries, a dead letter channel, and a circuit
breaker in front of a flaky service."),
+ ("fail-well", "Fail well", "Retries, a dead letter channel, and a circuit
breaker in front of a flaky service, then chaos rules that break the routes on
purpose to prove it."),
("connect", "Connect without a service", "Files, an HTTP client and a REST
server; everything runs inside the example."),
("connect-service", "Connect to one service", "SQL, JMS, MQTT, Kafka and
FTP against a service the Camel CLI starts for you with `camel infra run`."),
("contracts", "Contracts and security", "An OpenAPI contract served and
called, and an API protected by Keycloak."),