mirror of https://github.com/minio/minio.git
verify preconditions during CompleteMultipart (#19713)
Bonus: hold the write lock properly to apply optimistic concurrency during NewMultipartUpload()
This commit is contained in:
parent
60d7e8143a
commit
e8d14c0d90
|
@ -1542,13 +1542,15 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||||
} else {
|
} else {
|
||||||
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
|
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
|
||||||
}
|
}
|
||||||
if rinfo.Err != nil && minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
if rinfo.Err != nil {
|
||||||
rinfo.ReplicationStatus = replication.Failed
|
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
||||||
replLogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w",
|
rinfo.ReplicationStatus = replication.Failed
|
||||||
bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
replLogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w",
|
||||||
}
|
bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
||||||
if rinfo.Err != nil && minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
}
|
||||||
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
if minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
|
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -1568,7 +1570,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if minio.ToErrorResponse(err).Code == "PreconditionFailed" {
|
if minio.ToErrorResponse(err).Code == "PreconditionFailed" {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
attempts++
|
attempts++
|
||||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||||
|
@ -1643,6 +1645,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
||||||
UserMetadata: map[string]string{validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"]},
|
UserMetadata: map[string]string{validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"]},
|
||||||
Internal: minio.AdvancedPutOptions{
|
Internal: minio.AdvancedPutOptions{
|
||||||
SourceMTime: objInfo.ModTime,
|
SourceMTime: objInfo.ModTime,
|
||||||
|
SourceETag: objInfo.ETag,
|
||||||
// always set this to distinguish between `mc mirror` replication and serverside
|
// always set this to distinguish between `mc mirror` replication and serverside
|
||||||
ReplicationRequest: true,
|
ReplicationRequest: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -381,21 +381,24 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
|
||||||
// operation(s) on the object.
|
// operation(s) on the object.
|
||||||
func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string, object string, opts ObjectOptions) (*NewMultipartUploadResult, error) {
|
func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string, object string, opts ObjectOptions) (*NewMultipartUploadResult, error) {
|
||||||
if opts.CheckPrecondFn != nil {
|
if opts.CheckPrecondFn != nil {
|
||||||
// Lock the object before reading.
|
if !opts.NoLock {
|
||||||
lk := er.NewNSLock(bucket, object)
|
ns := er.NewNSLock(bucket, object)
|
||||||
lkctx, err := lk.GetRLock(ctx, globalOperationTimeout)
|
lkctx, err := ns.GetLock(ctx, globalOperationTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
|
defer ns.Unlock(lkctx)
|
||||||
|
opts.NoLock = true
|
||||||
}
|
}
|
||||||
rctx := lkctx.Context()
|
|
||||||
obj, err := er.getObjectInfo(rctx, bucket, object, opts)
|
obj, err := er.getObjectInfo(ctx, bucket, object, opts)
|
||||||
lk.RUnlock(lkctx)
|
if err == nil && opts.CheckPrecondFn(obj) {
|
||||||
if err != nil && !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if opts.CheckPrecondFn(obj) {
|
|
||||||
return nil, PreConditionFailed{}
|
return nil, PreConditionFailed{}
|
||||||
}
|
}
|
||||||
|
if err != nil && !isErrVersionNotFound(err) && !isErrObjectNotFound(err) && !isErrReadQuorum(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userDefined := cloneMSS(opts.UserDefined)
|
userDefined := cloneMSS(opts.UserDefined)
|
||||||
|
@ -1003,6 +1006,27 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||||
auditObjectErasureSet(ctx, object, &er)
|
auditObjectErasureSet(ctx, object, &er)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.CheckPrecondFn != nil {
|
||||||
|
if !opts.NoLock {
|
||||||
|
ns := er.NewNSLock(bucket, object)
|
||||||
|
lkctx, err := ns.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return ObjectInfo{}, err
|
||||||
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
|
defer ns.Unlock(lkctx)
|
||||||
|
opts.NoLock = true
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := er.getObjectInfo(ctx, bucket, object, opts)
|
||||||
|
if err == nil && opts.CheckPrecondFn(obj) {
|
||||||
|
return ObjectInfo{}, PreConditionFailed{}
|
||||||
|
}
|
||||||
|
if err != nil && !isErrVersionNotFound(err) && !isErrObjectNotFound(err) && !isErrReadQuorum(err) {
|
||||||
|
return ObjectInfo{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hold write locks to verify uploaded parts, also disallows any
|
// Hold write locks to verify uploaded parts, also disallows any
|
||||||
// parallel PutObjectPart() requests.
|
// parallel PutObjectPart() requests.
|
||||||
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
||||||
|
@ -1237,14 +1261,15 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hold namespace to complete the transaction
|
if !opts.NoLock {
|
||||||
lk := er.NewNSLock(bucket, object)
|
lk := er.NewNSLock(bucket, object)
|
||||||
lkctx, err := lk.GetLock(ctx, globalOperationTimeout)
|
lkctx, err := lk.GetLock(ctx, globalOperationTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oi, err
|
return ObjectInfo{}, err
|
||||||
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
|
defer lk.Unlock(lkctx)
|
||||||
}
|
}
|
||||||
ctx = lkctx.Context()
|
|
||||||
defer lk.Unlock(lkctx)
|
|
||||||
|
|
||||||
if checksumType.IsSet() {
|
if checksumType.IsSet() {
|
||||||
checksumType |= hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
checksumType |= hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
||||||
|
|
|
@ -997,6 +997,18 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||||
multipartETag := etag.Multipart(completeETags...)
|
multipartETag := etag.Multipart(completeETags...)
|
||||||
opts.UserDefined["etag"] = multipartETag.String()
|
opts.UserDefined["etag"] = multipartETag.String()
|
||||||
|
|
||||||
|
if opts.PreserveETag != "" ||
|
||||||
|
r.Header.Get(xhttp.IfMatch) != "" ||
|
||||||
|
r.Header.Get(xhttp.IfNoneMatch) != "" {
|
||||||
|
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
||||||
|
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return checkPreconditionsPUT(ctx, w, r, oi, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
objInfo, err := completeMultiPartUpload(ctx, bucket, object, uploadID, complMultipartUpload.Parts, opts)
|
objInfo, err := completeMultiPartUpload(ctx, bucket, object, uploadID, complMultipartUpload.Parts, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
|
Loading…
Reference in New Issue