storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/erasure-metadata-utils.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "hash/crc32" 24 25 "storj.io/minio/cmd/logger" 26 "storj.io/minio/pkg/sync/errgroup" 27 ) 28 29 // Returns number of errors that occurred the most (incl. nil) and the 30 // corresponding error value. NB When there is more than one error value that 31 // occurs maximum number of times, the error value returned depends on how 32 // golang's map orders keys. This doesn't affect correctness as long as quorum 33 // value is greater than or equal to simple majority, since none of the equally 34 // maximal values would occur quorum or more number of times. 35 func reduceErrs(errs []error, ignoredErrs []error) (maxCount int, maxErr error) { 36 errorCounts := make(map[error]int) 37 for _, err := range errs { 38 if IsErrIgnored(err, ignoredErrs...) { 39 continue 40 } 41 errorCounts[err]++ 42 } 43 44 max := 0 45 for err, count := range errorCounts { 46 switch { 47 case max < count: 48 max = count 49 maxErr = err 50 51 // Prefer `nil` over other error values with the same 52 // number of occurrences. 53 case max == count && err == nil: 54 maxErr = err 55 } 56 } 57 return max, maxErr 58 } 59 60 // reduceQuorumErrs behaves like reduceErrs by only for returning 61 // values of maximally occurring errors validated against a generic 62 // quorum number that can be read or write quorum depending on usage. 63 func reduceQuorumErrs(ctx context.Context, errs []error, ignoredErrs []error, quorum int, quorumErr error) error { 64 maxCount, maxErr := reduceErrs(errs, ignoredErrs) 65 if maxCount >= quorum { 66 return maxErr 67 } 68 return quorumErr 69 } 70 71 // reduceReadQuorumErrs behaves like reduceErrs but only for returning 72 // values of maximally occurring errors validated against readQuorum. 73 func reduceReadQuorumErrs(ctx context.Context, errs []error, ignoredErrs []error, readQuorum int) (maxErr error) { 74 return reduceQuorumErrs(ctx, errs, ignoredErrs, readQuorum, errErasureReadQuorum) 75 } 76 77 // reduceWriteQuorumErrs behaves like reduceErrs but only for returning 78 // values of maximally occurring errors validated against writeQuorum. 79 func reduceWriteQuorumErrs(ctx context.Context, errs []error, ignoredErrs []error, writeQuorum int) (maxErr error) { 80 return reduceQuorumErrs(ctx, errs, ignoredErrs, writeQuorum, errErasureWriteQuorum) 81 } 82 83 // Similar to 'len(slice)' but returns the actual elements count 84 // skipping the unallocated elements. 85 func diskCount(disks []StorageAPI) int { 86 diskCount := 0 87 for _, disk := range disks { 88 if disk == nil { 89 continue 90 } 91 diskCount++ 92 } 93 return diskCount 94 } 95 96 // hashOrder - hashes input key to return consistent 97 // hashed integer slice. Returned integer order is salted 98 // with an input key. This results in consistent order. 99 // NOTE: collisions are fine, we are not looking for uniqueness 100 // in the slices returned. 101 func hashOrder(key string, cardinality int) []int { 102 if cardinality <= 0 { 103 // Returns an empty int slice for cardinality < 0. 104 return nil 105 } 106 107 nums := make([]int, cardinality) 108 keyCrc := crc32.Checksum([]byte(key), crc32.IEEETable) 109 110 start := int(keyCrc % uint32(cardinality)) 111 for i := 1; i <= cardinality; i++ { 112 nums[i-1] = 1 + ((start + i) % cardinality) 113 } 114 return nums 115 } 116 117 // Reads all `xl.meta` metadata as a FileInfo slice. 118 // Returns error slice indicating the failed metadata reads. 119 func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, versionID string, readData bool) ([]FileInfo, []error) { 120 metadataArray := make([]FileInfo, len(disks)) 121 122 g := errgroup.WithNErrs(len(disks)) 123 // Read `xl.meta` in parallel across disks. 124 for index := range disks { 125 index := index 126 g.Go(func() (err error) { 127 if disks[index] == nil { 128 return errDiskNotFound 129 } 130 metadataArray[index], err = disks[index].ReadVersion(ctx, bucket, object, versionID, readData) 131 if err != nil { 132 if !IsErr(err, []error{ 133 errFileNotFound, 134 errVolumeNotFound, 135 errFileVersionNotFound, 136 errDiskNotFound, 137 }...) { 138 logger.LogOnceIf(ctx, fmt.Errorf("Drive %s, path (%s/%s) returned an error (%w)", 139 disks[index], bucket, object, err), 140 disks[index].String()) 141 } 142 } 143 return err 144 }, index) 145 } 146 147 // Return all the metadata. 148 return metadataArray, g.Wait() 149 } 150 151 // shuffleDisksAndPartsMetadataByIndex this function should be always used by GetObjectNInfo() 152 // and CompleteMultipartUpload code path, it is not meant to be used with PutObject, 153 // NewMultipartUpload metadata shuffling. 154 func shuffleDisksAndPartsMetadataByIndex(disks []StorageAPI, metaArr []FileInfo, fi FileInfo) (shuffledDisks []StorageAPI, shuffledPartsMetadata []FileInfo) { 155 shuffledDisks = make([]StorageAPI, len(disks)) 156 shuffledPartsMetadata = make([]FileInfo, len(disks)) 157 distribution := fi.Erasure.Distribution 158 159 var inconsistent int 160 for i, meta := range metaArr { 161 if disks[i] == nil { 162 // Assuming offline drives as inconsistent, 163 // to be safe and fallback to original 164 // distribution order. 165 inconsistent++ 166 continue 167 } 168 if !meta.IsValid() { 169 inconsistent++ 170 continue 171 } 172 if len(fi.Data) != len(meta.Data) { 173 inconsistent++ 174 continue 175 } 176 // check if erasure distribution order matches the index 177 // position if this is not correct we discard the disk 178 // and move to collect others 179 if distribution[i] != meta.Erasure.Index { 180 inconsistent++ // keep track of inconsistent entries 181 continue 182 } 183 shuffledDisks[meta.Erasure.Index-1] = disks[i] 184 shuffledPartsMetadata[meta.Erasure.Index-1] = metaArr[i] 185 } 186 187 // Inconsistent meta info is with in the limit of 188 // expected quorum, proceed with EcIndex based 189 // disk order. 190 if inconsistent < fi.Erasure.ParityBlocks { 191 return shuffledDisks, shuffledPartsMetadata 192 } 193 194 // fall back to original distribution based order. 195 return shuffleDisksAndPartsMetadata(disks, metaArr, fi) 196 } 197 198 // Return shuffled partsMetadata depending on fi.Distribution. 199 // additional validation is attempted and invalid metadata is 200 // automatically skipped only when fi.ModTime is non-zero 201 // indicating that this is called during read-phase 202 func shuffleDisksAndPartsMetadata(disks []StorageAPI, partsMetadata []FileInfo, fi FileInfo) (shuffledDisks []StorageAPI, shuffledPartsMetadata []FileInfo) { 203 shuffledDisks = make([]StorageAPI, len(disks)) 204 shuffledPartsMetadata = make([]FileInfo, len(partsMetadata)) 205 distribution := fi.Erasure.Distribution 206 207 init := fi.ModTime.IsZero() 208 // Shuffle slice xl metadata for expected distribution. 209 for index := range partsMetadata { 210 if disks[index] == nil { 211 continue 212 } 213 if !init && !partsMetadata[index].IsValid() { 214 // Check for parts metadata validity for only 215 // fi.ModTime is not empty - ModTime is always set, 216 // if object was ever written previously. 217 continue 218 } 219 if !init && len(fi.Data) != len(partsMetadata[index].Data) { 220 // Check for length of data parts only when 221 // fi.ModTime is not empty - ModTime is always set, 222 // if object was ever written previously. 223 continue 224 } 225 blockIndex := distribution[index] 226 shuffledPartsMetadata[blockIndex-1] = partsMetadata[index] 227 shuffledDisks[blockIndex-1] = disks[index] 228 } 229 return shuffledDisks, shuffledPartsMetadata 230 } 231 232 // Return shuffled partsMetadata depending on distribution. 233 func shufflePartsMetadata(partsMetadata []FileInfo, distribution []int) (shuffledPartsMetadata []FileInfo) { 234 if distribution == nil { 235 return partsMetadata 236 } 237 shuffledPartsMetadata = make([]FileInfo, len(partsMetadata)) 238 // Shuffle slice xl metadata for expected distribution. 239 for index := range partsMetadata { 240 blockIndex := distribution[index] 241 shuffledPartsMetadata[blockIndex-1] = partsMetadata[index] 242 } 243 return shuffledPartsMetadata 244 } 245 246 // shuffleDisks - shuffle input disks slice depending on the 247 // erasure distribution. Return shuffled slice of disks with 248 // their expected distribution. 249 func shuffleDisks(disks []StorageAPI, distribution []int) (shuffledDisks []StorageAPI) { 250 if distribution == nil { 251 return disks 252 } 253 shuffledDisks = make([]StorageAPI, len(disks)) 254 // Shuffle disks for expected distribution. 255 for index := range disks { 256 blockIndex := distribution[index] 257 shuffledDisks[blockIndex-1] = disks[index] 258 } 259 return shuffledDisks 260 } 261 262 // evalDisks - returns a new slice of disks where nil is set if 263 // the corresponding error in errs slice is not nil 264 func evalDisks(disks []StorageAPI, errs []error) []StorageAPI { 265 if len(errs) != len(disks) { 266 logger.LogIf(GlobalContext, errors.New("unexpected disks/errors slice length")) 267 return nil 268 } 269 newDisks := make([]StorageAPI, len(disks)) 270 for index := range errs { 271 if errs[index] == nil { 272 newDisks[index] = disks[index] 273 } else { 274 newDisks[index] = nil 275 } 276 } 277 return newDisks 278 } 279 280 // Errors specifically generated by calculatePartSizeFromIdx function. 281 var ( 282 errPartSizeZero = errors.New("Part size cannot be zero") 283 errPartSizeIndex = errors.New("Part index cannot be smaller than 1") 284 ) 285 286 // calculatePartSizeFromIdx calculates the part size according to input index. 287 // returns error if totalSize is -1, partSize is 0, partIndex is 0. 288 func calculatePartSizeFromIdx(ctx context.Context, totalSize int64, partSize int64, partIndex int) (currPartSize int64, err error) { 289 if totalSize < -1 { 290 logger.LogIf(ctx, errInvalidArgument) 291 return 0, errInvalidArgument 292 } 293 if partSize == 0 { 294 logger.LogIf(ctx, errPartSizeZero) 295 return 0, errPartSizeZero 296 } 297 if partIndex < 1 { 298 logger.LogIf(ctx, errPartSizeIndex) 299 return 0, errPartSizeIndex 300 } 301 if totalSize == -1 { 302 return -1, nil 303 } 304 if totalSize > 0 { 305 // Compute the total count of parts 306 partsCount := totalSize/partSize + 1 307 // Return the part's size 308 switch { 309 case int64(partIndex) < partsCount: 310 currPartSize = partSize 311 case int64(partIndex) == partsCount: 312 // Size of last part 313 currPartSize = totalSize % partSize 314 default: 315 currPartSize = 0 316 } 317 } 318 return currPartSize, nil 319 }