@@ -100,3 +100,84 @@ func TestIsRolloutManagedInstanceTemplateNameSupportsLegacyAndNewNames(t *testin
100100 }
101101 }
102102}
103+
104+ // TestIsRolloutManagedInstanceTemplateRejectsForeignRolloutCanaries verifies
105+ // that templates created by a different (previous) Rollout — which still carry
106+ // the "created-by-rollout" annotation but whose names reference that previous
107+ // Rollout's UID — are not treated as managed by the current Rollout.
108+ //
109+ // Prior to the fix, isRolloutManagedInstanceTemplate also matched on the
110+ // annotation alone, so leftover canary templates from a completed rollout were
111+ // misclassified as managed by every subsequent rollout. That misclassification
112+ // caused replaceShardingInstanceTemplatesFromSpec to return a non-empty map
113+ // without a default entry, which made tpls[""].DeepCopy() in the teardown
114+ // transformer dereference a nil pointer and panic the controller manager.
115+ func TestIsRolloutManagedInstanceTemplateRejectsForeignRolloutCanaries (t * testing.T ) {
116+ previous := & appsv1alpha1.Rollout {}
117+ previous .UID = types .UID ("11111111-1111-1111-1111-111111111111" )
118+ current := & appsv1alpha1.Rollout {}
119+ current .UID = types .UID ("22222222-2222-2222-2222-222222222222" )
120+
121+ previousCanary := appsv1.InstanceTemplate {
122+ Name : replaceInstanceTemplateNameSuffix (previous ),
123+ Annotations : map [string ]string {
124+ instanceTemplateCreatedByAnnotationKey : "yes" ,
125+ },
126+ }
127+ if ! isRolloutManagedInstanceTemplate (previous , previousCanary ) {
128+ t .Fatalf ("previous rollout should manage its own canary" )
129+ }
130+ if isRolloutManagedInstanceTemplate (current , previousCanary ) {
131+ t .Fatalf ("current rollout must not claim a canary created by a previous rollout" )
132+ }
133+ }
134+
135+ // TestReplaceShardingInstanceTemplatesAlwaysProvidesDefaultForFreshRollout
136+ // reproduces the consecutive-rollout panic. The cluster sharding spec contains
137+ // a canary template left behind by a successful prior rollout (named with the
138+ // previous rollout's UID, annotation set). A fresh rollout then asks for its
139+ // template map. Before the fix, replaceShardingInstanceTemplatesFromSpec
140+ // matched the leftover via the annotation, the early-return path fired with
141+ // only a non-default entry, and the caller saw tpls[""] == nil — exactly the
142+ // state that crashed the teardown transformer at tpls[""].DeepCopy().
143+ func TestReplaceShardingInstanceTemplatesAlwaysProvidesDefaultForFreshRollout (t * testing.T ) {
144+ previous := & appsv1alpha1.Rollout {}
145+ previous .UID = types .UID ("11111111-1111-1111-1111-111111111111" )
146+ current := & appsv1alpha1.Rollout {}
147+ current .UID = types .UID ("22222222-2222-2222-2222-222222222222" )
148+ current .Spec .Shardings = []appsv1alpha1.RolloutSharding {{
149+ Name : "shard" ,
150+ Strategy : appsv1alpha1.RolloutStrategy {
151+ Replace : & appsv1alpha1.RolloutStrategyReplace {},
152+ },
153+ }}
154+
155+ replicas := int32 (3 )
156+ spec := & appsv1.ClusterSharding {
157+ Name : "shard" ,
158+ Template : appsv1.ClusterComponentSpec {
159+ Replicas : replicas ,
160+ FlatInstanceOrdinal : true ,
161+ Instances : []appsv1.InstanceTemplate {
162+ {
163+ Name : replaceInstanceTemplateNameSuffix (previous ),
164+ Replicas : & replicas ,
165+ Annotations : map [string ]string {
166+ instanceTemplateCreatedByAnnotationKey : "yes" ,
167+ },
168+ },
169+ },
170+ },
171+ }
172+
173+ tpls , exist , err := replaceShardingInstanceTemplates (current , current .Spec .Shardings [0 ], spec )
174+ if err != nil {
175+ t .Fatalf ("unexpected error: %v" , err )
176+ }
177+ if exist {
178+ t .Fatalf ("expected exist=false (no template managed by current rollout), got true" )
179+ }
180+ if tpls ["" ] == nil {
181+ t .Fatalf ("expected a default template entry for the current rollout, got nil — this is the regression that panics teardown" )
182+ }
183+ }
0 commit comments