frostfs-node/pkg/local_object_storage/metabase/lock_test.go

285 lines
7.6 KiB
Go
Raw Normal View History

package meta_test
import (
"context"
"testing"
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"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
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"
objecttest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/test"
"github.com/stretchr/testify/require"
)
func TestDB_Lock(t *testing.T) {
t.Parallel()
cnr := cidtest.ID()
db := newDB(t)
defer func() { require.NoError(t, db.Close(context.Background())) }()
t.Run("empty locked list", func(t *testing.T) {
require.Panics(t, func() { _ = db.Lock(context.Background(), cnr, oid.ID{}, nil) })
require.Panics(t, func() { _ = db.Lock(context.Background(), cnr, oid.ID{}, []oid.ID{}) })
})
t.Run("(ir)regular", func(t *testing.T) {
for _, typ := range [...]objectSDK.Type{
objectSDK.TypeTombstone,
objectSDK.TypeLock,
objectSDK.TypeRegular,
} {
obj := objecttest.Object()
obj.SetType(typ)
obj.SetContainerID(cnr)
// save irregular object
err := metaPut(db, obj, nil)
require.NoError(t, err, typ)
var e *apistatus.LockNonRegularObject
id, _ := obj.ID()
// try to lock it
err = db.Lock(context.Background(), cnr, oidtest.ID(), []oid.ID{id})
if typ == objectSDK.TypeRegular {
require.NoError(t, err, typ)
} else {
require.ErrorAs(t, err, &e, typ)
}
}
})
t.Run("removing lock object", func(t *testing.T) {
objs, lockObj := putAndLockObj(t, db, 1)
objAddr := objectcore.AddressOf(objs[0])
lockAddr := objectcore.AddressOf(lockObj)
var inhumePrm meta.InhumePrm
inhumePrm.SetGCMark()
// check locking relation
var objLockedErr *apistatus.ObjectLocked
inhumePrm.SetAddresses(objAddr)
_, err := db.Inhume(context.Background(), inhumePrm)
require.ErrorAs(t, err, &objLockedErr)
tombAddr := oidtest.Address()
tombAddr.SetContainer(objAddr.Container())
inhumePrm.SetTombstoneAddress(tombAddr)
_, err = db.Inhume(context.Background(), inhumePrm)
require.ErrorAs(t, err, &objLockedErr)
// try to remove lock object
inhumePrm.SetAddresses(lockAddr)
_, err = db.Inhume(context.Background(), inhumePrm)
require.Error(t, err)
// check that locking relation has not been
// dropped
inhumePrm.SetAddresses(objAddr)
_, err = db.Inhume(context.Background(), inhumePrm)
require.ErrorAs(t, err, &objLockedErr)
tombAddr = oidtest.Address()
tombAddr.SetContainer(objAddr.Container())
inhumePrm.SetTombstoneAddress(tombAddr)
_, err = db.Inhume(context.Background(), inhumePrm)
require.ErrorAs(t, err, &objLockedErr)
})
t.Run("lock-unlock scenario", func(t *testing.T) {
objs, lockObj := putAndLockObj(t, db, 1)
objAddr := objectcore.AddressOf(objs[0])
lockAddr := objectcore.AddressOf(lockObj)
var objLockedErr *apistatus.ObjectLocked
// try to inhume locked object using tombstone
err := metaInhume(db, objAddr, lockAddr.Object())
require.ErrorAs(t, err, &objLockedErr)
// free locked object
var inhumePrm meta.InhumePrm
inhumePrm.SetAddresses(lockAddr)
inhumePrm.SetForceGCMark()
inhumePrm.SetLockObjectHandling()
res, err := db.Inhume(context.Background(), inhumePrm)
require.NoError(t, err)
require.Len(t, res.DeletedLockObjects(), 1)
require.Equal(t, objectcore.AddressOf(lockObj), res.DeletedLockObjects()[0])
_, err = db.FreeLockedBy([]oid.Address{lockAddr})
require.NoError(t, err)
inhumePrm.SetAddresses(objAddr)
inhumePrm.SetGCMark()
// now we can inhume the object
_, err = db.Inhume(context.Background(), inhumePrm)
require.NoError(t, err)
})
t.Run("force removing lock objects", func(t *testing.T) {
const objsNum = 3
// put and lock `objsNum` objects
objs, lockObj := putAndLockObj(t, db, objsNum)
// force remove objects
var inhumePrm meta.InhumePrm
inhumePrm.SetForceGCMark()
inhumePrm.SetAddresses(objectcore.AddressOf(lockObj))
inhumePrm.SetLockObjectHandling()
res, err := db.Inhume(context.Background(), inhumePrm)
require.NoError(t, err)
require.Len(t, res.DeletedLockObjects(), 1)
require.Equal(t, objectcore.AddressOf(lockObj), res.DeletedLockObjects()[0])
// unlock just objects that were locked by
// just removed locker
_, err = db.FreeLockedBy([]oid.Address{res.DeletedLockObjects()[0]})
require.NoError(t, err)
// removing objects after unlock
inhumePrm.SetGCMark()
for i := range objsNum {
inhumePrm.SetAddresses(objectcore.AddressOf(objs[i]))
res, err = db.Inhume(context.Background(), inhumePrm)
require.NoError(t, err)
require.Len(t, res.DeletedLockObjects(), 0)
}
})
t.Run("skipping lock object handling", func(t *testing.T) {
_, lockObj := putAndLockObj(t, db, 1)
var inhumePrm meta.InhumePrm
inhumePrm.SetForceGCMark()
inhumePrm.SetAddresses(objectcore.AddressOf(lockObj))
res, err := db.Inhume(context.Background(), inhumePrm)
require.NoError(t, err)
require.Len(t, res.DeletedLockObjects(), 0)
})
}
func TestDB_Lock_Expired(t *testing.T) {
t.Parallel()
es := &epochState{e: 123}
db := newDB(t, meta.WithEpochState(es))
defer func() { require.NoError(t, db.Close(context.Background())) }()
// put an object
addr := putWithExpiration(t, db, objectSDK.TypeRegular, 124)
// expire the obj
es.e = 125
_, err := metaGet(db, addr, false)
require.ErrorIs(t, err, meta.ErrObjectIsExpired)
// lock the obj
require.NoError(t, db.Lock(context.Background(), addr.Container(), oidtest.ID(), []oid.ID{addr.Object()}))
// object is expired but locked, thus, must be available
_, err = metaGet(db, addr, false)
require.NoError(t, err)
}
func TestDB_IsLocked(t *testing.T) {
t.Parallel()
db := newDB(t)
defer func() { require.NoError(t, db.Close(context.Background())) }()
// existing and locked objs
objs, _ := putAndLockObj(t, db, 5)
var prm meta.IsLockedPrm
for _, obj := range objs {
prm.SetAddress(objectcore.AddressOf(obj))
res, err := db.IsLocked(context.Background(), prm)
require.NoError(t, err)
require.True(t, res.Locked())
}
// some rand obj
prm.SetAddress(oidtest.Address())
res, err := db.IsLocked(context.Background(), prm)
require.NoError(t, err)
require.False(t, res.Locked())
// existing but not locked obj
obj := objecttest.Object()
var putPrm meta.PutPrm
putPrm.SetObject(obj)
_, err = db.Put(context.Background(), putPrm)
require.NoError(t, err)
prm.SetAddress(objectcore.AddressOf(obj))
res, err = db.IsLocked(context.Background(), prm)
require.NoError(t, err)
require.False(t, res.Locked())
}
// putAndLockObj puts object, returns it and its locker.
func putAndLockObj(t *testing.T, db *meta.DB, numOfLockedObjs int) ([]*objectSDK.Object, *objectSDK.Object) {
cnr := cidtest.ID()
lockedObjs := make([]*objectSDK.Object, 0, numOfLockedObjs)
lockedObjIDs := make([]oid.ID, 0, numOfLockedObjs)
for range numOfLockedObjs {
obj := testutil.GenerateObjectWithCID(cnr)
err := putBig(db, obj)
require.NoError(t, err)
id, _ := obj.ID()
lockedObjs = append(lockedObjs, obj)
lockedObjIDs = append(lockedObjIDs, id)
}
lockObj := testutil.GenerateObjectWithCID(cnr)
lockID, _ := lockObj.ID()
lockObj.SetType(objectSDK.TypeLock)
err := putBig(db, lockObj)
require.NoError(t, err)
err = db.Lock(context.Background(), cnr, lockID, lockedObjIDs)
require.NoError(t, err)
return lockedObjs, lockObj
}