Files
fips/testing/chaos/scenarios/cost-avoidance.yaml
T
Johnathan Corgan 2bc0345174 Make the cost-based chaos assertions reliable under parallel CI load
cost-avoidance and mixed-technology decide a parent by measured link cost
(etx * (1 + srtt_ms/100)). Under the local CI's 4-way-parallel chaos the fast
fiber link's measured srtt spikes from host scheduling and can exceed the
Bluetooth link's, flipping the choice and reddening a run that proves nothing
about the daemon. The old margin was ~0.4 cost units, which a ~24ms one-sided
scheduling blip closes.

Widen the Bluetooth delay to 150-250ms so the margin dwarfs any plausible
one-sided spike. The link still establishes well within the handshake budget
(a ~500ms RTT against a 30s stale-handshake window), so the losing candidate
is still a real, established peer rather than an absent one.

Add a netem mutation exclude_edges option and use it in mixed-technology to
pin the two links n08 chooses between, so a random degradation can't flip the
asserted comparison either. An unknown excluded edge is a hard error, since a
silent no-op would reintroduce the flakiness it exists to remove.

Validated by running both scenarios repeatedly under heavier-than-CI
concurrent load: the parent choice holds every time.
2026-07-23 18:58:49 +00:00

97 lines
3.1 KiB
YAML

# Cost-Based Parent Selection: Bottleneck Avoidance Test
#
# Topology (explicit 4-node diamond):
#
# n01 (root — smallest addr)
# / \
# fiber fiber
# / \
# n02 n03
# \ /
# BT fiber
# \ /
# n04 (test subject)
#
# n04 has two candidate parents at depth 1: n02 (via Bluetooth L2CAP)
# and n03 (via fiber). Cost-based selection should pick n03 because
# link_cost = etx * (1 + srtt_ms/100), so the far slower Bluetooth link
# costs far more:
# effective_depth(n02) = 1 + ~4.5 (Bluetooth, ~150-250ms) = ~5.5
# effective_depth(n03) = 1 + ~1.05 (fiber, ~1-5ms) = ~2.05
#
# The Bluetooth delay was widened deliberately, and the size is set by load,
# not by realism. At the original 15-40ms the cost margin was ~0.4, and even
# 80-120ms was not enough: under the CI's parallel-chaos contention the
# *measured* srtt of the fiber link spikes (host scheduling, not netem) and
# was observed to exceed a 120ms Bluetooth base, flipping n04 to n02. At
# 150-250ms the gap dwarfs any plausible one-sided fiber spike, so the
# assertion below is a reliable verdict rather than a coin toss under load.
# n04 still establishes the n02 link (it was parented to it when this flaked),
# so the choice remains real rather than vacuous.
#
# Validation: tree snapshot shows n04's parent is n03.
scenario:
name: "cost-avoidance"
seed: 42
duration_secs: 45
topology:
algorithm: explicit
num_nodes: 4
params:
adjacency:
- [n01, n02]
- [n01, n03]
- [n02, n04]
- [n03, n04]
subnet: "172.20.0.0/24"
ip_start: 10
netem:
enabled: true
default_policy:
# Fiber-like
delay_ms: [1, 5]
jitter_ms: [0, 1]
loss_pct: [0, 0.5]
link_policies:
# Bluetooth (L2CAP) link from n02 to n04. Delay widened to 150-250ms so the
# fiber-vs-Bluetooth cost margin survives CI load spikes on the fiber
# probe's measured latency (see header). Well within handshake timeouts, so
# the n02 link still establishes and the choice stays real.
- edges: ["n02-n04"]
policy:
delay_ms: [150, 250]
jitter_ms: [15, 30]
loss_pct: [2, 8]
link_flaps:
enabled: false
traffic:
enabled: true
max_concurrent: 2
interval_secs: {min: 15, max: 30}
duration_secs: {min: 5, max: 10}
parallel_streams: 2
# The validation line at the top of this file, encoded. It existed only as
# that comment and was checked by nothing, so the scenario could not fail
# on the one thing it was built to test.
#
# From the archived corpus: of the six runs that carry a status.txt and are
# therefore provably completed, n04's parent is n03 in all six. Among the
# 176 older runs the picture is quite different -- n04 is absent from the
# snapshot in 107 of them and is its own root in 49 -- but those are runs
# whose tree had not converged when the snapshot was taken rather than runs
# that chose the wrong parent, which is why the assertion fails an absent
# or self-parented node explicitly instead of skipping it.
assertions:
tree_parents:
n04: n03
logging:
rust_log: "info"
output_dir: "./sim-results"