From e2d26cd213f81e851cbe3d083deec863b1459d3f 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 tests for GC Signed-off-by: Aleksey Savchuk --- pkg/local_object_storage/engine/gc_test.go | 181 +++++++++++++++++++++ 1 file changed, 181 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..50cc04c51 --- /dev/null +++ b/pkg/local_object_storage/engine/gc_test.go @@ -0,0 +1,181 @@ +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 testLockObjects(t testing.TB, engine *StorageEngine, objects []*objectSDK.Object, expEpoch uint64) (lockObject *objectSDK.Object) { + require.NotEmpty(t, objects) + + require.NotEmpty(t, objects) + cnt := objectCore.AddressOf(objects[0]).Container() + + lockObject = testutil.GenerateObjectWithCID(cnt) + lockObject.SetType(objectSDK.TypeLock) + testutil.AddAttribute(lockObject, objectV2.SysAttributeExpEpoch, strconv.FormatUint(expEpoch, 10)) + + var putPrm PutPrm + putPrm.Object = lockObject + require.NoError(t, engine.Put(context.Background(), putPrm)) + + var lockedIDs []oid.ID + for _, object := range objects { + lockedIDs = append(lockedIDs, objectCore.AddressOf(object).Object()) + } + lockObjectID := objectCore.AddressOf(lockObject).Object() + + pivot := len(objects) / 2 + require.NoError(t, engine.Lock(context.Background(), cnt, lockObjectID, lockedIDs[:pivot], expEpoch)) + require.NoError(t, engine.Lock(context.Background(), cnt, lockObjectID, lockedIDs[pivot:], meta.NoExpirationEpoch)) + + 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) +} + +func TestGCHandleExpiredLockObjects(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 + }), + } + }).prepare(t).engine + defer func() { require.NoError(t, engine.Close(context.Background())) }() + + objects := testPopulateWithObjects(t, engine, container, objectCount) + lockObject := testLockObjects(t, engine, objects, expEpoch) + + engine.HandleNewEpoch(context.Background(), expEpoch+1) + + var headPrm HeadPrm + require.Eventually(t, func() bool { + for _, object := range objects { + locked, err := engine.IsLocked(context.Background(), objectCore.AddressOf(object)) + if err != nil || locked { + return false + } + } + + headPrm.WithAddress(objectCore.AddressOf(lockObject)) + _, err := engine.Head(context.Background(), headPrm) + return client.IsErrObjectNotFound(err) + }, 10*time.Second, 1*time.Second) +}