Skip to content

Commit bf348a0

Browse files
committed
fix(controller): correct baseSnapshot prune exemption and validation gate
1 parent 12c087d commit bf348a0

2 files changed

Lines changed: 63 additions & 6 deletions

File tree

internal/controller/impvmsnapshot_controller.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,23 @@ func (r *ImpVMSnapshotReconciler) Reconcile(ctx context.Context, req ctrl.Reques
8989
}
9090
log.Info("pruned old child", "parent", snap.Name, "child", toDelete[i].Name)
9191
}
92-
// Refresh children slice after pruning (rebuild from remaining).
93-
remaining := children[len(children)-retention:]
94-
// If baseSnapshot was skipped in toDelete, add it back if it was in toDelete range.
95-
children = remaining
92+
// Re-list from API server to get an accurate post-prune view (accounts for
93+
// baseSnapshot children that were skipped during deletion).
94+
postPrune := &impv1alpha1.ImpVMSnapshotList{}
95+
if err := r.List(ctx, postPrune,
96+
client.InNamespace(snap.Namespace),
97+
client.MatchingLabels{impv1alpha1.LabelSnapshotParent: snap.Name},
98+
); err != nil {
99+
return ctrl.Result{}, err
100+
}
101+
children = postPrune.Items
96102
}
97103

98104
// Design rule 6: BaseSnapshot validation.
99105
if snap.Spec.BaseSnapshot != "" {
100106
for i := range children {
101107
if children[i].Name == snap.Spec.BaseSnapshot &&
102-
children[i].Status.Phase == "Succeeded" &&
103-
children[i].Status.TerminatedAt != nil {
108+
children[i].Status.Phase == "Succeeded" {
104109
if snap.Status.BaseSnapshot != snap.Spec.BaseSnapshot {
105110
base := snap.DeepCopy()
106111
snap.Status.BaseSnapshot = snap.Spec.BaseSnapshot

internal/controller/impvmsnapshot_controller_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,56 @@ var _ = Describe("ImpVMSnapshot controller", func() {
242242
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: parentName, Namespace: "default"}, updated)).To(Succeed())
243243
Expect(updated.Status.BaseSnapshot).To(Equal(childName))
244244
})
245+
246+
It("TestOperatorSnapshotReconciler_baseSnapshotExemptFromPruning — baseSnapshot child is never pruned", func() {
247+
parentName := "snap-parent-exempt"
248+
baseChildName := "snap-parent-exec-0"
249+
otherChildName := "snap-parent-exec-1"
250+
parent := makeParentSnap(parentName, "default", func(s *impv1alpha1.ImpVMSnapshotSpec) {
251+
s.Retention = 1
252+
s.BaseSnapshot = baseChildName
253+
})
254+
Expect(k8sClient.Create(ctx, parent)).To(Succeed())
255+
DeferCleanup(func() {
256+
list := &impv1alpha1.ImpVMSnapshotList{}
257+
_ = k8sClient.List(ctx, list, client.InNamespace("default"), client.MatchingLabels{impv1alpha1.LabelSnapshotParent: parentName})
258+
for i := range list.Items {
259+
_ = k8sClient.Delete(ctx, &list.Items[i])
260+
}
261+
_ = k8sClient.Delete(ctx, parent)
262+
})
263+
264+
fetched := &impv1alpha1.ImpVMSnapshot{}
265+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: parentName, Namespace: "default"}, fetched)).To(Succeed())
266+
267+
now := metav1.Now()
268+
// Create baseSnapshot child (oldest, snap-parent-exec-0).
269+
baseChild := makeChildSnap(baseChildName, "default", parentName, fetched.UID)
270+
Expect(k8sClient.Create(ctx, baseChild)).To(Succeed())
271+
baseChild.Status.Phase = "Succeeded"
272+
baseChild.Status.TerminatedAt = &now
273+
Expect(k8sClient.Status().Update(ctx, baseChild)).To(Succeed())
274+
275+
// Create a second terminated child (newer, snap-parent-exec-1).
276+
otherChild := makeChildSnap(otherChildName, "default", parentName, fetched.UID)
277+
Expect(k8sClient.Create(ctx, otherChild)).To(Succeed())
278+
otherChild.Status.Phase = "Succeeded"
279+
otherChild.Status.TerminatedAt = &now
280+
Expect(k8sClient.Status().Update(ctx, otherChild)).To(Succeed())
281+
282+
r := newSnapshotReconciler()
283+
_, err := r.Reconcile(ctx, reconcile.Request{
284+
NamespacedName: types.NamespacedName{Name: parentName, Namespace: "default"},
285+
})
286+
Expect(err).NotTo(HaveOccurred())
287+
288+
// The elected baseSnapshot child must survive pruning even though it is the oldest.
289+
surviving := &impv1alpha1.ImpVMSnapshot{}
290+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: baseChildName, Namespace: "default"}, surviving)).To(Succeed())
291+
292+
// status.baseSnapshot should also be elected.
293+
updatedParent := &impv1alpha1.ImpVMSnapshot{}
294+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: parentName, Namespace: "default"}, updatedParent)).To(Succeed())
295+
Expect(updatedParent.Status.BaseSnapshot).To(Equal(baseChildName))
296+
})
245297
})

0 commit comments

Comments
 (0)