Repository navigation
fix(control-plane): keep provisioning gateways out of Degraded while pods start - #381
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Central YAML (inherited) Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Amber reviewStatus: Complete |
…pods start The health loop stamped a first-time provisioning gateway Degraded as soon as its Deployment's pod existed but was not yet available (pending, pulling its image, or starting). The rollout-aware readiness check only defers while a revision is still rolling out; once the updated replica exists it reports rollingOut=false, so the plain not-ready branch moved Provisioning -> Degraded well inside the provisioning path's own readiness window. The gateway then flipped to Running once the pod became ready (Provisioning -> Degraded -> Running). Add a deployment-readiness grace window, mirroring the route-readiness one: while the gateway is Provisioning the health loop keeps it at Provisioning, and only moves it to Degraded once the window (GATEWAY_DEPLOYMENT_READY_TIMEOUT, default 10m) elapses. A Running gateway that loses readiness is still moved to Degraded immediately. Supersedes openshift-online#261 (rebased onto the ObserveGatewayRollout health path). Co-authored-by: Wes <80289690+RunItBack1127@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
27f1197 to
245cc80
Compare
There was a problem hiding this comment.
Verdict
This is a well-scoped, correct fix: it adds a deployment-readiness grace window that keeps a first-time provisioning gateway at Provisioning while its pod is pending/pulling/starting, and only escalates to Degraded immediately for gateways that had already reached Running. The change mirrors the pre-existing route-readiness window precisely, ships a spec scenario, and is covered by focused tests for the within-window, beyond-window, immediate-degrade, and env-override paths.
What I verified
- State-machine correctness (
health.goreconcileGatewayHealth->evaluateDeploymentReadiness): the plain!readybranch now delegates toevaluateDeploymentReadiness, which extends the grace window only whencurrentPhase == Provisioning;Running(lost readiness) andDegradedgateways degrade immediately with no window. This matches the PR intent and the new spec scenario. - Timer lifecycle parity:
deploymentNotReadySinceis created (markDeploymentNotReady), read, and cleared (clearDeploymentTimer) using the same pattern as the pre-existingrouteNotReadySincemap. It is cleared on the route-eval branch, the default (Running) branch, on window-elapse, and for non-provisioning phases. No new class of unbounded map growth beyond what the route timer already carries. - Config parsing (
deploymentReadyTimeout()): validatesGATEWAY_DEPLOYMENT_READY_TIMEOUT, rejects non-positive/unparseable values, logs a WARN with%q(no injection vector; env is operator-controlled), and falls back to the 10m default. Config stays separate from code. - Error handling / conventions: no
panic(), no secrets in logs or reason strings,os/log/fmtalready imported, conventional commit message, spec updated alongside the behavior change. - Test Diff Scrutiny: the only edits to pre-existing tests are additive struct-literal fields in the
newHealthRec/newSettledHealthRechelpers (deploymentReadyTimeoutplus the new map). No existing assertion was flipped from accept->reject or optional->required; every behavioral test is net-new. No hidden contract change.
I could not run go build/go test (no Go toolchain in this environment), so the compile/test confirmation relies on static review plus the PR's stated make check / go test ./internal/reconciler/ results.
Cross-PR coordination
An open pull request (#261) proposes a competing fix for the same "Provisioning -> Degraded -> Running" flap and edits the identical three files (health.go, health_test.go, openshell-gateway-health.spec.md), also introducing a ~10m readiness-timeout mechanism. This PR's description explicitly states it supersedes #261 (rebased onto the ObserveGatewayRollout health path). These are duplicate solutions to one bug: maintainers must decide which one to take and close the other, rather than merge both. That is the one cross-PR item requiring a decision.
Previous concerns
- Prior Minor -
!ready && rollingOutbranch does not clear the deployment-not-ready timer: Still present. Thecase !ready && rollingOutarm (health.go~L365-L373) still returns early without callingclearDeploymentTimer, so a start time stamped while provisioning persists across a subsequent roll. This remains the intended backstop semantics (the window measures total provisioning-not-ready time, and the 10m default is generous), so it is non-blocking - noted again only so the choice stays explicit.
There were no other prior Amber findings and no open inline threads requiring a reply.
Findings Summary (ordered by severity, highest first):
- [Minor]
!ready && rollingOutbranch does not clear the deployment-not-ready timer; retained start time is likely intended backstop behavior but worth an explicit note - Reconciliation (health.go ~L365)
Convention Checklist:
| Convention | Result |
|---|---|
No panic() in production code |
Pass |
| Errors wrapped / status reason preserved | Pass |
| No secrets in logs or responses | Pass |
| Input validated (env value parsed/validated) | Pass |
| Reconcile pattern (not create-or-skip) | Pass |
| Config separate from code (env override) | Pass |
| Status updated on error/degrade paths | Pass |
| Proper context propagation | Pass |
| Test Diff Scrutiny (no flipped assertions) | Pass |
| Spec updated for behavior change | Pass |
| Conventional commit message | Pass |

Summary
Supersedes #261 (credit to @RunItBack1127 for the original diagnosis and approach). #261 no longer applies cleanly: #276 moved the health loop from
DeploymentReadinessto the rollout-awareObserveGatewayRollout. This PR redoes the fix on the current code.The bug
A newly created gateway could briefly show
Degradedwhile it was still being provisioned, then flip toRunning: Provisioning → Degraded → Running.ObserveGatewayRolloutonly reportsrollingOut=trueuntil the Deployment's updated replica exists. After that, while the pod is still Pending, pulling its image, or starting, it reportsrollingOut=false, ready=false. The health loop's plain!readybranch then stampsDegradedwithout checking whether the gateway ever reachedRunning. This happens well inside the provisioning path's own 2-minute readiness window (WaitForGatewayReady). It also contradicts the spec, which says the phase SHALL beProvisioningwhile the Deployment is applied but not yet Ready.Reproduction on
main(939a60e, kind in a Lima VM)The node was cordoned for 75s once the certgen hook pod was bound, so the gateway pod stayed Pending:
This is easy to miss in the standard e2e run: with the image already cached, the pod becomes Ready about 9s after the gateway is created, which is shorter than one 30s health tick. Slow image pulls or scheduling hit it on real clusters.
The fix
evaluateDeploymentReadiness,markDeploymentNotReady,clearDeploymentTimer).Provisioningand its Deployment is not Ready (with no rollout in progress), the gateway staysProvisioningandstatusgets the observed reason.Degradedwith statusdeployment not ready after <window>: <reason>.GATEWAY_DEPLOYMENT_READY_TIMEOUT(default10m). It normally never fires, because the provisioning path setsDegradeditself after its own 2-minute window. It covers the case where that provisioning pass never finishes, e.g. the control plane restarts mid-provision.RunningandDegradedgateways that lose readiness still go toDegradedimmediately. Steady-state behaviour doesn't change.openshell-gateway-health.spec.md.Test plan
go test ./internal/reconciler/passesTestReconcileGatewayHealth_ProvisioningPodNotYetAvailableStaysProvisioningfails onmainwith the same symptom as the VM run (phase updated to Degraded (status "0/1 updated replicas available")) and passes with this changeRunning/Degradeddegrade immediately, and parsing of the env overridemake checkDegradedstamp🤖 Generated with Claude Code