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

     1  /*
     2  Copyright 2021 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 machinedeployment
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/pkg/errors"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/klog/v2"
    26  	ctrl "sigs.k8s.io/controller-runtime"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    30  	"sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil"
    31  	"sigs.k8s.io/cluster-api/util/patch"
    32  )
    33  
    34  // rolloutOnDelete implements the logic for the OnDelete MachineDeploymentStrategyType.
    35  func (r *Reconciler) rolloutOnDelete(ctx context.Context, md *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet) error {
    36  	newMS, oldMSs, err := r.getAllMachineSetsAndSyncRevision(ctx, md, msList, true)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	// newMS can be nil in case there is already a MachineSet associated with this deployment,
    42  	// but there are only either changes in annotations or MinReadySeconds. Or in other words,
    43  	// this can be nil if there are changes, but no replacement of existing machines is needed.
    44  	if newMS == nil {
    45  		return nil
    46  	}
    47  
    48  	allMSs := append(oldMSs, newMS)
    49  
    50  	// Scale up, if we can.
    51  	if err := r.reconcileNewMachineSetOnDelete(ctx, allMSs, newMS, md); err != nil {
    52  		return err
    53  	}
    54  
    55  	if err := r.syncDeploymentStatus(allMSs, newMS, md); err != nil {
    56  		return err
    57  	}
    58  
    59  	// Scale down, if we can.
    60  	if err := r.reconcileOldMachineSetsOnDelete(ctx, oldMSs, allMSs, md); err != nil {
    61  		return err
    62  	}
    63  
    64  	if err := r.syncDeploymentStatus(allMSs, newMS, md); err != nil {
    65  		return err
    66  	}
    67  
    68  	if mdutil.DeploymentComplete(md, &md.Status) {
    69  		if err := r.cleanupDeployment(ctx, oldMSs, md); err != nil {
    70  			return err
    71  		}
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  // reconcileOldMachineSetsOnDelete handles reconciliation of Old MachineSets associated with the MachineDeployment in the OnDelete MachineDeploymentStrategyType.
    78  func (r *Reconciler) reconcileOldMachineSetsOnDelete(ctx context.Context, oldMSs []*clusterv1.MachineSet, allMSs []*clusterv1.MachineSet, deployment *clusterv1.MachineDeployment) error {
    79  	log := ctrl.LoggerFrom(ctx)
    80  	if deployment.Spec.Replicas == nil {
    81  		return errors.Errorf("spec replicas for MachineDeployment %q/%q is nil, this is unexpected",
    82  			deployment.Namespace, deployment.Name)
    83  	}
    84  	log.V(4).Info("Checking to see if machines have been deleted or are in the process of deleting for old machine sets")
    85  	totalReplicas := mdutil.GetReplicaCountForMachineSets(allMSs)
    86  	scaleDownAmount := totalReplicas - *deployment.Spec.Replicas
    87  	for _, oldMS := range oldMSs {
    88  		log := log.WithValues("MachineSet", klog.KObj(oldMS))
    89  		if oldMS.Spec.Replicas == nil || *oldMS.Spec.Replicas <= 0 {
    90  			log.V(4).Info("fully scaled down")
    91  			continue
    92  		}
    93  		if oldMS.Annotations == nil {
    94  			oldMS.Annotations = map[string]string{}
    95  		}
    96  		if _, ok := oldMS.Annotations[clusterv1.DisableMachineCreateAnnotation]; !ok {
    97  			log.V(4).Info("setting annotation on old MachineSet to disable machine creation")
    98  			patchHelper, err := patch.NewHelper(oldMS, r.Client)
    99  			if err != nil {
   100  				return err
   101  			}
   102  			oldMS.Annotations[clusterv1.DisableMachineCreateAnnotation] = "true"
   103  			if err := patchHelper.Patch(ctx, oldMS); err != nil {
   104  				return err
   105  			}
   106  		}
   107  		selectorMap, err := metav1.LabelSelectorAsMap(&oldMS.Spec.Selector)
   108  		if err != nil {
   109  			log.V(4).Error(err, "failed to convert MachineSet label selector to a map")
   110  			continue
   111  		}
   112  		log.V(4).Info("Fetching Machines associated with MachineSet")
   113  		// Get all Machines linked to this MachineSet.
   114  		allMachinesInOldMS := &clusterv1.MachineList{}
   115  		if err := r.Client.List(ctx,
   116  			allMachinesInOldMS,
   117  			client.InNamespace(oldMS.Namespace),
   118  			client.MatchingLabels(selectorMap),
   119  		); err != nil {
   120  			return errors.Wrap(err, "failed to list machines")
   121  		}
   122  		totalMachineCount := int32(len(allMachinesInOldMS.Items))
   123  		log.V(4).Info("Retrieved machines", "totalMachineCount", totalMachineCount)
   124  		updatedReplicaCount := totalMachineCount - mdutil.GetDeletingMachineCount(allMachinesInOldMS)
   125  		if updatedReplicaCount < 0 {
   126  			return errors.Errorf("negative updated replica count %d for MachineSet %q, this is unexpected", updatedReplicaCount, oldMS.Name)
   127  		}
   128  		machineSetScaleDownAmountDueToMachineDeletion := *oldMS.Spec.Replicas - updatedReplicaCount
   129  		if machineSetScaleDownAmountDueToMachineDeletion < 0 {
   130  			log.V(4).Error(errors.Errorf("unexpected negative scale down amount: %d", machineSetScaleDownAmountDueToMachineDeletion), fmt.Sprintf("Error reconciling MachineSet %s", oldMS.Name))
   131  		}
   132  		scaleDownAmount -= machineSetScaleDownAmountDueToMachineDeletion
   133  		log.V(4).Info("Adjusting replica count for deleted machines", "oldReplicas", oldMS.Spec.Replicas, "newReplicas", updatedReplicaCount)
   134  		log.V(4).Info("Scaling down", "replicas", updatedReplicaCount)
   135  		if err := r.scaleMachineSet(ctx, oldMS, updatedReplicaCount, deployment); err != nil {
   136  			return err
   137  		}
   138  	}
   139  	log.V(4).Info("Finished reconcile of Old MachineSets to account for deleted machines. Now analyzing if there's more potential to scale down")
   140  	for _, oldMS := range oldMSs {
   141  		log := log.WithValues("MachineSet", klog.KObj(oldMS))
   142  		if scaleDownAmount <= 0 {
   143  			break
   144  		}
   145  		if oldMS.Spec.Replicas == nil || *oldMS.Spec.Replicas <= 0 {
   146  			log.V(4).Info("Fully scaled down")
   147  			continue
   148  		}
   149  		updatedReplicaCount := *oldMS.Spec.Replicas
   150  		if updatedReplicaCount >= scaleDownAmount {
   151  			updatedReplicaCount -= scaleDownAmount
   152  			scaleDownAmount = 0
   153  		} else {
   154  			scaleDownAmount -= updatedReplicaCount
   155  			updatedReplicaCount = 0
   156  		}
   157  		log.V(4).Info("Scaling down", "replicas", updatedReplicaCount)
   158  		if err := r.scaleMachineSet(ctx, oldMS, updatedReplicaCount, deployment); err != nil {
   159  			return err
   160  		}
   161  	}
   162  	log.V(4).Info("Finished reconcile of all old MachineSets")
   163  	return nil
   164  }
   165  
   166  // reconcileNewMachineSetOnDelete handles reconciliation of the latest MachineSet associated with the MachineDeployment in the OnDelete MachineDeploymentStrategyType.
   167  func (r *Reconciler) reconcileNewMachineSetOnDelete(ctx context.Context, allMSs []*clusterv1.MachineSet, newMS *clusterv1.MachineSet, deployment *clusterv1.MachineDeployment) error {
   168  	// logic same as reconcile logic for RollingUpdate
   169  	log := ctrl.LoggerFrom(ctx, "MachineSet", klog.KObj(newMS))
   170  
   171  	if newMS.Annotations != nil {
   172  		if _, ok := newMS.Annotations[clusterv1.DisableMachineCreateAnnotation]; ok {
   173  			log.V(4).Info("removing annotation on latest MachineSet to enable machine creation")
   174  			patchHelper, err := patch.NewHelper(newMS, r.Client)
   175  			if err != nil {
   176  				return err
   177  			}
   178  			delete(newMS.Annotations, clusterv1.DisableMachineCreateAnnotation)
   179  			err = patchHelper.Patch(ctx, newMS)
   180  			if err != nil {
   181  				return err
   182  			}
   183  		}
   184  	}
   185  	return r.reconcileNewMachineSet(ctx, allMSs, newMS, deployment)
   186  }