Gate dual-era e2e on the full replica count - #6057
Merged
Merged
Conversation
jhrozek
requested review from
ChrisJBurns,
JAORMX,
blkt,
jerm-dro,
rdimitrov,
reyortiz3 and
tgrunnagle
as code owners
July 27, 2026 20:06
JAORMX
previously approved these changes
Jul 27, 2026
The "brings up both proxy and backend replicas ready" spec failed
intermittently on the v1.33.7 lifecycle leg, asserting
status.readyReplicas == 2 and reading 1.
The value was accurate, not stale. testutil.CheckPodsReady returns as
soon as ONE matching pod is ready -- its own doc comment says "at least
one pod" -- and the spec used it as a two-replica gate. Both gates
cleared with 1 of 2 replicas ready, so the assertion ran while the
second proxy replica genuinely had not come up. The same file already
documented this trap in its pod-eviction spec ("CheckPodsReady only
requires ONE ready pod ... it never proves the StatefulSet actually
replaced the evicted one"); the gates above it were tripping over it.
Add CheckPodsReadyCount for workloads with a known replica count and use
it for both gates, so the spec waits for what its name claims. The
backend count was previously asserted nowhere, which let the following
spec spray Modern requests at a possibly single-replica backend and
silently void the premise of the suite. Also poll the status block
rather than reading it once: the reconciler writes it, so it trails pod
readiness by up to one reconcile even with correct gates.
Terminating pods are excluded from the ready count, matching the
operator's own accounting in categorizePodStatus (issue #4498), so a pod
on its way out cannot satisfy a gate. CheckPodsReady keeps its one-pod
contract -- single-replica callers in the vMCP suite rely on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jhrozek
force-pushed
the
fix-dual-era-readyreplicas-flake
branch
from
July 27, 2026 20:26
06dfda4 to
553eb98
Compare
JAORMX
approved these changes
Jul 27, 2026
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6057 +/- ##
==========================================
+ Coverage 72.20% 72.23% +0.02%
==========================================
Files 721 721
Lines 75062 75085 +23
==========================================
+ Hits 54198 54234 +36
+ Misses 17003 16973 -30
- Partials 3861 3878 +17 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ChrisJBurns
approved these changes
Jul 27, 2026
11 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.
Summary
Dual-Era Multi-Replica Backendfailed intermittently on thev1.33.7lifecycle leg, assertingstatus.readyReplicas == 2and reading1:The value was accurate, not stale.
testutil.CheckPodsReadyreturns as soon as one matching pod is ready — its own doc comment says "at least one pod", and it bails only onrunningPods == 0. The spec used it as a two-replica gate:So the assertion ran while the second proxy replica genuinely had not come up. The test never waited for two replicas at all.
The same file already documented this trap — its pod-eviction spec says "CheckPodsReady only requires ONE ready pod ... it never proves the StatefulSet actually replaced the evicted one" — and the gates above it were tripping over exactly that.
A consequence worth calling out: the backend replica count was asserted nowhere, so the following spec ("serves many Modern requests across the multi-replica deployment") could run against a single-replica backend and silently void the premise of the suite.
What changed
testutil.CheckPodsReadyCountfor workloads with a known replica count, and use it for both gates so the spec waits for what its name claims. Terminating pods are excluded from the ready count, matching the operator's own accounting incategorizePodStatus([BUG] Inflated ReadyReplicas During Rolling Updates in k8s #4498), so a pod on its way out cannot satisfy a gate.CheckPodsReadykeeps its one-pod contract — single-replica callers in the vMCP suite rely on it deliberately, so it was left alone rather than tightened.Status, so it trails pod readiness by up to one reconcile even with correct gates. This part is necessary but was not the cause.Type of change
Test plan
task test)task test-e2e)task lint-fix)Test-only change. Verified with
go build ./test/e2e/...andgo veton both touched packages.The count logic was checked against a fake client rather than assumed, in a throwaway harness (not committed), confirming: 1-of-2 ready fails with
got 1 ready of 2; the same state passes the oldCheckPodsReady, which is the flake mechanism reproduced directly; 2-of-2 passes; a terminating-but-Ready pod does not count; an over-count fails rather than silently passing.The suite itself needs a kind cluster with NodePort mappings and runs in CI only (
test-e2e-lifecycle.yml), so CI here is the real signal.On intermittency: a rerun of the original failing job on #6055 — which contains no fix — passed all three legs including
v1.33.7. So a green run does not by itself prove this fixed; what the change does is make the gate assert the condition it was always supposed to.Does this introduce a user-facing change?
No.
Special notes for reviewers
Deliberately does not raise
timeout(3m) or add a sleep.Two things found while diagnosing this and left out to keep the PR test-only:
cmd/thv-operator/api/v1beta1/mcpserver_types.go:398saysBackendReplicas"controls the backend Deployment", but the backend is a StatefulSet (pkg/runner/config.go:316,pkg/container/runtime/types.go:300, created inpkg/container/kubernetes/client.go:567). Doc comment only, no behavior impact..claude/rules/testing.mdsays nothing about polling or replica counts, so "never gate a multi-replica workload with a one-pod helper" is not written down anywhere a future spec author would see it.An earlier revision of this PR attributed the flake to reconciler status lag and fixed only the polling. That diagnosis was wrong; the branch has been rewritten to tell the correct story in one commit.
Generated with Claude Code