mirror of
https://github.com/minio/minio.git
synced 2025-04-22 19:35:47 -04:00
allow copyObject to rotate storageClass of objects (#9362)
Added additional mint tests as well to verify, this functionality. Fixes #9357
This commit is contained in:
parent
7758524703
commit
8bae956df6
@ -610,10 +610,29 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m
|
|||||||
// remove SSE Headers from source info
|
// remove SSE Headers from source info
|
||||||
crypto.RemoveSSEHeaders(defaultMeta)
|
crypto.RemoveSSEHeaders(defaultMeta)
|
||||||
|
|
||||||
|
// Storage class is special, it can be replaced regardless of the
|
||||||
|
// metadata directive, if set should be preserved and replaced
|
||||||
|
// to the destination metadata.
|
||||||
|
sc := r.Header.Get(xhttp.AmzStorageClass)
|
||||||
|
if sc == "" {
|
||||||
|
sc = r.URL.Query().Get(xhttp.AmzStorageClass)
|
||||||
|
}
|
||||||
|
|
||||||
// if x-amz-metadata-directive says REPLACE then
|
// if x-amz-metadata-directive says REPLACE then
|
||||||
// we extract metadata from the input headers.
|
// we extract metadata from the input headers.
|
||||||
if isDirectiveReplace(r.Header.Get(xhttp.AmzMetadataDirective)) {
|
if isDirectiveReplace(r.Header.Get(xhttp.AmzMetadataDirective)) {
|
||||||
return extractMetadata(ctx, r)
|
emetadata, err := extractMetadata(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sc != "" {
|
||||||
|
emetadata[xhttp.AmzStorageClass] = sc
|
||||||
|
}
|
||||||
|
return emetadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc != "" {
|
||||||
|
defaultMeta[xhttp.AmzStorageClass] = sc
|
||||||
}
|
}
|
||||||
|
|
||||||
// if x-amz-metadata-directive says COPY then we
|
// if x-amz-metadata-directive says COPY then we
|
||||||
@ -778,6 +797,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate storage class metadata if present
|
||||||
|
dstSc := r.Header.Get(xhttp.AmzStorageClass)
|
||||||
|
if dstSc != "" && !storageclass.IsValid(dstSc) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidStorageClass), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Check if bucket encryption is enabled
|
// Check if bucket encryption is enabled
|
||||||
_, encEnabled := globalBucketSSEConfigSys.Get(dstBucket)
|
_, encEnabled := globalBucketSSEConfigSys.Get(dstBucket)
|
||||||
// This request header needs to be set prior to setting ObjectOptions
|
// This request header needs to be set prior to setting ObjectOptions
|
||||||
@ -842,6 +868,15 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
srcInfo.metadataOnly = true
|
srcInfo.metadataOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chStorageClass bool
|
||||||
|
if dstSc != "" {
|
||||||
|
sc, ok := srcInfo.UserDefined[xhttp.AmzStorageClass]
|
||||||
|
if (ok && dstSc != sc) || (srcInfo.StorageClass != dstSc) {
|
||||||
|
chStorageClass = true
|
||||||
|
srcInfo.metadataOnly = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
var length = srcInfo.Size
|
var length = srcInfo.Size
|
||||||
|
|
||||||
@ -923,9 +958,10 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
// If src == dst and either
|
// If src == dst and either
|
||||||
// - the object is encrypted using SSE-C and two different SSE-C keys are present
|
// - the object is encrypted using SSE-C and two different SSE-C keys are present
|
||||||
// - the object is encrypted using SSE-S3 and the SSE-S3 header is present
|
// - the object is encrypted using SSE-S3 and the SSE-S3 header is present
|
||||||
// than execute a key rotation.
|
// - the object storage class is not changing
|
||||||
|
// then execute a key rotation.
|
||||||
var keyRotation bool
|
var keyRotation bool
|
||||||
if cpSrcDstSame && (sseCopyC && sseC) {
|
if cpSrcDstSame && (sseCopyC && sseC) && !chStorageClass {
|
||||||
oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined)
|
oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
@ -643,12 +643,139 @@ function test_copy_object() {
|
|||||||
out=$($function)
|
out=$($function)
|
||||||
rv=$?
|
rv=$?
|
||||||
hash2=$(echo "$out" | jq -r .CopyObjectResult.ETag | sed -e 's/^"//' -e 's/"$//')
|
hash2=$(echo "$out" | jq -r .CopyObjectResult.ETag | sed -e 's/^"//' -e 's/"$//')
|
||||||
if [ $rv -eq 0 ] && [ "$HASH_1_KB" == "$hash2" ]; then
|
if [ $rv -eq 0 ] && [ "$HASH_1_KB" != "$hash2" ]; then
|
||||||
function="delete_bucket"
|
# Verification failed
|
||||||
out=$(delete_bucket "$bucket_name")
|
rv=1
|
||||||
|
out="Hash mismatch expected $HASH_1_KB, got $hash2"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
log_success "$(get_duration "$start_time")" "${test_function}"
|
||||||
|
else
|
||||||
|
# clean up and log error
|
||||||
|
${AWS} s3 rb s3://"${bucket_name}" --force > /dev/null 2>&1
|
||||||
|
log_failure "$(get_duration "$start_time")" "${function}" "${out}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return $rv
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy object tests for server side copy
|
||||||
|
# of the object, validates returned md5sum.
|
||||||
|
# validates change in storage class as well
|
||||||
|
function test_copy_object_storage_class() {
|
||||||
|
# log start time
|
||||||
|
start_time=$(get_time)
|
||||||
|
|
||||||
|
function="make_bucket"
|
||||||
|
bucket_name=$(make_bucket)
|
||||||
|
rv=$?
|
||||||
|
|
||||||
|
# if make bucket succeeds upload a file
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
function="${AWS} s3api put-object --body ${MINT_DATA_DIR}/datafile-1-kB --bucket ${bucket_name} --key datafile-1-kB"
|
||||||
|
out=$($function 2>&1)
|
||||||
|
rv=$?
|
||||||
|
else
|
||||||
|
# if make bucket fails, $bucket_name has the error output
|
||||||
|
out="${bucket_name}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# copy object server side
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
function="${AWS} s3api copy-object --bucket ${bucket_name} --storage-class REDUCED_REDUNDANCY --key datafile-1-kB-copy --copy-source ${bucket_name}/datafile-1-kB"
|
||||||
|
test_function=${function}
|
||||||
|
out=$($function)
|
||||||
|
rv=$?
|
||||||
|
hash2=$(echo "$out" | jq -r .CopyObjectResult.ETag | sed -e 's/^"//' -e 's/"$//')
|
||||||
|
if [ $rv -eq 0 ] && [ "$HASH_1_KB" != "$hash2" ]; then
|
||||||
|
# Verification failed
|
||||||
|
rv=1
|
||||||
|
out="Hash mismatch expected $HASH_1_KB, got $hash2"
|
||||||
|
fi
|
||||||
|
# if copy succeeds stat the object
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
function="${AWS} s3api head-object --bucket ${bucket_name} --key datafile-1-kB-copy"
|
||||||
|
# save the ref to function being tested, so it can be logged
|
||||||
|
test_function=${function}
|
||||||
|
out=$($function 2>&1)
|
||||||
|
storageClass=$(echo "$out" | jq -r .StorageClass)
|
||||||
rv=$?
|
rv=$?
|
||||||
# The command passed, but the verification failed
|
fi
|
||||||
out="Verification failed for copied object"
|
# if head-object succeeds, verify metadata has storage class
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
if [ "${storageClass}" == "null" ]; then
|
||||||
|
rv=1
|
||||||
|
out="StorageClass was not applied"
|
||||||
|
elif [ "${storageClass}" == "STANDARD" ]; then
|
||||||
|
rv=1
|
||||||
|
out="StorageClass was applied incorrectly"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
log_success "$(get_duration "$start_time")" "${test_function}"
|
||||||
|
else
|
||||||
|
# clean up and log error
|
||||||
|
${AWS} s3 rb s3://"${bucket_name}" --force > /dev/null 2>&1
|
||||||
|
log_failure "$(get_duration "$start_time")" "${function}" "${out}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return $rv
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy object tests for server side copy
|
||||||
|
# to itself by changing storage class
|
||||||
|
function test_copy_object_storage_class_same() {
|
||||||
|
# log start time
|
||||||
|
start_time=$(get_time)
|
||||||
|
|
||||||
|
function="make_bucket"
|
||||||
|
bucket_name=$(make_bucket)
|
||||||
|
rv=$?
|
||||||
|
|
||||||
|
# if make bucket succeeds upload a file
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
function="${AWS} s3api put-object --body ${MINT_DATA_DIR}/datafile-1-kB --bucket ${bucket_name} --key datafile-1-kB"
|
||||||
|
out=$($function 2>&1)
|
||||||
|
rv=$?
|
||||||
|
else
|
||||||
|
# if make bucket fails, $bucket_name has the error output
|
||||||
|
out="${bucket_name}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# copy object server side
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
function="${AWS} s3api copy-object --bucket ${bucket_name} --storage-class REDUCED_REDUNDANCY --key datafile-1-kB --copy-source ${bucket_name}/datafile-1-kB"
|
||||||
|
test_function=${function}
|
||||||
|
out=$($function)
|
||||||
|
rv=$?
|
||||||
|
hash2=$(echo "$out" | jq -r .CopyObjectResult.ETag | sed -e 's/^"//' -e 's/"$//')
|
||||||
|
if [ $rv -eq 0 ] && [ "$HASH_1_KB" != "$hash2" ]; then
|
||||||
|
# Verification failed
|
||||||
|
rv=1
|
||||||
|
out="Hash mismatch expected $HASH_1_KB, got $hash2"
|
||||||
|
fi
|
||||||
|
# if copy succeeds stat the object
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
function="${AWS} s3api head-object --bucket ${bucket_name} --key datafile-1-kB"
|
||||||
|
# save the ref to function being tested, so it can be logged
|
||||||
|
test_function=${function}
|
||||||
|
out=$($function 2>&1)
|
||||||
|
storageClass=$(echo "$out" | jq -r .StorageClass)
|
||||||
|
rv=$?
|
||||||
|
fi
|
||||||
|
# if head-object succeeds, verify metadata has storage class
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
if [ "${storageClass}" == "null" ]; then
|
||||||
|
rv=1
|
||||||
|
out="StorageClass was not applied"
|
||||||
|
elif [ "${storageClass}" == "STANDARD" ]; then
|
||||||
|
rv=1
|
||||||
|
out="StorageClass was applied incorrectly"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -1471,7 +1598,7 @@ function test_legal_hold() {
|
|||||||
out="${bucket_name}"
|
out="${bucket_name}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# if upload succeeds download the file
|
# if upload succeeds stat the file
|
||||||
if [ $rv -eq 0 ]; then
|
if [ $rv -eq 0 ]; then
|
||||||
function="${AWS} s3api head-object --bucket ${bucket_name} --key datafile-1-kB"
|
function="${AWS} s3api head-object --bucket ${bucket_name} --key datafile-1-kB"
|
||||||
# save the ref to function being tested, so it can be logged
|
# save the ref to function being tested, so it can be logged
|
||||||
@ -1551,6 +1678,8 @@ main() {
|
|||||||
test_multipart_upload && \
|
test_multipart_upload && \
|
||||||
test_max_key_list && \
|
test_max_key_list && \
|
||||||
test_copy_object && \
|
test_copy_object && \
|
||||||
|
test_copy_object_storage_class && \
|
||||||
|
test_copy_object_storage_class_same && \
|
||||||
test_presigned_object && \
|
test_presigned_object && \
|
||||||
test_upload_object_10 && \
|
test_upload_object_10 && \
|
||||||
test_multipart_upload_10 && \
|
test_multipart_upload_10 && \
|
||||||
|
Loading…
x
Reference in New Issue
Block a user