diff --git a/.gitignore b/.gitignore index d2365037..09d1f028 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,5 @@ VERSION /versitygw.spec *.tar *.tar.gz -/rand.data +**/rand.data /profile.txt diff --git a/cmd/versitygw/test.go b/cmd/versitygw/test.go index 83073e6f..bd1dea62 100644 --- a/cmd/versitygw/test.go +++ b/cmd/versitygw/test.go @@ -157,6 +157,14 @@ func initTestCommands() []*cli.Command { removes both the object and bucket`, Action: getAction(integration.TestInvalidMultiParts), }, + { + Name: "object-tag-actions", + Usage: "Tests get/put/delete object tag actions.", + Description: `Creates a bucket with s3 gateway action, puts an object in it, + puts some tags for the object, gets the tags, compares the results, removes the tags, + gets the tags again, checks it to be empty, then removes both the object and bucket`, + Action: getAction(integration.TestPutGetRemoveTags), + }, { Name: "full-flow", Usage: "Tests the full flow of gateway.", diff --git a/integration/tests.go b/integration/tests.go index a11ebe4f..0d89658e 100644 --- a/integration/tests.go +++ b/integration/tests.go @@ -9,7 +9,6 @@ import ( "io" "math" "os" - "strings" "sync" "time" @@ -21,65 +20,6 @@ var ( shortTimeout = 10 * time.Second ) -func setup(s *S3Conf, bucket string) error { - s3client := s3.NewFromConfig(s.Config()) - - ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) - _, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{ - Bucket: &bucket, - }) - cancel() - return err -} - -func teardown(s *S3Conf, bucket string) error { - s3client := s3.NewFromConfig(s.Config()) - - deleteObject := func(bucket, key, versionId *string) error { - ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) - _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ - Bucket: bucket, - Key: key, - VersionId: versionId, - }) - cancel() - if err != nil { - return fmt.Errorf("failed to delete object %v: %v", *key, err) - } - return nil - } - - in := &s3.ListObjectsV2Input{Bucket: &bucket} - for { - ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) - out, err := s3client.ListObjectsV2(ctx, in) - cancel() - if err != nil { - return fmt.Errorf("failed to list objects: %v", err) - } - - for _, item := range out.Contents { - err = deleteObject(&bucket, item.Key, nil) - if err != nil { - return err - } - } - - if out.IsTruncated { - in.ContinuationToken = out.ContinuationToken - } else { - break - } - } - - ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) - _, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{ - Bucket: &bucket, - }) - cancel() - return err -} - func TestMakeBucket(s *S3Conf) { testname := "test make bucket" runF(testname) @@ -245,20 +185,6 @@ func TestPutGetMPObject(s *S3Conf) { passF(testname) } -func isEqual(a, b []byte) bool { - if len(a) != len(b) { - return false - } - - for i, d := range a { - if d != b[i] { - return false - } - } - - return true -} - func TestPutDirObject(s *S3Conf) { testname := "test put directory object" runF(testname) @@ -434,16 +360,6 @@ func TestListObject(s *S3Conf) { passF(testname) } -func contains(name string, list []types.Object) bool { - for _, item := range list { - fmt.Println(*item.Key) - if strings.EqualFold(name, *item.Key) { - return true - } - } - return false -} - func TestListAbortMultiPartObject(s *S3Conf) { testname := "list/abort multipart objects" runF(testname) @@ -528,15 +444,6 @@ func TestListAbortMultiPartObject(s *S3Conf) { passF(testname) } -func containsUID(name, id string, list []types.MultipartUpload) bool { - for _, item := range list { - if strings.EqualFold(name, *item.Key) && strings.EqualFold(id, *item.UploadId) { - return true - } - } - return false -} - func TestListMultiParts(s *S3Conf) { testname := "list multipart parts" runF(testname) @@ -907,15 +814,6 @@ func TestIncompleteMultiParts(s *S3Conf) { passF(testname) } -func containsPart(part int32, list []types.Part) bool { - for _, item := range list { - if item.PartNumber == part { - return true - } - } - return false -} - func TestIncompletePutObject(s *S3Conf) { testname := "test incomplete put object" runF(testname) @@ -1037,18 +935,6 @@ func TestRangeGet(s *S3Conf) { passF(testname) } -func isSame(a, b []byte) bool { - if len(a) != len(b) { - return false - } - for i, x := range a { - if x != b[i] { - return false - } - } - return true -} - func TestInvalidMultiParts(s *S3Conf) { testname := "invalid multipart parts" runF(testname) @@ -1205,6 +1091,101 @@ func TestPerformance(s *S3Conf, upload, download bool, files int, objectSize int return nil } +func TestPutGetRemoveTags(s *S3Conf) { + testname := "test put/get/remove object tags" + runF(testname) + + bucket := "testbucket1" + + err := setup(s, bucket) + if err != nil { + failF("%v: %v", testname, err) + return + } + + obj := "myobject" + s3client := s3.NewFromConfig(s.Config()) + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.PutObject(ctx, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + }) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + + key1 := "hello1" + key2 := "hello2" + val1 := "world1" + val2 := "world2" + + tagging := types.Tagging{TagSet: []types.Tag{{Key: &key1, Value: &val1}, {Key: &key2, Value: &val2}}} + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ + Bucket: &bucket, + Key: &obj, + Tagging: &tagging, + }) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ + Key: &obj, + Bucket: &bucket, + }) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + + ok := areTagsSame(tagging.TagSet, out.TagSet) + if !ok { + failF("%v: expected %v instead got %v", testname, tagging.TagSet, out.TagSet) + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{ + Key: &obj, + Bucket: &bucket, + }) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + out, err = s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ + Key: &obj, + Bucket: &bucket, + }) + cancel() + if err != nil { + failF("%v: %v", testname, err) + return + } + + if len(out.TagSet) > 0 { + failF("%v: expected empty tag set instead got %v", testname, out.TagSet) + } + + err = teardown(s, bucket) + if err != nil { + failF("%v: %v", testname, err) + return + } + passF(testname) +} + // Full flow test func TestFullFlow(s *S3Conf) { // TODO: add more test cases to get 100% coverage @@ -1220,4 +1201,5 @@ func TestFullFlow(s *S3Conf) { TestListAbortMultiPartObject(s) TestRangeGet(s) TestInvalidMultiParts(s) + TestPutGetRemoveTags(s) } diff --git a/integration/utils.go b/integration/utils.go new file mode 100644 index 00000000..bae9094c --- /dev/null +++ b/integration/utils.go @@ -0,0 +1,138 @@ +package integration + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" +) + +func setup(s *S3Conf, bucket string) error { + s3client := s3.NewFromConfig(s.Config()) + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{ + Bucket: &bucket, + }) + cancel() + return err +} + +func teardown(s *S3Conf, bucket string) error { + s3client := s3.NewFromConfig(s.Config()) + + deleteObject := func(bucket, key, versionId *string) error { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ + Bucket: bucket, + Key: key, + VersionId: versionId, + }) + cancel() + if err != nil { + return fmt.Errorf("failed to delete object %v: %v", *key, err) + } + return nil + } + + in := &s3.ListObjectsV2Input{Bucket: &bucket} + for { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListObjectsV2(ctx, in) + cancel() + if err != nil { + return fmt.Errorf("failed to list objects: %v", err) + } + + for _, item := range out.Contents { + err = deleteObject(&bucket, item.Key, nil) + if err != nil { + return err + } + } + + if out.IsTruncated { + in.ContinuationToken = out.ContinuationToken + } else { + break + } + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{ + Bucket: &bucket, + }) + cancel() + return err +} + +func isEqual(a, b []byte) bool { + if len(a) != len(b) { + return false + } + + for i, d := range a { + if d != b[i] { + return false + } + } + + return true +} + +func contains(name string, list []types.Object) bool { + for _, item := range list { + fmt.Println(*item.Key) + if strings.EqualFold(name, *item.Key) { + return true + } + } + return false +} + +func containsUID(name, id string, list []types.MultipartUpload) bool { + for _, item := range list { + if strings.EqualFold(name, *item.Key) && strings.EqualFold(id, *item.UploadId) { + return true + } + } + return false +} + +func containsPart(part int32, list []types.Part) bool { + for _, item := range list { + if item.PartNumber == part { + return true + } + } + return false +} + +func isSame(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i, x := range a { + if x != b[i] { + return false + } + } + return true +} + +// 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 { + return false + } + } + return true +}