@@ -151,6 +151,154 @@ var _ = Describe("OpsUtil functions", func() {
151151 Expect (opsPhase ).Should (Equal (opsv1alpha1 .OpsFailedPhase ))
152152 })
153153
154+ It ("keeps restart ops running when a failed progress is not backed by a failed component" , func () {
155+ By ("init operations resources " )
156+ opsRes , _ , _ := initOperationsResources (compDefName , clusterName )
157+ testapps .MockInstanceSetComponent (& testCtx , clusterName , defaultCompName )
158+ pods := testapps .MockInstanceSetPods (& testCtx , nil , opsRes .Cluster , defaultCompName )
159+ time .Sleep (time .Second )
160+
161+ ops := testops .NewOpsRequestObj ("restart-ops-" + randomStr , testCtx .DefaultNamespace ,
162+ clusterName , opsv1alpha1 .RestartType )
163+ ops .Spec .RestartList = []opsv1alpha1.ComponentOps {{ComponentName : defaultCompName }}
164+ opsRes .OpsRequest = testops .CreateOpsRequest (ctx , testCtx , ops )
165+ opsRes .OpsRequest .Status .Phase = opsv1alpha1 .OpsRunningPhase
166+ opsRes .OpsRequest .Status .StartTimestamp = metav1 .NewTime (time .Now ().Add (- time .Second ))
167+ runtimes , err := buildOpsRuntimes (ctx , k8sClient , opsRes )
168+ Expect (err ).Should (BeNil ())
169+ opsRes .Runtimes = runtimes
170+
171+ handleRestartProgress := func (reqCtx intctrlutil.RequestCtx ,
172+ cli client.Client ,
173+ opsRes * OpsResource ,
174+ pgRes * progressResource ,
175+ compStatus * opsv1alpha1.OpsRequestComponentStatus ) (expectProgressCount int32 , completedCount int32 , err error ) {
176+ pgRes .deferInstanceFailureToWorkloadPhase = true
177+ return handleComponentStatusProgress (reqCtx , cli , opsRes , pgRes , compStatus ,
178+ func (ops * opsv1alpha1.OpsRequest , instance Instance , pgRes * progressResource ) bool {
179+ creationTimestamp := instance .GetCreationTimestamp ()
180+ return ! creationTimestamp .Before (& ops .Status .StartTimestamp )
181+ })
182+ }
183+
184+ recreatePod := func (pod * corev1.Pod ) * corev1.Pod {
185+ testk8s .MockPodIsTerminating (ctx , testCtx , pod )
186+ testk8s .RemovePodFinalizer (ctx , testCtx , pod )
187+ return testapps .MockInstanceSetPod (& testCtx , nil , clusterName , defaultCompName , pod .Name , "follower" )
188+ }
189+ for i := range pods {
190+ pods [i ] = recreatePod (pods [i ])
191+ }
192+ testk8s .MockPodIsFailed (ctx , testCtx , pods [2 ])
193+
194+ reqCtx := intctrlutil.RequestCtx {Ctx : ctx }
195+ compOpsHelper := newComponentOpsHelper (opsRes .OpsRequest .Spec .RestartList )
196+ opsPhase , requeueAfter , err := compOpsHelper .reconcileActionWithComponentOps (reqCtx , k8sClient , opsRes ,
197+ "test" , handleRestartProgress )
198+ Expect (err ).Should (BeNil ())
199+ Expect (opsPhase ).Should (Equal (opsv1alpha1 .OpsRunningPhase ))
200+ Expect (requeueAfter ).Should (BeZero ())
201+ Expect (opsRes .OpsRequest .Status .Progress ).Should (Equal ("2/3" ))
202+ progressDetail := findStatusProgressDetail (opsRes .OpsRequest .Status .Components [defaultCompName ].ProgressDetails ,
203+ getProgressObjectKey (constant .PodKind , pods [2 ].Name ))
204+ Expect (progressDetail ).ShouldNot (BeNil ())
205+ Expect (progressDetail .Status ).Should (Equal (opsv1alpha1 .ProcessingProgressStatus ))
206+
207+ By ("mock the failed instance recovers while the component remains Running" )
208+ recoveredPod := pods [2 ]
209+ patch := client .MergeFrom (recoveredPod .DeepCopy ())
210+ recoveredPod .Status .Conditions = []corev1.PodCondition {
211+ {
212+ Type : corev1 .PodReady ,
213+ Status : corev1 .ConditionTrue ,
214+ },
215+ {
216+ Type : corev1 .ContainersReady ,
217+ Status : corev1 .ConditionTrue ,
218+ },
219+ }
220+ recoveredPod .Status .ContainerStatuses = []corev1.ContainerStatus {
221+ {
222+ Name : recoveredPod .Spec .Containers [0 ].Name ,
223+ State : corev1.ContainerState {
224+ Running : & corev1.ContainerStateRunning {},
225+ },
226+ },
227+ }
228+ Expect (k8sClient .Status ().Patch (ctx , recoveredPod , patch )).Should (Succeed ())
229+
230+ opsPhase , _ , err = compOpsHelper .reconcileActionWithComponentOps (reqCtx , k8sClient , opsRes ,
231+ "test" , handleRestartProgress )
232+ Expect (err ).Should (BeNil ())
233+ Expect (opsPhase ).Should (Equal (opsv1alpha1 .OpsSucceedPhase ))
234+ Expect (opsRes .OpsRequest .Status .Progress ).Should (Equal ("3/3" ))
235+ })
236+
237+ It ("fails restart ops when component reaches terminal failed phase" , func () {
238+ By ("init operations resources " )
239+ opsRes , _ , _ := initOperationsResources (compDefName , clusterName )
240+ testapps .MockInstanceSetComponent (& testCtx , clusterName , defaultCompName )
241+ pods := testapps .MockInstanceSetPods (& testCtx , nil , opsRes .Cluster , defaultCompName )
242+ time .Sleep (time .Second )
243+
244+ ops := testops .NewOpsRequestObj ("restart-ops-" + randomStr , testCtx .DefaultNamespace ,
245+ clusterName , opsv1alpha1 .RestartType )
246+ ops .Spec .RestartList = []opsv1alpha1.ComponentOps {{ComponentName : defaultCompName }}
247+ opsRes .OpsRequest = testops .CreateOpsRequest (ctx , testCtx , ops )
248+ opsRes .OpsRequest .Status .Phase = opsv1alpha1 .OpsRunningPhase
249+ opsRes .OpsRequest .Status .StartTimestamp = metav1 .NewTime (time .Now ().Add (- time .Second ))
250+ runtimes , err := buildOpsRuntimes (ctx , k8sClient , opsRes )
251+ Expect (err ).Should (BeNil ())
252+ opsRes .Runtimes = runtimes
253+
254+ handleRestartProgress := func (reqCtx intctrlutil.RequestCtx ,
255+ cli client.Client ,
256+ opsRes * OpsResource ,
257+ pgRes * progressResource ,
258+ compStatus * opsv1alpha1.OpsRequestComponentStatus ) (expectProgressCount int32 , completedCount int32 , err error ) {
259+ pgRes .deferInstanceFailureToWorkloadPhase = true
260+ return handleComponentStatusProgress (reqCtx , cli , opsRes , pgRes , compStatus ,
261+ func (ops * opsv1alpha1.OpsRequest , instance Instance , pgRes * progressResource ) bool {
262+ creationTimestamp := instance .GetCreationTimestamp ()
263+ return ! creationTimestamp .Before (& ops .Status .StartTimestamp )
264+ })
265+ }
266+
267+ recreatePod := func (pod * corev1.Pod ) * corev1.Pod {
268+ testk8s .MockPodIsTerminating (ctx , testCtx , pod )
269+ testk8s .RemovePodFinalizer (ctx , testCtx , pod )
270+ return testapps .MockInstanceSetPod (& testCtx , nil , clusterName , defaultCompName , pod .Name , "follower" )
271+ }
272+ for i := range pods {
273+ pods [i ] = recreatePod (pods [i ])
274+ }
275+ testk8s .MockPodIsFailed (ctx , testCtx , pods [2 ])
276+
277+ reqCtx := intctrlutil.RequestCtx {Ctx : ctx }
278+ compOpsHelper := newComponentOpsHelper (opsRes .OpsRequest .Spec .RestartList )
279+ opsPhase , requeueAfter , err := compOpsHelper .reconcileActionWithComponentOps (reqCtx , k8sClient , opsRes ,
280+ "test" , handleRestartProgress )
281+ Expect (err ).Should (BeNil ())
282+ Expect (opsPhase ).Should (Equal (opsv1alpha1 .OpsRunningPhase ))
283+ Expect (requeueAfter ).Should (BeZero ())
284+
285+ By ("mock component reaches terminal Failed phase" )
286+ clusterComp := opsRes .Cluster .Status .Components [defaultCompName ]
287+ clusterComp .Phase = appsv1 .FailedComponentPhase
288+ opsRes .Cluster .Status .SetComponentStatus (defaultCompName , clusterComp )
289+
290+ opsPhase , requeueAfter , err = compOpsHelper .reconcileActionWithComponentOps (reqCtx , k8sClient , opsRes ,
291+ "test" , handleRestartProgress )
292+ Expect (err ).Should (BeNil ())
293+ Expect (requeueAfter ).Should (BeZero ())
294+ Expect (opsPhase ).Should (Equal (opsv1alpha1 .OpsFailedPhase ))
295+ Expect (opsRes .OpsRequest .Status .Progress ).Should (Equal ("3/3" ))
296+ progressDetail := findStatusProgressDetail (opsRes .OpsRequest .Status .Components [defaultCompName ].ProgressDetails ,
297+ getProgressObjectKey (constant .PodKind , pods [2 ].Name ))
298+ Expect (progressDetail ).ShouldNot (BeNil ())
299+ Expect (progressDetail .Status ).Should (Equal (opsv1alpha1 .FailedProgressStatus ))
300+ })
301+
154302 It ("Test opsRequest with disable ha" , func () {
155303 By ("init operations resources " )
156304 opsRes , _ , _ := initOperationsResources (compDefName , clusterName )
0 commit comments