@@ -165,16 +165,76 @@ func TestScheduler_DeploymentPromotionPrimaryNotReady(t *testing.T) {
165165 require .NotNil (t , canaryDep .Spec .Replicas )
166166 assert .Equal (t , canaryReplicas , * canaryDep .Spec .Replicas , "canary must not be scaled to zero when promotion fails" )
167167
168- // traffic must NOT be shifted entirely onto the broken primary
168+ // traffic must be routed to the healthy canary, off the broken primary
169169 primaryWeight , canaryWeight , _ , err := mocks .router .GetRoutes (mocks .canary )
170170 require .NoError (t , err )
171- assert .NotEqual (t , 100 , primaryWeight , "traffic must not be routed entirely to the unhealthy primary" )
172- assert .Greater (t , canaryWeight , 0 , "canary must keep serving traffic when promotion fails " )
171+ assert .Equal (t , 0 , primaryWeight , "no traffic must remain on the unhealthy primary" )
172+ assert .Equal (t , 100 , canaryWeight , "all traffic must be routed to the healthy canary " )
173173
174174 // the rollout is reported as failed so it stops advancing and alerts
175175 c , err := mocks .flaggerClient .FlaggerV1beta1 ().Canaries ("default" ).Get (context .TODO (), "podinfo" , metav1.GetOptions {})
176176 require .NoError (t , err )
177177 assert .Equal (t , flaggerv1 .CanaryPhaseFailed , c .Status .Phase )
178+ assert .Equal (t , 100 , c .Status .CanaryWeight , "reported canary weight must match the traffic on the canary" )
179+ }
180+
181+ // when the primary fails after promotion traffic has already been shifted to it
182+ // (Finalising phase), traffic must be routed back to the healthy canary (#1898)
183+ func TestScheduler_DeploymentPromotionFailedAfterTrafficShift (t * testing.T ) {
184+ mocks := newDeploymentFixture (nil )
185+
186+ // initializing
187+ mocks .ctrl .advanceCanary ("podinfo" , "default" )
188+ mocks .makePrimaryReady (t )
189+
190+ // initialized
191+ mocks .ctrl .advanceCanary ("podinfo" , "default" )
192+
193+ // update
194+ dep2 := newDeploymentTestDeploymentV2 ()
195+ _ , err := mocks .kubeClient .AppsV1 ().Deployments ("default" ).Update (context .TODO (), dep2 , metav1.UpdateOptions {})
196+ require .NoError (t , err )
197+
198+ // detect changes -> progressing, canary scaled up
199+ mocks .ctrl .advanceCanary ("podinfo" , "default" )
200+ mocks .makeCanaryReady (t )
201+
202+ canaryDep , err := mocks .kubeClient .AppsV1 ().Deployments ("default" ).Get (context .TODO (), "podinfo" , metav1.GetOptions {})
203+ require .NoError (t , err )
204+ require .NotNil (t , canaryDep .Spec .Replicas )
205+ canaryReplicas := * canaryDep .Spec .Replicas
206+ require .Greater (t , canaryReplicas , int32 (0 ))
207+
208+ // simulate: promotion already shifted all traffic to the primary (Finalising)
209+ cd , err := mocks .flaggerClient .FlaggerV1beta1 ().Canaries ("default" ).Get (context .TODO (), "podinfo" , metav1.GetOptions {})
210+ require .NoError (t , err )
211+ err = mocks .deployer .SetStatusPhase (cd , flaggerv1 .CanaryPhaseFinalising )
212+ require .NoError (t , err )
213+ require .NoError (t , mocks .router .SetRoutes (mocks .canary , 100 , 0 , false ))
214+
215+ // the promoted primary then fails to stay ready
216+ mocks .makePrimaryNotReady (t )
217+
218+ // advance: Flagger observes the primary is stuck
219+ mocks .ctrl .advanceCanary ("podinfo" , "default" )
220+
221+ // traffic must be routed back to the healthy canary, off the broken primary
222+ primaryWeight , canaryWeight , _ , err := mocks .router .GetRoutes (mocks .canary )
223+ require .NoError (t , err )
224+ assert .Equal (t , 0 , primaryWeight , "no traffic must remain on the unhealthy primary" )
225+ assert .Equal (t , 100 , canaryWeight , "all traffic must be routed to the healthy canary" )
226+
227+ // the canary must not be scaled to zero
228+ canaryDep , err = mocks .kubeClient .AppsV1 ().Deployments ("default" ).Get (context .TODO (), "podinfo" , metav1.GetOptions {})
229+ require .NoError (t , err )
230+ require .NotNil (t , canaryDep .Spec .Replicas )
231+ assert .Equal (t , canaryReplicas , * canaryDep .Spec .Replicas , "canary must not be scaled to zero when promotion fails" )
232+
233+ // reported canary weight must match the routing (not zeroed)
234+ c , err := mocks .flaggerClient .FlaggerV1beta1 ().Canaries ("default" ).Get (context .TODO (), "podinfo" , metav1.GetOptions {})
235+ require .NoError (t , err )
236+ assert .Equal (t , flaggerv1 .CanaryPhaseFailed , c .Status .Phase )
237+ assert .Equal (t , 100 , c .Status .CanaryWeight , "reported canary weight must match the traffic on the canary" )
178238}
179239
180240func TestScheduler_DeploymentSkipAnalysis (t * testing.T ) {
0 commit comments