@@ -114,6 +114,36 @@ function createK8sVolumeObject (volume: Volume): K8sVolume {
114
114
return obj ;
115
115
}
116
116
117
+ // Duplicate request cache entry helps to detect retransmits of the same request
118
+ //
119
+ // This may seem like a useless thing but k8s is agressive on retransmitting
120
+ // requests. The first retransmit happens just a tens of ms after the original
121
+ // request. Having many requests that are the same in progress creates havoc
122
+ // and forces mayastor to execute repeating code.
123
+ //
124
+ // NOTE: Assumption is that k8s doesn't submit duplicate request for the same
125
+ // volume (the same uuid) with different parameters.
126
+ //
127
+ class Request {
128
+ uuid : string ; // ID of the object in the operation
129
+ op : string ; // name of the operation
130
+ callbacks : CsiDoneCb [ ] ; // callbacks to call when done
131
+
132
+ constructor ( uuid : string , op : string , cb : CsiDoneCb ) {
133
+ this . uuid = uuid ;
134
+ this . op = op ;
135
+ this . callbacks = [ cb ] ;
136
+ }
137
+
138
+ wait ( cb : CsiDoneCb ) {
139
+ this . callbacks . push ( cb ) ;
140
+ }
141
+
142
+ done ( err : any , resp ?: any ) {
143
+ this . callbacks . forEach ( ( cb ) => cb ( err , resp ) ) ;
144
+ }
145
+ }
146
+
117
147
// CSI Controller implementation.
118
148
//
119
149
// It implements Identity and Controller grpc services from csi proto file.
@@ -127,6 +157,7 @@ class CsiServer {
127
157
private sockPath : string ;
128
158
private nextListContextId : number ;
129
159
private listContexts : Record < string , ListContext > ;
160
+ private duplicateRequestCache : Request [ ] ;
130
161
131
162
// Creates new csi server
132
163
//
@@ -139,6 +170,7 @@ class CsiServer {
139
170
this . sockPath = sockPath ;
140
171
this . nextListContextId = 1 ;
141
172
this . listContexts = { } ;
173
+ this . duplicateRequestCache = [ ] ;
142
174
143
175
// The data returned by identity service should be kept in sync with
144
176
// responses for the same methods on storage node.
@@ -255,6 +287,32 @@ class CsiServer {
255
287
this . ready = false ;
256
288
}
257
289
290
+ // Find outstanding request by uuid and operation type.
291
+ _findRequest ( uuid : string , op : string ) : Request | undefined {
292
+ return this . duplicateRequestCache . find ( ( e ) => e . uuid === uuid && e . op === op ) ;
293
+ }
294
+
295
+ _beginRequest ( uuid : string , op : string , cb : CsiDoneCb ) : Request | undefined {
296
+ let request = this . _findRequest ( uuid , op ) ;
297
+ if ( request ) {
298
+ log . debug ( `Duplicate ${ op } volume request detected` ) ;
299
+ request . wait ( cb ) ;
300
+ return ;
301
+ }
302
+ request = new Request ( uuid , op , cb ) ;
303
+ this . duplicateRequestCache . push ( request ) ;
304
+ return request ;
305
+ }
306
+
307
+ // Remove request entry from the cache and call done callbacks.
308
+ _endRequest ( request : Request , err : any , resp ?: any ) {
309
+ let idx = this . duplicateRequestCache . indexOf ( request ) ;
310
+ if ( idx >= 0 ) {
311
+ this . duplicateRequestCache . splice ( idx , 1 ) ;
312
+ }
313
+ request . done ( err , resp ) ;
314
+ }
315
+
258
316
//
259
317
// Implementation of CSI identity methods
260
318
//
@@ -400,6 +458,12 @@ class CsiServer {
400
458
count = 1 ;
401
459
}
402
460
461
+ // If this is a duplicate request then assure it is executed just once.
462
+ let request = this . _beginRequest ( uuid , 'create' , cb ) ;
463
+ if ( ! request ) {
464
+ return ;
465
+ }
466
+
403
467
// create the volume
404
468
let volume ;
405
469
try {
@@ -412,12 +476,14 @@ class CsiServer {
412
476
protocol : protocol
413
477
} ) ;
414
478
} catch ( err ) {
415
- return cb ( err ) ;
479
+ this . _endRequest ( request , err ) ;
480
+ return ;
416
481
}
417
482
418
483
// This was used in the old days for NBD protocol
419
484
const accessibleTopology : TopologyKeys [ ] = [ ] ;
420
- cb ( null , {
485
+
486
+ this . _endRequest ( request , null , {
421
487
volume : {
422
488
capacityBytes : volume . getSize ( ) ,
423
489
volumeId : uuid ,
@@ -437,13 +503,19 @@ class CsiServer {
437
503
438
504
log . debug ( `Request to destroy volume "${ args . volumeId } "` ) ;
439
505
506
+ // If this is a duplicate request then assure it is executed just once.
507
+ let request = this . _beginRequest ( args . volumeId , 'delete' , cb ) ;
508
+ if ( ! request ) {
509
+ return ;
510
+ }
511
+
440
512
try {
441
513
await this . volumes . destroyVolume ( args . volumeId ) ;
442
514
} catch ( err ) {
443
- return cb ( err ) ;
515
+ return this . _endRequest ( request , err ) ;
444
516
}
445
517
log . info ( `Volume "${ args . volumeId } " destroyed` ) ;
446
- cb ( null ) ;
518
+ this . _endRequest ( request , null ) ;
447
519
}
448
520
449
521
async listVolumes ( call : any , cb : CsiDoneCb ) {
@@ -542,6 +614,12 @@ class CsiServer {
542
614
return cb ( err ) ;
543
615
}
544
616
617
+ // If this is a duplicate request then assure it is executed just once.
618
+ let request = this . _beginRequest ( args . volumeId , 'publish' , cb ) ;
619
+ if ( ! request ) {
620
+ return ;
621
+ }
622
+
545
623
const publishContext : any = { } ;
546
624
try {
547
625
publishContext . uri = await volume . publish ( protocol ) ;
@@ -551,15 +629,16 @@ class CsiServer {
551
629
} catch ( err ) {
552
630
if ( err . code === grpc . status . ALREADY_EXISTS ) {
553
631
log . debug ( `Volume "${ args . volumeId } " already published on this node` ) ;
554
- cb ( null , { publishContext } ) ;
632
+ this . _endRequest ( request , null , { publishContext } ) ;
555
633
} else {
556
634
cb ( err ) ;
635
+ this . _endRequest ( request , err ) ;
557
636
}
558
637
return ;
559
638
}
560
639
561
640
log . info ( `Published volume "${ args . volumeId } " over ${ protocol } ` ) ;
562
- cb ( null , { publishContext } ) ;
641
+ this . _endRequest ( request , null , { publishContext } ) ;
563
642
}
564
643
565
644
async controllerUnpublishVolume ( call : any , cb : CsiDoneCb ) {
@@ -580,13 +659,20 @@ class CsiServer {
580
659
} catch ( err ) {
581
660
return cb ( err ) ;
582
661
}
662
+
663
+ // If this is a duplicate request then assure it is executed just once.
664
+ let request = this . _beginRequest ( args . volumeId , 'unpublish' , cb ) ;
665
+ if ( ! request ) {
666
+ return ;
667
+ }
668
+
583
669
try {
584
670
await volume . unpublish ( ) ;
585
671
} catch ( err ) {
586
- return cb ( err ) ;
672
+ return this . _endRequest ( request , err ) ;
587
673
}
588
674
log . info ( `Unpublished volume "${ args . volumeId } "` ) ;
589
- cb ( null , { } ) ;
675
+ this . _endRequest ( request , null , { } ) ;
590
676
}
591
677
592
678
async validateVolumeCapabilities ( call : any , cb : CsiDoneCb ) {
0 commit comments