Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
298 lines
9.7 KiB
Go
298 lines
9.7 KiB
Go
package s3api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestSuspendedVersioningNullOverwrite tests the scenario where:
|
|
// 1. Create object before versioning is enabled (pre-versioning object)
|
|
// 2. Enable versioning, then suspend it
|
|
// 3. Overwrite the object (should replace the null version, not create duplicate)
|
|
// 4. List versions should show only 1 version with versionId "null"
|
|
//
|
|
// This test corresponds to: test_versioning_obj_plain_null_version_overwrite_suspended
|
|
func TestSuspendedVersioningNullOverwrite(t *testing.T) {
|
|
ctx := context.Background()
|
|
client := getS3Client(t)
|
|
|
|
// Create bucket
|
|
bucketName := getNewBucketName()
|
|
createBucket(t, client, bucketName)
|
|
defer deleteBucket(t, client, bucketName)
|
|
|
|
objectKey := "testobjbar"
|
|
|
|
// Step 1: Put object before versioning is configured (pre-versioning object)
|
|
content1 := []byte("foooz")
|
|
_, err := client.PutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(objectKey),
|
|
Body: bytes.NewReader(content1),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create pre-versioning object: %v", err)
|
|
}
|
|
t.Logf("Created pre-versioning object")
|
|
|
|
// Step 2: Enable versioning
|
|
_, err = client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{
|
|
Bucket: aws.String(bucketName),
|
|
VersioningConfiguration: &types.VersioningConfiguration{
|
|
Status: types.BucketVersioningStatusEnabled,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to enable versioning: %v", err)
|
|
}
|
|
t.Logf("Enabled versioning")
|
|
|
|
// Step 3: Suspend versioning
|
|
_, err = client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{
|
|
Bucket: aws.String(bucketName),
|
|
VersioningConfiguration: &types.VersioningConfiguration{
|
|
Status: types.BucketVersioningStatusSuspended,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to suspend versioning: %v", err)
|
|
}
|
|
t.Logf("Suspended versioning")
|
|
|
|
// Step 4: Overwrite the object during suspended versioning
|
|
content2 := []byte("zzz")
|
|
putResp, err := client.PutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(objectKey),
|
|
Body: bytes.NewReader(content2),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to overwrite object during suspended versioning: %v", err)
|
|
}
|
|
|
|
// Verify no VersionId is returned for suspended versioning
|
|
if putResp.VersionId != nil {
|
|
t.Errorf("Suspended versioning should NOT return VersionId, but got: %s", *putResp.VersionId)
|
|
}
|
|
t.Logf("Overwrote object during suspended versioning (no VersionId returned as expected)")
|
|
|
|
// Step 5: Verify content is updated
|
|
getResp, err := client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(objectKey),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to get object: %v", err)
|
|
}
|
|
defer getResp.Body.Close()
|
|
|
|
gotContent := new(bytes.Buffer)
|
|
gotContent.ReadFrom(getResp.Body)
|
|
if !bytes.Equal(gotContent.Bytes(), content2) {
|
|
t.Errorf("Expected content %q, got %q", content2, gotContent.Bytes())
|
|
}
|
|
t.Logf("Object content is correctly updated to: %q", content2)
|
|
|
|
// Step 6: List object versions - should have only 1 version
|
|
listResp, err := client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: aws.String(bucketName),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list object versions: %v", err)
|
|
}
|
|
|
|
// Count versions (excluding delete markers)
|
|
versionCount := len(listResp.Versions)
|
|
deleteMarkerCount := len(listResp.DeleteMarkers)
|
|
|
|
t.Logf("List results: %d versions, %d delete markers", versionCount, deleteMarkerCount)
|
|
for i, v := range listResp.Versions {
|
|
t.Logf(" Version %d: Key=%s, VersionId=%s, IsLatest=%v, Size=%d",
|
|
i, *v.Key, *v.VersionId, v.IsLatest, v.Size)
|
|
}
|
|
|
|
// THIS IS THE KEY ASSERTION: Should have exactly 1 version, not 2
|
|
if versionCount != 1 {
|
|
t.Errorf("Expected 1 version after suspended versioning overwrite, got %d versions", versionCount)
|
|
t.Error("BUG: Duplicate null versions detected! The overwrite should have replaced the pre-versioning object.")
|
|
} else {
|
|
t.Logf("PASS: Only 1 version found (no duplicate null versions)")
|
|
}
|
|
|
|
if deleteMarkerCount != 0 {
|
|
t.Errorf("Expected 0 delete markers, got %d", deleteMarkerCount)
|
|
}
|
|
|
|
// Verify the version has versionId "null"
|
|
if versionCount > 0 {
|
|
if listResp.Versions[0].VersionId == nil || *listResp.Versions[0].VersionId != "null" {
|
|
t.Errorf("Expected VersionId to be 'null', got %v", listResp.Versions[0].VersionId)
|
|
} else {
|
|
t.Logf("Version ID is 'null' as expected")
|
|
}
|
|
}
|
|
|
|
// Step 7: Delete the null version
|
|
_, err = client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(objectKey),
|
|
VersionId: aws.String("null"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to delete null version: %v", err)
|
|
}
|
|
t.Logf("Deleted null version")
|
|
|
|
// Step 8: Verify object no longer exists
|
|
_, err = client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(objectKey),
|
|
})
|
|
if err == nil {
|
|
t.Error("Expected object to not exist after deleting null version")
|
|
}
|
|
t.Logf("Object no longer exists after deleting null version")
|
|
|
|
// Step 9: Verify no versions remain
|
|
listResp, err = client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: aws.String(bucketName),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list object versions: %v", err)
|
|
}
|
|
|
|
if len(listResp.Versions) != 0 || len(listResp.DeleteMarkers) != 0 {
|
|
t.Errorf("Expected no versions or delete markers, got %d versions and %d delete markers",
|
|
len(listResp.Versions), len(listResp.DeleteMarkers))
|
|
} else {
|
|
t.Logf("No versions remain after deletion")
|
|
}
|
|
}
|
|
|
|
// TestEnabledVersioningReturnsVersionId tests that when versioning is ENABLED,
|
|
// every PutObject operation returns a version ID
|
|
//
|
|
// This test corresponds to the create_multiple_versions helper function
|
|
func TestEnabledVersioningReturnsVersionId(t *testing.T) {
|
|
ctx := context.Background()
|
|
client := getS3Client(t)
|
|
|
|
// Create bucket
|
|
bucketName := getNewBucketName()
|
|
createBucket(t, client, bucketName)
|
|
defer deleteBucket(t, client, bucketName)
|
|
|
|
objectKey := "testobj"
|
|
|
|
// Enable versioning
|
|
_, err := client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{
|
|
Bucket: aws.String(bucketName),
|
|
VersioningConfiguration: &types.VersioningConfiguration{
|
|
Status: types.BucketVersioningStatusEnabled,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to enable versioning: %v", err)
|
|
}
|
|
t.Logf("Enabled versioning")
|
|
|
|
// Create multiple versions
|
|
numVersions := 3
|
|
versionIds := make([]string, 0, numVersions)
|
|
|
|
for i := 0; i < numVersions; i++ {
|
|
content := []byte("content-" + string(rune('0'+i)))
|
|
putResp, err := client.PutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(objectKey),
|
|
Body: bytes.NewReader(content),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create version %d: %v", i, err)
|
|
}
|
|
|
|
// THIS IS THE KEY ASSERTION: VersionId MUST be returned for enabled versioning
|
|
if putResp.VersionId == nil {
|
|
t.Errorf("FAILED: PutObject with enabled versioning MUST return VersionId, but got nil for version %d", i)
|
|
} else {
|
|
versionId := *putResp.VersionId
|
|
if versionId == "" {
|
|
t.Errorf("FAILED: PutObject returned empty VersionId for version %d", i)
|
|
} else if versionId == "null" {
|
|
t.Errorf("FAILED: PutObject with enabled versioning should NOT return 'null' version ID, got: %s", versionId)
|
|
} else {
|
|
versionIds = append(versionIds, versionId)
|
|
t.Logf("Version %d created with VersionId: %s", i, versionId)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(versionIds) != numVersions {
|
|
t.Errorf("Expected %d version IDs, got %d", numVersions, len(versionIds))
|
|
}
|
|
|
|
// List versions to verify all were created
|
|
listResp, err := client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: aws.String(bucketName),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list object versions: %v", err)
|
|
}
|
|
|
|
if len(listResp.Versions) != numVersions {
|
|
t.Errorf("Expected %d versions in list, got %d", numVersions, len(listResp.Versions))
|
|
} else {
|
|
t.Logf("All %d versions are listed", numVersions)
|
|
}
|
|
|
|
// Verify all version IDs match
|
|
for i, v := range listResp.Versions {
|
|
t.Logf(" Version %d: VersionId=%s, Size=%d, IsLatest=%v", i, *v.VersionId, v.Size, v.IsLatest)
|
|
}
|
|
}
|
|
|
|
// The suspended PUT retires the null delete marker a preceding DELETE left, the same
|
|
// as the copy and multipart paths. While the regular-path object owns the null slot a
|
|
// leftover marker is shadowed, so it only shows once that null version goes away.
|
|
func TestSuspendedPutRetiresDeleteMarker(t *testing.T) {
|
|
client := getS3Client(t)
|
|
bucketName := getNewBucketName()
|
|
|
|
createBucket(t, client, bucketName)
|
|
defer deleteBucket(t, client, bucketName)
|
|
|
|
objectKey := "suspended-put-after-delete.txt"
|
|
|
|
enableVersioning(t, client, bucketName)
|
|
putObject(t, client, bucketName, objectKey, "pre-suspension-content")
|
|
suspendVersioning(t, client, bucketName)
|
|
|
|
putObject(t, client, bucketName, objectKey, "null-version-content")
|
|
deleteKey(t, client, bucketName, objectKey)
|
|
putObject(t, client, bucketName, objectKey, "replacement-content")
|
|
|
|
headObject(t, client, bucketName, objectKey)
|
|
|
|
// Drop the null version just written; a retired marker leaves nothing behind.
|
|
_, err := client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(objectKey),
|
|
VersionId: aws.String("null"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
listResp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
|
|
Bucket: aws.String(bucketName),
|
|
Prefix: aws.String(objectKey),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Empty(t, listResp.DeleteMarkers, "the suspended PUT should have retired the null delete marker")
|
|
}
|