mirror of
https://github.com/pgsty/minio.git
synced 2026-10-07 10:15:58 +03:00
fix(federation): bind copy timestamps to committed writes
Return the committed object or part time on federation write responses and capture it for CopyObject and UploadPartCopy without a follow-up read. Keep successful writes compatible with targets that do not supply a time. Reject authenticated raw SSE-C replica CopyObject across deployments before forwarding. Extend the existing SSE fixtures to verify empty objects, stored checksums, KMS contexts and multipart sources. Refs: #169, #168, #171 Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -30,6 +30,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
miniocredentials "github.com/minio/minio-go/v7/pkg/credentials"
|
||||
@@ -51,7 +52,7 @@ func TestAPIFederatedUploadPartChecksumResponse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIFederatedUploadPartChecksumResponse(_ ObjectLayer, instanceType, bucketName string,
|
||||
func testAPIFederatedUploadPartChecksumResponse(objectAPI ObjectLayer, instanceType, bucketName string,
|
||||
apiRouter http.Handler, credentials auth.Credentials, t *testing.T,
|
||||
) {
|
||||
algorithms := []struct {
|
||||
@@ -101,6 +102,18 @@ func testAPIFederatedUploadPartChecksumResponse(_ ObjectLayer, instanceType, buc
|
||||
if got := rec.Header().Get(xhttp.AmzChecksumType); got != "" {
|
||||
t.Fatalf("%s: UploadPart returned checksum type %q", instanceType, got)
|
||||
}
|
||||
modified := rec.Header().Get(federatedLastModified)
|
||||
if userAgent.want {
|
||||
parts, err := objectAPI.ListObjectParts(t.Context(), bucketName, object, uploadID, 0, 100, ObjectOptions{})
|
||||
if err != nil || len(parts.Parts) != 1 {
|
||||
t.Fatalf("stored part: %v, %v", parts, err)
|
||||
}
|
||||
if modified != parts.Parts[0].LastModified.UTC().Format(time.RFC3339Nano) {
|
||||
t.Errorf("response time %q does not match committed part time %s", modified, parts.Parts[0].LastModified)
|
||||
}
|
||||
} else if modified != "" {
|
||||
t.Errorf("ordinary UploadPart exposed federation timestamp %q", modified)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -158,7 +171,7 @@ func TestAPIFederatedUploadPartChecksumConcurrentOverwrite(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIFederatedUploadPartChecksumConcurrentOverwrite(_ ObjectLayer, instanceType, bucketName string,
|
||||
func testAPIFederatedUploadPartChecksumConcurrentOverwrite(objectAPI ObjectLayer, instanceType, bucketName string,
|
||||
apiRouter http.Handler, credentials auth.Credentials, t *testing.T,
|
||||
) {
|
||||
object := "federation/concurrent-overwrite"
|
||||
@@ -194,6 +207,10 @@ func testAPIFederatedUploadPartChecksumConcurrentOverwrite(_ ObjectLayer, instan
|
||||
}
|
||||
close(start)
|
||||
wg.Wait()
|
||||
parts, err := objectAPI.ListObjectParts(t.Context(), bucketName, object, uploadID, 0, 100, ObjectOptions{})
|
||||
if err != nil || len(parts.Parts) != 1 {
|
||||
t.Fatalf("stored part: %v, %v", parts, err)
|
||||
}
|
||||
|
||||
for i, rec := range recorders {
|
||||
if rec.Code != http.StatusOK {
|
||||
@@ -213,6 +230,13 @@ func testAPIFederatedUploadPartChecksumConcurrentOverwrite(_ ObjectLayer, instan
|
||||
if want := hex.EncodeToString(md5sum[:]); canonicalizeETag(etags[0]) != want {
|
||||
t.Fatalf("%s: concurrent request %d ETag %q, want %q", instanceType, i, etags[0], want)
|
||||
}
|
||||
modified, err := time.Parse(time.RFC3339Nano, rec.Header().Get(federatedLastModified))
|
||||
if err != nil || modified.IsZero() {
|
||||
t.Fatalf("concurrent request %d returned invalid time %v: %v", i, modified, err)
|
||||
}
|
||||
if canonicalizeETag(etags[0]) == parts.Parts[0].ETag && !modified.Equal(parts.Parts[0].LastModified) {
|
||||
t.Errorf("surviving part has time %s, its writer returned %s", parts.Parts[0].LastModified, modified)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +376,9 @@ func testAPIFederatedCopyObjectPartChecksum(objectAPI ObjectLayer, instanceType,
|
||||
if len(parts.Parts) != 1 {
|
||||
t.Fatalf("%s: ListParts returned %d parts, want 1", instanceType, len(parts.Parts))
|
||||
}
|
||||
if response.LastModified != parts.Parts[0].LastModified {
|
||||
t.Errorf("CopyPartResult time = %q, want stored part time %q", response.LastModified, parts.Parts[0].LastModified)
|
||||
}
|
||||
if got := partChecksum(algorithm.typ, parts.Parts[0]); got != want {
|
||||
t.Fatalf("%s: persisted part %s is %q, want %q", instanceType, algorithm.typ.String(), got, want)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user