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