[#42] Fix using separate container for lifecycles
All checks were successful
/ DCO (pull_request) Successful in 1m11s
/ Builds (1.21) (pull_request) Successful in 1m23s
/ Builds (1.22) (pull_request) Successful in 1m21s
/ Vulncheck (pull_request) Successful in 4m33s
/ Lint (pull_request) Successful in 5m56s
/ Tests (1.21) (pull_request) Successful in 1m7s
/ Tests (1.22) (pull_request) Successful in 1m22s

Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
Marina Biryukova 2024-07-23 14:53:34 +03:00
parent a4a4573b10
commit 5863af4a26
8 changed files with 131 additions and 90 deletions

View file

@ -165,18 +165,14 @@ func prepareHandlerContextBase(t *testing.T, cacheCfg *layer.CachesConfig) *hand
features := &layer.FeatureSettingsMock{} features := &layer.FeatureSettingsMock{}
res, err := tp.CreateContainer(context.Background(), layer.PrmContainerCreate{Name: ".bucket-lifecycles"})
require.NoError(t, err)
layerCfg := &layer.Config{ layerCfg := &layer.Config{
Cache: layer.NewCache(cacheCfg), Cache: layer.NewCache(cacheCfg),
AnonKey: layer.AnonymousKey{Key: key}, AnonKey: layer.AnonymousKey{Key: key},
Resolver: testResolver, Resolver: testResolver,
TreeService: treeMock, TreeService: treeMock,
Features: features, Features: features,
GateOwner: owner, GateOwner: owner,
LifecycleCnrInfo: &data.BucketInfo{CID: res.ContainerID}, GateKey: key,
GateKey: key,
} }
var pp netmap.PlacementPolicy var pp netmap.PlacementPolicy

View file

@ -817,5 +817,20 @@ func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
} }
n.cache.DeleteBucket(p.BktInfo) n.cache.DeleteBucket(p.BktInfo)
return n.frostFS.DeleteContainer(ctx, p.BktInfo.CID, p.SessionToken)
lifecycleObj, err := n.treeService.GetBucketLifecycleConfiguration(ctx, p.BktInfo)
if err != nil {
n.reqLogger(ctx).Error(logs.GetBucketLifecycle, zap.Error(err))
}
err = n.frostFS.DeleteContainer(ctx, p.BktInfo.CID, p.SessionToken)
if err != nil {
return fmt.Errorf("delete container: %w", err)
}
if !lifecycleObj.Container().Equals(p.BktInfo.CID) {
n.deleteLifecycleObject(ctx, p.BktInfo, lifecycleObj)
}
return nil
} }

View file

