mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-18 03:58:32 +02:00
feat(s3/lifecycle): metadata-only delete when entry TtlSec > 0 (Phase 2b) (#9390)
* refactor(s3): thread metadataOnly into delete helpers Add a metadataOnly bool to deleteUnversionedObjectWithClient and deleteSpecificObjectVersion. When true the helper sends IsDeleteData= false to the filer's DeleteEntry RPC so per-chunk DeleteFile RPCs are skipped — the volume server reclaims chunks on its own at TTL drop. Non-lifecycle callers (DELETE handlers, batch delete) pass false to preserve today's eager-chunk-delete behavior; only the lifecycle handler in the next commit will pass true. * feat(s3/lifecycle): metadata-only delete when entry TtlSec > 0 Per-write TTL stamping (PR 9377) sets Attributes.TtlSec on every lifecycle-fitting entry. When the live entry the LifecycleDelete handler fetched carries TtlSec > 0 the volume server is guaranteed to reclaim chunks at TTL drop, so the filer can skip per-chunk DeleteFile RPCs and just remove the entry record. lifecycleDispatch now computes metadataOnly from the live entry and threads it through the unversioned, suspended-null, and noncurrent/expired-marker delete paths. createDeleteMarker is unaffected — it creates a marker, never deletes chunks.
This commit is contained in:
1 parent
255e9cd0f7
commit
bb0c7c779f
4 files changed
+93
-13
No files matched your search
@@ -0,0 +1,66 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDeleteUnversionedObjectWithClient_MetadataOnlySkipsChunkDelete(t *testing.T) {
|
||||
// metadataOnly=true must reach the filer as IsDeleteData=false so the
|
||||
// volume server reclaims chunks via TTL instead of the filer enqueueing
|
||||
// per-chunk DeleteFile RPCs.
|
||||
s3a := &S3ApiServer{option: &S3ApiServerOption{BucketsPath: "/buckets"}}
|
||||
client := &deleteObjectEntryTestClient{}
|
||||
|
||||
err := s3a.deleteUnversionedObjectWithClient(client, "b", "k", true)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client.deleteReq)
|
||||
assert.Equal(t, "/buckets/b", client.deleteReq.Directory)
|
||||
assert.Equal(t, "k", client.deleteReq.Name)
|
||||
assert.False(t, client.deleteReq.IsDeleteData, "metadataOnly must clear IsDeleteData")
|
||||
}
|
||||
|
||||
func TestDeleteUnversionedObjectWithClient_FullDeletePreservesIsDeleteData(t *testing.T) {
|
||||
// Default behavior (metadataOnly=false): filer should still enqueue
|
||||
// chunk deletions as before.
|
||||
s3a := &S3ApiServer{option: &S3ApiServerOption{BucketsPath: "/buckets"}}
|
||||
client := &deleteObjectEntryTestClient{}
|
||||
|
||||
err := s3a.deleteUnversionedObjectWithClient(client, "b", "k", false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client.deleteReq)
|
||||
assert.True(t, client.deleteReq.IsDeleteData, "default delete must keep IsDeleteData true")
|
||||
}
|
||||
|
||||
func TestDeleteUnversionedObjectWithClient_FullPathFromBucketsRoot(t *testing.T) {
|
||||
// Sanity: BucketsPath joins to <bucketsPath>/<bucket>/<object> in the
|
||||
// DeleteEntryRequest so the filer can locate the entry. Object keys
|
||||
// with multiple path segments should split into Directory + Name
|
||||
// correctly.
|
||||
s3a := &S3ApiServer{option: &S3ApiServerOption{BucketsPath: "/buckets"}}
|
||||
client := &deleteObjectEntryTestClient{}
|
||||
|
||||
err := s3a.deleteUnversionedObjectWithClient(client, "mybucket", "a/b/c.txt", false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client.deleteReq)
|
||||
assert.Equal(t, "/buckets/mybucket/a/b", client.deleteReq.Directory)
|
||||
assert.Equal(t, "c.txt", client.deleteReq.Name)
|
||||
}
|
||||
|
||||
func TestDeleteUnversionedObjectWithClient_PropagatesEntryAttributesIrrelevant(t *testing.T) {
|
||||
// The metadataOnly decision is the caller's responsibility (the
|
||||
// lifecycle handler). This function is dumb plumbing — it must not
|
||||
// inspect the entry itself, only translate the bool to IsDeleteData.
|
||||
s3a := &S3ApiServer{option: &S3ApiServerOption{BucketsPath: "/buckets"}}
|
||||
client := &deleteObjectEntryTestClient{
|
||||
// A response with no error is fine; attributes on a delete are unused.
|
||||
deleteResp: &filer_pb.DeleteEntryResponse{},
|
||||
}
|
||||
|
||||
require.NoError(t, s3a.deleteUnversionedObjectWithClient(client, "b", "k", true))
|
||||
require.NotNil(t, client.deleteReq)
|
||||
assert.False(t, client.deleteReq.IsDeleteData)
|
||||
}
|
||||
Reference in new issue
Block a user