Move nslocking from s3 layer to object layer (#5382)

Fixes #5350
This commit is contained in:
poornas 2018-01-12 20:34:52 -08:00 committed by Nitish Tiwari
parent dd202a1a5f
commit 0bb6247056
42 changed files with 468 additions and 355 deletions

View File

@ -412,11 +412,7 @@ func (adminAPI adminAPIHandlers) ClearLocksHandler(w http.ResponseWriter, r *htt
errorIf(err, "Failed to marshal lock information into json.") errorIf(err, "Failed to marshal lock information into json.")
return return
} }
newObjectLayerFn().ClearLocks(volLocks)
// Remove lock matching bucket/prefix held longer than duration.
for _, volLock := range volLocks {
globalNSMutex.ForceUnlock(volLock.Bucket, volLock.Object)
}
// Reply with list of locks cleared, as json. // Reply with list of locks cleared, as json.
writeSuccessResponseJSON(w, jsonBytes) writeSuccessResponseJSON(w, jsonBytes)

View File

@ -239,7 +239,7 @@ func runGetObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var buffer = new(bytes.Buffer) var buffer = new(bytes.Buffer)
err = obj.GetObject(bucket, "object"+strconv.Itoa(i%10), 0, int64(objSize), buffer) err = obj.GetObject(bucket, "object"+strconv.Itoa(i%10), 0, int64(objSize), buffer, "")
if err != nil { if err != nil {
b.Error(err) b.Error(err)
} }
@ -402,7 +402,7 @@ func runGetObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
i := 0 i := 0
for pb.Next() { for pb.Next() {
err = obj.GetObject(bucket, "object"+strconv.Itoa(i), 0, int64(objSize), ioutil.Discard) err = obj.GetObject(bucket, "object"+strconv.Itoa(i), 0, int64(objSize), ioutil.Discard, "")
if err != nil { if err != nil {
b.Error(err) b.Error(err)
} }

View File

@ -301,16 +301,9 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
} }
return return
} }
objectLock := globalNSMutex.NewNSLock(bucket, obj.ObjectName) dErr := objectAPI.DeleteObject(bucket, obj.ObjectName)
if timedOutErr := objectLock.GetLock(globalObjectTimeout); timedOutErr != nil { if dErr != nil {
dErrs[i] = timedOutErr dErrs[i] = dErr
} else {
defer objectLock.Unlock()
dErr := objectAPI.DeleteObject(bucket, obj.ObjectName)
if dErr != nil {
dErrs[i] = dErr
}
} }
}(index, object) }(index, object)
} }
@ -404,13 +397,6 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
return return
} }
bucketLock := globalNSMutex.NewNSLock(bucket, "")
if bucketLock.GetLock(globalObjectTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer bucketLock.Unlock()
// Proceed to creating a bucket. // Proceed to creating a bucket.
err := objectAPI.MakeBucketWithLocation(bucket, "") err := objectAPI.MakeBucketWithLocation(bucket, "")
if err != nil { if err != nil {
@ -559,13 +545,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
objectLock := globalNSMutex.NewNSLock(bucket, object)
if objectLock.GetLock(globalObjectTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer objectLock.Unlock()
hashReader, err := hash.NewReader(fileBody, fileSize, "", "") hashReader, err := hash.NewReader(fileBody, fileSize, "", "")
if err != nil { if err != nil {
errorIf(err, "Unable to initialize hashReader.") errorIf(err, "Unable to initialize hashReader.")

View File

@ -148,15 +148,8 @@ func initBucketPolicies(objAPI ObjectLayer) error {
func readBucketPolicyJSON(bucket string, objAPI ObjectLayer) (bucketPolicyReader io.Reader, err error) { func readBucketPolicyJSON(bucket string, objAPI ObjectLayer) (bucketPolicyReader io.Reader, err error) {
policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig) policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig)
// Acquire a read lock on policy config before reading.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, policyPath)
if err = objLock.GetRLock(globalOperationTimeout); err != nil {
return nil, err
}
defer objLock.RUnlock()
var buffer bytes.Buffer var buffer bytes.Buffer
err = objAPI.GetObject(minioMetaBucket, policyPath, 0, -1, &buffer) err = objAPI.GetObject(minioMetaBucket, policyPath, 0, -1, &buffer, "")
if err != nil { if err != nil {
if isErrObjectNotFound(err) || isErrIncompleteBody(err) { if isErrObjectNotFound(err) || isErrIncompleteBody(err) {
return nil, BucketPolicyNotFound{Bucket: bucket} return nil, BucketPolicyNotFound{Bucket: bucket}
@ -190,12 +183,6 @@ func readBucketPolicy(bucket string, objAPI ObjectLayer) (policy.BucketAccessPol
// if no policies are found. // if no policies are found.
func removeBucketPolicy(bucket string, objAPI ObjectLayer) error { func removeBucketPolicy(bucket string, objAPI ObjectLayer) error {
policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig) policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig)
// Acquire a write lock on policy config before modifying.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, policyPath)
if err := objLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objLock.Unlock()
err := objAPI.DeleteObject(minioMetaBucket, policyPath) err := objAPI.DeleteObject(minioMetaBucket, policyPath)
if err != nil { if err != nil {
err = errors.Cause(err) err = errors.Cause(err)
@ -215,13 +202,6 @@ func writeBucketPolicy(bucket string, objAPI ObjectLayer, bpy policy.BucketAcces
return err return err
} }
policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig) policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig)
// Acquire a write lock on policy config before modifying.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, policyPath)
if err = objLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objLock.Unlock()
hashReader, err := hash.NewReader(bytes.NewReader(buf), int64(len(buf)), "", getSHA256Hash(buf)) hashReader, err := hash.NewReader(bytes.NewReader(buf), int64(len(buf)), "", getSHA256Hash(buf))
if err != nil { if err != nil {
errorIf(err, "Unable to set policy for the bucket %s", bucket) errorIf(err, "Unable to set policy for the bucket %s", bucket)

View File

@ -365,15 +365,8 @@ func loadNotificationConfig(bucket string, objAPI ObjectLayer) (*notificationCon
// Construct the notification config path. // Construct the notification config path.
ncPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig) ncPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig)
// Acquire a write lock on notification config before modifying.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, ncPath)
if err := objLock.GetRLock(globalOperationTimeout); err != nil {
return nil, err
}
defer objLock.RUnlock()
var buffer bytes.Buffer var buffer bytes.Buffer
err := objAPI.GetObject(minioMetaBucket, ncPath, 0, -1, &buffer) // Read everything. err := objAPI.GetObject(minioMetaBucket, ncPath, 0, -1, &buffer, "") // Read everything.
if err != nil { if err != nil {
// 'notification.xml' not found return // 'notification.xml' not found return
// 'errNoSuchNotifications'. This is default when no // 'errNoSuchNotifications'. This is default when no
@ -416,15 +409,8 @@ func loadListenerConfig(bucket string, objAPI ObjectLayer) ([]listenerConfig, er
// Construct the notification config path. // Construct the notification config path.
lcPath := path.Join(bucketConfigPrefix, bucket, bucketListenerConfig) lcPath := path.Join(bucketConfigPrefix, bucket, bucketListenerConfig)
// Acquire a write lock on notification config before modifying.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, lcPath)
if err := objLock.GetRLock(globalOperationTimeout); err != nil {
return nil, err
}
defer objLock.RUnlock()
var buffer bytes.Buffer var buffer bytes.Buffer
err := objAPI.GetObject(minioMetaBucket, lcPath, 0, -1, &buffer) err := objAPI.GetObject(minioMetaBucket, lcPath, 0, -1, &buffer, "")
if err != nil { if err != nil {
// 'listener.json' not found return // 'listener.json' not found return
// 'errNoSuchNotifications'. This is default when no // 'errNoSuchNotifications'. This is default when no
@ -463,12 +449,6 @@ func persistNotificationConfig(bucket string, ncfg *notificationConfig, obj Obje
// build path // build path
ncPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig) ncPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig)
// Acquire a write lock on notification config before modifying.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, ncPath)
if err = objLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objLock.Unlock()
// write object to path // write object to path
hashReader, err := hash.NewReader(bytes.NewReader(buf), int64(len(buf)), "", getSHA256Hash(buf)) hashReader, err := hash.NewReader(bytes.NewReader(buf), int64(len(buf)), "", getSHA256Hash(buf))
@ -494,12 +474,6 @@ func persistListenerConfig(bucket string, lcfg []listenerConfig, obj ObjectLayer
// build path // build path
lcPath := path.Join(bucketConfigPrefix, bucket, bucketListenerConfig) lcPath := path.Join(bucketConfigPrefix, bucket, bucketListenerConfig)
// Acquire a write lock on notification config before modifying.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, lcPath)
if err = objLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objLock.Unlock()
// write object to path // write object to path
hashReader, err := hash.NewReader(bytes.NewReader(buf), int64(len(buf)), "", getSHA256Hash(buf)) hashReader, err := hash.NewReader(bytes.NewReader(buf), int64(len(buf)), "", getSHA256Hash(buf))
@ -526,12 +500,6 @@ func removeNotificationConfig(bucket string, objAPI ObjectLayer) error {
ncPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig) ncPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig)
// Acquire a write lock on notification config before modifying.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, ncPath)
if err := objLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objLock.Unlock()
return objAPI.DeleteObject(minioMetaBucket, ncPath) return objAPI.DeleteObject(minioMetaBucket, ncPath)
} }
@ -540,12 +508,6 @@ func removeListenerConfig(bucket string, objAPI ObjectLayer) error {
// make the path // make the path
lcPath := path.Join(bucketConfigPrefix, bucket, bucketListenerConfig) lcPath := path.Join(bucketConfigPrefix, bucket, bucketListenerConfig)
// Acquire a write lock on notification config before modifying.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, lcPath)
if err := objLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objLock.Unlock()
return objAPI.DeleteObject(minioMetaBucket, lcPath) return objAPI.DeleteObject(minioMetaBucket, lcPath)
} }

View File

@ -109,7 +109,7 @@ func (fs fsObjects) listMultipartUploadIDs(bucketName, objectName, uploadIDMarke
// Hold the lock so that two parallel complete-multipart-uploads // Hold the lock so that two parallel complete-multipart-uploads
// do not leave a stale uploads.json behind. // do not leave a stale uploads.json behind.
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucketName, objectName)) objectMPartPathLock := fs.nsMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucketName, objectName))
if err := objectMPartPathLock.GetRLock(globalListingTimeout); err != nil { if err := objectMPartPathLock.GetRLock(globalListingTimeout); err != nil {
return nil, false, errors.Trace(err) return nil, false, errors.Trace(err)
} }
@ -408,7 +408,7 @@ func (fs fsObjects) NewMultipartUpload(bucket, object string, meta map[string]st
// Hold the lock so that two parallel complete-multipart-uploads // Hold the lock so that two parallel complete-multipart-uploads
// do not leave a stale uploads.json behind. // do not leave a stale uploads.json behind.
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object)) objectMPartPathLock := fs.nsMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object))
if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil { if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil {
return "", err return "", err
} }
@ -437,17 +437,34 @@ func partToAppend(fsMeta fsMetaV1, fsAppendMeta fsMetaV1) (part objectPartInfo,
// object. Internally incoming data is written to '.minio.sys/tmp' location // object. Internally incoming data is written to '.minio.sys/tmp' location
// and safely renamed to '.minio.sys/multipart' for reach parts. // and safely renamed to '.minio.sys/multipart' for reach parts.
func (fs fsObjects) CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, uploadID string, partID int, func (fs fsObjects) CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, uploadID string, partID int,
startOffset int64, length int64, metadata map[string]string) (pi PartInfo, e error) { startOffset int64, length int64, metadata map[string]string, srcEtag string) (pi PartInfo, e error) {
// Hold read locks on source object only if we are
// going to read data from source object.
objectSRLock := fs.nsMutex.NewNSLock(srcBucket, srcObject)
if err := objectSRLock.GetRLock(globalObjectTimeout); err != nil {
return pi, err
}
defer objectSRLock.RUnlock()
if err := checkNewMultipartArgs(srcBucket, srcObject, fs); err != nil { if err := checkNewMultipartArgs(srcBucket, srcObject, fs); err != nil {
return pi, err return pi, err
} }
if srcEtag != "" {
etag, err := fs.getObjectETag(srcBucket, srcObject)
if err != nil {
return pi, toObjectErr(err, srcBucket, srcObject)
}
if etag != srcEtag {
return pi, toObjectErr(errors.Trace(InvalidETag{}), srcBucket, srcObject)
}
}
// Initialize pipe. // Initialize pipe.
pipeReader, pipeWriter := io.Pipe() pipeReader, pipeWriter := io.Pipe()
go func() { go func() {
if gerr := fs.GetObject(srcBucket, srcObject, startOffset, length, pipeWriter); gerr != nil { if gerr := fs.getObject(srcBucket, srcObject, startOffset, length, pipeWriter, srcEtag); gerr != nil {
errorIf(gerr, "Unable to read %s/%s.", srcBucket, srcObject) errorIf(gerr, "Unable to read %s/%s.", srcBucket, srcObject)
pipeWriter.CloseWithError(gerr) pipeWriter.CloseWithError(gerr)
return return
@ -491,7 +508,7 @@ func (fs fsObjects) PutObjectPart(bucket, object, uploadID string, partID int, d
// Hold the lock so that two parallel complete-multipart-uploads // Hold the lock so that two parallel complete-multipart-uploads
// do not leave a stale uploads.json behind. // do not leave a stale uploads.json behind.
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object)) objectMPartPathLock := fs.nsMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object))
if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil { if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil {
return pi, err return pi, err
} }
@ -557,7 +574,7 @@ func (fs fsObjects) PutObjectPart(bucket, object, uploadID string, partID int, d
partPath := pathJoin(bucket, object, uploadID, partSuffix) partPath := pathJoin(bucket, object, uploadID, partSuffix)
// Lock the part so that another part upload with same part-number gets blocked // Lock the part so that another part upload with same part-number gets blocked
// while the part is getting appended in the background. // while the part is getting appended in the background.
partLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, partPath) partLock := fs.nsMutex.NewNSLock(minioMetaMultipartBucket, partPath)
if err = partLock.GetLock(globalOperationTimeout); err != nil { if err = partLock.GetLock(globalOperationTimeout); err != nil {
return pi, err return pi, err
} }
@ -689,7 +706,7 @@ func (fs fsObjects) ListObjectParts(bucket, object, uploadID string, partNumberM
// Hold the lock so that two parallel complete-multipart-uploads // Hold the lock so that two parallel complete-multipart-uploads
// do not leave a stale uploads.json behind. // do not leave a stale uploads.json behind.
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object)) objectMPartPathLock := fs.nsMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object))
if err := objectMPartPathLock.GetRLock(globalListingTimeout); err != nil { if err := objectMPartPathLock.GetRLock(globalListingTimeout); err != nil {
return lpi, errors.Trace(err) return lpi, errors.Trace(err)
} }
@ -714,6 +731,12 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
return oi, err return oi, err
} }
// Hold write lock on the object.
destLock := fs.nsMutex.NewNSLock(bucket, object)
if err := destLock.GetLock(globalObjectTimeout); err != nil {
return oi, err
}
defer destLock.Unlock()
// Check if an object is present as one of the parent dir. // Check if an object is present as one of the parent dir.
if fs.parentDirIsObject(bucket, pathutil.Dir(object)) { if fs.parentDirIsObject(bucket, pathutil.Dir(object)) {
return oi, toObjectErr(errors.Trace(errFileAccessDenied), bucket, object) return oi, toObjectErr(errors.Trace(errFileAccessDenied), bucket, object)
@ -734,7 +757,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
// Hold the lock so that two parallel complete-multipart-uploads // Hold the lock so that two parallel complete-multipart-uploads
// do not leave a stale uploads.json behind. // do not leave a stale uploads.json behind.
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object)) objectMPartPathLock := fs.nsMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object))
if err = objectMPartPathLock.GetLock(globalOperationTimeout); err != nil { if err = objectMPartPathLock.GetLock(globalOperationTimeout); err != nil {
return oi, err return oi, err
} }
@ -974,7 +997,7 @@ func (fs fsObjects) AbortMultipartUpload(bucket, object, uploadID string) error
// Hold the lock so that two parallel complete-multipart-uploads // Hold the lock so that two parallel complete-multipart-uploads
// do not leave a stale uploads.json behind. // do not leave a stale uploads.json behind.
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, objectMPartPathLock := fs.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, object)) pathJoin(bucket, object))
if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil { if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil {
return err return err

View File

@ -26,6 +26,7 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"syscall" "syscall"
"time"
"github.com/minio/minio/pkg/errors" "github.com/minio/minio/pkg/errors"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
@ -52,6 +53,9 @@ type fsObjects struct {
// To manage the appendRoutine go0routines // To manage the appendRoutine go0routines
bgAppend *backgroundAppend bgAppend *backgroundAppend
// name space mutex for object layer
nsMutex *nsLockMap
} }
// Initializes meta volume on all the fs path. // Initializes meta volume on all the fs path.
@ -138,6 +142,7 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) {
bgAppend: &backgroundAppend{ bgAppend: &backgroundAppend{
infoMap: make(map[string]bgAppendPartsInfo), infoMap: make(map[string]bgAppendPartsInfo),
}, },
nsMutex: newNSLock(false),
} }
// Once the filesystem has initialized hold the read lock for // Once the filesystem has initialized hold the read lock for
@ -183,6 +188,18 @@ func (fs fsObjects) StorageInfo() StorageInfo {
return storageInfo return storageInfo
} }
// Locking operations
// List namespace locks held in object layer
func (fs fsObjects) ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
return []VolumeLockInfo{}, NotImplemented{}
}
// Clear namespace locks held in object layer
func (fs fsObjects) ClearLocks([]VolumeLockInfo) error {
return NotImplemented{}
}
/// Bucket operations /// Bucket operations
// getBucketDir - will convert incoming bucket names to // getBucketDir - will convert incoming bucket names to
@ -213,6 +230,11 @@ func (fs fsObjects) statBucketDir(bucket string) (os.FileInfo, error) {
// MakeBucket - create a new bucket, returns if it // MakeBucket - create a new bucket, returns if it
// already exists. // already exists.
func (fs fsObjects) MakeBucketWithLocation(bucket, location string) error { func (fs fsObjects) MakeBucketWithLocation(bucket, location string) error {
bucketLock := fs.nsMutex.NewNSLock(bucket, "")
if err := bucketLock.GetLock(globalObjectTimeout); err != nil {
return err
}
defer bucketLock.Unlock()
bucketDir, err := fs.getBucketDir(bucket) bucketDir, err := fs.getBucketDir(bucket)
if err != nil { if err != nil {
return toObjectErr(err, bucket) return toObjectErr(err, bucket)
@ -313,7 +335,29 @@ func (fs fsObjects) DeleteBucket(bucket string) error {
// CopyObject - copy object source object to destination object. // CopyObject - copy object source object to destination object.
// if source object and destination object are same we only // if source object and destination object are same we only
// update metadata. // update metadata.
func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string, metadata map[string]string) (oi ObjectInfo, e error) { func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string, metadata map[string]string, srcEtag string) (oi ObjectInfo, e error) {
cpSrcDstSame := srcBucket == dstBucket && srcObject == dstObject
// Hold write lock on destination since in both cases
// - if source and destination are same
// - if source and destination are different
// it is the sole mutating state.
objectDWLock := fs.nsMutex.NewNSLock(dstBucket, dstObject)
if err := objectDWLock.GetLock(globalObjectTimeout); err != nil {
return oi, err
}
defer objectDWLock.Unlock()
// if source and destination are different, we have to hold
// additional read lock as well to protect against writes on
// source.
if !cpSrcDstSame {
// Hold read locks on source object only if we are
// going to read data from source object.
objectSRLock := fs.nsMutex.NewNSLock(srcBucket, srcObject)
if err := objectSRLock.GetRLock(globalObjectTimeout); err != nil {
return oi, err
}
defer objectSRLock.RUnlock()
}
if _, err := fs.statBucketDir(srcBucket); err != nil { if _, err := fs.statBucketDir(srcBucket); err != nil {
return oi, toObjectErr(err, srcBucket) return oi, toObjectErr(err, srcBucket)
} }
@ -323,6 +367,15 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string
if err != nil { if err != nil {
return oi, toObjectErr(err, srcBucket, srcObject) return oi, toObjectErr(err, srcBucket, srcObject)
} }
if srcEtag != "" {
etag, perr := fs.getObjectETag(srcBucket, srcObject)
if perr != nil {
return oi, toObjectErr(perr, srcBucket, srcObject)
}
if etag != srcEtag {
return oi, toObjectErr(errors.Trace(InvalidETag{}), srcBucket, srcObject)
}
}
// Check if this request is only metadata update. // Check if this request is only metadata update.
cpMetadataOnly := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) cpMetadataOnly := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject))
@ -355,7 +408,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string
go func() { go func() {
var startOffset int64 // Read the whole file. var startOffset int64 // Read the whole file.
if gerr := fs.GetObject(srcBucket, srcObject, startOffset, length, pipeWriter); gerr != nil { if gerr := fs.getObject(srcBucket, srcObject, startOffset, length, pipeWriter, ""); gerr != nil {
errorIf(gerr, "Unable to read %s/%s.", srcBucket, srcObject) errorIf(gerr, "Unable to read %s/%s.", srcBucket, srcObject)
pipeWriter.CloseWithError(gerr) pipeWriter.CloseWithError(gerr)
return return
@ -368,7 +421,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string
return oi, toObjectErr(err, dstBucket, dstObject) return oi, toObjectErr(err, dstBucket, dstObject)
} }
objInfo, err := fs.PutObject(dstBucket, dstObject, hashReader, metadata) objInfo, err := fs.putObject(dstBucket, dstObject, hashReader, metadata)
if err != nil { if err != nil {
return oi, toObjectErr(err, dstBucket, dstObject) return oi, toObjectErr(err, dstBucket, dstObject)
} }
@ -385,11 +438,22 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string
// //
// startOffset indicates the starting read location of the object. // startOffset indicates the starting read location of the object.
// length indicates the total length of the object. // length indicates the total length of the object.
func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, writer io.Writer) (err error) { func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, writer io.Writer, etag string) (err error) {
if err = checkBucketAndObjectNamesFS(bucket, object); err != nil { if err = checkBucketAndObjectNamesFS(bucket, object); err != nil {
return err return err
} }
// Lock the object before reading.
objectLock := fs.nsMutex.NewNSLock(bucket, object)
if err := objectLock.GetRLock(globalObjectTimeout); err != nil {
return err
}
defer objectLock.RUnlock()
return fs.getObject(bucket, object, offset, length, writer, etag)
}
// getObject - wrapper for GetObject
func (fs fsObjects) getObject(bucket, object string, offset int64, length int64, writer io.Writer, etag string) (err error) {
if _, err = fs.statBucketDir(bucket); err != nil { if _, err = fs.statBucketDir(bucket); err != nil {
return toObjectErr(err, bucket) return toObjectErr(err, bucket)
} }
@ -419,6 +483,15 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64,
defer fs.rwPool.Close(fsMetaPath) defer fs.rwPool.Close(fsMetaPath)
} }
if etag != "" {
objEtag, perr := fs.getObjectETag(bucket, object)
if perr != nil {
return toObjectErr(errors.Trace(perr), bucket, object)
}
if objEtag != etag {
return toObjectErr(errors.Trace(InvalidETag{}), bucket, object)
}
}
// Read the object, doesn't exist returns an s3 compatible error. // Read the object, doesn't exist returns an s3 compatible error.
fsObjPath := pathJoin(fs.fsPath, bucket, object) fsObjPath := pathJoin(fs.fsPath, bucket, object)
reader, size, err := fsOpenFile(fsObjPath, offset) reader, size, err := fsOpenFile(fsObjPath, offset)
@ -516,6 +589,13 @@ func checkBucketAndObjectNamesFS(bucket, object string) error {
// GetObjectInfo - reads object metadata and replies back ObjectInfo. // GetObjectInfo - reads object metadata and replies back ObjectInfo.
func (fs fsObjects) GetObjectInfo(bucket, object string) (oi ObjectInfo, e error) { func (fs fsObjects) GetObjectInfo(bucket, object string) (oi ObjectInfo, e error) {
// Lock the object before reading.
objectLock := fs.nsMutex.NewNSLock(bucket, object)
if err := objectLock.GetRLock(globalObjectTimeout); err != nil {
return oi, err
}
defer objectLock.RUnlock()
if err := checkBucketAndObjectNamesFS(bucket, object); err != nil { if err := checkBucketAndObjectNamesFS(bucket, object); err != nil {
return oi, err return oi, err
} }
@ -552,11 +632,21 @@ func (fs fsObjects) parentDirIsObject(bucket, parent string) bool {
// Additionally writes `fs.json` which carries the necessary metadata // Additionally writes `fs.json` which carries the necessary metadata
// for future object operations. // for future object operations.
func (fs fsObjects) PutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, retErr error) { func (fs fsObjects) PutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, retErr error) {
// Lock the object.
objectLock := fs.nsMutex.NewNSLock(bucket, object)
if err := objectLock.GetLock(globalObjectTimeout); err != nil {
return objInfo, err
}
defer objectLock.Unlock()
return fs.putObject(bucket, object, data, metadata)
}
// putObject - wrapper for PutObject
func (fs fsObjects) putObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, retErr error) {
// No metadata is set, allocate a new one. // No metadata is set, allocate a new one.
if metadata == nil { if metadata == nil {
metadata = make(map[string]string) metadata = make(map[string]string)
} }
var err error var err error
// Validate if bucket name is valid and exists. // Validate if bucket name is valid and exists.
@ -677,6 +767,13 @@ func (fs fsObjects) PutObject(bucket string, object string, data *hash.Reader, m
// DeleteObject - deletes an object from a bucket, this operation is destructive // DeleteObject - deletes an object from a bucket, this operation is destructive
// and there are no rollbacks supported. // and there are no rollbacks supported.
func (fs fsObjects) DeleteObject(bucket, object string) error { func (fs fsObjects) DeleteObject(bucket, object string) error {
// Acquire a write lock before deleting the object.
objectLock := fs.nsMutex.NewNSLock(bucket, object)
if err := objectLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objectLock.Unlock()
if err := checkBucketAndObjectNamesFS(bucket, object); err != nil { if err := checkBucketAndObjectNamesFS(bucket, object); err != nil {
return err return err
} }
@ -825,7 +922,7 @@ func (fs fsObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKey
// Convert entry to ObjectInfo // Convert entry to ObjectInfo
entryToObjectInfo := func(entry string) (objInfo ObjectInfo, err error) { entryToObjectInfo := func(entry string) (objInfo ObjectInfo, err error) {
// Protect the entry from concurrent deletes, or renames. // Protect the entry from concurrent deletes, or renames.
objectLock := globalNSMutex.NewNSLock(bucket, entry) objectLock := fs.nsMutex.NewNSLock(bucket, entry)
if err = objectLock.GetRLock(globalListingTimeout); err != nil { if err = objectLock.GetRLock(globalListingTimeout); err != nil {
return ObjectInfo{}, err return ObjectInfo{}, err
} }

View File

@ -128,7 +128,7 @@ func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Re
setHeadGetRespHeaders(w, r.URL.Query()) setHeadGetRespHeaders(w, r.URL.Query())
httpWriter := ioutil.WriteOnClose(w) httpWriter := ioutil.WriteOnClose(w)
// Reads the object at startOffset and writes to mw. // Reads the object at startOffset and writes to mw.
if err = getObject(bucket, object, startOffset, length, httpWriter); err != nil { if err = getObject(bucket, object, startOffset, length, httpWriter, objInfo.ETag); err != nil {
errorIf(err, "Unable to write to client.") errorIf(err, "Unable to write to client.")
if !httpWriter.HasWritten() { if !httpWriter.HasWritten() {
// Error response only if no data has been written to client yet. i.e if // Error response only if no data has been written to client yet. i.e if
@ -250,14 +250,6 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re
} }
} }
// Lock the object.
objectLock := globalNSMutex.NewNSLock(bucket, object)
if objectLock.GetLock(globalOperationTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer objectLock.Unlock()
var ( var (
// Make sure we hex encode md5sum here. // Make sure we hex encode md5sum here.
md5hex = hex.EncodeToString(md5Bytes) md5hex = hex.EncodeToString(md5Bytes)

View File

@ -45,7 +45,7 @@ type Gateway interface {
type GatewayLayer interface { type GatewayLayer interface {
ObjectLayer ObjectLayer
AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer, etag string) (err error)
AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error)
AnonPutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (ObjectInfo, error) AnonPutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (ObjectInfo, error)

View File

@ -18,6 +18,7 @@ package cmd
import ( import (
"io" "io"
"time"
"github.com/minio/minio-go/pkg/policy" "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/pkg/errors" "github.com/minio/minio/pkg/errors"
@ -38,7 +39,7 @@ func (a GatewayUnsupported) NewMultipartUpload(bucket string, object string, met
} }
// CopyObjectPart copy part of object to other bucket and object // CopyObjectPart copy part of object to other bucket and object
func (a GatewayUnsupported) CopyObjectPart(srcBucket string, srcObject string, destBucket string, destObject string, uploadID string, partID int, startOffset int64, length int64, metadata map[string]string) (pi PartInfo, err error) { func (a GatewayUnsupported) CopyObjectPart(srcBucket string, srcObject string, destBucket string, destObject string, uploadID string, partID int, startOffset int64, length int64, metadata map[string]string, srcEtag string) (pi PartInfo, err error) {
return pi, errors.Trace(NotImplemented{}) return pi, errors.Trace(NotImplemented{})
} }
@ -132,7 +133,7 @@ func (a GatewayUnsupported) AnonPutObject(bucket, object string, data *hash.Read
} }
// AnonGetObject downloads object anonymously. // AnonGetObject downloads object anonymously.
func (a GatewayUnsupported) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) { func (a GatewayUnsupported) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer, etag string) (err error) {
return errors.Trace(NotImplemented{}) return errors.Trace(NotImplemented{})
} }
@ -143,6 +144,18 @@ func (a GatewayUnsupported) AnonGetObjectInfo(bucket, object string) (objInfo Ob
// CopyObject copies a blob from source container to destination container. // CopyObject copies a blob from source container to destination container.
func (a GatewayUnsupported) CopyObject(srcBucket string, srcObject string, destBucket string, destObject string, func (a GatewayUnsupported) CopyObject(srcBucket string, srcObject string, destBucket string, destObject string,
metadata map[string]string) (objInfo ObjectInfo, err error) { metadata map[string]string, srcEtag string) (objInfo ObjectInfo, err error) {
return objInfo, errors.Trace(NotImplemented{}) return objInfo, errors.Trace(NotImplemented{})
} }
// Locking operations
// ListLocks lists namespace locks held in object layer
func (a GatewayUnsupported) ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
return []VolumeLockInfo{}, errors.Trace(NotImplemented{})
}
// ClearLocks clears namespace locks held in object layer
func (a GatewayUnsupported) ClearLocks([]VolumeLockInfo) error {
return errors.Trace(NotImplemented{})
}

View File

@ -145,7 +145,7 @@ func (a *azureObjects) AnonGetBucketInfo(bucket string) (bucketInfo minio.Bucket
// AnonGetObject - SendGET request without authentication. // AnonGetObject - SendGET request without authentication.
// This is needed when clients send GET requests on objects that can be downloaded without auth. // This is needed when clients send GET requests on objects that can be downloaded without auth.
func (a *azureObjects) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) { func (a *azureObjects) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer, etag string) (err error) {
h := make(http.Header) h := make(http.Header)
if length > 0 && startOffset > 0 { if length > 0 && startOffset > 0 {
h.Add("Range", fmt.Sprintf("bytes=%d-%d", startOffset, startOffset+length-1)) h.Add("Range", fmt.Sprintf("bytes=%d-%d", startOffset, startOffset+length-1))

View File

@ -554,7 +554,7 @@ func (a *azureObjects) ListObjectsV2(bucket, prefix, continuationToken, delimite
// //
// startOffset indicates the starting read location of the object. // startOffset indicates the starting read location of the object.
// length indicates the total length of the object. // length indicates the total length of the object.
func (a *azureObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) error { func (a *azureObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer, etag string) error {
// startOffset cannot be negative. // startOffset cannot be negative.
if startOffset < 0 { if startOffset < 0 {
return azureToObjectError(errors.Trace(minio.InvalidRange{}), bucket, object) return azureToObjectError(errors.Trace(minio.InvalidRange{}), bucket, object)
@ -621,7 +621,7 @@ func (a *azureObjects) PutObject(bucket, object string, data *hash.Reader, metad
// CopyObject - Copies a blob from source container to destination container. // CopyObject - Copies a blob from source container to destination container.
// Uses Azure equivalent CopyBlob API. // Uses Azure equivalent CopyBlob API.
func (a *azureObjects) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) { func (a *azureObjects) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string, srcEtag string) (objInfo minio.ObjectInfo, err error) {
srcBlobURL := a.client.GetContainerReference(srcBucket).GetBlobReference(srcObject).GetURL() srcBlobURL := a.client.GetContainerReference(srcBucket).GetBlobReference(srcObject).GetURL()
destBlob := a.client.GetContainerReference(destBucket).GetBlobReference(destObject) destBlob := a.client.GetContainerReference(destBucket).GetBlobReference(destObject)
azureMeta, props, err := s3MetaToAzureProperties(metadata) azureMeta, props, err := s3MetaToAzureProperties(metadata)

View File

@ -43,7 +43,7 @@ func mkRange(offset, size int64) string {
// AnonGetObject - performs a plain http GET request on a public resource, // AnonGetObject - performs a plain http GET request on a public resource,
// fails if the resource is not public. // fails if the resource is not public.
func (l *b2Objects) AnonGetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer) error { func (l *b2Objects) AnonGetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer, etag string) error {
uri := fmt.Sprintf("%s/file/%s/%s", l.b2Client.DownloadURI, bucket, object) uri := fmt.Sprintf("%s/file/%s/%s", l.b2Client.DownloadURI, bucket, object)
req, err := http.NewRequest("GET", uri, nil) req, err := http.NewRequest("GET", uri, nil)
if err != nil { if err != nil {

View File

@ -379,7 +379,7 @@ func (l *b2Objects) ListObjectsV2(bucket, prefix, continuationToken, delimiter s
// //
// startOffset indicates the starting read location of the object. // startOffset indicates the starting read location of the object.
// length indicates the total length of the object. // length indicates the total length of the object.
func (l *b2Objects) GetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer) error { func (l *b2Objects) GetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer, etag string) error {
bkt, err := l.Bucket(bucket) bkt, err := l.Bucket(bucket)
if err != nil { if err != nil {
return err return err
@ -523,7 +523,7 @@ func (l *b2Objects) PutObject(bucket string, object string, data *h2.Reader, met
// CopyObject copies a blob from source container to destination container. // CopyObject copies a blob from source container to destination container.
func (l *b2Objects) CopyObject(srcBucket string, srcObject string, dstBucket string, func (l *b2Objects) CopyObject(srcBucket string, srcObject string, dstBucket string,
dstObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) { dstObject string, metadata map[string]string, srcEtag string) (objInfo minio.ObjectInfo, err error) {
return objInfo, errors.Trace(minio.NotImplemented{}) return objInfo, errors.Trace(minio.NotImplemented{})
} }

View File

@ -33,7 +33,7 @@ func toGCSPublicURL(bucket, object string) string {
} }
// AnonGetObject - Get object anonymously // AnonGetObject - Get object anonymously
func (l *gcsGateway) AnonGetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer) error { func (l *gcsGateway) AnonGetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer, etag string) error {
req, err := http.NewRequest("GET", toGCSPublicURL(bucket, object), nil) req, err := http.NewRequest("GET", toGCSPublicURL(bucket, object), nil)
if err != nil { if err != nil {
return gcsToObjectError(errors.Trace(err), bucket, object) return gcsToObjectError(errors.Trace(err), bucket, object)

View File

@ -725,7 +725,7 @@ func (l *gcsGateway) ListObjectsV2(bucket, prefix, continuationToken, delimiter
// //
// startOffset indicates the starting read location of the object. // startOffset indicates the starting read location of the object.
// length indicates the total length of the object. // length indicates the total length of the object.
func (l *gcsGateway) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { func (l *gcsGateway) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer, etag string) error {
// if we want to mimic S3 behavior exactly, we need to verify if bucket exists first, // if we want to mimic S3 behavior exactly, we need to verify if bucket exists first,
// otherwise gcs will just return object not exist in case of non-existing bucket // otherwise gcs will just return object not exist in case of non-existing bucket
if _, err := l.client.Bucket(bucket).Attrs(l.ctx); err != nil { if _, err := l.client.Bucket(bucket).Attrs(l.ctx); err != nil {
@ -813,7 +813,7 @@ func (l *gcsGateway) PutObject(bucket string, key string, data *hash.Reader, met
// CopyObject - Copies a blob from source container to destination container. // CopyObject - Copies a blob from source container to destination container.
func (l *gcsGateway) CopyObject(srcBucket string, srcObject string, destBucket string, destObject string, func (l *gcsGateway) CopyObject(srcBucket string, srcObject string, destBucket string, destObject string,
metadata map[string]string) (minio.ObjectInfo, error) { metadata map[string]string, srcEtag string) (minio.ObjectInfo, error) {
src := l.client.Bucket(srcBucket).Object(srcObject) src := l.client.Bucket(srcBucket).Object(srcObject)
dst := l.client.Bucket(destBucket).Object(destObject) dst := l.client.Bucket(destBucket).Object(destObject)

View File

@ -468,7 +468,7 @@ func (t *tritonObjects) ListObjectsV2(bucket, prefix, continuationToken, delimit
// indicates the total length of the object. // indicates the total length of the object.
// //
// https://apidocs.joyent.com/manta/api.html#GetObject // https://apidocs.joyent.com/manta/api.html#GetObject
func (t *tritonObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) error { func (t *tritonObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer, etag string) error {
// Start offset cannot be negative. // Start offset cannot be negative.
if startOffset < 0 { if startOffset < 0 {
return errors.Trace(fmt.Errorf("Unexpected error")) return errors.Trace(fmt.Errorf("Unexpected error"))
@ -568,7 +568,7 @@ func (t *tritonObjects) PutObject(bucket, object string, data *hash.Reader, meta
// Uses Manta Snaplinks API. // Uses Manta Snaplinks API.
// //
// https://apidocs.joyent.com/manta/api.html#PutSnapLink // https://apidocs.joyent.com/manta/api.html#PutSnapLink
func (t *tritonObjects) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) { func (t *tritonObjects) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string, srcEtag string) (objInfo minio.ObjectInfo, err error) {
ctx := context.Background() ctx := context.Background()
if err = t.client.SnapLinks().Put(ctx, &storage.PutSnapLinkInput{ if err = t.client.SnapLinks().Put(ctx, &storage.PutSnapLinkInput{
SourcePath: path.Join(mantaRoot, srcBucket, srcObject), SourcePath: path.Join(mantaRoot, srcBucket, srcObject),

View File

@ -29,8 +29,8 @@ func (l *ossObjects) AnonPutObject(bucket, object string, data *hash.Reader, met
} }
// AnonGetObject - Get object anonymously // AnonGetObject - Get object anonymously
func (l *ossObjects) AnonGetObject(bucket, key string, startOffset, length int64, writer io.Writer) error { func (l *ossObjects) AnonGetObject(bucket, key string, startOffset, length int64, writer io.Writer, etag string) error {
return ossGetObject(l.anonClient, bucket, key, startOffset, length, writer) return ossGetObject(l.anonClient, bucket, key, startOffset, length, writer, etag)
} }
// AnonGetObjectInfo - Get object info anonymously // AnonGetObjectInfo - Get object info anonymously

View File

@ -506,7 +506,7 @@ func (l *ossObjects) ListObjectsV2(bucket, prefix, continuationToken, delimiter
// //
// startOffset indicates the starting read location of the object. // startOffset indicates the starting read location of the object.
// length indicates the total length of the object. // length indicates the total length of the object.
func ossGetObject(client *oss.Client, bucket, key string, startOffset, length int64, writer io.Writer) error { func ossGetObject(client *oss.Client, bucket, key string, startOffset, length int64, writer io.Writer, etag string) error {
if length < 0 && length != -1 { if length < 0 && length != -1 {
return ossToObjectError(errors.Trace(fmt.Errorf("Invalid argument")), bucket, key) return ossToObjectError(errors.Trace(fmt.Errorf("Invalid argument")), bucket, key)
} }
@ -539,8 +539,8 @@ func ossGetObject(client *oss.Client, bucket, key string, startOffset, length in
// //
// startOffset indicates the starting read location of the object. // startOffset indicates the starting read location of the object.
// length indicates the total length of the object. // length indicates the total length of the object.
func (l *ossObjects) GetObject(bucket, key string, startOffset, length int64, writer io.Writer) error { func (l *ossObjects) GetObject(bucket, key string, startOffset, length int64, writer io.Writer, etag string) error {
return ossGetObject(l.Client, bucket, key, startOffset, length, writer) return ossGetObject(l.Client, bucket, key, startOffset, length, writer, etag)
} }
func translatePlainError(err error) error { func translatePlainError(err error) error {
@ -618,7 +618,7 @@ func (l *ossObjects) PutObject(bucket, object string, data *hash.Reader, metadat
} }
// CopyObject copies an object from source bucket to a destination bucket. // CopyObject copies an object from source bucket to a destination bucket.
func (l *ossObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) { func (l *ossObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string, metadata map[string]string, srcEtag string) (objInfo minio.ObjectInfo, err error) {
bkt, err := l.Client.Bucket(srcBucket) bkt, err := l.Client.Bucket(srcBucket)
if err != nil { if err != nil {
return objInfo, ossToObjectError(errors.Trace(err), srcBucket, srcObject) return objInfo, ossToObjectError(errors.Trace(err), srcBucket, srcObject)
@ -804,7 +804,7 @@ func ossListObjectParts(client *oss.Client, bucket, object, uploadID string, par
// CopyObjectPart creates a part in a multipart upload by copying // CopyObjectPart creates a part in a multipart upload by copying
// existing object or a part of it. // existing object or a part of it.
func (l *ossObjects) CopyObjectPart(srcBucket, srcObject, destBucket, destObject, uploadID string, func (l *ossObjects) CopyObjectPart(srcBucket, srcObject, destBucket, destObject, uploadID string,
partID int, startOffset, length int64, metadata map[string]string) (p minio.PartInfo, err error) { partID int, startOffset, length int64, metadata map[string]string, srcEtag string) (p minio.PartInfo, err error) {
bkt, err := l.Client.Bucket(destBucket) bkt, err := l.Client.Bucket(destBucket)
if err != nil { if err != nil {

View File

@ -37,7 +37,7 @@ func (l *s3Objects) AnonPutObject(bucket string, object string, data *hash.Reade
} }
// AnonGetObject - Get object anonymously // AnonGetObject - Get object anonymously
func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer, etag string) error {
opts := miniogo.GetObjectOptions{} opts := miniogo.GetObjectOptions{}
if err := opts.SetRange(startOffset, startOffset+length-1); err != nil { if err := opts.SetRange(startOffset, startOffset+length-1); err != nil {
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, key) return minio.ErrorRespToObjectError(errors.Trace(err), bucket, key)

View File

@ -254,7 +254,7 @@ func (l *s3Objects) ListObjectsV2(bucket, prefix, continuationToken, delimiter s
// //
// startOffset indicates the starting read location of the object. // startOffset indicates the starting read location of the object.
// length indicates the total length of the object. // length indicates the total length of the object.
func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer, etag string) error {
if length < 0 && length != -1 { if length < 0 && length != -1 {
return minio.ErrorRespToObjectError(errors.Trace(minio.InvalidRange{}), bucket, key) return minio.ErrorRespToObjectError(errors.Trace(minio.InvalidRange{}), bucket, key)
} }
@ -298,7 +298,7 @@ func (l *s3Objects) PutObject(bucket string, object string, data *hash.Reader, m
} }
// CopyObject copies an object from source bucket to a destination bucket. // CopyObject copies an object from source bucket to a destination bucket.
func (l *s3Objects) CopyObject(srcBucket string, srcObject string, dstBucket string, dstObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) { func (l *s3Objects) CopyObject(srcBucket string, srcObject string, dstBucket string, dstObject string, metadata map[string]string, srcEtag string) (objInfo minio.ObjectInfo, err error) {
// Set this header such that following CopyObject() always sets the right metadata on the destination. // Set this header such that following CopyObject() always sets the right metadata on the destination.
// metadata input is already a trickled down value from interpreting x-amz-metadata-directive at // metadata input is already a trickled down value from interpreting x-amz-metadata-directive at
// handler layer. So what we have right now is supposed to be applied on the destination object anyways. // handler layer. So what we have right now is supposed to be applied on the destination object anyways.
@ -354,7 +354,7 @@ func (l *s3Objects) PutObjectPart(bucket string, object string, uploadID string,
// CopyObjectPart creates a part in a multipart upload by copying // CopyObjectPart creates a part in a multipart upload by copying
// existing object or a part of it. // existing object or a part of it.
func (l *s3Objects) CopyObjectPart(srcBucket, srcObject, destBucket, destObject, uploadID string, func (l *s3Objects) CopyObjectPart(srcBucket, srcObject, destBucket, destObject, uploadID string,
partID int, startOffset, length int64, metadata map[string]string) (p minio.PartInfo, err error) { partID int, startOffset, length int64, metadata map[string]string, srcEtag string) (p minio.PartInfo, err error) {
completePart, err := l.Client.CopyObjectPart(srcBucket, srcObject, destBucket, destObject, completePart, err := l.Client.CopyObjectPart(srcBucket, srcObject, destBucket, destObject,
uploadID, partID, startOffset, length, metadata) uploadID, partID, startOffset, length, metadata)

View File

@ -410,7 +410,7 @@ func (s *siaObjects) ListObjects(bucket string, prefix string, marker string, de
return loi, nil return loi, nil
} }
func (s *siaObjects) GetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer) error { func (s *siaObjects) GetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer, etag string) error {
dstFile := path.Join(s.TempDir, minio.MustGetUUID()) dstFile := path.Join(s.TempDir, minio.MustGetUUID())
defer os.Remove(dstFile) defer os.Remove(dstFile)

View File

@ -66,47 +66,6 @@ type OpsLockState struct {
// listLocksInfo - Fetches locks held on bucket, matching prefix held for longer than duration. // listLocksInfo - Fetches locks held on bucket, matching prefix held for longer than duration.
func listLocksInfo(bucket, prefix string, duration time.Duration) []VolumeLockInfo { func listLocksInfo(bucket, prefix string, duration time.Duration) []VolumeLockInfo {
globalNSMutex.lockMapMutex.Lock() locksInfo, _ := newObjectLayerFn().ListLocks(bucket, prefix, duration)
defer globalNSMutex.lockMapMutex.Unlock() return locksInfo
// Fetch current time once instead of fetching system time for every lock.
timeNow := UTCNow()
volumeLocks := []VolumeLockInfo{}
for param, debugLock := range globalNSMutex.debugLockMap {
if param.volume != bucket {
continue
}
// N B empty prefix matches all param.path.
if !hasPrefix(param.path, prefix) {
continue
}
volLockInfo := VolumeLockInfo{
Bucket: param.volume,
Object: param.path,
LocksOnObject: debugLock.counters.total,
TotalBlockedLocks: debugLock.counters.blocked,
LocksAcquiredOnObject: debugLock.counters.granted,
}
// Filter locks that are held on bucket, prefix.
for opsID, lockInfo := range debugLock.lockInfo {
// filter locks that were held for longer than duration.
elapsed := timeNow.Sub(lockInfo.since)
if elapsed < duration {
continue
}
// Add locks that are held for longer than duration.
volLockInfo.LockDetailsOnObject = append(volLockInfo.LockDetailsOnObject,
OpsLockState{
OperationID: opsID,
LockSource: lockInfo.lockSource,
LockType: lockInfo.lType,
Status: lockInfo.status,
Since: lockInfo.since,
})
volumeLocks = append(volumeLocks, volLockInfo)
}
}
return volumeLocks
} }

View File

@ -28,11 +28,22 @@ func TestListLocksInfo(t *testing.T) {
// instrumentation information. // instrumentation information.
isDistXL := false isDistXL := false
initNSLock(isDistXL) initNSLock(isDistXL)
objAPI := newObjectLayerFn()
if objAPI == nil {
t.Errorf("Failed to initialize object layer")
}
var nsMutex *nsLockMap
switch objAPI.(type) {
case *fsObjects:
nsMutex = objAPI.(*fsObjects).nsMutex
case *xlObjects:
nsMutex = objAPI.(*xlObjects).nsMutex
}
// Acquire a few locks to populate lock instrumentation. // Acquire a few locks to populate lock instrumentation.
// Take 10 read locks on bucket1/prefix1/obj1 // Take 10 read locks on bucket1/prefix1/obj1
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
readLk := globalNSMutex.NewNSLock("bucket1", "prefix1/obj1") readLk := nsMutex.NewNSLock("bucket1", "prefix1/obj1")
if readLk.GetRLock(newDynamicTimeout(60*time.Second, time.Second)) != nil { if readLk.GetRLock(newDynamicTimeout(60*time.Second, time.Second)) != nil {
t.Errorf("Failed to get read lock on iteration %d", i) t.Errorf("Failed to get read lock on iteration %d", i)
} }
@ -40,7 +51,7 @@ func TestListLocksInfo(t *testing.T) {
// Take write locks on bucket1/prefix/obj{11..19} // Take write locks on bucket1/prefix/obj{11..19}
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
wrLk := globalNSMutex.NewNSLock("bucket1", fmt.Sprintf("prefix1/obj%d", 10+i)) wrLk := nsMutex.NewNSLock("bucket1", fmt.Sprintf("prefix1/obj%d", 10+i))
if wrLk.GetLock(newDynamicTimeout(60*time.Second, time.Second)) != nil { if wrLk.GetLock(newDynamicTimeout(60*time.Second, time.Second)) != nil {
t.Errorf("Failed to get write lock on iteration %d", i) t.Errorf("Failed to get write lock on iteration %d", i)
} }

View File

@ -92,9 +92,9 @@ func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int)
return clnts, myNode return clnts, myNode
} }
// initNSLock - initialize name space lock map. // newNSLock - return a new name space lock map.
func initNSLock(isDistXL bool) { func newNSLock(isDistXL bool) *nsLockMap {
globalNSMutex = &nsLockMap{ nsMutex := nsLockMap{
isDistXL: isDistXL, isDistXL: isDistXL,
lockMap: make(map[nsParam]*nsLock), lockMap: make(map[nsParam]*nsLock),
counters: &lockStat{}, counters: &lockStat{},
@ -102,7 +102,13 @@ func initNSLock(isDistXL bool) {
// Initialize nsLockMap with entry for instrumentation information. // Initialize nsLockMap with entry for instrumentation information.
// Entries of <volume,path> -> stateInfo of locks // Entries of <volume,path> -> stateInfo of locks
globalNSMutex.debugLockMap = make(map[nsParam]*debugLockInfoPerVolumePath) nsMutex.debugLockMap = make(map[nsParam]*debugLockInfoPerVolumePath)
return &nsMutex
}
// initNSLock - initialize name space lock map.
func initNSLock(isDistXL bool) {
globalNSMutex = newNSLock(isDistXL)
} }
// nsParam - carries name space resource. // nsParam - carries name space resource.

View File

@ -349,6 +349,13 @@ func (e PartTooBig) Error() string {
return "Part size bigger than the allowed limit" return "Part size bigger than the allowed limit"
} }
// InvalidETag error returned when the etag has changed on disk
type InvalidETag struct{}
func (e InvalidETag) Error() string {
return "etag of the object has changed"
}
// NotImplemented If a feature is not implemented // NotImplemented If a feature is not implemented
type NotImplemented struct{} type NotImplemented struct{}

View File

@ -152,7 +152,7 @@ func testGetObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
} }
for i, testCase := range testCases { for i, testCase := range testCases {
err = obj.GetObject(testCase.bucketName, testCase.objectName, testCase.startOffset, testCase.length, testCase.writer) err = obj.GetObject(testCase.bucketName, testCase.objectName, testCase.startOffset, testCase.length, testCase.writer, "")
if err != nil && testCase.shouldPass { if err != nil && testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error()) t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error())
} }
@ -262,7 +262,7 @@ func testGetObjectPermissionDenied(obj ObjectLayer, instanceType string, disks [
} }
} }
err = obj.GetObject(testCase.bucketName, testCase.objectName, testCase.startOffset, testCase.length, testCase.writer) err = obj.GetObject(testCase.bucketName, testCase.objectName, testCase.startOffset, testCase.length, testCase.writer, "")
if err != nil && testCase.shouldPass { if err != nil && testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error()) t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error())
} }
@ -423,7 +423,7 @@ func testGetObjectDiskNotFound(obj ObjectLayer, instanceType string, disks []str
} }
for i, testCase := range testCases { for i, testCase := range testCases {
err = obj.GetObject(testCase.bucketName, testCase.objectName, testCase.startOffset, testCase.length, testCase.writer) err = obj.GetObject(testCase.bucketName, testCase.objectName, testCase.startOffset, testCase.length, testCase.writer, "")
if err != nil && testCase.shouldPass { if err != nil && testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error()) t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error())
} }

View File

@ -18,6 +18,7 @@ package cmd
import ( import (
"io" "io"
"time"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
) )
@ -36,16 +37,16 @@ type ObjectLayer interface {
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error)
// Object operations. // Object operations.
GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer, etag string) (err error)
GetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) GetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error)
PutObject(bucket, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, err error) PutObject(bucket, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, err error)
CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string) (objInfo ObjectInfo, err error) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string, srcETag string) (objInfo ObjectInfo, err error)
DeleteObject(bucket, object string) error DeleteObject(bucket, object string) error
// Multipart operations. // Multipart operations.
ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error)
NewMultipartUpload(bucket, object string, metadata map[string]string) (uploadID string, err error) NewMultipartUpload(bucket, object string, metadata map[string]string) (uploadID string, err error)
CopyObjectPart(srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, startOffset int64, length int64, metadata map[string]string) (info PartInfo, err error) CopyObjectPart(srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, startOffset int64, length int64, metadata map[string]string, srcEtag string) (info PartInfo, err error)
PutObjectPart(bucket, object, uploadID string, partID int, data *hash.Reader) (info PartInfo, err error) PutObjectPart(bucket, object, uploadID string, partID int, data *hash.Reader) (info PartInfo, err error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker int, maxParts int) (result ListPartsInfo, err error) ListObjectParts(bucket, object, uploadID string, partNumberMarker int, maxParts int) (result ListPartsInfo, err error)
AbortMultipartUpload(bucket, object, uploadID string) error AbortMultipartUpload(bucket, object, uploadID string) error
@ -58,4 +59,8 @@ type ObjectLayer interface {
ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error)
ListUploadsHeal(bucket, prefix, marker, uploadIDMarker, ListUploadsHeal(bucket, prefix, marker, uploadIDMarker,
delimiter string, maxUploads int) (ListMultipartsInfo, error) delimiter string, maxUploads int) (ListMultipartsInfo, error)
// Locking operations
ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error)
ClearLocks([]VolumeLockInfo) error
} }

View File

@ -169,7 +169,7 @@ func writeUploadJSON(u *uploadsV1, uploadsPath, tmpPath string, disk StorageAPI)
} }
// listMultipartUploadIDs - list all the upload ids from a marker up to 'count'. // listMultipartUploadIDs - list all the upload ids from a marker up to 'count'.
func listMultipartUploadIDs(bucketName, objectName, uploadIDMarker string, count int, disk StorageAPI) ([]MultipartInfo, bool, error) { func (xl xlObjects) listMultipartUploadIDs(bucketName, objectName, uploadIDMarker string, count int, disk StorageAPI) ([]MultipartInfo, bool, error) {
var uploads []MultipartInfo var uploads []MultipartInfo
// Read `uploads.json`. // Read `uploads.json`.
uploadsJSON, err := readUploadsJSON(bucketName, objectName, disk) uploadsJSON, err := readUploadsJSON(bucketName, objectName, disk)

View File

@ -230,12 +230,6 @@ func isETagEqual(left, right string) bool {
// is a common function to be called from object handlers and // is a common function to be called from object handlers and
// web handlers. // web handlers.
func deleteObject(obj ObjectLayer, bucket, object string, r *http.Request) (err error) { func deleteObject(obj ObjectLayer, bucket, object string, r *http.Request) (err error) {
// Acquire a write lock before deleting the object.
objectLock := globalNSMutex.NewNSLock(bucket, object)
if err = objectLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objectLock.Unlock()
// Proceed to delete the object. // Proceed to delete the object.
if err = obj.DeleteObject(bucket, object); err != nil { if err = obj.DeleteObject(bucket, object); err != nil {

View File

@ -107,14 +107,6 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
return return
} }
// Lock the object before reading.
objectLock := globalNSMutex.NewNSLock(bucket, object)
if objectLock.GetRLock(globalObjectTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer objectLock.RUnlock()
objInfo, err := objectAPI.GetObjectInfo(bucket, object) objInfo, err := objectAPI.GetObjectInfo(bucket, object)
if err != nil { if err != nil {
apiErr := toAPIErrorCode(err) apiErr := toAPIErrorCode(err)
@ -182,7 +174,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
httpWriter := ioutil.WriteOnClose(writer) httpWriter := ioutil.WriteOnClose(writer)
// Reads the object at startOffset and writes to mw. // Reads the object at startOffset and writes to mw.
if err = objectAPI.GetObject(bucket, object, startOffset, length, httpWriter); err != nil { if err = objectAPI.GetObject(bucket, object, startOffset, length, httpWriter, objInfo.ETag); err != nil {
errorIf(err, "Unable to write to client.") errorIf(err, "Unable to write to client.")
if !httpWriter.HasWritten() { // write error response only if no data has been written to client yet if !httpWriter.HasWritten() { // write error response only if no data has been written to client yet
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@ -234,14 +226,6 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
return return
} }
// Lock the object before reading.
objectLock := globalNSMutex.NewNSLock(bucket, object)
if objectLock.GetRLock(globalObjectTimeout) != nil {
writeErrorResponseHeadersOnly(w, ErrOperationTimedOut)
return
}
defer objectLock.RUnlock()
objInfo, err := objectAPI.GetObjectInfo(bucket, object) objInfo, err := objectAPI.GetObjectInfo(bucket, object)
if err != nil { if err != nil {
apiErr := toAPIErrorCode(err) apiErr := toAPIErrorCode(err)
@ -362,32 +346,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(w, ErrNotImplemented, r.URL) writeErrorResponse(w, ErrNotImplemented, r.URL)
return return
} }
cpSrcDstSame := srcBucket == dstBucket && srcObject == dstObject cpSrcDstSame := srcBucket == dstBucket && srcObject == dstObject
// Hold write lock on destination since in both cases
// - if source and destination are same
// - if source and destination are different
// it is the sole mutating state.
objectDWLock := globalNSMutex.NewNSLock(dstBucket, dstObject)
if objectDWLock.GetLock(globalObjectTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer objectDWLock.Unlock()
// if source and destination are different, we have to hold
// additional read lock as well to protect against writes on
// source.
if !cpSrcDstSame {
// Hold read locks on source object only if we are
// going to read data from source object.
objectSRLock := globalNSMutex.NewNSLock(srcBucket, srcObject)
if objectSRLock.GetRLock(globalObjectTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer objectSRLock.RUnlock()
}
objInfo, err := objectAPI.GetObjectInfo(srcBucket, srcObject) objInfo, err := objectAPI.GetObjectInfo(srcBucket, srcObject)
if err != nil { if err != nil {
@ -424,7 +383,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// Copy source object to destination, if source and destination // Copy source object to destination, if source and destination
// object is same then only metadata is updated. // object is same then only metadata is updated.
objInfo, err = objectAPI.CopyObject(srcBucket, srcObject, dstBucket, dstObject, newMetadata) objInfo, err = objectAPI.CopyObject(srcBucket, srcObject, dstBucket, dstObject, newMetadata, objInfo.ETag)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
@ -538,14 +497,6 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
} }
} }
// Lock the object.
objectLock := globalNSMutex.NewNSLock(bucket, object)
if objectLock.GetLock(globalObjectTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer objectLock.Unlock()
var ( var (
md5hex = hex.EncodeToString(md5Bytes) md5hex = hex.EncodeToString(md5Bytes)
sha256hex = "" sha256hex = ""
@ -749,15 +700,6 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
return return
} }
// Hold read locks on source object only if we are
// going to read data from source object.
objectSRLock := globalNSMutex.NewNSLock(srcBucket, srcObject)
if objectSRLock.GetRLock(globalObjectTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer objectSRLock.RUnlock()
objInfo, err := objectAPI.GetObjectInfo(srcBucket, srcObject) objInfo, err := objectAPI.GetObjectInfo(srcBucket, srcObject)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@ -799,7 +741,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
// Copy source object to destination, if source and destination // Copy source object to destination, if source and destination
// object is same then only metadata is updated. // object is same then only metadata is updated.
partInfo, err := objectAPI.CopyObjectPart(srcBucket, srcObject, dstBucket, partInfo, err := objectAPI.CopyObjectPart(srcBucket, srcObject, dstBucket,
dstObject, uploadID, partID, startOffset, length, nil) dstObject, uploadID, partID, startOffset, length, nil, objInfo.ETag)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
@ -1057,14 +999,6 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
completeParts = append(completeParts, part) completeParts = append(completeParts, part)
} }
// Hold write lock on the object.
destLock := globalNSMutex.NewNSLock(bucket, object)
if destLock.GetLock(globalObjectTimeout) != nil {
writeErrorResponse(w, ErrOperationTimedOut, r.URL)
return
}
defer destLock.Unlock()
objInfo, err := objectAPI.CompleteMultipartUpload(bucket, object, uploadID, completeParts) objInfo, err := objectAPI.CompleteMultipartUpload(bucket, object, uploadID, completeParts)
if err != nil { if err != nil {
err = errors.Cause(err) err = errors.Cause(err)

View File

@ -766,7 +766,7 @@ func testAPIPutObjectStreamSigV4Handler(obj ObjectLayer, instanceType, bucketNam
t.Fatalf("Test %d: %s: ContentEncoding is set to \"%s\" which is unexpected, expected \"%s\"", i+1, instanceType, objInfo.ContentEncoding, expectedContentEncoding) t.Fatalf("Test %d: %s: ContentEncoding is set to \"%s\" which is unexpected, expected \"%s\"", i+1, instanceType, objInfo.ContentEncoding, expectedContentEncoding)
} }
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(testCase.dataLen), buffer) err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(testCase.dataLen), buffer, objInfo.ETag)
if err != nil { if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err) t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
} }
@ -943,7 +943,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
// Fetch the object to check whether the content is same as the one uploaded via PutObject. // Fetch the object to check whether the content is same as the one uploaded via PutObject.
err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(len(bytesData)), buffer) err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(len(bytesData)), buffer, "")
if err != nil { if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err) t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
} }
@ -986,7 +986,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
if testCase.expectedRespStatus == http.StatusOK { if testCase.expectedRespStatus == http.StatusOK {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
// Fetch the object to check whether the content is same as the one uploaded via PutObject. // Fetch the object to check whether the content is same as the one uploaded via PutObject.
err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(len(bytesData)), buffer) err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(len(bytesData)), buffer, "")
if err != nil { if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err) t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
} }
@ -1138,7 +1138,7 @@ func testAPICopyObjectPartHandlerSanity(obj ObjectLayer, instanceType, bucketNam
} }
var buf bytes.Buffer var buf bytes.Buffer
if err = obj.GetObject(bucketName, testObject, 0, int64(len(bytesData[0].byteData)), &buf); err != nil { if err = obj.GetObject(bucketName, testObject, 0, int64(len(bytesData[0].byteData)), &buf, ""); err != nil {
t.Fatalf("Test: %s reading completed file failed: <ERROR> %v", instanceType, err) t.Fatalf("Test: %s reading completed file failed: <ERROR> %v", instanceType, err)
} }
if !bytes.Equal(buf.Bytes(), bytesData[0].byteData) { if !bytes.Equal(buf.Bytes(), bytesData[0].byteData) {
@ -1795,7 +1795,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
if rec.Code == http.StatusOK { if rec.Code == http.StatusOK {
// See if the new object is formed. // See if the new object is formed.
// testing whether the copy was successful. // testing whether the copy was successful.
err = obj.GetObject(testCase.bucketName, testCase.newObjectName, 0, int64(len(bytesData[0].byteData)), buffers[0]) err = obj.GetObject(testCase.bucketName, testCase.newObjectName, 0, int64(len(bytesData[0].byteData)), buffers[0], "")
if err != nil { if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err) t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
} }

View File

@ -207,7 +207,7 @@ func testMultipleObjectCreation(obj ObjectLayer, instanceType string, t TestErrH
for key, value := range objects { for key, value := range objects {
var byteBuffer bytes.Buffer var byteBuffer bytes.Buffer
err = obj.GetObject("bucket", key, 0, int64(len(value)), &byteBuffer) err = obj.GetObject("bucket", key, 0, int64(len(value)), &byteBuffer, "")
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@ -456,7 +456,7 @@ func testObjectOverwriteWorks(obj ObjectLayer, instanceType string, t TestErrHan
} }
var bytesBuffer bytes.Buffer var bytesBuffer bytes.Buffer
err = obj.GetObject("bucket", "object", 0, length, &bytesBuffer) err = obj.GetObject("bucket", "object", 0, length, &bytesBuffer, "")
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@ -523,7 +523,7 @@ func testPutObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
err = obj.GetObject("bucket", "object", 0, length, &bytesBuffer1) err = obj.GetObject("bucket", "object", 0, length, &bytesBuffer1, "")
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@ -536,7 +536,7 @@ func testPutObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
err = obj.GetObject("bucket", "object", 0, length, &bytesBuffer2) err = obj.GetObject("bucket", "object", 0, length, &bytesBuffer2, "")
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@ -566,7 +566,7 @@ func testPutObjectInSubdir(obj ObjectLayer, instanceType string, t TestErrHandle
} }
var bytesBuffer bytes.Buffer var bytesBuffer bytes.Buffer
err = obj.GetObject("bucket", "dir1/dir2/object", 0, length, &bytesBuffer) err = obj.GetObject("bucket", "dir1/dir2/object", 0, length, &bytesBuffer, "")
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }

View File

@ -131,12 +131,6 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
return toJSONError(errInvalidBucketName) return toJSONError(errInvalidBucketName)
} }
bucketLock := globalNSMutex.NewNSLock(args.BucketName, "")
if err := bucketLock.GetLock(globalObjectTimeout); err != nil {
return toJSONError(errOperationTimedOut)
}
defer bucketLock.Unlock()
if err := objectAPI.MakeBucketWithLocation(args.BucketName, globalServerConfig.GetRegion()); err != nil { if err := objectAPI.MakeBucketWithLocation(args.BucketName, globalServerConfig.GetRegion()); err != nil {
return toJSONError(err, args.BucketName) return toJSONError(err, args.BucketName)
} }
@ -160,12 +154,6 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
return toJSONError(errAuthentication) return toJSONError(errAuthentication)
} }
bucketLock := globalNSMutex.NewNSLock(args.BucketName, "")
if err := bucketLock.GetLock(globalObjectTimeout); err != nil {
return toJSONError(errOperationTimedOut)
}
defer bucketLock.Unlock()
err := objectAPI.DeleteBucket(args.BucketName) err := objectAPI.DeleteBucket(args.BucketName)
if err != nil { if err != nil {
return toJSONError(err, args.BucketName) return toJSONError(err, args.BucketName)
@ -564,14 +552,6 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
return return
} }
// Lock the object.
objectLock := globalNSMutex.NewNSLock(bucket, object)
if objectLock.GetLock(globalObjectTimeout) != nil {
writeWebErrorResponse(w, errOperationTimedOut)
return
}
defer objectLock.Unlock()
hashReader, err := hash.NewReader(r.Body, size, "", "") hashReader, err := hash.NewReader(r.Body, size, "", "")
if err != nil { if err != nil {
writeWebErrorResponse(w, err) writeWebErrorResponse(w, err)
@ -614,15 +594,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
// Add content disposition. // Add content disposition.
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(object))) w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(object)))
// Lock the object before reading. if err := objectAPI.GetObject(bucket, object, 0, -1, w, ""); err != nil {
objectLock := globalNSMutex.NewNSLock(bucket, object)
if objectLock.GetRLock(globalObjectTimeout) != nil {
writeWebErrorResponse(w, errOperationTimedOut)
return
}
defer objectLock.RUnlock()
if err := objectAPI.GetObject(bucket, object, 0, -1, w); err != nil {
/// No need to print error, response writer already written to. /// No need to print error, response writer already written to.
return return
} }
@ -686,7 +658,7 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
writeWebErrorResponse(w, errUnexpected) writeWebErrorResponse(w, errUnexpected)
return err return err
} }
return objectAPI.GetObject(args.BucketName, objectName, 0, info.Size, writer) return objectAPI.GetObject(args.BucketName, objectName, 0, info.Size, writer, "")
} }
if !hasSuffix(object, slashSeparator) { if !hasSuffix(object, slashSeparator) {

View File

@ -887,7 +887,7 @@ func testUploadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandler
} }
var byteBuffer bytes.Buffer var byteBuffer bytes.Buffer
err = obj.GetObject(bucketName, objectName, 0, int64(len(content)), &byteBuffer) err = obj.GetObject(bucketName, objectName, 0, int64(len(content)), &byteBuffer, "")
if err != nil { if err != nil {
t.Fatalf("Failed, %v", err) t.Fatalf("Failed, %v", err)
} }

View File

@ -33,6 +33,11 @@ var bucketMetadataOpIgnoredErrs = append(bucketOpIgnoredErrs, errVolumeNotFound)
// MakeBucket - make a bucket. // MakeBucket - make a bucket.
func (xl xlObjects) MakeBucketWithLocation(bucket, location string) error { func (xl xlObjects) MakeBucketWithLocation(bucket, location string) error {
bucketLock := xl.nsMutex.NewNSLock(bucket, "")
if err := bucketLock.GetLock(globalObjectTimeout); err != nil {
return err
}
defer bucketLock.Unlock()
// Verify if bucket is valid. // Verify if bucket is valid.
if !IsValidBucketName(bucket) { if !IsValidBucketName(bucket) {
return errors.Trace(BucketNameInvalid{Bucket: bucket}) return errors.Trace(BucketNameInvalid{Bucket: bucket})

View File

@ -83,6 +83,11 @@ func (xl xlObjects) HealBucket(bucket string) error {
// get write quorum for an object // get write quorum for an object
writeQuorum := len(xl.storageDisks)/2 + 1 writeQuorum := len(xl.storageDisks)/2 + 1
bucketLock := xl.nsMutex.NewNSLock(bucket, "")
if err := bucketLock.GetLock(globalHealingTimeout); err != nil {
return err
}
defer bucketLock.Unlock()
// Heal bucket. // Heal bucket.
if err := healBucket(xl.storageDisks, bucket, writeQuorum); err != nil { if err := healBucket(xl.storageDisks, bucket, writeQuorum); err != nil {
@ -95,11 +100,6 @@ func (xl xlObjects) HealBucket(bucket string) error {
// Heal bucket - create buckets on disks where it does not exist. // Heal bucket - create buckets on disks where it does not exist.
func healBucket(storageDisks []StorageAPI, bucket string, writeQuorum int) error { func healBucket(storageDisks []StorageAPI, bucket string, writeQuorum int) error {
bucketLock := globalNSMutex.NewNSLock(bucket, "")
if err := bucketLock.GetLock(globalHealingTimeout); err != nil {
return err
}
defer bucketLock.Unlock()
// Initialize sync waitgroup. // Initialize sync waitgroup.
var wg = &sync.WaitGroup{} var wg = &sync.WaitGroup{}
@ -316,6 +316,12 @@ func quickHeal(xlObj xlObjects, writeQuorum int, readQuorum int) error {
for bucketName, occCount := range bucketOcc { for bucketName, occCount := range bucketOcc {
// Heal bucket only if healing is needed. // Heal bucket only if healing is needed.
if occCount != len(xlObj.storageDisks) { if occCount != len(xlObj.storageDisks) {
bucketLock := xlObj.nsMutex.NewNSLock(bucketName, "")
if perr := bucketLock.GetLock(globalHealingTimeout); perr != nil {
return perr
}
defer bucketLock.Unlock()
// Heal bucket and then proceed to heal bucket metadata if any. // Heal bucket and then proceed to heal bucket metadata if any.
if err = healBucket(xlObj.storageDisks, bucketName, writeQuorum); err == nil { if err = healBucket(xlObj.storageDisks, bucketName, writeQuorum); err == nil {
if err = healBucketMetadata(xlObj, bucketName); err == nil { if err = healBucketMetadata(xlObj, bucketName); err == nil {
@ -542,7 +548,7 @@ func (xl xlObjects) HealObject(bucket, object string) (int, int, error) {
} }
// Lock the object before healing. // Lock the object before healing.
objectLock := globalNSMutex.NewNSLock(bucket, object) objectLock := xl.nsMutex.NewNSLock(bucket, object)
if err := objectLock.GetRLock(globalHealingTimeout); err != nil { if err := objectLock.GetRLock(globalHealingTimeout); err != nil {
return 0, 0, err return 0, 0, err
} }

View File

@ -143,7 +143,7 @@ func (xl xlObjects) listObjectsHeal(bucket, prefix, marker, delimiter string, ma
} }
// Check if the current object needs healing // Check if the current object needs healing
objectLock := globalNSMutex.NewNSLock(bucket, objInfo.Name) objectLock := xl.nsMutex.NewNSLock(bucket, objInfo.Name)
if err := objectLock.GetRLock(globalHealingTimeout); err != nil { if err := objectLock.GetRLock(globalHealingTimeout); err != nil {
return loi, err return loi, err
} }
@ -223,12 +223,12 @@ func (xl xlObjects) ListUploadsHeal(bucket, prefix, marker, uploadIDMarker,
} }
// Fetches list of multipart uploadIDs given bucket, keyMarker, uploadIDMarker. // Fetches list of multipart uploadIDs given bucket, keyMarker, uploadIDMarker.
func fetchMultipartUploadIDs(bucket, keyMarker, uploadIDMarker string, func (xl xlObjects) fetchMultipartUploadIDs(bucket, keyMarker, uploadIDMarker string,
maxUploads int, disks []StorageAPI) (uploads []MultipartInfo, end bool, maxUploads int, disks []StorageAPI) (uploads []MultipartInfo, end bool,
err error) { err error) {
// Hold a read lock on keyMarker path. // Hold a read lock on keyMarker path.
keyMarkerLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, keyMarkerLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, keyMarker)) pathJoin(bucket, keyMarker))
if err = keyMarkerLock.GetRLock(globalHealingTimeout); err != nil { if err = keyMarkerLock.GetRLock(globalHealingTimeout); err != nil {
return uploads, end, err return uploads, end, err
@ -237,7 +237,7 @@ func fetchMultipartUploadIDs(bucket, keyMarker, uploadIDMarker string,
if disk == nil { if disk == nil {
continue continue
} }
uploads, end, err = listMultipartUploadIDs(bucket, keyMarker, uploads, end, err = xl.listMultipartUploadIDs(bucket, keyMarker,
uploadIDMarker, maxUploads, disk) uploadIDMarker, maxUploads, disk)
if err == nil || if err == nil ||
!errors.IsErrIgnored(err, objMetadataOpIgnoredErrs...) { !errors.IsErrIgnored(err, objMetadataOpIgnoredErrs...) {
@ -268,7 +268,7 @@ func (xl xlObjects) listMultipartUploadsHeal(bucket, prefix, keyMarker,
// List all upload ids for the given keyMarker, starting from // List all upload ids for the given keyMarker, starting from
// uploadIDMarker. // uploadIDMarker.
if uploadIDMarker != "" { if uploadIDMarker != "" {
uploads, _, err = fetchMultipartUploadIDs(bucket, keyMarker, uploads, _, err = xl.fetchMultipartUploadIDs(bucket, keyMarker,
uploadIDMarker, maxUploads, xl.getLoadBalancedDisks()) uploadIDMarker, maxUploads, xl.getLoadBalancedDisks())
if err != nil { if err != nil {
return lmi, err return lmi, err
@ -349,7 +349,7 @@ func (xl xlObjects) listMultipartUploadsHeal(bucket, prefix, keyMarker,
var newUploads []MultipartInfo var newUploads []MultipartInfo
var end bool var end bool
uploadIDMarker = "" uploadIDMarker = ""
newUploads, end, err = fetchMultipartUploadIDs(bucket, entry, uploadIDMarker, newUploads, end, err = xl.fetchMultipartUploadIDs(bucket, entry, uploadIDMarker,
uploadsLeft, xl.getLoadBalancedDisks()) uploadsLeft, xl.getLoadBalancedDisks())
if err != nil { if err != nil {
return lmi, err return lmi, err

View File

@ -306,7 +306,7 @@ func (xl xlObjects) listMultipartUploadsCleanup(bucket, prefix, keyMarker, uploa
// uploadIDMarker first. // uploadIDMarker first.
if uploadIDMarker != "" { if uploadIDMarker != "" {
// hold lock on keyMarker path // hold lock on keyMarker path
keyMarkerLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, keyMarkerLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, keyMarker)) pathJoin(bucket, keyMarker))
if err = keyMarkerLock.GetRLock(globalListingTimeout); err != nil { if err = keyMarkerLock.GetRLock(globalListingTimeout); err != nil {
return lmi, err return lmi, err
@ -315,7 +315,7 @@ func (xl xlObjects) listMultipartUploadsCleanup(bucket, prefix, keyMarker, uploa
if disk == nil { if disk == nil {
continue continue
} }
uploads, _, err = listMultipartUploadIDs(bucket, keyMarker, uploadIDMarker, maxUploads, disk) uploads, _, err = xl.listMultipartUploadIDs(bucket, keyMarker, uploadIDMarker, maxUploads, disk)
if err == nil { if err == nil {
break break
} }
@ -374,7 +374,7 @@ func (xl xlObjects) listMultipartUploadsCleanup(bucket, prefix, keyMarker, uploa
// For the new object entry we get all its // For the new object entry we get all its
// pending uploadIDs. // pending uploadIDs.
entryLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, entryLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, entry)) pathJoin(bucket, entry))
if err = entryLock.GetRLock(globalListingTimeout); err != nil { if err = entryLock.GetRLock(globalListingTimeout); err != nil {
return lmi, err return lmi, err
@ -384,7 +384,7 @@ func (xl xlObjects) listMultipartUploadsCleanup(bucket, prefix, keyMarker, uploa
if disk == nil { if disk == nil {
continue continue
} }
newUploads, end, err = listMultipartUploadIDs(bucket, entry, uploadIDMarker, maxUploads, disk) newUploads, end, err = xl.listMultipartUploadIDs(bucket, entry, uploadIDMarker, maxUploads, disk)
if err == nil { if err == nil {
break break
} }
@ -465,8 +465,14 @@ func (xl xlObjects) ListMultipartUploads(bucket, object, keyMarker, uploadIDMark
if disk == nil { if disk == nil {
continue continue
} }
// Hold the lock so that two parallel complete-multipart-uploads
uploads, _, err := listMultipartUploadIDs(bucket, object, uploadIDMarker, maxUploads, disk) // do not leave a stale uploads.json behind.
objectMPartPathLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object))
if err := objectMPartPathLock.GetRLock(globalListingTimeout); err != nil {
return lmi, err
}
defer objectMPartPathLock.RUnlock()
uploads, _, err := xl.listMultipartUploadIDs(bucket, object, uploadIDMarker, maxUploads, disk)
if err != nil { if err != nil {
return lmi, err return lmi, err
} }
@ -525,7 +531,7 @@ func (xl xlObjects) newMultipartUpload(bucket string, object string, meta map[st
// This lock needs to be held for any changes to the directory // This lock needs to be held for any changes to the directory
// contents of ".minio.sys/multipart/object/" // contents of ".minio.sys/multipart/object/"
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, objectMPartPathLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, object)) pathJoin(bucket, object))
if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil { if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil {
return "", err return "", err
@ -582,17 +588,34 @@ func (xl xlObjects) NewMultipartUpload(bucket, object string, meta map[string]st
// data is read from an existing object. // data is read from an existing object.
// //
// Implements S3 compatible Upload Part Copy API. // Implements S3 compatible Upload Part Copy API.
func (xl xlObjects) CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, uploadID string, partID int, startOffset int64, length int64, metadata map[string]string) (pi PartInfo, e error) { func (xl xlObjects) CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, uploadID string, partID int, startOffset int64, length int64, metadata map[string]string, srcEtag string) (pi PartInfo, e error) {
// Hold read locks on source object only if we are
// going to read data from source object.
objectSRLock := xl.nsMutex.NewNSLock(srcBucket, srcObject)
if err := objectSRLock.GetRLock(globalObjectTimeout); err != nil {
return pi, err
}
defer objectSRLock.RUnlock()
if err := checkNewMultipartArgs(srcBucket, srcObject, xl); err != nil { if err := checkNewMultipartArgs(srcBucket, srcObject, xl); err != nil {
return pi, err return pi, err
} }
if srcEtag != "" {
objInfo, err := xl.getObjectInfo(srcBucket, srcObject)
if err != nil {
return pi, toObjectErr(err, srcBucket, srcObject)
}
if objInfo.ETag != srcEtag {
return pi, toObjectErr(errors.Trace(InvalidETag{}), srcBucket, srcObject)
}
}
// Initialize pipe. // Initialize pipe.
pipeReader, pipeWriter := io.Pipe() pipeReader, pipeWriter := io.Pipe()
go func() { go func() {
if gerr := xl.GetObject(srcBucket, srcObject, startOffset, length, pipeWriter); gerr != nil { if gerr := xl.getObject(srcBucket, srcObject, startOffset, length, pipeWriter, ""); gerr != nil {
errorIf(gerr, "Unable to read object `%s/%s`.", srcBucket, srcObject) errorIf(gerr, "Unable to read %s of the object `%s/%s`.", srcBucket, srcObject)
pipeWriter.CloseWithError(toObjectErr(gerr, srcBucket, srcObject)) pipeWriter.CloseWithError(toObjectErr(gerr, srcBucket, srcObject))
return return
} }
@ -631,12 +654,20 @@ func (xl xlObjects) PutObjectPart(bucket, object, uploadID string, partID int, d
return pi, toObjectErr(errors.Trace(errInvalidArgument)) return pi, toObjectErr(errors.Trace(errInvalidArgument))
} }
// Hold the lock so that two parallel complete-multipart-uploads
// do not leave a stale uploads.json behind.
objectMPartPathLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object))
if err := objectMPartPathLock.GetLock(globalOperationTimeout); err != nil {
return pi, err
}
defer objectMPartPathLock.Unlock()
var partsMetadata []xlMetaV1 var partsMetadata []xlMetaV1
var errs []error var errs []error
uploadIDPath := pathJoin(bucket, object, uploadID) uploadIDPath := pathJoin(bucket, object, uploadID)
// pre-check upload id lock. // pre-check upload id lock.
preUploadIDLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, uploadIDPath) preUploadIDLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket, uploadIDPath)
if err := preUploadIDLock.GetRLock(globalOperationTimeout); err != nil { if err := preUploadIDLock.GetRLock(globalOperationTimeout); err != nil {
return pi, err return pi, err
} }
@ -708,7 +739,7 @@ func (xl xlObjects) PutObjectPart(bucket, object, uploadID string, partID int, d
} }
// post-upload check (write) lock // post-upload check (write) lock
postUploadIDLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, uploadIDPath) postUploadIDLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket, uploadIDPath)
if err = postUploadIDLock.GetLock(globalOperationTimeout); err != nil { if err = postUploadIDLock.GetLock(globalOperationTimeout); err != nil {
return pi, err return pi, err
} }
@ -859,10 +890,16 @@ func (xl xlObjects) ListObjectParts(bucket, object, uploadID string, partNumberM
if err := checkListPartsArgs(bucket, object, xl); err != nil { if err := checkListPartsArgs(bucket, object, xl); err != nil {
return lpi, err return lpi, err
} }
// Hold the lock so that two parallel complete-multipart-uploads
// do not leave a stale uploads.json behind.
objectMPartPathLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object))
if err := objectMPartPathLock.GetRLock(globalListingTimeout); err != nil {
return lpi, errors.Trace(err)
}
defer objectMPartPathLock.RUnlock()
// Hold lock so that there is no competing // Hold lock so that there is no competing
// abort-multipart-upload or complete-multipart-upload. // abort-multipart-upload or complete-multipart-upload.
uploadIDLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, uploadIDLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, object, uploadID)) pathJoin(bucket, object, uploadID))
if err := uploadIDLock.GetLock(globalListingTimeout); err != nil { if err := uploadIDLock.GetLock(globalListingTimeout); err != nil {
return lpi, err return lpi, err
@ -886,14 +923,19 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload
if err := checkCompleteMultipartArgs(bucket, object, xl); err != nil { if err := checkCompleteMultipartArgs(bucket, object, xl); err != nil {
return oi, err return oi, err
} }
// Hold write lock on the object.
destLock := xl.nsMutex.NewNSLock(bucket, object)
if err := destLock.GetLock(globalObjectTimeout); err != nil {
return oi, err
}
defer destLock.Unlock()
// Hold lock so that // Hold lock so that
// //
// 1) no one aborts this multipart upload // 1) no one aborts this multipart upload
// //
// 2) no one does a parallel complete-multipart-upload on this // 2) no one does a parallel complete-multipart-upload on this
// multipart upload // multipart upload
uploadIDLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, uploadIDLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, object, uploadID)) pathJoin(bucket, object, uploadID))
if err := uploadIDLock.GetLock(globalOperationTimeout); err != nil { if err := uploadIDLock.GetLock(globalOperationTimeout); err != nil {
return oi, err return oi, err
@ -1034,7 +1076,7 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload
// Prefetch the object from disk by triggering a fake GetObject call // Prefetch the object from disk by triggering a fake GetObject call
// Unlike a regular single PutObject, multipart PutObject is comes in // Unlike a regular single PutObject, multipart PutObject is comes in
// stages and it is harder to cache. // stages and it is harder to cache.
go xl.GetObject(bucket, object, 0, objectSize, ioutil.Discard) go xl.GetObject(bucket, object, 0, objectSize, ioutil.Discard, s3MD5)
} }
}() }()
@ -1075,7 +1117,7 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload
// Hold the lock so that two parallel // Hold the lock so that two parallel
// complete-multipart-uploads do not leave a stale // complete-multipart-uploads do not leave a stale
// uploads.json behind. // uploads.json behind.
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, objectMPartPathLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, object)) pathJoin(bucket, object))
if err = objectMPartPathLock.GetLock(globalOperationTimeout); err != nil { if err = objectMPartPathLock.GetLock(globalOperationTimeout); err != nil {
return oi, toObjectErr(err, bucket, object) return oi, toObjectErr(err, bucket, object)
@ -1155,7 +1197,7 @@ func (xl xlObjects) abortMultipartUpload(bucket, object, uploadID string) (err e
// hold lock so we don't compete with a complete, or abort // hold lock so we don't compete with a complete, or abort
// multipart request. // multipart request.
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, objectMPartPathLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, object)) pathJoin(bucket, object))
if err = objectMPartPathLock.GetLock(globalOperationTimeout); err != nil { if err = objectMPartPathLock.GetLock(globalOperationTimeout); err != nil {
return toObjectErr(err, bucket, object) return toObjectErr(err, bucket, object)
@ -1186,10 +1228,9 @@ func (xl xlObjects) AbortMultipartUpload(bucket, object, uploadID string) error
if err := checkAbortMultipartArgs(bucket, object, xl); err != nil { if err := checkAbortMultipartArgs(bucket, object, xl); err != nil {
return err return err
} }
// Hold lock so that there is no competing // Hold lock so that there is no competing
// complete-multipart-upload or put-object-part. // complete-multipart-upload or put-object-part.
uploadIDLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, uploadIDLock := xl.nsMutex.NewNSLock(minioMetaMultipartBucket,
pathJoin(bucket, object, uploadID)) pathJoin(bucket, object, uploadID))
if err := uploadIDLock.GetLock(globalOperationTimeout); err != nil { if err := uploadIDLock.GetLock(globalOperationTimeout); err != nil {
return err return err

View File

@ -57,7 +57,40 @@ func (xl xlObjects) prepareFile(bucket, object string, size int64, onlineDisks [
// CopyObject - copy object source object to destination object. // CopyObject - copy object source object to destination object.
// if source object and destination object are same we only // if source object and destination object are same we only
// update metadata. // update metadata.
func (xl xlObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string, metadata map[string]string) (oi ObjectInfo, e error) { func (xl xlObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string, metadata map[string]string, srcEtag string) (oi ObjectInfo, e error) {
cpSrcDstSame := srcBucket == dstBucket && srcObject == dstObject
// Hold write lock on destination since in both cases
// - if source and destination are same
// - if source and destination are different
// it is the sole mutating state.
objectDWLock := xl.nsMutex.NewNSLock(dstBucket, dstObject)
if err := objectDWLock.GetLock(globalObjectTimeout); err != nil {
return oi, err
}
defer objectDWLock.Unlock()
// if source and destination are different, we have to hold
// additional read lock as well to protect against writes on
// source.
if !cpSrcDstSame {
// Hold read locks on source object only if we are
// going to read data from source object.
objectSRLock := xl.nsMutex.NewNSLock(srcBucket, srcObject)
if err := objectSRLock.GetRLock(globalObjectTimeout); err != nil {
return oi, err
}
defer objectSRLock.RUnlock()
}
if srcEtag != "" {
objInfo, perr := xl.getObjectInfo(srcBucket, srcObject)
if perr != nil {
return oi, toObjectErr(perr, srcBucket, srcObject)
}
if objInfo.ETag != srcEtag {
return oi, toObjectErr(errors.Trace(InvalidETag{}), srcBucket, srcObject)
}
}
// Read metadata associated with the object from all disks. // Read metadata associated with the object from all disks.
metaArr, errs := readAllXLMetadata(xl.storageDisks, srcBucket, srcObject) metaArr, errs := readAllXLMetadata(xl.storageDisks, srcBucket, srcObject)
@ -114,8 +147,8 @@ func (xl xlObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string
go func() { go func() {
var startOffset int64 // Read the whole file. var startOffset int64 // Read the whole file.
if gerr := xl.GetObject(srcBucket, srcObject, startOffset, length, pipeWriter); gerr != nil { if gerr := xl.getObject(srcBucket, srcObject, startOffset, length, pipeWriter, ""); gerr != nil {
errorIf(gerr, "Unable to read object `%s/%s`.", srcBucket, srcObject) errorIf(gerr, "Unable to read %s of the object `%s/%s`.", srcBucket, srcObject)
pipeWriter.CloseWithError(toObjectErr(gerr, srcBucket, srcObject)) pipeWriter.CloseWithError(toObjectErr(gerr, srcBucket, srcObject))
return return
} }
@ -127,7 +160,7 @@ func (xl xlObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string
return oi, toObjectErr(errors.Trace(err), dstBucket, dstObject) return oi, toObjectErr(errors.Trace(err), dstBucket, dstObject)
} }
objInfo, err := xl.PutObject(dstBucket, dstObject, hashReader, metadata) objInfo, err := xl.putObject(dstBucket, dstObject, hashReader, metadata)
if err != nil { if err != nil {
return oi, toObjectErr(err, dstBucket, dstObject) return oi, toObjectErr(err, dstBucket, dstObject)
} }
@ -144,7 +177,19 @@ func (xl xlObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string
// //
// startOffset indicates the starting read location of the object. // startOffset indicates the starting read location of the object.
// length indicates the total length of the object. // length indicates the total length of the object.
func (xl xlObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) error { func (xl xlObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer, etag string) error {
// Lock the object before reading.
objectLock := xl.nsMutex.NewNSLock(bucket, object)
if err := objectLock.GetRLock(globalObjectTimeout); err != nil {
return err
}
defer objectLock.RUnlock()
return xl.getObject(bucket, object, startOffset, length, writer, etag)
}
// getObject wrapper for xl GetObject
func (xl xlObjects) getObject(bucket, object string, startOffset int64, length int64, writer io.Writer, etag string) error {
if err := checkGetObjArgs(bucket, object); err != nil { if err := checkGetObjArgs(bucket, object); err != nil {
return err return err
} }
@ -311,6 +356,13 @@ func (xl xlObjects) GetObject(bucket, object string, startOffset int64, length i
// GetObjectInfo - reads object metadata and replies back ObjectInfo. // GetObjectInfo - reads object metadata and replies back ObjectInfo.
func (xl xlObjects) GetObjectInfo(bucket, object string) (oi ObjectInfo, e error) { func (xl xlObjects) GetObjectInfo(bucket, object string) (oi ObjectInfo, e error) {
// Lock the object before reading.
objectLock := xl.nsMutex.NewNSLock(bucket, object)
if err := objectLock.GetRLock(globalObjectTimeout); err != nil {
return oi, err
}
defer objectLock.RUnlock()
if err := checkGetObjArgs(bucket, object); err != nil { if err := checkGetObjArgs(bucket, object); err != nil {
return oi, err return oi, err
} }
@ -445,6 +497,17 @@ func renameObject(disks []StorageAPI, srcBucket, srcObject, dstBucket, dstObject
// writes `xl.json` which carries the necessary metadata for future // writes `xl.json` which carries the necessary metadata for future
// object operations. // object operations.
func (xl xlObjects) PutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, err error) { func (xl xlObjects) PutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, err error) {
// Lock the object.
objectLock := xl.nsMutex.NewNSLock(bucket, object)
if err := objectLock.GetLock(globalObjectTimeout); err != nil {
return objInfo, err
}
defer objectLock.Unlock()
return xl.putObject(bucket, object, data, metadata)
}
// putObject wrapper for xl PutObject
func (xl xlObjects) putObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, err error) {
// This is a special case with size as '0' and object ends with // This is a special case with size as '0' and object ends with
// a slash separator, we treat it like a valid operation and // a slash separator, we treat it like a valid operation and
// return success. // return success.
@ -715,6 +778,13 @@ func (xl xlObjects) deleteObject(bucket, object string) error {
// any error as it is not necessary for the handler to reply back a // any error as it is not necessary for the handler to reply back a
// response to the client request. // response to the client request.
func (xl xlObjects) DeleteObject(bucket, object string) (err error) { func (xl xlObjects) DeleteObject(bucket, object string) (err error) {
// Acquire a write lock before deleting the object.
objectLock := xl.nsMutex.NewNSLock(bucket, object)
if perr := objectLock.GetLock(globalOperationTimeout); perr != nil {
return perr
}
defer objectLock.Unlock()
if err = checkDelObjArgs(bucket, object); err != nil { if err = checkDelObjArgs(bucket, object); err != nil {
return err return err
} }

View File

@ -206,7 +206,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
} }
} }
// Fetch object from store. // Fetch object from store.
err = xl.GetObject(bucket, object, 0, int64(len("abcd")), ioutil.Discard) err = xl.GetObject(bucket, object, 0, int64(len("abcd")), ioutil.Discard, "")
err = errors.Cause(err) err = errors.Cause(err)
if err != toObjectErr(errXLReadQuorum, bucket, object) { if err != toObjectErr(errXLReadQuorum, bucket, object) {
t.Errorf("Expected putObject to fail with %v, but failed with %v", toObjectErr(errXLWriteQuorum, bucket, object), err) t.Errorf("Expected putObject to fail with %v, but failed with %v", toObjectErr(errXLWriteQuorum, bucket, object), err)

View File

@ -21,6 +21,7 @@ import (
"runtime/debug" "runtime/debug"
"sort" "sort"
"sync" "sync"
"time"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/minio/minio/pkg/disk" "github.com/minio/minio/pkg/disk"
@ -59,6 +60,9 @@ type xlObjects struct {
// Object cache enabled. // Object cache enabled.
objCacheEnabled bool objCacheEnabled bool
// name space mutex for object layer
nsMutex *nsLockMap
} }
// list of all errors that can be ignored in tree walk operation in XL // list of all errors that can be ignored in tree walk operation in XL
@ -106,8 +110,8 @@ func newXLObjects(storageDisks []StorageAPI) (ObjectLayer, error) {
mutex: &sync.Mutex{}, mutex: &sync.Mutex{},
storageDisks: newStorageDisks, storageDisks: newStorageDisks,
listPool: listPool, listPool: listPool,
nsMutex: newNSLock(globalIsDistXL),
} }
// Get cache size if _MINIO_CACHE environment variable is set. // Get cache size if _MINIO_CACHE environment variable is set.
var maxCacheSize uint64 var maxCacheSize uint64
if !globalXLObjCacheDisabled { if !globalXLObjCacheDisabled {
@ -171,6 +175,63 @@ func (xl xlObjects) Shutdown() error {
return nil return nil
} }
// Locking operations
// List namespace locks held in object layer
func (xl xlObjects) ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
xl.nsMutex.lockMapMutex.Lock()
defer xl.nsMutex.lockMapMutex.Unlock()
// Fetch current time once instead of fetching system time for every lock.
timeNow := UTCNow()
volumeLocks := []VolumeLockInfo{}
for param, debugLock := range xl.nsMutex.debugLockMap {
if param.volume != bucket {
continue
}
// N B empty prefix matches all param.path.
if !hasPrefix(param.path, prefix) {
continue
}
volLockInfo := VolumeLockInfo{
Bucket: param.volume,
Object: param.path,
LocksOnObject: debugLock.counters.total,
TotalBlockedLocks: debugLock.counters.blocked,
LocksAcquiredOnObject: debugLock.counters.granted,
}
// Filter locks that are held on bucket, prefix.
for opsID, lockInfo := range debugLock.lockInfo {
// filter locks that were held for longer than duration.
elapsed := timeNow.Sub(lockInfo.since)
if elapsed < duration {
continue
}
// Add locks that are held for longer than duration.
volLockInfo.LockDetailsOnObject = append(volLockInfo.LockDetailsOnObject,
OpsLockState{
OperationID: opsID,
LockSource: lockInfo.lockSource,
LockType: lockInfo.lType,
Status: lockInfo.status,
Since: lockInfo.since,
})
volumeLocks = append(volumeLocks, volLockInfo)
}
}
return volumeLocks, nil
}
// Clear namespace locks held in object layer
func (xl xlObjects) ClearLocks(volLocks []VolumeLockInfo) error {
// Remove lock matching bucket/prefix held longer than duration.
for _, volLock := range volLocks {
xl.nsMutex.ForceUnlock(volLock.Bucket, volLock.Object)
}
return nil
}
// byDiskTotal is a collection satisfying sort.Interface. // byDiskTotal is a collection satisfying sort.Interface.
type byDiskTotal []disk.Info type byDiskTotal []disk.Info