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