s3: route versioned COPY and delete-marker off the DLM

Both create a unique version/marker file plus an atomic latest-pointer flip —
the same shape as versioned PutObject — so both now reuse FinalizeVersionedWrite
instead of the distributed lock.

finalizeCopyDestination (VersioningEnabled) and createDeleteMarker take an
optional owner + condition; when the .versions owner is known they run the
demote + pointer flip (and the precondition) atomically on that owner via
routedVersionedFinalize, rolling back the version/marker file on failure and
surfacing a precondition miss as 412. CopyObjectHandler and DeleteObjectHandler
take the routed path for versioning-enabled destinations and skip the lock;
suspended versioning, version-specific deletes, metadata-only self-copy, and the
lifecycle marker path keep the existing lock-based path.

No proto or filer changes — reuses the op from the versioned-PutObject PR.
This commit is contained in:
Chris Lu committed 2026-05-23 09:48:25 -07:00
1 parent 2d41d37150
commit a99c8214bb
5 files changed
+107 -15

No files matched your search

@@ -2,6 +2,8 @@ package s3api
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"time"
@@ -13,6 +15,24 @@ import (
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)
// errVersionedPreconditionFailed is returned by versioned finalize helpers whose
// contract is (…, error) when the routed FinalizeVersionedWrite reports a failed
// precondition, so callers can surface it as 412 rather than a generic 500.
var errVersionedPreconditionFailed = errors.New("versioned write precondition failed")
// finalizeCodeToError adapts a routedVersionedFinalize result to an error: nil on
// success, the precondition sentinel on 412, else a generic error.
func finalizeCodeToError(code s3err.ErrorCode) error {
switch code {
case s3err.ErrNone:
return nil
case s3err.ErrPreconditionFailed:
return errVersionedPreconditionFailed
default:
return fmt.Errorf("versioned finalize failed: code %v", code)
}
}
// objectWriteOwner returns the filer that owns all of an object's writes, or ""
// when no ring view is available. It hashes the object key with the same prefix
// the non-versioned path uses (routedObjectOwner), so normal, suspended, and