Skip to content

Commit a6cb9bd

Browse files
committed
Adding repro script for ghost bucket generation
1 parent a6c4875 commit a6cb9bd

File tree

3 files changed

+265
-0
lines changed

3 files changed

+265
-0
lines changed

ghost-bucket/go.mod

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module ghost-bucket
2+
3+
go 1.21.4
4+
5+
require (
6+
github.com/aws/aws-sdk-go-v2/config v1.28.0
7+
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0
8+
golang.org/x/sync v0.8.0
9+
)
10+
11+
require (
12+
github.com/aws/aws-sdk-go-v2 v1.32.2 // indirect
13+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect
14+
github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect
15+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect
16+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect
17+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect
18+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
19+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 // indirect
20+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect
21+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 // indirect
22+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect
23+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 // indirect
24+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect
25+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect
26+
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect
27+
github.com/aws/smithy-go v1.22.0 // indirect
28+
)

ghost-bucket/go.sum

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI=
2+
github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
3+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0=
4+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA=
5+
github.com/aws/aws-sdk-go-v2/config v1.28.0 h1:FosVYWcqEtWNxHn8gB/Vs6jOlNwSoyOCA/g/sxyySOQ=
6+
github.com/aws/aws-sdk-go-v2/config v1.28.0/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc=
7+
github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8=
8+
github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU=
9+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q=
10+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw=
11+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk=
12+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y=
13+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s=
14+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60=
15+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
16+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
17+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE=
18+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ=
19+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g=
20+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ=
21+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4=
22+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw=
23+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA=
24+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0=
25+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg=
26+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM=
27+
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0 h1:xA6XhTF7PE89BCNHJbQi8VvPzcgMtmGC5dr8S8N7lHk=
28+
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8=
29+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk=
30+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U=
31+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o=
32+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI=
33+
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo=
34+
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo=
35+
github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM=
36+
github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
37+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
38+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=

