mirror of
https://github.com/pgsty/minio.git
synced 2026-09-05 18:16:16 +03:00
fix: return checksums from CopyObject
Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -1832,6 +1832,12 @@
|
|||||||
"cmd:cmd:field:CopyObjectPartResponse.ETag",
|
"cmd:cmd:field:CopyObjectPartResponse.ETag",
|
||||||
"cmd:cmd:field:CopyObjectPartResponse.LastModified",
|
"cmd:cmd:field:CopyObjectPartResponse.LastModified",
|
||||||
"cmd:cmd:field:CopyObjectPartResponse.XMLName",
|
"cmd:cmd:field:CopyObjectPartResponse.XMLName",
|
||||||
|
"cmd:cmd:field:CopyObjectResponse.ChecksumCRC32",
|
||||||
|
"cmd:cmd:field:CopyObjectResponse.ChecksumCRC32C",
|
||||||
|
"cmd:cmd:field:CopyObjectResponse.ChecksumCRC64NVME",
|
||||||
|
"cmd:cmd:field:CopyObjectResponse.ChecksumSHA1",
|
||||||
|
"cmd:cmd:field:CopyObjectResponse.ChecksumSHA256",
|
||||||
|
"cmd:cmd:field:CopyObjectResponse.ChecksumType",
|
||||||
"cmd:cmd:field:CopyObjectResponse.ETag",
|
"cmd:cmd:field:CopyObjectResponse.ETag",
|
||||||
"cmd:cmd:field:CopyObjectResponse.LastModified",
|
"cmd:cmd:field:CopyObjectResponse.LastModified",
|
||||||
"cmd:cmd:field:CopyObjectResponse.XMLName",
|
"cmd:cmd:field:CopyObjectResponse.XMLName",
|
||||||
|
|||||||
+18
-5
@@ -27,7 +27,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/minio/minio/internal/amztime"
|
"github.com/minio/minio/internal/amztime"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
@@ -380,6 +379,13 @@ type CopyObjectResponse struct {
|
|||||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"`
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"`
|
||||||
LastModified string // time string of format "2006-01-02T15:04:05.000Z"
|
LastModified string // time string of format "2006-01-02T15:04:05.000Z"
|
||||||
ETag string // md5sum of the copied object.
|
ETag string // md5sum of the copied object.
|
||||||
|
|
||||||
|
ChecksumCRC32 string `xml:",omitempty"`
|
||||||
|
ChecksumCRC32C string `xml:",omitempty"`
|
||||||
|
ChecksumSHA1 string `xml:",omitempty"`
|
||||||
|
ChecksumSHA256 string `xml:",omitempty"`
|
||||||
|
ChecksumCRC64NVME string `xml:",omitempty"`
|
||||||
|
ChecksumType string `xml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyObjectPartResponse container returns ETag and LastModified of the successfully copied object
|
// CopyObjectPartResponse container returns ETag and LastModified of the successfully copied object
|
||||||
@@ -769,11 +775,18 @@ func generateListObjectsV2Response(ctx context.Context, bucket, prefix, token, n
|
|||||||
|
|
||||||
type metaCheckFn = func(name string, action policy.Action) (s3Err APIErrorCode)
|
type metaCheckFn = func(name string, action policy.Action) (s3Err APIErrorCode)
|
||||||
|
|
||||||
// generates CopyObjectResponse from etag and lastModified time.
|
// generates CopyObjectResponse from the committed object information.
|
||||||
func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
|
func generateCopyObjectResponse(oi ObjectInfo, h http.Header) CopyObjectResponse {
|
||||||
|
cs, _ := oi.decryptChecksums(0, h)
|
||||||
return CopyObjectResponse{
|
return CopyObjectResponse{
|
||||||
ETag: "\"" + etag + "\"",
|
ETag: "\"" + oi.ETag + "\"",
|
||||||
LastModified: amztime.ISO8601Format(lastModified.UTC()),
|
LastModified: amztime.ISO8601Format(oi.ModTime.UTC()),
|
||||||
|
ChecksumCRC32: cs[hash.ChecksumCRC32.String()],
|
||||||
|
ChecksumCRC32C: cs[hash.ChecksumCRC32C.String()],
|
||||||
|
ChecksumSHA1: cs[hash.ChecksumSHA1.String()],
|
||||||
|
ChecksumSHA256: cs[hash.ChecksumSHA256.String()],
|
||||||
|
ChecksumCRC64NVME: cs[hash.ChecksumCRC64NVME.String()],
|
||||||
|
ChecksumType: cs[xhttp.AmzChecksumType],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/xml"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -117,6 +118,33 @@ func assertCopyChecksum(t *testing.T, obj ObjectLayer, bucket, object string, ty
|
|||||||
return oi
|
return oi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertCopyChecksumResponse(t *testing.T, rec *httptest.ResponseRecorder, typ hash.ChecksumType, data []byte) {
|
||||||
|
t.Helper()
|
||||||
|
var response CopyObjectResponse
|
||||||
|
if err := xml.Unmarshal(rec.Body.Bytes(), &response); err != nil {
|
||||||
|
t.Fatalf("unable to decode CopyObjectResult: %v", err)
|
||||||
|
}
|
||||||
|
var got string
|
||||||
|
switch typ.Base() {
|
||||||
|
case hash.ChecksumCRC32:
|
||||||
|
got = response.ChecksumCRC32
|
||||||
|
case hash.ChecksumCRC32C:
|
||||||
|
got = response.ChecksumCRC32C
|
||||||
|
case hash.ChecksumSHA1:
|
||||||
|
got = response.ChecksumSHA1
|
||||||
|
case hash.ChecksumSHA256:
|
||||||
|
got = response.ChecksumSHA256
|
||||||
|
case hash.ChecksumCRC64NVME:
|
||||||
|
got = response.ChecksumCRC64NVME
|
||||||
|
}
|
||||||
|
if want := mustChecksum(t, typ, data); got != want {
|
||||||
|
t.Fatalf("CopyObjectResult %s checksum %q, want %q: %s", typ.String(), got, want, rec.Body.String())
|
||||||
|
}
|
||||||
|
if response.ChecksumType != xhttp.AmzChecksumTypeFullObject {
|
||||||
|
t.Fatalf("CopyObjectResult checksum type %q, want %q", response.ChecksumType, xhttp.AmzChecksumTypeFullObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestAPICopyObjectServerSideChecksum verifies that server-computed checksums
|
// TestAPICopyObjectServerSideChecksum verifies that server-computed checksums
|
||||||
// cover the logical object, never the compressed storage stream.
|
// cover the logical object, never the compressed storage stream.
|
||||||
func TestAPICopyObjectServerSideChecksum(t *testing.T) {
|
func TestAPICopyObjectServerSideChecksum(t *testing.T) {
|
||||||
@@ -187,6 +215,7 @@ func testAPICopyObjectServerSideChecksum(obj ObjectLayer, instanceType, bucketNa
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, tc.typ, data)
|
||||||
|
|
||||||
info := assertCopyChecksum(t, obj, bucketName, destination, tc.typ, data, tc.compressed, nil)
|
info := assertCopyChecksum(t, obj, bucketName, destination, tc.typ, data, tc.compressed, nil)
|
||||||
md5sum := md5.Sum(data)
|
md5sum := md5.Sum(data)
|
||||||
@@ -280,6 +309,7 @@ func testAPICopyObjectServerSideChecksumEncryption(obj ObjectLayer, instanceType
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: SSE-S3 CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: SSE-S3 CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, hash.ChecksumCRC32, data)
|
||||||
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, variant.compressed, nil)
|
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, variant.compressed, nil)
|
||||||
if got := readCopyChecksumObject(t, obj, bucketName, destination, ObjectOptions{}); !bytes.Equal(got, data) {
|
if got := readCopyChecksumObject(t, obj, bucketName, destination, ObjectOptions{}); !bytes.Equal(got, data) {
|
||||||
t.Fatalf("%s: SSE-S3 round-trip body differs", instanceType)
|
t.Fatalf("%s: SSE-S3 round-trip body differs", instanceType)
|
||||||
@@ -296,6 +326,7 @@ func testAPICopyObjectServerSideChecksumEncryption(obj ObjectLayer, instanceType
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: SSE-S3 source CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: SSE-S3 source CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, hash.ChecksumCRC32, data)
|
||||||
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, true, nil)
|
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, true, nil)
|
||||||
if got := readCopyChecksumObject(t, obj, bucketName, destination, ObjectOptions{}); !bytes.Equal(got, data) {
|
if got := readCopyChecksumObject(t, obj, bucketName, destination, ObjectOptions{}); !bytes.Equal(got, data) {
|
||||||
t.Fatalf("%s: SSE-S3 source round-trip body differs", instanceType)
|
t.Fatalf("%s: SSE-S3 source round-trip body differs", instanceType)
|
||||||
@@ -340,6 +371,7 @@ func testAPICopyObjectServerSideChecksumEncryption(obj ObjectLayer, instanceType
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: SSE-C CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: SSE-C CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, hash.ChecksumCRC32, data)
|
||||||
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, variant.compressed, decryptHeaders)
|
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, variant.compressed, decryptHeaders)
|
||||||
|
|
||||||
req, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", bucketName, destination),
|
req, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", bucketName, destination),
|
||||||
@@ -390,6 +422,7 @@ func testAPICopyObjectServerSideChecksumSourceVariants(obj ObjectLayer, instance
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, hash.ChecksumCRC32, data)
|
||||||
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, true, nil)
|
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, true, nil)
|
||||||
|
|
||||||
rec = copyChecksumRequest(t, apiRouter, credentials, bucketName, source, source, map[string]string{
|
rec = copyChecksumRequest(t, apiRouter, credentials, bucketName, source, source, map[string]string{
|
||||||
@@ -399,6 +432,7 @@ func testAPICopyObjectServerSideChecksumSourceVariants(obj ObjectLayer, instance
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: in-place CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: in-place CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, hash.ChecksumSHA256, data)
|
||||||
assertCopyChecksum(t, obj, bucketName, source, hash.ChecksumSHA256, data, true, nil)
|
assertCopyChecksum(t, obj, bucketName, source, hash.ChecksumSHA256, data, true, nil)
|
||||||
if got := readCopyChecksumObject(t, obj, bucketName, source, ObjectOptions{}); !bytes.Equal(got, data) {
|
if got := readCopyChecksumObject(t, obj, bucketName, source, ObjectOptions{}); !bytes.Equal(got, data) {
|
||||||
t.Fatalf("%s: in-place CopyObject body differs", instanceType)
|
t.Fatalf("%s: in-place CopyObject body differs", instanceType)
|
||||||
@@ -415,6 +449,7 @@ func testAPICopyObjectServerSideChecksumSourceVariants(obj ObjectLayer, instance
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, hash.ChecksumCRC32, data)
|
||||||
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, true, nil)
|
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, true, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -447,6 +482,7 @@ func testAPICopyObjectServerSideChecksumSourceVariants(obj ObjectLayer, instance
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, typ, full)
|
||||||
assertCopyChecksum(t, obj, bucketName, destination, typ, full, true, nil)
|
assertCopyChecksum(t, obj, bucketName, destination, typ, full, true, nil)
|
||||||
if got := readCopyChecksumObject(t, obj, bucketName, destination, ObjectOptions{}); !bytes.Equal(got, full) {
|
if got := readCopyChecksumObject(t, obj, bucketName, destination, ObjectOptions{}); !bytes.Equal(got, full) {
|
||||||
t.Fatalf("%s: multipart source round-trip body differs", instanceType)
|
t.Fatalf("%s: multipart source round-trip body differs", instanceType)
|
||||||
@@ -475,6 +511,7 @@ func testAPICopyObjectServerSideChecksumSourceVariants(obj ObjectLayer, instance
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
t.Fatalf("%s: CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||||
}
|
}
|
||||||
|
assertCopyChecksumResponse(t, rec, hash.ChecksumCRC32, boundary.data)
|
||||||
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, boundary.data, boundary.compressed, nil)
|
assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, boundary.data, boundary.compressed, nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1810,7 +1810,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
origETag := objInfo.ETag
|
origETag := objInfo.ETag
|
||||||
objInfo.ETag = getDecryptedETag(r.Header, objInfo, false)
|
objInfo.ETag = getDecryptedETag(r.Header, objInfo, false)
|
||||||
response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime)
|
response := generateCopyObjectResponse(objInfo, r.Header)
|
||||||
encodedSuccessResponse := encodeResponse(response)
|
encodedSuccessResponse := encodeResponse(response)
|
||||||
|
|
||||||
if dsc := mustReplicate(ctx, dstBucket, dstObject, objInfo.getMustReplicateOptions(replication.ObjectReplicationType, dstOpts)); dsc.ReplicateAny() {
|
if dsc := mustReplicate(ctx, dstBucket, dstObject, objInfo.getMustReplicateOptions(replication.ObjectReplicationType, dstOpts)); dsc.ReplicateAny() {
|
||||||
|
|||||||
Reference in New Issue
Block a user