[#1527] engine: Add tests for handling expired objects on inhume and lock

Currently, it's allowed to inhume or lock an expired object.
Consider the following scenario:

1) An user inhumes or locks an object
2) The object expires
3) GC hasn't yet deleted the object
4) The node loses the associated tombstone or lock
5) Another node replicates tombstone or lock to the first node

In this case, the second node succeeds, which is the desired behavior.

Signed-off-by: Aleksey Savchuk <a.savchuk@yadro.com>
This commit is contained in:
Aleksey Savchuk 2024-12-02 16:16:50 +03:00
parent 9cabca9dfe
commit 432042c534
Signed by: a-savchuk
GPG key ID: 70C0A7FF6F9C4639
2 changed files with 106 additions and 0 deletions

View file

@ -2,13 +2,17 @@ package engine
import (
"context"
"strconv"
"testing"
"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"
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"
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
"github.com/stretchr/testify/require"
)
@ -84,3 +88,52 @@ func TestStorageEngine_Inhume(t *testing.T) {
require.Empty(t, addrs)
})
}
func TestInhumeExpiredRegularObject(t *testing.T) {
t.Parallel()
const currEpoch = 42
const objectExpiresAfter = currEpoch - 1
engine := testNewEngine(t).setShardsNumAdditionalOpts(t, 1, func(_ int) []shard.Option {
return []shard.Option{
shard.WithDisabledGC(),
shard.WithMetaBaseOptions(append(
testGetDefaultMetabaseOptions(t),
meta.WithEpochState(epochState{currEpoch}),
)...),
}
}).prepare(t).engine
cnr := cidtest.ID()
generateAndPutObject := func() *objectSDK.Object {
obj := testutil.GenerateObjectWithCID(cnr)
testutil.AddAttribute(obj, objectV2.SysAttributeExpEpoch, strconv.Itoa(objectExpiresAfter))
var putPrm PutPrm
putPrm.Object = obj
require.NoError(t, engine.Put(context.Background(), putPrm))
return obj
}
t.Run("inhume with tombstone", func(t *testing.T) {
obj := generateAndPutObject()
ts := oidtest.Address()
ts.SetContainer(cnr)
var prm InhumePrm
prm.WithTarget(ts, object.AddressOf(obj))
_, err := engine.Inhume(context.Background(), prm)
require.NoError(t, err)
})
t.Run("inhume without tombstone", func(t *testing.T) {
obj := generateAndPutObject()
var prm InhumePrm
prm.MarkAsGarbage(object.AddressOf(obj))
_, err := engine.Inhume(context.Background(), prm)
require.NoError(t, err)
})
}

View file

@ -292,3 +292,56 @@ func TestLockForceRemoval(t *testing.T) {
_, err = e.Inhume(context.Background(), inhumePrm)
require.NoError(t, err)
}
func TestLockExpiredRegularObject(t *testing.T) {
const currEpoch = 42
const objectExpiresAfter = currEpoch - 1
engine := testNewEngine(t).setShardsNumAdditionalOpts(t, 1, func(_ int) []shard.Option {
return []shard.Option{
shard.WithDisabledGC(),
shard.WithMetaBaseOptions(append(
testGetDefaultMetabaseOptions(t),
meta.WithEpochState(epochState{currEpoch}),
)...),
}
}).prepare(t).engine
cnr := cidtest.ID()
object := testutil.GenerateObjectWithCID(cnr)
testutil.AddAttribute(object, objectV2.SysAttributeExpEpoch, strconv.Itoa(objectExpiresAfter))
address := objectcore.AddressOf(object)
var putPrm PutPrm
putPrm.Object = object
require.NoError(t, engine.Put(context.Background(), putPrm))
var getPrm GetPrm
var errNotFound *apistatus.ObjectNotFound
getPrm.WithAddress(address)
_, err := engine.Get(context.Background(), getPrm)
require.ErrorAs(t, err, &errNotFound)
t.Run("lock expired regular object", func(t *testing.T) {
engine.Lock(context.Background(),
address.Container(),
oidtest.ID(),
[]oid.ID{address.Object()},
)
res, err := engine.IsLocked(context.Background(), objectcore.AddressOf(object))
require.NoError(t, err)
require.True(t, res)
})
t.Run("get expired and locked regular object", func(t *testing.T) {
getPrm.WithAddress(objectcore.AddressOf(object))
res, err := engine.Get(context.Background(), getPrm)
require.NoError(t, err)
require.Equal(t, res.Object(), object)
})
}