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) +}