github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/peer-s3-server.go (about)

     1  // Copyright (c) 2015-2022 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  
    24  	"github.com/minio/madmin-go/v3"
    25  	"github.com/minio/pkg/v2/sync/errgroup"
    26  )
    27  
    28  const (
    29  	peerS3Bucket            = "bucket"
    30  	peerS3BucketDeleted     = "bucket-deleted"
    31  	peerS3BucketForceCreate = "force-create"
    32  	peerS3BucketForceDelete = "force-delete"
    33  )
    34  
    35  func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (res madmin.HealResultItem, err error) {
    36  	globalLocalDrivesMu.RLock()
    37  	localDrives := cloneDrives(globalLocalDrives)
    38  	globalLocalDrivesMu.RUnlock()
    39  
    40  	// Initialize sync waitgroup.
    41  	g := errgroup.WithNErrs(len(localDrives))
    42  
    43  	// Disk states slices
    44  	beforeState := make([]string, len(localDrives))
    45  	afterState := make([]string, len(localDrives))
    46  
    47  	// Make a volume entry on all underlying storage disks.
    48  	for index := range localDrives {
    49  		index := index
    50  		g.Go(func() (serr error) {
    51  			if localDrives[index] == nil {
    52  				beforeState[index] = madmin.DriveStateOffline
    53  				afterState[index] = madmin.DriveStateOffline
    54  				return errDiskNotFound
    55  			}
    56  
    57  			beforeState[index] = madmin.DriveStateOk
    58  			afterState[index] = madmin.DriveStateOk
    59  
    60  			if bucket == minioReservedBucket {
    61  				return nil
    62  			}
    63  
    64  			_, serr = localDrives[index].StatVol(ctx, bucket)
    65  			if serr != nil {
    66  				if serr == errDiskNotFound {
    67  					beforeState[index] = madmin.DriveStateOffline
    68  					afterState[index] = madmin.DriveStateOffline
    69  					return serr
    70  				}
    71  				if serr != errVolumeNotFound {
    72  					beforeState[index] = madmin.DriveStateCorrupt
    73  					afterState[index] = madmin.DriveStateCorrupt
    74  					return serr
    75  				}
    76  
    77  				beforeState[index] = madmin.DriveStateMissing
    78  				afterState[index] = madmin.DriveStateMissing
    79  
    80  				return serr
    81  			}
    82  			return nil
    83  		}, index)
    84  	}
    85  
    86  	errs := g.Wait()
    87  
    88  	// Initialize heal result info
    89  	res = madmin.HealResultItem{
    90  		Type:      madmin.HealItemBucket,
    91  		Bucket:    bucket,
    92  		DiskCount: len(localDrives),
    93  		SetCount:  -1, // explicitly set an invalid value -1, for bucket heal scenario
    94  	}
    95  
    96  	// mutate only if not a dry-run
    97  	if opts.DryRun {
    98  		return res, nil
    99  	}
   100  
   101  	for i := range beforeState {
   102  		res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{
   103  			UUID:     "",
   104  			Endpoint: localDrives[i].String(),
   105  			State:    beforeState[i],
   106  		})
   107  	}
   108  
   109  	// check dangling and delete bucket only if its not a meta bucket
   110  	if !isMinioMetaBucketName(bucket) && !isAllBucketsNotFound(errs) && opts.Remove {
   111  		g := errgroup.WithNErrs(len(localDrives))
   112  		for index := range localDrives {
   113  			index := index
   114  			g.Go(func() error {
   115  				if localDrives[index] == nil {
   116  					return errDiskNotFound
   117  				}
   118  				localDrives[index].DeleteVol(ctx, bucket, false)
   119  				return nil
   120  			}, index)
   121  		}
   122  
   123  		g.Wait()
   124  	}
   125  
   126  	// Create the quorum lost volume only if its nor makred for delete
   127  	if !opts.Remove {
   128  		// Initialize sync waitgroup.
   129  		g = errgroup.WithNErrs(len(localDrives))
   130  
   131  		// Make a volume entry on all underlying storage disks.
   132  		for index := range localDrives {
   133  			index := index
   134  			g.Go(func() error {
   135  				if beforeState[index] == madmin.DriveStateMissing {
   136  					err := localDrives[index].MakeVol(ctx, bucket)
   137  					if err == nil {
   138  						afterState[index] = madmin.DriveStateOk
   139  					}
   140  					return err
   141  				}
   142  				return errs[index]
   143  			}, index)
   144  		}
   145  
   146  		errs = g.Wait()
   147  	}
   148  
   149  	for i := range afterState {
   150  		res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
   151  			UUID:     "",
   152  			Endpoint: localDrives[i].String(),
   153  			State:    afterState[i],
   154  		})
   155  	}
   156  	return res, nil
   157  }
   158  
   159  func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
   160  	globalLocalDrivesMu.RLock()
   161  	localDrives := cloneDrives(globalLocalDrives)
   162  	globalLocalDrivesMu.RUnlock()
   163  
   164  	quorum := (len(localDrives) / 2)
   165  
   166  	buckets = make([]BucketInfo, 0, 32)
   167  	healBuckets := map[string]VolInfo{}
   168  
   169  	// lists all unique buckets across drives.
   170  	if err := listAllBuckets(ctx, localDrives, healBuckets, quorum); err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	// include deleted buckets in listBuckets output
   175  	deletedBuckets := map[string]VolInfo{}
   176  
   177  	if opts.Deleted {
   178  		// lists all deleted buckets across drives.
   179  		if err := listDeletedBuckets(ctx, localDrives, deletedBuckets, quorum); err != nil {
   180  			return nil, err
   181  		}
   182  	}
   183  
   184  	for _, v := range healBuckets {
   185  		bi := BucketInfo{
   186  			Name:    v.Name,
   187  			Created: v.Created,
   188  		}
   189  		if vi, ok := deletedBuckets[v.Name]; ok {
   190  			bi.Deleted = vi.Created
   191  		}
   192  		buckets = append(buckets, bi)
   193  	}
   194  
   195  	for _, v := range deletedBuckets {
   196  		if _, ok := healBuckets[v.Name]; !ok {
   197  			buckets = append(buckets, BucketInfo{
   198  				Name:    v.Name,
   199  				Deleted: v.Created,
   200  			})
   201  		}
   202  	}
   203  
   204  	return buckets, nil
   205  }
   206  
   207  func cloneDrives(drives []StorageAPI) []StorageAPI {
   208  	newDrives := make([]StorageAPI, len(drives))
   209  	copy(newDrives, drives)
   210  	return newDrives
   211  }
   212  
   213  func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
   214  	globalLocalDrivesMu.RLock()
   215  	localDrives := cloneDrives(globalLocalDrives)
   216  	globalLocalDrivesMu.RUnlock()
   217  
   218  	g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
   219  	bucketsInfo := make([]BucketInfo, len(localDrives))
   220  
   221  	// Make a volume entry on all underlying storage disks.
   222  	for index := range localDrives {
   223  		index := index
   224  		g.Go(func() error {
   225  			if localDrives[index] == nil {
   226  				return errDiskNotFound
   227  			}
   228  			volInfo, err := localDrives[index].StatVol(ctx, bucket)
   229  			if err != nil {
   230  				if opts.Deleted {
   231  					dvi, derr := localDrives[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucket))
   232  					if derr != nil {
   233  						return err
   234  					}
   235  					bucketsInfo[index] = BucketInfo{Name: bucket, Deleted: dvi.Created}
   236  					return nil
   237  				}
   238  				return err
   239  			}
   240  
   241  			bucketsInfo[index] = BucketInfo{Name: bucket, Created: volInfo.Created}
   242  			return nil
   243  		}, index)
   244  	}
   245  
   246  	errs := g.Wait()
   247  	if err := reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(localDrives) / 2)); err != nil {
   248  		return BucketInfo{}, err
   249  	}
   250  
   251  	var bucketInfo BucketInfo
   252  	for i, err := range errs {
   253  		if err == nil {
   254  			bucketInfo = bucketsInfo[i]
   255  			break
   256  		}
   257  	}
   258  
   259  	return bucketInfo, nil
   260  }
   261  
   262  func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
   263  	globalLocalDrivesMu.RLock()
   264  	localDrives := cloneDrives(globalLocalDrives)
   265  	globalLocalDrivesMu.RUnlock()
   266  
   267  	g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
   268  
   269  	// Make a volume entry on all underlying storage disks.
   270  	for index := range localDrives {
   271  		index := index
   272  		g.Go(func() error {
   273  			if localDrives[index] == nil {
   274  				return errDiskNotFound
   275  			}
   276  			return localDrives[index].DeleteVol(ctx, bucket, opts.Force)
   277  		}, index)
   278  	}
   279  
   280  	var recreate bool
   281  	errs := g.Wait()
   282  	for index, err := range errs {
   283  		if errors.Is(err, errVolumeNotEmpty) {
   284  			recreate = true
   285  		}
   286  		if err == nil && recreate {
   287  			// ignore any errors
   288  			localDrives[index].MakeVol(ctx, bucket)
   289  		}
   290  	}
   291  
   292  	// Since we recreated buckets and error was `not-empty`, return not-empty.
   293  	if recreate {
   294  		return errVolumeNotEmpty
   295  	} // for all other errors reduce by write quorum.
   296  
   297  	return reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(localDrives)/2)+1)
   298  }
   299  
   300  func makeBucketLocal(ctx context.Context, bucket string, opts MakeBucketOptions) error {
   301  	globalLocalDrivesMu.RLock()
   302  	localDrives := cloneDrives(globalLocalDrives)
   303  	globalLocalDrivesMu.RUnlock()
   304  
   305  	g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
   306  
   307  	// Make a volume entry on all underlying storage disks.
   308  	for index := range localDrives {
   309  		index := index
   310  		g.Go(func() error {
   311  			if localDrives[index] == nil {
   312  				return errDiskNotFound
   313  			}
   314  			err := localDrives[index].MakeVol(ctx, bucket)
   315  			if opts.ForceCreate && errors.Is(err, errVolumeExists) {
   316  				// No need to return error when force create was
   317  				// requested.
   318  				return nil
   319  			}
   320  			return err
   321  		}, index)
   322  	}
   323  
   324  	errs := g.Wait()
   325  	return reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(localDrives)/2)+1)
   326  }