frostfs-node/pkg/local_object_storage/engine/lock_test.go
Pavel Karpy bd27837364 [#1321] node: Register GC event channel before shard init
Morph "NewEpoch" event handling was registered in a closure over
`addNewEpochNotificationHandler` func. That may lead to the data race:
if a shard was initialized before the event registration, everything works
as planned, but if registration was made earlier, it was not able to
include GC handlers since a shard has not called `eventChanInit` yet and,
therefore, it has not registered handler yet.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
2022-04-25 10:39:37 +03:00

193 lines
4.8 KiB
Go

package engine
import (
"os"
"strconv"
"testing"
"time"
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/shard"
"github.com/nspcc-dev/neofs-node/pkg/util"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/nspcc-dev/neofs-sdk-go/object/address"
objecttest "github.com/nspcc-dev/neofs-sdk-go/object/address/test"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test"
"github.com/panjf2000/ants/v2"
"github.com/stretchr/testify/require"
)
func TestLockUserScenario(t *testing.T) {
t.Skip("posted bug neofs-node#1227")
// Tested user actions:
// 1. stores some object
// 2. locks the object
// 3. tries to inhume the object with tombstone and expects failure
// 4. saves tombstone for LOCK-object and inhumes the LOCK-object using it
// 5. waits for an epoch after the tombstone expiration one
// 6. tries to inhume the object and expects success
chEvents := make([]chan shard.Event, 2)
for i := range chEvents {
chEvents[i] = make(chan shard.Event, 1)
}
e := testEngineFromShardOpts(t, 2, func(i int) []shard.Option {
return []shard.Option{
shard.WithGCEventChannel(chEvents[i]),
shard.WithGCWorkerPoolInitializer(func(sz int) util.WorkerPool {
pool, err := ants.NewPool(sz)
require.NoError(t, err)
return pool
}),
}
})
t.Cleanup(func() {
_ = e.Close()
_ = os.RemoveAll(t.Name())
})
const lockerTombExpiresAfter = 13
lockerID := oidtest.ID()
tombForLockID := oidtest.ID()
tombID := oidtest.ID()
cnr := cidtest.ID()
var err error
var objAddr address.Address
objAddr.SetContainerID(cnr)
var tombAddr address.Address
tombAddr.SetContainerID(cnr)
tombAddr.SetObjectID(tombID)
var lockerAddr address.Address
lockerAddr.SetContainerID(cnr)
lockerAddr.SetObjectID(lockerID)
var tombForLockAddr address.Address
tombForLockAddr.SetContainerID(cnr)
tombForLockAddr.SetObjectID(tombForLockID)
// 1.
obj := generateObjectWithCID(t, cnr)
objAddr.SetObjectID(obj.ID())
err = Put(e, obj)
require.NoError(t, err)
// 2.
err = e.Lock(*cnr, *lockerID, []oid.ID{*obj.ID()})
require.NoError(t, err)
// 3.
_, err = e.Inhume(new(InhumePrm).WithTarget(&tombAddr, &objAddr))
require.ErrorAs(t, err, new(apistatus.ObjectLocked))
// 4.
var a object.Attribute
a.SetKey(objectV2.SysAttributeExpEpoch)
a.SetValue(strconv.Itoa(lockerTombExpiresAfter))
tombObj := generateObjectWithCID(t, cnr)
tombObj.SetType(object.TypeTombstone)
tombObj.SetID(tombForLockID)
tombObj.SetAttributes(a)
err = Put(e, tombObj)
require.NoError(t, err)
_, err = e.Inhume(new(InhumePrm).WithTarget(&tombForLockAddr, &lockerAddr))
require.NoError(t, err, new(apistatus.ObjectLocked))
// 5.
for i := range chEvents {
chEvents[i] <- shard.EventNewEpoch(lockerTombExpiresAfter + 1)
}
// delay for GC
time.Sleep(time.Second)
_, err = e.Inhume(new(InhumePrm).WithTarget(&tombAddr, &objAddr))
require.NoError(t, err)
}
func TestLockExpiration(t *testing.T) {
// Tested scenario:
// 1. some object is stored
// 2. lock object for it is stored, and the object is locked
// 3. lock expiration epoch is coming
// 4. after some delay the object is not locked anymore
chEvents := make([]chan shard.Event, 2)
for i := range chEvents {
chEvents[i] = make(chan shard.Event, 1)
}
e := testEngineFromShardOpts(t, 2, func(i int) []shard.Option {
return []shard.Option{
shard.WithGCEventChannel(chEvents[i]),
shard.WithGCWorkerPoolInitializer(func(sz int) util.WorkerPool {
pool, err := ants.NewPool(sz)
require.NoError(t, err)
return pool
}),
}
})
t.Cleanup(func() {
_ = e.Close()
_ = os.RemoveAll(t.Name())
})
const lockerExpiresAfter = 13
cnr := cidtest.ID()
var err error
// 1.
obj := generateObjectWithCID(t, cnr)
err = Put(e, obj)
require.NoError(t, err)
// 2.
var a object.Attribute
a.SetKey(objectV2.SysAttributeExpEpoch)
a.SetValue(strconv.Itoa(lockerExpiresAfter))
lock := generateObjectWithCID(t, cnr)
lock.SetType(object.TypeLock)
lock.SetAttributes(a)
err = Put(e, lock)
require.NoError(t, err)
err = e.Lock(*cnr, *lock.ID(), []oid.ID{*obj.ID()})
require.NoError(t, err)
_, err = e.Inhume(new(InhumePrm).WithTarget(objecttest.Address(), objectcore.AddressOf(obj)))
require.ErrorAs(t, err, new(apistatus.ObjectLocked))
// 3.
for i := range chEvents {
chEvents[i] <- shard.EventNewEpoch(lockerExpiresAfter + 1)
}
// delay for GC processing. It can't be estimated, but making it bigger
// will slow down test
time.Sleep(time.Second)
// 4.
_, err = e.Inhume(new(InhumePrm).WithTarget(objecttest.Address(), objectcore.AddressOf(obj)))
require.NoError(t, err)
}