mirror of
https://github.com/minio/minio.git
synced 2025-02-03 01:46:00 -05:00
Revert "Heal buckets at node level (#18504)"
This reverts commit 708296ae1b671efd9531729b741c96b9aaa95801.
This commit is contained in:
parent
8fc200c0cc
commit
e30c0e7ca3
@ -128,6 +128,159 @@ func (er erasureObjects) listAndHeal(bucket, prefix string, healEntry func(strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HealBucket heals a bucket if it doesn't exist on one of the disks, additionally
|
||||||
|
// also heals the missing entries for bucket metadata files
|
||||||
|
// `policy.json, notification.xml, listeners.json`.
|
||||||
|
func (er erasureObjects) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
||||||
|
result madmin.HealResultItem, err error,
|
||||||
|
) {
|
||||||
|
storageDisks := er.getDisks()
|
||||||
|
storageEndpoints := er.getEndpoints()
|
||||||
|
|
||||||
|
// Heal bucket.
|
||||||
|
return er.healBucket(ctx, storageDisks, storageEndpoints, bucket, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heal bucket - create buckets on disks where it does not exist.
|
||||||
|
func (er erasureObjects) healBucket(ctx context.Context, storageDisks []StorageAPI, storageEndpoints []Endpoint, bucket string, opts madmin.HealOpts) (res madmin.HealResultItem, err error) {
|
||||||
|
// get write quorum for an object
|
||||||
|
writeQuorum := len(storageDisks) - er.defaultParityCount
|
||||||
|
if writeQuorum == er.defaultParityCount {
|
||||||
|
writeQuorum++
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalTrace.NumSubscribers(madmin.TraceHealing) > 0 {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
healTrace(healingMetricBucket, startTime, bucket, "", &opts, err, &res)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize sync waitgroup.
|
||||||
|
g := errgroup.WithNErrs(len(storageDisks))
|
||||||
|
|
||||||
|
// Disk states slices
|
||||||
|
beforeState := make([]string, len(storageDisks))
|
||||||
|
afterState := make([]string, len(storageDisks))
|
||||||
|
|
||||||
|
// Make a volume entry on all underlying storage disks.
|
||||||
|
for index := range storageDisks {
|
||||||
|
index := index
|
||||||
|
g.Go(func() error {
|
||||||
|
if storageDisks[index] == nil {
|
||||||
|
beforeState[index] = madmin.DriveStateOffline
|
||||||
|
afterState[index] = madmin.DriveStateOffline
|
||||||
|
return errDiskNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeState[index] = madmin.DriveStateOk
|
||||||
|
afterState[index] = madmin.DriveStateOk
|
||||||
|
|
||||||
|
if bucket == minioReservedBucket {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, serr := storageDisks[index].StatVol(ctx, bucket); serr != nil {
|
||||||
|
if serr == errDiskNotFound {
|
||||||
|
beforeState[index] = madmin.DriveStateOffline
|
||||||
|
afterState[index] = madmin.DriveStateOffline
|
||||||
|
return serr
|
||||||
|
}
|
||||||
|
if serr != errVolumeNotFound {
|
||||||
|
beforeState[index] = madmin.DriveStateCorrupt
|
||||||
|
afterState[index] = madmin.DriveStateCorrupt
|
||||||
|
return serr
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeState[index] = madmin.DriveStateMissing
|
||||||
|
afterState[index] = madmin.DriveStateMissing
|
||||||
|
|
||||||
|
// mutate only if not a dry-run
|
||||||
|
if opts.DryRun {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return serr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := g.Wait()
|
||||||
|
|
||||||
|
// Initialize heal result info
|
||||||
|
res = madmin.HealResultItem{
|
||||||
|
Type: madmin.HealItemBucket,
|
||||||
|
Bucket: bucket,
|
||||||
|
DiskCount: len(storageDisks),
|
||||||
|
ParityBlocks: er.defaultParityCount,
|
||||||
|
DataBlocks: len(storageDisks) - er.defaultParityCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range beforeState {
|
||||||
|
res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{
|
||||||
|
UUID: "",
|
||||||
|
Endpoint: storageEndpoints[i].String(),
|
||||||
|
State: beforeState[i],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
reducedErr := reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, res.DataBlocks)
|
||||||
|
if errors.Is(reducedErr, errVolumeNotFound) && !opts.Recreate {
|
||||||
|
for i := range beforeState {
|
||||||
|
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
|
||||||
|
UUID: "",
|
||||||
|
Endpoint: storageEndpoints[i].String(),
|
||||||
|
State: madmin.DriveStateOk,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize sync waitgroup.
|
||||||
|
g = errgroup.WithNErrs(len(storageDisks))
|
||||||
|
|
||||||
|
// Make a volume entry on all underlying storage disks.
|
||||||
|
for index := range storageDisks {
|
||||||
|
index := index
|
||||||
|
g.Go(func() error {
|
||||||
|
if beforeState[index] == madmin.DriveStateMissing {
|
||||||
|
makeErr := storageDisks[index].MakeVol(ctx, bucket)
|
||||||
|
if makeErr == nil {
|
||||||
|
afterState[index] = madmin.DriveStateOk
|
||||||
|
}
|
||||||
|
return makeErr
|
||||||
|
}
|
||||||
|
return errs[index]
|
||||||
|
}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
errs = g.Wait()
|
||||||
|
|
||||||
|
reducedErr = reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, writeQuorum)
|
||||||
|
if reducedErr != nil {
|
||||||
|
// If we have exactly half the drives not available,
|
||||||
|
// we should still allow HealBucket to not return error.
|
||||||
|
// this is necessary for starting the server.
|
||||||
|
readQuorum := res.DataBlocks
|
||||||
|
switch reduceReadQuorumErrs(ctx, errs, nil, readQuorum) {
|
||||||
|
case nil:
|
||||||
|
case errDiskNotFound:
|
||||||
|
default:
|
||||||
|
return res, reducedErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range afterState {
|
||||||
|
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
|
||||||
|
UUID: "",
|
||||||
|
Endpoint: storageEndpoints[i].String(),
|
||||||
|
State: afterState[i],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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 map[string]VolInfo, readQuorum int) error {
|
||||||
@ -867,22 +1020,6 @@ func isAllNotFound(errs []error) bool {
|
|||||||
return len(errs) > 0
|
return len(errs) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAllBucketsNotFound will return true if all the errors are either errFileNotFound
|
|
||||||
// or errFileCorrupt
|
|
||||||
// A 0 length slice will always return false.
|
|
||||||
func isAllBucketsNotFound(errs []error) bool {
|
|
||||||
if len(errs) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
notFoundCount := 0
|
|
||||||
for _, err := range errs {
|
|
||||||
if err != nil && errors.Is(err, errVolumeNotFound) {
|
|
||||||
notFoundCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(errs) == notFoundCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObjectDir is considered dangling/corrupted if any only
|
// ObjectDir is considered dangling/corrupted if any only
|
||||||
// if total disks - a combination of corrupted and missing
|
// if total disks - a combination of corrupted and missing
|
||||||
// files is lesser than N/2+1 number of disks.
|
// files is lesser than N/2+1 number of disks.
|
||||||
@ -908,7 +1045,7 @@ func isObjectDirDangling(errs []error) (ok bool) {
|
|||||||
return found < notFound && found > 0
|
return found < notFound && found > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object is considered dangling/corrupted if and only
|
// Object is considered dangling/corrupted if any only
|
||||||
// if total disks - a combination of corrupted and missing
|
// if total disks - a combination of corrupted and missing
|
||||||
// files is lesser than number of data blocks.
|
// files is lesser than number of data blocks.
|
||||||
func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (validMeta FileInfo, ok bool) {
|
func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (validMeta FileInfo, ok bool) {
|
||||||
|
@ -362,7 +362,7 @@ func TestHealing(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// This would create the bucket.
|
// This would create the bucket.
|
||||||
_, err = obj.HealBucket(ctx, bucket, madmin.HealOpts{
|
_, err = er.HealBucket(ctx, bucket, madmin.HealOpts{
|
||||||
DryRun: false,
|
DryRun: false,
|
||||||
Remove: false,
|
Remove: false,
|
||||||
})
|
})
|
||||||
@ -543,7 +543,7 @@ func TestHealingVersioned(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// This would create the bucket.
|
// This would create the bucket.
|
||||||
_, err = obj.HealBucket(ctx, bucket, madmin.HealOpts{
|
_, err = er.HealBucket(ctx, bucket, madmin.HealOpts{
|
||||||
DryRun: false,
|
DryRun: false,
|
||||||
Remove: false,
|
Remove: false,
|
||||||
})
|
})
|
||||||
|
@ -1923,55 +1923,8 @@ func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts
|
|||||||
hopts.Recreate = false
|
hopts.Recreate = false
|
||||||
defer z.HealObject(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, bucket, bucketMetadataFile), "", hopts)
|
defer z.HealObject(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, bucket, bucketMetadataFile), "", hopts)
|
||||||
|
|
||||||
type DiskStat struct {
|
|
||||||
VolInfos []VolInfo
|
|
||||||
Errs []error
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pool := range z.serverPools {
|
for _, pool := range z.serverPools {
|
||||||
// map of node wise disk stats
|
result, err := pool.HealBucket(ctx, bucket, opts)
|
||||||
diskStats := make(map[string]DiskStat)
|
|
||||||
for _, set := range pool.sets {
|
|
||||||
vis := []VolInfo{}
|
|
||||||
errs := []error{}
|
|
||||||
for _, disk := range set.getDisks() {
|
|
||||||
if disk == OfflineDisk {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
vi, err := disk.StatVol(ctx, bucket)
|
|
||||||
hostName := disk.Hostname()
|
|
||||||
if disk.IsLocal() {
|
|
||||||
hostName = "local"
|
|
||||||
}
|
|
||||||
ds, ok := diskStats[hostName]
|
|
||||||
if !ok {
|
|
||||||
newds := DiskStat{
|
|
||||||
VolInfos: vis,
|
|
||||||
Errs: errs,
|
|
||||||
}
|
|
||||||
diskStats[hostName] = newds
|
|
||||||
} else {
|
|
||||||
ds.VolInfos = append(ds.VolInfos, vi)
|
|
||||||
ds.Errs = append(ds.Errs, err)
|
|
||||||
diskStats[hostName] = ds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nodeCount := len(diskStats)
|
|
||||||
bktNotFoundCount := 0
|
|
||||||
for _, ds := range diskStats {
|
|
||||||
if isAllBucketsNotFound(ds.Errs) {
|
|
||||||
bktNotFoundCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if the bucket if not found on more than hslf the no of nodes, its dagling
|
|
||||||
if bktNotFoundCount > nodeCount/2 {
|
|
||||||
opts.Remove = true
|
|
||||||
} else {
|
|
||||||
opts.Recreate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := z.s3Peer.HealBucket(ctx, bucket, opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(BucketNotFound); ok {
|
if _, ok := err.(BucketNotFound); ok {
|
||||||
continue
|
continue
|
||||||
|
@ -1168,6 +1168,28 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HealBucket - heals inconsistent buckets and bucket metadata on all sets.
|
||||||
|
func (s *erasureSets) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
|
||||||
|
// Initialize heal result info
|
||||||
|
result = madmin.HealResultItem{
|
||||||
|
Type: madmin.HealItemBucket,
|
||||||
|
Bucket: bucket,
|
||||||
|
DiskCount: s.setCount * s.setDriveCount,
|
||||||
|
SetCount: s.setCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, set := range s.sets {
|
||||||
|
healResult, err := set.HealBucket(ctx, bucket, opts)
|
||||||
|
if err != nil {
|
||||||
|
return result, toObjectErr(err, bucket)
|
||||||
|
}
|
||||||
|
result.Before.Drives = append(result.Before.Drives, healResult.Before.Drives...)
|
||||||
|
result.After.Drives = append(result.After.Drives, healResult.After.Drives...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// HealObject - heals inconsistent object on a hashedSet based on object name.
|
// HealObject - heals inconsistent object on a hashedSet based on object name.
|
||||||
func (s *erasureSets) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
func (s *erasureSets) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||||
return s.getHashedSet(object).HealObject(ctx, bucket, object, versionID, opts)
|
return s.getHashedSet(object).HealObject(ctx, bucket, object, versionID, opts)
|
||||||
|
@ -143,13 +143,10 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
healBuckets := make([]string, len(buckets))
|
healBuckets := make([]string, len(buckets))
|
||||||
copy(healBuckets, buckets)
|
copy(healBuckets, buckets)
|
||||||
|
|
||||||
objAPI := newObjectLayerFn()
|
// Heal all buckets first in this erasure set - this is useful
|
||||||
if objAPI == nil {
|
// for new objects upload in different buckets to be successful
|
||||||
return errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, bucket := range healBuckets {
|
for _, bucket := range healBuckets {
|
||||||
_, err := objAPI.HealBucket(ctx, bucket, madmin.HealOpts{ScanMode: scanMode})
|
_, err := er.HealBucket(ctx, bucket, madmin.HealOpts{ScanMode: scanMode})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log bucket healing error if any, we shall retry again.
|
// Log bucket healing error if any, we shall retry again.
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@ -201,7 +198,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
|
|
||||||
// Heal current bucket again in case if it is failed
|
// Heal current bucket again in case if it is failed
|
||||||
// in the beginning of erasure set healing
|
// in the beginning of erasure set healing
|
||||||
if _, err := objAPI.HealBucket(ctx, bucket, madmin.HealOpts{
|
if _, err := er.HealBucket(ctx, bucket, madmin.HealOpts{
|
||||||
ScanMode: scanMode,
|
ScanMode: scanMode,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
|
@ -25,9 +25,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/rest"
|
"github.com/minio/minio/internal/rest"
|
||||||
"github.com/minio/pkg/v2/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
@ -38,7 +36,6 @@ var errPeerOffline = errors.New("peer is offline")
|
|||||||
|
|
||||||
type peerS3Client interface {
|
type peerS3Client interface {
|
||||||
ListBuckets(ctx context.Context, opts BucketOptions) ([]BucketInfo, error)
|
ListBuckets(ctx context.Context, opts BucketOptions) ([]BucketInfo, error)
|
||||||
HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error)
|
|
||||||
GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error)
|
GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error)
|
||||||
MakeBucket(ctx context.Context, bucket string, opts MakeBucketOptions) error
|
MakeBucket(ctx context.Context, bucket string, opts MakeBucketOptions) error
|
||||||
DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error
|
DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error
|
||||||
@ -70,10 +67,6 @@ func (l localPeerS3Client) ListBuckets(ctx context.Context, opts BucketOptions)
|
|||||||
return listBucketsLocal(ctx, opts)
|
return listBucketsLocal(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l localPeerS3Client) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
|
||||||
return healBucketLocal(ctx, bucket, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l localPeerS3Client) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
|
func (l localPeerS3Client) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
|
||||||
return getBucketInfoLocal(ctx, bucket, opts)
|
return getBucketInfoLocal(ctx, bucket, opts)
|
||||||
}
|
}
|
||||||
@ -131,51 +124,6 @@ func NewS3PeerSys(endpoints EndpointServerPools) *S3PeerSys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealBucket - heals buckets at node level
|
|
||||||
func (sys *S3PeerSys) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
|
||||||
g := errgroup.WithNErrs(len(sys.peerClients))
|
|
||||||
|
|
||||||
healBucketResults := make([]madmin.HealResultItem, len(sys.peerClients))
|
|
||||||
for idx, client := range sys.peerClients {
|
|
||||||
idx := idx
|
|
||||||
client := client
|
|
||||||
g.Go(func() error {
|
|
||||||
if client == nil {
|
|
||||||
return errPeerOffline
|
|
||||||
}
|
|
||||||
res, err := client.HealBucket(ctx, bucket, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
healBucketResults[idx] = res
|
|
||||||
return nil
|
|
||||||
}, idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := g.Wait()
|
|
||||||
|
|
||||||
for poolIdx := 0; poolIdx < sys.poolsCount; poolIdx++ {
|
|
||||||
perPoolErrs := make([]error, 0, len(sys.peerClients))
|
|
||||||
for i, client := range sys.peerClients {
|
|
||||||
if slices.Contains(client.GetPools(), poolIdx) {
|
|
||||||
perPoolErrs = append(perPoolErrs, errs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
quorum := len(perPoolErrs) / 2
|
|
||||||
if poolErr := reduceWriteQuorumErrs(ctx, perPoolErrs, bucketOpIgnoredErrs, quorum); poolErr != nil {
|
|
||||||
return madmin.HealResultItem{}, poolErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, err := range errs {
|
|
||||||
if err == nil {
|
|
||||||
return healBucketResults[i], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return madmin.HealResultItem{}, toObjectErr(errVolumeNotFound, bucket)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListBuckets lists buckets across all nodes and returns a consistent view:
|
// ListBuckets lists buckets across all nodes and returns a consistent view:
|
||||||
// - Return an error when a pool cannot return N/2+1 valid bucket information
|
// - Return an error when a pool cannot return N/2+1 valid bucket information
|
||||||
// - For each pool, check if the bucket exists in N/2+1 nodes before including it in the final result
|
// - For each pool, check if the bucket exists in N/2+1 nodes before including it in the final result
|
||||||
@ -237,22 +185,6 @@ func (sys *S3PeerSys) ListBuckets(ctx context.Context, opts BucketOptions) ([]Bu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// loop through buckets and see if some with lost quorum
|
|
||||||
// these could be stale buckets lying around, queue a heal
|
|
||||||
// of such a bucket. This is needed here as we identify such
|
|
||||||
// buckets here while listing buckets. As part of regular
|
|
||||||
// globalBucketMetadataSys.Init() call would get a valid
|
|
||||||
// buckets only and not the quourum lost ones like this, so
|
|
||||||
// explicit call
|
|
||||||
for bktName, count := range bucketsMap {
|
|
||||||
if count < quorum {
|
|
||||||
// Queue a bucket heal task
|
|
||||||
globalMRFState.addPartialOp(partialOperation{
|
|
||||||
bucket: bktName,
|
|
||||||
queued: time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]BucketInfo, 0, len(resultMap))
|
result := make([]BucketInfo, 0, len(resultMap))
|
||||||
@ -327,23 +259,6 @@ func (client *remotePeerS3Client) ListBuckets(ctx context.Context, opts BucketOp
|
|||||||
return buckets, err
|
return buckets, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *remotePeerS3Client) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set(peerS3Bucket, bucket)
|
|
||||||
v.Set(peerS3BucketDeleted, strconv.FormatBool(opts.Remove))
|
|
||||||
|
|
||||||
respBody, err := client.call(peerS3MethodHealBucket, v, nil, -1)
|
|
||||||
if err != nil {
|
|
||||||
return madmin.HealResultItem{}, err
|
|
||||||
}
|
|
||||||
defer xhttp.DrainBody(respBody)
|
|
||||||
|
|
||||||
var res madmin.HealResultItem
|
|
||||||
err = gob.NewDecoder(respBody).Decode(&res)
|
|
||||||
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBucketInfo returns bucket stat info from a peer
|
// GetBucketInfo returns bucket stat info from a peer
|
||||||
func (client *remotePeerS3Client) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
|
func (client *remotePeerS3Client) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
|
@ -21,10 +21,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
@ -44,7 +42,6 @@ const (
|
|||||||
peerS3MethodGetBucketInfo = "/get-bucket-info"
|
peerS3MethodGetBucketInfo = "/get-bucket-info"
|
||||||
peerS3MethodDeleteBucket = "/delete-bucket"
|
peerS3MethodDeleteBucket = "/delete-bucket"
|
||||||
peerS3MethodListBuckets = "/list-buckets"
|
peerS3MethodListBuckets = "/list-buckets"
|
||||||
peerS3MethodHealBucket = "/heal-bucket"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -81,133 +78,6 @@ func (s *peerS3Server) HealthHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
s.IsValid(w, r)
|
s.IsValid(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (res madmin.HealResultItem, err error) {
|
|
||||||
globalLocalDrivesMu.RLock()
|
|
||||||
globalLocalDrives := globalLocalDrives
|
|
||||||
globalLocalDrivesMu.RUnlock()
|
|
||||||
|
|
||||||
// Initialize sync waitgroup.
|
|
||||||
g := errgroup.WithNErrs(len(globalLocalDrives))
|
|
||||||
|
|
||||||
// Disk states slices
|
|
||||||
beforeState := make([]string, len(globalLocalDrives))
|
|
||||||
afterState := make([]string, len(globalLocalDrives))
|
|
||||||
|
|
||||||
// Make a volume entry on all underlying storage disks.
|
|
||||||
for index := range globalLocalDrives {
|
|
||||||
index := index
|
|
||||||
g.Go(func() (serr error) {
|
|
||||||
if globalLocalDrives[index] == nil {
|
|
||||||
beforeState[index] = madmin.DriveStateOffline
|
|
||||||
afterState[index] = madmin.DriveStateOffline
|
|
||||||
return errDiskNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeState[index] = madmin.DriveStateOk
|
|
||||||
afterState[index] = madmin.DriveStateOk
|
|
||||||
|
|
||||||
if bucket == minioReservedBucket {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, serr = globalLocalDrives[index].StatVol(ctx, bucket)
|
|
||||||
if serr != nil {
|
|
||||||
if serr == errDiskNotFound {
|
|
||||||
beforeState[index] = madmin.DriveStateOffline
|
|
||||||
afterState[index] = madmin.DriveStateOffline
|
|
||||||
return serr
|
|
||||||
}
|
|
||||||
if serr != errVolumeNotFound {
|
|
||||||
beforeState[index] = madmin.DriveStateCorrupt
|
|
||||||
afterState[index] = madmin.DriveStateCorrupt
|
|
||||||
return serr
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeState[index] = madmin.DriveStateMissing
|
|
||||||
afterState[index] = madmin.DriveStateMissing
|
|
||||||
|
|
||||||
// mutate only if not a dry-run
|
|
||||||
if opts.DryRun {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return serr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := g.Wait()
|
|
||||||
|
|
||||||
// Initialize heal result info
|
|
||||||
res = madmin.HealResultItem{
|
|
||||||
Type: madmin.HealItemBucket,
|
|
||||||
Bucket: bucket,
|
|
||||||
DiskCount: len(globalLocalDrives),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range beforeState {
|
|
||||||
res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{
|
|
||||||
UUID: "",
|
|
||||||
Endpoint: globalLocalDrives[i].String(),
|
|
||||||
State: beforeState[i],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// check dangling and delete bucket only if its not a meta bucket
|
|
||||||
if !isMinioMetaBucketName(bucket) && !isAllBucketsNotFound(errs) && opts.Remove {
|
|
||||||
g := errgroup.WithNErrs(len(globalLocalDrives))
|
|
||||||
for index := range globalLocalDrives {
|
|
||||||
index := index
|
|
||||||
g.Go(func() error {
|
|
||||||
if globalLocalDrives[index] == nil {
|
|
||||||
return errDiskNotFound
|
|
||||||
}
|
|
||||||
err := globalLocalDrives[index].DeleteVol(ctx, bucket, false)
|
|
||||||
if errors.Is(err, errVolumeNotEmpty) {
|
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("While deleting dangling Bucket (%s), Drive %s:%s returned an error (%w)",
|
|
||||||
bucket, globalLocalDrives[index].Hostname(), globalLocalDrives[index], err), "delete-dangling-bucket-"+bucket)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the quorum lost volume only if its nor makred for delete
|
|
||||||
if !opts.Remove {
|
|
||||||
// Initialize sync waitgroup.
|
|
||||||
g = errgroup.WithNErrs(len(globalLocalDrives))
|
|
||||||
|
|
||||||
// Make a volume entry on all underlying storage disks.
|
|
||||||
for index := range globalLocalDrives {
|
|
||||||
index := index
|
|
||||||
g.Go(func() error {
|
|
||||||
if beforeState[index] == madmin.DriveStateMissing {
|
|
||||||
makeErr := globalLocalDrives[index].MakeVol(ctx, bucket)
|
|
||||||
if makeErr == nil {
|
|
||||||
afterState[index] = madmin.DriveStateOk
|
|
||||||
}
|
|
||||||
return makeErr
|
|
||||||
}
|
|
||||||
return errs[index]
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
errs = g.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range afterState {
|
|
||||||
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
|
|
||||||
UUID: "",
|
|
||||||
Endpoint: globalLocalDrives[i].String(),
|
|
||||||
State: afterState[i],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
|
func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
globalLocalDrives := globalLocalDrives
|
globalLocalDrives := globalLocalDrives
|
||||||
@ -389,30 +259,6 @@ func (s *peerS3Server) ListBucketsHandler(w http.ResponseWriter, r *http.Request
|
|||||||
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(buckets))
|
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(buckets))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *peerS3Server) HealBucketHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !s.IsValid(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bucketDeleted := r.Form.Get(peerS3BucketDeleted) == "true"
|
|
||||||
|
|
||||||
bucket := r.Form.Get(peerS3Bucket)
|
|
||||||
if isMinioMetaBucket(bucket) {
|
|
||||||
s.writeErrorResponse(w, errInvalidArgument)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := healBucketLocal(r.Context(), bucket, madmin.HealOpts{
|
|
||||||
Remove: bucketDeleted,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
s.writeErrorResponse(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBucketInfoHandler implements peer BuckeInfo call, returns bucket create date.
|
// GetBucketInfoHandler implements peer BuckeInfo call, returns bucket create date.
|
||||||
func (s *peerS3Server) GetBucketInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *peerS3Server) GetBucketInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !s.IsValid(w, r) {
|
if !s.IsValid(w, r) {
|
||||||
@ -487,5 +333,4 @@ func registerPeerS3Handlers(router *mux.Router) {
|
|||||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodDeleteBucket).HandlerFunc(h(server.DeleteBucketHandler))
|
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodDeleteBucket).HandlerFunc(h(server.DeleteBucketHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodGetBucketInfo).HandlerFunc(h(server.GetBucketInfoHandler))
|
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodGetBucketInfo).HandlerFunc(h(server.GetBucketInfoHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodListBuckets).HandlerFunc(h(server.ListBucketsHandler))
|
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodListBuckets).HandlerFunc(h(server.ListBucketsHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodHealBucket).HandlerFunc(h(server.HealBucketHandler))
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user