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 }