@@ -938,7 +938,7 @@ func TestRequestWatch(t *testing.T) {
938
938
},
939
939
Err : true ,
940
940
ErrFn : func (err error ) bool {
941
- return apierrors .IsInternalError (err )
941
+ return ! apierrors .IsInternalError (err ) && strings . Contains ( err . Error (), "failed to reset the request body while retrying a request: EOF" )
942
942
},
943
943
},
944
944
{
@@ -954,7 +954,10 @@ func TestRequestWatch(t *testing.T) {
954
954
serverReturns : []responseErr {
955
955
{response : nil , err : io .EOF },
956
956
},
957
- Empty : true ,
957
+ Err : true ,
958
+ ErrFn : func (err error ) bool {
959
+ return ! apierrors .IsInternalError (err )
960
+ },
958
961
},
959
962
{
960
963
name : "max retries 2, server always returns a response with Retry-After header" ,
@@ -1130,7 +1133,7 @@ func TestRequestStream(t *testing.T) {
1130
1133
},
1131
1134
Err : true ,
1132
1135
ErrFn : func (err error ) bool {
1133
- return apierrors .IsInternalError (err )
1136
+ return ! apierrors .IsInternalError (err ) && strings . Contains ( err . Error (), "failed to reset the request body while retrying a request: EOF" )
1134
1137
},
1135
1138
},
1136
1139
{
@@ -1371,8 +1374,6 @@ func (b *testBackoffManager) Sleep(d time.Duration) {
1371
1374
}
1372
1375
1373
1376
func TestCheckRetryClosesBody (t * testing.T ) {
1374
- // unblock CI until http://issue.k8s.io/108906 is resolved in 1.24
1375
- t .Skip ("http://issue.k8s.io/108906" )
1376
1377
count := 0
1377
1378
ch := make (chan struct {})
1378
1379
testServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
@@ -2435,6 +2436,7 @@ func TestRequestWithRetry(t *testing.T) {
2435
2436
body io.Reader
2436
2437
serverReturns responseErr
2437
2438
errExpected error
2439
+ errContains string
2438
2440
transformFuncInvokedExpected int
2439
2441
roundTripInvokedExpected int
2440
2442
}{
@@ -2451,7 +2453,7 @@ func TestRequestWithRetry(t *testing.T) {
2451
2453
body : & readSeeker {err : io .EOF },
2452
2454
serverReturns : responseErr {response : retryAfterResponse (), err : nil },
2453
2455
errExpected : nil ,
2454
- transformFuncInvokedExpected : 1 ,
2456
+ transformFuncInvokedExpected : 0 ,
2455
2457
roundTripInvokedExpected : 1 ,
2456
2458
},
2457
2459
{
@@ -2474,7 +2476,7 @@ func TestRequestWithRetry(t *testing.T) {
2474
2476
name : "server returns retryable err, request body Seek returns error, retry aborted" ,
2475
2477
body : & readSeeker {err : io .EOF },
2476
2478
serverReturns : responseErr {response : nil , err : io .ErrUnexpectedEOF },
2477
- errExpected : io . ErrUnexpectedEOF ,
2479
+ errContains : "failed to reset the request body while retrying a request: EOF" ,
2478
2480
transformFuncInvokedExpected : 0 ,
2479
2481
roundTripInvokedExpected : 1 ,
2480
2482
},
@@ -2517,8 +2519,15 @@ func TestRequestWithRetry(t *testing.T) {
2517
2519
if test .transformFuncInvokedExpected != transformFuncInvoked {
2518
2520
t .Errorf ("Expected transform func to be invoked %d times, but got: %d" , test .transformFuncInvokedExpected , transformFuncInvoked )
2519
2521
}
2520
- if test .errExpected != unWrap (err ) {
2521
- t .Errorf ("Expected error: %v, but got: %v" , test .errExpected , unWrap (err ))
2522
+ switch {
2523
+ case test .errExpected != nil :
2524
+ if test .errExpected != unWrap (err ) {
2525
+ t .Errorf ("Expected error: %v, but got: %v" , test .errExpected , unWrap (err ))
2526
+ }
2527
+ case len (test .errContains ) > 0 :
2528
+ if ! strings .Contains (err .Error (), test .errContains ) {
2529
+ t .Errorf ("Expected error message to caontain: %q, but got: %q" , test .errContains , err .Error ())
2530
+ }
2522
2531
}
2523
2532
})
2524
2533
}
@@ -3531,3 +3540,103 @@ func TestTransportConcurrency(t *testing.T) {
3531
3540
})
3532
3541
}
3533
3542
}
3543
+
3544
+ // TODO: see if we can consolidate the other trackers into one.
3545
+ type requestBodyTracker struct {
3546
+ io.ReadSeeker
3547
+ f func (string )
3548
+ }
3549
+
3550
+ func (t * requestBodyTracker ) Read (p []byte ) (int , error ) {
3551
+ t .f ("Request.Body.Read" )
3552
+ return t .ReadSeeker .Read (p )
3553
+ }
3554
+
3555
+ func (t * requestBodyTracker ) Seek (offset int64 , whence int ) (int64 , error ) {
3556
+ t .f ("Request.Body.Seek" )
3557
+ return t .ReadSeeker .Seek (offset , whence )
3558
+ }
3559
+
3560
+ type responseBodyTracker struct {
3561
+ io.ReadCloser
3562
+ f func (string )
3563
+ }
3564
+
3565
+ func (t * responseBodyTracker ) Read (p []byte ) (int , error ) {
3566
+ t .f ("Response.Body.Read" )
3567
+ return t .ReadCloser .Read (p )
3568
+ }
3569
+
3570
+ func (t * responseBodyTracker ) Close () error {
3571
+ t .f ("Response.Body.Close" )
3572
+ return t .ReadCloser .Close ()
3573
+ }
3574
+
3575
+ type recorder struct {
3576
+ order []string
3577
+ }
3578
+
3579
+ func (r * recorder ) record (call string ) {
3580
+ r .order = append (r .order , call )
3581
+ }
3582
+
3583
+ func TestRequestBodyResetOrder (t * testing.T ) {
3584
+ recorder := & recorder {}
3585
+ respBodyTracker := & responseBodyTracker {
3586
+ ReadCloser : nil , // the server will fill it
3587
+ f : recorder .record ,
3588
+ }
3589
+
3590
+ var attempts int
3591
+ client := clientForFunc (func (req * http.Request ) (* http.Response , error ) {
3592
+ defer func () {
3593
+ attempts ++
3594
+ }()
3595
+
3596
+ // read the request body.
3597
+ ioutil .ReadAll (req .Body )
3598
+
3599
+ // first attempt, we send a retry-after
3600
+ if attempts == 0 {
3601
+ resp := retryAfterResponse ()
3602
+ respBodyTracker .ReadCloser = ioutil .NopCloser (bytes .NewReader ([]byte {}))
3603
+ resp .Body = respBodyTracker
3604
+ return resp , nil
3605
+ }
3606
+
3607
+ return & http.Response {StatusCode : http .StatusOK }, nil
3608
+ })
3609
+
3610
+ reqBodyTracker := & requestBodyTracker {
3611
+ ReadSeeker : bytes .NewReader ([]byte {}), // empty body ensures one Read operation at most.
3612
+ f : recorder .record ,
3613
+ }
3614
+ req := & Request {
3615
+ verb : "POST" ,
3616
+ body : reqBodyTracker ,
3617
+ c : & RESTClient {
3618
+ content : defaultContentConfig (),
3619
+ Client : client ,
3620
+ },
3621
+ backoff : & noSleepBackOff {},
3622
+ retry : & withRetry {maxRetries : 1 },
3623
+ }
3624
+
3625
+ req .Do (context .Background ())
3626
+
3627
+ expected := []string {
3628
+ // 1st attempt: the server handler reads the request body
3629
+ "Request.Body.Read" ,
3630
+ // the server sends a retry-after, client reads the
3631
+ // response body, and closes it
3632
+ "Response.Body.Read" ,
3633
+ "Response.Body.Close" ,
3634
+ // client retry logic seeks to the beginning of the request body
3635
+ "Request.Body.Seek" ,
3636
+ // 2nd attempt: the server reads the request body
3637
+ "Request.Body.Read" ,
3638
+ }
3639
+ if ! reflect .DeepEqual (expected , recorder .order ) {
3640
+ t .Errorf ("Expected invocation request and response body operations for retry do not match: %s" , cmp .Diff (expected , recorder .order ))
3641
+ }
3642
+ }
0 commit comments