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