diff --git a/integration/s3gateway_test.sh b/integration/s3gateway_test.sh index b4c5b0a10aa8..514dff49c778 100755 --- a/integration/s3gateway_test.sh +++ b/integration/s3gateway_test.sh @@ -732,6 +732,89 @@ function test_multipart_upload() { fi fi + + for key in "afile" "bfile" "bfile" "documents/report1.pdf" "documents/report2.pdf" "ebook" "photos/2021/a2.png" "photos/2021/a3.png" "photos/2022/a4.png" + do + if [ $rv -eq 0 ]; then + # create multipart + function="${AWS} s3api create-multipart-upload --bucket ${bucket_name} --key ${key}" + test_function=${function} + out=$($function) + rv=$? + upload_id=$(echo "$out" | jq -r .UploadId) + fi + done + + if [ $rv -eq 0 ]; then + function="${AWS} s3api list-multipart-uploads --bucket ${bucket_name}" + test_function=${function} + out=$($function) + rv=$? + keys=$(echo "$out" | jq 'foreach .Uploads[] as $upload (null; . + $upload.Key)') + if [ $keys != "afilebfilebfiledocuments/report1.pdfdocuments/report2.pdfebookphotos/2021/a2.pngphotos/2021/a3.pngphotos/2022/a4.png" ]; then + rv=1 + out="list-multipart-uploads failed" + fi + fi + + if [ $rv -eq 0 ]; then + function="${AWS} s3api list-multipart-uploads --bucket ${bucket_name} --key-marker bfile" + test_function=${function} + out=$($function) + rv=$? + keys=$(echo "$out" | jq 'foreach .Uploads[] as $upload (null; . + $upload.Key)') + if [ $keys != "bfilebfiledocuments/report1.pdfdocuments/report2.pdfebookphotos/2021/a2.pngphotos/2021/a3.pngphotos/2022/a4.png" ]; then + rv=1 + out="list-multipart-upload failed" + fi + fi + + if [ $rv -eq 0 ]; then + function="${AWS} s3api list-multipart-uploads --bucket ${bucket_name} --key-marker bfile --delimiter /" + test_function=${function} + out=$($function) + rv=$? + keys=$(echo "$out" | jq 'foreach .Uploads[] as $upload (null; . + $upload.Key)') + if [ $keys != "afilebfilebfileebook" ]; then + rv=1 + out="list-multipart-uploads failed" + fi + keys=$(echo "$out" | jq 'foreach .CommonPrefixes[] as $CommonPrefix (null; . + $CommonPrefix.Prefix)') + if [ $keys != "documents/photos/" ]; then + rv=1 + out="list-multipart-uploads failed" + fi + fi + + if [ $rv -eq 0 ]; then + function="${AWS} s3api list-multipart-uploads --bucket ${bucket_name} --delimiter / --max-upload 5" + test_function=${function} + out=$($function) + rv=$? + keys=$(echo "$out" | jq 'foreach .Uploads[] as $upload (null; . + $upload.Key)') + if [ $keys != "afilebfilebfileebook" ]; then + rv=1 + out="list-multipart-uploads failed" + fi + keys=$(echo "$out" | jq -r '.CommonPrefixes[0].Prefix') + if [ $keys != "documents/" ]; then + rv=1 + out="list-multipart-uploads failed" + fi + fi + + if [ $rv -eq 0 ]; then + function="${AWS} s3api list-multipart-uploads --bucket ${bucket_name} --prefix documents/" + test_function=${function} + out=$($function) + rv=$? + keys=$(echo "$out" | jq 'foreach .Uploads[] as $upload (null; . + $upload.Key)') + if [ $keys != "documents/report1.pdfdocuments/report2.pdf" ]; then + rv=1 + out="list-multipart-upload failed" + fi + fi + if [ $rv -eq 0 ]; then function="delete_bucket" out=$(delete_bucket "$bucket_name") diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index 5cb4019bc2b9..5d9ff6f45e75 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -842,12 +842,19 @@ func (n *jfsObjects) ListMultipartUploads(ctx context.Context, bucket string, pr lmi.KeyMarker = keyMarker lmi.UploadIDMarker = uploadIDMarker lmi.MaxUploads = maxUploads + lmi.Delimiter = delimiter + commPrefixSet := make(map[string]struct{}) for _, e := range entries { uploadID := string(e.Name) - if uploadID > uploadIDMarker { - object_, _ := n.fs.GetXattr(mctx, n.upath(bucket, uploadID), uploadKeyName) - object := string(object_) - if strings.HasPrefix(object, prefix) && object > keyMarker { + // todo: parallel + object_, eno := n.fs.GetXattr(mctx, n.upath(bucket, uploadID), uploadKeyName) + if eno != 0 { + logger.Warnf("get object xattr error %s: %s, ignore this item", n.upath(bucket, uploadID), eno) + continue + } + object := string(object_) + if strings.HasPrefix(object, prefix) { + if keyMarker != "" && object+uploadID > keyMarker+uploadIDMarker || keyMarker == "" { lmi.Uploads = append(lmi.Uploads, minio.MultipartInfo{ Object: object, UploadID: uploadID, @@ -856,11 +863,48 @@ func (n *jfsObjects) ListMultipartUploads(ctx context.Context, bucket string, pr } } } - if len(lmi.Uploads) > maxUploads { + + sort.Slice(lmi.Uploads, func(i, j int) bool { + if lmi.Uploads[i].Object == lmi.Uploads[j].Object { + return lmi.Uploads[i].UploadID < lmi.Uploads[j].UploadID + } else { + return lmi.Uploads[i].Object < lmi.Uploads[j].Object + } + }) + + if delimiter != "" { + var tmp []minio.MultipartInfo + for _, info := range lmi.Uploads { + if maxUploads == 0 { + lmi.IsTruncated = true + break + } + index := strings.Index(strings.TrimLeft(info.Object, prefix), delimiter) + if index == -1 { + tmp = append(tmp, info) + maxUploads-- + } else { + commPrefix := info.Object[:index+1] + if _, ok := commPrefixSet[commPrefix]; ok { + continue + } + commPrefixSet[commPrefix] = struct{}{} + maxUploads-- + } + } + lmi.Uploads = tmp + for prefix := range commPrefixSet { + lmi.CommonPrefixes = append(lmi.CommonPrefixes, prefix) + } + sort.Strings(lmi.CommonPrefixes) + } else if len(lmi.Uploads) > maxUploads { lmi.IsTruncated = true lmi.Uploads = lmi.Uploads[:maxUploads] - lmi.NextKeyMarker = keyMarker - lmi.NextUploadIDMarker = lmi.Uploads[maxUploads-1].UploadID + } + + if len(lmi.Uploads) != 0 { + lmi.NextKeyMarker = lmi.Uploads[len(lmi.Uploads)-1].Object + lmi.NextUploadIDMarker = lmi.Uploads[len(lmi.Uploads)-1].UploadID } return lmi, jfsToObjectErr(ctx, err, bucket) }