diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 54add786..c7e7b9b6 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -979,6 +979,7 @@ func (p *Posix) HeadObject(bucket, object string) (*s3.HeadObjectOutput, error) } func (p *Posix) CopyObject(srcBucket, srcObject, DstBucket, dstObject string) (*s3.CopyObjectOutput, error) { + fmt.Println(srcBucket, srcObject, DstBucket, dstObject) _, err := os.Stat(srcBucket) if errors.Is(err, fs.ErrNotExist) { return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket) @@ -1005,7 +1006,12 @@ func (p *Posix) CopyObject(srcBucket, srcObject, DstBucket, dstObject string) (* } defer f.Close() - etag, err := p.PutObject(&s3.PutObjectInput{Bucket: &DstBucket, Key: &dstObject, Body: f}) + fInfo, err := f.Stat() + if err != nil { + return nil, fmt.Errorf("stat object: %w", err) + } + + etag, err := p.PutObject(&s3.PutObjectInput{Bucket: &DstBucket, Key: &dstObject, Body: f, ContentLength: fInfo.Size()}) if err != nil { return nil, err } diff --git a/cmd/versitygw/test.go b/cmd/versitygw/test.go index 40846891..578b30a4 100644 --- a/cmd/versitygw/test.go +++ b/cmd/versitygw/test.go @@ -68,17 +68,20 @@ func initTestFlags() []cli.Flag { func initTestCommands() []*cli.Command { return []*cli.Command{ { - Name: "make-bucket", - Usage: "Test bucket creation.", + Name: "bucket-actions", + Usage: "Test bucket creation, checking the existence, deletes it.", Description: `Calls s3 gateway create-bucket action to create a new bucket, - then calls delete-bucket action to delete the bucket.`, + calls head-bucket action to check the existence, then calls delete-bucket action to delete the bucket.`, Action: getAction(integration.TestMakeBucket), }, { - Name: "put-get-object", - Usage: "Test put & get object.", + Name: "object-actions", + Usage: "Test put/get/delete/copy objects.", Description: `Creates a bucket with s3 gateway action, puts an object in it, - gets the object from the bucket, deletes both the object and bucket.`, + tries to copy into another bucket, that doesn't exist, creates the destination bucket for copying, + copies the object, get's the object to check the length and content, + get's the copied object to check the length and content, deletes all the objects inside the source bucket, + deletes both the objects and buckets.`, Action: getAction(integration.TestPutGetObject), }, { diff --git a/integration/tests.go b/integration/tests.go index e4866cb5..482e8eda 100644 --- a/integration/tests.go +++ b/integration/tests.go @@ -22,20 +22,34 @@ var ( ) func TestMakeBucket(s *S3Conf) { - testname := "test make bucket" + testname := "test make/head/delete bucket" runF(testname) + s3client := s3.NewFromConfig(s.Config()) + bucket := "testbucket" - err := setup(s, bucket) + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket}) + cancel() + if err == nil { + failF("%v: expected error, instead got success response", testname) + return + } + + err = setup(s, bucket) if err != nil { failF("%v: %v", testname, err) return } - passF(testname) - testname = "test delete empty bucket" - runF(testname) + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket}) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } err = teardown(s, bucket) if err != nil { @@ -46,10 +60,16 @@ func TestMakeBucket(s *S3Conf) { } func TestPutGetObject(s *S3Conf) { - testname := "test put/get object" + testname := "test put/get/delete/copy objects" runF(testname) bucket := "testbucket1" + dstBucket := "testdstbucket" + obj := "myobject" + obj2 := "myobject2" + copySource := bucket + "/" + obj + + s3client := s3.NewFromConfig(s.Config()) err := setup(s, bucket) if err != nil { @@ -64,13 +84,22 @@ func TestPutGetObject(s *S3Conf) { csum := sha256.Sum256(data) r := bytes.NewReader(data) - name := "myobject" - s3client := s3.NewFromConfig(s.Config()) - ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, - Key: &name, + Key: &obj, + Body: r, + }) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.PutObject(ctx, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj2, Body: r, }) cancel() @@ -82,7 +111,7 @@ func TestPutGetObject(s *S3Conf) { ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, - Key: &name, + Key: &obj, }) defer cancel() if err != nil { @@ -108,11 +137,101 @@ func TestPutGetObject(s *S3Conf) { return } + // Expected error: destination bucket doesn't exist + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{Bucket: &dstBucket, Key: &obj, CopySource: ©Source}) + cancel() + if err == nil { + failF("%v: expect bucket not found error instead got success response", testname) + return + } + + err = setup(s, dstBucket) + if err != nil { + failF("%v: %v", testname, err) + return + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{Bucket: &dstBucket, Key: &obj, CopySource: ©Source}) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + copyObjOut, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &dstBucket, + Key: &obj, + }) + defer cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + defer copyObjOut.Body.Close() + + if copyObjOut.ContentLength != int64(datalen) { + failF("%v: content length got %v expected %v", testname, copyObjOut.ContentLength, datalen) + return + } + + b, err = io.ReadAll(copyObjOut.Body) + if err != nil { + failF("%v: read body %v", testname, err) + return + } + + copysum := sha256.Sum256(b) + if csum != copysum { + failF("%v: copied object checksum got %x expected %x", testname, copysum, csum) + return + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{Bucket: &bucket, Delete: &types.Delete{Objects: []types.ObjectIdentifier{{Key: &obj}, {Key: &obj2}}}}) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + + objCount := 0 + + in := &s3.ListObjectsV2Input{Bucket: &bucket} + for { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListObjectsV2(ctx, in) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + objCount += len(out.Contents) + if out.IsTruncated { + in.ContinuationToken = out.ContinuationToken + } else { + break + } + } + + if objCount != 2 { + failF("%v: expected object count %v instead got %v", testname, 2, objCount) + } + err = teardown(s, bucket) if err != nil { failF("%v: %v", testname, err) return } + + err = teardown(s, dstBucket) + if err != nil { + failF("%v: %v", testname, err) + return + } + passF(testname) } diff --git a/integration/utils.go b/integration/utils.go index eef2f2a1..bbdc95b9 100644 --- a/integration/utils.go +++ b/integration/utils.go @@ -111,21 +111,28 @@ func containsPart(part int32, list []types.Part) bool { return false } -// Checks if the slices contain the same objects, if the objects doesn't -// contain map, slice, channel. func areTagsSame(tags1, tags2 []types.Tag) bool { if len(tags1) != len(tags2) { return false } - for i, tag := range tags1 { - if *tag.Key != *tags2[i].Key || *tag.Value != *tags2[i].Value { + for _, tag := range tags1 { + if !containsTag(tag, tags2) { return false } } return true } +func containsTag(tag types.Tag, list []types.Tag) bool { + for _, item := range list { + if *item.Key == *tag.Key && *item.Value == *tag.Value { + return true + } + } + return false +} + func checkGrants(grts1, grts2 []types.Grant) bool { if len(grts1) != len(grts2) { return false