k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/volume/attachdetach/attach_detach_controller.go (about) 1 /* 2 Copyright 2016 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 attachdetach implements a controller to manage volume attach and detach 18 // operations. 19 package attachdetach 20 21 import ( 22 "context" 23 "fmt" 24 "net" 25 "time" 26 27 "k8s.io/klog/v2" 28 "k8s.io/mount-utils" 29 utilexec "k8s.io/utils/exec" 30 31 authenticationv1 "k8s.io/api/authentication/v1" 32 v1 "k8s.io/api/core/v1" 33 apierrors "k8s.io/apimachinery/pkg/api/errors" 34 "k8s.io/apimachinery/pkg/labels" 35 "k8s.io/apimachinery/pkg/types" 36 "k8s.io/apimachinery/pkg/util/runtime" 37 "k8s.io/apimachinery/pkg/util/wait" 38 utilfeature "k8s.io/apiserver/pkg/util/feature" 39 coreinformers "k8s.io/client-go/informers/core/v1" 40 storageinformersv1 "k8s.io/client-go/informers/storage/v1" 41 clientset "k8s.io/client-go/kubernetes" 42 "k8s.io/client-go/kubernetes/scheme" 43 v1core "k8s.io/client-go/kubernetes/typed/core/v1" 44 corelisters "k8s.io/client-go/listers/core/v1" 45 storagelistersv1 "k8s.io/client-go/listers/storage/v1" 46 kcache "k8s.io/client-go/tools/cache" 47 "k8s.io/client-go/tools/record" 48 "k8s.io/client-go/util/workqueue" 49 csitrans "k8s.io/csi-translation-lib" 50 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache" 51 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/metrics" 52 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/populator" 53 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler" 54 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/statusupdater" 55 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/util" 56 "k8s.io/kubernetes/pkg/controller/volume/common" 57 "k8s.io/kubernetes/pkg/volume" 58 "k8s.io/kubernetes/pkg/volume/csi" 59 "k8s.io/kubernetes/pkg/volume/csimigration" 60 volumeutil "k8s.io/kubernetes/pkg/volume/util" 61 "k8s.io/kubernetes/pkg/volume/util/operationexecutor" 62 "k8s.io/kubernetes/pkg/volume/util/subpath" 63 "k8s.io/kubernetes/pkg/volume/util/volumepathhandler" 64 ) 65 66 // TimerConfig contains configuration of internal attach/detach timers and 67 // should be used only to speed up tests. DefaultTimerConfig is the suggested 68 // timer configuration for production. 69 type TimerConfig struct { 70 // ReconcilerLoopPeriod is the amount of time the reconciler loop waits 71 // between successive executions 72 ReconcilerLoopPeriod time.Duration 73 74 // ReconcilerMaxWaitForUnmountDuration is the maximum amount of time the 75 // attach detach controller will wait for a volume to be safely unmounted 76 // from its node. Once this time has expired, the controller will assume the 77 // node or kubelet are unresponsive and will detach the volume anyway. 78 ReconcilerMaxWaitForUnmountDuration time.Duration 79 80 // DesiredStateOfWorldPopulatorLoopSleepPeriod is the amount of time the 81 // DesiredStateOfWorldPopulator loop waits between successive executions 82 DesiredStateOfWorldPopulatorLoopSleepPeriod time.Duration 83 84 // DesiredStateOfWorldPopulatorListPodsRetryDuration is the amount of 85 // time the DesiredStateOfWorldPopulator loop waits between list pods 86 // calls. 87 DesiredStateOfWorldPopulatorListPodsRetryDuration time.Duration 88 } 89 90 // DefaultTimerConfig is the default configuration of Attach/Detach controller 91 // timers. 92 var DefaultTimerConfig = TimerConfig{ 93 ReconcilerLoopPeriod: 100 * time.Millisecond, 94 ReconcilerMaxWaitForUnmountDuration: 6 * time.Minute, 95 DesiredStateOfWorldPopulatorLoopSleepPeriod: 1 * time.Minute, 96 DesiredStateOfWorldPopulatorListPodsRetryDuration: 3 * time.Minute, 97 } 98 99 // AttachDetachController defines the operations supported by this controller. 100 type AttachDetachController interface { 101 Run(ctx context.Context) 102 GetDesiredStateOfWorld() cache.DesiredStateOfWorld 103 } 104 105 // NewAttachDetachController returns a new instance of AttachDetachController. 106 func NewAttachDetachController( 107 ctx context.Context, 108 kubeClient clientset.Interface, 109 podInformer coreinformers.PodInformer, 110 nodeInformer coreinformers.NodeInformer, 111 pvcInformer coreinformers.PersistentVolumeClaimInformer, 112 pvInformer coreinformers.PersistentVolumeInformer, 113 csiNodeInformer storageinformersv1.CSINodeInformer, 114 csiDriverInformer storageinformersv1.CSIDriverInformer, 115 volumeAttachmentInformer storageinformersv1.VolumeAttachmentInformer, 116 plugins []volume.VolumePlugin, 117 prober volume.DynamicPluginProber, 118 disableReconciliationSync bool, 119 reconcilerSyncDuration time.Duration, 120 disableForceDetachOnTimeout bool, 121 timerConfig TimerConfig) (AttachDetachController, error) { 122 123 logger := klog.FromContext(ctx) 124 125 adc := &attachDetachController{ 126 kubeClient: kubeClient, 127 pvcLister: pvcInformer.Lister(), 128 pvcsSynced: pvcInformer.Informer().HasSynced, 129 pvLister: pvInformer.Lister(), 130 pvsSynced: pvInformer.Informer().HasSynced, 131 podLister: podInformer.Lister(), 132 podsSynced: podInformer.Informer().HasSynced, 133 podIndexer: podInformer.Informer().GetIndexer(), 134 nodeLister: nodeInformer.Lister(), 135 nodesSynced: nodeInformer.Informer().HasSynced, 136 pvcQueue: workqueue.NewTypedRateLimitingQueueWithConfig( 137 workqueue.DefaultTypedControllerRateLimiter[string](), 138 workqueue.TypedRateLimitingQueueConfig[string]{Name: "pvcs"}, 139 ), 140 } 141 142 adc.csiNodeLister = csiNodeInformer.Lister() 143 adc.csiNodeSynced = csiNodeInformer.Informer().HasSynced 144 145 adc.csiDriverLister = csiDriverInformer.Lister() 146 adc.csiDriversSynced = csiDriverInformer.Informer().HasSynced 147 148 adc.volumeAttachmentLister = volumeAttachmentInformer.Lister() 149 adc.volumeAttachmentSynced = volumeAttachmentInformer.Informer().HasSynced 150 151 if err := adc.volumePluginMgr.InitPlugins(plugins, prober, adc); err != nil { 152 return nil, fmt.Errorf("could not initialize volume plugins for Attach/Detach Controller: %w", err) 153 } 154 155 adc.broadcaster = record.NewBroadcaster(record.WithContext(ctx)) 156 recorder := adc.broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "attachdetach-controller"}) 157 blkutil := volumepathhandler.NewBlockVolumePathHandler() 158 159 adc.desiredStateOfWorld = cache.NewDesiredStateOfWorld(&adc.volumePluginMgr) 160 adc.actualStateOfWorld = cache.NewActualStateOfWorld(&adc.volumePluginMgr) 161 adc.attacherDetacher = 162 operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( 163 kubeClient, 164 &adc.volumePluginMgr, 165 recorder, 166 blkutil)) 167 adc.nodeStatusUpdater = statusupdater.NewNodeStatusUpdater( 168 kubeClient, nodeInformer.Lister(), adc.actualStateOfWorld) 169 170 // Default these to values in options 171 adc.reconciler = reconciler.NewReconciler( 172 timerConfig.ReconcilerLoopPeriod, 173 timerConfig.ReconcilerMaxWaitForUnmountDuration, 174 reconcilerSyncDuration, 175 disableReconciliationSync, 176 disableForceDetachOnTimeout, 177 adc.desiredStateOfWorld, 178 adc.actualStateOfWorld, 179 adc.attacherDetacher, 180 adc.nodeStatusUpdater, 181 adc.nodeLister, 182 recorder) 183 184 csiTranslator := csitrans.New() 185 adc.intreeToCSITranslator = csiTranslator 186 adc.csiMigratedPluginManager = csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate) 187 188 adc.desiredStateOfWorldPopulator = populator.NewDesiredStateOfWorldPopulator( 189 timerConfig.DesiredStateOfWorldPopulatorLoopSleepPeriod, 190 timerConfig.DesiredStateOfWorldPopulatorListPodsRetryDuration, 191 podInformer.Lister(), 192 adc.desiredStateOfWorld, 193 &adc.volumePluginMgr, 194 pvcInformer.Lister(), 195 pvInformer.Lister(), 196 adc.csiMigratedPluginManager, 197 adc.intreeToCSITranslator) 198 199 podInformer.Informer().AddEventHandler(kcache.ResourceEventHandlerFuncs{ 200 AddFunc: func(obj interface{}) { 201 adc.podAdd(logger, obj) 202 }, 203 UpdateFunc: func(oldObj, newObj interface{}) { 204 adc.podUpdate(logger, oldObj, newObj) 205 }, 206 DeleteFunc: func(obj interface{}) { 207 adc.podDelete(logger, obj) 208 }, 209 }) 210 211 // This custom indexer will index pods by its PVC keys. Then we don't need 212 // to iterate all pods every time to find pods which reference given PVC. 213 if err := common.AddPodPVCIndexerIfNotPresent(adc.podIndexer); err != nil { 214 return nil, fmt.Errorf("could not initialize attach detach controller: %w", err) 215 } 216 217 nodeInformer.Informer().AddEventHandler(kcache.ResourceEventHandlerFuncs{ 218 AddFunc: func(obj interface{}) { 219 adc.nodeAdd(logger, obj) 220 }, 221 UpdateFunc: func(oldObj, newObj interface{}) { 222 adc.nodeUpdate(logger, oldObj, newObj) 223 }, 224 DeleteFunc: func(obj interface{}) { 225 adc.nodeDelete(logger, obj) 226 }, 227 }) 228 229 pvcInformer.Informer().AddEventHandler(kcache.ResourceEventHandlerFuncs{ 230 AddFunc: func(obj interface{}) { 231 adc.enqueuePVC(obj) 232 }, 233 UpdateFunc: func(old, new interface{}) { 234 adc.enqueuePVC(new) 235 }, 236 }) 237 238 return adc, nil 239 } 240 241 type attachDetachController struct { 242 // kubeClient is the kube API client used by volumehost to communicate with 243 // the API server. 244 kubeClient clientset.Interface 245 246 // pvcLister is the shared PVC lister used to fetch and store PVC 247 // objects from the API server. It is shared with other controllers and 248 // therefore the PVC objects in its store should be treated as immutable. 249 pvcLister corelisters.PersistentVolumeClaimLister 250 pvcsSynced kcache.InformerSynced 251 252 // pvLister is the shared PV lister used to fetch and store PV objects 253 // from the API server. It is shared with other controllers and therefore 254 // the PV objects in its store should be treated as immutable. 255 pvLister corelisters.PersistentVolumeLister 256 pvsSynced kcache.InformerSynced 257 258 podLister corelisters.PodLister 259 podsSynced kcache.InformerSynced 260 podIndexer kcache.Indexer 261 262 nodeLister corelisters.NodeLister 263 nodesSynced kcache.InformerSynced 264 265 csiNodeLister storagelistersv1.CSINodeLister 266 csiNodeSynced kcache.InformerSynced 267 268 // csiDriverLister is the shared CSIDriver lister used to fetch and store 269 // CSIDriver objects from the API server. It is shared with other controllers 270 // and therefore the CSIDriver objects in its store should be treated as immutable. 271 csiDriverLister storagelistersv1.CSIDriverLister 272 csiDriversSynced kcache.InformerSynced 273 274 // volumeAttachmentLister is the shared volumeAttachment lister used to fetch and store 275 // VolumeAttachment objects from the API server. It is shared with other controllers 276 // and therefore the VolumeAttachment objects in its store should be treated as immutable. 277 volumeAttachmentLister storagelistersv1.VolumeAttachmentLister 278 volumeAttachmentSynced kcache.InformerSynced 279 280 // volumePluginMgr used to initialize and fetch volume plugins 281 volumePluginMgr volume.VolumePluginMgr 282 283 // desiredStateOfWorld is a data structure containing the desired state of 284 // the world according to this controller: i.e. what nodes the controller 285 // is managing, what volumes it wants be attached to these nodes, and which 286 // pods are scheduled to those nodes referencing the volumes. 287 // The data structure is populated by the controller using a stream of node 288 // and pod API server objects fetched by the informers. 289 desiredStateOfWorld cache.DesiredStateOfWorld 290 291 // actualStateOfWorld is a data structure containing the actual state of 292 // the world according to this controller: i.e. which volumes are attached 293 // to which nodes. 294 // The data structure is populated upon successful completion of attach and 295 // detach actions triggered by the controller and a periodic sync with 296 // storage providers for the "true" state of the world. 297 actualStateOfWorld cache.ActualStateOfWorld 298 299 // attacherDetacher is used to start asynchronous attach and operations 300 attacherDetacher operationexecutor.OperationExecutor 301 302 // reconciler is used to run an asynchronous periodic loop to reconcile the 303 // desiredStateOfWorld with the actualStateOfWorld by triggering attach 304 // detach operations using the attacherDetacher. 305 reconciler reconciler.Reconciler 306 307 // nodeStatusUpdater is used to update node status with the list of attached 308 // volumes 309 nodeStatusUpdater statusupdater.NodeStatusUpdater 310 311 // desiredStateOfWorldPopulator runs an asynchronous periodic loop to 312 // populate the current pods using podInformer. 313 desiredStateOfWorldPopulator populator.DesiredStateOfWorldPopulator 314 315 // broadcaster is broadcasting events 316 broadcaster record.EventBroadcaster 317 318 // pvcQueue is used to queue pvc objects 319 pvcQueue workqueue.TypedRateLimitingInterface[string] 320 321 // csiMigratedPluginManager detects in-tree plugins that have been migrated to CSI 322 csiMigratedPluginManager csimigration.PluginManager 323 324 // intreeToCSITranslator translates from in-tree volume specs to CSI 325 intreeToCSITranslator csimigration.InTreeToCSITranslator 326 } 327 328 func (adc *attachDetachController) Run(ctx context.Context) { 329 defer runtime.HandleCrash() 330 defer adc.pvcQueue.ShutDown() 331 332 // Start events processing pipeline. 333 adc.broadcaster.StartStructuredLogging(3) 334 adc.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: adc.kubeClient.CoreV1().Events("")}) 335 defer adc.broadcaster.Shutdown() 336 337 logger := klog.FromContext(ctx) 338 logger.Info("Starting attach detach controller") 339 defer logger.Info("Shutting down attach detach controller") 340 341 synced := []kcache.InformerSynced{adc.podsSynced, adc.nodesSynced, adc.pvcsSynced, adc.pvsSynced, 342 adc.csiNodeSynced, adc.csiDriversSynced, adc.volumeAttachmentSynced} 343 if !kcache.WaitForNamedCacheSync("attach detach", ctx.Done(), synced...) { 344 return 345 } 346 347 err := adc.populateActualStateOfWorld(logger) 348 if err != nil { 349 logger.Error(err, "Error populating the actual state of world") 350 } 351 err = adc.populateDesiredStateOfWorld(logger) 352 if err != nil { 353 logger.Error(err, "Error populating the desired state of world") 354 } 355 go adc.reconciler.Run(ctx) 356 go adc.desiredStateOfWorldPopulator.Run(ctx) 357 go wait.UntilWithContext(ctx, adc.pvcWorker, time.Second) 358 metrics.Register(adc.pvcLister, 359 adc.pvLister, 360 adc.podLister, 361 adc.actualStateOfWorld, 362 adc.desiredStateOfWorld, 363 &adc.volumePluginMgr, 364 adc.csiMigratedPluginManager, 365 adc.intreeToCSITranslator) 366 367 <-ctx.Done() 368 } 369 370 func (adc *attachDetachController) populateActualStateOfWorld(logger klog.Logger) error { 371 logger.V(5).Info("Populating ActualStateOfworld") 372 nodes, err := adc.nodeLister.List(labels.Everything()) 373 if err != nil { 374 return err 375 } 376 377 for _, node := range nodes { 378 nodeName := types.NodeName(node.Name) 379 380 for _, attachedVolume := range node.Status.VolumesAttached { 381 uniqueName := attachedVolume.Name 382 // The nil VolumeSpec is safe only in the case the volume is not in use by any pod. 383 // In such a case it should be detached in the first reconciliation cycle and the 384 // volume spec is not needed to detach a volume. If the volume is used by a pod, it 385 // its spec can be: this would happen during in the populateDesiredStateOfWorld which 386 // scans the pods and updates their volumes in the ActualStateOfWorld too. 387 err = adc.actualStateOfWorld.MarkVolumeAsAttached(logger, uniqueName, nil /* VolumeSpec */, nodeName, attachedVolume.DevicePath) 388 if err != nil { 389 logger.Error(err, "Failed to mark the volume as attached") 390 continue 391 } 392 } 393 adc.actualStateOfWorld.SetVolumesMountedByNode(logger, node.Status.VolumesInUse, nodeName) 394 adc.addNodeToDswp(node, types.NodeName(node.Name)) 395 } 396 err = adc.processVolumeAttachments(logger) 397 if err != nil { 398 logger.Error(err, "Failed to process volume attachments") 399 } 400 return err 401 } 402 403 func (adc *attachDetachController) getNodeVolumeDevicePath( 404 volumeName v1.UniqueVolumeName, nodeName types.NodeName) (string, error) { 405 var devicePath string 406 var found bool 407 node, err := adc.nodeLister.Get(string(nodeName)) 408 if err != nil { 409 return devicePath, err 410 } 411 for _, attachedVolume := range node.Status.VolumesAttached { 412 if volumeName == attachedVolume.Name { 413 devicePath = attachedVolume.DevicePath 414 found = true 415 break 416 } 417 } 418 if !found { 419 err = fmt.Errorf("Volume %s not found on node %s", volumeName, nodeName) 420 } 421 422 return devicePath, err 423 } 424 425 func (adc *attachDetachController) populateDesiredStateOfWorld(logger klog.Logger) error { 426 logger.V(5).Info("Populating DesiredStateOfworld") 427 428 pods, err := adc.podLister.List(labels.Everything()) 429 if err != nil { 430 return err 431 } 432 for _, pod := range pods { 433 podToAdd := pod 434 adc.podAdd(logger, podToAdd) 435 for _, podVolume := range podToAdd.Spec.Volumes { 436 nodeName := types.NodeName(podToAdd.Spec.NodeName) 437 // The volume specs present in the ActualStateOfWorld are nil, let's replace those 438 // with the correct ones found on pods. The present in the ASW with no corresponding 439 // pod will be detached and the spec is irrelevant. 440 volumeSpec, err := util.CreateVolumeSpec(logger, podVolume, podToAdd, nodeName, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator) 441 if err != nil { 442 logger.Error( 443 err, 444 "Error creating spec for volume of pod", 445 "pod", klog.KObj(podToAdd), 446 "volumeName", podVolume.Name) 447 continue 448 } 449 plugin, err := adc.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec) 450 if err != nil || plugin == nil { 451 logger.V(10).Info( 452 "Skipping volume for pod: it does not implement attacher interface", 453 "pod", klog.KObj(podToAdd), 454 "volumeName", podVolume.Name, 455 "err", err) 456 continue 457 } 458 volumeName, err := volumeutil.GetUniqueVolumeNameFromSpec(plugin, volumeSpec) 459 if err != nil { 460 logger.Error( 461 err, 462 "Failed to find unique name for volume of pod", 463 "pod", klog.KObj(podToAdd), 464 "volumeName", podVolume.Name) 465 continue 466 } 467 attachState := adc.actualStateOfWorld.GetAttachState(volumeName, nodeName) 468 if attachState == cache.AttachStateAttached { 469 logger.V(10).Info("Volume is attached to node. Marking as attached in ActualStateOfWorld", 470 "node", klog.KRef("", string(nodeName)), 471 "volumeName", volumeName) 472 devicePath, err := adc.getNodeVolumeDevicePath(volumeName, nodeName) 473 if err != nil { 474 logger.Error(err, "Failed to find device path") 475 continue 476 } 477 err = adc.actualStateOfWorld.MarkVolumeAsAttached(logger, volumeName, volumeSpec, nodeName, devicePath) 478 if err != nil { 479 logger.Error(err, "Failed to update volume spec for node", "node", klog.KRef("", string(nodeName))) 480 } 481 } 482 } 483 } 484 485 return nil 486 } 487 488 func (adc *attachDetachController) podAdd(logger klog.Logger, obj interface{}) { 489 pod, ok := obj.(*v1.Pod) 490 if pod == nil || !ok { 491 return 492 } 493 if pod.Spec.NodeName == "" { 494 // Ignore pods without NodeName, indicating they are not scheduled. 495 return 496 } 497 498 volumeActionFlag := util.DetermineVolumeAction( 499 pod, 500 adc.desiredStateOfWorld, 501 true /* default volume action */) 502 503 util.ProcessPodVolumes(logger, pod, volumeActionFlag, /* addVolumes */ 504 adc.desiredStateOfWorld, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator) 505 } 506 507 // GetDesiredStateOfWorld returns desired state of world associated with controller 508 func (adc *attachDetachController) GetDesiredStateOfWorld() cache.DesiredStateOfWorld { 509 return adc.desiredStateOfWorld 510 } 511 512 func (adc *attachDetachController) podUpdate(logger klog.Logger, oldObj, newObj interface{}) { 513 pod, ok := newObj.(*v1.Pod) 514 if pod == nil || !ok { 515 return 516 } 517 if pod.Spec.NodeName == "" { 518 // Ignore pods without NodeName, indicating they are not scheduled. 519 return 520 } 521 522 volumeActionFlag := util.DetermineVolumeAction( 523 pod, 524 adc.desiredStateOfWorld, 525 true /* default volume action */) 526 527 util.ProcessPodVolumes(logger, pod, volumeActionFlag, /* addVolumes */ 528 adc.desiredStateOfWorld, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator) 529 } 530 531 func (adc *attachDetachController) podDelete(logger klog.Logger, obj interface{}) { 532 pod, ok := obj.(*v1.Pod) 533 if pod == nil || !ok { 534 return 535 } 536 537 util.ProcessPodVolumes(logger, pod, false, /* addVolumes */ 538 adc.desiredStateOfWorld, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator) 539 } 540 541 func (adc *attachDetachController) nodeAdd(logger klog.Logger, obj interface{}) { 542 node, ok := obj.(*v1.Node) 543 // TODO: investigate if nodeName is empty then if we can return 544 // kubernetes/kubernetes/issues/37777 545 if node == nil || !ok { 546 return 547 } 548 nodeName := types.NodeName(node.Name) 549 adc.nodeUpdate(logger, nil, obj) 550 // kubernetes/kubernetes/issues/37586 551 // This is to workaround the case when a node add causes to wipe out 552 // the attached volumes field. This function ensures that we sync with 553 // the actual status. 554 adc.actualStateOfWorld.SetNodeStatusUpdateNeeded(logger, nodeName) 555 } 556 557 func (adc *attachDetachController) nodeUpdate(logger klog.Logger, oldObj, newObj interface{}) { 558 node, ok := newObj.(*v1.Node) 559 // TODO: investigate if nodeName is empty then if we can return 560 if node == nil || !ok { 561 return 562 } 563 564 nodeName := types.NodeName(node.Name) 565 adc.addNodeToDswp(node, nodeName) 566 adc.processVolumesInUse(logger, nodeName, node.Status.VolumesInUse) 567 } 568 569 func (adc *attachDetachController) nodeDelete(logger klog.Logger, obj interface{}) { 570 node, ok := obj.(*v1.Node) 571 if node == nil || !ok { 572 return 573 } 574 575 nodeName := types.NodeName(node.Name) 576 if err := adc.desiredStateOfWorld.DeleteNode(nodeName); err != nil { 577 // This might happen during drain, but we still want it to appear in our logs 578 logger.Info("Error removing node from desired-state-of-world", "node", klog.KObj(node), "err", err) 579 } 580 581 adc.processVolumesInUse(logger, nodeName, node.Status.VolumesInUse) 582 } 583 584 func (adc *attachDetachController) enqueuePVC(obj interface{}) { 585 key, err := kcache.DeletionHandlingMetaNamespaceKeyFunc(obj) 586 if err != nil { 587 runtime.HandleError(fmt.Errorf("Couldn't get key for object %+v: %v", obj, err)) 588 return 589 } 590 adc.pvcQueue.Add(key) 591 } 592 593 // pvcWorker processes items from pvcQueue 594 func (adc *attachDetachController) pvcWorker(ctx context.Context) { 595 for adc.processNextItem(klog.FromContext(ctx)) { 596 } 597 } 598 599 func (adc *attachDetachController) processNextItem(logger klog.Logger) bool { 600 keyObj, shutdown := adc.pvcQueue.Get() 601 if shutdown { 602 return false 603 } 604 defer adc.pvcQueue.Done(keyObj) 605 606 if err := adc.syncPVCByKey(logger, keyObj); err != nil { 607 // Rather than wait for a full resync, re-add the key to the 608 // queue to be processed. 609 adc.pvcQueue.AddRateLimited(keyObj) 610 runtime.HandleError(fmt.Errorf("failed to sync pvc %q, will retry again: %w", keyObj, err)) 611 return true 612 } 613 614 // Finally, if no error occurs we Forget this item so it does not 615 // get queued again until another change happens. 616 adc.pvcQueue.Forget(keyObj) 617 return true 618 } 619 620 func (adc *attachDetachController) syncPVCByKey(logger klog.Logger, key string) error { 621 logger.V(5).Info("syncPVCByKey", "pvcKey", key) 622 namespace, name, err := kcache.SplitMetaNamespaceKey(key) 623 if err != nil { 624 logger.V(4).Info("Error getting namespace & name of pvc to get pvc from informer", "pvcKey", key, "err", err) 625 return nil 626 } 627 pvc, err := adc.pvcLister.PersistentVolumeClaims(namespace).Get(name) 628 if apierrors.IsNotFound(err) { 629 logger.V(4).Info("Error getting pvc from informer", "pvcKey", key, "err", err) 630 return nil 631 } 632 if err != nil { 633 return err 634 } 635 636 if pvc.Status.Phase != v1.ClaimBound || pvc.Spec.VolumeName == "" { 637 // Skip unbound PVCs. 638 return nil 639 } 640 641 objs, err := adc.podIndexer.ByIndex(common.PodPVCIndex, key) 642 if err != nil { 643 return err 644 } 645 for _, obj := range objs { 646 pod, ok := obj.(*v1.Pod) 647 if !ok { 648 continue 649 } 650 // we are only interested in active pods with nodeName set 651 if len(pod.Spec.NodeName) == 0 || volumeutil.IsPodTerminated(pod, pod.Status) { 652 continue 653 } 654 volumeActionFlag := util.DetermineVolumeAction( 655 pod, 656 adc.desiredStateOfWorld, 657 true /* default volume action */) 658 659 util.ProcessPodVolumes(logger, pod, volumeActionFlag, /* addVolumes */ 660 adc.desiredStateOfWorld, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator) 661 } 662 return nil 663 } 664 665 // processVolumesInUse processes the list of volumes marked as "in-use" 666 // according to the specified Node's Status.VolumesInUse and updates the 667 // corresponding volume in the actual state of the world to indicate that it is 668 // mounted. 669 func (adc *attachDetachController) processVolumesInUse( 670 logger klog.Logger, nodeName types.NodeName, volumesInUse []v1.UniqueVolumeName) { 671 logger.V(4).Info("processVolumesInUse for node", "node", klog.KRef("", string(nodeName))) 672 adc.actualStateOfWorld.SetVolumesMountedByNode(logger, volumesInUse, nodeName) 673 } 674 675 // Process Volume-Attachment objects. 676 // Should be called only after populating attached volumes in the ASW. 677 // For each VA object, this function checks if its present in the ASW. 678 // If not, adds the volume to ASW as an "uncertain" attachment. 679 // In the reconciler, the logic checks if the volume is present in the DSW; 680 // 681 // if yes, the reconciler will attempt attach on the volume; 682 // if not (could be a dangling attachment), the reconciler will detach this volume. 683 func (adc *attachDetachController) processVolumeAttachments(logger klog.Logger) error { 684 vas, err := adc.volumeAttachmentLister.List(labels.Everything()) 685 if err != nil { 686 logger.Error(err, "Failed to list VolumeAttachment objects") 687 return err 688 } 689 for _, va := range vas { 690 nodeName := types.NodeName(va.Spec.NodeName) 691 pvName := va.Spec.Source.PersistentVolumeName 692 if pvName == nil { 693 // Currently VA objects are created for CSI volumes only. nil pvName is unexpected, generate a warning 694 logger.Info("Skipping the va as its pvName is nil", "node", klog.KRef("", string(nodeName)), "vaName", va.Name) 695 continue 696 } 697 pv, err := adc.pvLister.Get(*pvName) 698 if err != nil { 699 logger.Error(err, "Unable to lookup pv object", "PV", klog.KRef("", *pvName)) 700 continue 701 } 702 703 var plugin volume.AttachableVolumePlugin 704 volumeSpec := volume.NewSpecFromPersistentVolume(pv, false) 705 706 // Consult csiMigratedPluginManager first before querying the plugins registered during runtime in volumePluginMgr. 707 // In-tree plugins that provisioned PVs will not be registered anymore after migration to CSI, once the respective 708 // feature gate is enabled. 709 if inTreePluginName, err := adc.csiMigratedPluginManager.GetInTreePluginNameFromSpec(pv, nil); err == nil { 710 if adc.csiMigratedPluginManager.IsMigrationEnabledForPlugin(inTreePluginName) { 711 // PV is migrated and should be handled by the CSI plugin instead of the in-tree one 712 plugin, _ = adc.volumePluginMgr.FindAttachablePluginByName(csi.CSIPluginName) 713 // podNamespace is not needed here for Azurefile as the volumeName generated will be the same with or without podNamespace 714 volumeSpec, err = csimigration.TranslateInTreeSpecToCSI(volumeSpec, "" /* podNamespace */, adc.intreeToCSITranslator) 715 if err != nil { 716 logger.Error(err, "Failed to translate intree volumeSpec to CSI volumeSpec for volume", "node", klog.KRef("", string(nodeName)), "inTreePluginName", inTreePluginName, "vaName", va.Name, "PV", klog.KRef("", *pvName)) 717 continue 718 } 719 } 720 } 721 722 if plugin == nil { 723 plugin, err = adc.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec) 724 if err != nil || plugin == nil { 725 // Currently VA objects are created for CSI volumes only. nil plugin is unexpected, generate a warning 726 logger.Info("Skipping processing the volume on node, no attacher interface found", "node", klog.KRef("", string(nodeName)), "PV", klog.KRef("", *pvName), "err", err) 727 continue 728 } 729 } 730 731 volumeName, err := volumeutil.GetUniqueVolumeNameFromSpec(plugin, volumeSpec) 732 if err != nil { 733 logger.Error(err, "Failed to find unique name for volume", "node", klog.KRef("", string(nodeName)), "vaName", va.Name, "PV", klog.KRef("", *pvName)) 734 continue 735 } 736 attachState := adc.actualStateOfWorld.GetAttachState(volumeName, nodeName) 737 if attachState == cache.AttachStateDetached { 738 logger.V(1).Info("Marking volume attachment as uncertain as volume is not attached", "node", klog.KRef("", string(nodeName)), "volumeName", volumeName, "attachState", attachState) 739 err = adc.actualStateOfWorld.MarkVolumeAsUncertain(logger, volumeName, volumeSpec, nodeName) 740 if err != nil { 741 logger.Error(err, "MarkVolumeAsUncertain fail to add the volume to ASW", "node", klog.KRef("", string(nodeName)), "volumeName", volumeName) 742 } 743 } 744 } 745 return nil 746 } 747 748 var _ volume.VolumeHost = &attachDetachController{} 749 var _ volume.AttachDetachVolumeHost = &attachDetachController{} 750 751 func (adc *attachDetachController) CSINodeLister() storagelistersv1.CSINodeLister { 752 return adc.csiNodeLister 753 } 754 755 func (adc *attachDetachController) CSIDriverLister() storagelistersv1.CSIDriverLister { 756 return adc.csiDriverLister 757 } 758 759 func (adc *attachDetachController) IsAttachDetachController() bool { 760 return true 761 } 762 763 func (adc *attachDetachController) VolumeAttachmentLister() storagelistersv1.VolumeAttachmentLister { 764 return adc.volumeAttachmentLister 765 } 766 767 // VolumeHost implementation 768 // This is an unfortunate requirement of the current factoring of volume plugin 769 // initializing code. It requires kubelet specific methods used by the mounting 770 // code to be implemented by all initializers even if the initializer does not 771 // do mounting (like this attach/detach controller). 772 // Issue kubernetes/kubernetes/issues/14217 to fix this. 773 func (adc *attachDetachController) GetPluginDir(podUID string) string { 774 return "" 775 } 776 777 func (adc *attachDetachController) GetVolumeDevicePluginDir(podUID string) string { 778 return "" 779 } 780 781 func (adc *attachDetachController) GetPodsDir() string { 782 return "" 783 } 784 785 func (adc *attachDetachController) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string { 786 return "" 787 } 788 789 func (adc *attachDetachController) GetPodPluginDir(podUID types.UID, pluginName string) string { 790 return "" 791 } 792 793 func (adc *attachDetachController) GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string { 794 return "" 795 } 796 797 func (adc *attachDetachController) GetKubeClient() clientset.Interface { 798 return adc.kubeClient 799 } 800 801 func (adc *attachDetachController) NewWrapperMounter(volName string, spec volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { 802 return nil, fmt.Errorf("NewWrapperMounter not supported by Attach/Detach controller's VolumeHost implementation") 803 } 804 805 func (adc *attachDetachController) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) { 806 return nil, fmt.Errorf("NewWrapperUnmounter not supported by Attach/Detach controller's VolumeHost implementation") 807 } 808 809 func (adc *attachDetachController) GetMounter(pluginName string) mount.Interface { 810 return nil 811 } 812 813 func (adc *attachDetachController) GetHostName() string { 814 return "" 815 } 816 817 func (adc *attachDetachController) GetHostIP() (net.IP, error) { 818 return nil, fmt.Errorf("GetHostIP() not supported by Attach/Detach controller's VolumeHost implementation") 819 } 820 821 func (adc *attachDetachController) GetNodeAllocatable() (v1.ResourceList, error) { 822 return v1.ResourceList{}, nil 823 } 824 825 func (adc *attachDetachController) GetAttachedVolumesFromNodeStatus() (map[v1.UniqueVolumeName]string, error) { 826 return map[v1.UniqueVolumeName]string{}, nil 827 } 828 829 func (adc *attachDetachController) GetSecretFunc() func(namespace, name string) (*v1.Secret, error) { 830 return func(_, _ string) (*v1.Secret, error) { 831 return nil, fmt.Errorf("GetSecret unsupported in attachDetachController") 832 } 833 } 834 835 func (adc *attachDetachController) GetConfigMapFunc() func(namespace, name string) (*v1.ConfigMap, error) { 836 return func(_, _ string) (*v1.ConfigMap, error) { 837 return nil, fmt.Errorf("GetConfigMap unsupported in attachDetachController") 838 } 839 } 840 841 func (adc *attachDetachController) GetServiceAccountTokenFunc() func(_, _ string, _ *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) { 842 return func(_, _ string, _ *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) { 843 return nil, fmt.Errorf("GetServiceAccountToken unsupported in attachDetachController") 844 } 845 } 846 847 func (adc *attachDetachController) DeleteServiceAccountTokenFunc() func(types.UID) { 848 return func(types.UID) { 849 // nolint:logcheck 850 klog.ErrorS(nil, "DeleteServiceAccountToken unsupported in attachDetachController") 851 } 852 } 853 854 func (adc *attachDetachController) GetExec(pluginName string) utilexec.Interface { 855 return utilexec.New() 856 } 857 858 func (adc *attachDetachController) addNodeToDswp(node *v1.Node, nodeName types.NodeName) { 859 if _, exists := node.Annotations[volumeutil.ControllerManagedAttachAnnotation]; exists { 860 // Node specifies annotation indicating it should be managed by attach 861 // detach controller. Add it to desired state of world. 862 adc.desiredStateOfWorld.AddNode(nodeName) 863 } 864 } 865 866 func (adc *attachDetachController) GetNodeLabels() (map[string]string, error) { 867 return nil, fmt.Errorf("GetNodeLabels() unsupported in Attach/Detach controller") 868 } 869 870 func (adc *attachDetachController) GetNodeName() types.NodeName { 871 return "" 872 } 873 874 func (adc *attachDetachController) GetEventRecorder() record.EventRecorder { 875 return nil 876 } 877 878 func (adc *attachDetachController) GetSubpather() subpath.Interface { 879 // Subpaths not needed in attachdetach controller 880 return nil 881 } 882 883 func (adc *attachDetachController) GetCSIDriverLister() storagelistersv1.CSIDriverLister { 884 return adc.csiDriverLister 885 }