15
15
package storage
16
16
17
17
import (
18
+ "bytes"
18
19
"context"
19
20
"errors"
20
21
"fmt"
22
+ "io"
21
23
"log"
22
24
"net/url"
23
25
"os"
@@ -27,6 +29,7 @@ import (
27
29
"time"
28
30
29
31
"cloud.google.com/go/iam/apiv1/iampb"
32
+ "cloud.google.com/go/storage/experimental"
30
33
"github.com/google/go-cmp/cmp"
31
34
"github.com/googleapis/gax-go/v2"
32
35
"github.com/googleapis/gax-go/v2/apierror"
@@ -948,7 +951,6 @@ func initEmulatorClients() func() error {
948
951
log .Fatalf ("Error setting up HTTP client for emulator tests: %v" , err )
949
952
return noopCloser
950
953
}
951
-
952
954
emulatorClients = map [string ]storageClient {
953
955
"http" : httpClient ,
954
956
"grpc" : grpcClient ,
@@ -1335,10 +1337,14 @@ func TestObjectConditionsEmulated(t *testing.T) {
1335
1337
// Test that RetryNever prevents any retries from happening in both transports.
1336
1338
func TestRetryNeverEmulated (t * testing.T ) {
1337
1339
transportClientTest (context .Background (), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1340
+ _ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {}, nil )
1341
+ if err != nil {
1342
+ t .Fatalf ("creating bucket: %v" , err )
1343
+ }
1338
1344
instructions := map [string ][]string {"storage.buckets.get" : {"return-503" }}
1339
- testID := createRetryTest (t , project , bucket , client , instructions )
1345
+ testID := createRetryTest (t , client , instructions )
1340
1346
ctx = callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
1341
- _ , err : = client .GetBucket (ctx , bucket , nil , withRetryConfig (& retryConfig {policy : RetryNever }))
1347
+ _ , err = client .GetBucket (ctx , bucket , nil , withRetryConfig (& retryConfig {policy : RetryNever }))
1342
1348
1343
1349
var ae * apierror.APIError
1344
1350
if errors .As (err , & ae ) {
@@ -1354,12 +1360,16 @@ func TestRetryNeverEmulated(t *testing.T) {
1354
1360
// Test that errors are wrapped correctly if retry happens until a timeout.
1355
1361
func TestRetryTimeoutEmulated (t * testing.T ) {
1356
1362
transportClientTest (context .Background (), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1363
+ _ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {}, nil )
1364
+ if err != nil {
1365
+ t .Fatalf ("creating bucket: %v" , err )
1366
+ }
1357
1367
instructions := map [string ][]string {"storage.buckets.get" : {"return-503" , "return-503" , "return-503" , "return-503" , "return-503" }}
1358
- testID := createRetryTest (t , project , bucket , client , instructions )
1368
+ testID := createRetryTest (t , client , instructions )
1359
1369
ctx = callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
1360
1370
ctx , cancel := context .WithTimeout (ctx , 100 * time .Millisecond )
1361
1371
defer cancel ()
1362
- _ , err : = client .GetBucket (ctx , bucket , nil , idempotent (true ))
1372
+ _ , err = client .GetBucket (ctx , bucket , nil , idempotent (true ))
1363
1373
1364
1374
var ae * apierror.APIError
1365
1375
if errors .As (err , & ae ) {
@@ -1379,11 +1389,15 @@ func TestRetryTimeoutEmulated(t *testing.T) {
1379
1389
// Test that errors are wrapped correctly if retry happens until max attempts.
1380
1390
func TestRetryMaxAttemptsEmulated (t * testing.T ) {
1381
1391
transportClientTest (context .Background (), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1392
+ _ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {}, nil )
1393
+ if err != nil {
1394
+ t .Fatalf ("creating bucket: %v" , err )
1395
+ }
1382
1396
instructions := map [string ][]string {"storage.buckets.get" : {"return-503" , "return-503" , "return-503" , "return-503" , "return-503" }}
1383
- testID := createRetryTest (t , project , bucket , client , instructions )
1397
+ testID := createRetryTest (t , client , instructions )
1384
1398
ctx = callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
1385
1399
config := & retryConfig {maxAttempts : expectedAttempts (3 ), backoff : & gax.Backoff {Initial : 10 * time .Millisecond }}
1386
- _ , err : = client .GetBucket (ctx , bucket , nil , idempotent (true ), withRetryConfig (config ))
1400
+ _ , err = client .GetBucket (ctx , bucket , nil , idempotent (true ), withRetryConfig (config ))
1387
1401
1388
1402
var ae * apierror.APIError
1389
1403
if errors .As (err , & ae ) {
@@ -1426,8 +1440,12 @@ func TestTimeoutErrorEmulated(t *testing.T) {
1426
1440
// Test that server-side DEADLINE_EXCEEDED errors are retried as expected with gRPC.
1427
1441
func TestRetryDeadlineExceedeEmulated (t * testing.T ) {
1428
1442
transportClientTest (context .Background (), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1443
+ _ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {}, nil )
1444
+ if err != nil {
1445
+ t .Fatalf ("creating bucket: %v" , err )
1446
+ }
1429
1447
instructions := map [string ][]string {"storage.buckets.get" : {"return-504" , "return-504" }}
1430
- testID := createRetryTest (t , project , bucket , client , instructions )
1448
+ testID := createRetryTest (t , client , instructions )
1431
1449
ctx = callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
1432
1450
config := & retryConfig {maxAttempts : expectedAttempts (4 ), backoff : & gax.Backoff {Initial : 10 * time .Millisecond }}
1433
1451
if _ , err := client .GetBucket (ctx , bucket , nil , idempotent (true ), withRetryConfig (config )); err != nil {
@@ -1436,17 +1454,61 @@ func TestRetryDeadlineExceedeEmulated(t *testing.T) {
1436
1454
})
1437
1455
}
1438
1456
1457
+ // Test validates the retry for stalled read-request, when client is created with
1458
+ // WithReadStallTimeout.
1459
+ func TestRetryReadReqStallEmulated (t * testing.T ) {
1460
+ multiTransportTest (skipJSONReads (skipGRPC ("not supported" ), "not supported" ), t , func (t * testing.T , ctx context.Context , project , _ string , client * Client ) {
1461
+ // Setup bucket and upload object.
1462
+ bucket := fmt .Sprintf ("http-bucket-%d" , time .Now ().Nanosecond ())
1463
+ if _ , err := client .tc .CreateBucket (context .Background (), project , bucket , & BucketAttrs {Name : bucket }, nil ); err != nil {
1464
+ t .Fatalf ("client.CreateBucket: %v" , err )
1465
+ }
1466
+
1467
+ name , _ , _ , err := createObjectWithContent (ctx , bucket , randomBytes3MiB )
1468
+ if err != nil {
1469
+ t .Fatalf ("createObject: %v" , err )
1470
+ }
1471
+
1472
+ // Plant stall at start for 2s.
1473
+ instructions := map [string ][]string {"storage.objects.get" : {"stall-for-2s-after-0K" }}
1474
+ testID := createRetryTest (t , client .tc , instructions )
1475
+ ctx = callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
1476
+
1477
+ ctx , cancel := context .WithTimeout (ctx , 5 * time .Second )
1478
+ defer cancel ()
1479
+
1480
+ r , err := client .tc .NewRangeReader (ctx , & newRangeReaderParams {
1481
+ bucket : bucket ,
1482
+ object : name ,
1483
+ gen : defaultGen ,
1484
+ offset : 0 ,
1485
+ length : - 1 ,
1486
+ }, idempotent (true ))
1487
+ if err != nil {
1488
+ t .Fatalf ("NewRangeReader: %v" , err )
1489
+ }
1490
+ defer r .Close ()
1491
+
1492
+ buf := & bytes.Buffer {}
1493
+ if _ , err := io .Copy (buf , r ); err != nil {
1494
+ t .Fatalf ("io.Copy: %v" , err )
1495
+ }
1496
+ if ! bytes .Equal (buf .Bytes (), randomBytes3MiB ) {
1497
+ t .Errorf ("content does not match, got len %v, want len %v" , buf .Len (), len (randomBytes3MiB ))
1498
+ }
1499
+
1500
+ }, experimental .WithReadStallTimeout (
1501
+ & experimental.ReadStallTimeoutConfig {
1502
+ TargetPercentile : 0.99 ,
1503
+ Min : time .Second ,
1504
+ }))
1505
+ }
1506
+
1439
1507
// createRetryTest creates a bucket in the emulator and sets up a test using the
1440
1508
// Retry Test API for the given instructions. This is intended for emulator tests
1441
1509
// of retry behavior that are not covered by conformance tests.
1442
- func createRetryTest (t * testing.T , project , bucket string , client storageClient , instructions map [string ][]string ) string {
1510
+ func createRetryTest (t * testing.T , client storageClient , instructions map [string ][]string ) string {
1443
1511
t .Helper ()
1444
- ctx := context .Background ()
1445
-
1446
- _ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {}, nil )
1447
- if err != nil {
1448
- t .Fatalf ("creating bucket: %v" , err )
1449
- }
1450
1512
1451
1513
// Need the HTTP hostname to set up a retry test, as well as knowledge of
1452
1514
// underlying transport to specify instructions.
@@ -1470,14 +1532,20 @@ func createRetryTest(t *testing.T, project, bucket string, client storageClient,
1470
1532
return et .id
1471
1533
}
1472
1534
1473
- // createObject creates an object in the emulator and returns its name, generation, and
1474
- // metageneration.
1535
+ // createObject creates an object in the emulator with content randomBytesToWrite and
1536
+ // returns its name, generation, and metageneration.
1475
1537
func createObject (ctx context.Context , bucket string ) (string , int64 , int64 , error ) {
1538
+ return createObjectWithContent (ctx , bucket , randomBytesToWrite )
1539
+ }
1540
+
1541
+ // createObject creates an object in the emulator with the provided []byte contents,
1542
+ // and returns its name, generation, and metageneration.
1543
+ func createObjectWithContent (ctx context.Context , bucket string , bytes []byte ) (string , int64 , int64 , error ) {
1476
1544
prefix := time .Now ().Nanosecond ()
1477
1545
objName := fmt .Sprintf ("%d-object" , prefix )
1478
1546
1479
1547
w := veneerClient .Bucket (bucket ).Object (objName ).NewWriter (ctx )
1480
- if _ , err := w .Write (randomBytesToWrite ); err != nil {
1548
+ if _ , err := w .Write (bytes ); err != nil {
1481
1549
return "" , 0 , 0 , fmt .Errorf ("failed to populate test data: %w" , err )
1482
1550
}
1483
1551
if err := w .Close (); err != nil {
0 commit comments