aboutsummaryrefslogtreecommitdiff
path: root/test/s3/versioning/s3_versioning_object_lock_test.go
diff options
context:
space:
mode:
authorChris Lu <chrislusf@users.noreply.github.com>2025-07-16 23:00:25 -0700
committerGitHub <noreply@github.com>2025-07-16 23:00:25 -0700
commita524b4f485ce5aa2f234c742bd7d1e75386f569b (patch)
tree794b343485e4adace63dc091703d2368f9075616 /test/s3/versioning/s3_versioning_object_lock_test.go
parent89706d36dccc5d851ef6b818f0dd32249e6560a3 (diff)
downloadseaweedfs-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.go160
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)
+}