@@ -482,46 +482,86 @@ func TestReconcileDeleteSkipsUnownedPods(t *testing.T) {
482482 }
483483}
484484
485- func TestReconcileDeleteRemovesBothSnapshotTags (t * testing.T ) {
485+ // :hibernate is always orphaned at CocoonSet teardown so it's always cleaned;
486+ // :latest is only orphaned when snapshotPolicy says no push happened for that
487+ // slot. Always/main-only-slot-0 leave :latest in place for downstream retag.
488+ func TestReconcileDeleteSnapshotPolicyGC (t * testing.T ) {
486489 scheme := testScheme (t )
487- cs := newCocoonSet ("demo" )
488- cs .Finalizers = []string {finalizerName }
489- // snapshotPolicy=never proves the cleanup is not gated by it.
490- cs .Spec .SnapshotPolicy = cocoonv1 .SnapshotPolicyNever
491- cs .Status .Agents = []cocoonv1.AgentStatus {
492- {Slot : 0 , Role : "main" , PodName : "demo-0" , VMName : "vk-ns-demo-0" },
493- }
494-
495- cli := ctrlfake .NewClientBuilder ().
496- WithScheme (scheme ).
497- WithObjects (cs , mustBuildAgentPod (t , cs , 0 , "" , "" , scheme )).
498- Build ()
499- reg := & fakeRegistry {}
500- r := & Reconciler {Client : cli , Scheme : scheme , Epoch : reg }
501-
502- // The fake client deletes synchronously, so the single reconcile sees the
503- // pod gone in the re-list and runs the GC inline.
504- if _ , err := r .reconcileDelete (t .Context (), cs ); err != nil {
505- t .Fatalf ("reconcileDelete: %v" , err )
506- }
507-
508- want := []string {
509- "vk-ns-demo-0:" + meta .DefaultSnapshotTag ,
510- "vk-ns-demo-0:" + meta .HibernateSnapshotTag ,
490+ cases := []struct {
491+ name string
492+ policy cocoonv1.SnapshotPolicy
493+ agents []cocoonv1.AgentStatus
494+ toolboxes []cocoonv1.ToolboxStatus
495+ want []string
496+ }{
497+ {
498+ name : "never drops both tags — no push happened" ,
499+ policy : cocoonv1 .SnapshotPolicyNever ,
500+ agents : []cocoonv1.AgentStatus {
501+ {Slot : 0 , Role : "main" , PodName : "demo-0" , VMName : "vk-ns-demo-0" },
502+ },
503+ want : []string {
504+ "vk-ns-demo-0:" + meta .HibernateSnapshotTag ,
505+ "vk-ns-demo-0:" + meta .DefaultSnapshotTag ,
506+ },
507+ },
508+ {
509+ name : "always preserves :latest for downstream retag" ,
510+ policy : cocoonv1 .SnapshotPolicyAlways ,
511+ agents : []cocoonv1.AgentStatus {
512+ {Slot : 0 , Role : "main" , PodName : "demo-0" , VMName : "vk-ns-demo-0" },
513+ },
514+ want : []string {"vk-ns-demo-0:" + meta .HibernateSnapshotTag },
515+ },
516+ {
517+ name : "main-only keeps slot 0, drops other slots and toolboxes" ,
518+ policy : cocoonv1 .SnapshotPolicyMainOnly ,
519+ agents : []cocoonv1.AgentStatus {
520+ {Slot : 0 , Role : "main" , PodName : "demo-0" , VMName : "vk-ns-demo-0" },
521+ {Slot : 1 , Role : "sub" , PodName : "demo-1" , VMName : "vk-ns-demo-1" },
522+ },
523+ toolboxes : []cocoonv1.ToolboxStatus {
524+ {Name : "tb" , PodName : "demo-tb" , VMName : "vk-ns-demo-tb" },
525+ },
526+ want : []string {
527+ "vk-ns-demo-0:" + meta .HibernateSnapshotTag ,
528+ "vk-ns-demo-1:" + meta .HibernateSnapshotTag ,
529+ "vk-ns-demo-1:" + meta .DefaultSnapshotTag ,
530+ "vk-ns-demo-tb:" + meta .HibernateSnapshotTag ,
531+ "vk-ns-demo-tb:" + meta .DefaultSnapshotTag ,
532+ },
533+ },
511534 }
512- if ! slices .Equal (reg .deleted , want ) {
513- t .Errorf ("DeleteManifest calls = %v, want %v" , reg .deleted , want )
535+ for _ , c := range cases {
536+ t .Run (c .name , func (t * testing.T ) {
537+ cs := newCocoonSet ("demo" )
538+ cs .Finalizers = []string {finalizerName }
539+ cs .Spec .SnapshotPolicy = c .policy
540+ cs .Status .Agents = c .agents
541+ cs .Status .Toolboxes = c .toolboxes
542+
543+ cli := ctrlfake .NewClientBuilder ().WithScheme (scheme ).WithObjects (cs ).Build ()
544+ reg := & fakeRegistry {}
545+ r := & Reconciler {Client : cli , Scheme : scheme , Epoch : reg }
546+
547+ if _ , err := r .reconcileDelete (t .Context (), cs ); err != nil {
548+ t .Fatalf ("reconcileDelete: %v" , err )
549+ }
550+ if ! slices .Equal (reg .deleted , c .want ) {
551+ t .Errorf ("DeleteManifest calls = %v, want %v" , reg .deleted , c .want )
552+ }
553+ })
514554 }
515555}
516556
517557// Race window: pod created but Status.Agents not yet patched with VMName.
518- // reconcileDelete's first pass sees the pod and must stash its VMName onto
519- // the annotation, so the second pass (after the pod is gone) still GCs the tag.
558+ // reconcileDelete's first pass sees the pod and stashes its VMName onto the
559+ // annotation, so the second pass still GCs :hibernate (default policy
560+ // preserves :latest per TestReconcileDeleteAlwaysPolicyPreservesLatest).
520561func TestReconcileDeleteStashesPodVMNamesEvenWhenStatusIsEmpty (t * testing.T ) {
521562 scheme := testScheme (t )
522563 cs := newCocoonSet ("demo" )
523564 cs .Finalizers = []string {finalizerName }
524- // Status.Agents intentionally empty — reconciler hadn't patched it yet.
525565
526566 cli := ctrlfake .NewClientBuilder ().
527567 WithScheme (scheme ).
@@ -534,18 +574,14 @@ func TestReconcileDeleteStashesPodVMNamesEvenWhenStatusIsEmpty(t *testing.T) {
534574 t .Fatalf ("reconcileDelete: %v" , err )
535575 }
536576
537- want := []string {
538- "vk-ns-demo-0:" + meta .DefaultSnapshotTag ,
539- "vk-ns-demo-0:" + meta .HibernateSnapshotTag ,
540- }
577+ want := []string {"vk-ns-demo-0:" + meta .HibernateSnapshotTag }
541578 if ! slices .Equal (reg .deleted , want ) {
542579 t .Errorf ("DeleteManifest calls = %v, want %v" , reg .deleted , want )
543580 }
544581}
545582
546583// Real clusters terminate pods asynchronously: by the time GC runs, the pod
547- // list is already empty. VM names must come from Status, not from a re-list
548- // (which is what the bug fix is about).
584+ // list is already empty. VM names must come from Status, not from a re-list.
549585func TestReconcileDeleteCleansTagsAfterPodsGone (t * testing.T ) {
550586 scheme := testScheme (t )
551587 cs := newCocoonSet ("demo" )
@@ -558,7 +594,6 @@ func TestReconcileDeleteCleansTagsAfterPodsGone(t *testing.T) {
558594 {Name : "tb" , PodName : "demo-tb" , VMName : "vk-ns-demo-tb" },
559595 }
560596
561- // No pods in the fake client — kubelet already terminated them.
562597 cli := ctrlfake .NewClientBuilder ().WithScheme (scheme ).WithObjects (cs ).Build ()
563598 reg := & fakeRegistry {}
564599 r := & Reconciler {Client : cli , Scheme : scheme , Epoch : reg }
@@ -567,12 +602,10 @@ func TestReconcileDeleteCleansTagsAfterPodsGone(t *testing.T) {
567602 t .Fatalf ("reconcileDelete: %v" , err )
568603 }
569604
605+ // Default policy is always, so :latest is preserved for every VM.
570606 want := []string {
571- "vk-ns-demo-0:" + meta .DefaultSnapshotTag ,
572607 "vk-ns-demo-0:" + meta .HibernateSnapshotTag ,
573- "vk-ns-demo-1:" + meta .DefaultSnapshotTag ,
574608 "vk-ns-demo-1:" + meta .HibernateSnapshotTag ,
575- "vk-ns-demo-tb:" + meta .DefaultSnapshotTag ,
576609 "vk-ns-demo-tb:" + meta .HibernateSnapshotTag ,
577610 }
578611 if ! slices .Equal (reg .deleted , want ) {
0 commit comments