s3api: make conditional mutations atomic and AWS-compatible (#8802)
* s3api: serialize conditional write finalization * s3api: add conditional delete mutation checks * s3api: enforce destination conditions for copy * s3api: revalidate multipart completion under lock * s3api: rollback failed put finalization hooks * s3api: report delete-marker version deletions * s3api: fix copy destination versioning edge cases * s3api: make versioned multipart completion idempotent * test/s3: cover conditional mutation regressions * s3api: rollback failed copy version finalization * s3api: resolve suspended delete conditions via latest entry * s3api: remove copy test null-version injection * s3api: reject out-of-order multipart completions * s3api: preserve multipart replay version metadata * s3api: surface copy destination existence errors * s3api: simplify delete condition target resolution * test/s3: make conditional delete assertions order independent * test/s3: add distributed lock gateway integration * s3api: fail closed multipart versioned completion * s3api: harden copy metadata and overwrite paths * s3api: create delete markers for suspended deletes * s3api: allow duplicate multipart completion parts
This commit is contained in:
@@ -8,7 +8,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
type H map[string]string
|
||||
@@ -396,6 +398,58 @@ func TestProcessMetadataBytes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCopyMetadataPreservesInternalFields(t *testing.T) {
|
||||
existing := map[string][]byte{
|
||||
s3_constants.SeaweedFSSSEKMSKey: []byte("kms-secret"),
|
||||
s3_constants.SeaweedFSSSEIV: []byte("iv"),
|
||||
"X-Amz-Meta-Old": []byte("old"),
|
||||
"X-Amz-Tagging-Old": []byte("old-tag"),
|
||||
s3_constants.AmzStorageClass: []byte("STANDARD"),
|
||||
}
|
||||
updated := map[string][]byte{
|
||||
"X-Amz-Meta-New": []byte("new"),
|
||||
"X-Amz-Tagging-New": []byte("new-tag"),
|
||||
s3_constants.AmzStorageClass: []byte("GLACIER"),
|
||||
}
|
||||
|
||||
merged := mergeCopyMetadata(existing, updated)
|
||||
|
||||
if got := string(merged[s3_constants.SeaweedFSSSEKMSKey]); got != "kms-secret" {
|
||||
t.Fatalf("expected internal KMS key to be preserved, got %q", got)
|
||||
}
|
||||
if got := string(merged[s3_constants.SeaweedFSSSEIV]); got != "iv" {
|
||||
t.Fatalf("expected internal IV to be preserved, got %q", got)
|
||||
}
|
||||
if _, ok := merged["X-Amz-Meta-Old"]; ok {
|
||||
t.Fatalf("expected stale user metadata to be removed, got %#v", merged)
|
||||
}
|
||||
if _, ok := merged["X-Amz-Tagging-Old"]; ok {
|
||||
t.Fatalf("expected stale tagging metadata to be removed, got %#v", merged)
|
||||
}
|
||||
if got := string(merged["X-Amz-Meta-New"]); got != "new" {
|
||||
t.Fatalf("expected replacement user metadata to be applied, got %q", got)
|
||||
}
|
||||
if got := string(merged["X-Amz-Tagging-New"]); got != "new-tag" {
|
||||
t.Fatalf("expected replacement tagging metadata to be applied, got %q", got)
|
||||
}
|
||||
if got := string(merged[s3_constants.AmzStorageClass]); got != "GLACIER" {
|
||||
t.Fatalf("expected storage class to be updated, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyEntryETagPrefersStoredETag(t *testing.T) {
|
||||
entry := &filer_pb.Entry{
|
||||
Extended: map[string][]byte{
|
||||
s3_constants.ExtETagKey: []byte("\"stored-etag\""),
|
||||
},
|
||||
Attributes: &filer_pb.FuseAttributes{},
|
||||
}
|
||||
|
||||
if got := copyEntryETag(util.FullPath("/buckets/test-bucket/object.txt"), entry); got != "\"stored-etag\"" {
|
||||
t.Fatalf("copyEntryETag() = %q, want %q", got, "\"stored-etag\"")
|
||||
}
|
||||
}
|
||||
|
||||
func fmtTagging(maps ...map[string]string) {
|
||||
for _, m := range maps {
|
||||
if tagging := m[s3_constants.AmzObjectTagging]; len(tagging) > 0 {
|
||||
@@ -556,9 +610,8 @@ func TestCleanupVersioningMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCopyVersioningIntegration validates the interaction between
|
||||
// shouldCreateVersionForCopy and cleanupVersioningMetadata functions.
|
||||
// This integration test ensures the complete fix for issue #7505.
|
||||
// TestCopyVersioningIntegration validates the metadata shaping that happens
|
||||
// before copy finalization for each destination versioning mode.
|
||||
func TestCopyVersioningIntegration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -581,7 +634,7 @@ func TestCopyVersioningIntegration(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SuspendedCleansMetadata",
|
||||
name: "SuspendedCleansVersionMetadataBeforeFinalize",
|
||||
versioningState: s3_constants.VersioningSuspended,
|
||||
sourceMetadata: map[string][]byte{
|
||||
s3_constants.ExtVersionIdKey: []byte("v123"),
|
||||
|
||||
Reference in New Issue
Block a user