1515package storage
1616
1717import (
18+ "bytes"
1819 "context"
1920 "errors"
2021 "fmt"
22+ "io"
2123 "log"
2224 "net/url"
2325 "os"
@@ -27,6 +29,7 @@ import (
2729 "time"
2830
2931 "cloud.google.com/go/iam/apiv1/iampb"
32+ "cloud.google.com/go/storage/experimental"
3033 "github.com/google/go-cmp/cmp"
3134 "github.com/googleapis/gax-go/v2"
3235 "github.com/googleapis/gax-go/v2/apierror"
@@ -948,7 +951,6 @@ func initEmulatorClients() func() error {
948951 log .Fatalf ("Error setting up HTTP client for emulator tests: %v" , err )
949952 return noopCloser
950953 }
951-
952954 emulatorClients = map [string ]storageClient {
953955 "http" : httpClient ,
954956 "grpc" : grpcClient ,
@@ -1335,10 +1337,14 @@ func TestObjectConditionsEmulated(t *testing.T) {
13351337// Test that RetryNever prevents any retries from happening in both transports.
13361338func TestRetryNeverEmulated (t * testing.T ) {
13371339 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+ }
13381344 instructions := map [string ][]string {"storage.buckets.get" : {"return-503" }}
1339- testID := createRetryTest (t , project , bucket , client , instructions )
1345+ testID := createRetryTest (t , client , instructions )
13401346 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 }))
13421348
13431349 var ae * apierror.APIError
13441350 if errors .As (err , & ae ) {
@@ -1354,12 +1360,16 @@ func TestRetryNeverEmulated(t *testing.T) {
13541360// Test that errors are wrapped correctly if retry happens until a timeout.
13551361func TestRetryTimeoutEmulated (t * testing.T ) {
13561362 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+ }
13571367 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 )
13591369 ctx = callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
13601370 ctx , cancel := context .WithTimeout (ctx , 100 * time .Millisecond )
13611371 defer cancel ()
1362- _ , err : = client .GetBucket (ctx , bucket , nil , idempotent (true ))
1372+ _ , err = client .GetBucket (ctx , bucket , nil , idempotent (true ))
13631373
13641374 var ae * apierror.APIError
13651375 if errors .As (err , & ae ) {
@@ -1379,11 +1389,15 @@ func TestRetryTimeoutEmulated(t *testing.T) {
13791389// Test that errors are wrapped correctly if retry happens until max attempts.
13801390func TestRetryMaxAttemptsEmulated (t * testing.T ) {
13811391 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+ }
13821396 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 )
13841398 ctx = callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
13851399 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 ))
13871401
13881402 var ae * apierror.APIError
13891403 if errors .As (err , & ae ) {
@@ -1426,8 +1440,12 @@ func TestTimeoutErrorEmulated(t *testing.T) {
14261440// Test that server-side DEADLINE_EXCEEDED errors are retried as expected with gRPC.
14271441func TestRetryDeadlineExceedeEmulated (t * testing.T ) {
14281442 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+ }
14291447 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 )
14311449 ctx = callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
14321450 config := & retryConfig {maxAttempts : expectedAttempts (4 ), backoff : & gax.Backoff {Initial : 10 * time .Millisecond }}
14331451 if _ , err := client .GetBucket (ctx , bucket , nil , idempotent (true ), withRetryConfig (config )); err != nil {
@@ -1436,17 +1454,61 @@ func TestRetryDeadlineExceedeEmulated(t *testing.T) {
14361454 })
14371455}
14381456
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+
14391507// createRetryTest creates a bucket in the emulator and sets up a test using the
14401508// Retry Test API for the given instructions. This is intended for emulator tests
14411509// 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 {
14431511 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- }
14501512
14511513 // Need the HTTP hostname to set up a retry test, as well as knowledge of
14521514 // underlying transport to specify instructions.
@@ -1470,14 +1532,20 @@ func createRetryTest(t *testing.T, project, bucket string, client storageClient,
14701532 return et .id
14711533}
14721534
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.
14751537func 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 ) {
14761544 prefix := time .Now ().Nanosecond ()
14771545 objName := fmt .Sprintf ("%d-object" , prefix )
14781546
14791547 w := veneerClient .Bucket (bucket ).Object (objName ).NewWriter (ctx )
1480- if _ , err := w .Write (randomBytesToWrite ); err != nil {
1548+ if _ , err := w .Write (bytes ); err != nil {
14811549 return "" , 0 , 0 , fmt .Errorf ("failed to populate test data: %w" , err )
14821550 }
14831551 if err := w .Close (); err != nil {
0 commit comments