Repository navigation
test(engine): wait for the /fleet record of an apply as for /health - #50
Merged
Merged
Conversation
Every failure of `TestEngineHealthReportsTheAppliedEpoch` was its last
step: one read of `/fleet` after waiting for `/health`, which answered
the zero record (`LastApply() = {Epoch:0 RevisionID: Status: ...}`).
The engine orders an apply that way on purpose (Crewlet's
`internal/engine/reconcile.go`, `Reconciler.apply`: "THE OUTCOME
REACHES THIS NODE'S OWN PROGRESS FIRST, before the record that tells
its peers"). A successful apply moves the node's own progress, which
`/health`'s applied epoch reads, then its boot row, and only then
writes the record its peers read in `/fleet` (`Reconciler.record`,
through `RecordApply`), two writes to the coordination store later. A
single read of `/fleet` made as soon as `/health` reports the epoch can
land between the two and find the record not yet written. It is not a
product defect: every reader of that record in the operator already
allows for the lag and keeps more for one look, never less
(`failureAccount` says the record may not hold the failure yet,
`narrowPlacement` waits for the record to name the revision, and
`keepServing` reads a zero record as a node that has not said what it
runs).
The case now waits for the record as it waits for /health: a record
behind the apply's epoch (none yet, or an earlier apply's) is the lag and
is waited for; one at the apply's epoch or later that is not the
expected record fails at once, naming itself (recordSettled). The wait is
not claimed to turn red reliably without the fix, since whether a single
read lands in the gap is timing; what is pinned deterministically is the
decision, by TestARecordOfTheApplyIsWaitedForAndAWrongOneFails, which
fails if any record at the epoch settles or if a wrong one is waited on.
Signed-off-by: pinotelio <ahmadreza.mollapour@gmail.com>
… wrong record fails fast The gap a single read can fall into is the node-store write of the boot row and the coordination-store write of the record, not two writes to the coordination store. recordSettled fails at once on an error record at the apply's epoch on purpose: that is the engine's account of a failed apply, not lag. And the case's docblock is rewrapped to the file's width. Signed-off-by: pinotelio <ahmadreza.mollapour@gmail.com>
8 of 9 tasks
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.
Fixes the flake of
internal/engineTestEngineHealthReportsTheAppliedEpoch: it failed 2 of 3 local runs and again in #49's engine-backed run, withLastApply() = {Epoch:0 RevisionID: Status: ...}.Root cause
Every failure of
TestEngineHealthReportsTheAppliedEpochwas its last step: one read of/fleetafter waiting for/health, which answered the zero record (LastApply() = {Epoch:0 RevisionID: Status: ...}). The engine orders an apply that way on purpose (Crewlet'sinternal/engine/reconcile.go,Reconciler.apply: "THE OUTCOME REACHES THIS NODE'S OWN PROGRESS FIRST, before the record that tells its peers"). A successful apply moves the node's own progress, which/health's applied epoch reads, then writes its boot row to the node's own store (Reconciler.serve), and only then writes the record its peers read in/fleetto the coordination store (Reconciler.record, throughRecordApply): one write to the node's store and one to the coordination store later. A single read of/fleetmade as soon as/healthreports the epoch can land between the two and find the record not yet written. It is not a product defect: every reader of that record in the operator already allows for the lag and keeps more for one look, never less (failureAccountsays the record may not hold the failure yet,narrowPlacementwaits for the record to name the revision, andkeepServingreads a zero record as a node that has not said what it runs).Fix (test only)
/fleetrecord as it waits for/health, with the same boundedeventually.recordSettled).TestARecordOfTheApplyIsWaitedForAndAWrongOneFailspins that decision deterministically. It covers a record not yet written, an earlier apply's, the apply's own, another revision at the apply's epoch, and a failure at it. It fails if any record at the epoch is accepted, or if a wrong one is waited on instead of failing./healthand the record is timing.Checks
-race -p 1:TestEngineHealthReportsTheAppliedEpoch, 3 runs in a row: ok;./internal/engine/: ok.golangci-lint(v2.14.0, go1.27.2): 0 issues.The commit message says the gap is "two writes to the coordination store"; it is the node-store write of the boot row and the coordination-store write of the record, as above (corrected here rather than by amending).