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