Skip to content

Commit cdc9f47

Browse files
committed
add beforeStageTasks to stagedUpdateRun
Signed-off-by: Britania Rodriguez Reyes <[email protected]>
1 parent 083affc commit cdc9f47

File tree

10 files changed

+1009
-85
lines changed

10 files changed

+1009
-85
lines changed

apis/placement/v1beta1/commons.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,11 @@ const (
167167
// TargetUpdatingStageNameLabel indicates the updating stage name on a staged run related object.
168168
TargetUpdatingStageNameLabel = FleetPrefix + "targetUpdatingStage"
169169

170-
// ApprovalTaskNameFmt is the format of the approval task name.
171-
ApprovalTaskNameFmt = "%s-%s"
170+
// BeforeStageApprovalTaskNameFmt is the format of the before stage approval task name.
171+
BeforeStageApprovalTaskNameFmt = "%s-before-%s"
172+
173+
// AfterStageApprovalTaskNameFmt is the format of the after stage approval task name.
174+
AfterStageApprovalTaskNameFmt = "%s-after-%s"
172175
)
173176

174177
var (

pkg/controllers/updaterun/controller_integration_test.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -543,17 +543,18 @@ func generateTestClusterStagedUpdateStrategy() *placementv1beta1.ClusterStagedUp
543543
}
544544
}
545545

546-
func generateTestClusterStagedUpdateStrategyWithSingleStage(afterStageTasks []placementv1beta1.StageTask) *placementv1beta1.ClusterStagedUpdateStrategy {
546+
func generateTestClusterStagedUpdateStrategyWithSingleStage(afterStageTasks, beforeStageTasks []placementv1beta1.StageTask) *placementv1beta1.ClusterStagedUpdateStrategy {
547547
return &placementv1beta1.ClusterStagedUpdateStrategy{
548548
ObjectMeta: metav1.ObjectMeta{
549549
Name: testUpdateStrategyName,
550550
},
551551
Spec: placementv1beta1.UpdateStrategySpec{
552552
Stages: []placementv1beta1.StageConfig{
553553
{
554-
Name: "stage1",
555-
LabelSelector: &metav1.LabelSelector{}, // Select all clusters.
556-
AfterStageTasks: afterStageTasks,
554+
Name: "stage1",
555+
LabelSelector: &metav1.LabelSelector{}, // Select all clusters.
556+
AfterStageTasks: afterStageTasks,
557+
BeforeStageTasks: beforeStageTasks,
557558
},
558559
},
559560
},
@@ -720,15 +721,7 @@ func generateTrueCondition(obj client.Object, condType any) metav1.Condition {
720721
}
721722
typeStr = string(cond)
722723
case placementv1beta1.StageTaskConditionType:
723-
switch cond {
724-
case placementv1beta1.StageTaskConditionWaitTimeElapsed:
725-
reason = condition.AfterStageTaskWaitTimeElapsedReason
726-
case placementv1beta1.StageTaskConditionApprovalRequestCreated:
727-
reason = condition.AfterStageTaskApprovalRequestCreatedReason
728-
case placementv1beta1.StageTaskConditionApprovalRequestApproved:
729-
reason = condition.AfterStageTaskApprovalRequestApprovedReason
730-
}
731-
typeStr = string(cond)
724+
return generateTrueStageTaskCondition(obj, cond, false)
732725
case placementv1beta1.ApprovalRequestConditionType:
733726
switch cond {
734727
case placementv1beta1.ApprovalRequestConditionApproved:
@@ -816,3 +809,33 @@ func generateFalseProgressingCondition(obj client.Object, condType any, succeede
816809
falseCond.Reason = reason
817810
return falseCond
818811
}
812+
813+
func generateTrueStageTaskCondition(obj client.Object, condType any, isBeforeStage bool) metav1.Condition {
814+
reason, typeStr := "", ""
815+
switch cond := condType.(type) {
816+
case placementv1beta1.StageTaskConditionType:
817+
switch cond {
818+
case placementv1beta1.StageTaskConditionWaitTimeElapsed:
819+
reason = condition.AfterStageTaskWaitTimeElapsedReason
820+
case placementv1beta1.StageTaskConditionApprovalRequestCreated:
821+
if isBeforeStage {
822+
reason = condition.BeforeStageTaskApprovalRequestCreatedReason
823+
} else {
824+
reason = condition.AfterStageTaskApprovalRequestCreatedReason
825+
}
826+
case placementv1beta1.StageTaskConditionApprovalRequestApproved:
827+
if isBeforeStage {
828+
reason = condition.BeforeStageTaskApprovalRequestApprovedReason
829+
} else {
830+
reason = condition.AfterStageTaskApprovalRequestApprovedReason
831+
}
832+
}
833+
typeStr = string(cond)
834+
}
835+
return metav1.Condition{
836+
Status: metav1.ConditionTrue,
837+
Type: typeStr,
838+
ObservedGeneration: obj.GetGeneration(),
839+
Reason: reason,
840+
}
841+
}

pkg/controllers/updaterun/execution.go

Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
89179
func (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

Comments
 (0)