Skip to content

Commit ed6177d

Browse files
authored
feat: stopped update run implementation (#364)
1 parent 0552f2e commit ed6177d

File tree

9 files changed

+1186
-19
lines changed

9 files changed

+1186
-19
lines changed

pkg/controllers/updaterun/controller.go

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req runtime.Request) (runtim
7777
klog.ErrorS(err, "Failed to get updateRun object", "updateRun", req.NamespacedName)
7878
return runtime.Result{}, client.IgnoreNotFound(err)
7979
}
80+
81+
// Update all existing conditions' ObservedGeneration to the current generation.
82+
updateAllStatusConditionsGeneration(updateRun.GetUpdateRunStatus(), updateRun.GetGeneration())
83+
8084
runObjRef := klog.KObj(updateRun)
8185

8286
// Remove waitTime from the updateRun status for BeforeStageTask and AfterStageTask for type Approval.
@@ -110,12 +114,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req runtime.Request) (runtim
110114
var toBeUpdatedBindings, toBeDeletedBindings []placementv1beta1.BindingObj
111115
updateRunStatus := updateRun.GetUpdateRunStatus()
112116
initCond := meta.FindStatusCondition(updateRunStatus.Conditions, string(placementv1beta1.StagedUpdateRunConditionInitialized))
113-
// Check if initialized regardless of generation.
114-
// The updateRun spec fields are immutable except for the state field. When the state changes,
115-
// the update run generation increments, but we don't need to reinitialize since initialization is a one-time setup.
116-
if !(initCond != nil && initCond.Status == metav1.ConditionTrue) {
117+
if !condition.IsConditionStatusTrue(initCond, updateRun.GetGeneration()) {
117118
// Check if initialization failed for the current generation.
118-
if initCond != nil && initCond.Status == metav1.ConditionFalse {
119+
if condition.IsConditionStatusFalse(initCond, updateRun.GetGeneration()) {
119120
klog.V(2).InfoS("The updateRun has failed to initialize", "errorMsg", initCond.Message, "updateRun", runObjRef)
120121
return runtime.Result{}, nil
121122
}
@@ -158,9 +159,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req runtime.Request) (runtim
158159
return runtime.Result{}, r.recordUpdateRunSucceeded(ctx, updateRun)
159160
}
160161

161-
// Execute the updateRun.
162-
if state == placementv1beta1.StateRun {
163-
klog.V(2).InfoS("Continue to execute the updateRun", "state", state, "updatingStageIndex", updatingStageIndex, "updateRun", runObjRef)
162+
switch state {
163+
case placementv1beta1.StateInitialize:
164+
klog.V(2).InfoS("The updateRun is initialized but not executed, waiting to execute", "state", state, "updateRun", runObjRef)
165+
case placementv1beta1.StateRun:
166+
// Execute the updateRun.
167+
klog.InfoS("Continue to execute the updateRun", "updatingStageIndex", updatingStageIndex, "updateRun", runObjRef)
164168
finished, waitTime, execErr := r.execute(ctx, updateRun, updatingStageIndex, toBeUpdatedBindings, toBeDeletedBindings)
165169
if errors.Is(execErr, errStagedUpdatedAborted) {
166170
// errStagedUpdatedAborted cannot be retried.
@@ -182,8 +186,19 @@ func (r *Reconciler) Reconcile(ctx context.Context, req runtime.Request) (runtim
182186
return runtime.Result{}, execErr
183187
}
184188
return runtime.Result{Requeue: true, RequeueAfter: waitTime}, nil
189+
case placementv1beta1.StateStop:
190+
// Stop the updateRun.
191+
klog.InfoS("Stopping the updateRun", "state", state, "updatingStageIndex", updatingStageIndex, "updateRun", runObjRef)
192+
// TODO(britaniar): Implement the stopping logic for in-progress stages.
193+
194+
klog.V(2).InfoS("The updateRun is stopped", "updateRun", runObjRef)
195+
return runtime.Result{}, r.recordUpdateRunStopped(ctx, updateRun)
196+
default:
197+
// Initialize, Run, or Stop are the only supported states.
198+
unexpectedErr := controller.NewUnexpectedBehaviorError(fmt.Errorf("found unsupported updateRun state: %s", state))
199+
klog.ErrorS(unexpectedErr, "Invalid updateRun state", "state", state, "updateRun", runObjRef)
200+
return runtime.Result{}, r.recordUpdateRunFailed(ctx, updateRun, unexpectedErr.Error())
185201
}
186-
klog.V(2).InfoS("The updateRun is initialized but not executed, waiting to execute", "state", state, "updateRun", runObjRef)
187202
return runtime.Result{}, nil
188203
}
189204

@@ -277,6 +292,25 @@ func (r *Reconciler) recordUpdateRunFailed(ctx context.Context, updateRun placem
277292
return nil
278293
}
279294

295+
// recordUpdateRunStopped records the progressing condition as stopped in the updateRun status.
296+
func (r *Reconciler) recordUpdateRunStopped(ctx context.Context, updateRun placementv1beta1.UpdateRunObj) error {
297+
updateRunStatus := updateRun.GetUpdateRunStatus()
298+
meta.SetStatusCondition(&updateRunStatus.Conditions, metav1.Condition{
299+
Type: string(placementv1beta1.StagedUpdateRunConditionProgressing),
300+
Status: metav1.ConditionFalse,
301+
ObservedGeneration: updateRun.GetGeneration(),
302+
Reason: condition.UpdateRunStoppedReason,
303+
Message: "The update run has been stopped",
304+
})
305+
306+
if updateErr := r.Client.Status().Update(ctx, updateRun); updateErr != nil {
307+
klog.ErrorS(updateErr, "Failed to update the updateRun status as stopped", "updateRun", klog.KObj(updateRun))
308+
// updateErr can be retried.
309+
return controller.NewUpdateIgnoreConflictError(updateErr)
310+
}
311+
return nil
312+
}
313+
280314
// recordUpdateRunStatus records the updateRun status.
281315
func (r *Reconciler) recordUpdateRunStatus(ctx context.Context, updateRun placementv1beta1.UpdateRunObj) error {
282316
if updateErr := r.Client.Status().Update(ctx, updateRun); updateErr != nil {
@@ -484,3 +518,57 @@ func removeWaitTimeFromUpdateRunStatus(updateRun placementv1beta1.UpdateRunObj)
484518
}
485519
}
486520
}
521+
522+
// updateAllStatusConditionsGeneration iterates through all existing conditions in the UpdateRun status
523+
// and updates their ObservedGeneration field to the current UpdateRun generation.
524+
func updateAllStatusConditionsGeneration(updateRunStatus *placementv1beta1.UpdateRunStatus, generation int64) {
525+
// Update main UpdateRun conditions.
526+
for i := range updateRunStatus.Conditions {
527+
updateRunStatus.Conditions[i].ObservedGeneration = generation
528+
}
529+
530+
// Update stage-level conditions and nested task conditions if it exists.
531+
for i := range updateRunStatus.StagesStatus {
532+
stageStatus := &updateRunStatus.StagesStatus[i]
533+
534+
// Update stage conditions.
535+
updateAllStageStatusConditionsGeneration(stageStatus, generation)
536+
}
537+
538+
// Update deletion stage conditions and nested tasks if it exists.
539+
if updateRunStatus.DeletionStageStatus != nil {
540+
deletionStageStatus := updateRunStatus.DeletionStageStatus
541+
542+
// Update deletion stage conditions.
543+
updateAllStageStatusConditionsGeneration(deletionStageStatus, generation)
544+
}
545+
}
546+
547+
// updateAllStageStatusConditionsGeneration updates all conditions' ObservedGeneration in the given stage status.
548+
func updateAllStageStatusConditionsGeneration(stageStatus *placementv1beta1.StageUpdatingStatus, generation int64) {
549+
// Update stage conditions.
550+
for j := range stageStatus.Conditions {
551+
stageStatus.Conditions[j].ObservedGeneration = generation
552+
}
553+
554+
// Update before stage task conditions.
555+
for j := range stageStatus.BeforeStageTaskStatus {
556+
for k := range stageStatus.BeforeStageTaskStatus[j].Conditions {
557+
stageStatus.BeforeStageTaskStatus[j].Conditions[k].ObservedGeneration = generation
558+
}
559+
}
560+
561+
// Update after stage task conditions.
562+
for j := range stageStatus.AfterStageTaskStatus {
563+
for k := range stageStatus.AfterStageTaskStatus[j].Conditions {
564+
stageStatus.AfterStageTaskStatus[j].Conditions[k].ObservedGeneration = generation
565+
}
566+
}
567+
568+
// Update cluster-level conditions.
569+
for j := range stageStatus.Clusters {
570+
for k := range stageStatus.Clusters[j].Conditions {
571+
stageStatus.Clusters[j].Conditions[k].ObservedGeneration = generation
572+
}
573+
}
574+
}

pkg/controllers/updaterun/controller_integration_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/google/go-cmp/cmp"
2727
. "github.com/onsi/ginkgo/v2"
2828
. "github.com/onsi/gomega"
29+
"github.com/prometheus/client_golang/prometheus"
2930
prometheusclientmodel "github.com/prometheus/client_model/go"
3031

3132
corev1 "k8s.io/api/core/v1"
@@ -332,6 +333,16 @@ func generateFailedMetric(updateRun *placementv1beta1.ClusterStagedUpdateRun) *p
332333
}
333334
}
334335

336+
func generateStoppedMetric(updateRun *placementv1beta1.ClusterStagedUpdateRun) *prometheusclientmodel.Metric {
337+
return &prometheusclientmodel.Metric{
338+
Label: generateMetricsLabels(updateRun, string(placementv1beta1.StagedUpdateRunConditionProgressing),
339+
string(metav1.ConditionFalse), condition.UpdateRunStoppedReason),
340+
Gauge: &prometheusclientmodel.Gauge{
341+
Value: ptr.To(float64(time.Now().UnixNano()) / 1e9),
342+
},
343+
}
344+
}
345+
335346
func generateSucceededMetric(updateRun *placementv1beta1.ClusterStagedUpdateRun) *prometheusclientmodel.Metric {
336347
return &prometheusclientmodel.Metric{
337348
Label: generateMetricsLabels(updateRun, string(placementv1beta1.StagedUpdateRunConditionSucceeded),
@@ -342,6 +353,24 @@ func generateSucceededMetric(updateRun *placementv1beta1.ClusterStagedUpdateRun)
342353
}
343354
}
344355

356+
func labelPairsToMap(pairs []*prometheusclientmodel.LabelPair) prometheus.Labels {
357+
m := prometheus.Labels{}
358+
for _, p := range pairs {
359+
m[p.GetName()] = p.GetValue()
360+
}
361+
return m
362+
}
363+
364+
func removeMetricFromMetricList(metricList []*prometheusclientmodel.Metric, metricToRemove *prometheusclientmodel.Metric) []*prometheusclientmodel.Metric {
365+
var result []*prometheusclientmodel.Metric
366+
for _, metric := range metricList {
367+
if !cmp.Equal(labelPairsToMap(metric.Label), labelPairsToMap(metricToRemove.Label)) {
368+
result = append(result, metric)
369+
}
370+
}
371+
return result
372+
}
373+
345374
func generateTestClusterStagedUpdateRun() *placementv1beta1.ClusterStagedUpdateRun {
346375
return &placementv1beta1.ClusterStagedUpdateRun{
347376
ObjectMeta: metav1.ObjectMeta{
@@ -823,3 +852,9 @@ func generateFalseProgressingCondition(obj client.Object, condType any, reason s
823852
falseCond.Reason = reason
824853
return falseCond
825854
}
855+
856+
func generateFalseConditionWithReason(obj client.Object, condType any, reason string) metav1.Condition {
857+
falseCond := generateFalseCondition(obj, condType)
858+
falseCond.Reason = reason
859+
return falseCond
860+
}

0 commit comments

Comments
 (0)