sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/machinedeployment/machinedeployment_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 machinedeployment 18 19 import ( 20 "context" 21 22 "github.com/pkg/errors" 23 apierrors "k8s.io/apimachinery/pkg/api/errors" 24 kerrors "k8s.io/apimachinery/pkg/util/errors" 25 "k8s.io/klog/v2" 26 ctrl "sigs.k8s.io/controller-runtime" 27 "sigs.k8s.io/controller-runtime/pkg/builder" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 "sigs.k8s.io/controller-runtime/pkg/controller" 30 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 31 "sigs.k8s.io/controller-runtime/pkg/handler" 32 33 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 "sigs.k8s.io/cluster-api/internal/controllers/topology/machineset" 35 tlog "sigs.k8s.io/cluster-api/internal/log" 36 "sigs.k8s.io/cluster-api/util" 37 "sigs.k8s.io/cluster-api/util/annotations" 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;machinedeployments/finalizers,verbs=get;list;watch;update;patch 45 // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinesets,verbs=get;list;watch 46 47 // Reconciler deletes referenced templates during deletion of topology-owned MachineDeployments. 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 MachineDeployment deletion completes. 50 // Note: To achieve this the cluster topology controller sets a finalizer to hook into the MachineDeployment 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 clusterToMachineDeployments, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &clusterv1.MachineDeploymentList{}, mgr.GetScheme()) 61 if err != nil { 62 return err 63 } 64 65 err = ctrl.NewControllerManagedBy(mgr). 66 For(&clusterv1.MachineDeployment{}, 67 builder.WithPredicates( 68 predicates.All(ctrl.LoggerFrom(ctx), 69 predicates.ResourceIsTopologyOwned(ctrl.LoggerFrom(ctx)), 70 predicates.ResourceNotPaused(ctrl.LoggerFrom(ctx))), 71 ), 72 ). 73 Named("topology/machinedeployment"). 74 WithOptions(options). 75 WithEventFilter(predicates.ResourceHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)). 76 Watches( 77 &clusterv1.Cluster{}, 78 handler.EnqueueRequestsFromMapFunc(clusterToMachineDeployments), 79 builder.WithPredicates( 80 predicates.All(ctrl.LoggerFrom(ctx), 81 predicates.ClusterUnpaused(ctrl.LoggerFrom(ctx)), 82 predicates.ClusterHasTopology(ctrl.LoggerFrom(ctx)), 83 ), 84 ), 85 ). 86 Complete(r) 87 if err != nil { 88 return errors.Wrap(err, "failed setting up with a controller manager") 89 } 90 91 return nil 92 } 93 94 // Reconcile deletes referenced templates during deletion of topology-owned MachineDeployments. 95 // The templates are only deleted, if they are not used in other MachineDeployments or MachineSets which are not in deleting state, 96 // i.e. the templates would otherwise be orphaned after the MachineDeployment deletion completes. 97 // Additional context: 98 // * MachineDeployment deletion: 99 // - MachineDeployments are deleted and garbage collected first (without waiting until all MachineSets are also deleted). 100 // - After that, deletion of MachineSets is automatically triggered by Kubernetes based on owner references. 101 // 102 // Note: We assume templates are not reused by different MachineDeployments, which is only true for topology-owned 103 // 104 // MachineDeployments. 105 // 106 // We don't have to set the finalizer, as it's already set during MachineDeployment creation 107 // in the cluster topology controller. 108 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { 109 log := ctrl.LoggerFrom(ctx) 110 111 // Fetch the MachineDeployment instance. 112 md := &clusterv1.MachineDeployment{} 113 if err := r.Client.Get(ctx, req.NamespacedName, md); 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 MachineDeployment/%s", req.NamespacedName.Name) 120 } 121 122 log = log.WithValues("Cluster", klog.KRef(md.Namespace, md.Spec.ClusterName)) 123 ctx = ctrl.LoggerInto(ctx, log) 124 125 cluster, err := util.GetClusterByName(ctx, r.Client, md.Namespace, md.Spec.ClusterName) 126 if err != nil { 127 return ctrl.Result{}, err 128 } 129 130 // Return early if the object or Cluster is paused. 131 if annotations.IsPaused(cluster, md) { 132 log.Info("Reconciliation is paused for this object") 133 return ctrl.Result{}, nil 134 } 135 136 // Create a patch helper to add or remove the finalizer from the MachineDeployment. 137 patchHelper, err := patch.NewHelper(md, r.Client) 138 if err != nil { 139 return ctrl.Result{}, err 140 } 141 defer func() { 142 if err := patchHelper.Patch(ctx, md); err != nil { 143 reterr = kerrors.NewAggregate([]error{reterr, err}) 144 } 145 }() 146 147 // Handle deletion reconciliation loop. 148 if !md.ObjectMeta.DeletionTimestamp.IsZero() { 149 return ctrl.Result{}, r.reconcileDelete(ctx, md) 150 } 151 152 // Add finalizer first if not set to avoid the race condition between init and delete. 153 // Note: Finalizers in general can only be added when the deletionTimestamp is not set. 154 if !controllerutil.ContainsFinalizer(md, clusterv1.MachineDeploymentTopologyFinalizer) { 155 controllerutil.AddFinalizer(md, clusterv1.MachineDeploymentTopologyFinalizer) 156 return ctrl.Result{}, nil 157 } 158 159 return ctrl.Result{}, nil 160 } 161 162 // reconcileDelete deletes templates referenced in a MachineDeployment, if the templates are not used by other 163 // MachineDeployments or MachineSets. 164 func (r *Reconciler) reconcileDelete(ctx context.Context, md *clusterv1.MachineDeployment) error { 165 // Get the corresponding MachineSets. 166 msList, err := machineset.GetMachineSetsForDeployment(ctx, r.APIReader, client.ObjectKeyFromObject(md)) 167 if err != nil { 168 return err 169 } 170 171 // Calculate which templates are still in use by MachineDeployments or MachineSets which are not in deleting state. 172 templatesInUse, err := machineset.CalculateTemplatesInUse(md, msList) 173 if err != nil { 174 return err 175 } 176 177 // Delete unused templates. 178 ref := md.Spec.Template.Spec.Bootstrap.ConfigRef 179 if err := machineset.DeleteTemplateIfUnused(ctx, r.Client, templatesInUse, ref); err != nil { 180 return errors.Wrapf(err, "failed to delete bootstrap template for %s", tlog.KObj{Obj: md}) 181 } 182 ref = &md.Spec.Template.Spec.InfrastructureRef 183 if err := machineset.DeleteTemplateIfUnused(ctx, r.Client, templatesInUse, ref); err != nil { 184 return errors.Wrapf(err, "failed to delete infrastructure template for %s", tlog.KObj{Obj: md}) 185 } 186 187 // If the MachineDeployment has a MachineHealthCheck delete it. 188 if err := r.deleteMachineHealthCheckForMachineDeployment(ctx, md); err != nil { 189 return err 190 } 191 192 controllerutil.RemoveFinalizer(md, clusterv1.MachineDeploymentTopologyFinalizer) 193 194 return nil 195 } 196 197 func (r *Reconciler) deleteMachineHealthCheckForMachineDeployment(ctx context.Context, md *clusterv1.MachineDeployment) error { 198 // MachineHealthCheck will always share the name and namespace of the MachineDeployment which created it. 199 // Create a barebones MachineHealthCheck with the MachineDeployment name and namespace and delete the object. 200 201 mhc := &clusterv1.MachineHealthCheck{} 202 mhc.SetName(md.Name) 203 mhc.SetNamespace(md.Namespace) 204 if err := r.Client.Delete(ctx, mhc); err != nil { 205 // If there is no MachineHealthCheck associated with the machineDeployment return no error. 206 if apierrors.IsNotFound(err) { 207 return nil 208 } 209 return errors.Wrapf(err, "failed to delete %s", tlog.KObj{Obj: mhc}) 210 } 211 return nil 212 }