Skip to content

Commit 49fba4e

Browse files
fix: ensure StableVersion only advances after full rollout completion in AgentDeployment controller
1 parent 5587757 commit 49fba4e

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

internal/controller/agentdeployment_controller.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,27 @@ func (r *AgentDeploymentReconciler) updateStatus(ctx context.Context, ad *agentr
299299
latest.Status.Phase = agentraxv1alpha1.PhaseDegraded
300300
} else {
301301
RemoveCondition(latest, agentraxv1alpha1.ConditionImagePullFailed)
302-
if dep.Status.ReadyReplicas > 0 {
302+
303+
// A rollout is complete when the Deployment controller has observed the
304+
// latest generation and every replica is both updated and available.
305+
// Checking only ReadyReplicas > 0 would promote StableVersion prematurely
306+
// while old-image pods are still serving traffic during a rolling update.
307+
replicas := int32(1)
308+
if dep.Spec.Replicas != nil {
309+
replicas = *dep.Spec.Replicas
310+
}
311+
rolloutComplete := dep.Status.ObservedGeneration >= dep.Generation &&
312+
dep.Status.UpdatedReplicas == replicas &&
313+
dep.Status.AvailableReplicas == replicas
314+
315+
if rolloutComplete {
303316
latest.Status.Phase = agentraxv1alpha1.PhaseRunning
304-
latest.Status.StableVersion = latest.Spec.Image
317+
// Derive StableVersion from the image the Deployment controller
318+
// applied — not from latest.Spec.Image — so it reflects what is
319+
// actually running, even if the spec was updated again since.
320+
if len(dep.Spec.Template.Spec.Containers) > 0 {
321+
latest.Status.StableVersion = dep.Spec.Template.Spec.Containers[0].Image
322+
}
305323
SetCondition(latest, agentraxv1alpha1.ConditionReady, metav1.ConditionTrue, "DeploymentReady", "Deployment is ready")
306324
SetCondition(latest, agentraxv1alpha1.ConditionReconciled, metav1.ConditionTrue, "ReconcileSuccess", "Latest generation reconciled")
307325
} else {

internal/controller/agentdeployment_controller_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,44 @@ var _ = Describe("AgentDeployment Controller", func() {
381381
return dep.Spec.Template.Spec.Containers[0].Image
382382
}, testTimeout, testInterval).Should(Equal("nginx:1.25"), "child Deployment image should be updated")
383383
})
384+
385+
It("does not advance StableVersion during a partial rollout", func() {
386+
// Record the StableVersion before the image change. In envtest no pods
387+
// run, so the initial StableVersion is empty (rollout never completes).
388+
// After updating the image the Deployment generation advances but
389+
// ObservedGeneration / UpdatedReplicas / AvailableReplicas never satisfy
390+
// the rollout-complete gate — so StableVersion must stay empty.
391+
ad := &agentraxv1alpha1.AgentDeployment{}
392+
Expect(k8sClient.Get(ctx, key, ad)).To(Succeed())
393+
stableVersionBefore := ad.Status.StableVersion
394+
395+
ad.Spec.Image = "nginx:1.25"
396+
Expect(k8sClient.Update(ctx, ad)).To(Succeed())
397+
398+
// Wait for the Deployment spec to reflect the new image so we know the
399+
// reconciler has processed the update.
400+
Eventually(func() string {
401+
dep := &appsv1.Deployment{}
402+
if err := k8sClient.Get(ctx, key, dep); err != nil {
403+
return ""
404+
}
405+
if len(dep.Spec.Template.Spec.Containers) == 0 {
406+
return ""
407+
}
408+
return dep.Spec.Template.Spec.Containers[0].Image
409+
}, testTimeout, testInterval).Should(Equal("nginx:1.25"), "Deployment spec image should be updated")
410+
411+
// Give the reconciler a moment to run and potentially update status,
412+
// then assert StableVersion has not advanced beyond its pre-update value.
413+
Consistently(func() string {
414+
current := &agentraxv1alpha1.AgentDeployment{}
415+
if err := k8sClient.Get(ctx, key, current); err != nil {
416+
return ""
417+
}
418+
return current.Status.StableVersion
419+
}, 2*time.Second, testInterval).Should(Equal(stableVersionBefore),
420+
"StableVersion must not advance while rollout is incomplete")
421+
})
384422
})
385423

386424
Describe("Self-healing", func() {

0 commit comments

Comments
 (0)