Repository navigation
test(two-node): simulator of the two-node HA campaign in CI - #1488
Merged
Merged
Conversation
| # RPC routing (only RPCClient._request is replaced) | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| class SimRPCClient(RPCClient): |
| def rpc_distr_status_events_update(self, **_): | ||
| return True | ||
|
|
||
| def rpc_bdev_get_bdevs(self, name=None, **_): |
| _local.task = self | ||
| try: | ||
| self.result = self.fn() | ||
| except BaseException as e: # noqa: BLE001 - handed to on_done |
The AWS two-node campaign (c1-c7b) found 18 bugs that no unit or integration test caught: interaction and timing bugs between the arbiter, the node's HA state machine, SPDK's fence/ANA/port handling and the control-plane flows. tests/integration/two_node_sim runs the bed's scenarios and one regression scenario per finding against the real code. - Real: the arbiter (decision, events, planned loss), its default helpers, every RPCClient wrapper (only _request is routed), the port guards the flows consult, the ANA helpers, task records, real FDB. - Modelled: the two nodes (JC HA state machine ported from ultra alg_two_node_ha.cpp, SPDK fence/unfence/port ownership/ANA/takeover latch), the network, the multipath client, the Go collector, and the shells of the restart/port-allow/migration/monitor/shutdown flows around the real guards. - Virtual time: no real sleeps or timeouts. RPCs on the decision loop delay the next verdict (F16); pool and recovery calls run on cooperative threads parked on the virtual clock, so their side effects land when the call returns. Deterministic. - Invariants: one writer per LVS, no stale node serving while the up-to-date one is down, grant within lease TTL + margin, client stall limits, no IO errors outside partitions, no unexpected abort, healthy again without a manual step. - Proof: per finding, the scenario passes on main and fails, with the finding's signature, when its bug is put back (node switch, or the real function patched to its pre-fix behaviour). F17/F18 (open) are strict xfails checked against their failure signature; F9/F11 not modelled. CI: "Two-node simulation" runs the fast tier (tox -e two-node-sim), and "Two-node simulation (full)" everything (tox -e two-node-sim-full). The default integration run excludes the simulator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… bed stall metric, F19 xfail Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
schmidt-scaled
force-pushed
the
test/two-node-simulator
branch
from
October 15, 2026 14:39
b9f01f1 to
04d241f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The AWS two-node campaign (c1–c7b, 2026-10) found 18 bugs (F1–F18) that no unit or integration test caught. They sat in the interplay of the arbiter, the node's HA state machine, SPDK's fence/ANA/port handling and the control-plane flows, and in their timing. This PR adds
tests/integration/two_node_sim: the bed's scenarios and one regression scenario per finding, run against the real code on simulated nodes and a real FoundationDB, in CI. Test code only; no product change.What runs for real, what is modelled
arbitration/arbiter.py,decision.py,events.py,planned.pyalg_two_node_ha.cpp(hold, lease, epochs/ESTALE, EBUSY/EAGAIN, abort-on-wake, the remote-JM watchdog), and SPDK's lvol side (fence/unfence with port ownership, takeover latch, ANA groups, devices)RPCClientwrapper; onlyRPCClient._requestis routed_single_peer_jm_is_complete,_two_node_fence_holds_ports,_may_unblock_peer_port,_jc_compression_status_with_retry,_failover/_failback_primary_ana,_set_lvol_ana_on_nodetasks_controllertask records,set_node_status, all FDB reads/writes (real FDB, as the integration tier requires)Virtual time: no real sleeps or timeouts. RPCs on the decision loop delay its next verdict, which is exactly the F16 failure. Pool and recovery calls run on cooperative threads parked on the virtual clock, so their side effects land when the call returns. Runs are deterministic.
Invariants
bdev_lvol_update_lvstore/set_leaderreaches the survivor while its peer is gone (F17, fix(two-node): remove promote-at-grant (c7b F17) #1485);it/analyze.pymax_stall_after_inject_s): the longest run of whole seconds of fio's 1 s IOPS log without a completion, from the injection. Faults are injected 0.5 s into a second (the bed's alignment was random);Scenarios and findings
01a 01b 02a 02b 03 05 05b 07 08 10b04-shutdown-sn2F19-long-freeze-primaryF18-ana-groups-restoredsimnode.RESTORE_ALL_ANA_GROUPS)F1 F2 F3 F4 F5 F6 F7 F8 F10 F12 F13 F14 F15 F16 F17test_finding_is_fixedpasses on main;test_finding_is_detectedfails with the finding's signature when its bug is put back (a node switch, or the real function patched back to its pre-fix behaviour, e.g. F16 = the probe inline on the decision thread with 5 s + retry; F17 = #1476's promote-at-grant sent after a degraded grant). F1/F2/F3/F4/F10/F16 also check that the fixed run reached its critical step.Proof that the detection works: every
test_finding_is_detected[*]passes, i.e. each scenario fails for its own reason with the bug back in. For layered fixes the switch removes every layer: F5 = node watchdog + lease-expiry grant; F12 = abort-on-wake + device check + stuck-rebuild recovery; F13 = abort-on-wake (fence stall).New finding F19 (not fixed here)
Since #1485 removed promote-at-grant, nothing moves a frozen primary's LVS to the granted survivor. Its volume's IO resumes only when the survivor's hublvol redirect to the frozen peer fails at the SPDK-to-SPDK keep-alive (
constants.KATO, 5 s), or when the frozen node aborts on wake. Repro:pytest tests/integration/two_node_sim -k F19(sn2 hung 6 s, grant at 1.93 s): stall 4 s (gap 5.15 s), no IO errors; a 10 s freeze gives the same. On the bed, c7b (with promote-at-grant) stalled 2 s and c5 02b 3 s. 02b itself still passes because its freeze is shorter than the keep-alive. Fix direction: on a solo grant stop redirecting to the lost peer, without a leadership RPC on the takeover path that F17 forbids.CI and runtime
tox -e two-node-sim): the fast tier, meant to be required on every PR. It runs smoke, 01b, 02b, 07 and the F3/F16 proofs.tox -e two-node-sim-full): everything.-m "not slow and not two_node_sim").No real waiting remains. The wall time is FDB round trips of the real code: the arbiter alone does about 13 reads per 100 ms tick, about 6 of them of the same
ClusterArbitrationrecord, which is worth a look on its own. Measured locally on WSL2 with about 2 ms per FDB round trip:Making the fast job required is a branch-protection setting, which needs a repo admin.
See
tests/integration/two_node_sim/AGENTS.mdfor the model, its limits and how to add a finding.🤖 Generated with Claude Code