fix(runner-controller): stop the reaper warning about a detach that cannot succeed #311
Inga granskare
Etiketter
Inga etiketter
area/backups
area/ci
area/control-panel
area/identity
area/infra
area/observability
area/payments
area/security
area/storage
area/web
blocked
needs-info
needs-triage
ready-for-implementation
type
bug
type
chore
type
docs
type
epic
type
feature
type
task
wontfix
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Inget förfallodatum satt.
Beroenden
Inga beroenden satta
Referens
bitborg/bitborg-infra!311
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "fix/reaper-root-volume-noise"
Borttagning av en gren är permanent. Även om den borttagna grenen kan fortsätta existera en kort tid innan den faktiskt tas bort, kan det INTE ångras i de flesta fall. Vill du fortsätta?
Closes #305.
What it is
Boot volumes are created with
delete_on_termination=True, so the root volume cascades when the serveris deleted. The detach loop in
_delete_ephemeral_serveris belt-and-suspenders for extra attachedvolumes — but on every ordinary reap it also tried the root volume, which OpenStack always refuses:
Measured at 2–7 occurrences per hour, indefinitely, with different volume and server IDs each time.
Volumes are not leaking — checked before changing anything
gitborg_runner_controller_os_volume_gb_usedoscillates between 640 and 1020 GB over 7 days and returnsto a 640–720 baseline against a 5000 GB quota. It does not climb.
last_loop_okis 1,active_vmsandboot_error_vmsare 0. The volumes do get released, because deleting the server cascades the root device.Why fix it anyway
This is the code path whose failures are the signal for orphaned boot volumes — and a prior CI outage was
caused by 227 of them. A genuine reap failure arriving in the middle of a permanent stream of
identical-looking warnings, which everyone has learned to ignore, is exactly how the next leak goes
unnoticed. Removing a false alarm from an alerting path is worth more than the tidiness suggests.
How
A pure predicate,
_is_expected_root_volume_refusal(status_code, message), splits "expected, cascadesanyway" from "actually failed". Only a genuine failure stays a
WARNING; the root-device refusal logs atINFOand says that the server delete will cascade it.Kept pure and separate from the SDK exception so it can be tested on a bare interpreter, matching the
other decision predicates in this file. Matching on the message text is deliberate and noted inline:
OpenStack has no distinct error code for this, and 400 on its own is far too broad to treat as benign.
Verified
8 new checks in
test_controller_logic.py, following the existing dependency-free assert pattern —17/17 checks passed.Mutation-checked rather than merely passing. Replacing the predicate body with
return True— thedangerous over-broad direction, which would silence real failures — fails exactly the five cases that
matter:
Both files parse, and every line is inside
ruff.toml's 120-character limit.ruffitself is baked intothe runner image rather than available locally, so CI is the first place it actually runs — flagging that
rather than claiming a lint pass I did not perform.
Disclosure on ordering: the implementation was written before its tests here, unlike the test-first
work elsewhere today. This changes a log level rather than behaviour, and the mutation check is the
evidence that the tests constrain it. Said plainly rather than implied otherwise.
Applying
--tags runner-controller. The controller image is rebuilt fromfiles/controller.py, so the unitrestarts. No behavioural change to provisioning, reaping or deletion — only which severity a known
outcome is logged at.
Open question carried over from the issue
The ~640 GB baseline with zero active runner VMs is larger than the obviously-known persistent volumes
account for. It may be entirely legitimate (host root volumes, the monitoring host, images or snapshots
against the same project quota) but it was not chased down, and this PR does not address it. Recorded in
#305 so the number is not mistaken for verified.
a4a9d7489df7916e8e9af7916e8e9a0b14f76e0f