@@ -68,6 +68,15 @@ func (r *Reconciler) execute(
6868 updateRunStatus := updateRun .GetUpdateRunStatus ()
6969 if updatingStageIndex < len (updateRunStatus .StagesStatus ) {
7070 updatingStage := & updateRunStatus .StagesStatus [updatingStageIndex ]
71+ approved , beforeStageTaskErr := r .checkBeforeStageTasksStatus (ctx , updatingStageIndex , updateRun )
72+ if beforeStageTaskErr != nil {
73+ return false , 0 , beforeStageTaskErr
74+ }
75+ if ! approved {
76+ markStageUpdatingWaiting (updatingStage , updateRun .GetGeneration (), true )
77+ markUpdateRunWaiting (updateRun , updatingStage .StageName , true )
78+ return false , clusterUpdatingWaitTime , nil
79+ }
7180 waitTime , execErr := r .executeUpdatingStage (ctx , updateRun , updatingStageIndex , toBeUpdatedBindings )
7281 if errors .Is (execErr , errStagedUpdatedAborted ) {
7382 markStageUpdatingFailed (updatingStage , updateRun .GetGeneration (), execErr .Error ())
@@ -85,6 +94,87 @@ func (r *Reconciler) execute(
8594 return finished , clusterUpdatingWaitTime , execErr
8695}
8796
97+ // checkBeforeStageTasksStatus checks if the before stage tasks have finished.
98+ // It returns if the before stage tasks have finished or error if the before stage tasks failed.
99+ // It also returns the time to wait before rechecking the wait type of task. It turns -1 if the task is not a wait type.
100+ func (r * Reconciler ) checkBeforeStageTasksStatus (ctx context.Context , updatingStageIndex int , updateRun placementv1beta1.UpdateRunObj ) (bool , error ) {
101+ updateRunRef := klog .KObj (updateRun )
102+ updateRunStatus := updateRun .GetUpdateRunStatus ()
103+ updatingStageStatus := & updateRunStatus .StagesStatus [updatingStageIndex ]
104+ updatingStage := & updateRunStatus .UpdateStrategySnapshot .Stages [updatingStageIndex ]
105+ if updatingStage .BeforeStageTasks == nil {
106+ klog .V (2 ).InfoS ("There is no before stage task for this stage" , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
107+ return true , nil
108+ }
109+ passed := true
110+ for i , task := range updatingStage .BeforeStageTasks {
111+ switch task .Type {
112+ case placementv1beta1 .StageTaskTypeApproval :
113+ beforeStageTaskApproved := condition .IsConditionStatusTrue (meta .FindStatusCondition (updatingStageStatus .BeforeStageTaskStatus [i ].Conditions , string (placementv1beta1 .StageTaskConditionApprovalRequestApproved )), updateRun .GetGeneration ())
114+ if beforeStageTaskApproved {
115+ // The beforeStageTask has been approved.
116+ continue
117+ }
118+ // Check if the approval request has been created.
119+ approvalRequest := buildApprovalRequestObject (types.NamespacedName {Name : updatingStageStatus .BeforeStageTaskStatus [i ].ApprovalRequestName , Namespace : updateRun .GetNamespace ()}, updatingStage .Name , updateRun .GetName ())
120+ requestRef := klog .KObj (approvalRequest )
121+ if err := r .Client .Create (ctx , approvalRequest ); err != nil {
122+ if apierrors .IsAlreadyExists (err ) {
123+ // The approval task already exists.
124+ markBeforeStageRequestCreated (& updatingStageStatus .BeforeStageTaskStatus [i ], updateRun .GetGeneration ())
125+ if err = r .Client .Get (ctx , client .ObjectKeyFromObject (approvalRequest ), approvalRequest ); err != nil {
126+ klog .ErrorS (err , "Failed to get the already existing approval request" , "approvalRequest" , requestRef , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
127+ return false , controller .NewAPIServerError (true , err )
128+ }
129+ approvalRequestSpec := approvalRequest .GetApprovalRequestSpec ()
130+ if approvalRequestSpec .TargetStage != updatingStage .Name || approvalRequestSpec .TargetUpdateRun != updateRun .GetName () {
131+ unexpectedErr := controller .NewUnexpectedBehaviorError (fmt .Errorf ("the approval request task `%s/%s` is targeting update run `%s/%s` and stage `%s`" , approvalRequest .GetNamespace (), approvalRequest .GetName (), approvalRequest .GetNamespace (), approvalRequestSpec .TargetUpdateRun , approvalRequestSpec .TargetStage ))
132+ klog .ErrorS (unexpectedErr , "Found an approval request targeting wrong stage" , "approvalRequestTask" , requestRef , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
133+ return false , fmt .Errorf ("%w: %s" , errStagedUpdatedAborted , unexpectedErr .Error ())
134+ }
135+ approvalRequestStatus := approvalRequest .GetApprovalRequestStatus ()
136+ approvalAccepted := condition .IsConditionStatusTrue (meta .FindStatusCondition (approvalRequestStatus .Conditions , string (placementv1beta1 .ApprovalRequestConditionApprovalAccepted )), approvalRequest .GetGeneration ())
137+ approved := condition .IsConditionStatusTrue (meta .FindStatusCondition (approvalRequestStatus .Conditions , string (placementv1beta1 .ApprovalRequestConditionApproved )), approvalRequest .GetGeneration ())
138+ if ! approvalAccepted && ! approved {
139+ klog .V (2 ).InfoS ("The approval request has not been approved yet" , "approvalRequestTask" , requestRef , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
140+ passed = false
141+ continue
142+ }
143+ if approved {
144+ klog .V (2 ).InfoS ("The approval request has been approved" , "approvalRequestTask" , requestRef , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
145+ if ! approvalAccepted {
146+ if err = r .updateApprovalRequestAccepted (ctx , approvalRequest ); err != nil {
147+ klog .ErrorS (err , "Failed to accept the approved approval request" , "approvalRequest" , requestRef , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
148+ // retriable err
149+ return false , err
150+ }
151+ }
152+ } else {
153+ // Approved state should not change once the approval is accepted.
154+ klog .V (2 ).InfoS ("The approval request has been approval-accepted, ignoring changing back to unapproved" , "approvalRequestTask" , requestRef , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
155+ }
156+ markBeforeStageRequestApproved (& updatingStageStatus .BeforeStageTaskStatus [i ], updateRun .GetGeneration ())
157+ } else {
158+ // retriable error
159+ klog .ErrorS (err , "Failed to create the approval request" , "approvalRequest" , requestRef , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
160+ return false , controller .NewAPIServerError (false , err )
161+ }
162+ } else {
163+ // The approval request has been created for the first time.
164+ klog .V (2 ).InfoS ("The approval request has been created" , "approvalRequestTask" , requestRef , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
165+ markBeforeStageRequestCreated (& updatingStageStatus .BeforeStageTaskStatus [i ], updateRun .GetGeneration ())
166+ passed = false
167+ }
168+ case placementv1beta1 .StageTaskTypeTimedWait :
169+ // Timed wait is not supported in before stage task.
170+ unexpectedErr := controller .NewUnexpectedBehaviorError (fmt .Errorf ("found unsupported timed wait task in before stage tasks" ))
171+ klog .ErrorS (unexpectedErr , "Timed wait task is not supported in before stage tasks" , "stage" , updatingStage .Name , "updateRun" , updateRunRef )
172+ return false , fmt .Errorf ("%w: %s" , errStagedUpdatedAborted , unexpectedErr .Error ())
173+ }
174+ }
175+ return passed , nil
176+ }
177+
88178// executeUpdatingStage executes a single updating stage by updating the bindings.
89179func (r * Reconciler ) executeUpdatingStage (
90180 ctx context.Context ,
@@ -212,8 +302,8 @@ func (r *Reconciler) executeUpdatingStage(
212302
213303 if finishedClusterCount == len (updatingStageStatus .Clusters ) {
214304 // All the clusters in the stage have been updated.
215- markUpdateRunWaiting (updateRun , updatingStageStatus .StageName )
216- markStageUpdatingWaiting (updatingStageStatus , updateRun .GetGeneration ())
305+ markUpdateRunWaiting (updateRun , updatingStageStatus .StageName , false )
306+ markStageUpdatingWaiting (updatingStageStatus , updateRun .GetGeneration (), false )
217307 klog .V (2 ).InfoS ("The stage has finished all cluster updating" , "stage" , updatingStageStatus .StageName , "updateRun" , updateRunRef )
218308 // Check if the after stage tasks are ready.
219309 approved , waitTime , err := r .checkAfterStageTasksStatus (ctx , updatingStageIndex , updateRun )
@@ -556,8 +646,18 @@ func markUpdateRunStuck(updateRun placementv1beta1.UpdateRunObj, stageName, clus
556646}
557647
558648// markUpdateRunWaiting marks the updateRun as waiting in memory.
559- func markUpdateRunWaiting (updateRun placementv1beta1.UpdateRunObj , stageName string ) {
649+ func markUpdateRunWaiting (updateRun placementv1beta1.UpdateRunObj , stageName string , isBeforeStage bool ) {
560650 updateRunStatus := updateRun .GetUpdateRunStatus ()
651+ if isBeforeStage {
652+ meta .SetStatusCondition (& updateRunStatus .Conditions , metav1.Condition {
653+ Type : string (placementv1beta1 .StagedUpdateRunConditionProgressing ),
654+ Status : metav1 .ConditionFalse ,
655+ ObservedGeneration : updateRun .GetGeneration (),
656+ Reason : condition .UpdateRunWaitingReason ,
657+ Message : fmt .Sprintf ("The updateRun is waiting for before-stage tasks in stage %s to complete" , stageName ),
658+ })
659+ return
660+ }
561661 meta .SetStatusCondition (& updateRunStatus .Conditions , metav1.Condition {
562662 Type : string (placementv1beta1 .StagedUpdateRunConditionProgressing ),
563663 Status : metav1 .ConditionFalse ,
@@ -582,7 +682,17 @@ func markStageUpdatingStarted(stageUpdatingStatus *placementv1beta1.StageUpdatin
582682}
583683
584684// markStageUpdatingWaiting marks the stage updating status as waiting in memory.
585- func markStageUpdatingWaiting (stageUpdatingStatus * placementv1beta1.StageUpdatingStatus , generation int64 ) {
685+ func markStageUpdatingWaiting (stageUpdatingStatus * placementv1beta1.StageUpdatingStatus , generation int64 , isBeforeStage bool ) {
686+ if isBeforeStage {
687+ meta .SetStatusCondition (& stageUpdatingStatus .Conditions , metav1.Condition {
688+ Type : string (placementv1beta1 .StageUpdatingConditionProgressing ),
689+ Status : metav1 .ConditionFalse ,
690+ ObservedGeneration : generation ,
691+ Reason : condition .StageUpdatingWaitingReason ,
692+ Message : "All before-stage tasks are not completed, waiting for approval" ,
693+ })
694+ return
695+ }
586696 meta .SetStatusCondition (& stageUpdatingStatus .Conditions , metav1.Condition {
587697 Type : string (placementv1beta1 .StageUpdatingConditionProgressing ),
588698 Status : metav1 .ConditionFalse ,
@@ -699,3 +809,25 @@ func markAfterStageWaitTimeElapsed(afterStageTaskStatus *placementv1beta1.StageT
699809 Message : "Wait time elapsed" ,
700810 })
701811}
812+
813+ // markBeforeStageRequestCreated marks the Approval before stage task as ApprovalRequestCreated in memory.
814+ func markBeforeStageRequestCreated (beforeStageTaskStatus * placementv1beta1.StageTaskStatus , generation int64 ) {
815+ meta .SetStatusCondition (& beforeStageTaskStatus .Conditions , metav1.Condition {
816+ Type : string (placementv1beta1 .StageTaskConditionApprovalRequestCreated ),
817+ Status : metav1 .ConditionTrue ,
818+ ObservedGeneration : generation ,
819+ Reason : condition .BeforeStageTaskApprovalRequestCreatedReason ,
820+ Message : "ApprovalRequest object is created" ,
821+ })
822+ }
823+
824+ // markBeforeStageRequestApproved marks the Approval before stage task as Approved in memory.
825+ func markBeforeStageRequestApproved (beforeStageTaskStatus * placementv1beta1.StageTaskStatus , generation int64 ) {
826+ meta .SetStatusCondition (& beforeStageTaskStatus .Conditions , metav1.Condition {
827+ Type : string (placementv1beta1 .StageTaskConditionApprovalRequestApproved ),
828+ Status : metav1 .ConditionTrue ,
829+ ObservedGeneration : generation ,
830+ Reason : condition .BeforeStageTaskApprovalRequestApprovedReason ,
831+ Message : "ApprovalRequest object is approved" ,
832+ })
833+ }
0 commit comments