mirror of
https://github.com/pgsty/minio.git
synced 2026-11-10 09:35:31 +03:00
fix(restore): rehydrate the selected version under its object lock
Preserve handler version context, resolve the requested version's pool, and return on the initial read failure without writing metadata. Propagate owned NoLock to single-part writes, multipart completion and failure cleanup in the same patch. Preserve the original modification time on cleanup. Route the restore status copy by version and allow internal multipart rehydration only for a matching completed transition. Single-pool restores own the metadata snapshot through download and cleanup. Keep public completion in object-before-upload order, using the target set's object lock for single-pool calls, so concurrent completion cannot invert locks. Cover real signed POSTs, null/UUID and cross-pool identity, download failures, multipart overwrite guards, Object Lock updates and concurrent completion. This package depends on the restored-copy cleanup safety delivered in R2. Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
+23
-2
@@ -2644,6 +2644,8 @@ func (er erasureObjects) updateRestoreMetadata(ctx context.Context, bucket, obje
|
||||
VersionID: oi.VersionID,
|
||||
}, ObjectOptions{
|
||||
VersionID: oi.VersionID,
|
||||
MTime: oi.ModTime,
|
||||
NoLock: opts.NoLock,
|
||||
}); err != nil {
|
||||
storageLogIf(ctx, fmt.Errorf("Unable to update transition restore metadata for %s/%s(%s): %s", bucket, object, oi.VersionID, err))
|
||||
return err
|
||||
@@ -2654,6 +2656,19 @@ func (er erasureObjects) updateRestoreMetadata(ctx context.Context, bucket, obje
|
||||
// restoreTransitionedObject for multipart object chunks the file stream from remote tier into the same number of parts
|
||||
// as in the xl.meta for this version and rehydrates the part.n into the fi.DataDir for this version as in the xl.meta
|
||||
func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket string, object string, opts ObjectOptions) error {
|
||||
if !opts.NoLock {
|
||||
// Single-pool and direct callers must protect the metadata snapshot
|
||||
// through download, rehydration and failure cleanup, just like pools.
|
||||
lk := er.NewNSLock(bucket, object)
|
||||
lkctx, err := lk.GetLock(ctx, globalDeleteOperationTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx = lkctx.Context()
|
||||
defer lk.Unlock(lkctx)
|
||||
opts.NoLock = true
|
||||
}
|
||||
|
||||
setRestoreHeaderFn := func(oi ObjectInfo, rerr error) error {
|
||||
if rerr == nil {
|
||||
return nil // nothing to do; restore object was successful
|
||||
@@ -2665,11 +2680,13 @@ func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket s
|
||||
// get the file info on disk for transitioned object
|
||||
actualfi, _, _, err := er.getObjectFileInfo(ctx, bucket, object, opts, false)
|
||||
if err != nil {
|
||||
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
|
||||
// No version was read, so there is no restore metadata to update.
|
||||
return toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
oi = actualfi.ToObjectInfo(bucket, object, opts.Versioned || opts.VersionSuspended)
|
||||
ropts := putRestoreOpts(bucket, object, opts.Transition.RestoreRequest, oi)
|
||||
ropts.NoLock = opts.NoLock
|
||||
if len(oi.Parts) == 1 {
|
||||
var rs *HTTPRangeSpec
|
||||
gr, err := getTransitionedObjectReader(ctx, bucket, object, rs, http.Header{}, oi, opts)
|
||||
@@ -2719,7 +2736,11 @@ func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket s
|
||||
})
|
||||
}
|
||||
_, err = er.CompleteMultipartUpload(ctx, bucket, object, res.UploadID, uploadedParts, ObjectOptions{
|
||||
MTime: oi.ModTime,
|
||||
MTime: oi.ModTime,
|
||||
NoLock: opts.NoLock,
|
||||
Versioned: opts.Versioned,
|
||||
VersionSuspended: opts.VersionSuspended,
|
||||
Transition: opts.Transition,
|
||||
})
|
||||
return setRestoreHeaderFn(oi, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user