diff --git a/weed/s3api/s3api_object_handlers_put.go b/weed/s3api/s3api_object_handlers_put.go index 2ce91e07c..94433ecd6 100644 --- a/weed/s3api/s3api_object_handlers_put.go +++ b/weed/s3api/s3api_object_handlers_put.go @@ -636,11 +636,25 @@ func (s3a *S3ApiServer) updateLatestVersionInDirectory(bucket, object, versionId bucketDir := s3a.option.BucketsPath + "/" + bucket versionsObjectPath := object + ".versions" - // Get the current .versions directory entry + // Get the current .versions directory entry, create if it doesn't exist versionsEntry, err := s3a.getEntry(bucketDir, versionsObjectPath) if err != nil { - glog.Errorf("updateLatestVersionInDirectory: failed to get .versions entry: %v", err) - return fmt.Errorf("failed to get .versions entry: %w", err) + glog.V(2).Infof("updateLatestVersionInDirectory: .versions directory doesn't exist for %s/%s, creating it", bucket, object) + // Create the .versions directory if it doesn't exist + err = s3a.mkdir(bucketDir, versionsObjectPath, func(entry *filer_pb.Entry) { + entry.Attributes.Mime = s3_constants.FolderMimeType + if entry.Extended == nil { + entry.Extended = make(map[string][]byte) + } + entry.Extended[s3_constants.ExtLatestVersionIdKey] = []byte(versionId) + entry.Extended[s3_constants.ExtLatestVersionFileNameKey] = []byte(versionFileName) + }) + if err != nil { + glog.Errorf("updateLatestVersionInDirectory: failed to create .versions directory: %v", err) + return fmt.Errorf("failed to create .versions directory: %w", err) + } + glog.V(2).Infof("updateLatestVersionInDirectory: created .versions directory for %s/%s with version %s", bucket, object, versionId) + return nil } // Add or update the latest version metadata diff --git a/weed/s3api/s3api_object_versioning.go b/weed/s3api/s3api_object_versioning.go index e9802d71c..2ef445594 100644 --- a/weed/s3api/s3api_object_versioning.go +++ b/weed/s3api/s3api_object_versioning.go @@ -151,6 +151,8 @@ func (s3a *S3ApiServer) createDeleteMarker(bucket, object string) (string, error func (s3a *S3ApiServer) listObjectVersions(bucket, prefix, keyMarker, versionIdMarker, delimiter string, maxKeys int) (*S3ListObjectVersionsResult, error) { var allVersions []interface{} // Can contain VersionEntry or DeleteMarkerEntry + glog.V(1).Infof("listObjectVersions: listing versions for bucket %s, prefix '%s'", bucket, prefix) + // Track objects that have been processed to avoid duplicates processedObjects := make(map[string]bool) @@ -161,9 +163,12 @@ func (s3a *S3ApiServer) listObjectVersions(bucket, prefix, keyMarker, versionIdM bucketPath := path.Join(s3a.option.BucketsPath, bucket) err := s3a.findVersionsRecursively(bucketPath, "", &allVersions, processedObjects, seenVersionIds, bucket, prefix) if err != nil { + glog.Errorf("listObjectVersions: findVersionsRecursively failed: %v", err) return nil, err } + glog.V(1).Infof("listObjectVersions: found %d total versions", len(allVersions)) + // Sort by key, then by LastModified (newest first), then by VersionId for deterministic ordering sort.Slice(allVersions, func(i, j int) bool { var keyI, keyJ string @@ -218,6 +223,8 @@ func (s3a *S3ApiServer) listObjectVersions(bucket, prefix, keyMarker, versionIdM IsTruncated: len(allVersions) > maxKeys, } + glog.V(1).Infof("listObjectVersions: building response with %d versions (truncated: %v)", len(allVersions), result.IsTruncated) + // Limit results if len(allVersions) > maxKeys { allVersions = allVersions[:maxKeys] @@ -239,15 +246,19 @@ func (s3a *S3ApiServer) listObjectVersions(bucket, prefix, keyMarker, versionIdM result.DeleteMarkers = make([]DeleteMarkerEntry, 0) // Add versions to result - for _, version := range allVersions { + for i, version := range allVersions { switch v := version.(type) { case *VersionEntry: + glog.V(2).Infof("listObjectVersions: adding version %d: key=%s, versionId=%s", i, v.Key, v.VersionId) result.Versions = append(result.Versions, *v) case *DeleteMarkerEntry: + glog.V(2).Infof("listObjectVersions: adding delete marker %d: key=%s, versionId=%s", i, v.Key, v.VersionId) result.DeleteMarkers = append(result.DeleteMarkers, *v) } } + glog.V(1).Infof("listObjectVersions: final result - %d versions, %d delete markers", len(result.Versions), len(result.DeleteMarkers)) + return result, nil } @@ -768,20 +779,23 @@ func (s3a *S3ApiServer) getLatestObjectVersion(bucket, object string) (*filer_pb bucketDir := s3a.option.BucketsPath + "/" + bucket versionsObjectPath := object + ".versions" + glog.V(1).Infof("getLatestObjectVersion: looking for latest version of %s/%s", bucket, object) + // Get the .versions directory entry to read latest version metadata versionsEntry, err := s3a.getEntry(bucketDir, versionsObjectPath) if err != nil { // .versions directory doesn't exist - this can happen for objects that existed // before versioning was enabled on the bucket. Fall back to checking for a // regular (non-versioned) object file. - glog.V(2).Infof("getLatestObjectVersion: no .versions directory for %s%s, checking for pre-versioning object", bucket, object) + glog.V(1).Infof("getLatestObjectVersion: no .versions directory for %s%s (error: %v), checking for pre-versioning object", bucket, object, err) regularEntry, regularErr := s3a.getEntry(bucketDir, object) if regularErr != nil { + glog.V(1).Infof("getLatestObjectVersion: no pre-versioning object found for %s%s (error: %v)", bucket, object, regularErr) return nil, fmt.Errorf("failed to get %s%s .versions directory and no regular object found: %w", bucket, object, err) } - glog.V(2).Infof("getLatestObjectVersion: found pre-versioning object for %s/%s", bucket, object) + glog.V(1).Infof("getLatestObjectVersion: found pre-versioning object for %s/%s", bucket, object) return regularEntry, nil }