@ -12,7 +12,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -45,7 +44,7 @@ func (n *Layer) PutBucketLifecycleConfiguration(ctx context.Context, p *PutBucke
createdObj, err := n.objectPutAndHash(ctx, prm, lifecycleBkt) createdObj, err := n.objectPutAndHash(ctx, prm, lifecycleBkt)
if err != nil { if err != nil {
return err return fmt.Errorf("put lifecycle object: %w", err)
} }
hashBytes, err := base64.StdEncoding.DecodeString(p.MD5Hash) hashBytes, err := base64.StdEncoding.DecodeString(p.MD5Hash)
@ -54,19 +53,19 @@ func (n *Layer) PutBucketLifecycleConfiguration(ctx context.Context, p *PutBucke
} }
if !bytes.Equal(hashBytes, createdObj.MD5Sum) { if !bytes.Equal(hashBytes, createdObj.MD5Sum) {
n.deleteLifecycleObject(ctx, lifecycleBkt, createdObj.ID) n.deleteLifecycleObject(ctx, p.BktInfo, newAddress(lifecycleBkt.CID, createdObj.ID))
return apiErr.GetAPIError(apiErr.ErrInvalidDigest) return apiErr.GetAPIError(apiErr.ErrInvalidDigest)
} }
objIDToDelete, err := n.treeService.PutBucketLifecycleConfiguration(ctx, p.BktInfo, createdObj.ID) objToDelete, err := n.treeService.PutBucketLifecycleConfiguration(ctx, p.BktInfo, newAddress(lifecycleBkt.CID, createdObj.ID))
objIDToDeleteNotFound := errors.Is(err, ErrNoNodeToRemove) objToDeleteNotFound := errors.Is(err, ErrNoNodeToRemove)
if err != nil && !objIDToDeleteNotFound { if err != nil && !objToDeleteNotFound {
return err return err
} }
if !objIDToDeleteNotFound { if !objToDeleteNotFound {
n.deleteLifecycleObject(ctx, lifecycleBkt, objIDToDelete) n.deleteLifecycleObject(ctx, p.BktInfo, objToDelete)
} }
n.cache.PutLifecycleConfiguration(n.BearerOwner(ctx), p.BktInfo, p.LifecycleCfg) n.cache.PutLifecycleConfiguration(n.BearerOwner(ctx), p.BktInfo, p.LifecycleCfg)
@ -75,18 +74,18 @@ func (n *Layer) PutBucketLifecycleConfiguration(ctx context.Context, p *PutBucke
} }
// deleteLifecycleObject removes object and logs in case of error. // deleteLifecycleObject removes object and logs in case of error.
func (n *Layer) deleteLifecycleObject(ctx context.Context, lifecycleBkt *data.BucketInfo, objID oid.ID) { func (n *Layer) deleteLifecycleObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) {
var err error var prmAuth PrmAuth
if n.lifecycleCnrInfo == nil { lifecycleBkt := bktInfo
err = n.objectDelete(ctx, lifecycleBkt, objID) if !addr.Container().Equals(bktInfo.CID) {
} else { lifecycleBkt = &data.BucketInfo{CID: addr.Container()}
err = n.objectDeleteWithAuth(ctx, lifecycleBkt, objID, PrmAuth{PrivateKey: &n.gateKey.PrivateKey}) prmAuth.PrivateKey = &n.gateKey.PrivateKey
} }
if err != nil { if err := n.objectDeleteWithAuth(ctx, lifecycleBkt, addr.Object(), prmAuth); err != nil {
n.reqLogger(ctx).Error(logs.CouldntDeleteLifecycleObject, zap.Error(err), n.reqLogger(ctx).Error(logs.CouldntDeleteLifecycleObject, zap.Error(err),
zap.String("cid", lifecycleBkt.CID.EncodeToString()), zap.String("cid", lifecycleBkt.CID.EncodeToString()),
zap.String("oid", objID.EncodeToString())) zap.String("oid", addr.Object().EncodeToString()))
} }
} }
@ -96,25 +95,26 @@ func (n *Layer) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *da
return cfg, nil return cfg, nil
} }
objID, err := n.treeService.GetBucketLifecycleConfiguration(ctx, bktInfo) addr, err := n.treeService.GetBucketLifecycleConfiguration(ctx, bktInfo)
objIDNotFound := errors.Is(err, ErrNodeNotFound) objNotFound := errors.Is(err, ErrNodeNotFound)
if err != nil && !objIDNotFound { if err != nil && !objNotFound {
return nil, err return nil, err
} }
if objIDNotFound { if objNotFound {
return nil, fmt.Errorf("%w: %s", apiErr.GetAPIError(apiErr.ErrNoSuchLifecycleConfiguration), err.Error()) return nil, fmt.Errorf("%w: %s", apiErr.GetAPIError(apiErr.ErrNoSuchLifecycleConfiguration), err.Error())
} }
var obj *object.Object var prmAuth PrmAuth
if n.lifecycleCnrInfo == nil { lifecycleBkt := bktInfo
if obj, err = n.objectGet(ctx, bktInfo, objID); err != nil { if !addr.Container().Equals(bktInfo.CID) {
return nil, err lifecycleBkt = &data.BucketInfo{CID: addr.Container()}
} prmAuth.PrivateKey = &n.gateKey.PrivateKey
} else { }
if obj, err = n.objectGetWithAuth(ctx, n.lifecycleCnrInfo, objID, PrmAuth{PrivateKey: &n.gateKey.PrivateKey}); err != nil {
return nil, err obj, err := n.objectGetWithAuth(ctx, lifecycleBkt, addr.Object(), prmAuth)
} if err != nil {
return nil, fmt.Errorf("get lifecycle object: %w", err)
} }
lifecycleCfg := &data.LifecycleConfiguration{} lifecycleCfg := &data.LifecycleConfiguration{}
@ -129,20 +129,22 @@ func (n *Layer) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *da
} }
func (n *Layer) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) error { func (n *Layer) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) error {
objID, err := n.treeService.DeleteBucketLifecycleConfiguration(ctx, bktInfo) obj, err := n.treeService.DeleteBucketLifecycleConfiguration(ctx, bktInfo)
objIDNotFound := errors.Is(err, ErrNoNodeToRemove) objNotFound := errors.Is(err, ErrNoNodeToRemove)
if err != nil && !objIDNotFound { if err != nil && !objNotFound {
return err return err
} }
if !objIDNotFound {
if n.lifecycleCnrInfo == nil { if !objNotFound {
if err = n.objectDelete(ctx, bktInfo, objID); err != nil { var prmAuth PrmAuth
return err lifecycleBkt := bktInfo
} if !obj.Container().Equals(bktInfo.CID) {
} else { lifecycleBkt = &data.BucketInfo{CID: obj.Container()}
if err = n.objectDeleteWithAuth(ctx, n.lifecycleCnrInfo, objID, PrmAuth{PrivateKey: &n.gateKey.PrivateKey}); err != nil { prmAuth.PrivateKey = &n.gateKey.PrivateKey
return err }
}
if err = n.objectDeleteWithAuth(ctx, lifecycleBkt, obj.Object(), prmAuth); err != nil {
return fmt.Errorf("delete lifecycle object: %w", err)
} }
} }

View file

@ -392,49 +392,49 @@ LOOP:
return result, nil return result, nil
} }
func (t *TreeServiceMock) PutBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oid.ID, error) { func (t *TreeServiceMock) PutBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oid.Address, error) {
systemMap, ok := t.system[bktInfo.CID.EncodeToString()] systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
if !ok { if !ok {
systemMap = make(map[string]*data.BaseNodeVersion) systemMap = make(map[string]*data.BaseNodeVersion)
} }
systemMap["lifecycle"] = &data.BaseNodeVersion{ systemMap["lifecycle"] = &data.BaseNodeVersion{
OID: objID, OID: addr.Object(),
} }
t.system[bktInfo.CID.EncodeToString()] = systemMap t.system[bktInfo.CID.EncodeToString()] = systemMap
return oid.ID{}, ErrNoNodeToRemove return oid.Address{}, ErrNoNodeToRemove
} }
func (t *TreeServiceMock) GetBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo) (oid.ID, error) { func (t *TreeServiceMock) GetBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
systemMap, ok := t.system[bktInfo.CID.EncodeToString()] systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
if !ok { if !ok {
return oid.ID{}, ErrNodeNotFound return oid.Address{}, ErrNodeNotFound
} }
node, ok := systemMap["lifecycle"] node, ok := systemMap["lifecycle"]
if !ok { if !ok {
return oid.ID{}, ErrNodeNotFound return oid.Address{}, ErrNodeNotFound
} }
return node.OID, nil return newAddress(bktInfo.CID, node.OID), nil
} }
func (t *TreeServiceMock) DeleteBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo) (oid.ID, error) { func (t *TreeServiceMock) DeleteBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
systemMap, ok := t.system[bktInfo.CID.EncodeToString()] systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
if !ok { if !ok {
return oid.ID{}, ErrNoNodeToRemove return oid.Address{}, ErrNoNodeToRemove
} }
node, ok := systemMap["lifecycle"] node, ok := systemMap["lifecycle"]
if !ok { if !ok {
return oid.ID{}, ErrNoNodeToRemove return oid.Address{}, ErrNoNodeToRemove
} }
delete(systemMap, "lifecycle") delete(systemMap, "lifecycle")
return node.OID, nil return newAddress(bktInfo.CID, node.OID), nil
} }
func (t *TreeServiceMock) DeleteMultipartUpload(_ context.Context, bktInfo *data.BucketInfo, multipartInfo *data.MultipartInfo) error { func (t *TreeServiceMock) DeleteMultipartUpload(_ context.Context, bktInfo *data.BucketInfo, multipartInfo *data.MultipartInfo) error {

View file

@ -63,9 +63,9 @@ type TreeService interface {
AddPart(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64, info *data.PartInfo) (oldObjIDToDelete oid.ID, err error) AddPart(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64, info *data.PartInfo) (oldObjIDToDelete oid.ID, err error)
GetParts(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64) ([]*data.PartInfo, error) GetParts(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64) ([]*data.PartInfo, error)
PutBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oldObjIDToDelete oid.ID, err error) PutBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oldObjToDelete oid.Address, err error)
GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error)
DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error)
// Compound methods for optimizations // Compound methods for optimizations

