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  }