[#1585] metabase: Add missing expiration epoch for object stored with EC
All checks were successful
DCO action / DCO (pull_request) Successful in 17m11s
Pre-commit hooks / Pre-commit (pull_request) Successful in 18m42s
Vulncheck / Vulncheck (pull_request) Successful in 19m49s
Tests and linters / Run gofumpt (pull_request) Successful in 20m25s
Tests and linters / Tests (pull_request) Successful in 20m34s
Build / Build Components (pull_request) Successful in 21m8s
Tests and linters / Lint (pull_request) Successful in 21m11s
Tests and linters / Tests with -race (pull_request) Successful in 21m22s
Tests and linters / Staticcheck (pull_request) Successful in 21m31s
Tests and linters / gopls check (pull_request) Successful in 22m18s

Suppose there's a complex object stored with EC. It's divided into
parts, and these parts are further divided into chunks, except for
the linking object. The chunks and linking object are stored in the
metabase.

An expiration epoch of the original object should be stored in the
expired objects index. In the described scenario, there's no way to
determine the expiration epoch of the object from its chunks because
a chunk's parent is a part of the original object, not the original
object itself. However, the epoch can be determined from the linking
object.

Previously, whether the epoch was stored or not depended on the order
in which the chunks and linking object were written. Now it's fixed.

The absense of the expiration epoch prevented the GC from deleting
this object upon its expiration.

Signed-off-by: Aleksey Savchuk <a.savchuk@yadro.com>
This commit is contained in:
Aleksey Savchuk 2024-12-27 15:24:07 +03:00
parent fa08bfa553
commit d4ff44ef83
Signed by: a-savchuk
GPG key ID: 70C0A7FF6F9C4639
2 changed files with 70 additions and 1 deletions

View file

@ -2,12 +2,18 @@ package meta_test
import (
"context"
"strconv"
"testing"
"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"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/erasurecode"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/transformer"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require"
)
@ -92,3 +98,59 @@ func getAddressSafe(t *testing.T, o *objectSDK.Object) oid.Address {
addr.SetObject(id)
return addr
}
func TestPutExpiredComplexObjectWithEC(t *testing.T) {
const (
currEpoch = 100
expEpoch = currEpoch - 1
partSize = 1 << 10
partCount = 5
dataCount = 2
parityCount = 1
)
db := newDB(t, meta.WithEpochState(epochState{currEpoch}))
defer func() { require.NoError(t, db.Close(context.Background())) }()
cntr := cidtest.ID()
parent := testutil.GenerateObjectWithCID(cntr)
testutil.AddAttribute(parent, objectV2.SysAttributeExpEpoch, strconv.Itoa(expEpoch))
key, err := keys.NewPrivateKey()
require.NoError(t, err)
var target testTarget
ids := cutObject(t, transformer.NewPayloadSizeLimiter(transformer.Params{
Key: &key.PrivateKey,
NextTargetInit: func() transformer.ObjectWriter { return &target },
NetworkState: epochState{currEpoch},
MaxSize: partSize,
}), parent, partCount*partSize)
n := len(target.objects)
objects, link := target.objects[:n-1], target.objects[n-1]
ecc, err := erasurecode.NewConstructor(dataCount, parityCount)
require.NoError(t, err)
var chunks []*objectSDK.Object
for _, object := range objects {
objectChunks, err := ecc.Split(object, &key.PrivateKey)
require.NoError(t, err)
chunks = append(chunks, objectChunks...)
}
// link mustn't be first, it's needed to reproduces the bug
for _, chunk := range chunks {
require.NoError(t, metaPut(db, chunk, nil))
}
require.NoError(t, metaPut(db, link, nil))
var addr oid.Address
addr.SetContainer(cntr)
addr.SetObject(*ids.ParentID)
_, err = metaGet(db, addr, false)
require.ErrorIs(t, err, meta.ErrObjectIsExpired)
}

View file

@ -159,7 +159,14 @@ func (db *DB) updateObj(tx *bbolt.Tx, obj *objectSDK.Object, id []byte, si *obje
// a linking object to put (or vice versa), we should update split info
// with object ids of these objects
if isParent {
return updateSplitInfo(tx, objectCore.AddressOf(obj), si)
addr := objectCore.AddressOf(obj)
objKey := objectKey(addr.Object(), make([]byte, objectKeySize))
if err := putExpirationEpoch(tx, obj, addr, objKey); err != nil {
return err
}
return updateSplitInfo(tx, addr, si)
}
return nil