sigs.k8s.io/cluster-api@v1.6.3/internal/controllers/topology/machineset/machineset_controller.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 machineset 18 19 import ( 20 "context" 21 22 "github.com/pkg/errors" 23 apierrors "k8s.io/apimachinery/pkg/api/errors" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 "k8s.io/apimachinery/pkg/types" 26 kerrors "k8s.io/apimachinery/pkg/util/errors" 27 "k8s.io/klog/v2" 28 ctrl "sigs.k8s.io/controller-runtime" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 "sigs.k8s.io/controller-runtime/pkg/controller" 31 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 32 33 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 tlog "sigs.k8s.io/cluster-api/internal/log" 35 "sigs.k8s.io/cluster-api/util" 36 "sigs.k8s.io/cluster-api/util/annotations" 37 clog "sigs.k8s.io/cluster-api/util/log" 38 "sigs.k8s.io/cluster-api/util/patch" 39 "sigs.k8s.io/cluster-api/util/predicates" 40 ) 41 42 // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io;bootstrap.cluster.x-k8s.io,resources=*,verbs=delete 43 // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=get;list;watch 44 // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinedeployments,verbs=get;list;watch 45 // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinesets;machinesets/finalizers,verbs=get;list;watch;update;patch 46 47 // Reconciler deletes referenced templates during deletion of topology-owned MachineSets. 48 // The templates are only deleted, if they are not used in other MachineDeployments or MachineSets which are not in deleting state, 49 // i.e. the templates would otherwise be orphaned after the MachineSet deletion completes. 50 // Note: To achieve this the reconciler sets a finalizer to hook into the MachineSet deletions. 51 type Reconciler struct { 52 Client client.Client 53 // APIReader is used to list MachineSets directly via the API server to avoid 54 // race conditions caused by an outdated cache. 55 APIReader client.Reader 56 WatchFilterValue string 57 } 58 59 func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { 60 err := ctrl.NewControllerManagedBy(mgr). 61 For(&clusterv1.MachineSet{}). 62 Named("topology/machineset"). 63 WithOptions(options). 64 WithEventFilter(predicates.All(ctrl.LoggerFrom(ctx), 65 predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue), 66 predicates.ResourceIsTopologyOwned(ctrl.LoggerFrom(ctx)), 67 )). 68 Complete(r) 69 if err != nil { 70 return errors.Wrap(err, "failed setting up with a controller manager") 71 } 72 73 return nil 74 } 75 76 // Reconcile deletes referenced templates during deletion of topology-owned MachineSets. 77 // The templates are only deleted, if they are not used in other MachineDeployments or MachineSets which are not in deleting state, 78 // i.e. the templates would otherwise be orphaned after the MachineSet deletion completes. 79 // Additional context: 80 // * MachineSet deletion: 81 // - MachineSets are deleted and garbage collected first (without waiting until all Machines are also deleted) 82 // - After that, deletion of Machines is automatically triggered by Kubernetes based on owner references. 83 // 84 // Note: We assume templates are not reused by different MachineDeployments, which is (only) true for topology-owned 85 // 86 // MachineDeployments. 87 // 88 // We don't have to set the finalizer, as it's already set during MachineSet creation 89 // in the MachineSet controller. 90 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { 91 // Fetch the MachineSet instance. 92 ms := &clusterv1.MachineSet{} 93 if err := r.Client.Get(ctx, req.NamespacedName, ms); err != nil { 94 if apierrors.IsNotFound(err) { 95 // Object not found, return. 96 return ctrl.Result{}, nil 97 } 98 // Error reading the object - requeue the request. 99 return ctrl.Result{}, errors.Wrapf(err, "failed to get MachineSet/%s", req.NamespacedName.Name) 100 } 101 102 // AddOwners adds the owners of MachineSet as k/v pairs to the logger. 103 // Specifically, it will add MachineDeployment. 104 ctx, log, err := clog.AddOwners(ctx, r.Client, ms) 105 if err != nil { 106 return ctrl.Result{}, err 107 } 108 109 log = log.WithValues("Cluster", klog.KRef(ms.Namespace, ms.Spec.ClusterName)) 110 ctx = ctrl.LoggerInto(ctx, log) 111 112 cluster, err := util.GetClusterByName(ctx, r.Client, ms.Namespace, ms.Spec.ClusterName) 113 if err != nil { 114 return ctrl.Result{}, err 115 } 116 117 // Return early if the object or Cluster is paused. 118 if annotations.IsPaused(cluster, ms) { 119 log.Info("Reconciliation is paused for this object") 120 return ctrl.Result{}, nil 121 } 122 123 // Create a patch helper to add or remove the finalizer from the MachineSet. 124 patchHelper, err := patch.NewHelper(ms, r.Client) 125 if err != nil { 126 return ctrl.Result{}, errors.Wrapf(err, "failed to create patch helper for %s", tlog.KObj{Obj: ms}) 127 } 128 defer func() { 129 if err := patchHelper.Patch(ctx, ms); err != nil { 130 reterr = kerrors.NewAggregate([]error{reterr, errors.Wrapf(err, "failed to patch %s", tlog.KObj{Obj: ms})}) 131 } 132 }() 133 134 // Handle deletion reconciliation loop. 135 if !ms.ObjectMeta.DeletionTimestamp.IsZero() { 136 return ctrl.Result{}, r.reconcileDelete(ctx, ms) 137 } 138 139 // Add finalizer first if not set to avoid the race condition between init and delete. 140 // Note: Finalizers in general can only be added when the deletionTimestamp is not set. 141 if !controllerutil.ContainsFinalizer(ms, clusterv1.MachineSetTopologyFinalizer) { 142 controllerutil.AddFinalizer(ms, clusterv1.MachineSetTopologyFinalizer) 143 return ctrl.Result{}, nil 144 } 145 146 return ctrl.Result{}, nil 147 } 148 149 // reconcileDelete deletes templates referenced in a MachineSet, if the templates are not used by other 150 // MachineDeployments or MachineSets. 151 func (r *Reconciler) reconcileDelete(ctx context.Context, ms *clusterv1.MachineSet) error { 152 // Gets the name of the MachineDeployment that controls this MachineSet. 153 mdName, err := getMachineDeploymentName(ms) 154 if err != nil { 155 return err 156 } 157 158 // Get all the MachineSets for the MachineDeployment. 159 msList, err := GetMachineSetsForDeployment(ctx, r.APIReader, *mdName) 160 if err != nil { 161 return err 162 } 163 164 // Fetch the MachineDeployment instance, if it still exists. 165 // Note: This can happen because MachineDeployments are deleted before their corresponding MachineSets. 166 md := &clusterv1.MachineDeployment{} 167 if err := r.Client.Get(ctx, *mdName, md); err != nil { 168 if !apierrors.IsNotFound(err) { 169 // Error reading the object - requeue the request. 170 return errors.Wrapf(err, "failed to get MachineDeployment/%s", mdName.Name) 171 } 172 // If the MachineDeployment doesn't exist anymore, set md to nil, so we can handle that case correctly below. 173 md = nil 174 } 175 176 // Calculate which templates are still in use by MachineDeployments or MachineSets which are not in deleting state. 177 templatesInUse, err := CalculateTemplatesInUse(md, msList) 178 if err != nil { 179 return err 180 } 181 182 // Delete unused templates. 183 ref := ms.Spec.Template.Spec.Bootstrap.ConfigRef 184 if err := DeleteTemplateIfUnused(ctx, r.Client, templatesInUse, ref); err != nil { 185 return errors.Wrapf(err, "failed to delete bootstrap template for %s", tlog.KObj{Obj: ms}) 186 } 187 ref = &ms.Spec.Template.Spec.InfrastructureRef 188 if err := DeleteTemplateIfUnused(ctx, r.Client, templatesInUse, ref); err != nil { 189 return errors.Wrapf(err, "failed to delete infrastructure template for %s", tlog.KObj{Obj: ms}) 190 } 191 192 // Remove the finalizer so the MachineSet can be garbage collected by Kubernetes. 193 controllerutil.RemoveFinalizer(ms, clusterv1.MachineSetTopologyFinalizer) 194 195 return nil 196 } 197 198 // getMachineDeploymentName calculates the MachineDeployment name based on owner references. 199 func getMachineDeploymentName(ms *clusterv1.MachineSet) (*types.NamespacedName, error) { 200 for _, ref := range ms.GetOwnerReferences() { 201 if ref.Kind != "MachineDeployment" { 202 continue 203 } 204 gv, err := schema.ParseGroupVersion(ref.APIVersion) 205 if err != nil { 206 return nil, errors.Errorf("could not calculate MachineDeployment name for %s: invalid apiVersion %q: %v", 207 tlog.KObj{Obj: ms}, ref.APIVersion, err) 208 } 209 if gv.Group == clusterv1.GroupVersion.Group { 210 return &client.ObjectKey{Namespace: ms.Namespace, Name: ref.Name}, nil 211 } 212 } 213 214 // Note: Once we set an owner reference to a MachineDeployment in a MachineSet it stays there 215 // and is not deleted when the MachineDeployment is deleted. So we assume there's something wrong, 216 // if we couldn't find a MachineDeployment owner reference. 217 return nil, errors.Errorf("could not calculate MachineDeployment name for %s", tlog.KObj{Obj: ms}) 218 }