@@ -49,6 +49,11 @@ func TestFreshDeployment(t *testing.T) {
4949 assert .True (t , actions [1 ].IsSkipped ())
5050}
5151
52+ // TestUpdateWithAllReadyInstances verifies that a template change on Ready
53+ // instances rolls them by delete+recreate (not an in-place update), ordered
54+ // highest-ordinal-first with only the first action active. An in-place update
55+ // would never roll the backing pod, since the unikraft provider bakes the pod
56+ // at creation time and ignores spec changes on an existing pod.
5257func TestUpdateWithAllReadyInstances (t * testing.T ) {
5358 ctx := context .Background ()
5459 control := New ()
@@ -67,11 +72,11 @@ func TestUpdateWithAllReadyInstances(t *testing.T) {
6772 assert .Len (t , actions , 2 )
6873
6974 assert .Equal (t , "test-deploy-1" , actions [0 ].Object .GetName ())
70- assert .Equal (t , instancecontrol .ActionTypeUpdate , actions [0 ].ActionType ())
75+ assert .Equal (t , instancecontrol .ActionTypeDelete , actions [0 ].ActionType ())
7176 assert .False (t , actions [0 ].IsSkipped ())
7277
7378 assert .Equal (t , "test-deploy-0" , actions [1 ].Object .GetName ())
74- assert .Equal (t , instancecontrol .ActionTypeUpdate , actions [1 ].ActionType ())
79+ assert .Equal (t , instancecontrol .ActionTypeDelete , actions [1 ].ActionType ())
7580 assert .True (t , actions [1 ].IsSkipped ())
7681}
7782
@@ -244,38 +249,48 @@ func TestInstanceLabels_FourNewLabelsStamped(t *testing.T) {
244249 "PlacementNameLabel must equal deployment.Spec.PlacementName" )
245250}
246251
247- // TestInstanceLabels_PropagatedOnUpdate verifies that when an existing instance
248- // is updated (rolling update path), the four new labels are refreshed from the
249- // deployment so they remain accurate after spec changes.
250- func TestInstanceLabels_PropagatedOnUpdate (t * testing.T ) {
252+ // TestInstanceLabels_RefreshedOnRecreate verifies that when a template change
253+ // rolls an instance, the recreated instance carries the four self-describing
254+ // labels sourced from the WorkloadDeployment. A template change no longer
255+ // updates the instance in place; it deletes the drifted instance and recreates
256+ // it via the create path on the following reconcile, which stamps the labels.
257+ func TestInstanceLabels_RefreshedOnRecreate (t * testing.T ) {
251258 ctx := context .Background ()
252259 control := New ()
253260
254261 deployment := getWorkloadDeployment ("test-labels-update" , 1 )
255262
256- // Build a ready existing instance.
263+ // A ready existing instance on the old template hash .
257264 currentInstances := []v1alpha.Instance {* getInstanceForDeployment (deployment , 0 )}
258265
259- // Trigger a rolling update by changing the image.
266+ // Trigger a roll by changing the image.
260267 deployment .Spec .Template .Spec .Runtime .Sandbox .Containers [0 ].Image = "updated-image"
261268
269+ // First reconcile: the drifted instance is deleted (recreate), not updated.
262270 actions , err := control .GetActions (ctx , scheme , deployment , currentInstances )
271+ assert .NoError (t , err )
272+ assert .Len (t , actions , 1 )
273+ assert .Equal (t , instancecontrol .ActionTypeDelete , actions [0 ].ActionType ())
274+ assert .Equal (t , "test-labels-update-0" , actions [0 ].Object .GetName ())
263275
276+ // Next reconcile, after the old instance has been fully deleted and is gone:
277+ // the empty slot is refilled by the create path, which stamps the labels.
278+ actions , err = control .GetActions (ctx , scheme , deployment , nil )
264279 assert .NoError (t , err )
265280 assert .Len (t , actions , 1 )
266- assert .Equal (t , instancecontrol .ActionTypeUpdate , actions [0 ].ActionType ())
281+ assert .Equal (t , instancecontrol .ActionTypeCreate , actions [0 ].ActionType ())
267282
268283 instance , ok := actions [0 ].Object .(* v1alpha.Instance )
269284 assert .True (t , ok )
270285
271286 assert .Equal (t , deployment .GetName (), instance .Labels [v1alpha .WorkloadDeploymentNameLabel ],
272- "WorkloadDeploymentNameLabel must be refreshed on update " )
287+ "WorkloadDeploymentNameLabel must be set on the recreated instance " )
273288 assert .Equal (t , deployment .Spec .CityCode , instance .Labels [v1alpha .CityCodeLabel ],
274- "CityCodeLabel must be refreshed on update " )
289+ "CityCodeLabel must be set on the recreated instance " )
275290 assert .Equal (t , deployment .Spec .WorkloadRef .Name , instance .Labels [v1alpha .WorkloadNameLabel ],
276- "WorkloadNameLabel must be refreshed on update " )
291+ "WorkloadNameLabel must be set on the recreated instance " )
277292 assert .Equal (t , deployment .Spec .PlacementName , instance .Labels [v1alpha .PlacementNameLabel ],
278- "PlacementNameLabel must be refreshed on update " )
293+ "PlacementNameLabel must be set on the recreated instance " )
279294}
280295
281296// TestInstanceLocation_SetWhenDeploymentStatusLocationPresent verifies that when
@@ -331,7 +346,7 @@ func TestInstanceLocation_NilWhenDeploymentStatusLocationAbsent(t *testing.T) {
331346
332347// TestLabelBackfill_NotReadyMatchingHash verifies that a not-Ready instance
333348// with an unchanged template hash receives a PatchLabels action when it is
334- // missing controller-managed labels. The action must not be a rollout Update ,
349+ // missing controller-managed labels. The action must not be a rollout recreate ,
335350// must not alter spec/template, and must not block subsequent instances.
336351func TestLabelBackfill_NotReadyMatchingHash (t * testing.T ) {
337352 ctx := context .Background ()
@@ -361,15 +376,15 @@ func TestLabelBackfill_NotReadyMatchingHash(t *testing.T) {
361376 assert .NoError (t , err )
362377
363378 // Collect actions by type.
364- var waitActions , createActions , updateActions , patchActions []instancecontrol.Action
379+ var waitActions , createActions , recreateActions , patchActions []instancecontrol.Action
365380 for _ , a := range actions {
366381 switch a .ActionType () {
367382 case instancecontrol .ActionTypeWait :
368383 waitActions = append (waitActions , a )
369384 case instancecontrol .ActionTypeCreate :
370385 createActions = append (createActions , a )
371- case instancecontrol .ActionTypeUpdate :
372- updateActions = append (updateActions , a )
386+ case instancecontrol .ActionTypeDelete :
387+ recreateActions = append (recreateActions , a )
373388 case instancecontrol .ActionTypePatchLabels :
374389 patchActions = append (patchActions , a )
375390 }
@@ -383,8 +398,8 @@ func TestLabelBackfill_NotReadyMatchingHash(t *testing.T) {
383398 assert .Len (t , createActions , 1 , "instance-1 create action must be present" )
384399 assert .True (t , createActions [0 ].IsSkipped (), "create for instance-1 must be skipped while instance-0 is waiting" )
385400
386- // No template Update actions must be produced.
387- assert .Empty (t , updateActions , "no template Update must be produced for a matching-hash instance" )
401+ // No rollout recreate actions must be produced.
402+ assert .Empty (t , recreateActions , "no rollout recreate must be produced for a matching-hash instance" )
388403
389404 // A PatchLabels action must be produced for instance-0.
390405 assert .Len (t , patchActions , 1 , "exactly one PatchLabels action for the label-drifted instance" )
@@ -439,7 +454,7 @@ func TestLabelBackfill_Idempotent(t *testing.T) {
439454
440455// TestLabelBackfill_ReadyInstanceCorrected verifies that a Ready instance with
441456// correct template hash but drifted labels receives a PatchLabels action
442- // without triggering a template rollout Update .
457+ // without triggering a rollout recreate .
443458func TestLabelBackfill_ReadyInstanceCorrected (t * testing.T ) {
444459 ctx := context .Background ()
445460 control := New ()
@@ -456,18 +471,18 @@ func TestLabelBackfill_ReadyInstanceCorrected(t *testing.T) {
456471
457472 assert .NoError (t , err )
458473
459- var updateActions , patchActions []instancecontrol.Action
474+ var recreateActions , patchActions []instancecontrol.Action
460475 for _ , a := range actions {
461476 switch a .ActionType () {
462- case instancecontrol .ActionTypeUpdate :
463- updateActions = append (updateActions , a )
477+ case instancecontrol .ActionTypeDelete :
478+ recreateActions = append (recreateActions , a )
464479 case instancecontrol .ActionTypePatchLabels :
465480 patchActions = append (patchActions , a )
466481 }
467482 }
468483
469- // No template Update must be produced — template hash matches.
470- assert .Empty (t , updateActions , "no template Update must be produced for a matching-hash ready instance" )
484+ // No rollout recreate must be produced — template hash matches.
485+ assert .Empty (t , recreateActions , "no rollout recreate must be produced for a matching-hash ready instance" )
471486
472487 // A PatchLabels action must be produced.
473488 assert .Len (t , patchActions , 1 , "PatchLabels action must be produced for the label-drifted ready instance" )
@@ -478,8 +493,9 @@ func TestLabelBackfill_ReadyInstanceCorrected(t *testing.T) {
478493}
479494
480495// TestLabelBackfill_DoesNotAffectRollingUpdate verifies that a genuine template
481- // change on a Ready instance still produces a normal ordered Update action and
482- // that the PatchLabels path does not interfere with or duplicate it.
496+ // change on a Ready instance still produces the normal ordered roll (a recreate
497+ // Delete per instance) and that the PatchLabels path does not interfere with or
498+ // duplicate it.
483499func TestLabelBackfill_DoesNotAffectRollingUpdate (t * testing.T ) {
484500 ctx := context .Background ()
485501 control := New ()
@@ -516,23 +532,23 @@ func TestLabelBackfill_DoesNotAffectRollingUpdate(t *testing.T) {
516532
517533 assert .NoError (t , err )
518534
519- var updateActions , patchActions []instancecontrol.Action
535+ var recreateActions , patchActions []instancecontrol.Action
520536 for _ , a := range actions {
521537 switch a .ActionType () {
522- case instancecontrol .ActionTypeUpdate :
523- updateActions = append (updateActions , a )
538+ case instancecontrol .ActionTypeDelete :
539+ recreateActions = append (recreateActions , a )
524540 case instancecontrol .ActionTypePatchLabels :
525541 patchActions = append (patchActions , a )
526542 }
527543 }
528544
529- // Two Update actions expected (one per instance), ordered highest-to-lowest.
530- assert .Len (t , updateActions , 2 , "both instances must produce Update actions on template change" )
531- assert .Equal (t , "test-backfill-rolling-1" , updateActions [0 ].Object .GetName (),
532- "Update actions must be ordered highest ordinal first" )
533- assert .Equal (t , "test-backfill-rolling-0" , updateActions [1 ].Object .GetName ())
534- assert .False (t , updateActions [0 ].IsSkipped (), "first Update must be active" )
535- assert .True (t , updateActions [1 ].IsSkipped (), "second Update must be skipped (ordered rollout)" )
545+ // Two recreate (Delete) actions expected (one per instance), ordered highest-to-lowest.
546+ assert .Len (t , recreateActions , 2 , "both instances must produce recreate actions on template change" )
547+ assert .Equal (t , "test-backfill-rolling-1" , recreateActions [0 ].Object .GetName (),
548+ "recreate actions must be ordered highest ordinal first" )
549+ assert .Equal (t , "test-backfill-rolling-0" , recreateActions [1 ].Object .GetName ())
550+ assert .False (t , recreateActions [0 ].IsSkipped (), "first recreate must be active" )
551+ assert .True (t , recreateActions [1 ].IsSkipped (), "second recreate must be skipped (ordered rollout)" )
536552
537553 // No PatchLabels — all labels are already correct.
538554 assert .Empty (t , patchActions , "no PatchLabels when all labels are already correct" )
0 commit comments