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

     1  /*
     2  Copyright 2017 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 expand
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"time"
    24  
    25  	"k8s.io/klog/v2"
    26  	"k8s.io/mount-utils"
    27  	utilexec "k8s.io/utils/exec"
    28  
    29  	authenticationv1 "k8s.io/api/authentication/v1"
    30  	v1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/apimachinery/pkg/util/runtime"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    37  	coreinformers "k8s.io/client-go/informers/core/v1"
    38  	clientset "k8s.io/client-go/kubernetes"
    39  	"k8s.io/client-go/kubernetes/scheme"
    40  	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
    41  	corelisters "k8s.io/client-go/listers/core/v1"
    42  	"k8s.io/client-go/tools/cache"
    43  	"k8s.io/client-go/tools/record"
    44  	"k8s.io/client-go/util/workqueue"
    45  	"k8s.io/kubernetes/pkg/controller/volume/events"
    46  	"k8s.io/kubernetes/pkg/features"
    47  	"k8s.io/kubernetes/pkg/volume"
    48  	"k8s.io/kubernetes/pkg/volume/csimigration"
    49  	"k8s.io/kubernetes/pkg/volume/util"
    50  	"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
    51  	"k8s.io/kubernetes/pkg/volume/util/subpath"
    52  	volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
    53  	"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
    54  )
    55  
    56  const (
    57  	// number of default volume expansion workers
    58  	defaultWorkerCount = 10
    59  )
    60  
    61  // ExpandController expands the pvs
    62  type ExpandController interface {
    63  	Run(ctx context.Context)
    64  }
    65  
    66  // CSINameTranslator can get the CSI Driver name based on the in-tree plugin name
    67  type CSINameTranslator interface {
    68  	GetCSINameFromInTreeName(pluginName string) (string, error)
    69  }
    70  
    71  type expandController struct {
    72  	// kubeClient is the kube API client used by volumehost to communicate with
    73  	// the API server.
    74  	kubeClient clientset.Interface
    75  
    76  	// pvcLister is the shared PVC lister used to fetch and store PVC
    77  	// objects from the API server. It is shared with other controllers and
    78  	// therefore the PVC objects in its store should be treated as immutable.
    79  	pvcLister  corelisters.PersistentVolumeClaimLister
    80  	pvcsSynced cache.InformerSynced
    81  
    82  	// volumePluginMgr used to initialize and fetch volume plugins
    83  	volumePluginMgr volume.VolumePluginMgr
    84  
    85  	// recorder is used to record events in the API server
    86  	recorder record.EventRecorder
    87  
    88  	operationGenerator operationexecutor.OperationGenerator
    89  
    90  	queue workqueue.TypedRateLimitingInterface[string]
    91  
    92  	translator CSINameTranslator
    93  
    94  	csiMigratedPluginManager csimigration.PluginManager
    95  }
    96  
    97  // NewExpandController expands the pvs
    98  func NewExpandController(
    99  	ctx context.Context,
   100  	kubeClient clientset.Interface,
   101  	pvcInformer coreinformers.PersistentVolumeClaimInformer,
   102  	plugins []volume.VolumePlugin,
   103  	translator CSINameTranslator,
   104  	csiMigratedPluginManager csimigration.PluginManager) (ExpandController, error) {
   105  
   106  	expc := &expandController{
   107  		kubeClient: kubeClient,
   108  		pvcLister:  pvcInformer.Lister(),
   109  		pvcsSynced: pvcInformer.Informer().HasSynced,
   110  		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
   111  			workqueue.DefaultTypedControllerRateLimiter[string](),
   112  			workqueue.TypedRateLimitingQueueConfig[string]{Name: "volume_expand"},
   113  		),
   114  		translator:               translator,
   115  		csiMigratedPluginManager: csiMigratedPluginManager,
   116  	}
   117  
   118  	if err := expc.volumePluginMgr.InitPlugins(plugins, nil, expc); err != nil {
   119  		return nil, fmt.Errorf("could not initialize volume plugins for Expand Controller : %+v", err)
   120  	}
   121  
   122  	eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx))
   123  	eventBroadcaster.StartStructuredLogging(3)
   124  	eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")})
   125  	expc.recorder = eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "volume_expand"})
   126  	blkutil := volumepathhandler.NewBlockVolumePathHandler()
   127  
   128  	expc.operationGenerator = operationexecutor.NewOperationGenerator(
   129  		kubeClient,
   130  		&expc.volumePluginMgr,
   131  		expc.recorder,
   132  		blkutil)
   133  
   134  	pvcInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
   135  		AddFunc: expc.enqueuePVC,
   136  		UpdateFunc: func(old, new interface{}) {
   137  			oldPVC, ok := old.(*v1.PersistentVolumeClaim)
   138  			if !ok {
   139  				return
   140  			}
   141  
   142  			oldReq := oldPVC.Spec.Resources.Requests[v1.ResourceStorage]
   143  			oldCap := oldPVC.Status.Capacity[v1.ResourceStorage]
   144  			newPVC, ok := new.(*v1.PersistentVolumeClaim)
   145  			if !ok {
   146  				return
   147  			}
   148  			newReq := newPVC.Spec.Resources.Requests[v1.ResourceStorage]
   149  			newCap := newPVC.Status.Capacity[v1.ResourceStorage]
   150  			// PVC will be enqueued under 2 circumstances
   151  			// 1. User has increased PVC's request capacity --> volume needs to be expanded
   152  			// 2. PVC status capacity has been expanded --> claim's bound PV has likely recently gone through filesystem resize, so remove AnnPreResizeCapacity annotation from PV
   153  			if newReq.Cmp(oldReq) > 0 || newCap.Cmp(oldCap) > 0 {
   154  				expc.enqueuePVC(new)
   155  			}
   156  		},
   157  		DeleteFunc: expc.enqueuePVC,
   158  	})
   159  
   160  	return expc, nil
   161  }
   162  
   163  func (expc *expandController) enqueuePVC(obj interface{}) {
   164  	pvc, ok := obj.(*v1.PersistentVolumeClaim)
   165  	if !ok {
   166  		return
   167  	}
   168  
   169  	if pvc.Status.Phase == v1.ClaimBound {
   170  		key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(pvc)
   171  		if err != nil {
   172  			runtime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", pvc, err))
   173  			return
   174  		}
   175  		expc.queue.Add(key)
   176  	}
   177  }
   178  
   179  func (expc *expandController) processNextWorkItem(ctx context.Context) bool {
   180  	key, shutdown := expc.queue.Get()
   181  	if shutdown {
   182  		return false
   183  	}
   184  	defer expc.queue.Done(key)
   185  
   186  	err := expc.syncHandler(ctx, key)
   187  	if err == nil {
   188  		expc.queue.Forget(key)
   189  		return true
   190  	}
   191  
   192  	runtime.HandleError(fmt.Errorf("%v failed with : %v", key, err))
   193  	expc.queue.AddRateLimited(key)
   194  
   195  	return true
   196  }
   197  
   198  // syncHandler performs actual expansion of volume. If an error is returned
   199  // from this function - PVC will be requeued for resizing.
   200  func (expc *expandController) syncHandler(ctx context.Context, key string) error {
   201  	namespace, name, err := cache.SplitMetaNamespaceKey(key)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	pvc, err := expc.pvcLister.PersistentVolumeClaims(namespace).Get(name)
   206  	if errors.IsNotFound(err) {
   207  		return nil
   208  	}
   209  	logger := klog.FromContext(ctx)
   210  	if err != nil {
   211  		logger.V(5).Info("Error getting PVC from informer", "pvcKey", key, "err", err)
   212  		return err
   213  	}
   214  
   215  	pv, err := expc.getPersistentVolume(ctx, pvc)
   216  	if err != nil {
   217  		logger.V(5).Info("Error getting Persistent Volume for PVC from informer", "pvcKey", key, "pvcUID", pvc.UID, "err", err)
   218  		return err
   219  	}
   220  
   221  	if pv.Spec.ClaimRef == nil || pvc.Namespace != pv.Spec.ClaimRef.Namespace || pvc.UID != pv.Spec.ClaimRef.UID {
   222  		err := fmt.Errorf("persistent Volume is not bound to PVC being updated : %s", key)
   223  		logger.V(4).Info("", "err", err)
   224  		return err
   225  	}
   226  
   227  	pvcRequestSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
   228  	pvcStatusSize := pvc.Status.Capacity[v1.ResourceStorage]
   229  
   230  	// call expand operation only under two condition
   231  	// 1. pvc's request size has been expanded and is larger than pvc's current status size
   232  	// 2. pv has an pre-resize capacity annotation
   233  	if pvcRequestSize.Cmp(pvcStatusSize) <= 0 && !metav1.HasAnnotation(pv.ObjectMeta, util.AnnPreResizeCapacity) {
   234  		return nil
   235  	}
   236  
   237  	volumeSpec := volume.NewSpecFromPersistentVolume(pv, false)
   238  	migratable, err := expc.csiMigratedPluginManager.IsMigratable(volumeSpec)
   239  	if err != nil {
   240  		logger.V(4).Info("Failed to check CSI migration status for PVC with error", "pvcKey", key, "err", err)
   241  		return nil
   242  	}
   243  	// handle CSI migration scenarios before invoking FindExpandablePluginBySpec for in-tree
   244  	if migratable {
   245  		inTreePluginName, err := expc.csiMigratedPluginManager.GetInTreePluginNameFromSpec(volumeSpec.PersistentVolume, volumeSpec.Volume)
   246  		if err != nil {
   247  			logger.V(4).Info("Error getting in-tree plugin name from persistent volume", "volumeName", volumeSpec.PersistentVolume.Name, "err", err)
   248  			return err
   249  		}
   250  
   251  		msg := fmt.Sprintf("CSI migration enabled for %s; waiting for external resizer to expand the pvc", inTreePluginName)
   252  		expc.recorder.Event(pvc, v1.EventTypeNormal, events.ExternalExpanding, msg)
   253  		csiResizerName, err := expc.translator.GetCSINameFromInTreeName(inTreePluginName)
   254  		if err != nil {
   255  			errorMsg := fmt.Sprintf("error getting CSI driver name for pvc %s, with error %v", key, err)
   256  			expc.recorder.Event(pvc, v1.EventTypeWarning, events.ExternalExpanding, errorMsg)
   257  			return fmt.Errorf(errorMsg)
   258  		}
   259  
   260  		pvc, err := util.SetClaimResizer(pvc, csiResizerName, expc.kubeClient)
   261  		if err != nil {
   262  			errorMsg := fmt.Sprintf("error setting resizer annotation to pvc %s, with error %v", key, err)
   263  			expc.recorder.Event(pvc, v1.EventTypeWarning, events.ExternalExpanding, errorMsg)
   264  			return fmt.Errorf(errorMsg)
   265  		}
   266  		return nil
   267  	}
   268  
   269  	volumePlugin, err := expc.volumePluginMgr.FindExpandablePluginBySpec(volumeSpec)
   270  	if err != nil || volumePlugin == nil {
   271  		msg := "waiting for an external controller to expand this PVC"
   272  		eventType := v1.EventTypeNormal
   273  		if err != nil {
   274  			eventType = v1.EventTypeWarning
   275  		}
   276  		expc.recorder.Event(pvc, eventType, events.ExternalExpanding, msg)
   277  		logger.Info("Waiting for an external controller to expand the PVC", "pvcKey", key, "pvcUID", pvc.UID)
   278  		// If we are expecting that an external plugin will handle resizing this volume then
   279  		// is no point in requeuing this PVC.
   280  		return nil
   281  	}
   282  
   283  	volumeResizerName := volumePlugin.GetPluginName()
   284  	return expc.expand(logger, pvc, pv, volumeResizerName)
   285  }
   286  
   287  func (expc *expandController) expand(logger klog.Logger, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume, resizerName string) error {
   288  	// if node expand is complete and pv's annotation can be removed, remove the annotation from pv and return
   289  	if expc.isNodeExpandComplete(logger, pvc, pv) && metav1.HasAnnotation(pv.ObjectMeta, util.AnnPreResizeCapacity) {
   290  		return util.DeleteAnnPreResizeCapacity(pv, expc.GetKubeClient())
   291  	}
   292  
   293  	var generatedOptions volumetypes.GeneratedOperations
   294  	var err error
   295  	if utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure) {
   296  		generatedOptions, err = expc.operationGenerator.GenerateExpandAndRecoverVolumeFunc(pvc, pv, resizerName)
   297  		if err != nil {
   298  			logger.Error(err, "Error starting ExpandVolume for pvc", "PVC", klog.KObj(pvc))
   299  			return err
   300  		}
   301  	} else {
   302  		pvc, err := util.MarkResizeInProgressWithResizer(pvc, resizerName, expc.kubeClient)
   303  		if err != nil {
   304  			logger.Error(err, "Error setting PVC in progress with error", "PVC", klog.KObj(pvc), "err", err)
   305  			return err
   306  		}
   307  
   308  		generatedOptions, err = expc.operationGenerator.GenerateExpandVolumeFunc(pvc, pv)
   309  		if err != nil {
   310  			logger.Error(err, "Error starting ExpandVolume for pvc with error", "PVC", klog.KObj(pvc), "err", err)
   311  			return err
   312  		}
   313  	}
   314  
   315  	logger.V(5).Info("Starting ExpandVolume for volume", "volumeName", util.GetPersistentVolumeClaimQualifiedName(pvc))
   316  	_, detailedErr := generatedOptions.Run()
   317  
   318  	return detailedErr
   319  }
   320  
   321  // TODO make concurrency configurable (workers argument). previously, nestedpendingoperations spawned unlimited goroutines
   322  func (expc *expandController) Run(ctx context.Context) {
   323  	defer runtime.HandleCrash()
   324  	defer expc.queue.ShutDown()
   325  	logger := klog.FromContext(ctx)
   326  	logger.Info("Starting expand controller")
   327  	defer logger.Info("Shutting down expand controller")
   328  
   329  	if !cache.WaitForNamedCacheSync("expand", ctx.Done(), expc.pvcsSynced) {
   330  		return
   331  	}
   332  
   333  	for i := 0; i < defaultWorkerCount; i++ {
   334  		go wait.UntilWithContext(ctx, expc.runWorker, time.Second)
   335  	}
   336  
   337  	<-ctx.Done()
   338  }
   339  
   340  func (expc *expandController) runWorker(ctx context.Context) {
   341  	for expc.processNextWorkItem(ctx) {
   342  	}
   343  }
   344  
   345  func (expc *expandController) getPersistentVolume(ctx context.Context, pvc *v1.PersistentVolumeClaim) (*v1.PersistentVolume, error) {
   346  	volumeName := pvc.Spec.VolumeName
   347  	pv, err := expc.kubeClient.CoreV1().PersistentVolumes().Get(ctx, volumeName, metav1.GetOptions{})
   348  
   349  	if err != nil {
   350  		return nil, fmt.Errorf("failed to get PV %q: %v", volumeName, err)
   351  	}
   352  
   353  	return pv, nil
   354  }
   355  
   356  // isNodeExpandComplete returns true if  pvc.Status.Capacity >= pv.Spec.Capacity
   357  func (expc *expandController) isNodeExpandComplete(logger klog.Logger, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) bool {
   358  	logger.V(4).Info("pv and pvc capacity", "PV", klog.KObj(pv), "pvCapacity", pv.Spec.Capacity[v1.ResourceStorage], "PVC", klog.KObj(pvc), "pvcCapacity", pvc.Status.Capacity[v1.ResourceStorage])
   359  	pvcSpecCap := pvc.Spec.Resources.Requests.Storage()
   360  	pvcStatusCap, pvCap := pvc.Status.Capacity[v1.ResourceStorage], pv.Spec.Capacity[v1.ResourceStorage]
   361  
   362  	// since we allow shrinking volumes, we must compare both pvc status and capacity
   363  	// with pv spec capacity.
   364  	if pvcStatusCap.Cmp(*pvcSpecCap) >= 0 && pvcStatusCap.Cmp(pvCap) >= 0 {
   365  		return true
   366  	}
   367  	return false
   368  }
   369  
   370  // Implementing VolumeHost interface
   371  func (expc *expandController) GetPluginDir(pluginName string) string {
   372  	return ""
   373  }
   374  
   375  func (expc *expandController) GetVolumeDevicePluginDir(pluginName string) string {
   376  	return ""
   377  }
   378  
   379  func (expc *expandController) GetPodsDir() string {
   380  	return ""
   381  }
   382  
   383  func (expc *expandController) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string {
   384  	return ""
   385  }
   386  
   387  func (expc *expandController) GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string {
   388  	return ""
   389  }
   390  
   391  func (expc *expandController) GetPodPluginDir(podUID types.UID, pluginName string) string {
   392  	return ""
   393  }
   394  
   395  func (expc *expandController) GetKubeClient() clientset.Interface {
   396  	return expc.kubeClient
   397  }
   398  
   399  func (expc *expandController) NewWrapperMounter(volName string, spec volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
   400  	return nil, fmt.Errorf("NewWrapperMounter not supported by expand controller's VolumeHost implementation")
   401  }
   402  
   403  func (expc *expandController) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) {
   404  	return nil, fmt.Errorf("NewWrapperUnmounter not supported by expand controller's VolumeHost implementation")
   405  }
   406  
   407  func (expc *expandController) GetMounter(pluginName string) mount.Interface {
   408  	return nil
   409  }
   410  
   411  func (expc *expandController) GetExec(pluginName string) utilexec.Interface {
   412  	return utilexec.New()
   413  }
   414  
   415  func (expc *expandController) GetHostName() string {
   416  	return ""
   417  }
   418  
   419  func (expc *expandController) GetHostIP() (net.IP, error) {
   420  	return nil, fmt.Errorf("GetHostIP not supported by expand controller's VolumeHost implementation")
   421  }
   422  
   423  func (expc *expandController) GetNodeAllocatable() (v1.ResourceList, error) {
   424  	return v1.ResourceList{}, nil
   425  }
   426  
   427  func (expc *expandController) GetSecretFunc() func(namespace, name string) (*v1.Secret, error) {
   428  	return func(_, _ string) (*v1.Secret, error) {
   429  		return nil, fmt.Errorf("GetSecret unsupported in expandController")
   430  	}
   431  }
   432  
   433  func (expc *expandController) GetConfigMapFunc() func(namespace, name string) (*v1.ConfigMap, error) {
   434  	return func(_, _ string) (*v1.ConfigMap, error) {
   435  		return nil, fmt.Errorf("GetConfigMap unsupported in expandController")
   436  	}
   437  }
   438  
   439  func (expc *expandController) GetAttachedVolumesFromNodeStatus() (map[v1.UniqueVolumeName]string, error) {
   440  	return map[v1.UniqueVolumeName]string{}, nil
   441  }
   442  
   443  func (expc *expandController) GetServiceAccountTokenFunc() func(_, _ string, _ *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
   444  	return func(_, _ string, _ *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
   445  		return nil, fmt.Errorf("GetServiceAccountToken unsupported in expandController")
   446  	}
   447  }
   448  
   449  func (expc *expandController) DeleteServiceAccountTokenFunc() func(types.UID) {
   450  	return func(types.UID) {
   451  		//nolint:logcheck
   452  		klog.ErrorS(nil, "DeleteServiceAccountToken unsupported in expandController")
   453  	}
   454  }
   455  
   456  func (expc *expandController) GetNodeLabels() (map[string]string, error) {
   457  	return nil, fmt.Errorf("GetNodeLabels unsupported in expandController")
   458  }
   459  
   460  func (expc *expandController) GetNodeName() types.NodeName {
   461  	return ""
   462  }
   463  
   464  func (expc *expandController) GetEventRecorder() record.EventRecorder {
   465  	return expc.recorder
   466  }
   467  
   468  func (expc *expandController) GetSubpather() subpath.Interface {
   469  	// not needed for expand controller
   470  	return nil
   471  }