sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machinedeployment/mdutil/util.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes 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 mdutil implements MachineDeployment utilities.
    18  package mdutil
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"github.com/go-logr/logr"
    28  	"github.com/pkg/errors"
    29  	corev1 "k8s.io/api/core/v1"
    30  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    31  	"k8s.io/apimachinery/pkg/api/meta"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	intstrutil "k8s.io/apimachinery/pkg/util/intstr"
    35  	"k8s.io/klog/v2"
    36  	"k8s.io/utils/integer"
    37  	ctrl "sigs.k8s.io/controller-runtime"
    38  
    39  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    40  	"sigs.k8s.io/cluster-api/util/conversion"
    41  )
    42  
    43  // MachineSetsByDecreasingReplicas sorts the list of MachineSets in decreasing order of replicas,
    44  // using creation time (ascending order) and name (alphabetical) as tie breakers.
    45  type MachineSetsByDecreasingReplicas []*clusterv1.MachineSet
    46  
    47  func (o MachineSetsByDecreasingReplicas) Len() int      { return len(o) }
    48  func (o MachineSetsByDecreasingReplicas) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
    49  func (o MachineSetsByDecreasingReplicas) Less(i, j int) bool {
    50  	if o[i].Spec.Replicas == nil {
    51  		return false
    52  	}
    53  	if o[j].Spec.Replicas == nil {
    54  		return true
    55  	}
    56  	if *o[i].Spec.Replicas == *o[j].Spec.Replicas {
    57  		if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) {
    58  			return o[i].Name < o[j].Name
    59  		}
    60  		return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
    61  	}
    62  	return *o[i].Spec.Replicas > *o[j].Spec.Replicas
    63  }
    64  
    65  // MachineSetsByCreationTimestamp sorts a list of MachineSet by creation timestamp, using their names as a tie breaker.
    66  type MachineSetsByCreationTimestamp []*clusterv1.MachineSet
    67  
    68  func (o MachineSetsByCreationTimestamp) Len() int      { return len(o) }
    69  func (o MachineSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
    70  func (o MachineSetsByCreationTimestamp) Less(i, j int) bool {
    71  	if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) {
    72  		return o[i].Name < o[j].Name
    73  	}
    74  	return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
    75  }
    76  
    77  // MachineSetsBySizeOlder sorts a list of MachineSet by size in descending order, using their creation timestamp or name as a tie breaker.
    78  // By using the creation timestamp, this sorts from old to new machine sets.
    79  type MachineSetsBySizeOlder []*clusterv1.MachineSet
    80  
    81  func (o MachineSetsBySizeOlder) Len() int      { return len(o) }
    82  func (o MachineSetsBySizeOlder) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
    83  func (o MachineSetsBySizeOlder) Less(i, j int) bool {
    84  	if *(o[i].Spec.Replicas) == *(o[j].Spec.Replicas) {
    85  		return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
    86  	}
    87  	return *(o[i].Spec.Replicas) > *(o[j].Spec.Replicas)
    88  }
    89  
    90  // MachineSetsBySizeNewer sorts a list of MachineSet by size in descending order, using their creation timestamp or name as a tie breaker.
    91  // By using the creation timestamp, this sorts from new to old machine sets.
    92  type MachineSetsBySizeNewer []*clusterv1.MachineSet
    93  
    94  func (o MachineSetsBySizeNewer) Len() int      { return len(o) }
    95  func (o MachineSetsBySizeNewer) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
    96  func (o MachineSetsBySizeNewer) Less(i, j int) bool {
    97  	if *(o[i].Spec.Replicas) == *(o[j].Spec.Replicas) {
    98  		return o[j].CreationTimestamp.Before(&o[i].CreationTimestamp)
    99  	}
   100  	return *(o[i].Spec.Replicas) > *(o[j].Spec.Replicas)
   101  }
   102  
   103  // SetDeploymentRevision updates the revision for a deployment.
   104  func SetDeploymentRevision(deployment *clusterv1.MachineDeployment, revision string) bool {
   105  	updated := false
   106  
   107  	if deployment.Annotations == nil {
   108  		deployment.Annotations = make(map[string]string)
   109  	}
   110  	if deployment.Annotations[clusterv1.RevisionAnnotation] != revision {
   111  		deployment.Annotations[clusterv1.RevisionAnnotation] = revision
   112  		updated = true
   113  	}
   114  
   115  	return updated
   116  }
   117  
   118  // MaxRevision finds the highest revision in the machine sets.
   119  func MaxRevision(ctx context.Context, allMSs []*clusterv1.MachineSet) int64 {
   120  	log := ctrl.LoggerFrom(ctx)
   121  
   122  	max := int64(0)
   123  	for _, ms := range allMSs {
   124  		if v, err := Revision(ms); err != nil {
   125  			// Skip the machine sets when it failed to parse their revision information
   126  			log.Error(err, fmt.Sprintf("Couldn't parse revision for MachineSet %s, deployment controller will skip it when reconciling revisions", ms.Name))
   127  		} else if v > max {
   128  			max = v
   129  		}
   130  	}
   131  	return max
   132  }
   133  
   134  // Revision returns the revision number of the input object.
   135  func Revision(obj runtime.Object) (int64, error) {
   136  	acc, err := meta.Accessor(obj)
   137  	if err != nil {
   138  		return 0, err
   139  	}
   140  	v, ok := acc.GetAnnotations()[clusterv1.RevisionAnnotation]
   141  	if !ok {
   142  		return 0, nil
   143  	}
   144  	return strconv.ParseInt(v, 10, 64)
   145  }
   146  
   147  var annotationsToSkip = map[string]bool{
   148  	corev1.LastAppliedConfigAnnotation:  true,
   149  	clusterv1.RevisionAnnotation:        true,
   150  	clusterv1.RevisionHistoryAnnotation: true,
   151  	clusterv1.DesiredReplicasAnnotation: true,
   152  	clusterv1.MaxReplicasAnnotation:     true,
   153  
   154  	// Exclude the conversion annotation, to avoid infinite loops between the conversion webhook
   155  	// and the MachineDeployment controller syncing the annotations between a MachineDeployment
   156  	// and its linked MachineSets.
   157  	//
   158  	// See https://github.com/kubernetes-sigs/cluster-api/pull/3010#issue-413767831 for more details.
   159  	conversion.DataAnnotation: true,
   160  }
   161  
   162  // skipCopyAnnotation returns true if we should skip copying the annotation with the given annotation key
   163  // TODO(tbd): How to decide which annotations should / should not be copied?
   164  //
   165  //	See https://github.com/kubernetes/kubernetes/pull/20035#issuecomment-179558615
   166  func skipCopyAnnotation(key string) bool {
   167  	return annotationsToSkip[key]
   168  }
   169  
   170  func getMaxReplicasAnnotation(ms *clusterv1.MachineSet, logger logr.Logger) (int32, bool) {
   171  	return getIntFromAnnotation(ms, clusterv1.MaxReplicasAnnotation, logger)
   172  }
   173  
   174  func getIntFromAnnotation(ms *clusterv1.MachineSet, annotationKey string, logger logr.Logger) (int32, bool) {
   175  	logger = logger.WithValues("MachineSet", klog.KObj(ms))
   176  
   177  	annotationValue, ok := ms.Annotations[annotationKey]
   178  	if !ok {
   179  		return int32(0), false
   180  	}
   181  	intValue, err := strconv.ParseInt(annotationValue, 10, 32)
   182  	if err != nil {
   183  		logger.V(2).Info(fmt.Sprintf("Cannot convert annotation %q with value %q to integer", annotationKey, annotationValue))
   184  		return int32(0), false
   185  	}
   186  	return int32(intValue), true
   187  }
   188  
   189  // ComputeMachineSetAnnotations computes the annotations that should be set on the MachineSet.
   190  // Note: The passed in newMS is nil if the new MachineSet doesn't exist in the apiserver yet.
   191  func ComputeMachineSetAnnotations(ctx context.Context, deployment *clusterv1.MachineDeployment, oldMSs []*clusterv1.MachineSet, newMS *clusterv1.MachineSet) (map[string]string, error) {
   192  	// Copy annotations from Deployment annotations while filtering out some annotations
   193  	// that we don't want to propagate.
   194  	annotations := map[string]string{}
   195  	for k, v := range deployment.Annotations {
   196  		if skipCopyAnnotation(k) {
   197  			continue
   198  		}
   199  		annotations[k] = v
   200  	}
   201  
   202  	// The newMS's revision should be the greatest among all MSes. Usually, its revision number is newRevision (the max revision number
   203  	// of all old MSes + 1). However, it's possible that some old MSes are deleted after the newMS revision being updated, and
   204  	// newRevision becomes smaller than newMS's revision. We will never decrease a revision of a MachineSet.
   205  	maxOldRevision := MaxRevision(ctx, oldMSs)
   206  	newRevisionInt := maxOldRevision + 1
   207  	newRevision := strconv.FormatInt(newRevisionInt, 10)
   208  	if newMS != nil {
   209  		currentRevision, currentRevisionExists := newMS.Annotations[clusterv1.RevisionAnnotation]
   210  		if currentRevisionExists {
   211  			currentRevisionInt, err := strconv.ParseInt(currentRevision, 10, 64)
   212  			if err != nil {
   213  				return nil, errors.Wrapf(err, "failed to parse current revision on MachineSet %s", klog.KObj(newMS))
   214  			}
   215  			if newRevisionInt < currentRevisionInt {
   216  				newRevision = currentRevision
   217  			}
   218  		}
   219  
   220  		// Ensure we preserve the revision history annotation in any case if it already exists.
   221  		// Note: With Server-Side-Apply not setting the annotation would drop it.
   222  		revisionHistory, revisionHistoryExists := newMS.Annotations[clusterv1.RevisionHistoryAnnotation]
   223  		if revisionHistoryExists {
   224  			annotations[clusterv1.RevisionHistoryAnnotation] = revisionHistory
   225  		}
   226  
   227  		// If the revision changes then add the old revision to the revision history annotation
   228  		if currentRevisionExists && currentRevision != newRevision {
   229  			oldRevisions := strings.Split(revisionHistory, ",")
   230  			if oldRevisions[0] == "" {
   231  				annotations[clusterv1.RevisionHistoryAnnotation] = currentRevision
   232  			} else {
   233  				annotations[clusterv1.RevisionHistoryAnnotation] = strings.Join(append(oldRevisions, currentRevision), ",")
   234  			}
   235  		}
   236  	}
   237  
   238  	annotations[clusterv1.RevisionAnnotation] = newRevision
   239  	annotations[clusterv1.DesiredReplicasAnnotation] = fmt.Sprintf("%d", *deployment.Spec.Replicas)
   240  	annotations[clusterv1.MaxReplicasAnnotation] = fmt.Sprintf("%d", *(deployment.Spec.Replicas)+MaxSurge(*deployment))
   241  	return annotations, nil
   242  }
   243  
   244  // FindOneActiveOrLatest returns the only active or the latest machine set in case there is at most one active
   245  // machine set. If there are more than one active machine sets, return nil so machine sets can be scaled down
   246  // to the point where there is only one active machine set.
   247  func FindOneActiveOrLatest(newMS *clusterv1.MachineSet, oldMSs []*clusterv1.MachineSet) *clusterv1.MachineSet {
   248  	if newMS == nil && len(oldMSs) == 0 {
   249  		return nil
   250  	}
   251  
   252  	sort.Sort(sort.Reverse(MachineSetsByCreationTimestamp(oldMSs)))
   253  	allMSs := FilterActiveMachineSets(append(oldMSs, newMS))
   254  
   255  	switch len(allMSs) {
   256  	case 0:
   257  		// If there is no active machine set then we should return the newest.
   258  		if newMS != nil {
   259  			return newMS
   260  		}
   261  		return oldMSs[0]
   262  	case 1:
   263  		return allMSs[0]
   264  	default:
   265  		return nil
   266  	}
   267  }
   268  
   269  // SetReplicasAnnotations sets the desiredReplicas and maxReplicas into the annotations.
   270  func SetReplicasAnnotations(ms *clusterv1.MachineSet, desiredReplicas, maxReplicas int32) bool {
   271  	updated := false
   272  	if ms.Annotations == nil {
   273  		ms.Annotations = make(map[string]string)
   274  	}
   275  	desiredString := fmt.Sprintf("%d", desiredReplicas)
   276  	if hasString := ms.Annotations[clusterv1.DesiredReplicasAnnotation]; hasString != desiredString {
   277  		ms.Annotations[clusterv1.DesiredReplicasAnnotation] = desiredString
   278  		updated = true
   279  	}
   280  	if hasString := ms.Annotations[clusterv1.MaxReplicasAnnotation]; hasString != fmt.Sprintf("%d", maxReplicas) {
   281  		ms.Annotations[clusterv1.MaxReplicasAnnotation] = fmt.Sprintf("%d", maxReplicas)
   282  		updated = true
   283  	}
   284  	return updated
   285  }
   286  
   287  // ReplicasAnnotationsNeedUpdate return true if the replicas annotation needs to be updated.
   288  func ReplicasAnnotationsNeedUpdate(ms *clusterv1.MachineSet, desiredReplicas, maxReplicas int32) bool {
   289  	if ms.Annotations == nil {
   290  		return true
   291  	}
   292  	desiredString := fmt.Sprintf("%d", desiredReplicas)
   293  	if hasString := ms.Annotations[clusterv1.DesiredReplicasAnnotation]; hasString != desiredString {
   294  		return true
   295  	}
   296  	if hasString := ms.Annotations[clusterv1.MaxReplicasAnnotation]; hasString != fmt.Sprintf("%d", maxReplicas) {
   297  		return true
   298  	}
   299  	return false
   300  }
   301  
   302  // MaxUnavailable returns the maximum unavailable machines a rolling deployment can take.
   303  func MaxUnavailable(deployment clusterv1.MachineDeployment) int32 {
   304  	if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 {
   305  		return int32(0)
   306  	}
   307  	// Error caught by validation
   308  	_, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas))
   309  	if maxUnavailable > *deployment.Spec.Replicas {
   310  		return *deployment.Spec.Replicas
   311  	}
   312  	return maxUnavailable
   313  }
   314  
   315  // MaxSurge returns the maximum surge machines a rolling deployment can take.
   316  func MaxSurge(deployment clusterv1.MachineDeployment) int32 {
   317  	if !IsRollingUpdate(&deployment) {
   318  		return int32(0)
   319  	}
   320  	// Error caught by validation
   321  	maxSurge, _, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas))
   322  	return maxSurge
   323  }
   324  
   325  // GetProportion will estimate the proportion for the provided machine set using 1. the current size
   326  // of the parent deployment, 2. the replica count that needs be added on the machine sets of the
   327  // deployment, and 3. the total replicas added in the machine sets of the deployment so far.
   328  func GetProportion(ms *clusterv1.MachineSet, md clusterv1.MachineDeployment, deploymentReplicasToAdd, deploymentReplicasAdded int32, logger logr.Logger) int32 {
   329  	if ms == nil || *(ms.Spec.Replicas) == 0 || deploymentReplicasToAdd == 0 || deploymentReplicasToAdd == deploymentReplicasAdded {
   330  		return int32(0)
   331  	}
   332  
   333  	msFraction := getMachineSetFraction(*ms, md, logger)
   334  	allowed := deploymentReplicasToAdd - deploymentReplicasAdded
   335  
   336  	if deploymentReplicasToAdd > 0 {
   337  		// Use the minimum between the machine set fraction and the maximum allowed replicas
   338  		// when scaling up. This way we ensure we will not scale up more than the allowed
   339  		// replicas we can add.
   340  		return min(msFraction, allowed)
   341  	}
   342  	// Use the maximum between the machine set fraction and the maximum allowed replicas
   343  	// when scaling down. This way we ensure we will not scale down more than the allowed
   344  	// replicas we can remove.
   345  	return max(msFraction, allowed)
   346  }
   347  
   348  // getMachineSetFraction estimates the fraction of replicas a machine set can have in
   349  // 1. a scaling event during a rollout or 2. when scaling a paused deployment.
   350  func getMachineSetFraction(ms clusterv1.MachineSet, md clusterv1.MachineDeployment, logger logr.Logger) int32 {
   351  	// If we are scaling down to zero then the fraction of this machine set is its whole size (negative)
   352  	if *(md.Spec.Replicas) == int32(0) {
   353  		return -*(ms.Spec.Replicas)
   354  	}
   355  
   356  	deploymentReplicas := *(md.Spec.Replicas) + MaxSurge(md)
   357  	annotatedReplicas, ok := getMaxReplicasAnnotation(&ms, logger)
   358  	if !ok {
   359  		// If we cannot find the annotation then fallback to the current deployment size. Note that this
   360  		// will not be an accurate proportion estimation in case other machine sets have different values
   361  		// which means that the deployment was scaled at some point but we at least will stay in limits
   362  		// due to the min-max comparisons in getProportion.
   363  		annotatedReplicas = md.Status.Replicas
   364  	}
   365  
   366  	// We should never proportionally scale up from zero which means ms.spec.replicas and annotatedReplicas
   367  	// will never be zero here.
   368  	newMSsize := (float64(*(ms.Spec.Replicas) * deploymentReplicas)) / float64(annotatedReplicas)
   369  	return integer.RoundToInt32(newMSsize) - *(ms.Spec.Replicas)
   370  }
   371  
   372  // EqualMachineTemplate returns true if two given machineTemplateSpec are equal,
   373  // ignoring all the in-place propagated fields, and the version from external references.
   374  func EqualMachineTemplate(template1, template2 *clusterv1.MachineTemplateSpec) bool {
   375  	t1Copy := MachineTemplateDeepCopyRolloutFields(template1)
   376  	t2Copy := MachineTemplateDeepCopyRolloutFields(template2)
   377  
   378  	return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
   379  }
   380  
   381  // MachineTemplateDeepCopyRolloutFields copies a MachineTemplateSpec
   382  // and sets all fields that should be propagated in-place to nil and drops version from
   383  // external references.
   384  func MachineTemplateDeepCopyRolloutFields(template *clusterv1.MachineTemplateSpec) *clusterv1.MachineTemplateSpec {
   385  	templateCopy := template.DeepCopy()
   386  
   387  	// Drop labels and annotations
   388  	templateCopy.Labels = nil
   389  	templateCopy.Annotations = nil
   390  
   391  	// Drop node timeout values
   392  	templateCopy.Spec.NodeDrainTimeout = nil
   393  	templateCopy.Spec.NodeDeletionTimeout = nil
   394  	templateCopy.Spec.NodeVolumeDetachTimeout = nil
   395  
   396  	// Remove the version part from the references APIVersion field,
   397  	// for more details see issue #2183 and #2140.
   398  	templateCopy.Spec.InfrastructureRef.APIVersion = templateCopy.Spec.InfrastructureRef.GroupVersionKind().Group
   399  	if templateCopy.Spec.Bootstrap.ConfigRef != nil {
   400  		templateCopy.Spec.Bootstrap.ConfigRef.APIVersion = templateCopy.Spec.Bootstrap.ConfigRef.GroupVersionKind().Group
   401  	}
   402  
   403  	return templateCopy
   404  }
   405  
   406  // FindNewMachineSet returns the new MS this given deployment targets (the one with the same machine template, ignoring
   407  // in-place mutable fields).
   408  // Note: If the reconciliation time is after the deployment's `rolloutAfter` time, a MS has to be newer than
   409  // `rolloutAfter` to be considered as matching the deployment's intent.
   410  // NOTE: If we find a matching MachineSet which only differs in in-place mutable fields we can use it to
   411  // fulfill the intent of the MachineDeployment by just updating the MachineSet to propagate in-place mutable fields.
   412  // Thus we don't have to create a new MachineSet and we can avoid an unnecessary rollout.
   413  // NOTE: Even after we changed EqualMachineTemplate to ignore fields that are propagated in-place we can guarantee that if there exists a "new machineset"
   414  // using the old logic then a new machineset will definitely exist using the new logic. The new logic is looser. Therefore, we will
   415  // not face a case where there exists a machine set matching the old logic but there does not exist a machineset matching the new logic.
   416  // In fact previously not matching MS can now start matching the target. Since there could be multiple matches, lets choose the
   417  // MS with the most replicas so that there is minimum machine churn.
   418  func FindNewMachineSet(deployment *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet, reconciliationTime *metav1.Time) *clusterv1.MachineSet {
   419  	sort.Sort(MachineSetsByDecreasingReplicas(msList))
   420  	for i := range msList {
   421  		if EqualMachineTemplate(&msList[i].Spec.Template, &deployment.Spec.Template) &&
   422  			!shouldRolloutAfter(msList[i], reconciliationTime, deployment.Spec.RolloutAfter) {
   423  			// In rare cases, such as after cluster upgrades, Deployment may end up with
   424  			// having more than one new MachineSets that have the same template,
   425  			// see https://github.com/kubernetes/kubernetes/issues/40415
   426  			// We deterministically choose the oldest new MachineSet with matching template hash.
   427  			return msList[i]
   428  		}
   429  	}
   430  	// new MachineSet does not exist.
   431  	return nil
   432  }
   433  
   434  func shouldRolloutAfter(ms *clusterv1.MachineSet, reconciliationTime *metav1.Time, rolloutAfter *metav1.Time) bool {
   435  	if ms == nil {
   436  		return false
   437  	}
   438  	if reconciliationTime == nil || rolloutAfter == nil {
   439  		return false
   440  	}
   441  	return ms.CreationTimestamp.Before(rolloutAfter) && rolloutAfter.Before(reconciliationTime)
   442  }
   443  
   444  // FindOldMachineSets returns the old machine sets targeted by the given Deployment, within the given slice of MSes.
   445  // Returns a list of machine sets which contains all old machine sets.
   446  func FindOldMachineSets(deployment *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet, reconciliationTime *metav1.Time) []*clusterv1.MachineSet {
   447  	allMSs := make([]*clusterv1.MachineSet, 0, len(msList))
   448  	newMS := FindNewMachineSet(deployment, msList, reconciliationTime)
   449  	for _, ms := range msList {
   450  		// Filter out new machine set
   451  		if newMS != nil && ms.UID == newMS.UID {
   452  			continue
   453  		}
   454  		allMSs = append(allMSs, ms)
   455  	}
   456  	return allMSs
   457  }
   458  
   459  // GetReplicaCountForMachineSets returns the sum of Replicas of the given machine sets.
   460  func GetReplicaCountForMachineSets(machineSets []*clusterv1.MachineSet) int32 {
   461  	totalReplicas := int32(0)
   462  	for _, ms := range machineSets {
   463  		if ms != nil {
   464  			totalReplicas += *(ms.Spec.Replicas)
   465  		}
   466  	}
   467  	return totalReplicas
   468  }
   469  
   470  // GetActualReplicaCountForMachineSets returns the sum of actual replicas of the given machine sets.
   471  func GetActualReplicaCountForMachineSets(machineSets []*clusterv1.MachineSet) int32 {
   472  	totalActualReplicas := int32(0)
   473  	for _, ms := range machineSets {
   474  		if ms != nil {
   475  			totalActualReplicas += ms.Status.Replicas
   476  		}
   477  	}
   478  	return totalActualReplicas
   479  }
   480  
   481  // TotalMachineSetsReplicaSum returns sum of max(ms.Spec.Replicas, ms.Status.Replicas) across all the machine sets.
   482  //
   483  // This is used to guarantee that the total number of machines will not exceed md.Spec.Replicas + maxSurge.
   484  // Use max(spec.Replicas,status.Replicas) to cover the cases that:
   485  // 1. Scale up, where spec.Replicas increased but no machine created yet, so spec.Replicas > status.Replicas
   486  // 2. Scale down, where spec.Replicas decreased but machine not deleted yet, so spec.Replicas < status.Replicas.
   487  func TotalMachineSetsReplicaSum(machineSets []*clusterv1.MachineSet) int32 {
   488  	totalReplicas := int32(0)
   489  	for _, ms := range machineSets {
   490  		if ms != nil {
   491  			totalReplicas += max(*(ms.Spec.Replicas), ms.Status.Replicas)
   492  		}
   493  	}
   494  	return totalReplicas
   495  }
   496  
   497  // GetReadyReplicaCountForMachineSets returns the number of ready machines corresponding to the given machine sets.
   498  func GetReadyReplicaCountForMachineSets(machineSets []*clusterv1.MachineSet) int32 {
   499  	totalReadyReplicas := int32(0)
   500  	for _, ms := range machineSets {
   501  		if ms != nil {
   502  			totalReadyReplicas += ms.Status.ReadyReplicas
   503  		}
   504  	}
   505  	return totalReadyReplicas
   506  }
   507  
   508  // GetAvailableReplicaCountForMachineSets returns the number of available machines corresponding to the given machine sets.
   509  func GetAvailableReplicaCountForMachineSets(machineSets []*clusterv1.MachineSet) int32 {
   510  	totalAvailableReplicas := int32(0)
   511  	for _, ms := range machineSets {
   512  		if ms != nil {
   513  			totalAvailableReplicas += ms.Status.AvailableReplicas
   514  		}
   515  	}
   516  	return totalAvailableReplicas
   517  }
   518  
   519  // IsRollingUpdate returns true if the strategy type is a rolling update.
   520  func IsRollingUpdate(deployment *clusterv1.MachineDeployment) bool {
   521  	return deployment.Spec.Strategy.Type == clusterv1.RollingUpdateMachineDeploymentStrategyType
   522  }
   523  
   524  // DeploymentComplete considers a deployment to be complete once all of its desired replicas
   525  // are updated and available, and no old machines are running.
   526  func DeploymentComplete(deployment *clusterv1.MachineDeployment, newStatus *clusterv1.MachineDeploymentStatus) bool {
   527  	return newStatus.UpdatedReplicas == *(deployment.Spec.Replicas) &&
   528  		newStatus.Replicas == *(deployment.Spec.Replicas) &&
   529  		newStatus.AvailableReplicas == *(deployment.Spec.Replicas) &&
   530  		newStatus.ObservedGeneration >= deployment.Generation
   531  }
   532  
   533  // NewMSNewReplicas calculates the number of replicas a deployment's new MS should have.
   534  // When one of the following is true, we're rolling out the deployment; otherwise, we're scaling it.
   535  // 1) The new MS is saturated: newMS's replicas == deployment's replicas
   536  // 2) For RollingUpdateStrategy: Max number of machines allowed is reached: deployment's replicas + maxSurge == all MSs' replicas.
   537  // 3) For OnDeleteStrategy: Max number of machines allowed is reached: deployment's replicas == all MSs' replicas.
   538  func NewMSNewReplicas(deployment *clusterv1.MachineDeployment, allMSs []*clusterv1.MachineSet, newMSReplicas int32) (int32, error) {
   539  	switch deployment.Spec.Strategy.Type {
   540  	case clusterv1.RollingUpdateMachineDeploymentStrategyType:
   541  		// Check if we can scale up.
   542  		maxSurge, err := intstrutil.GetScaledValueFromIntOrPercent(deployment.Spec.Strategy.RollingUpdate.MaxSurge, int(*(deployment.Spec.Replicas)), true)
   543  		if err != nil {
   544  			return 0, err
   545  		}
   546  		// Find the total number of machines
   547  		currentMachineCount := TotalMachineSetsReplicaSum(allMSs)
   548  		maxTotalMachines := *(deployment.Spec.Replicas) + int32(maxSurge)
   549  		if currentMachineCount >= maxTotalMachines {
   550  			// Cannot scale up.
   551  			return newMSReplicas, nil
   552  		}
   553  		// Scale up.
   554  		scaleUpCount := maxTotalMachines - currentMachineCount
   555  		// Do not exceed the number of desired replicas.
   556  		scaleUpCount = min(scaleUpCount, *(deployment.Spec.Replicas)-newMSReplicas)
   557  		return newMSReplicas + scaleUpCount, nil
   558  	case clusterv1.OnDeleteMachineDeploymentStrategyType:
   559  		// Find the total number of machines
   560  		currentMachineCount := TotalMachineSetsReplicaSum(allMSs)
   561  		if currentMachineCount >= *(deployment.Spec.Replicas) {
   562  			// Cannot scale up as more replicas exist than desired number of replicas in the MachineDeployment.
   563  			return newMSReplicas, nil
   564  		}
   565  		// Scale up the latest MachineSet so the total amount of replicas across all MachineSets match
   566  		// the desired number of replicas in the MachineDeployment
   567  		scaleUpCount := *(deployment.Spec.Replicas) - currentMachineCount
   568  		return newMSReplicas + scaleUpCount, nil
   569  	default:
   570  		return 0, fmt.Errorf("failed to compute replicas: deployment strategy %v isn't supported", deployment.Spec.Strategy.Type)
   571  	}
   572  }
   573  
   574  // IsSaturated checks if the new machine set is saturated by comparing its size with its deployment size.
   575  // Both the deployment and the machine set have to believe this machine set can own all of the desired
   576  // replicas in the deployment and the annotation helps in achieving that. All machines of the MachineSet
   577  // need to be available.
   578  func IsSaturated(deployment *clusterv1.MachineDeployment, ms *clusterv1.MachineSet) bool {
   579  	if ms == nil {
   580  		return false
   581  	}
   582  	desiredString := ms.Annotations[clusterv1.DesiredReplicasAnnotation]
   583  	desired, err := strconv.ParseInt(desiredString, 10, 32)
   584  	if err != nil {
   585  		return false
   586  	}
   587  	return *(ms.Spec.Replicas) == *(deployment.Spec.Replicas) &&
   588  		int32(desired) == *(deployment.Spec.Replicas) &&
   589  		ms.Status.AvailableReplicas == *(deployment.Spec.Replicas)
   590  }
   591  
   592  // ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one
   593  // step. For example:
   594  //
   595  // 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1)
   596  // 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1)
   597  // 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
   598  // 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1)
   599  // 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
   600  // 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1).
   601  func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) {
   602  	surge, err := intstrutil.GetScaledValueFromIntOrPercent(maxSurge, int(desired), true)
   603  	if err != nil {
   604  		return 0, 0, err
   605  	}
   606  	unavailable, err := intstrutil.GetScaledValueFromIntOrPercent(maxUnavailable, int(desired), false)
   607  	if err != nil {
   608  		return 0, 0, err
   609  	}
   610  
   611  	if surge == 0 && unavailable == 0 {
   612  		// Validation should never allow the user to explicitly use zero values for both maxSurge
   613  		// maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero.
   614  		// If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the
   615  		// theory that surge might not work due to quota.
   616  		unavailable = 1
   617  	}
   618  
   619  	return int32(surge), int32(unavailable), nil
   620  }
   621  
   622  // FilterActiveMachineSets returns machine sets that have (or at least ought to have) machines.
   623  func FilterActiveMachineSets(machineSets []*clusterv1.MachineSet) []*clusterv1.MachineSet {
   624  	activeFilter := func(ms *clusterv1.MachineSet) bool {
   625  		return ms != nil && ms.Spec.Replicas != nil && *(ms.Spec.Replicas) > 0
   626  	}
   627  	return FilterMachineSets(machineSets, activeFilter)
   628  }
   629  
   630  type filterMS func(ms *clusterv1.MachineSet) bool
   631  
   632  // FilterMachineSets returns machine sets that are filtered by filterFn (all returned ones should match filterFn).
   633  func FilterMachineSets(mSes []*clusterv1.MachineSet, filterFn filterMS) []*clusterv1.MachineSet {
   634  	var filtered []*clusterv1.MachineSet
   635  	for i := range mSes {
   636  		if filterFn(mSes[i]) {
   637  			filtered = append(filtered, mSes[i])
   638  		}
   639  	}
   640  	return filtered
   641  }
   642  
   643  // CloneAndAddLabel clones the given map and returns a new map with the given key and value added.
   644  // Returns the given map, if labelKey is empty.
   645  func CloneAndAddLabel(labels map[string]string, labelKey, labelValue string) map[string]string {
   646  	if labelKey == "" {
   647  		// Don't need to add a label.
   648  		return labels
   649  	}
   650  	// Clone.
   651  	newLabels := map[string]string{}
   652  	for key, value := range labels {
   653  		newLabels[key] = value
   654  	}
   655  	newLabels[labelKey] = labelValue
   656  	return newLabels
   657  }
   658  
   659  // CloneSelectorAndAddLabel clones the given selector and returns a new selector with the given key and value added.
   660  // Returns the given selector, if labelKey is empty.
   661  func CloneSelectorAndAddLabel(selector *metav1.LabelSelector, labelKey, labelValue string) *metav1.LabelSelector {
   662  	if labelKey == "" {
   663  		// Don't need to add a label.
   664  		return selector
   665  	}
   666  
   667  	// Clone.
   668  	newSelector := new(metav1.LabelSelector)
   669  
   670  	// TODO(madhusudancs): Check if you can use deepCopy_extensions_LabelSelector here.
   671  	newSelector.MatchLabels = make(map[string]string)
   672  	if selector.MatchLabels != nil {
   673  		for key, val := range selector.MatchLabels {
   674  			newSelector.MatchLabels[key] = val
   675  		}
   676  	}
   677  	newSelector.MatchLabels[labelKey] = labelValue
   678  
   679  	if selector.MatchExpressions != nil {
   680  		newMExps := make([]metav1.LabelSelectorRequirement, len(selector.MatchExpressions))
   681  		for i, me := range selector.MatchExpressions {
   682  			newMExps[i].Key = me.Key
   683  			newMExps[i].Operator = me.Operator
   684  			if me.Values != nil {
   685  				newMExps[i].Values = make([]string, len(me.Values))
   686  				copy(newMExps[i].Values, me.Values)
   687  			} else {
   688  				newMExps[i].Values = nil
   689  			}
   690  		}
   691  		newSelector.MatchExpressions = newMExps
   692  	} else {
   693  		newSelector.MatchExpressions = nil
   694  	}
   695  
   696  	return newSelector
   697  }
   698  
   699  // GetDeletingMachineCount gets the number of machines that are in the process of being deleted
   700  // in a machineList.
   701  func GetDeletingMachineCount(machineList *clusterv1.MachineList) int32 {
   702  	var deletingMachineCount int32
   703  	for _, machine := range machineList.Items {
   704  		if !machine.GetDeletionTimestamp().IsZero() {
   705  			deletingMachineCount++
   706  		}
   707  	}
   708  	return deletingMachineCount
   709  }