From cee94aae33062ea0f7b790a5236fcb1b5a492eb9 Mon Sep 17 00:00:00 2001 From: Aleksey Savchuk Date: Thu, 19 Dec 2024 13:47:31 +0300 Subject: [PATCH] [#1445] engine/test: Add test for GC handling of expired tombstones Since the GC behavior is changing drastically. This test is needed to ensure that the GC correctly deletes expired tombstones and graves. The test uses graves of both old and new formats. Signed-off-by: Aleksey Savchuk --- pkg/local_object_storage/engine/gc_test.go | 111 +++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 pkg/local_object_storage/engine/gc_test.go diff --git a/pkg/local_object_storage/engine/gc_test.go b/pkg/local_object_storage/engine/gc_test.go new file mode 100644 index 000000000..ad6041394 --- /dev/null +++ b/pkg/local_object_storage/engine/gc_test.go @@ -0,0 +1,111 @@ +package engine + +import ( + "context" + "strconv" + "testing" + "time" + + objectCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/internal/testutil" + meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util" + objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" + objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + "github.com/panjf2000/ants/v2" + "github.com/stretchr/testify/require" +) + +func testPopulateWithObjects(t testing.TB, engine *StorageEngine, cnt cid.ID, objectCount int) (objects []*objectSDK.Object) { + require.Positive(t, objectCount) + + var putPrm PutPrm + for range objectCount { + putPrm.Object = testutil.GenerateObjectWithCID(cnt) + require.NoError(t, engine.Put(context.Background(), putPrm)) + objects = append(objects, putPrm.Object) + } + return objects +} + +func testInhumeObjects(t testing.TB, engine *StorageEngine, objects []*objectSDK.Object, expEpoch uint64) (tombstone *objectSDK.Object) { + require.NotEmpty(t, objects) + cnt := objectCore.AddressOf(objects[0]).Container() + + tombstone = testutil.GenerateObjectWithCID(cnt) + tombstone.SetType(objectSDK.TypeTombstone) + testutil.AddAttribute(tombstone, objectV2.SysAttributeExpEpoch, strconv.FormatUint(expEpoch, 10)) + + var putPrm PutPrm + putPrm.Object = tombstone + require.NoError(t, engine.Put(context.Background(), putPrm)) + + var addrs []oid.Address + for _, object := range objects { + addrs = append(addrs, objectCore.AddressOf(object)) + } + tombstoneAddr := objectCore.AddressOf(tombstone) + + pivot := len(objects) / 2 + + var inhumePrm InhumePrm + inhumePrm.WithTarget(tombstoneAddr, expEpoch, addrs[:pivot]...) + err := engine.Inhume(context.Background(), inhumePrm) + require.NoError(t, err) + + inhumePrm.WithTarget(tombstoneAddr, meta.NoExpirationEpoch, addrs[pivot:]...) + err = engine.Inhume(context.Background(), inhumePrm) + require.NoError(t, err) + + return +} + +func TestGCHandleExpiredTombstones(t *testing.T) { + t.Parallel() + + const ( + numShards = 2 + objectCount = 10 * numShards + expEpoch = 1 + ) + + container := cidtest.ID() + + engine := testNewEngine(t). + setShardsNumAdditionalOpts(t, numShards, func(_ int) []shard.Option { + return []shard.Option{ + shard.WithGCWorkerPoolInitializer(func(sz int) util.WorkerPool { + pool, err := ants.NewPool(sz) + require.NoError(t, err) + return pool + }), + shard.WithTombstoneSource(tss{expEpoch}), + } + }).prepare(t).engine + defer func() { require.NoError(t, engine.Close(context.Background())) }() + + objects := testPopulateWithObjects(t, engine, container, objectCount) + tombstone := testInhumeObjects(t, engine, objects, expEpoch) + + engine.HandleNewEpoch(context.Background(), expEpoch+1) + + var headPrm HeadPrm + require.Eventually(t, func() bool { + for _, object := range objects { + headPrm.WithAddress(objectCore.AddressOf(object)) + _, err := engine.Head(context.Background(), headPrm) + if !client.IsErrObjectNotFound(err) { + return false + } + } + + headPrm.WithAddress(objectCore.AddressOf(tombstone)) + _, err := engine.Head(context.Background(), headPrm) + return client.IsErrObjectNotFound(err) + }, 10*time.Second, 1*time.Second) +}