ghost-bucket/main.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math/rand"
7+
"os"
8+
"sync/atomic"
9+
"time"
10+
11+
"github.com/aws/aws-sdk-go-v2/aws"
12+
"github.com/aws/aws-sdk-go-v2/config"
13+
"github.com/aws/aws-sdk-go-v2/service/s3"
14+
"golang.org/x/sync/errgroup"
15+
)
16+
17+
/*
18+
Dirty script to generate ghost buckets. It performs a lot of CreateBucket and DeleteBucket operations in parallel, for a
19+
large number of buckets. After a while, it checks if the buckets are ghost buckets and prints a list. It then tries to
20+
recreate the ghost buckets and checks again. It prints a list of deep ghost buckets which are the buckets that remain in
21+
the ghost state after trying to recreate them.
22+
23+
Exanple usage:
24+
AWS_ACCESS_KEY_ID=accessKey1 AWS_SECRET_ACCESS_KEY=verySecretKey1 S3_ENDPOINT_URL=127.0.0.1:8000 go run main.go
25+
26+
This should work both locally and remotely.
27+
28+
*/
29+
30+
func main() {
31+
32+
nBucket := 500
33+
34+
// Create an s3 bucket with the name "my-bucket"
35+
cfg, err := config.LoadDefaultConfig(context.TODO(),
36+
config.WithRegion("us-east-1"),
37+
config.WithBaseEndpoint("http://"+os.Getenv("S3_ENDPOINT_URL")+"/"),
38+
config.WithRetryer(func() aws.Retryer {
39+
return aws.NopRetryer{}
40+
}),
41+
)
42+
if err != nil {
43+
panic("configuration error, " + err.Error())
44+
}
45+
46+
client := s3.NewFromConfig(cfg)
47+
group, _ := errgroup.WithContext(context.Background())
48+
group.SetLimit(400)
49+
50+
bucketNumber := atomic.Int32{}
51+
52+
// Generate random prefix
53+
base := rand.Intn(4e9)
54+
55+
for i := 0; i < nBucket; i++ {
56+
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
57+
group.Go(func() error {
58+
bucketName := fmt.Sprintf("test-%010d-%010d", base, bucketNumber.Add(1))
59+
hammerWithRequests(client, bucketName)
60+
return nil
61+
62+
})
63+
}
64+
65+
if err := group.Wait(); err != nil {
66+
fmt.Println("error: ", err)
67+
}
68+
69+
fmt.Println("waiting for ghost buckets to appear")
70+
time.Sleep(30 * time.Second)
71+
fmt.Println("checking for ghost buckets")
72+
ghostBuckets := make([]bool, nBucket)
73+
74+
for i := 0; i < nBucket; i++ {
75+
i := i
76+
group.Go(func() error {
77+
bucketName := fmt.Sprintf("test-%010d-%010d", base, i)
78+
fmt.Println("checking bucket for ghostness: ", bucketName)
79+
ghost, err := isGhostBucket(client, bucketName)
80+
if err != nil {
81+
return err
82+
}
83+
ghostBuckets[i] = ghost
84+
return nil
85+
})
86+
}
87+
88+
if err := group.Wait(); err != nil {
89+
fmt.Println("error: ", err)
90+
}
91+
92+
for i, ghost := range ghostBuckets {
93+
if ghost {
94+
fmt.Printf("bucketName: %s isGhostBucket: %t\n", fmt.Sprintf("test-%010d-%010d", base, i), ghost)
95+
}
96+
}
97+
98+
bucketNumber.Store(0)
99+
100+
group.SetLimit(100)
101+
for i := 0; i < nBucket; i++ {
102+
group.Go(func() error {
103+
bucketN := bucketNumber.Add(1) - 1
104+
if !ghostBuckets[bucketN] {
105+
return nil
106+
}
107+
bucketName := fmt.Sprintf("test-%010d-%010d", base, bucketN)
108+
fmt.Println("attempting to recreate bucket: ", bucketName)
109+
for range make([]struct{}, 50) {
110+
client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
111+
Bucket: &bucketName,
112+
})
113+
time.Sleep(100 * time.Millisecond)
114+
}
115+
return nil
116+
})
117+
}
118+
119+
group.Wait()
120+
121+
deepGhostBuckets := make([]bool, nBucket)
122+
123+
for i := 0; i < nBucket; i++ {
124+
i := i
125+
group.Go(func() error {
126+
bucketName := fmt.Sprintf("test-%010d-%010d", base, i)
127+
ghost, err := isGhostBucket(client, bucketName)
128+
if err != nil {
129+
return err
130+
}
131+
deepGhostBuckets[i] = ghost
132+
return nil
133+
})
134+
}
135+
136+
if err := group.Wait(); err != nil {
137+
fmt.Println("error: ", err)
138+
}
139+
140+
for i, ghost := range deepGhostBuckets {
141+
if ghost {
142+
fmt.Printf("bucketName: %s isDeepGhostBucket: %t\n", fmt.Sprintf("test-%010d-%010d", base, i), ghost)
143+
}
144+
}
145+
}
146+
147+
func isGhostBucket(client *s3.Client, bucketName string) (bool, error) {
148+
149+
var existsInList, existsInHead bool
150+
151+
out, err := client.ListBuckets(context.Background(), &s3.ListBucketsInput{})
152+
if err != nil {
153+
return false, err
154+
}
155+
for _, b := range out.Buckets {
156+
if *b.Name == bucketName {
157+
existsInList = true
158+
}
159+
}
160+
_, err = client.HeadBucket(context.Background(), &s3.HeadBucketInput{
161+
Bucket: &bucketName,
162+
})
163+
existsInHead = err == nil
164+
165+
return existsInList && !existsInHead, nil
166+
}
167+
168+
func hammerWithRequests(client *s3.Client, bucketName string) error {
169+
client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
170+
Bucket: &bucketName,
171+
})
172+
group, _ := errgroup.WithContext(context.Background())
173+
for i := 0; i < 100; i++ {
174+
group.Go(func() error {
175+
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
176+
ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(1000*time.Millisecond))
177+
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
178+
Bucket: &bucketName,
179+
})
180+
if err != nil {
181+
fmt.Println("error creating bucket", bucketName, ": ", err)
182+
}
183+
return nil
184+
})
185+
group.Go(func() error {
186+
time.Sleep(time.Duration(rand.Intn(5000)) * time.Millisecond)
187+
ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(1000*time.Millisecond))
188+
_, err := client.DeleteBucket(ctx, &s3.DeleteBucketInput{
189+
Bucket: &bucketName,
190+
})
191+
if err != nil {
192+
fmt.Println("error deleting bucket", bucketName, ": ", err)
193+
}
194+
return nil
195+
})
196+
}
197+
group.Wait()
198+
return nil
199+
}

0 commit comments

Comments
 (0)