@@ -30,14 +30,18 @@ import (
30
30
"google.golang.org/grpc/status"
31
31
"k8s.io/apimachinery/pkg/util/sets"
32
32
"k8s.io/apimachinery/pkg/util/uuid"
33
- "k8s.io/apimachinery/pkg/util/wait"
34
- "k8s.io/client-go/util/workqueue"
33
+ "k8s.io/client-go/util/flowcontrol"
35
34
"k8s.io/klog"
36
35
37
36
"sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common"
38
37
gce "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/compute"
39
38
)
40
39
40
+ const (
41
+ nodeBackoffInitialDuration = 200 * time .Millisecond
42
+ nodeBackoffMaxDuration = 2 * time .Minute
43
+ )
44
+
41
45
type GCEControllerServer struct {
42
46
Driver * GCEDriver
43
47
CloudProvider gce.GCECompute
@@ -50,17 +54,9 @@ type GCEControllerServer struct {
50
54
// Aborted error
51
55
volumeLocks * common.VolumeLocks
52
56
53
- // queue is a rate limited work queue for Controller Publish/Unpublish
54
- // Volume calls
55
- queue workqueue.RateLimitingInterface
56
-
57
- // publishErrorsSeenOnNode is a list of nodes with attach/detach
58
- // operation failures so those nodes shall be rate limited for all
59
- // the attach/detach operations until there is an attach / detach
60
- // operation succeeds
61
- publishErrorsSeenOnNode map [string ]bool
62
-
63
- opsManager * OpsManager
57
+ // nodeBackoff keeps track of any active backoff condition on a given node, and the time when retry of controller publish/unpublish is permissible.
58
+ nodeBackoff * flowcontrol.Backoff
59
+ opsManager * OpsManager
64
60
}
65
61
66
62
type workItem struct {
@@ -337,75 +333,31 @@ func (gceCS *GCEControllerServer) DeleteVolume(ctx context.Context, req *csi.Del
337
333
// Run starts the GCEControllerServer.
338
334
func (gceCS * GCEControllerServer ) Run () {
339
335
go gceCS .opsManager .HydrateOpsCache ()
340
- go wait .Until (gceCS .worker , 1 * time .Second , wait .NeverStop )
341
- }
342
-
343
- func (gceCS * GCEControllerServer ) worker () {
344
- // Runs until workqueue is shut down
345
- for gceCS .processNextWorkItem () {
346
- }
347
- }
348
-
349
- func (gceCS * GCEControllerServer ) processNextWorkItem () bool {
350
- item , quit := gceCS .queue .Get ()
351
- if quit {
352
- return false
353
- }
354
- defer gceCS .queue .Done (item )
355
-
356
- workItem , ok := item .(* workItem )
357
- if ! ok {
358
- gceCS .queue .AddRateLimited (item )
359
- return true
360
- }
361
-
362
- if workItem .publishReq != nil {
363
- _ , err := gceCS .executeControllerPublishVolume (workItem .ctx , workItem .publishReq )
364
-
365
- if err != nil {
366
- klog .Errorf ("ControllerPublishVolume failed with error: %v" , err )
367
- }
368
- }
369
-
370
- if workItem .unpublishReq != nil {
371
- _ , err := gceCS .executeControllerUnpublishVolume (workItem .ctx , workItem .unpublishReq )
372
-
373
- if err != nil {
374
- klog .Errorf ("ControllerUnpublishVolume failed with error: %v" , err )
375
- }
376
- }
377
-
378
- gceCS .queue .Forget (item )
379
- return true
380
336
}
381
337
382
338
func (gceCS * GCEControllerServer ) ControllerPublishVolume (ctx context.Context , req * csi.ControllerPublishVolumeRequest ) (* csi.ControllerPublishVolumeResponse , error ) {
383
339
if ! gceCS .opsManager .IsReady () {
384
340
return nil , status .Errorf (codes .Aborted , "Cache not ready" )
385
341
}
386
342
387
- // Only valid requests will be queued
343
+ // Only valid requests will be accepted
388
344
_ , _ , err := gceCS .validateControllerPublishVolumeRequest (ctx , req )
389
-
390
345
if err != nil {
391
346
return nil , err
392
347
}
393
348
394
- // If the node is not marked, proceed the request
395
- if _ , found := gceCS .publishErrorsSeenOnNode [req .NodeId ]; ! found {
396
- return gceCS .executeControllerPublishVolume (ctx , req )
349
+ if gceCS .nodeBackoff .IsInBackOffSinceUpdate (req .NodeId , gceCS .nodeBackoff .Clock .Now ()) {
350
+ return nil , status .Errorf (codes .Unavailable , "ControllerPublish not permitted on node %q due to backoff" , req .NodeId )
397
351
}
398
352
399
- // Node is marked so queue up the request. Note the original gRPC context may get canceled,
400
- // so a new one is created here.
401
- //
402
- // Note that the original context probably has a timeout (see csiAttach in external-attacher),
403
- // which is ignored.
404
- gceCS .queue .AddRateLimited (& workItem {
405
- ctx : context .Background (),
406
- publishReq : req ,
407
- })
408
- return nil , status .Error (codes .Unavailable , "Request queued due to error condition on node" )
353
+ resp , backoff , err := gceCS .executeControllerPublishVolume (ctx , req )
354
+ if backoff {
355
+ gceCS .nodeBackoff .Next (req .NodeId , gceCS .nodeBackoff .Clock .Now ())
356
+ } else if err == nil {
357
+ gceCS .nodeBackoff .Reset (req .NodeId )
358
+ }
359
+
360
+ return resp , err
409
361
}
410
362
411
363
func (gceCS * GCEControllerServer ) validateControllerPublishVolumeRequest (ctx context.Context , req * csi.ControllerPublishVolumeRequest ) (string , * meta.Key , error ) {
@@ -436,11 +388,12 @@ func (gceCS *GCEControllerServer) validateControllerPublishVolumeRequest(ctx con
436
388
return project , volKey , nil
437
389
}
438
390
439
- func (gceCS * GCEControllerServer ) executeControllerPublishVolume (ctx context.Context , req * csi.ControllerPublishVolumeRequest ) (* csi.ControllerPublishVolumeResponse , error ) {
391
+ // executeControllerPublishVolume returns the controllerPublishVolumeResponse object. If there is an error, the function provides additional boolean hint on whether to set a backoff condition on the node.
392
+ func (gceCS * GCEControllerServer ) executeControllerPublishVolume (ctx context.Context , req * csi.ControllerPublishVolumeRequest ) (* csi.ControllerPublishVolumeResponse , bool , error ) {
440
393
project , volKey , err := gceCS .validateControllerPublishVolumeRequest (ctx , req )
441
394
442
395
if err != nil {
443
- return nil , err
396
+ return nil , false , err
444
397
}
445
398
446
399
volumeID := req .GetVolumeId ()
@@ -455,36 +408,36 @@ func (gceCS *GCEControllerServer) executeControllerPublishVolume(ctx context.Con
455
408
project , volKey , err = gceCS .CloudProvider .RepairUnderspecifiedVolumeKey (ctx , project , volKey )
456
409
if err != nil {
457
410
if gce .IsGCENotFoundError (err ) {
458
- return nil , status .Errorf (codes .NotFound , "ControllerPublishVolume could not find volume with ID %v: %v" , volumeID , err )
411
+ return nil , false , status .Errorf (codes .NotFound , "ControllerPublishVolume could not find volume with ID %v: %v" , volumeID , err )
459
412
}
460
- return nil , status .Errorf (codes .Internal , "ControllerPublishVolume error repairing underspecified volume key: %v" , err )
413
+ return nil , false , status .Errorf (codes .Internal , "ControllerPublishVolume error repairing underspecified volume key: %v" , err )
461
414
}
462
415
463
416
// Acquires the lock for the volume on that node only, because we need to support the ability
464
417
// to publish the same volume onto different nodes concurrently
465
418
lockingVolumeID := fmt .Sprintf ("%s/%s" , nodeID , volumeID )
466
419
if acquired := gceCS .volumeLocks .TryAcquire (lockingVolumeID ); ! acquired {
467
- return nil , status .Errorf (codes .Aborted , common .VolumeOperationAlreadyExistsFmt , lockingVolumeID )
420
+ return nil , false , status .Errorf (codes .Aborted , common .VolumeOperationAlreadyExistsFmt , lockingVolumeID )
468
421
}
469
422
defer gceCS .volumeLocks .Release (lockingVolumeID )
470
423
471
424
_ , err = gceCS .CloudProvider .GetDisk (ctx , project , volKey , gce .GCEAPIVersionV1 )
472
425
if err != nil {
473
426
if gce .IsGCENotFoundError (err ) {
474
- return nil , status .Error (codes .NotFound , fmt .Sprintf ("Could not find disk %v: %v" , volKey .String (), err ))
427
+ return nil , false , status .Error (codes .NotFound , fmt .Sprintf ("Could not find disk %v: %v" , volKey .String (), err ))
475
428
}
476
- return nil , status .Error (codes .Internal , fmt .Sprintf ("Unknown get disk error: %v" , err ))
429
+ return nil , true , status .Error (codes .Internal , fmt .Sprintf ("Unknown get disk error: %v" , err ))
477
430
}
478
431
instanceZone , instanceName , err := common .NodeIDToZoneAndName (nodeID )
479
432
if err != nil {
480
- return nil , status .Error (codes .NotFound , fmt .Sprintf ("could not split nodeID: %v" , err ))
433
+ return nil , false , status .Error (codes .NotFound , fmt .Sprintf ("could not split nodeID: %v" , err ))
481
434
}
482
435
instance , err := gceCS .CloudProvider .GetInstanceOrError (ctx , instanceZone , instanceName )
483
436
if err != nil {
484
437
if gce .IsGCENotFoundError (err ) {
485
- return nil , status .Error (codes .NotFound , fmt .Sprintf ("Could not find instance %v: %v" , nodeID , err ))
438
+ return nil , false , status .Error (codes .NotFound , fmt .Sprintf ("Could not find instance %v: %v" , nodeID , err ))
486
439
}
487
- return nil , status .Error (codes .Internal , fmt .Sprintf ("Unknown get instance error: %v" , err ))
440
+ return nil , true , status .Error (codes .Internal , fmt .Sprintf ("Unknown get instance error: %v" , err ))
488
441
}
489
442
490
443
readWrite := "READ_WRITE"
@@ -494,23 +447,23 @@ func (gceCS *GCEControllerServer) executeControllerPublishVolume(ctx context.Con
494
447
495
448
deviceName , err := common .GetDeviceName (volKey )
496
449
if err != nil {
497
- return nil , status .Error (codes .Internal , fmt .Sprintf ("error getting device name: %v" , err ))
450
+ return nil , false , status .Error (codes .Internal , fmt .Sprintf ("error getting device name: %v" , err ))
498
451
}
499
452
500
453
attached , err := diskIsAttachedAndCompatible (deviceName , instance , volumeCapability , readWrite )
501
454
if err != nil {
502
- return nil , status .Error (codes .AlreadyExists , fmt .Sprintf ("Disk %v already published to node %v but incompatbile: %v" , volKey .Name , nodeID , err ))
455
+ return nil , false , status .Error (codes .AlreadyExists , fmt .Sprintf ("Disk %v already published to node %v but incompatbile: %v" , volKey .Name , nodeID , err ))
503
456
}
504
457
if attached {
505
458
// Volume is attached to node. Success!
506
459
klog .V (4 ).Infof ("ControllerPublishVolume succeeded for disk %v to instance %v, already attached." , volKey , nodeID )
507
- return pubVolResp , nil
460
+ return pubVolResp , false , nil
508
461
}
509
462
510
463
// Check and initiate an attach disk operation.
511
464
instanceZone , instanceName , err = common .NodeIDToZoneAndName (nodeID )
512
465
if err != nil {
513
- return nil , status .Error (codes .InvalidArgument , fmt .Sprintf ("could not split nodeID: %v" , err ))
466
+ return nil , false , status .Error (codes .InvalidArgument , fmt .Sprintf ("could not split nodeID: %v" , err ))
514
467
}
515
468
516
469
err = gceCS .opsManager .ExecuteAttachDisk (ctx , & AttachDiskOpts {
@@ -523,48 +476,44 @@ func (gceCS *GCEControllerServer) executeControllerPublishVolume(ctx context.Con
523
476
InstanceName : instanceName ,
524
477
})
525
478
if err != nil {
526
- return nil , err
479
+ backoff := false
480
+ // Errors during API calls are reported as Internal
481
+ if isInternalError (err ) {
482
+ backoff = true
483
+ }
484
+ return nil , backoff , err
527
485
}
528
486
529
487
err = gceCS .CloudProvider .WaitForAttach (ctx , project , volKey , instanceZone , instanceName )
530
488
if err != nil {
531
- // Mark the node and rate limit all the following attach/detach
532
- // operations for this node
533
- gceCS .publishErrorsSeenOnNode [nodeID ] = true
534
- return nil , status .Error (codes .Internal , fmt .Sprintf ("unknown WaitForAttach error: %v" , err ))
489
+ return nil , true , status .Error (codes .Internal , fmt .Sprintf ("unknown WaitForAttach error: %v" , err ))
535
490
}
536
491
537
- // Attach succeeds so unmark the node
538
- delete (gceCS .publishErrorsSeenOnNode , nodeID )
539
-
540
492
klog .V (4 ).Infof ("ControllerPublishVolume succeeded for disk %v to instance %v" , volKey , nodeID )
541
- return pubVolResp , nil
493
+ return pubVolResp , false , nil
542
494
}
543
495
544
496
func (gceCS * GCEControllerServer ) ControllerUnpublishVolume (ctx context.Context , req * csi.ControllerUnpublishVolumeRequest ) (* csi.ControllerUnpublishVolumeResponse , error ) {
545
497
if ! gceCS .opsManager .IsReady () {
546
498
return nil , status .Errorf (codes .Aborted , "Cache not ready" )
547
499
}
548
500
549
- // Only valid requests will be queued
501
+ // Only valid requests will be accepted.
550
502
_ , _ , err := gceCS .validateControllerUnpublishVolumeRequest (ctx , req )
551
-
552
503
if err != nil {
553
504
return nil , err
554
505
}
555
506
556
- // If the node is not marked, proceed the request
557
- if _ , found := gceCS .publishErrorsSeenOnNode [req .NodeId ]; ! found {
558
- return gceCS .executeControllerUnpublishVolume (ctx , req )
507
+ if gceCS .nodeBackoff .IsInBackOffSinceUpdate (req .NodeId , gceCS .nodeBackoff .Clock .Now ()) {
508
+ return nil , status .Errorf (codes .Unavailable , "ControllerUnpublish not permitted on node %q due to backoff" , req .NodeId )
559
509
}
560
-
561
- // Node is marked so queue up the request
562
- gceCS .queue .AddRateLimited (& workItem {
563
- ctx : ctx ,
564
- unpublishReq : req ,
565
- })
566
-
567
- return & csi.ControllerUnpublishVolumeResponse {}, nil
510
+ resp , backoff , err := gceCS .executeControllerUnpublishVolume (ctx , req )
511
+ if backoff {
512
+ gceCS .nodeBackoff .Next (req .NodeId , gceCS .nodeBackoff .Clock .Now ())
513
+ } else if err == nil {
514
+ gceCS .nodeBackoff .Reset (req .NodeId )
515
+ }
516
+ return resp , err
568
517
}
569
518
570
519
func (gceCS * GCEControllerServer ) validateControllerUnpublishVolumeRequest (ctx context.Context , req * csi.ControllerUnpublishVolumeRequest ) (string , * meta.Key , error ) {
@@ -586,56 +535,57 @@ func (gceCS *GCEControllerServer) validateControllerUnpublishVolumeRequest(ctx c
586
535
return project , volKey , nil
587
536
}
588
537
589
- func (gceCS * GCEControllerServer ) executeControllerUnpublishVolume (ctx context.Context , req * csi.ControllerUnpublishVolumeRequest ) (* csi.ControllerUnpublishVolumeResponse , error ) {
538
+ // executeControllerPublishVolume returns the controllerUnpublishVolumeResponse object. If there is an error, the function provides additional boolean hint on whether to set a backoff condition on the node.
539
+ func (gceCS * GCEControllerServer ) executeControllerUnpublishVolume (ctx context.Context , req * csi.ControllerUnpublishVolumeRequest ) (* csi.ControllerUnpublishVolumeResponse , bool , error ) {
590
540
project , volKey , err := gceCS .validateControllerUnpublishVolumeRequest (ctx , req )
591
541
592
542
if err != nil {
593
- return nil , err
543
+ return nil , false , err
594
544
}
595
545
596
546
volumeID := req .GetVolumeId ()
597
547
nodeID := req .GetNodeId ()
598
548
project , volKey , err = gceCS .CloudProvider .RepairUnderspecifiedVolumeKey (ctx , project , volKey )
599
549
if err != nil {
600
550
if gce .IsGCENotFoundError (err ) {
601
- return nil , status .Errorf (codes .NotFound , "ControllerUnpublishVolume could not find volume with ID %v: %v" , volumeID , err )
551
+ return nil , false , status .Errorf (codes .NotFound , "ControllerUnpublishVolume could not find volume with ID %v: %v" , volumeID , err )
602
552
}
603
- return nil , status .Errorf (codes .Internal , "ControllerUnpublishVolume error repairing underspecified volume key: %v" , err )
553
+ return nil , true , status .Errorf (codes .Internal , "ControllerUnpublishVolume error repairing underspecified volume key: %v" , err )
604
554
}
605
555
606
556
// Acquires the lock for the volume on that node only, because we need to support the ability
607
557
// to unpublish the same volume from different nodes concurrently
608
558
lockingVolumeID := fmt .Sprintf ("%s/%s" , nodeID , volumeID )
609
559
if acquired := gceCS .volumeLocks .TryAcquire (lockingVolumeID ); ! acquired {
610
- return nil , status .Errorf (codes .Aborted , common .VolumeOperationAlreadyExistsFmt , lockingVolumeID )
560
+ return nil , false , status .Errorf (codes .Aborted , common .VolumeOperationAlreadyExistsFmt , lockingVolumeID )
611
561
}
612
562
defer gceCS .volumeLocks .Release (lockingVolumeID )
613
563
614
564
instanceZone , instanceName , err := common .NodeIDToZoneAndName (nodeID )
615
565
if err != nil {
616
- return nil , status .Error (codes .InvalidArgument , fmt .Sprintf ("could not split nodeID: %v" , err ))
566
+ return nil , false , status .Error (codes .InvalidArgument , fmt .Sprintf ("could not split nodeID: %v" , err ))
617
567
}
618
568
instance , err := gceCS .CloudProvider .GetInstanceOrError (ctx , instanceZone , instanceName )
619
569
if err != nil {
620
570
if gce .IsGCENotFoundError (err ) {
621
571
// Node not existing on GCE means that disk has been detached
622
572
klog .Warningf ("Treating volume %v as unpublished because node %v could not be found" , volKey .String (), instanceName )
623
- return & csi.ControllerUnpublishVolumeResponse {}, nil
573
+ return & csi.ControllerUnpublishVolumeResponse {}, false , nil
624
574
}
625
- return nil , status .Error (codes .Internal , fmt .Sprintf ("error getting instance: %v" , err ))
575
+ return nil , true , status .Error (codes .Internal , fmt .Sprintf ("error getting instance: %v" , err ))
626
576
}
627
577
628
578
deviceName , err := common .GetDeviceName (volKey )
629
579
if err != nil {
630
- return nil , status .Error (codes .Internal , fmt .Sprintf ("error getting device name: %v" , err ))
580
+ return nil , false , status .Error (codes .Internal , fmt .Sprintf ("error getting device name: %v" , err ))
631
581
}
632
582
633
583
attached := diskIsAttached (deviceName , instance )
634
584
635
585
if ! attached {
636
586
// Volume is not attached to node. Success!
637
587
klog .V (4 ).Infof ("ControllerUnpublishVolume succeeded for disk %v from node %v. Already not attached." , volKey , nodeID )
638
- return & csi.ControllerUnpublishVolumeResponse {}, nil
588
+ return & csi.ControllerUnpublishVolumeResponse {}, false , nil
639
589
}
640
590
641
591
err = gceCS .opsManager .ExecuteDetachDisk (ctx , & DetachDiskOpts {
@@ -645,12 +595,17 @@ func (gceCS *GCEControllerServer) executeControllerUnpublishVolume(ctx context.C
645
595
InstanceName : instanceName ,
646
596
})
647
597
if err != nil {
648
- return nil , err
598
+ backoff := false
599
+ // Errors during API calls are reported as Internal
600
+ if isInternalError (err ) {
601
+ backoff = true
602
+ }
603
+ return nil , backoff , err
649
604
}
650
605
651
- delete (gceCS .publishErrorsSeenOnNode , nodeID )
606
+ // delete(gceCS.publishErrorsSeenOnNode, nodeID)
652
607
klog .V (4 ).Infof ("ControllerUnpublishVolume succeeded for disk %v from node %v" , volKey , nodeID )
653
- return & csi.ControllerUnpublishVolumeResponse {}, nil
608
+ return & csi.ControllerUnpublishVolumeResponse {}, false , nil
654
609
}
655
610
656
611
func (gceCS * GCEControllerServer ) ValidateVolumeCapabilities (ctx context.Context , req * csi.ValidateVolumeCapabilitiesRequest ) (* csi.ValidateVolumeCapabilitiesResponse , error ) {
0 commit comments