do not list buckets without local quorum (#20852)

ListBuckets() would result in listing buckets
without quorum, this PR fixes the behavior.
This commit is contained in:
Harshavardhana 2025-01-19 15:13:17 -08:00 committed by GitHub
parent 3d0f513ee2
commit 779ec8f0d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 54 additions and 34 deletions

View File

@ -32,6 +32,7 @@ import (
"github.com/minio/minio/internal/grid" "github.com/minio/minio/internal/grid"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v3/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
"github.com/puzpuzpuz/xsync/v3"
) )
//go:generate stringer -type=healingMetric -trimprefix=healingMetric $GOFILE //go:generate stringer -type=healingMetric -trimprefix=healingMetric $GOFILE
@ -117,9 +118,8 @@ func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string,
// listAllBuckets lists all buckets from all disks. It also // listAllBuckets lists all buckets from all disks. It also
// returns the occurrence of each buckets in all disks // returns the occurrence of each buckets in all disks
func listAllBuckets(ctx context.Context, storageDisks []StorageAPI, healBuckets map[string]VolInfo, readQuorum int) error { func listAllBuckets(ctx context.Context, storageDisks []StorageAPI, healBuckets *xsync.MapOf[string, VolInfo], readQuorum int) error {
g := errgroup.WithNErrs(len(storageDisks)) g := errgroup.WithNErrs(len(storageDisks))
var mu sync.Mutex
for index := range storageDisks { for index := range storageDisks {
index := index index := index
g.Go(func() error { g.Go(func() error {
@ -131,6 +131,7 @@ func listAllBuckets(ctx context.Context, storageDisks []StorageAPI, healBuckets
if err != nil { if err != nil {
return err return err
} }
for _, volInfo := range volsInfo { for _, volInfo := range volsInfo {
// StorageAPI can send volume names which are // StorageAPI can send volume names which are
// incompatible with buckets - these are // incompatible with buckets - these are
@ -138,25 +139,44 @@ func listAllBuckets(ctx context.Context, storageDisks []StorageAPI, healBuckets
if isReservedOrInvalidBucket(volInfo.Name, false) { if isReservedOrInvalidBucket(volInfo.Name, false) {
continue continue
} }
mu.Lock()
if _, ok := healBuckets[volInfo.Name]; !ok { healBuckets.Compute(volInfo.Name, func(oldValue VolInfo, loaded bool) (newValue VolInfo, del bool) {
healBuckets[volInfo.Name] = volInfo if loaded {
} newValue = oldValue
mu.Unlock() newValue.count = oldValue.count + 1
return newValue, false
}
return VolInfo{
Name: volInfo.Name,
Created: volInfo.Created,
count: 1,
}, false
})
} }
return nil return nil
}, index) }, index)
} }
return reduceReadQuorumErrs(ctx, g.Wait(), bucketMetadataOpIgnoredErrs, readQuorum)
if err := reduceReadQuorumErrs(ctx, g.Wait(), bucketMetadataOpIgnoredErrs, readQuorum); err != nil {
return err
}
healBuckets.Range(func(volName string, volInfo VolInfo) bool {
if volInfo.count < readQuorum {
healBuckets.Delete(volName)
}
return true
})
return nil
} }
var errLegacyXLMeta = errors.New("legacy XL meta")
var errOutdatedXLMeta = errors.New("outdated XL meta")
var ( var (
errPartCorrupt = errors.New("part corrupt") errLegacyXLMeta = errors.New("legacy XL meta")
errPartMissing = errors.New("part missing") errOutdatedXLMeta = errors.New("outdated XL meta")
errPartCorrupt = errors.New("part corrupt")
errPartMissing = errors.New("part missing")
) )
// Only heal on disks where we are sure that healing is needed. We can expand // Only heal on disks where we are sure that healing is needed. We can expand

View File

@ -39,6 +39,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v3/console" "github.com/minio/pkg/v3/console"
"github.com/minio/pkg/v3/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
"github.com/puzpuzpuz/xsync/v3"
) )
// setsDsyncLockers is encapsulated type for Close() // setsDsyncLockers is encapsulated type for Close()
@ -701,9 +702,8 @@ func (s *erasureSets) getHashedSet(input string) (set *erasureObjects) {
} }
// listDeletedBuckets lists deleted buckets from all disks. // listDeletedBuckets lists deleted buckets from all disks.
func listDeletedBuckets(ctx context.Context, storageDisks []StorageAPI, delBuckets map[string]VolInfo, readQuorum int) error { func listDeletedBuckets(ctx context.Context, storageDisks []StorageAPI, delBuckets *xsync.MapOf[string, VolInfo], readQuorum int) error {
g := errgroup.WithNErrs(len(storageDisks)) g := errgroup.WithNErrs(len(storageDisks))
var mu sync.Mutex
for index := range storageDisks { for index := range storageDisks {
index := index index := index
g.Go(func() error { g.Go(func() error {
@ -722,11 +722,7 @@ func listDeletedBuckets(ctx context.Context, storageDisks []StorageAPI, delBucke
vi, err := storageDisks[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, volName)) vi, err := storageDisks[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, volName))
if err == nil { if err == nil {
vi.Name = strings.TrimSuffix(volName, SlashSeparator) vi.Name = strings.TrimSuffix(volName, SlashSeparator)
mu.Lock() delBuckets.Store(volName, vi)
if _, ok := delBuckets[volName]; !ok {
delBuckets[volName] = vi
}
mu.Unlock()
} }
} }
return nil return nil

View File

@ -23,6 +23,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/pkg/v3/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
"github.com/puzpuzpuz/xsync/v3"
) )
const ( const (
@ -164,7 +165,7 @@ func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []Bucket
quorum := (len(localDrives) / 2) quorum := (len(localDrives) / 2)
buckets = make([]BucketInfo, 0, 32) buckets = make([]BucketInfo, 0, 32)
healBuckets := map[string]VolInfo{} healBuckets := xsync.NewMapOf[string, VolInfo]()
// lists all unique buckets across drives. // lists all unique buckets across drives.
if err := listAllBuckets(ctx, localDrives, healBuckets, quorum); err != nil { if err := listAllBuckets(ctx, localDrives, healBuckets, quorum); err != nil {
@ -172,7 +173,7 @@ func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []Bucket
} }
// include deleted buckets in listBuckets output // include deleted buckets in listBuckets output
deletedBuckets := map[string]VolInfo{} deletedBuckets := xsync.NewMapOf[string, VolInfo]()
if opts.Deleted { if opts.Deleted {
// lists all deleted buckets across drives. // lists all deleted buckets across drives.
@ -181,25 +182,27 @@ func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []Bucket
} }
} }
for _, v := range healBuckets { healBuckets.Range(func(_ string, volInfo VolInfo) bool {
bi := BucketInfo{ bi := BucketInfo{
Name: v.Name, Name: volInfo.Name,
Created: v.Created, Created: volInfo.Created,
} }
if vi, ok := deletedBuckets[v.Name]; ok { if vi, ok := deletedBuckets.Load(volInfo.Name); ok {
bi.Deleted = vi.Created bi.Deleted = vi.Created
} }
buckets = append(buckets, bi) buckets = append(buckets, bi)
} return true
})
for _, v := range deletedBuckets { deletedBuckets.Range(func(_ string, v VolInfo) bool {
if _, ok := healBuckets[v.Name]; !ok { if _, ok := healBuckets.Load(v.Name); !ok {
buckets = append(buckets, BucketInfo{ buckets = append(buckets, BucketInfo{
Name: v.Name, Name: v.Name,
Deleted: v.Created, Deleted: v.Created,
}) })
} }
} return true
})
return buckets, nil return buckets, nil
} }

View File

@ -101,8 +101,6 @@ type VolsInfo []VolInfo
// VolInfo - represents volume stat information. // VolInfo - represents volume stat information.
// The above means that any added/deleted fields are incompatible. // The above means that any added/deleted fields are incompatible.
// //
// The above means that any added/deleted fields are incompatible.
//
//msgp:tuple VolInfo //msgp:tuple VolInfo
type VolInfo struct { type VolInfo struct {
// Name of the volume. // Name of the volume.
@ -110,6 +108,9 @@ type VolInfo struct {
// Date and time when the volume was created. // Date and time when the volume was created.
Created time.Time Created time.Time
// total VolInfo counts
count int
} }
// FilesInfo represent a list of files, additionally // FilesInfo represent a list of files, additionally

View File

@ -759,7 +759,7 @@ func TestXLStorageListVols(t *testing.T) {
if volInfos, err = xlStorage.ListVols(context.Background()); err != nil { if volInfos, err = xlStorage.ListVols(context.Background()); err != nil {
t.Fatalf("expected: <nil>, got: %s", err) t.Fatalf("expected: <nil>, got: %s", err)
} else if len(volInfos) != 1 { } else if len(volInfos) != 1 {
t.Fatalf("expected: one entry, got: %s", volInfos) t.Fatalf("expected: one entry, got: %v", volInfos)
} }
// TestXLStorage non-empty list vols. // TestXLStorage non-empty list vols.