k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/volume/pvprotection/pv_protection_controller.go (about)

     1  /*
     2  Copyright 2018 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 pvprotection
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	coreinformers "k8s.io/client-go/informers/core/v1"
    30  	clientset "k8s.io/client-go/kubernetes"
    31  	corelisters "k8s.io/client-go/listers/core/v1"
    32  	"k8s.io/client-go/tools/cache"
    33  	"k8s.io/client-go/util/workqueue"
    34  	"k8s.io/klog/v2"
    35  	"k8s.io/kubernetes/pkg/controller/volume/protectionutil"
    36  	"k8s.io/kubernetes/pkg/util/slice"
    37  	volumeutil "k8s.io/kubernetes/pkg/volume/util"
    38  )
    39  
    40  // Controller is controller that removes PVProtectionFinalizer
    41  // from PVs that are not bound to PVCs.
    42  type Controller struct {
    43  	client clientset.Interface
    44  
    45  	pvLister       corelisters.PersistentVolumeLister
    46  	pvListerSynced cache.InformerSynced
    47  
    48  	queue workqueue.TypedRateLimitingInterface[string]
    49  }
    50  
    51  // NewPVProtectionController returns a new *Controller.
    52  func NewPVProtectionController(logger klog.Logger, pvInformer coreinformers.PersistentVolumeInformer, cl clientset.Interface) *Controller {
    53  	e := &Controller{
    54  		client: cl,
    55  		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
    56  			workqueue.DefaultTypedControllerRateLimiter[string](),
    57  			workqueue.TypedRateLimitingQueueConfig[string]{Name: "pvprotection"},
    58  		),
    59  	}
    60  
    61  	e.pvLister = pvInformer.Lister()
    62  	e.pvListerSynced = pvInformer.Informer().HasSynced
    63  	pvInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    64  		AddFunc: func(obj interface{}) {
    65  			e.pvAddedUpdated(logger, obj)
    66  		},
    67  		UpdateFunc: func(old, new interface{}) {
    68  			e.pvAddedUpdated(logger, new)
    69  		},
    70  	})
    71  
    72  	return e
    73  }
    74  
    75  // Run runs the controller goroutines.
    76  func (c *Controller) Run(ctx context.Context, workers int) {
    77  	defer utilruntime.HandleCrash()
    78  	defer c.queue.ShutDown()
    79  
    80  	logger := klog.FromContext(ctx)
    81  	logger.Info("Starting PV protection controller")
    82  	defer logger.Info("Shutting down PV protection controller")
    83  
    84  	if !cache.WaitForNamedCacheSync("PV protection", ctx.Done(), c.pvListerSynced) {
    85  		return
    86  	}
    87  
    88  	for i := 0; i < workers; i++ {
    89  		go wait.UntilWithContext(ctx, c.runWorker, time.Second)
    90  	}
    91  
    92  	<-ctx.Done()
    93  }
    94  
    95  func (c *Controller) runWorker(ctx context.Context) {
    96  	for c.processNextWorkItem(ctx) {
    97  	}
    98  }
    99  
   100  // processNextWorkItem deals with one pvcKey off the queue.  It returns false when it's time to quit.
   101  func (c *Controller) processNextWorkItem(ctx context.Context) bool {
   102  	pvKey, quit := c.queue.Get()
   103  	if quit {
   104  		return false
   105  	}
   106  	defer c.queue.Done(pvKey)
   107  
   108  	pvName := pvKey
   109  
   110  	err := c.processPV(ctx, pvName)
   111  	if err == nil {
   112  		c.queue.Forget(pvKey)
   113  		return true
   114  	}
   115  
   116  	utilruntime.HandleError(fmt.Errorf("PV %v failed with : %v", pvKey, err))
   117  	c.queue.AddRateLimited(pvKey)
   118  
   119  	return true
   120  }
   121  
   122  func (c *Controller) processPV(ctx context.Context, pvName string) error {
   123  	logger := klog.FromContext(ctx)
   124  	logger.V(4).Info("Processing PV", "PV", klog.KRef("", pvName))
   125  	startTime := time.Now()
   126  	defer func() {
   127  		logger.V(4).Info("Finished processing PV", "PV", klog.KRef("", pvName), "cost", time.Since(startTime))
   128  	}()
   129  
   130  	pv, err := c.pvLister.Get(pvName)
   131  	if apierrors.IsNotFound(err) {
   132  		logger.V(4).Info("PV not found, ignoring", "PV", klog.KRef("", pvName))
   133  		return nil
   134  	}
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	if protectionutil.IsDeletionCandidate(pv, volumeutil.PVProtectionFinalizer) {
   140  		// PV should be deleted. Check if it's used and remove finalizer if
   141  		// it's not.
   142  		isUsed := c.isBeingUsed(pv)
   143  		if !isUsed {
   144  			return c.removeFinalizer(ctx, pv)
   145  		}
   146  		logger.V(4).Info("Keeping PV because it is being used", "PV", klog.KRef("", pvName))
   147  	}
   148  
   149  	if protectionutil.NeedToAddFinalizer(pv, volumeutil.PVProtectionFinalizer) {
   150  		// PV is not being deleted -> it should have the finalizer. The
   151  		// finalizer should be added by admission plugin, this is just to add
   152  		// the finalizer to old PVs that were created before the admission
   153  		// plugin was enabled.
   154  		return c.addFinalizer(ctx, pv)
   155  	}
   156  	return nil
   157  }
   158  
   159  func (c *Controller) addFinalizer(ctx context.Context, pv *v1.PersistentVolume) error {
   160  	pvClone := pv.DeepCopy()
   161  	pvClone.ObjectMeta.Finalizers = append(pvClone.ObjectMeta.Finalizers, volumeutil.PVProtectionFinalizer)
   162  	_, err := c.client.CoreV1().PersistentVolumes().Update(ctx, pvClone, metav1.UpdateOptions{})
   163  	logger := klog.FromContext(ctx)
   164  	if err != nil {
   165  		logger.V(3).Info("Error adding protection finalizer to PV", "PV", klog.KObj(pv), "err", err)
   166  		return err
   167  	}
   168  	logger.V(3).Info("Added protection finalizer to PV", "PV", klog.KObj(pv))
   169  	return nil
   170  }
   171  
   172  func (c *Controller) removeFinalizer(ctx context.Context, pv *v1.PersistentVolume) error {
   173  	pvClone := pv.DeepCopy()
   174  	pvClone.ObjectMeta.Finalizers = slice.RemoveString(pvClone.ObjectMeta.Finalizers, volumeutil.PVProtectionFinalizer, nil)
   175  	_, err := c.client.CoreV1().PersistentVolumes().Update(ctx, pvClone, metav1.UpdateOptions{})
   176  	logger := klog.FromContext(ctx)
   177  	if err != nil {
   178  		logger.V(3).Info("Error removing protection finalizer from PV", "PV", klog.KObj(pv), "err", err)
   179  		return err
   180  	}
   181  	logger.V(3).Info("Removed protection finalizer from PV", "PV", klog.KObj(pv))
   182  	return nil
   183  }
   184  
   185  func (c *Controller) isBeingUsed(pv *v1.PersistentVolume) bool {
   186  	// check if PV is being bound to a PVC by its status
   187  	// the status will be updated by PV controller
   188  	if pv.Status.Phase == v1.VolumeBound {
   189  		// the PV is being used now
   190  		return true
   191  	}
   192  
   193  	return false
   194  }
   195  
   196  // pvAddedUpdated reacts to pv added/updated events
   197  func (c *Controller) pvAddedUpdated(logger klog.Logger, obj interface{}) {
   198  	pv, ok := obj.(*v1.PersistentVolume)
   199  	if !ok {
   200  		utilruntime.HandleError(fmt.Errorf("PV informer returned non-PV object: %#v", obj))
   201  		return
   202  	}
   203  	logger.V(4).Info("Got event on PV", "PV", klog.KObj(pv))
   204  
   205  	if protectionutil.NeedToAddFinalizer(pv, volumeutil.PVProtectionFinalizer) || protectionutil.IsDeletionCandidate(pv, volumeutil.PVProtectionFinalizer) {
   206  		c.queue.Add(pv.Name)
   207  	}
   208  }