View file

@ -168,13 +168,12 @@ func prepareContext(t *testing.T, cachesConfig ...*CachesConfig) *testContext {
user.IDFromKey(&owner, key.PrivateKey.PublicKey) user.IDFromKey(&owner, key.PrivateKey.PublicKey)
layerCfg := &Config{ layerCfg := &Config{
Cache: NewCache(config), Cache: NewCache(config),
AnonKey: AnonymousKey{Key: key}, AnonKey: AnonymousKey{Key: key},
TreeService: NewTreeService(), TreeService: NewTreeService(),
Features: &FeatureSettingsMock{}, Features: &FeatureSettingsMock{},
GateOwner: owner, GateOwner: owner,
LifecycleCnrInfo: &data.BucketInfo{CID: res.ContainerID}, GateKey: key,
GateKey: key,
} }
return &testContext{ return &testContext{

View file

@ -153,4 +153,5 @@ const (
CouldntCacheLifecycleConfiguration = "couldn't cache lifecycle configuration" CouldntCacheLifecycleConfiguration = "couldn't cache lifecycle configuration"
CouldNotFetchLifecycleContainerInfo = "couldn't fetch lifecycle container info" CouldNotFetchLifecycleContainerInfo = "couldn't fetch lifecycle container info"
FoundSeveralBucketLifecycleNodes = "found several bucket lifecycle nodes, latest be used" FoundSeveralBucketLifecycleNodes = "found several bucket lifecycle nodes, latest be used"
GetBucketLifecycle = "get bucket lifecycle"
) )

View file

@ -15,6 +15,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -84,6 +85,7 @@ const (
ownerKeyKV = "ownerKey" ownerKeyKV = "ownerKey"
lockConfigurationKV = "LockConfiguration" lockConfigurationKV = "LockConfiguration"
oidKV = "OID" oidKV = "OID"
cidKV = "CID"
isCombinedKV = "IsCombined" isCombinedKV = "IsCombined"
isUnversionedKV = "IsUnversioned" isUnversionedKV = "IsUnversioned"
@ -1374,22 +1376,23 @@ func (c *Tree) GetParts(ctx context.Context, bktInfo *data.BucketInfo, multipart
return result, nil return result, nil
} }
func (c *Tree) PutBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oid.ID, error) { func (c *Tree) PutBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oid.Address, error) {
node, err := c.getSystemNode(ctx, bktInfo, []string{bucketLifecycleFilename}) node, err := c.getSystemNode(ctx, bktInfo, []string{bucketLifecycleFilename})
isErrNotFound := errors.Is(err, layer.ErrNodeNotFound) isErrNotFound := errors.Is(err, layer.ErrNodeNotFound)
if err != nil && !isErrNotFound { if err != nil && !isErrNotFound {
return oid.ID{}, fmt.Errorf("couldn't get node: %w", err) return oid.Address{}, fmt.Errorf("couldn't get node: %w", err)
} }
meta := make(map[string]string) meta := make(map[string]string)
meta[FileNameKey] = bucketLifecycleFilename meta[FileNameKey] = bucketLifecycleFilename
meta[oidKV] = objID.EncodeToString() meta[oidKV] = addr.Object().EncodeToString()
meta[cidKV] = addr.Container().EncodeToString()
if isErrNotFound { if isErrNotFound {
if _, err = c.service.AddNode(ctx, bktInfo, systemTree, 0, meta); err != nil { if _, err = c.service.AddNode(ctx, bktInfo, systemTree, 0, meta); err != nil {
return oid.ID{}, err return oid.Address{}, err
} }
return oid.ID{}, layer.ErrNoNodeToRemove return oid.Address{}, layer.ErrNoNodeToRemove
} }
ind := node.GetLatestNodeIndex() ind := node.GetLatestNodeIndex()
@ -1397,22 +1400,27 @@ func (c *Tree) PutBucketLifecycleConfiguration(ctx context.Context, bktInfo *dat
c.reqLogger(ctx).Warn(logs.FoundSeveralBucketLifecycleNodes) c.reqLogger(ctx).Warn(logs.FoundSeveralBucketLifecycleNodes)
} }
return node.ObjID, c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta) prevAddr, err := getAddress(node)
}
func (c *Tree) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) {
node, err := c.getSystemNode(ctx, bktInfo, []string{bucketLifecycleFilename})
if err != nil { if err != nil {
return oid.ID{}, err return oid.Address{}, fmt.Errorf("couldn't get prev lifecycle object addr: %w", err)
} }
return node.ObjID, nil return prevAddr, c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta)
} }
func (c *Tree) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) { func (c *Tree) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
node, err := c.getSystemNode(ctx, bktInfo, []string{bucketLifecycleFilename})
if err != nil {
return oid.Address{}, err
}
return getAddress(node)
}
func (c *Tree) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
node, err := c.getSystemNode(ctx, bktInfo, []string{bucketLifecycleFilename}) node, err := c.getSystemNode(ctx, bktInfo, []string{bucketLifecycleFilename})
if err != nil && !errors.Is(err, layer.ErrNodeNotFound) { if err != nil && !errors.Is(err, layer.ErrNodeNotFound) {
return oid.ID{}, err return oid.Address{}, err
} }
if node != nil { if node != nil {
@ -1421,10 +1429,30 @@ func (c *Tree) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *
c.reqLogger(ctx).Warn(logs.FoundSeveralBucketLifecycleNodes) c.reqLogger(ctx).Warn(logs.FoundSeveralBucketLifecycleNodes)
} }
return node.ObjID, c.service.RemoveNode(ctx, bktInfo, systemTree, node.ID[ind]) addr, err := getAddress(node)
if err != nil {
return oid.Address{}, fmt.Errorf("couldn't get lifecycle object addr: %w", err)
}
return addr, c.service.RemoveNode(ctx, bktInfo, systemTree, node.ID[ind])
} }
return oid.ID{}, layer.ErrNoNodeToRemove return oid.Address{}, layer.ErrNoNodeToRemove
}
func getAddress(node *treeNode) (oid.Address, error) {
var addr oid.Address
addr.SetObject(node.ObjID)
if cidStr, ok := node.Get(cidKV); ok {
var cnrID cid.ID
if err := cnrID.DecodeString(cidStr); err != nil {
return oid.Address{}, fmt.Errorf("couldn't decode cid: %w", err)
}
addr.SetContainer(cnrID)
}
return addr, nil
} }
func (c *Tree) DeleteMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo, multipartInfo *data.MultipartInfo) error { func (c *Tree) DeleteMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo, multipartInfo *data.MultipartInfo) error {