Browse Source

set ModifiedTsNs

pull/7481/head
chrislu 3 weeks ago
parent
commit
206e036e27
  1. 18
      weed/s3api/s3api_object_handlers_copy.go
  2. 15
      weed/s3api/s3api_object_handlers_put.go

18
weed/s3api/s3api_object_handlers_copy.go

@ -341,7 +341,7 @@ func (s3a *S3ApiServer) CopyObjectHandler(w http.ResponseWriter, r *http.Request
func pathToBucketAndObject(path string) (bucket, object string) { func pathToBucketAndObject(path string) (bucket, object string) {
// Remove leading slash if present // Remove leading slash if present
path = strings.TrimPrefix(path, "/") path = strings.TrimPrefix(path, "/")
// Split by first slash to separate bucket and object // Split by first slash to separate bucket and object
parts := strings.SplitN(path, "/", 2) parts := strings.SplitN(path, "/", 2)
if len(parts) == 2 { if len(parts) == 2 {
@ -360,7 +360,7 @@ func pathToBucketObjectAndVersion(path string) (bucket, object, versionId string
// Parse versionId from query string if present ONLY at path boundaries // Parse versionId from query string if present ONLY at path boundaries
// Format: /bucket/object?versionId=version-id // Format: /bucket/object?versionId=version-id
// Must ensure we're not matching "?versionId=" that's part of the object name itself // Must ensure we're not matching "?versionId=" that's part of the object name itself
// Look for ?versionId= that comes after the bucket/object path // Look for ?versionId= that comes after the bucket/object path
// The key insight: a real query parameter must either be at the end or followed by & // The key insight: a real query parameter must either be at the end or followed by &
if idx := strings.Index(path, "?versionId="); idx != -1 { if idx := strings.Index(path, "?versionId="); idx != -1 {
@ -395,9 +395,9 @@ func (s3a *S3ApiServer) CopyObjectPartHandler(w http.ResponseWriter, r *http.Req
// Copy source path. // Copy source path.
cpSrcPath := r.Header.Get("X-Amz-Copy-Source") cpSrcPath := r.Header.Get("X-Amz-Copy-Source")
glog.V(2).Infof("CopyObjectPart: Raw copy source header=%q (len=%d)", cpSrcPath, len(cpSrcPath)) glog.V(2).Infof("CopyObjectPart: Raw copy source header=%q (len=%d)", cpSrcPath, len(cpSrcPath))
// Try URL unescaping - AWS SDK sends URL-encoded copy sources // Try URL unescaping - AWS SDK sends URL-encoded copy sources
unescapedPath, err := url.QueryUnescape(cpSrcPath) unescapedPath, err := url.QueryUnescape(cpSrcPath)
if err != nil { if err != nil {
@ -406,18 +406,18 @@ func (s3a *S3ApiServer) CopyObjectPartHandler(w http.ResponseWriter, r *http.Req
unescapedPath = cpSrcPath unescapedPath = cpSrcPath
} }
cpSrcPath = unescapedPath cpSrcPath = unescapedPath
glog.V(2).Infof("CopyObjectPart: After unescape=%q (len=%d, bytes=%v)", cpSrcPath, len(cpSrcPath), []byte(cpSrcPath)) glog.V(2).Infof("CopyObjectPart: After unescape=%q (len=%d, bytes=%v)", cpSrcPath, len(cpSrcPath), []byte(cpSrcPath))
srcBucket, srcObject, srcVersionId := pathToBucketObjectAndVersion(cpSrcPath) srcBucket, srcObject, srcVersionId := pathToBucketObjectAndVersion(cpSrcPath)
glog.V(2).Infof("CopyObjectPart: Parsed srcBucket=%q (len=%d), srcObject=%q (len=%d), srcVersionId=%q",
glog.V(2).Infof("CopyObjectPart: Parsed srcBucket=%q (len=%d), srcObject=%q (len=%d), srcVersionId=%q",
srcBucket, len(srcBucket), srcObject, len(srcObject), srcVersionId) srcBucket, len(srcBucket), srcObject, len(srcObject), srcVersionId)
// If source object is empty or bucket is empty, reply back invalid copy source. // If source object is empty or bucket is empty, reply back invalid copy source.
// Note: srcObject can be "/" for root-level objects, but empty string means parsing failed // Note: srcObject can be "/" for root-level objects, but empty string means parsing failed
if srcObject == "" || srcBucket == "" { if srcObject == "" || srcBucket == "" {
glog.Errorf("CopyObjectPart: Invalid copy source - srcBucket=%q, srcObject=%q (original header: %q)",
glog.Errorf("CopyObjectPart: Invalid copy source - srcBucket=%q, srcObject=%q (original header: %q)",
srcBucket, srcObject, r.Header.Get("X-Amz-Copy-Source")) srcBucket, srcObject, r.Header.Get("X-Amz-Copy-Source"))
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidCopySource) s3err.WriteErrorResponse(w, r, s3err.ErrInvalidCopySource)
return return

15
weed/s3api/s3api_object_handlers_put.go

@ -387,13 +387,16 @@ func (s3a *S3ApiServer) putToFiler(r *http.Request, uploadUrl string, dataReader
// IMPORTANT: FileChunk.ETag must be base64-encoded (from uploadResult.ContentMd5) // IMPORTANT: FileChunk.ETag must be base64-encoded (from uploadResult.ContentMd5)
// NOT hex-encoded etag! The filer.ETagChunks() function expects base64. // NOT hex-encoded etag! The filer.ETagChunks() function expects base64.
// ModifiedTsNs is critical for multipart race condition handling - when multiple
// uploads for the same part number occur, the one with the latest timestamp wins
fileChunk := &filer_pb.FileChunk{ fileChunk := &filer_pb.FileChunk{
FileId: assignResult.FileId,
Offset: 0,
Size: uint64(uploadResult.Size),
ETag: uploadResult.ContentMd5, // Base64-encoded MD5 from volume server
Fid: fid,
CipherKey: uploadResult.CipherKey,
FileId: assignResult.FileId,
Offset: 0,
Size: uint64(uploadResult.Size),
ModifiedTsNs: now.UnixNano(), // Set to current time for duplicate part resolution
ETag: uploadResult.ContentMd5, // Base64-encoded MD5 from volume server
Fid: fid,
CipherKey: uploadResult.CipherKey,
} }
glog.V(3).Infof("putToFiler: Created FileChunk - fileId=%s, size=%d, etag(base64)=%s (for multipart ETag calc)", glog.V(3).Infof("putToFiler: Created FileChunk - fileId=%s, size=%d, etag(base64)=%s (for multipart ETag calc)",

Loading…
Cancel
Save