* 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>
This commit is contained in:
@@ -32,7 +32,7 @@ const (
|
|||||||
// Standard S3 HTTP request constants
|
// Standard S3 HTTP request constants
|
||||||
const (
|
const (
|
||||||
// S3 storage class
|
// S3 storage class
|
||||||
AmzStorageClass = "x-amz-storage-class"
|
AmzStorageClass = "X-Amz-Storage-Class"
|
||||||
|
|
||||||
// S3 user-defined metadata
|
// S3 user-defined metadata
|
||||||
AmzUserMetaPrefix = "X-Amz-Meta-"
|
AmzUserMetaPrefix = "X-Amz-Meta-"
|
||||||
|
|||||||
@@ -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
|
// Parse and store object tags from X-Amz-Tagging header
|
||||||
// Fix for GitHub issue #7589: Tags sent during object upload were not being stored
|
// Fix for GitHub issue #7589: Tags sent during object upload were not being stored
|
||||||
if tagging := r.Header.Get(s3_constants.AmzObjectTagging); tagging != "" {
|
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)
|
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"
|
||||||
|
}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ type ObjectVersion struct {
|
|||||||
ETag string
|
ETag string
|
||||||
Size int64
|
Size int64
|
||||||
OwnerID string // Owner ID extracted from entry metadata
|
OwnerID string // Owner ID extracted from entry metadata
|
||||||
|
StorageClass string
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDeleteMarker creates a delete marker for versioned delete operations
|
// createDeleteMarker creates a delete marker for versioned delete operations
|
||||||
@@ -413,7 +414,7 @@ func (vc *versionCollector) addVersion(version *ObjectVersion, objectKey string)
|
|||||||
ETag: version.ETag,
|
ETag: version.ETag,
|
||||||
Size: version.Size,
|
Size: version.Size,
|
||||||
Owner: vc.s3a.getObjectOwnerFromVersion(version, vc.bucket, objectKey),
|
Owner: vc.s3a.getObjectOwnerFromVersion(version, vc.bucket, objectKey),
|
||||||
StorageClass: "STANDARD",
|
StorageClass: StorageClass(vc.s3a.getStorageClassFromExtended(entryExtended(version))),
|
||||||
}
|
}
|
||||||
*vc.allVersions = append(*vc.allVersions, versionEntry)
|
*vc.allVersions = append(*vc.allVersions, versionEntry)
|
||||||
}
|
}
|
||||||
@@ -484,7 +485,7 @@ func (vc *versionCollector) processExplicitDirectory(entryPath string, entry *fi
|
|||||||
ETag: "\"d41d8cd98f00b204e9800998ecf8427e\"", // Empty content ETag
|
ETag: "\"d41d8cd98f00b204e9800998ecf8427e\"", // Empty content ETag
|
||||||
Size: 0,
|
Size: 0,
|
||||||
Owner: vc.s3a.getObjectOwnerFromEntry(entry),
|
Owner: vc.s3a.getObjectOwnerFromEntry(entry),
|
||||||
StorageClass: "STANDARD",
|
StorageClass: StorageClass(vc.s3a.getStorageClassFromExtended(entry.Extended)),
|
||||||
}
|
}
|
||||||
*vc.allVersions = append(*vc.allVersions, versionEntry)
|
*vc.allVersions = append(*vc.allVersions, versionEntry)
|
||||||
}
|
}
|
||||||
@@ -545,7 +546,7 @@ func (vc *versionCollector) processRegularFile(currentPath, entryPath string, en
|
|||||||
ETag: vc.s3a.calculateETagFromChunks(entry.Chunks),
|
ETag: vc.s3a.calculateETagFromChunks(entry.Chunks),
|
||||||
Size: int64(entry.Attributes.FileSize),
|
Size: int64(entry.Attributes.FileSize),
|
||||||
Owner: vc.s3a.getObjectOwnerFromEntry(entry),
|
Owner: vc.s3a.getObjectOwnerFromEntry(entry),
|
||||||
StorageClass: "STANDARD",
|
StorageClass: StorageClass(vc.s3a.getStorageClassFromExtended(entry.Extended)),
|
||||||
}
|
}
|
||||||
*vc.allVersions = append(*vc.allVersions, versionEntry)
|
*vc.allVersions = append(*vc.allVersions, versionEntry)
|
||||||
}
|
}
|
||||||
@@ -726,6 +727,7 @@ func (s3a *S3ApiServer) getObjectVersionList(bucket, object string) ([]*ObjectVe
|
|||||||
IsDeleteMarker: isDeleteMarker,
|
IsDeleteMarker: isDeleteMarker,
|
||||||
LastModified: time.Unix(entry.Attributes.Mtime, 0),
|
LastModified: time.Unix(entry.Attributes.Mtime, 0),
|
||||||
OwnerID: ownerID,
|
OwnerID: ownerID,
|
||||||
|
StorageClass: s3a.getStorageClassFromExtended(entry.Extended),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isDeleteMarker {
|
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"}
|
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
|
// getObjectOwnerFromEntry extracts object owner information from a file entry
|
||||||
func (s3a *S3ApiServer) getObjectOwnerFromEntry(entry *filer_pb.Entry) CanonicalUser {
|
func (s3a *S3ApiServer) getObjectOwnerFromEntry(entry *filer_pb.Entry) CanonicalUser {
|
||||||
if entry != nil && entry.Extended != nil {
|
if entry != nil && entry.Extended != nil {
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ const (
|
|||||||
|
|
||||||
// Bucket encryption errors
|
// Bucket encryption errors
|
||||||
ErrNoSuchBucketEncryptionConfiguration
|
ErrNoSuchBucketEncryptionConfiguration
|
||||||
|
ErrInvalidStorageClass
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error message constants for checksum validation
|
// Error message constants for checksum validation
|
||||||
@@ -588,6 +589,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
|||||||
Description: "The server side encryption configuration was not found.",
|
Description: "The server side encryption configuration was not found.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
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.
|
// GetAPIError provides API Error for input API error code.
|
||||||
|
|||||||
Reference in New Issue
Block a user