mirror of
https://github.com/pgsty/minio.git
synced 2026-10-18 07:26:10 +03:00
4733e1423c
Signed-off-by: Feng Ruohang <rh@vonng.com>
107 lines
3.3 KiB
Go
107 lines
3.3 KiB
Go
// Copyright (c) 2026 Ruohang Feng
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync/atomic"
|
|
"testing"
|
|
)
|
|
|
|
type multipartRootScanGuard struct {
|
|
StorageAPI
|
|
roots *atomic.Int64
|
|
}
|
|
|
|
func (d multipartRootScanGuard) ListDir(ctx context.Context, original, volume, dir string, count int) ([]string, error) {
|
|
if volume == minioMetaMultipartBucket && dir == "" {
|
|
d.roots.Add(1)
|
|
return nil, errFaultyDisk
|
|
}
|
|
return d.StorageAPI.ListDir(ctx, original, volume, dir, count)
|
|
}
|
|
|
|
// A busy or unreadable diagnostic namespace must not become a prerequisite for
|
|
// ordinary listing, including the lower-level exact-object entry points.
|
|
func TestMultipartPublicListingIndependentOfDiagnostics(t *testing.T) {
|
|
for _, busy := range []bool{false, true} {
|
|
t.Run(map[bool]string{false: "unreadable-root", true: "busy-diagnostics"}[busy], func(t *testing.T) {
|
|
z, _ := consistencyPools(t)
|
|
bucket, router, err := initAPIHandlerTest(t.Context(), z, []string{"ListMultipartUploads"}, MakeBucketOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const key = "dir/known"
|
|
mp, err := z.NewMultipartUpload(t.Context(), bucket, key, ObjectOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var roots atomic.Int64
|
|
for _, pool := range z.serverPools {
|
|
for _, set := range pool.sets {
|
|
original := set.getDisks
|
|
set.getDisks = func() []StorageAPI {
|
|
disks := append([]StorageAPI(nil), original()...)
|
|
for i, disk := range disks {
|
|
if disk != nil {
|
|
disks[i] = multipartRootScanGuard{disk, &roots}
|
|
}
|
|
}
|
|
return disks
|
|
}
|
|
t.Cleanup(func() { set.getDisks = original })
|
|
}
|
|
}
|
|
if busy {
|
|
for range cap(multipartScanSlots) {
|
|
scan, err := startMultipartScan(t.Context(), true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer scan.close()
|
|
}
|
|
}
|
|
for _, prefix := range []string{"", key, "dir/"} {
|
|
req, err := newTestSignedRequestV4(http.MethodGet,
|
|
getListMultipartUploadsURLWithParams("", bucket, prefix, "", "", "", "1"),
|
|
0, nil, globalActiveCred.AccessKey, globalActiveCred.SecretKey, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
router.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("prefix %q: HTTP %d %s", prefix, rec.Code, rec.Body.String())
|
|
}
|
|
var got ListMultipartUploadsResponse
|
|
if err := xml.Unmarshal(rec.Body.Bytes(), &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if prefix == "dir/" {
|
|
if len(got.Uploads) != 0 {
|
|
t.Fatalf("partial prefix must retain the documented exact-key limitation: %+v", got)
|
|
}
|
|
} else if len(got.Uploads) != 1 || got.Uploads[0].UploadID != mp.UploadID {
|
|
t.Fatalf("known upload missing: %+v", got)
|
|
}
|
|
}
|
|
for _, pool := range z.serverPools {
|
|
for _, list := range []func(context.Context, string, string, string, string, string, int) (ListMultipartsInfo, error){
|
|
pool.ListMultipartUploads, pool.getHashedSet(key).ListMultipartUploads,
|
|
} {
|
|
if _, err := list(t.Context(), bucket, key, "", "", "", 10); err != nil {
|
|
t.Fatalf("lower-level exact listing: %v", err)
|
|
}
|
|
}
|
|
}
|
|
if roots.Load() != 0 {
|
|
t.Fatalf("public listing attempted %d global multipart scans", roots.Load())
|
|
}
|
|
})
|
|
}
|
|
}
|