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  }