Browse Source

handle retry if not found in .versions folder and should read the normal object

pull/7481/head
chrislu 3 weeks ago
parent
commit
8edb1e9641
  1. 116
      weed/s3api/s3api_object_handlers.go

116
weed/s3api/s3api_object_handlers.go

@ -327,14 +327,14 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
}
glog.V(1).Infof("GetObject: bucket %s, object %s, versioningConfigured=%v, versionId=%s", bucket, object, versioningConfigured, versionId)
glog.V(0).Infof("GetObject: bucket %s, object %s, versioningConfigured=%v, versionId=%s", bucket, object, versioningConfigured, versionId)
if versioningConfigured {
// Handle versioned GET - all versions are stored in .versions directory
// Handle versioned GET - check if specific version requested
var targetVersionId string
if versionId != "" {
// Request for specific version
// Request for specific version - must look in .versions directory
glog.V(2).Infof("GetObject: requesting specific version %s for %s%s", versionId, bucket, object)
entry, err = s3a.getSpecificObjectVersion(bucket, object, versionId)
if err != nil {
@ -350,20 +350,63 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
}
targetVersionId = versionId
} else {
// Request for latest version
glog.V(1).Infof("GetObject: requesting latest version for %s%s", bucket, object)
// Request for latest version - OPTIMIZATION for suspended versioning:
// For suspended versioning, new objects are stored at regular path with version ID "null".
// Check regular path FIRST to avoid 12-second retry delay on .versions directory.
glog.V(0).Infof("GetObject: requesting latest version for %s%s, checking regular path first", bucket, object)
bucketDir := s3a.option.BucketsPath + "/" + bucket
normalizedObject := removeDuplicateSlashes(object)
regularEntry, regularErr := s3a.getEntry(bucketDir, normalizedObject)
if regularErr == nil && regularEntry != nil {
// Found object at regular path - check if it's a null version or pre-versioning object
hasNullVersion := false
if regularEntry.Extended != nil {
if versionIdBytes, exists := regularEntry.Extended[s3_constants.ExtVersionIdKey]; exists {
versionIdStr := string(versionIdBytes)
if versionIdStr == "null" {
hasNullVersion = true
targetVersionId = "null"
}
}
}
if hasNullVersion || regularEntry.Extended == nil || regularEntry.Extended[s3_constants.ExtVersionIdKey] == nil {
// This is either a null version (suspended) or pre-versioning object
// Use it directly instead of checking .versions
glog.V(0).Infof("GetObject: found null/pre-versioning object at regular path for %s%s", bucket, object)
entry = regularEntry
if targetVersionId == "" {
targetVersionId = "null"
}
} else {
// Has a real version ID, must be in .versions - fall through to getLatestObjectVersion
glog.V(0).Infof("GetObject: regular path object has version ID, checking .versions for %s%s", bucket, object)
entry, err = s3a.getLatestObjectVersion(bucket, object)
if err != nil {
glog.Errorf("GetObject: Failed to get latest version for %s%s: %v", bucket, object, err)
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
return
}
}
} else {
// No object at regular path, check .versions directory
glog.V(0).Infof("GetObject: no object at regular path, checking .versions for %s%s", bucket, object)
entry, err = s3a.getLatestObjectVersion(bucket, object)
if err != nil {
glog.Errorf("GetObject: Failed to get latest version for %s%s: %v", bucket, object, err)
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
return
}
}
// Safety check: entry must be valid after successful retrieval
if entry == nil {
glog.Errorf("GetObject: getLatestObjectVersion returned nil entry without error for %s%s", bucket, object)
glog.Errorf("GetObject: entry is nil after versioned lookup for %s%s", bucket, object)
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
return
}
// Extract version ID if not already set
if targetVersionId == "" {
if entry.Extended != nil {
if versionIdBytes, exists := entry.Extended[s3_constants.ExtVersionIdKey]; exists {
targetVersionId = string(versionIdBytes)
@ -374,6 +417,7 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
targetVersionId = "null"
}
}
}
// Check if this is a delete marker
if entry.Extended != nil {
@ -631,6 +675,7 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
// For small files stored inline in entry.Content
if len(entry.Content) > 0 && totalSize == int64(len(entry.Content)) {
glog.V(0).Infof("streamFromVolumeServers: streaming %d bytes from entry.Content", len(entry.Content))
if isRangeRequest {
_, err := w.Write(entry.Content[offset : offset+size])
return err
@ -641,12 +686,19 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
// Get chunks
chunks := entry.GetChunks()
glog.V(0).Infof("streamFromVolumeServers: entry has %d chunks, totalSize=%d, len(entry.Content)=%d", len(chunks), totalSize, len(entry.Content))
if len(chunks) == 0 {
// BUG FIX: If totalSize > 0 but no chunks and no content, this is a data integrity issue
if totalSize > 0 && len(entry.Content) == 0 {
glog.Errorf("streamFromVolumeServers: Data integrity error - entry reports size %d but has no content or chunks", totalSize)
return fmt.Errorf("data integrity error: size %d reported but no content available", totalSize)
}
// Empty object - need to set headers before writing status
if !isRangeRequest {
// Headers were already set by setResponseHeaders above
w.WriteHeader(http.StatusOK)
}
glog.V(0).Infof("streamFromVolumeServers: empty object (totalSize=%d), returning", totalSize)
return nil
}
@ -1226,20 +1278,63 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
}
targetVersionId = versionId
} else {
// Request for latest version
glog.V(2).Infof("HeadObject: requesting latest version for %s%s", bucket, object)
// Request for latest version - OPTIMIZATION for suspended versioning:
// For suspended versioning, new objects are stored at regular path with version ID "null".
// Check regular path FIRST to avoid 12-second retry delay on .versions directory.
glog.V(0).Infof("HeadObject: requesting latest version for %s%s, checking regular path first", bucket, object)
bucketDir := s3a.option.BucketsPath + "/" + bucket
normalizedObject := removeDuplicateSlashes(object)
regularEntry, regularErr := s3a.getEntry(bucketDir, normalizedObject)
if regularErr == nil && regularEntry != nil {
// Found object at regular path - check if it's a null version or pre-versioning object
hasNullVersion := false
if regularEntry.Extended != nil {
if versionIdBytes, exists := regularEntry.Extended[s3_constants.ExtVersionIdKey]; exists {
versionIdStr := string(versionIdBytes)
if versionIdStr == "null" {
hasNullVersion = true
targetVersionId = "null"
}
}
}
if hasNullVersion || regularEntry.Extended == nil || regularEntry.Extended[s3_constants.ExtVersionIdKey] == nil {
// This is either a null version (suspended) or pre-versioning object
// Use it directly instead of checking .versions
glog.V(0).Infof("HeadObject: found null/pre-versioning object at regular path for %s%s", bucket, object)
entry = regularEntry
if targetVersionId == "" {
targetVersionId = "null"
}
} else {
// Has a real version ID, must be in .versions - fall through to getLatestObjectVersion
glog.V(0).Infof("HeadObject: regular path object has version ID, checking .versions for %s%s", bucket, object)
entry, err = s3a.getLatestObjectVersion(bucket, object)
if err != nil {
glog.Errorf("Failed to get latest version: %v", err)
glog.Errorf("HeadObject: Failed to get latest version for %s%s: %v", bucket, object, err)
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
return
}
}
} else {
// No object at regular path, check .versions directory
glog.V(0).Infof("HeadObject: no object at regular path, checking .versions for %s%s", bucket, object)
entry, err = s3a.getLatestObjectVersion(bucket, object)
if err != nil {
glog.Errorf("HeadObject: Failed to get latest version for %s%s: %v", bucket, object, err)
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
return
}
}
// Safety check: entry must be valid after successful retrieval
if entry == nil {
glog.Errorf("HeadObject: getLatestObjectVersion returned nil entry without error for %s%s", bucket, object)
glog.Errorf("HeadObject: entry is nil after versioned lookup for %s%s", bucket, object)
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
return
}
// Extract version ID if not already set
if targetVersionId == "" {
if entry.Extended != nil {
if versionIdBytes, exists := entry.Extended[s3_constants.ExtVersionIdKey]; exists {
targetVersionId = string(versionIdBytes)
@ -1250,6 +1345,7 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
targetVersionId = "null"
}
}
}
// Check if this is a delete marker
if entry.Extended != nil {

Loading…
Cancel
Save