fix(copy): preserve compression index on pooled copy rewrites

Forward dstOpts.IndexCB into erasureServerPools.CopyObject's PutObject options so multi-block S2 compressed copies (> compMinIndexSize) rewritten across pools persist Parts[0].Index for O(1) block-level ranged GET seeking.

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-11-11 15:49:35 +08:00
parent ef6efd3120
commit cd3e46a2b7
2 changed files with 23 additions and 1 deletions
+1
View File
@@ -1831,6 +1831,7 @@ func (z *erasureServerPools) CopyObject(ctx context.Context, srcBucket, srcObjec
MTime: dstOpts.MTime, MTime: dstOpts.MTime,
NoLock: true, NoLock: true,
EncryptFn: dstOpts.EncryptFn, EncryptFn: dstOpts.EncryptFn,
IndexCB: dstOpts.IndexCB,
WantChecksum: dstOpts.WantChecksum, WantChecksum: dstOpts.WantChecksum,
WantServerSideChecksumType: dstOpts.WantServerSideChecksumType, WantServerSideChecksumType: dstOpts.WantServerSideChecksumType,
ReplicationRequest: dstOpts.ReplicationRequest, ReplicationRequest: dstOpts.ReplicationRequest,
+22 -1
View File
@@ -84,7 +84,12 @@ func TestAPICopyHistoricalVersionAcrossPools(t *testing.T) {
if mode == "sse-c" { if mode == "sse-c" {
maps.Copy(sourceHeaders, customer(0x11)) maps.Copy(sourceHeaders, customer(0x11))
} }
data := bytes.Repeat([]byte("historical encrypted compressed content-"), 8192) pattern := []byte("historical encrypted compressed content-")
repeats := 8192
if mode == "plain" {
repeats = compMinIndexSize/len(pattern) + 1
}
data := bytes.Repeat(pattern, repeats)
sourceHeaders[xhttp.AmzChecksumCRC32] = mustChecksum(t, hash.ChecksumCRC32, data) sourceHeaders[xhttp.AmzChecksumCRC32] = mustChecksum(t, hash.ChecksumCRC32, data)
key := fmt.Sprintf("copy-%s-%s-%d.txt", mode, tc.movement, sourcePool) key := fmt.Sprintf("copy-%s-%s-%d.txt", mode, tc.movement, sourcePool)
placed.uploadPool = sourcePool placed.uploadPool = sourcePool
@@ -145,6 +150,22 @@ func TestAPICopyHistoricalVersionAcrossPools(t *testing.T) {
if copied.IsCompressed() != (!compressed && mode != "sse-c") { if copied.IsCompressed() != (!compressed && mode != "sse-c") {
t.Fatalf("copy did not use destination compression setting: %v", copied.IsCompressed()) t.Fatalf("copy did not use destination compression setting: %v", copied.IsCompressed())
} }
if copied.IsCompressed() && len(data) > compMinIndexSize {
if len(copied.Parts) == 0 || len(copied.Parts[0].Index) == 0 {
t.Fatalf("compressed multi-block copy lost seek index: %+v", copied.Parts)
}
rangeHeaders := maps.Clone(getHeaders)
rangeHeaders["Range"] = "bytes=6000000-6000127"
req, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", bucket, key)+"?versionId="+copied.VersionID, 0, nil, globalActiveCred.AccessKey, globalActiveCred.SecretKey, rangeHeaders)
if err != nil {
t.Fatal(err)
}
rrec := httptest.NewRecorder()
router.ServeHTTP(rrec, req)
if rrec.Code != http.StatusPartialContent || !bytes.Equal(rrec.Body.Bytes(), data[6000000:6000128]) {
t.Fatalf("ranged GET on compressed copy: %d len=%d", rrec.Code, rrec.Body.Len())
}
}
if mode == "sse-kms" { if mode == "sse-kms" {
assertFederationKMSContext(t, copied, headers[xhttp.AmzServerSideEncryptionKmsContext]) assertFederationKMSContext(t, copied, headers[xhttp.AmzServerSideEncryptionKmsContext])
assertFederationKMSContext(t, original, sourceHeaders[xhttp.AmzServerSideEncryptionKmsContext]) assertFederationKMSContext(t, original, sourceHeaders[xhttp.AmzServerSideEncryptionKmsContext])