diff --git a/api/handler/handlers_test.go b/api/handler/handlers_test.go index 7793036f..f18ca581 100644 --- a/api/handler/handlers_test.go +++ b/api/handler/handlers_test.go @@ -17,7 +17,6 @@ import ( "github.com/nspcc-dev/neofs-s3-gw/api/data" "github.com/nspcc-dev/neofs-s3-gw/api/layer" "github.com/nspcc-dev/neofs-s3-gw/api/resolver" - treetest "github.com/nspcc-dev/neofs-s3-gw/internal/neofstest/tree" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/user" @@ -59,7 +58,7 @@ func prepareHandlerContext(t *testing.T) *handlerContext { Caches: layer.DefaultCachesConfigs(zap.NewExample()), AnonKey: layer.AnonymousKey{Key: key}, Resolver: testResolver, - TreeService: treetest.NewTreeService(), + TreeService: layer.NewTreeService(), } h := &handler{ diff --git a/api/layer/layer.go b/api/layer/layer.go index 9c7e67b7..84c70c12 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -499,6 +499,8 @@ func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, settings if obj.Error = n.treeService.AddVersion(ctx, &bkt.CID, obj.Name, newVersion); obj.Error != nil { return obj } + + n.namesCache.Delete(bkt.Name + "/" + obj.Name) } else { versions, err := n.treeService.GetVersions(ctx, &bkt.CID, obj.Name) if err != nil { @@ -530,16 +532,7 @@ func (n *layer) removeVersionIfFound(ctx context.Context, bkt *data.BucketInfo, if err := n.objectDelete(ctx, bkt, version.OID); err != nil { return deleteMarkVersion, err } - if err := n.treeService.RemoveVersion(ctx, &bkt.CID, version.ID); err != nil { - return deleteMarkVersion, err - } - - p := &ObjectVersion{ - BktInfo: bkt, - ObjectName: obj.Name, - VersionID: version.OID.EncodeToString(), - } - return deleteMarkVersion, n.DeleteObjectTagging(ctx, p) + return deleteMarkVersion, n.treeService.RemoveVersion(ctx, &bkt.CID, version.ID) } return "", errors.GetAPIError(errors.ErrNoSuchVersion) diff --git a/api/layer/object.go b/api/layer/object.go index 31f844bb..7328702e 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -342,34 +342,31 @@ func (n *layer) headVersions(ctx context.Context, bkt *data.BucketInfo, objectNa } func (n *layer) headVersion(ctx context.Context, bkt *data.BucketInfo, p *HeadObjectParams) (*data.ObjectInfo, error) { + var err error + var foundVersion *data.NodeVersion if p.VersionID == unversionedObjectVersionID { - versions, err := n.headVersions(ctx, bkt, p.Object) + foundVersion, err = n.treeService.GetUnversioned(ctx, &bkt.CID, p.Object) if err != nil { + if errors.Is(err, ErrNodeNotFound) { + return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchVersion) + } return nil, err } + } else { + versions, err := n.treeService.GetVersions(ctx, &bkt.CID, p.Object) + if err != nil { + return nil, fmt.Errorf("couldn't get versions: %w", err) + } - objInfo := versions.getLast(FromUnversioned()) - if objInfo == nil { + for _, version := range versions { + if version.OID.EncodeToString() == p.VersionID { + foundVersion = version + break + } + } + if foundVersion == nil { return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchVersion) } - return objInfo, nil - } - - versions, err := n.treeService.GetVersions(ctx, &bkt.CID, p.Object) - if err != nil { - return nil, fmt.Errorf("couldn't get versions: %w", err) - } - - var foundVersion *data.NodeVersion - for _, version := range versions { - if version.OID.EncodeToString() == p.VersionID { - foundVersion = version - break - } - } - - if foundVersion == nil { - return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchVersion) } if headInfo := n.objCache.Get(newAddress(bkt.CID, foundVersion.OID)); headInfo != nil { diff --git a/internal/neofstest/tree/tree_mock.go b/api/layer/tree_mock.go similarity index 82% rename from internal/neofstest/tree/tree_mock.go rename to api/layer/tree_mock.go index 89b324eb..23bb02dd 100644 --- a/internal/neofstest/tree/tree_mock.go +++ b/api/layer/tree_mock.go @@ -1,8 +1,7 @@ -package tree +package layer import ( "context" - "errors" "sort" "strings" @@ -48,8 +47,6 @@ func (t *TreeServiceMock) DeleteBucketTagging(ctx context.Context, cnrID *cid.ID panic("implement me") } -var ErrNodeNotFound = errors.New("not found") - func NewTreeService() *TreeServiceMock { return &TreeServiceMock{ settings: make(map[string]*data.BucketSettings), @@ -107,7 +104,7 @@ func (t *TreeServiceMock) GetVersions(_ context.Context, cnrID *cid.ID, objectNa return versions, nil } -func (t *TreeServiceMock) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*data.NodeVersion, error) { +func (t *TreeServiceMock) GetLatestVersion(_ context.Context, cnrID *cid.ID, objectName string) (*data.NodeVersion, error) { cnrVersionsMap, ok := t.versions[cnrID.EncodeToString()] if !ok { return nil, ErrNodeNotFound @@ -154,8 +151,24 @@ func (t *TreeServiceMock) GetLatestVersionsByPrefix(_ context.Context, cnrID *ci return result, nil } -func (t *TreeServiceMock) GetUnversioned(ctx context.Context, cnrID *cid.ID, objectName string) (*data.NodeVersion, error) { - panic("implement me") +func (t *TreeServiceMock) GetUnversioned(_ context.Context, cnrID *cid.ID, objectName string) (*data.NodeVersion, error) { + cnrVersionsMap, ok := t.versions[cnrID.EncodeToString()] + if !ok { + return nil, ErrNodeNotFound + } + + versions, ok := cnrVersionsMap[objectName] + if !ok { + return nil, ErrNodeNotFound + } + + for _, version := range versions { + if version.IsUnversioned { + return version, nil + } + } + + return nil, ErrNodeNotFound } func (t *TreeServiceMock) AddVersion(_ context.Context, cnrID *cid.ID, objectName string, newVersion *data.NodeVersion) error { @@ -179,6 +192,7 @@ func (t *TreeServiceMock) AddVersion(_ context.Context, cnrID *cid.ID, objectNam if len(versions) != 0 { newVersion.ID = versions[len(versions)-1].ID + 1 + newVersion.Timestamp = versions[len(versions)-1].Timestamp + 1 } result := versions @@ -197,12 +211,38 @@ func (t *TreeServiceMock) AddVersion(_ context.Context, cnrID *cid.ID, objectNam return nil } -func (t *TreeServiceMock) RemoveVersion(ctx context.Context, cnrID *cid.ID, nodeID uint64) error { - panic("implement me") +func (t *TreeServiceMock) RemoveVersion(_ context.Context, cnrID *cid.ID, nodeID uint64) error { + cnrVersionsMap, ok := t.versions[cnrID.EncodeToString()] + if !ok { + return ErrNodeNotFound + } + + for key, versions := range cnrVersionsMap { + for i, node := range versions { + if node.ID == nodeID { + cnrVersionsMap[key] = append(versions[:i], versions[i+1:]...) + return nil + } + } + } + + return ErrNodeNotFound } -func (t *TreeServiceMock) GetAllVersionsByPrefix(ctx context.Context, cnrID *cid.ID, prefix string) ([]*data.NodeVersion, error) { - panic("implement me") +func (t *TreeServiceMock) GetAllVersionsByPrefix(_ context.Context, cnrID *cid.ID, prefix string) ([]*data.NodeVersion, error) { + cnrVersionsMap, ok := t.versions[cnrID.EncodeToString()] + if !ok { + return nil, nil + } + + var result []*data.NodeVersion + for objName, versions := range cnrVersionsMap { + if strings.HasPrefix(objName, prefix) { + result = append(result, versions...) + } + } + + return result, nil } func (t *TreeServiceMock) CreateMultipartUpload(ctx context.Context, cnrID *cid.ID, info *data.MultipartInfo) error { diff --git a/api/layer/versioning_test.go b/api/layer/versioning_test.go index e24c0c65..0a530b18 100644 --- a/api/layer/versioning_test.go +++ b/api/layer/versioning_test.go @@ -11,7 +11,6 @@ import ( "github.com/nspcc-dev/neofs-s3-gw/api" "github.com/nspcc-dev/neofs-s3-gw/api/data" "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" - treetest "github.com/nspcc-dev/neofs-s3-gw/internal/neofstest/tree" bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" @@ -177,7 +176,7 @@ func prepareContext(t *testing.T, cachesConfig ...*CachesConfig) *testContext { layerCfg := &Config{ Caches: config, AnonKey: AnonymousKey{Key: key}, - TreeService: treetest.NewTreeService(), + TreeService: NewTreeService(), } return &testContext{ @@ -251,6 +250,24 @@ func TestVersioningDeleteObject(t *testing.T) { tc.checkListObjects() } +func TestGetUnversioned(t *testing.T) { + tc := prepareContext(t) + + objContent := []byte("content obj1 v1") + objInfo := tc.putObject(objContent) + + settings := &data.BucketSettings{VersioningEnabled: false} + err := tc.layer.PutBucketSettings(tc.ctx, &PutSettingsParams{ + BktInfo: tc.bktInfo, + Settings: settings, + }) + require.NoError(t, err) + + resInfo, buffer := tc.getObject(tc.obj, unversionedObjectVersionID, false) + require.Equal(t, objContent, buffer) + require.Equal(t, objInfo.Version(), resInfo.Version()) +} + func TestVersioningDeleteSpecificObjectVersion(t *testing.T) { tc := prepareContext(t) settings := &data.BucketSettings{VersioningEnabled: true} @@ -521,77 +538,3 @@ func getTestObjectInfoEpoch(epoch uint64, id byte, addAttr, delAttr, delMarkAttr obj.CreationEpoch = epoch return obj } - -func TestSystemObjectsVersioning(t *testing.T) { - cacheConfig := DefaultCachesConfigs(zap.NewExample()) - cacheConfig.System.Lifetime = 0 - - tc := prepareContext(t, cacheConfig) - err := tc.layer.PutBucketSettings(tc.ctx, &PutSettingsParams{ - BktInfo: tc.bktInfo, - Settings: &data.BucketSettings{VersioningEnabled: false}, - }) - require.NoError(t, err) - - objMeta := tc.getSystemObject(tc.bktInfo.SettingsObjectName()) - require.NotNil(t, objMeta) - - err = tc.layer.PutBucketSettings(tc.ctx, &PutSettingsParams{ - BktInfo: tc.bktInfo, - Settings: &data.BucketSettings{VersioningEnabled: true}, - }) - require.NoError(t, err) - - cnrID, _ := objMeta.ContainerID() - objID, _ := objMeta.ID() - - var addr oid.Address - addr.SetContainer(cnrID) - addr.SetObject(objID) - - // simulate failed deletion - tc.testNeoFS.AddObject(addr.EncodeToString(), objMeta) - - versioning, err := tc.layer.GetBucketSettings(tc.ctx, tc.bktInfo) - require.NoError(t, err) - require.True(t, versioning.VersioningEnabled) -} - -func TestDeleteSystemObjectsVersioning(t *testing.T) { - cacheConfig := DefaultCachesConfigs(zap.NewExample()) - cacheConfig.System.Lifetime = 0 - - tc := prepareContext(t, cacheConfig) - - tagSet := map[string]string{ - "tag1": "val1", - } - - err := tc.layer.PutBucketTagging(tc.ctx, &tc.bktInfo.CID, tagSet) - require.NoError(t, err) - - objMeta := tc.getSystemObject(formBucketTagObjectName(tc.bktInfo.CID.EncodeToString())) - - tagSet["tag2"] = "val2" - err = tc.layer.PutBucketTagging(tc.ctx, &tc.bktInfo.CID, tagSet) - require.NoError(t, err) - - // simulate failed deletion - cnrID, _ := objMeta.ContainerID() - objID, _ := objMeta.ID() - tc.testNeoFS.AddObject(newAddress(cnrID, objID).EncodeToString(), objMeta) - - tagging, err := tc.layer.GetBucketTagging(tc.ctx, &tc.bktInfo.CID) - require.NoError(t, err) - - expectedTagSet := map[string]string{ - "tag1": "val1", - "tag2": "val2", - } - require.Equal(t, expectedTagSet, tagging) - - err = tc.layer.DeleteBucketTagging(tc.ctx, &tc.bktInfo.CID) - require.NoError(t, err) - - require.Nil(t, tc.getSystemObject(formBucketTagObjectName(tc.bktInfo.Name))) -}