minio/mint/build/versioning/list.go

671 lines
21 KiB
Go

/*
*
* Mint, (C) 2021 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package main
import (
"fmt"
"math/rand"
"reflect"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
)
// Test regular listing result with simple use cases:
// Upload an object ten times, delete it once (delete marker)
// and check listing result
func testListObjectVersionsSimple() {
startTime := time.Now()
function := "testListObjectVersionsSimple"
bucket := randString(60, rand.NewSource(time.Now().UnixNano()), "versioning-test-")
object := "testObject"
expiry := 1 * time.Minute
args := map[string]interface{}{
"bucketName": bucket,
"objectName": object,
"expiry": expiry,
}
_, err := s3Client.CreateBucket(&s3.CreateBucketInput{
Bucket: aws.String(bucket),
})
if err != nil {
failureLog(function, args, startTime, "", "CreateBucket failed", err).Fatal()
return
}
defer cleanupBucket(bucket, function, args, startTime)
putVersioningInput := &s3.PutBucketVersioningInput{
Bucket: aws.String(bucket),
VersioningConfiguration: &s3.VersioningConfiguration{
Status: aws.String("Enabled"),
},
}
_, err = s3Client.PutBucketVersioning(putVersioningInput)
if err != nil {
if strings.Contains(err.Error(), "NotImplemented: A header you provided implies functionality that is not implemented") {
ignoreLog(function, args, startTime, "Versioning is not implemented").Info()
return
}
failureLog(function, args, startTime, "", "Put versioning failed", err).Fatal()
return
}
for i := 0; i < 10; i++ {
putInput1 := &s3.PutObjectInput{
Body: aws.ReadSeekCloser(strings.NewReader("my content 1")),
Bucket: aws.String(bucket),
Key: aws.String(object),
}
_, err = s3Client.PutObject(putInput1)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("PUT expected to succeed but got %v", err), err).Fatal()
return
}
}
input := &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
}
deleteInput := &s3.DeleteObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(object),
}
_, err = s3Client.DeleteObject(deleteInput)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("Delete expected to succeed but got %v", err), err).Fatal()
return
}
// Accumulate all versions IDs
var versionIDs = make(map[string]struct{})
result, err := s3Client.ListObjectVersions(input)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
// Check the delete marker entries
if len(result.DeleteMarkers) != 1 {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected result", nil).Fatal()
return
}
dm := *result.DeleteMarkers[0]
if !*dm.IsLatest {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected result", nil).Fatal()
return
}
if *dm.Key != object {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected result", nil).Fatal()
return
}
if time.Since(*dm.LastModified) > time.Hour {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected result", nil).Fatal()
return
}
if *dm.VersionId == "" {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected result", nil).Fatal()
return
}
versionIDs[*dm.VersionId] = struct{}{}
// Check versions entries
if len(result.Versions) != 10 {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected result", nil).Fatal()
return
}
for _, version := range result.Versions {
v := *version
if *v.IsLatest {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected IsLatest field", nil).Fatal()
return
}
if *v.Key != object {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected Key field", nil).Fatal()
return
}
if time.Since(*v.LastModified) > time.Hour {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected LastModified field", nil).Fatal()
return
}
if *v.VersionId == "" {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected VersionId field", nil).Fatal()
return
}
if *v.ETag != "\"094459df8fcebffc70d9aa08d75f9944\"" {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected ETag field", nil).Fatal()
return
}
if *v.Size != 12 {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected Size field", nil).Fatal()
return
}
if *v.StorageClass != "STANDARD" {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected StorageClass field", nil).Fatal()
return
}
versionIDs[*v.VersionId] = struct{}{}
}
// Ensure that we have 11 distinct versions IDs
if len(versionIDs) != 11 {
failureLog(function, args, startTime, "", "ListObjectVersions didn't return 11 different version IDs", nil).Fatal()
return
}
successLogger(function, args, startTime).Info()
}
func testListObjectVersionsWithPrefixAndDelimiter() {
startTime := time.Now()
function := "testListObjectVersionsWithPrefixAndDelimiter"
bucket := randString(60, rand.NewSource(time.Now().UnixNano()), "versioning-test-")
object := "testObject"
expiry := 1 * time.Minute
args := map[string]interface{}{
"bucketName": bucket,
"objectName": object,
"expiry": expiry,
}
_, err := s3Client.CreateBucket(&s3.CreateBucketInput{
Bucket: aws.String(bucket),
})
if err != nil {
failureLog(function, args, startTime, "", "CreateBucket failed", err).Fatal()
return
}
defer cleanupBucket(bucket, function, args, startTime)
putVersioningInput := &s3.PutBucketVersioningInput{
Bucket: aws.String(bucket),
VersioningConfiguration: &s3.VersioningConfiguration{
Status: aws.String("Enabled"),
},
}
_, err = s3Client.PutBucketVersioning(putVersioningInput)
if err != nil {
if strings.Contains(err.Error(), "NotImplemented: A header you provided implies functionality that is not implemented") {
ignoreLog(function, args, startTime, "Versioning is not implemented").Info()
return
}
failureLog(function, args, startTime, "", "Put versioning failed", err).Fatal()
return
}
for _, objectName := range []string{"dir/object", "dir/dir/object", "object"} {
putInput := &s3.PutObjectInput{
Body: aws.ReadSeekCloser(strings.NewReader("my content 1")),
Bucket: aws.String(bucket),
Key: aws.String(objectName),
}
_, err = s3Client.PutObject(putInput)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("PUT expected to succeed but got %v", err), err).Fatal()
return
}
}
type objectResult struct {
name string
isLatest bool
}
type listResult struct {
versions []objectResult
commonPrefixes []string
}
simplifyListingResult := func(out *s3.ListObjectVersionsOutput) (result listResult) {
for _, commonPrefix := range out.CommonPrefixes {
result.commonPrefixes = append(result.commonPrefixes, *commonPrefix.Prefix)
}
for _, version := range out.Versions {
result.versions = append(result.versions, objectResult{name: *version.Key, isLatest: *version.IsLatest})
}
return
}
// Recursive listing
input := &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
}
result, err := s3Client.ListObjectVersions(input)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
gotResult := simplifyListingResult(result)
expectedResult := listResult{
versions: []objectResult{
objectResult{name: "dir/dir/object", isLatest: true},
objectResult{name: "dir/object", isLatest: true},
objectResult{name: "object", isLatest: true},
}}
if !reflect.DeepEqual(gotResult, expectedResult) {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected listing result", nil).Fatal()
return
}
// Listing with delimiter
input = &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
Delimiter: aws.String("/"),
}
result, err = s3Client.ListObjectVersions(input)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
gotResult = simplifyListingResult(result)
expectedResult = listResult{
versions: []objectResult{
objectResult{name: "object", isLatest: true},
},
commonPrefixes: []string{"dir/"}}
if !reflect.DeepEqual(gotResult, expectedResult) {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected listing result", nil).Fatal()
return
}
// Listing with prefix and delimiter
input = &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
Delimiter: aws.String("/"),
Prefix: aws.String("dir/"),
}
result, err = s3Client.ListObjectVersions(input)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
gotResult = simplifyListingResult(result)
expectedResult = listResult{
versions: []objectResult{
objectResult{name: "dir/object", isLatest: true},
},
commonPrefixes: []string{"dir/dir/"}}
if !reflect.DeepEqual(gotResult, expectedResult) {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected listing result", nil).Fatal()
return
}
successLogger(function, args, startTime).Info()
}
// Test if key marker continuation works in listing works well
func testListObjectVersionsKeysContinuation() {
startTime := time.Now()
function := "testListObjectKeysContinuation"
bucket := randString(60, rand.NewSource(time.Now().UnixNano()), "versioning-test-")
object := "testObject"
expiry := 1 * time.Minute
args := map[string]interface{}{
"bucketName": bucket,
"objectName": object,
"expiry": expiry,
}
_, err := s3Client.CreateBucket(&s3.CreateBucketInput{
Bucket: aws.String(bucket),
})
if err != nil {
failureLog(function, args, startTime, "", "CreateBucket failed", err).Fatal()
return
}
defer cleanupBucket(bucket, function, args, startTime)
putVersioningInput := &s3.PutBucketVersioningInput{
Bucket: aws.String(bucket),
VersioningConfiguration: &s3.VersioningConfiguration{
Status: aws.String("Enabled"),
},
}
_, err = s3Client.PutBucketVersioning(putVersioningInput)
if err != nil {
if strings.Contains(err.Error(), "NotImplemented: A header you provided implies functionality that is not implemented") {
ignoreLog(function, args, startTime, "Versioning is not implemented").Info()
return
}
failureLog(function, args, startTime, "", "Put versioning failed", err).Fatal()
return
}
for i := 0; i < 10; i++ {
putInput1 := &s3.PutObjectInput{
Body: aws.ReadSeekCloser(strings.NewReader("my content 1")),
Bucket: aws.String(bucket),
Key: aws.String(fmt.Sprintf("testobject-%d", i)),
}
_, err = s3Client.PutObject(putInput1)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("PUT expected to succeed but got %v", err), err).Fatal()
return
}
}
input := &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
MaxKeys: aws.Int64(5),
}
type resultPage struct {
versions []string
nextKeyMarker string
lastPage bool
}
var gotResult []resultPage
var numPages int
err = s3Client.ListObjectVersionsPages(input,
func(page *s3.ListObjectVersionsOutput, lastPage bool) bool {
numPages++
resultPage := resultPage{lastPage: lastPage}
if page.NextKeyMarker != nil {
resultPage.nextKeyMarker = *page.NextKeyMarker
}
for _, v := range page.Versions {
resultPage.versions = append(resultPage.versions, *v.Key)
}
gotResult = append(gotResult, resultPage)
return true
})
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
if numPages != 2 {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected number of pages", nil).Fatal()
return
}
expectedResult := []resultPage{
resultPage{versions: []string{"testobject-0", "testobject-1", "testobject-2", "testobject-3", "testobject-4"}, nextKeyMarker: "testobject-4", lastPage: false},
resultPage{versions: []string{"testobject-5", "testobject-6", "testobject-7", "testobject-8", "testobject-9"}, nextKeyMarker: "", lastPage: true},
}
if !reflect.DeepEqual(expectedResult, gotResult) {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected listing result", nil).Fatal()
return
}
successLogger(function, args, startTime).Info()
}
// Test if version id marker continuation works in listing works well
func testListObjectVersionsVersionIDContinuation() {
startTime := time.Now()
function := "testListObjectVersionIDContinuation"
bucket := randString(60, rand.NewSource(time.Now().UnixNano()), "versioning-test-")
object := "testObject"
expiry := 1 * time.Minute
args := map[string]interface{}{
"bucketName": bucket,
"objectName": object,
"expiry": expiry,
}
_, err := s3Client.CreateBucket(&s3.CreateBucketInput{
Bucket: aws.String(bucket),
})
if err != nil {
failureLog(function, args, startTime, "", "CreateBucket failed", err).Fatal()
return
}
defer cleanupBucket(bucket, function, args, startTime)
putVersioningInput := &s3.PutBucketVersioningInput{
Bucket: aws.String(bucket),
VersioningConfiguration: &s3.VersioningConfiguration{
Status: aws.String("Enabled"),
},
}
_, err = s3Client.PutBucketVersioning(putVersioningInput)
if err != nil {
if strings.Contains(err.Error(), "NotImplemented: A header you provided implies functionality that is not implemented") {
ignoreLog(function, args, startTime, "Versioning is not implemented").Info()
return
}
failureLog(function, args, startTime, "", "Put versioning failed", err).Fatal()
return
}
for i := 0; i < 10; i++ {
putInput1 := &s3.PutObjectInput{
Body: aws.ReadSeekCloser(strings.NewReader("my content 1")),
Bucket: aws.String(bucket),
Key: aws.String("testobject"),
}
_, err = s3Client.PutObject(putInput1)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("PUT expected to succeed but got %v", err), err).Fatal()
return
}
}
input := &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
MaxKeys: aws.Int64(5),
}
type resultPage struct {
versions []string
nextVersionIDMarker string
lastPage bool
}
var gotResult []resultPage
var gotNextVersionIDMarker string
var numPages int
err = s3Client.ListObjectVersionsPages(input,
func(page *s3.ListObjectVersionsOutput, lastPage bool) bool {
numPages++
resultPage := resultPage{lastPage: lastPage}
if page.NextVersionIdMarker != nil {
resultPage.nextVersionIDMarker = *page.NextVersionIdMarker
}
for _, v := range page.Versions {
resultPage.versions = append(resultPage.versions, *v.Key)
}
if !lastPage {
// There is only two pages, so here we are saving the version id
// of the last element in the first page of listing
gotNextVersionIDMarker = *(*page.Versions[len(page.Versions)-1]).VersionId
}
gotResult = append(gotResult, resultPage)
return true
})
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
if numPages != 2 {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected number of pages", nil).Fatal()
return
}
expectedResult := []resultPage{
resultPage{versions: []string{"testobject", "testobject", "testobject", "testobject", "testobject"}, nextVersionIDMarker: gotNextVersionIDMarker, lastPage: false},
resultPage{versions: []string{"testobject", "testobject", "testobject", "testobject", "testobject"}, lastPage: true},
}
if !reflect.DeepEqual(expectedResult, gotResult) {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected listing result", nil).Fatal()
return
}
successLogger(function, args, startTime).Info()
}
// Test listing object when there is some empty directory object
func testListObjectsVersionsWithEmptyDirObject() {
startTime := time.Now()
function := "testListObjectsVersionsWithEmptyDirObject"
bucket := randString(60, rand.NewSource(time.Now().UnixNano()), "versioning-test-")
object := "testObject"
expiry := 1 * time.Minute
args := map[string]interface{}{
"bucketName": bucket,
"objectName": object,
"expiry": expiry,
}
_, err := s3Client.CreateBucket(&s3.CreateBucketInput{
Bucket: aws.String(bucket),
})
if err != nil {
failureLog(function, args, startTime, "", "CreateBucket failed", err).Fatal()
return
}
defer cleanupBucket(bucket, function, args, startTime)
putVersioningInput := &s3.PutBucketVersioningInput{
Bucket: aws.String(bucket),
VersioningConfiguration: &s3.VersioningConfiguration{
Status: aws.String("Enabled"),
},
}
_, err = s3Client.PutBucketVersioning(putVersioningInput)
if err != nil {
if strings.Contains(err.Error(), "NotImplemented: A header you provided implies functionality that is not implemented") {
ignoreLog(function, args, startTime, "Versioning is not implemented").Info()
return
}
failureLog(function, args, startTime, "", "Put versioning failed", err).Fatal()
return
}
for _, objectName := range []string{"dir/object", "dir/"} {
putInput := &s3.PutObjectInput{
Body: aws.ReadSeekCloser(strings.NewReader("")),
Bucket: aws.String(bucket),
Key: aws.String(objectName),
}
_, err = s3Client.PutObject(putInput)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("PUT expected to succeed but got %v", err), err).Fatal()
return
}
}
type objectResult struct {
name string
etag string
isLatest bool
}
type listResult struct {
versions []objectResult
commonPrefixes []string
}
simplifyListingResult := func(out *s3.ListObjectVersionsOutput) (result listResult) {
for _, commonPrefix := range out.CommonPrefixes {
result.commonPrefixes = append(result.commonPrefixes, *commonPrefix.Prefix)
}
for _, version := range out.Versions {
result.versions = append(result.versions, objectResult{
name: *version.Key,
etag: *version.ETag,
isLatest: *version.IsLatest,
})
}
return
}
// Recursive listing
input := &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
}
result, err := s3Client.ListObjectVersions(input)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
gotResult := simplifyListingResult(result)
expectedResult := listResult{
versions: []objectResult{
objectResult{name: "dir/", etag: "\"d41d8cd98f00b204e9800998ecf8427e\"", isLatest: true},
objectResult{name: "dir/object", etag: "\"d41d8cd98f00b204e9800998ecf8427e\"", isLatest: true},
}}
if !reflect.DeepEqual(gotResult, expectedResult) {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected listing result", nil).Fatal()
return
}
// Listing with delimiter
input = &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
Delimiter: aws.String("/"),
}
result, err = s3Client.ListObjectVersions(input)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
gotResult = simplifyListingResult(result)
expectedResult = listResult{
commonPrefixes: []string{"dir/"}}
if !reflect.DeepEqual(gotResult, expectedResult) {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected listing result", nil).Fatal()
return
}
// Listing with prefix and delimiter
input = &s3.ListObjectVersionsInput{
Bucket: aws.String(bucket),
Delimiter: aws.String("/"),
Prefix: aws.String("dir/"),
}
result, err = s3Client.ListObjectVersions(input)
if err != nil {
failureLog(function, args, startTime, "", fmt.Sprintf("ListObjectVersions expected to succeed but got %v", err), err).Fatal()
return
}
gotResult = simplifyListingResult(result)
expectedResult = listResult{
versions: []objectResult{
{name: "dir/", etag: "\"d41d8cd98f00b204e9800998ecf8427e\"", isLatest: true},
{name: "dir/object", etag: "\"d41d8cd98f00b204e9800998ecf8427e\"", isLatest: true},
},
}
if !reflect.DeepEqual(gotResult, expectedResult) {
failureLog(function, args, startTime, "", "ListObjectVersions returned unexpected listing result", nil).Fatal()
return
}
successLogger(function, args, startTime).Info()
}