Chaos
Since Camel 4.24
The camel-chaos module lets a developer turn on faults in a running Camel application, such as "fail 30% of the sends to kafka:orders for one minute" or "stall route poller for 20 s", and see how the routes cope: whether onException, redelivery and the dead letter channel do what they should. A report then says per route how many exchanges completed, failed or were handled by the error handler, and, for a rule with an expectation, how many messages were lost, with a verdict.
| Chaos is for development only. It stays off with profile |
Getting it
-
camel run: add the dependency with--chaos(or--dep camel:chaos), with the default runtime or--runtime=main(not with Spring Boot or Quarkus).camel runuses profiledevby default, which switches chaos on. Then add rules while it runs withcamel cmd chaos-add(see Control while it runs). -
A plain
Mainor a test: add thecamel-chaosdependency and setcamel.chaos.enabled=true, for example in aCamelTestSupporttest withcamelContextConfiguration.withUseOverridePropertiesWithPropertiesComponent(props), wherepropshascamel.chaos.enabled=true(andcamel.chaos.seedfor a fixed sequence). -
Spring Boot and Quarkus: the plugin works with any
CamelContext. It readscamel.chaos.from the properties component, socamel.chaos.enabled=truein the application properties switches it on there too; but thecamel.chaos.options are camel-main options, and only camel-main checkscamel.chaos.rulesagainst the security policy (see below).
Switching it on
With the camel-chaos JAR on the classpath, chaos is switched on with profile dev (the default of camel run), or with camel.chaos.enabled=true (for a plain Main, a test or a deterministic simulation test). It is never switched on with profile prod, also when camel.chaos.enabled=true. Without a profile, the usual case for a plain Main, Spring Boot and Quarkus, chaos stays off unless camel.chaos.enabled=true.
With camel-main, when camel.chaos.* options are set and chaos would be switched on (profile dev or camel.chaos.enabled=true), but the camel-chaos JAR is not on the classpath, camel-main logs a WARN that the options are ignored.
When chaos is switched on, it installs its hooks when the CamelContext initializes, before the routes are created, and logs a WARN that it is on. The hooks do nothing until a rule is added. When chaos is off, nothing of it is installed (only the lifecycle strategy that made this decision stays, and does nothing), and camel.chaos.rules is ignored.
camel.chaos.rules is marked insecure:dev: with camel-main and profile prod (which sets camel.security.policy=fail) an application with rules configured refuses to start. Without camel-main this check does not run, and chaos stays off with profile prod anyway. Rules set from Java (chaosConfig().withRules(…)) are not checked either, as for every insecure:dev option.
Options
The options are read from the properties component, and are camel-main options (ChaosConfigurationProperties).
| Option | Default | Description |
|---|---|---|
|
| Install the hooks. Ignored with profile |
| The seed of the random draws of the rules. Without it a seed is picked at startup, and the report shows it. | |
| Rules to add at startup, separated by | |
|
| The longest time a rule runs: a rule without an |
|
| Log the report when the context stops, if any rule was added. |
For example, in application.properties:
camel.chaos.seed = 42
camel.chaos.rules = \
endpoint=kafka:orders* error exception=java.net.ConnectException message="broker down" rate=0.3 until=1m;\
endpoint=https:inventory* error status=503 header.Retry-After=5 rate=every:4 until=50;\
step=enrich* delay duration=500ms rate=0.1;\
route=poller stall duration=20s Rules can also be added, changed and removed from Java while the application runs:
ChaosManager chaos = ChaosManager.get(camelContext);
String id = chaos.addRule("endpoint=file:inbox* drop at=poll rate=0.5 until=2m");
chaos.changeRule(id, "endpoint=file:inbox* drop at=poll rate=0.2 until=2m");
List<String> warnings = chaos.checkRule(id);
chaos.removeRule(id);
List<String> ids = chaos.addRules("endpoint=seda:dlq error at=dlq; endpoint=https:inventory* error status=503");
String text = chaos.report().toText(); addRules adds rules separated by ;, as in camel.chaos.rules, all of them or none when one is not valid, so they are in place at the same time. checkRule says what a rule matches nothing of in the context now, see below.
Control while it runs
With the Camel CLI, against a running integration (myapp is its name or pid):
camel cmd chaos-add myapp --rule='endpoint=kafka:orders* delay duration=2s rate=0.2 until=5m'
camel cmd chaos-add myapp --rule='endpoint=seda:dlq error at=dlq' --rule='endpoint=https:inventory* error status=503'
camel cmd chaos-change myapp --id=r1 --rule='endpoint=kafka:orders* delay duration=5s rate=0.5 until=5m'
camel cmd chaos-remove myapp --id=r1
camel cmd chaos-clear
camel cmd chaos-clear myapp --reset
camel get chaos
camel get chaos myapp --report chaos-add prints the id of the new rule (r1, r2, …), and chaos-change the version of the rule that replaces it. --rule can be given more than once (or a rule can hold several separated by ;): the rules are added together, all or none, which matters when one must be in place before the other fires, as each chaos-add takes a moment. The ids are per integration, so chaos-add, chaos-change and chaos-remove need the name or pid of one integration; chaos-clear without one acts on every running integration that has chaos switched on. The rules that chaos-clear removes stay in the report; with --reset the report starts again, for a new experiment: no rules, route counts or verdict, as for an application that just started (with the same seed, and the ids of new rules going on from the last one). An id that does not exist is "not found" (exit code 3). camel get chaos lists the current rules with their id, version, state, matches and faults, the observed rate (RATE), when the rule last fired (LAST) and the effect of its faults (EFFECT), and --report shows the report, with the recent faults (also as JSON with --json); see The live state of the rules. Their NOTE column says what a lifecycle rule does to its route (route poller down, back in 12s) and why a rule did nothing when it was skipped. Without a rule to show, camel get chaos says so on stderr (no rules, chaos off, or no integration with camel-chaos).
A rule that matches nothing in the integration now is added with a warning, which chaos-add and chaos-change print, and which is logged: an endpoint= or component= pattern that matches no endpoint of the context, an at=poll for endpoints that no route consumes with a scheduled poll, a route= or step= pattern that matches no route or step id, and an expectation of a route that does not exist. Such a rule fires 0 times, and is often a typo; the warning names some of the endpoints, routes or steps there are. It is not an error, as the routes may still change (a toD creates its endpoints when it sends, and a route can be added later). The rules of camel.chaos.rules are checked when the context has started.
The commands go through the chaos action of the CLI connector, which calls ChaosManager: a rule goes through the same parser as camel.chaos.rules, and one that is not valid is refused with the reason. They work only when chaos is switched on in the integration (see Switching it on): they cannot switch it on, and with profile prod they are refused.
The chaos developer console (/q/dev/chaos with camel run --console) shows the same rules and report, as text or JSON. It only reads: a rule cannot be added, changed or removed over HTTP.
From an AI agent (MCP)
The Camel MCP server (camel mcp, see Camel MCP server) has the same six operations as tools, for an AI agent that tries a route against failures: camel_runtime_chaos_list, camel_runtime_chaos_report, camel_runtime_chaos_add, camel_runtime_chaos_change, camel_runtime_chaos_remove and camel_runtime_chaos_clear. They take the same one-line rules: adding, changing, removing and clearing go through the same chaos action of the CLI connector, and the list and the report read the console from the status of the application. So chaos must be switched on, and a rule that is not valid is refused with the reason; camel_runtime_chaos_clear takes reset=true as chaos-clear --reset does. camel_runtime_chaos_add takes several rules separated by ;, and the add and change tools return the warnings of a rule that matches nothing, so an agent can fix a typo with a change. Adding and changing a rule are marked destructive, so a client that honours destructiveHint asks before it runs them, and the read-write access level of the MCP security layer does not offer them; the list and the report are read-only. camel ask has the same tools, and asks before it adds or changes a rule. The development MCP tools that camel run --mcp (camel.jbang.mcp=true) serves over HTTP have only the list and the report.
Rules
A rule is one line: the target, the fault and its parameters as name=value, then at, rate, from and until, and optionally an expectation. A value with spaces or a ; is quoted with " or ' (\", \' and \\ inside quotes are a quote and a backslash).
target: route=<id> | endpoint=<uri> | component=<name> | step=<id> (* matches any characters)
fault: error | delay | drop | corrupt | stall | lifecycle, with its parameters
at: send | reply | dlq | poll | entry | step (optional)
rate: a probability (rate=0.3), every Nth match (rate=every:5), or bursts (rate=burst:5s/30s: every match
during the first 5s of each 30s); without it every match fires
from: when the rule starts after it is added (from=+30s), such as after the warm-up; without it at once
until: a number of matches (until=100) or a time (until=2m), counted from the start; without it
camel.chaos.maxDuration
time: a number with a unit (ms, s, m, h, d), also combined as 1m30s; a number alone is milliseconds
expect: expect=<route id>:<count> (at least that many completed exchanges), or
expect=<route id>:<kind><op><count> with kind completed | handled | failed | redeliveries
and op >= | <= | = (such as expect=orders:handled<=0); with an optional within=<time> The patterns of the targets are matched as text, with for any characters; they are not regular expressions. The match is case-sensitive and a can stand anywhere in the pattern, unlike the patterns of interceptSendToEndpoint, which ignore case and can be regular expressions: a rule, also one from a tool, cannot make matching slow. An endpoint= pattern is matched against the endpoint URI, with or without its query parameters, and the // after the scheme does not count (kafka:orders* matches kafka://orders?brokers=b1). A component= pattern is matched against the scheme of the URI only, so a pattern with a : (component=http:localhost) is rejected, with the endpoint= pattern to use instead. Some components give their endpoints the URI of their address: undertow:http://inventory/stock has the endpoint URI http://inventory/stock. When the scheme of the endpoint URI is not a name of the component in the context, chaos puts the default name of the component in front, and matches undertow:http://inventory/stock (with component=undertow, not component=http). A camel-netty-http endpoint has the endpoint URI netty-http://http://inventory/stock, and a camel-netty-http component added under another name is matched with netty-http in front too. A route= pattern is matched against the route id, and a step= pattern against the id of the step (to1, or the id set with id("enrich")).
A rule gets an id when it is added (r1, r2, …), which change and remove take. A change replaces the rule with a new one with the same id and the next version (a rule is version 1 when it is added), and the replaced one stays in the report: so the report has an entry per version of the id, and a tool tells them apart by the version of the entry (the latest has the highest). An until count counts the matches, so a rule with rate=every:N and an until count below N would never fire, and is rejected. A rule ends by itself after its until, at the latest after camel.chaos.maxDuration, so a forgotten rule cannot keep breaking a development instance; a stall or lifecycle rule ends when its route is back (see Route lifecycle faults). Rules stay when the routes are reloaded.
A rule with from=+30s starts 30 s after it is added (a rule of camel.chaos.rules 30 s after the context started), for faults after the warm-up of the application; until then it is waiting, and the matches do not count. Its until time counts from its start, so from=+30s until=1m fires from 30 s to 1 m 30 s after it is added. from is at most camel.chaos.maxDuration. A rule with rate=burst:5s/30s fires at every match during the first 5 s of each 30 s, counted from its start, and at none in between: outage windows, instead of the even spread of rate=0.17. The burst must be shorter than its period, and the period at most camel.chaos.maxDuration.
Targets and hooks
Each target has hooks, and each hook offers some of the faults. A rule fires at every hook of its target that offers its fault; at= narrows it to one hook. A rule whose at is not a hook of its target, or whose fault no hook of its target offers, is rejected when it is added, with an error that names the hooks of the target.
| Target | Hooks (at=) | Faults |
|---|---|---|
| The sends to a matching endpoint ( |
|
| The messages that enter the route ( |
|
| The matching steps ( |
|
So endpoint=file:inbox* drop skips the polls of the file:inbox consumer and makes the sends to file:inbox report success without writing; at=poll or at=send keeps one of them. route=orders error is rejected: the entry of a route offers only corrupt, and route= does not reach the sends and steps of the route (they are targeted with endpoint=, component= or step=). A rule that matches nothing, such as a poll rule for a consumer that does not poll on a schedule (Kafka, JMS), fires 0 times, as the report shows.
When several rules match the same send, poll, message or step, they are asked in the order they were added, and the first that fires decides the fault.
Faults
| Fault | Parameters | Effect |
|---|---|---|
|
| Fails the send or the step with that exception (with the message when the class has a constructor taking a With |
|
| The thread that sends, runs the step or polls waits that long first, as for a slow endpoint. With a range, each delay is a draw between the two times (both included), from the random sequence of the rule, so a seed gives the same delays again. |
| none | A send that reports success without calling the producer, or a poll that is skipped. |
|
| Changes the message: at a send, the exchange that is sent, so the endpoint gets the changed message, and after a |
|
| Suspends, stops or removes the route of a |
|
| Short for |
Where the faults are raised
| Hook | Where |
|---|---|
| Every send to an endpoint that fires |
| The same sends, after the producer succeeded, through the send hook of camel-core (its |
| The send of the dead letter channel ( |
| The polls of the scheduled poll consumers of the routes, around their poll strategy (also one set with |
| Each exchange that enters a matching route (a route policy, |
| Each step of a route, through the step hook of camel-core, right before the processor of the step is called, once for each attempt. So an |
The steps inside onException and doCatch are steps of the route, and the sends there (other than the one of the dead letter channel) are sends: so step= also fails the steps that handle a failure, and endpoint= the sends of onException. Name the endpoints or steps under test in the pattern to keep them out.
Route lifecycle faults
stall and lifecycle act on a route, not on its messages: route=poller stall duration=20s suspends route poller and resumes it 20 s later; route=orders lifecycle action=stop duration=1m stops and starts it again; route=orders lifecycle action=remove duration=10s removes it and adds the same route definition back. Each rule acts when it is added (a rule of camel.chaos.rules when the context has started), or from= later (route=poller stall duration=20s from=+1m), so at, rate and until are rejected; an expectation (expect=, within=) can be added.
A flapping route goes down more than once: route=orders stall duration=10s repeat=3 every=1m suspends route orders for 10 s three times, each time a minute after the last one went down. every must be longer than the duration, so the route is back before it goes down again, and the rule must be done within camel.chaos.maxDuration ((repeat - 1) * every + duration); repeat is at most 100. Each round acts as the first: a route that someone else stopped meanwhile is left as it is, and the rule ends there (skipped). Removing the rule between two rounds ends it, with the route as it is (back). The route= target is the id of one route, not a pattern, and the route must exist when the rule is added: a rule for a route that does not exist is rejected (with camel.chaos.rules the context does not start), and so is a duration longer than camel.chaos.maxDuration. While a lifecycle rule has a route removed, a rule for that route is still accepted (to change the rule, for example).
The action and the comeback run on a scheduled executor of the ExecutorServiceManager of the context (one thread, whose name ends with Chaos, created when the first such rule acts), not on the thread that adds the rule, so adding the rule returns at once, even while the route waits for its exchanges in flight to finish. The actions and comebacks of all rules run one after another on that thread, so a comeback can wait for the action of another rule, such as a stop that waits for its exchanges in flight; a rule that replaces one whose route is down acts after that route came back. The route is suspended, stopped, resumed and started through the route controller of the context (CamelContext.getRouteController()), and removed with CamelContext.removeRoute. A route that cannot be suspended (its consumer does not support it) is stopped and started instead, as Camel does for any suspend.
Chaos only brings back what it took down:
-
A route that is not started when the rule acts (stopped or suspended by someone, or not started yet) is left as it is: the rule does nothing, and the report says so (
skipped, with the reason). -
At the comeback, a route that someone else changed meanwhile (stopped a suspended route, started a stopped one, removed it, or loaded a new route with the same id, as a reload in dev mode does) is left as it is (
left, with the reason). -
Removing the rule (or changing or clearing it) while the route is down brings the route back at once.
-
When the context stops while a route is down, the comeback is cancelled and the executor is shut down, so nothing of chaos is left running; the report says that the route was not brought back (
cancelled). A removed route then stays removed, also when the context is started again; a suspended or stopped route is as the start of the context leaves it. A rule that has not acted yet does not act. -
A failure of the action or the comeback (such as a route that fails to start) is logged and reported (
failed). When the action fails halfway and leaves the route stopped or suspended (a remove that could stop the route but not remove it), chaos brings it back after the duration as forstoporsuspend(the state is thendownandback, notfailed), and the reason says so.
With the supervising route controller (camel.main.routeControllerSuperviseEnabled=true), a stop or suspend by chaos is a manual operation for the controller: it does not restart the route while it is down, and the comeback starts the route through the controller, which supervises it again (and retries a start that fails, with its back-off). A route that action=remove adds back is started through the route controller too, when adding its definition did not start it (the supervising controller takes a route that is added again under supervision, but does not start it), so it is supervised again as well. The supervising controller starts the routes in the background, so a rule of camel.chaos.rules can find its route not started yet, and then does nothing; add such a rule once the route runs.
action=remove needs the route definition of the route, so it works for the routes of the route model (Java, XML and YAML DSL). The definition that is added back is the one that was removed, as it was prepared, so the route comes back with the route configurations it had. A route created from a route template or a kamelet is rejected for action=remove (removing it closes the local beans of the template); stop and stall work for it.
The report shows what each lifecycle rule did, with the times after the rule was added, for example:
r3 ended: route=poller stall duration=20s
matches 1, fired 1, skipped 0
suspended route poller at +4ms, resumed at +20s6ms (down 20s2ms) With repeat= the line tells the round, and between two rounds when the next one starts (round 1 of 3: suspended route orders at +2ms, resumed at +10s5ms (down 10s3ms); round 2 in 49s).
In JSON each action counts in fired.lifecycle, and the rule has a lifecycle object: action, route, duration, state (pending, acting, down, comingBack, back, skipped, left, failed or cancelled; pending also between two rounds), round (the times it took the route down), repeat and every (with repeat=), actsIn (the milliseconds until it acts, again), downAt and backAt (milliseconds after the rule was added, of the last round), backIn while the route is down, endedEarly when the rule was removed while the route was down, and the reason when it did not go as planned. camel get chaos --json and the chaos dev console give the same object.
The report
ChaosManager.report() gives the report, as plain text (toText()) for a person and as JSON (toJson()) for a tool, and it is logged when the context stops (camel.chaos.reportOnShutdown), if any rule was added. It states the seed, each rule with its id and version, its matches, the faults that fired at each hook and the ones that were skipped (with the reasons, such as a stream body, counted in skippedReasons), its state (active, waiting for its start with from=, ended, removed or replaced), and per route. The rules are in the order of their ids, and the versions of an id from the latest. Per route:
-
completedandfailed: the exchanges that went through the route, each counted once, with how the route was done with it the last time. An exchange counts in every route it goes through: a route called withdirectcounts it too, the copy of a wire tap or of a seda queue counts in the route that consumes it, and the copies of a split or multicast count in the routes they are sent to. An exchange that goes through a route more than once (adirectroute called in a loop, or called again by a redelivery of the calling route) is still one exchange of that route: a failed pass and then a completed one count as one completed exchange. A message that fails and is read again by its consumer is another exchange: one failed and one completed exchange. An exchange marked rollback only counts as failed (kept by the consumer only for the JMS consumers below, withmarkRollbackOnly). Theentryfaults of a route are asked once per exchange too. -
failedKeptByConsumer: the failed exchanges whose message the consumer of the route keeps and reads again:file,ftp,sftp,ftpswithoutmoveFailed;jms,activemq,activemq6,amqpwithtransacted=true, or withacknowledgementModeName=CLIENT_ACKNOWLEDGE(the endpoint option; the number set withsetAcknowledgementModein Java is not seen) andasyncConsumer=false;kafkawithbreakOnFirstError=true. The options are read through the configurer of the endpoint, so options set on the component count too. The component is known by the scheme of the endpoint URI. -
handled: the completed exchanges after the error handler handled a failure (the dead letter channel, oronExceptionwithhandled(true)). A failure handled in a route called withdirectcounts as handled in the calling routes too. -
redeliveries: the redelivery attempts of the error handler, in the route that runs the step it redelivers.
The routes are counted while there are rules (also ended ones). Camel does not call the route policies while the context stops, so the exchanges that finish while the context drains them are not counted.
Without an expectation the verdict is PASS, as chaos makes exchanges fail on purpose, and the report does not say that a message was lost, except for the messages that a rule with at=dlq kept from reaching the dead letter channel, which then handled them: those count as lost (in the report of the rule as lostAtDlq), and make the verdict FAIL, with the reason. An expectation, expect=orders:100 within=2m, says that route orders completes 100 more exchanges after the rule is added and before the time is up (by default until the rule ends: its from plus its until time, else camel.chaos.maxDuration, which is also the longest within after the start of the rule). An exchange of the route counts once, the first time it completes in the route. An exchange that the error handler handled (for example one that went to the dead letter channel) completes, and so counts as completed, not as lost. The route id is not checked when the rule is added, as the route may come later (a WARN says so when a rule is added or changed, or the context starts, and no route has that id). An expectation is met when the count is reached in time, and unmet when the time is up without it: then the verdict is FAIL, with the reason, and the missing exchanges count as lost (100 expected, 97 completed: 3 lost). Before that it is pending, and so is the report (isFinal() is false): a tool must treat a PASS with a pending expectation as not decided yet. An expectation is cancelled when its rule is removed or changed first, or when the context stops first (also when the context is started again): the messages may still arrive, so it is neither met nor unmet, and it does not keep the report from being final.
Expectations on the error handler
An exchange that the dead letter channel handled counts as completed, so expect=orders:100 is met while every message goes to the dead letter channel. To check the error handler itself, an expectation names what it counts and how, expect=<route id>:<kind><op><count>:
| Kind | Counts, in the route, after the rule is added |
|---|---|
| The exchanges that completed (also after the error handler handled a failure); |
| The completed exchanges after the error handler handled a failure: the dead letter channel, or |
| The exchanges that failed (also marked rollback only). |
| The redelivery attempts of the error handler, in the route that runs the step it redelivers. |
The op is >= (at least), ⇐ (at most) or = (exactly). At least is met as soon as the count is reached, and unmet when the time is up without it. At most and exactly are unmet as soon as the count goes over, and decided when the time is up otherwise: an at most is met then, an exactly when the count is that many. Each exchange counts once per kind (the first time it completes, is handled or fails in the route).
For example, with redelivery that should recover from a broker that fails one send in ten:
camel.chaos.rules = endpoint=kafka:orders* error rate=0.1 until=2m expect=orders:handled<=0 The verdict is FAIL as soon as one message goes to the dead letter channel, with the reason (route orders: 1 exchange handled by the error handler within 2m0s, at most 0 expected (rule r1)), and PASS when the 2 minutes are up without one. expect=orders:handled>=5 checks that 5 messages reach the dead letter channel, also when it is an endpoint that no route consumes, and expect=orders:redeliveries⇐30 that redelivery does not retry too much. Only an unmet expectation of completed exchanges (at least or exactly) counts the missing exchanges as lost; the others give their reason only.
In JSON an expectation has its kind, op, count and the value counted so far (and completed, the same value, for an expectation of completed exchanges).
The live state of the rules
While the rules run, the report tells how each one is doing, and what its faults do:
-
the observed rate next to the rate of the rule: the share of the matches it fired at (
rate 4.8% of 5%,rate 20% of 1 in 5forrate=every:5, andrate 15% of 5s in 30sforrate=burst:5s/30s), forrate=every:Nthe matches until it fires again, and forrate=burst:how long its burst lasts or when the next one starts; -
when it last fired, and its faults per second over the last minute;
-
the effect of its faults on the exchanges they hit, each exchange counted once when it is done:
completed(without a redelivery after the fault, such as after adelay),recoveredby a redelivery of the error handler,handledby the error handler (such as the dead letter channel),failed, and the others still in flight. Only an exchange with a unit of work counts (one that a route processes), as that tells when it is done. For a lifecycle rule (orstall), the exchanges sent to the endpoint of its route while the route is down count, such asdirect:inventoryorseda:shipping(not an HTTP call to aplatform-httproute, which is another endpoint): a 3 second stop of a route called withdirect:inventory?block=falsethat sends 5 orders to the dead letter channel shows as5 handled by the error handler, as the redelivery is too short to ride it out. A circuit breaker runs its steps on a copy of the exchange: a fault in it counts with how the exchange was done, so a fault that the fallback took over shows ascompleted, and a message that the circuit breaker runs again at a redelivery counts once; -
the last 20 faults of all rules, the latest first: when, the rule, the fault, the hook, the route, the endpoint (without its query) or the step, and the exchange id.
An exchange that a rule faulted (or that was sent to the route of a lifecycle rule while it was down) gets the exchange property CamelChaosRule with the id of the rule (of the last one, when more rules did), so the tracer, the message history and a route of the dead letter channel can tell why it is there. When the error handler redelivers the exchange, the property is removed, as the faulted attempt is tried again: a later failure that chaos did not make is not marked (a fault at the redelivery marks it again):
r1 active: endpoint=kafka:orders* error rate=0.05 until=10m
matches 1250, fired 61 (send 61), skipped 0, ends in 7m12s
rate 4.9% of 5%, last fired 2s ago, 6 faults in the last minute
effect on 58 exchanges: 55 recovered by redelivery, 2 handled by the error handler, 1 in flight
recent faults:
2s ago r1 error ChaosException at send kafka://orders, route orders, exchange 4F2A... In JSON a rule has rate (the one of the rule: 1 without rate=, the share of each period in a burst for rate=burst:) and observedRate (once it matched), nextFireIn (the matches until a rate=every:N rule fires again), burstEndsIn or nextBurstIn (milliseconds, for an active rate=burst: rule), startsIn (milliseconds, for a rule that waits for its start), lastFiredAgo (milliseconds), faultsLastMinute, faultsPerSecond (60 counts, from the oldest second to the current one, when it fired in the last minute) and effect (faulted, completed, recovered, handled, failed and inFlight, once it faulted an exchange); a lifecycle rule has no rate. The report has recentFaults, each with timestamp, ago, rule, version, fault (such as error status 503 or delay 2s), hook (none for a lifecycle rule), route, endpoint or step, and exchangeId (none at a poll). ChaosManager.reset() (chaos-clear --reset) clears the recent faults too.
Time and the seed
The delay fault (and an error with after=) waits through TimeSource of camel-util, and the start and the end of a rule, its bursts, the time of an expectation and the times of a lifecycle rule are read through it too; the action and the comeback of a lifecycle rule are tasks of a scheduled executor of the ExecutorServiceManager. So in a deterministic simulation test (camel-test-dst) they run in virtual time: a 10 minute stall takes milliseconds, and with camel.chaos.seed set to the seed of the run, the same seed gives the same report again, times included. Chaos does not depend on camel-test-dst.
ChaosLifecycleDstTest in the tests of camel-chaos is such a test, and an example to copy: a route consumes a queue (a fake with a scheduled poll consumer) and calls a downstream system asynchronously (DstCamel.asyncStep, as a world needs for a route that is suspended or stopped while an exchange is in a downstream call). A stall or lifecycle rule, added while an exchange is in flight, takes the route down for up to 10 minutes while a step= rule fails the downstream call at a rate, and the test checks from the report that no message is lost (an expectation), that the redeliveries add up to the step faults, and that the route was down for exactly the duration.
The seed fixes the sequence of random draws of each rule (rate=<probability>, the delays of a range such as duration=100ms..2s, the random header values), not which exchange gets which draw: with several threads (concurrent consumers, parallel processing) the threads decide that, so two runs with the same seed can give different reports. With one thread and the same exchanges in the same order, the same seed gives the same faults.
The model of the rules
The rules are also described as data, for the tools that help to write them (a form in a terminal UI, an AI that turns a request into rules, the docs): META-INF/org/apache/camel/chaos/chaos-model.json in the camel-chaos JAR, also in the Camel catalog (CamelCatalog.chaosModelJSon()). It has the targets, the hooks of each target and the faults each one offers (and whether it is reached only with at=), the parameters of each fault with their types and limits, the options, the kinds and ops of an expectation, the limits, and recipes: known rules in plain words, grouped by what goes wrong, each a rule with placeholders (${endpoint}, ${rate}) and what to pick them from (the endpoints the routes send to, the routes, the steps). To guide the one who picks a recipe, each says what it does, what it can uncover, when not to use it, and what to watch while it runs, such as:
{
"id": "failed-after-success",
"group": "Downstream",
"title": "Did the work, then failed",
"description": "The send is made and succeeds, then the exchange fails, as when the reply is lost. The error handler sends it again, so the other side gets it twice",
"uncovers": [
"Duplicates: an order booked twice, a payment taken twice",
"Whether the other side or the route is idempotent, such as with an idempotent consumer or a key the other side checks"
],
"avoid": [
"Against a real service where a duplicate costs, such as one that charges or sends mail to people",
"With a high count: until=1 makes one send fail, which is enough to see one duplicate"
],
"watch": [
"What the other side received: two of the same",
"The redeliveries of the step"
],
"rule": "endpoint=${endpoint} error at=reply until=${until}"
} The model is made from the tables of the parser, so it says what the parser accepts; a tool still adds a rule through ChaosManager or the CLI connector, which check it as for any other rule.
Limits
-
A rule that ended stays in the rules (and the report) as
ended. The hooks skip it at once, and the next change of the rules (an add, change or remove) leaves it out of what the hooks read, as well as each expectation that is decided. -
At most 100 rules at a time. When there are 100, adding a rule first moves the rules that ended (and whose expectation is decided) into the report history, where their ids can no longer be changed or removed; the report keeps the last 100 removed, replaced or ended ones. An unmet expectation of a rule that leaves the report still counts in the verdict and in
lost. -
A rule is at most 2000 characters long.
-
There is one send hook and one step hook per
CamelContext: when another one is installed already, chaos keeps it, logs a WARN, and its rules do not fire at those hooks. -
A
stallorlifecyclerule acts on one route, once, orrepeat=times (at most 100). To take a route down again after that, add the rule again.