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 }