Browse Source

store S3 storage class in extended atrributes #7961 (#7962)

* store S3 storage class in extended atrributes #7961

* canonical

* remove issue reference

---------

Co-authored-by: Robert Schade <robert.schade@uni-paderborn.de>
Co-authored-by: Chris Lu <chris.lu@gmail.com>
pull/7965/head
Robert Schade 4 weeks ago
committed by GitHub
parent
commit
de3df211d7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      weed/s3api/s3_constants/header.go
  2. 26
      weed/s3api/s3api_object_handlers_put.go
  3. 18
      weed/s3api/s3api_object_versioning.go
  4. 6
      weed/s3api/s3err/s3api_errors.go

2
weed/s3api/s3_constants/header.go

@ -32,7 +32,7 @@ const (
// Standard S3 HTTP request constants
const (
// S3 storage class
AmzStorageClass = "x-amz-storage-class"
AmzStorageClass = "X-Amz-Storage-Class"
// S3 user-defined metadata
AmzUserMetaPrefix = "X-Amz-Meta-"

26
weed/s3api/s3api_object_handlers_put.go

@ -571,6 +571,15 @@ func (s3a *S3ApiServer) putToFiler(r *http.Request, filePath string, dataReader
}
}
// Store the storage class from header
if sc := r.Header.Get(s3_constants.AmzStorageClass); sc != "" {
if !validateStorageClass(sc) {
glog.Warningf("putToFiler: Invalid storage class '%s' for %s", sc, filePath)
return "", s3err.ErrInvalidStorageClass, SSEResponseMetadata{}
}
entry.Extended[s3_constants.AmzStorageClass] = []byte(sc)
}
// Parse and store object tags from X-Amz-Tagging header
// Fix for GitHub issue #7589: Tags sent during object upload were not being stored
if tagging := r.Header.Get(s3_constants.AmzObjectTagging); tagging != "" {
@ -1881,3 +1890,20 @@ func (s3a *S3ApiServer) deleteOrphanedChunks(chunks []*filer_pb.FileChunk) {
glog.V(3).Infof("deleteOrphanedChunks: successfully deleted all %d orphaned chunks", successCount)
}
}
func validateStorageClass(sc string) bool {
switch StorageClass(sc) {
case "STANDARD", "REDUCED_REDUNDANCY", "STANDARD_IA", "ONEZONE_IA", "INTELLIGENT_TIERING", "GLACIER", "DEEP_ARCHIVE", "OUTPOSTS", "GLACIER_IR", "SNOW":
return true
}
return false
}
func (s3a *S3ApiServer) getStorageClassFromExtended(extended map[string][]byte) string {
if extended != nil {
if sc, ok := extended[s3_constants.AmzStorageClass]; ok {
return string(sc)
}
}
return "STANDARD"
}

18
weed/s3api/s3api_object_versioning.go

@ -134,6 +134,7 @@ type ObjectVersion struct {
ETag string
Size int64
OwnerID string // Owner ID extracted from entry metadata
StorageClass string
}
// createDeleteMarker creates a delete marker for versioned delete operations
@ -413,7 +414,7 @@ func (vc *versionCollector) addVersion(version *ObjectVersion, objectKey string)
ETag: version.ETag,
Size: version.Size,
Owner: vc.s3a.getObjectOwnerFromVersion(version, vc.bucket, objectKey),
StorageClass: "STANDARD",
StorageClass: StorageClass(vc.s3a.getStorageClassFromExtended(entryExtended(version))),
}
*vc.allVersions = append(*vc.allVersions, versionEntry)
}
@ -484,7 +485,7 @@ func (vc *versionCollector) processExplicitDirectory(entryPath string, entry *fi
ETag: "\"d41d8cd98f00b204e9800998ecf8427e\"", // Empty content ETag
Size: 0,
Owner: vc.s3a.getObjectOwnerFromEntry(entry),
StorageClass: "STANDARD",
StorageClass: StorageClass(vc.s3a.getStorageClassFromExtended(entry.Extended)),
}
*vc.allVersions = append(*vc.allVersions, versionEntry)
}
@ -545,7 +546,7 @@ func (vc *versionCollector) processRegularFile(currentPath, entryPath string, en
ETag: vc.s3a.calculateETagFromChunks(entry.Chunks),
Size: int64(entry.Attributes.FileSize),
Owner: vc.s3a.getObjectOwnerFromEntry(entry),
StorageClass: "STANDARD",
StorageClass: StorageClass(vc.s3a.getStorageClassFromExtended(entry.Extended)),
}
*vc.allVersions = append(*vc.allVersions, versionEntry)
}
@ -726,6 +727,7 @@ func (s3a *S3ApiServer) getObjectVersionList(bucket, object string) ([]*ObjectVe
IsDeleteMarker: isDeleteMarker,
LastModified: time.Unix(entry.Attributes.Mtime, 0),
OwnerID: ownerID,
StorageClass: s3a.getStorageClassFromExtended(entry.Extended),
}
if !isDeleteMarker {
@ -1253,10 +1255,18 @@ func (s3a *S3ApiServer) getObjectOwnerFromVersion(version *ObjectVersion, bucket
}
}
// Ultimate fallback: return anonymous if no owner found
// Fallback: return anonymous if no owner found
return CanonicalUser{ID: s3_constants.AccountAnonymousId, DisplayName: "anonymous"}
}
func entryExtended(v *ObjectVersion) map[string][]byte {
return map[string][]byte{
s3_constants.AmzStorageClass: []byte(v.StorageClass),
s3_constants.ExtAmzOwnerKey: []byte(v.OwnerID),
s3_constants.ExtETagKey: []byte(v.ETag),
}
}
// getObjectOwnerFromEntry extracts object owner information from a file entry
func (s3a *S3ApiServer) getObjectOwnerFromEntry(entry *filer_pb.Entry) CanonicalUser {
if entry != nil && entry.Extended != nil {

6
weed/s3api/s3err/s3api_errors.go

@ -141,6 +141,7 @@ const (
// Bucket encryption errors
ErrNoSuchBucketEncryptionConfiguration
ErrInvalidStorageClass
)
// Error message constants for checksum validation
@ -588,6 +589,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "The server side encryption configuration was not found.",
HTTPStatusCode: http.StatusNotFound,
},
ErrInvalidStorageClass: {
Code: "InvalidStorageClass",
Description: "The storage class you specified is not valid",
HTTPStatusCode: http.StatusBadRequest,
},
}
// GetAPIError provides API Error for input API error code.

Loading…
Cancel
Save