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 }