github.com/oam-dev/kubevela@v1.9.11/pkg/rollout/rollout.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package rollout
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  
    24  	"github.com/pkg/errors"
    25  	k8stypes "k8s.io/apimachinery/pkg/types"
    26  	"k8s.io/client-go/util/retry"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	"github.com/oam-dev/kubevela/pkg/oam"
    30  
    31  	kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
    32  
    33  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    34  	"github.com/oam-dev/kubevela/pkg/multicluster"
    35  	"github.com/oam-dev/kubevela/pkg/resourcetracker"
    36  	velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
    37  )
    38  
    39  // ClusterRollout rollout in specified cluster
    40  type ClusterRollout struct {
    41  	*kruisev1alpha1.Rollout
    42  	Cluster string
    43  }
    44  
    45  func getAssociatedRollouts(ctx context.Context, cli client.Client, app *v1beta1.Application, withHistoryRTs bool) ([]*ClusterRollout, error) {
    46  	rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, cli, app)
    47  	if err != nil {
    48  		return nil, errors.Wrapf(err, "failed to list resource trackers")
    49  	}
    50  	if !withHistoryRTs {
    51  		historyRTs = []*v1beta1.ResourceTracker{}
    52  	}
    53  	var rollouts []*ClusterRollout
    54  	for _, rt := range append(historyRTs, rootRT, currentRT) {
    55  		if rt == nil {
    56  			continue
    57  		}
    58  		for _, mr := range rt.Spec.ManagedResources {
    59  			if mr.APIVersion == kruisev1alpha1.SchemeGroupVersion.String() && mr.Kind == "Rollout" {
    60  				rollout := &kruisev1alpha1.Rollout{}
    61  				if err = cli.Get(multicluster.ContextWithClusterName(ctx, mr.Cluster), k8stypes.NamespacedName{Namespace: mr.Namespace, Name: mr.Name}, rollout); err != nil {
    62  					if multicluster.IsNotFoundOrClusterNotExists(err) || velaerrors.IsCRDNotExists(err) {
    63  						continue
    64  					}
    65  					return nil, errors.Wrapf(err, "failed to get kruise rollout %s/%s in cluster %s", mr.Namespace, mr.Name, mr.Cluster)
    66  				}
    67  				if value, ok := rollout.Annotations[oam.AnnotationSkipResume]; ok && value == "true" {
    68  					continue
    69  				}
    70  				rollouts = append(rollouts, &ClusterRollout{Rollout: rollout, Cluster: mr.Cluster})
    71  			}
    72  		}
    73  	}
    74  	return rollouts, nil
    75  }
    76  
    77  // SuspendRollout find all rollouts associated with the application (including history RTs) and resume them
    78  func SuspendRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) error {
    79  	rollouts, err := getAssociatedRollouts(ctx, cli, app, true)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	for i := range rollouts {
    84  		rollout := rollouts[i]
    85  		if rollout.Status.Phase == kruisev1alpha1.RolloutPhaseProgressing && !rollout.Spec.Strategy.Paused {
    86  			_ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster)
    87  			rolloutKey := client.ObjectKeyFromObject(rollout.Rollout)
    88  			if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
    89  				if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil {
    90  					return err
    91  				}
    92  				if rollout.Status.Phase == kruisev1alpha1.RolloutPhaseProgressing && !rollout.Spec.Strategy.Paused {
    93  					rollout.Spec.Strategy.Paused = true
    94  					if err = cli.Update(_ctx, rollout.Rollout); err != nil {
    95  						return err
    96  					}
    97  					if writer != nil {
    98  						_, _ = fmt.Fprintf(writer, "Rollout %s/%s in cluster %s suspended.\n", rollout.Namespace, rollout.Name, rollout.Cluster)
    99  					}
   100  					return nil
   101  				}
   102  				return nil
   103  			}); err != nil {
   104  				return errors.Wrapf(err, "failed to suspend rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster)
   105  			}
   106  		}
   107  	}
   108  	return nil
   109  }
   110  
   111  // ResumeRollout find all rollouts associated with the application (in the current RT) and resume them
   112  func ResumeRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) (bool, error) {
   113  	rollouts, err := getAssociatedRollouts(ctx, cli, app, false)
   114  	if err != nil {
   115  		return false, err
   116  	}
   117  	modified := false
   118  	for i := range rollouts {
   119  		rollout := rollouts[i]
   120  		if rollout.Spec.Strategy.Paused || (rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused) {
   121  			_ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster)
   122  			rolloutKey := client.ObjectKeyFromObject(rollout.Rollout)
   123  			resumed := false
   124  			if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   125  				if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil {
   126  					return err
   127  				}
   128  				if rollout.Spec.Strategy.Paused {
   129  					rollout.Spec.Strategy.Paused = false
   130  					if err = cli.Update(_ctx, rollout.Rollout); err != nil {
   131  						return err
   132  					}
   133  					resumed = true
   134  					return nil
   135  				}
   136  				return nil
   137  			}); err != nil {
   138  				return false, errors.Wrapf(err, "failed to resume rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster)
   139  			}
   140  			if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   141  				if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil {
   142  					return err
   143  				}
   144  				if rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused {
   145  					rollout.Status.CanaryStatus.CurrentStepState = kruisev1alpha1.CanaryStepStateReady
   146  					if err = cli.Status().Update(_ctx, rollout.Rollout); err != nil {
   147  						return err
   148  					}
   149  					resumed = true
   150  					return nil
   151  				}
   152  				return nil
   153  			}); err != nil {
   154  				return false, errors.Wrapf(err, "failed to resume rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster)
   155  			}
   156  			if resumed {
   157  				modified = true
   158  				if writer != nil {
   159  					_, _ = fmt.Fprintf(writer, "Rollout %s/%s in cluster %s resumed.\n", rollout.Namespace, rollout.Name, rollout.Cluster)
   160  				}
   161  			}
   162  		}
   163  	}
   164  	return modified, nil
   165  }
   166  
   167  // RollbackRollout find all rollouts associated with the application (in the current RT) and disable the pause field.
   168  func RollbackRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) (bool, error) {
   169  	rollouts, err := getAssociatedRollouts(ctx, cli, app, false)
   170  	if err != nil {
   171  		return false, err
   172  	}
   173  	modified := false
   174  	for i := range rollouts {
   175  		rollout := rollouts[i]
   176  		if rollout.Spec.Strategy.Paused || (rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused) {
   177  			_ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster)
   178  			rolloutKey := client.ObjectKeyFromObject(rollout.Rollout)
   179  			resumed := false
   180  			if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   181  				if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil {
   182  					return err
   183  				}
   184  				if rollout.Spec.Strategy.Paused {
   185  					rollout.Spec.Strategy.Paused = false
   186  					if err = cli.Update(_ctx, rollout.Rollout); err != nil {
   187  						return err
   188  					}
   189  					resumed = true
   190  					return nil
   191  				}
   192  				return nil
   193  			}); err != nil {
   194  				return false, errors.Wrapf(err, "failed to rollback rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster)
   195  			}
   196  			if resumed {
   197  				modified = true
   198  				if writer != nil {
   199  					_, _ = fmt.Fprintf(writer, "Rollout %s/%s in cluster %s rollback.\n", rollout.Namespace, rollout.Name, rollout.Cluster)
   200  				}
   201  			}
   202  		}
   203  	}
   204  	return modified, nil
   205  }