1985 lines · 61.8 KB · Go
RawDownload
// Copyright (c) 2015-2021 MinIO, Inc.//// This file is part of MinIO Object Storage stack//// This program is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// This program is distributed in the hope that it will be useful// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with this program.  If not, see <http://www.gnu.org/licenses/>. package cmd import (	"bytes"	"context"	"errors"	"fmt"	"io"	"net/http"	"path"	"strconv"	"strings"	"sync" 	"github.com/klauspost/readahead"	"github.com/minio/madmin-go"	"github.com/minio/minio-go/v7/pkg/tags"	"github.com/minio/minio/internal/bucket/lifecycle"	"github.com/minio/minio/internal/bucket/object/lock"	"github.com/minio/minio/internal/bucket/replication"	"github.com/minio/minio/internal/event"	"github.com/minio/minio/internal/hash"	xhttp "github.com/minio/minio/internal/http"	xioutil "github.com/minio/minio/internal/ioutil"	"github.com/minio/minio/internal/logger"	"github.com/minio/minio/internal/sync/errgroup"	"github.com/minio/pkg/mimedb"	uatomic "go.uber.org/atomic") // list all errors which can be ignored in object operations.var objectOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errUnformattedDisk) // Object Operations func countOnlineDisks(onlineDisks []StorageAPI) (online int) {	for _, onlineDisk := range onlineDisks {		if onlineDisk != nil && onlineDisk.IsOnline() {			online++		}	}	return online} // CopyObject - copy object source object to destination object.// if source object and destination object are same we only// update metadata.func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (oi ObjectInfo, err error) {	auditObjectErasureSet(ctx, dstObject, &er) 	// This call shouldn't be used for anything other than metadata updates or adding self referential versions.	if !srcInfo.metadataOnly {		return oi, NotImplemented{}	} 	defer NSUpdated(dstBucket, dstObject) 	if !dstOpts.NoLock {		lk := er.NewNSLock(dstBucket, dstObject)		lkctx, err := lk.GetLock(ctx, globalOperationTimeout)		if err != nil {			return oi, err		}		ctx = lkctx.Context()		defer lk.Unlock(lkctx.Cancel)	}	// Read metadata associated with the object from all disks.	storageDisks := er.getDisks() 	var metaArr []FileInfo	var errs []error 	// Read metadata associated with the object from all disks.	if srcOpts.VersionID != "" {		metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true)	} else {		metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true)	} 	readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)	if err != nil {		if errors.Is(err, errErasureReadQuorum) && !strings.HasPrefix(srcBucket, minioMetaBucket) {			_, derr := er.deleteIfDangling(ctx, srcBucket, srcObject, metaArr, errs, nil, srcOpts)			if derr != nil {				err = derr			}		}		return ObjectInfo{}, toObjectErr(err, srcBucket, srcObject)	} 	// List all online disks.	onlineDisks, modTime := listOnlineDisks(storageDisks, metaArr, errs) 	// Pick latest valid metadata.	fi, err := pickValidFileInfo(ctx, metaArr, modTime, readQuorum)	if err != nil {		return oi, toObjectErr(err, srcBucket, srcObject)	}	if fi.Deleted {		if srcOpts.VersionID == "" {			return oi, toObjectErr(errFileNotFound, srcBucket, srcObject)		}		return fi.ToObjectInfo(srcBucket, srcObject, srcOpts.Versioned || srcOpts.VersionSuspended), toObjectErr(errMethodNotAllowed, srcBucket, srcObject)	} 	filterOnlineDisksInplace(fi, metaArr, onlineDisks) 	versionID := srcInfo.VersionID	if srcInfo.versionOnly {		versionID = dstOpts.VersionID		// preserve destination versionId if specified.		if versionID == "" {			versionID = mustGetUUID()			fi.IsLatest = true // we are creating a new version so this is latest.		}		modTime = UTCNow()	} 	fi.VersionID = versionID // set any new versionID we might have created	fi.ModTime = modTime     // set modTime for the new versionID	if !dstOpts.MTime.IsZero() {		modTime = dstOpts.MTime		fi.ModTime = dstOpts.MTime	}	fi.Metadata = srcInfo.UserDefined	srcInfo.UserDefined["etag"] = srcInfo.ETag 	inlineData := fi.InlineData()	freeVersionID := fi.TierFreeVersionID()	freeVersionMarker := fi.TierFreeVersion() 	// Update `xl.meta` content on each disks.	for index := range metaArr {		if metaArr[index].IsValid() {			metaArr[index].ModTime = modTime			metaArr[index].VersionID = versionID			if !metaArr[index].InlineData() {				// If the data is not inlined, we may end up incorrectly				// inlining the data here, that leads to an inconsistent				// situation where some objects are were not inlined				// were now inlined, make sure to `nil` the Data such				// that xl.meta is written as expected.				metaArr[index].Data = nil			}			metaArr[index].Metadata = srcInfo.UserDefined			// Preserve existing values			if inlineData {				metaArr[index].SetInlineData()			}			if freeVersionID != "" {				metaArr[index].SetTierFreeVersionID(freeVersionID)			}			if freeVersionMarker {				metaArr[index].SetTierFreeVersion()			}		}	} 	// Write unique `xl.meta` for each disk.	if _, err = writeUniqueFileInfo(ctx, onlineDisks, srcBucket, srcObject, metaArr, writeQuorum); err != nil {		return oi, toObjectErr(err, srcBucket, srcObject)	} 	return fi.ToObjectInfo(srcBucket, srcObject, srcOpts.Versioned || srcOpts.VersionSuspended), nil} // GetObjectNInfo - returns object info and an object// Read(Closer). When err != nil, the returned reader is always nil.func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {	auditObjectErasureSet(ctx, object, &er) 	var unlockOnDefer bool	nsUnlocker := func() {}	defer func() {		if unlockOnDefer {			nsUnlocker()		}	}() 	// Acquire lock	if lockType != noLock {		lock := er.NewNSLock(bucket, object)		switch lockType {		case writeLock:			lkctx, err := lock.GetLock(ctx, globalOperationTimeout)			if err != nil {				return nil, err			}			ctx = lkctx.Context()			nsUnlocker = func() { lock.Unlock(lkctx.Cancel) }		case readLock:			lkctx, err := lock.GetRLock(ctx, globalOperationTimeout)			if err != nil {				return nil, err			}			ctx = lkctx.Context()			nsUnlocker = func() { lock.RUnlock(lkctx.Cancel) }		}		unlockOnDefer = true	} 	fi, metaArr, onlineDisks, err := er.getObjectFileInfo(ctx, bucket, object, opts, true)	if err != nil {		return nil, toObjectErr(err, bucket, object)	} 	if !fi.DataShardFixed() {		diskMTime := pickValidDiskTimeWithQuorum(metaArr, fi.Erasure.DataBlocks)		if !diskMTime.Equal(timeSentinel) && !diskMTime.IsZero() {			for index := range onlineDisks {				if onlineDisks[index] == OfflineDisk {					continue				}				if !metaArr[index].IsValid() {					continue				}				if !metaArr[index].AcceptableDelta(diskMTime, shardDiskTimeDelta) {					// If disk mTime mismatches it is considered outdated					// https://github.com/minio/minio/pull/13803					//					// This check only is active if we could find maximally					// occurring disk mtimes that are somewhat same across					// the quorum. Allowing to skip those shards which we					// might think are wrong.					onlineDisks[index] = OfflineDisk				}			}		}	} 	objInfo := fi.ToObjectInfo(bucket, object, opts.Versioned || opts.VersionSuspended)	if objInfo.DeleteMarker {		if opts.VersionID == "" {			return &GetObjectReader{				ObjInfo: objInfo,			}, toObjectErr(errFileNotFound, bucket, object)		}		// Make sure to return object info to provide extra information.		return &GetObjectReader{			ObjInfo: objInfo,		}, toObjectErr(errMethodNotAllowed, bucket, object)	}	if objInfo.IsRemote() {		gr, err := getTransitionedObjectReader(ctx, bucket, object, rs, h, objInfo, opts)		if err != nil {			return nil, err		}		unlockOnDefer = false		return gr.WithCleanupFuncs(nsUnlocker), nil	} 	fn, off, length, err := NewGetObjectReader(rs, objInfo, opts)	if err != nil {		return nil, err	}	unlockOnDefer = false 	pr, pw := xioutil.WaitPipe()	go func() {		pw.CloseWithError(er.getObjectWithFileInfo(ctx, bucket, object, off, length, pw, fi, metaArr, onlineDisks))	}() 	// Cleanup function to cause the go routine above to exit, in	// case of incomplete read.	pipeCloser := func() {		pr.CloseWithError(nil)	} 	return fn(pr, h, pipeCloser, nsUnlocker)} func (er erasureObjects) getObjectWithFileInfo(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, fi FileInfo, metaArr []FileInfo, onlineDisks []StorageAPI) error {	// Reorder online disks based on erasure distribution order.	// Reorder parts metadata based on erasure distribution order.	onlineDisks, metaArr = shuffleDisksAndPartsMetadataByIndex(onlineDisks, metaArr, fi) 	// For negative length read everything.	if length < 0 {		length = fi.Size - startOffset	} 	// Reply back invalid range if the input offset and length fall out of range.	if startOffset > fi.Size || startOffset+length > fi.Size {		logger.LogIf(ctx, InvalidRange{startOffset, length, fi.Size}, logger.Application)		return InvalidRange{startOffset, length, fi.Size}	} 	// Get start part index and offset.	partIndex, partOffset, err := fi.ObjectToPartOffset(ctx, startOffset)	if err != nil {		return InvalidRange{startOffset, length, fi.Size}	} 	// Calculate endOffset according to length	endOffset := startOffset	if length > 0 {		endOffset += length - 1	} 	// Get last part index to read given length.	lastPartIndex, _, err := fi.ObjectToPartOffset(ctx, endOffset)	if err != nil {		return InvalidRange{startOffset, length, fi.Size}	} 	var totalBytesRead int64	erasure, err := NewErasure(ctx, fi.Erasure.DataBlocks, fi.Erasure.ParityBlocks, fi.Erasure.BlockSize)	if err != nil {		return toObjectErr(err, bucket, object)	} 	var healOnce sync.Once 	// once we have obtained a common FileInfo i.e latest, we should stick	// to single dataDir to read the content to avoid reading from some other	// dataDir that has stale FileInfo{} to ensure that we fail appropriately	// during reads and expect the same dataDir everywhere.	dataDir := fi.DataDir	for ; partIndex <= lastPartIndex; partIndex++ {		if length == totalBytesRead {			break		} 		partNumber := fi.Parts[partIndex].Number 		// Save the current part name and size.		partSize := fi.Parts[partIndex].Size 		partLength := partSize - partOffset		// partLength should be adjusted so that we don't write more data than what was requested.		if partLength > (length - totalBytesRead) {			partLength = length - totalBytesRead		} 		tillOffset := erasure.ShardFileOffset(partOffset, partLength, partSize)		// Get the checksums of the current part.		readers := make([]io.ReaderAt, len(onlineDisks))		prefer := make([]bool, len(onlineDisks))		for index, disk := range onlineDisks {			if disk == OfflineDisk {				continue			}			if !metaArr[index].IsValid() {				continue			}			checksumInfo := metaArr[index].Erasure.GetChecksumInfo(partNumber)			partPath := pathJoin(object, dataDir, fmt.Sprintf("part.%d", partNumber))			readers[index] = newBitrotReader(disk, metaArr[index].Data, bucket, partPath, tillOffset,				checksumInfo.Algorithm, checksumInfo.Hash, erasure.ShardSize()) 			// Prefer local disks			prefer[index] = disk.Hostname() == ""		} 		written, err := erasure.Decode(ctx, writer, readers, partOffset, partLength, partSize, prefer)		// Note: we should not be defer'ing the following closeBitrotReaders() call as		// we are inside a for loop i.e if we use defer, we would accumulate a lot of open files by the time		// we return from this function.		closeBitrotReaders(readers)		if err != nil {			// If we have successfully written all the content that was asked			// by the client, but we still see an error - this would mean			// that we have some parts or data blocks missing or corrupted			// - attempt a heal to successfully heal them for future calls.			if written == partLength {				var scan madmin.HealScanMode				switch {				case errors.Is(err, errFileNotFound):					scan = madmin.HealNormalScan				case errors.Is(err, errFileCorrupt):					scan = madmin.HealDeepScan				}				switch scan {				case madmin.HealNormalScan, madmin.HealDeepScan:					healOnce.Do(func() {						if _, healing := er.getOnlineDisksWithHealing(); !healing {							go healObject(bucket, object, fi.VersionID, scan)						}					})					// Healing is triggered and we have written					// successfully the content to client for					// the specific part, we should `nil` this error					// and proceed forward, instead of throwing errors.					err = nil				}			}			if err != nil {				return toObjectErr(err, bucket, object)			}		}		for i, r := range readers {			if r == nil {				onlineDisks[i] = OfflineDisk			}		}		// Track total bytes read from disk and written to the client.		totalBytesRead += partLength		// partOffset will be valid only for the first part, hence reset it to 0 for		// the remaining parts.		partOffset = 0	} // End of read all parts loop.	// Return success.	return nil} // GetObjectInfo - reads object metadata and replies back ObjectInfo.func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (info ObjectInfo, err error) {	auditObjectErasureSet(ctx, object, &er) 	if !opts.NoLock {		// Lock the object before reading.		lk := er.NewNSLock(bucket, object)		lkctx, err := lk.GetRLock(ctx, globalOperationTimeout)		if err != nil {			return ObjectInfo{}, err		}		ctx = lkctx.Context()		defer lk.RUnlock(lkctx.Cancel)	} 	return er.getObjectInfo(ctx, bucket, object, opts)} func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object string, metaArr []FileInfo, errs []error, dataErrs []error, opts ObjectOptions) (FileInfo, error) {	var err error	m, ok := isObjectDangling(metaArr, errs, dataErrs)	if ok {		err = errFileNotFound		if opts.VersionID != "" {			err = errFileVersionNotFound		}		defer NSUpdated(bucket, object) 		if opts.VersionID != "" {			er.deleteObjectVersion(ctx, bucket, object, 1, FileInfo{				VersionID: opts.VersionID,			}, false)		} else {			er.deleteObjectVersion(ctx, bucket, object, 1, FileInfo{				VersionID: m.VersionID,			}, false)		}	}	return m, err} func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData bool) ([]FileInfo, []error) {	metadataArray := make([]*xlMetaV2, len(disks))	metaFileInfos := make([]FileInfo, len(metadataArray))	metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks)) 	g := errgroup.WithNErrs(len(disks))	// Read `xl.meta` in parallel across disks.	for index := range disks {		index := index		g.Go(func() (err error) {			if disks[index] == nil {				return errDiskNotFound			}			rf, err := disks[index].ReadXL(ctx, bucket, object, readData)			if err != nil {				return err			} 			var xl xlMetaV2			if err = xl.LoadOrConvert(rf.Buf); err != nil {				return err			}			metadataArray[index] = &xl			metaFileInfos[index] = FileInfo{				DiskMTime: rf.DiskMTime,			}			return nil		}, index)	} 	errs := g.Wait()	for index, err := range errs {		if err == nil {			continue		}		if !IsErr(err, []error{			errFileNotFound,

Lines 1–500 of 1985.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t