package meta_test import ( "context" "math/rand" "strconv" "testing" object2 "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" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object" 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" oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" ) func TestDB_IterateExpired(t *testing.T) { db := newDB(t) defer func() { require.NoError(t, db.Close(context.Background())) }() const epoch = 13 const lockExpiresAfter = 1000 mAlive := map[objectSDK.Type]oid.Address{} mExpired := map[objectSDK.Type]oid.Address{} for _, typ := range []objectSDK.Type{ objectSDK.TypeRegular, objectSDK.TypeTombstone, objectSDK.TypeLock, } { mAlive[typ] = putWithExpiration(t, db, typ, epoch) mExpired[typ] = putWithExpiration(t, db, typ, epoch-1) } expiredLocked := putWithExpiration(t, db, objectSDK.TypeRegular, epoch-1) require.NoError(t, db.Lock(context.Background(), expiredLocked.Container(), oidtest.ID(), []oid.ID{expiredLocked.Object()}, lockExpiresAfter)) err := db.IterateExpired(context.Background(), epoch, func(exp *meta.ExpiredObject) error { if addr, ok := mAlive[exp.Type()]; ok { require.NotEqual(t, addr, exp.Address()) } require.NotEqual(t, expiredLocked, exp.Address()) addr, ok := mExpired[exp.Type()] require.True(t, ok) require.Equal(t, addr, exp.Address()) delete(mExpired, exp.Type()) return nil }) require.NoError(t, err) require.Empty(t, mExpired) } func putWithExpiration(t *testing.T, db *meta.DB, typ objectSDK.Type, expiresAt uint64) oid.Address { obj := testutil.GenerateObject() obj.SetType(typ) testutil.AddAttribute(obj, objectV2.SysAttributeExpEpoch, strconv.FormatUint(expiresAt, 10)) require.NoError(t, putBig(db, obj)) return object2.AddressOf(obj) } func TestIterateLocked(t *testing.T) { t.Parallel() const ( numObjects = 10 numLocksPerObject = 10 ) db := newDB(t) defer func() { require.NoError(t, db.Close(context.Background())) }() m := make(map[oid.Address][]meta.Lock) // every two objects have same container for range numObjects / 2 { container := cidtest.ID() object := testutil.GenerateObjectWithCID(container) require.NoError(t, metaPut(db, object, nil)) addr := object2.AddressOf(object) m[addr] = []meta.Lock{} object = testutil.GenerateObjectWithCID(container) require.NoError(t, metaPut(db, object, nil)) addr = object2.AddressOf(object) m[addr] = []meta.Lock{} } for addr := range m { // every object has locks of both old and new formats for range numLocksPerObject / 2 { m[addr] = append(m[addr], meta.Lock{ ID: oidtest.ID(), ExpEpoch: rand.Uint64(), }, meta.Lock{ ID: oidtest.ID(), ExpEpoch: meta.NoExpirationEpoch, }, ) } for _, lock := range m[addr] { require.NoError(t, db.Lock( context.Background(), addr.Container(), lock.ID, []oid.ID{addr.Object()}, lock.ExpEpoch, )) } } t.Run("just iterate", func(t *testing.T) { err := db.IterateLocked(context.Background(), nil, func(lo *meta.LockedObject) error { locks, ok := m[lo.Address()] require.True(t, ok) require.ElementsMatch(t, locks, lo.Locks()) return nil }) require.NoError(t, err) }) t.Run("iterate with offset", func(t *testing.T) { var foundObjects []oid.Address var offset *oid.Address for didStep := true; didStep; { didStep = false err := db.IterateLocked(context.Background(), offset, func(lo *meta.LockedObject) error { addr := lo.Address() foundObjects = append(foundObjects, addr) offset = &addr didStep = true return meta.ErrInterruptIterator }) require.NoError(t, err) } require.ElementsMatch(t, foundObjects, maps.Keys(m)) }) }