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  }