diff options
| author | Chris Lu <chrislusf@users.noreply.github.com> | 2025-07-16 23:00:25 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-16 23:00:25 -0700 |
| commit | a524b4f485ce5aa2f234c742bd7d1e75386f569b (patch) | |
| tree | 794b343485e4adace63dc091703d2368f9075616 /test/s3/versioning/s3_versioning_object_lock_test.go | |
| parent | 89706d36dccc5d851ef6b818f0dd32249e6560a3 (diff) | |
| download | seaweedfs-a524b4f485ce5aa2f234c742bd7d1e75386f569b.tar.xz seaweedfs-a524b4f485ce5aa2f234c742bd7d1e75386f569b.zip | |
Object locking need to persist the tags and set the headers (#6994)
* fix object locking read and write
No logic to include object lock metadata in HEAD/GET response headers
No logic to extract object lock metadata from PUT request headers
* add tests for object locking
* Update weed/s3api/s3api_object_handlers_put.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update weed/s3api/s3api_object_handlers.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* refactor
* add unit tests
* sync versions
* Update s3_worm_integration_test.go
* fix legal hold values
* lint
* fix tests
* racing condition when enable versioning
* fix tests
* validate put object lock header
* allow check lock permissions for PUT
* default to OFF legal hold
* only set object lock headers for objects that are actually from object lock-enabled buckets
fix --- FAIL: TestAddObjectLockHeadersToResponse/Handle_entry_with_no_object_lock_metadata (0.00s)
* address comments
* fix tests
* purge
* fix
* refactoring
* address comment
* address comment
* Update weed/s3api/s3api_object_handlers_put.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update weed/s3api/s3api_object_handlers_put.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update weed/s3api/s3api_object_handlers.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* avoid nil
* ensure locked objects cannot be overwritten
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Diffstat (limited to 'test/s3/versioning/s3_versioning_object_lock_test.go')
| -rw-r--r-- | test/s3/versioning/s3_versioning_object_lock_test.go | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/test/s3/versioning/s3_versioning_object_lock_test.go b/test/s3/versioning/s3_versioning_object_lock_test.go new file mode 100644 index 000000000..5c2689935 --- /dev/null +++ b/test/s3/versioning/s3_versioning_object_lock_test.go @@ -0,0 +1,160 @@ +package s3api + +import ( + "context" + "strings" + "testing" + "time" + + "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" +) + +// TestVersioningWithObjectLockHeaders ensures that versioned objects properly +// handle object lock headers in PUT requests and return them in HEAD/GET responses. +// This test would have caught the bug where object lock metadata was not returned +// in HEAD/GET responses. +func TestVersioningWithObjectLockHeaders(t *testing.T) { + client := getS3Client(t) + bucketName := getNewBucketName() + + // Create bucket with object lock and versioning enabled + createBucketWithObjectLock(t, client, bucketName) + defer deleteBucket(t, client, bucketName) + + key := "versioned-object-with-lock" + content1 := "version 1 content" + content2 := "version 2 content" + + // PUT first version with object lock headers + retainUntilDate1 := time.Now().Add(12 * time.Hour) + putResp1, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + Body: strings.NewReader(content1), + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: aws.Time(retainUntilDate1), + }) + require.NoError(t, err) + require.NotNil(t, putResp1.VersionId) + + // PUT second version with different object lock settings + retainUntilDate2 := time.Now().Add(24 * time.Hour) + putResp2, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + Body: strings.NewReader(content2), + ObjectLockMode: types.ObjectLockModeCompliance, + ObjectLockRetainUntilDate: aws.Time(retainUntilDate2), + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }) + require.NoError(t, err) + require.NotNil(t, putResp2.VersionId) + require.NotEqual(t, *putResp1.VersionId, *putResp2.VersionId) + + // Test HEAD latest version returns correct object lock metadata + t.Run("HEAD latest version", func(t *testing.T) { + headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + }) + require.NoError(t, err) + + // Should return metadata for version 2 (latest) + assert.Equal(t, types.ObjectLockModeCompliance, headResp.ObjectLockMode) + assert.NotNil(t, headResp.ObjectLockRetainUntilDate) + assert.WithinDuration(t, retainUntilDate2, *headResp.ObjectLockRetainUntilDate, 5*time.Second) + assert.Equal(t, types.ObjectLockLegalHoldStatusOn, headResp.ObjectLockLegalHoldStatus) + }) + + // Test HEAD specific version returns correct object lock metadata + t.Run("HEAD specific version", func(t *testing.T) { + headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + VersionId: putResp1.VersionId, + }) + require.NoError(t, err) + + // Should return metadata for version 1 + assert.Equal(t, types.ObjectLockModeGovernance, headResp.ObjectLockMode) + assert.NotNil(t, headResp.ObjectLockRetainUntilDate) + assert.WithinDuration(t, retainUntilDate1, *headResp.ObjectLockRetainUntilDate, 5*time.Second) + // Version 1 was created without legal hold, so AWS S3 defaults it to "OFF" + assert.Equal(t, types.ObjectLockLegalHoldStatusOff, headResp.ObjectLockLegalHoldStatus) + }) + + // Test GET latest version returns correct object lock metadata + t.Run("GET latest version", func(t *testing.T) { + getResp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + }) + require.NoError(t, err) + defer getResp.Body.Close() + + // Should return metadata for version 2 (latest) + assert.Equal(t, types.ObjectLockModeCompliance, getResp.ObjectLockMode) + assert.NotNil(t, getResp.ObjectLockRetainUntilDate) + assert.WithinDuration(t, retainUntilDate2, *getResp.ObjectLockRetainUntilDate, 5*time.Second) + assert.Equal(t, types.ObjectLockLegalHoldStatusOn, getResp.ObjectLockLegalHoldStatus) + }) + + // Test GET specific version returns correct object lock metadata + t.Run("GET specific version", func(t *testing.T) { + getResp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + VersionId: putResp1.VersionId, + }) + require.NoError(t, err) + defer getResp.Body.Close() + + // Should return metadata for version 1 + assert.Equal(t, types.ObjectLockModeGovernance, getResp.ObjectLockMode) + assert.NotNil(t, getResp.ObjectLockRetainUntilDate) + assert.WithinDuration(t, retainUntilDate1, *getResp.ObjectLockRetainUntilDate, 5*time.Second) + // Version 1 was created without legal hold, so AWS S3 defaults it to "OFF" + assert.Equal(t, types.ObjectLockLegalHoldStatusOff, getResp.ObjectLockLegalHoldStatus) + }) +} + +// waitForVersioningToBeEnabled polls the bucket versioning status until it's enabled +// This helps avoid race conditions where object lock is configured but versioning +// isn't immediately available +func waitForVersioningToBeEnabled(t *testing.T, client *s3.Client, bucketName string) { + timeout := time.Now().Add(10 * time.Second) + for time.Now().Before(timeout) { + resp, err := client.GetBucketVersioning(context.TODO(), &s3.GetBucketVersioningInput{ + Bucket: aws.String(bucketName), + }) + if err == nil && resp.Status == types.BucketVersioningStatusEnabled { + return // Versioning is enabled + } + + time.Sleep(100 * time.Millisecond) + } + t.Fatalf("Timeout waiting for versioning to be enabled on bucket %s", bucketName) +} + +// Helper function for creating buckets with object lock enabled +func createBucketWithObjectLock(t *testing.T, client *s3.Client, bucketName string) { + _, err := client.CreateBucket(context.TODO(), &s3.CreateBucketInput{ + Bucket: aws.String(bucketName), + ObjectLockEnabledForBucket: aws.Bool(true), + }) + require.NoError(t, err) + + // Wait for versioning to be automatically enabled by object lock + waitForVersioningToBeEnabled(t, client, bucketName) + + // Verify that object lock was actually enabled + t.Logf("Verifying object lock configuration for bucket %s", bucketName) + _, err = client.GetObjectLockConfiguration(context.TODO(), &s3.GetObjectLockConfigurationInput{ + Bucket: aws.String(bucketName), + }) + require.NoError(t, err, "Object lock should be configured for bucket %s", bucketName) +} |
