forked from TrueCloudLab/frostfs-s3-gw
[#542] Fix object removal
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
88c392d024
commit
fdf0974679
5 changed files with 268 additions and 181 deletions
|
@ -78,16 +78,14 @@ func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &layer.DeleteObjectParams{
|
p := &layer.DeleteObjectParams{
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
Objects: versionedObject,
|
Objects: versionedObject,
|
||||||
|
Settings: bktSettings,
|
||||||
}
|
}
|
||||||
deletedObjects, err := h.obj.DeleteObjects(r.Context(), p)
|
deletedObjects := h.obj.DeleteObjects(r.Context(), p)
|
||||||
deletedObject := deletedObjects[0]
|
deletedObject := deletedObjects[0]
|
||||||
if err == nil {
|
if deletedObject.Error != nil {
|
||||||
err = deletedObject.Error
|
if isErrObjectLocked(deletedObject.Error) {
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if isErrObjectLocked(err) {
|
|
||||||
h.logAndSendError(w, "object is locked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
h.logAndSendError(w, "object is locked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -95,7 +93,7 @@ func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
zap.String("request_id", reqInfo.RequestID),
|
zap.String("request_id", reqInfo.RequestID),
|
||||||
zap.String("bucket_name", reqInfo.BucketName),
|
zap.String("bucket_name", reqInfo.BucketName),
|
||||||
zap.String("object_name", reqInfo.ObjectName),
|
zap.String("object_name", reqInfo.ObjectName),
|
||||||
zap.Error(err))
|
zap.Error(deletedObject.Error))
|
||||||
}
|
}
|
||||||
|
|
||||||
var m *SendNotificationParams
|
var m *SendNotificationParams
|
||||||
|
@ -198,6 +196,12 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bktSettings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
marshaler := zapcore.ArrayMarshalerFunc(func(encoder zapcore.ArrayEncoder) error {
|
marshaler := zapcore.ArrayMarshalerFunc(func(encoder zapcore.ArrayEncoder) error {
|
||||||
for _, obj := range toRemove {
|
for _, obj := range toRemove {
|
||||||
encoder.AppendString(obj.String())
|
encoder.AppendString(obj.String())
|
||||||
|
@ -206,14 +210,11 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
|
||||||
})
|
})
|
||||||
|
|
||||||
p := &layer.DeleteObjectParams{
|
p := &layer.DeleteObjectParams{
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
Objects: toRemove,
|
Objects: toRemove,
|
||||||
}
|
Settings: bktSettings,
|
||||||
deletedObjects, err := h.obj.DeleteObjects(r.Context(), p)
|
|
||||||
if !requested.Quiet && err != nil {
|
|
||||||
h.logAndSendError(w, "couldn't delete objects", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
deletedObjects := h.obj.DeleteObjects(r.Context(), p)
|
||||||
|
|
||||||
var errs []error
|
var errs []error
|
||||||
for _, obj := range deletedObjects {
|
for _, obj := range deletedObjects {
|
||||||
|
|
|
@ -1,142 +1,187 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api"
|
"github.com/nspcc-dev/neofs-s3-gw/api"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
|
"github.com/nspcc-dev/neofs-s3-gw/api/data"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
emptyVersion = ""
|
||||||
|
)
|
||||||
|
|
||||||
func TestDeleteObject(t *testing.T) {
|
func TestDeleteObject(t *testing.T) {
|
||||||
ctx := context.Background()
|
|
||||||
tc := prepareHandlerContext(t)
|
tc := prepareHandlerContext(t)
|
||||||
|
|
||||||
bktName := "bucket-for-removal"
|
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||||
createTestBucket(ctx, t, tc, bktName)
|
bktInfo, objInfo := createBucketAndObject(t, tc, bktName, objName)
|
||||||
bktInfo, err := tc.Layer().GetBucketInfo(ctx, bktName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
objName := "object"
|
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||||
objInfo := createTestObject(ctx, t, tc, bktInfo, objName)
|
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
|
|
||||||
w, r := prepareTestRequest(t, bktName, objName, nil)
|
require.False(t, existInMockedNeoFS(tc, bktInfo, objInfo))
|
||||||
tc.Handler().HeadObjectHandler(w, r)
|
|
||||||
assertStatus(t, w, http.StatusOK)
|
|
||||||
|
|
||||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
|
||||||
tc.Handler().DeleteObjectHandler(w, r)
|
|
||||||
assertStatus(t, w, http.StatusNoContent)
|
|
||||||
|
|
||||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
|
||||||
tc.Handler().HeadObjectHandler(w, r)
|
|
||||||
assertStatus(t, w, http.StatusNotFound)
|
|
||||||
|
|
||||||
p := &layer.GetObjectParams{
|
|
||||||
BucketInfo: bktInfo,
|
|
||||||
ObjectInfo: objInfo,
|
|
||||||
Writer: io.Discard,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tc.Layer().GetObject(ctx, p)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteObjectVersioned(t *testing.T) {
|
func TestDeleteObjectVersioned(t *testing.T) {
|
||||||
ctx := context.Background()
|
|
||||||
tc := prepareHandlerContext(t)
|
tc := prepareHandlerContext(t)
|
||||||
|
|
||||||
bktName := "bucket-for-removal"
|
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||||
createTestBucket(ctx, t, tc, bktName)
|
bktInfo, objInfo := createVersionedBucketAndObject(t, tc, bktName, objName)
|
||||||
bktInfo, err := tc.Layer().GetBucketInfo(ctx, bktName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cfg := &VersioningConfiguration{Status: "Enabled"}
|
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||||
w, r := prepareTestRequest(t, bktName, "", cfg)
|
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
tc.Handler().PutBucketVersioningHandler(w, r)
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
assertStatus(t, w, http.StatusOK)
|
|
||||||
|
|
||||||
objName := "object"
|
checkFound(t, tc, bktName, objName, objInfo.Version())
|
||||||
objInfo := createTestObject(ctx, t, tc, bktInfo, objName)
|
deleteObject(t, tc, bktName, objName, objInfo.Version())
|
||||||
|
checkNotFound(t, tc, bktName, objName, objInfo.Version())
|
||||||
|
|
||||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
require.False(t, existInMockedNeoFS(tc, bktInfo, objInfo), "object exists but shouldn't")
|
||||||
tc.Handler().HeadObjectHandler(w, r)
|
}
|
||||||
assertStatus(t, w, http.StatusOK)
|
|
||||||
|
|
||||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
func TestRemoveDeleteMarker(t *testing.T) {
|
||||||
tc.Handler().DeleteObjectHandler(w, r)
|
tc := prepareHandlerContext(t)
|
||||||
assertStatus(t, w, http.StatusNoContent)
|
|
||||||
|
|
||||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||||
tc.Handler().HeadObjectHandler(w, r)
|
bktInfo, objInfo := createVersionedBucketAndObject(t, tc, bktName, objName)
|
||||||
assertStatus(t, w, http.StatusNotFound)
|
|
||||||
|
|
||||||
query := make(url.Values)
|
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||||
query.Add(api.QueryVersionID, objInfo.Version())
|
deleteMarkerVersion := deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
|
|
||||||
w, r = prepareTestFullRequest(t, bktName, objName, query, nil)
|
checkFound(t, tc, bktName, objName, objInfo.Version())
|
||||||
tc.Handler().HeadObjectHandler(w, r)
|
deleteObject(t, tc, bktName, objName, deleteMarkerVersion)
|
||||||
assertStatus(t, w, http.StatusOK)
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
|
|
||||||
w, r = prepareTestFullRequest(t, bktName, objName, query, nil)
|
require.True(t, existInMockedNeoFS(tc, bktInfo, objInfo), "object doesn't exist but should")
|
||||||
r.URL.RawQuery = query.Encode()
|
|
||||||
tc.Handler().DeleteObjectHandler(w, r)
|
|
||||||
assertStatus(t, w, http.StatusNoContent)
|
|
||||||
|
|
||||||
p := &layer.GetObjectParams{
|
|
||||||
BucketInfo: bktInfo,
|
|
||||||
ObjectInfo: objInfo,
|
|
||||||
Writer: io.Discard,
|
|
||||||
}
|
|
||||||
err = tc.Layer().GetObject(ctx, p)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteObjectCombined(t *testing.T) {
|
func TestDeleteObjectCombined(t *testing.T) {
|
||||||
ctx := context.Background()
|
|
||||||
tc := prepareHandlerContext(t)
|
tc := prepareHandlerContext(t)
|
||||||
|
|
||||||
bktName := "bucket-for-removal"
|
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||||
createTestBucket(ctx, t, tc, bktName)
|
bktInfo, objInfo := createBucketAndObject(t, tc, bktName, objName)
|
||||||
bktInfo, err := tc.Layer().GetBucketInfo(ctx, bktName)
|
|
||||||
|
putBucketVersioning(t, tc, bktName, true)
|
||||||
|
|
||||||
|
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||||
|
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
|
|
||||||
|
checkFound(t, tc, bktName, objName, objInfo.Version())
|
||||||
|
|
||||||
|
require.True(t, existInMockedNeoFS(tc, bktInfo, objInfo), "object doesn't exist but should")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteObjectSuspended(t *testing.T) {
|
||||||
|
tc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||||
|
bktInfo, objInfo := createBucketAndObject(t, tc, bktName, objName)
|
||||||
|
|
||||||
|
putBucketVersioning(t, tc, bktName, true)
|
||||||
|
|
||||||
|
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||||
|
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
|
|
||||||
|
putBucketVersioning(t, tc, bktName, false)
|
||||||
|
|
||||||
|
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
|
checkNotFound(t, tc, bktName, objName, objInfo.Version())
|
||||||
|
|
||||||
|
require.False(t, existInMockedNeoFS(tc, bktInfo, objInfo), "object exists but shouldn't")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteMarkers(t *testing.T) {
|
||||||
|
tc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||||
|
createTestBucket(tc.Context(), t, tc, bktName)
|
||||||
|
putBucketVersioning(t, tc, bktName, true)
|
||||||
|
|
||||||
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
|
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
|
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
|
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||||
|
|
||||||
|
versions := listVersions(t, tc, bktName)
|
||||||
|
require.Len(t, versions.DeleteMarker, 3, "invalid delete markers length")
|
||||||
|
require.Len(t, versions.Version, 0, "versions must be empty")
|
||||||
|
|
||||||
|
require.Len(t, listOIDsFromMockedNeoFS(t, tc, bktName, objName), 0, "shouldn't be any object in neofs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createBucketAndObject(t *testing.T, tc *handlerContext, bktName, objName string) (*data.BucketInfo, *data.ObjectInfo) {
|
||||||
|
createTestBucket(tc.Context(), t, tc, bktName)
|
||||||
|
bktInfo, err := tc.Layer().GetBucketInfo(tc.Context(), bktName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
objName := "object"
|
objInfo := createTestObject(tc.Context(), t, tc, bktInfo, objName)
|
||||||
objInfo := createTestObject(ctx, t, tc, bktInfo, objName)
|
|
||||||
|
|
||||||
w, r := prepareTestRequest(t, bktName, objName, nil)
|
return bktInfo, objInfo
|
||||||
tc.Handler().HeadObjectHandler(w, r)
|
}
|
||||||
assertStatus(t, w, http.StatusOK)
|
|
||||||
|
|
||||||
cfg := &VersioningConfiguration{Status: "Enabled"}
|
func createVersionedBucketAndObject(t *testing.T, tc *handlerContext, bktName, objName string) (*data.BucketInfo, *data.ObjectInfo) {
|
||||||
w, r = prepareTestRequest(t, bktName, objName, cfg)
|
createTestBucket(tc.Context(), t, tc, bktName)
|
||||||
|
bktInfo, err := tc.Layer().GetBucketInfo(tc.Context(), bktName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
putBucketVersioning(t, tc, bktName, true)
|
||||||
|
|
||||||
|
objInfo := createTestObject(tc.Context(), t, tc, bktInfo, objName)
|
||||||
|
|
||||||
|
return bktInfo, objInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func putBucketVersioning(t *testing.T, tc *handlerContext, bktName string, enabled bool) {
|
||||||
|
cfg := &VersioningConfiguration{Status: "Suspended"}
|
||||||
|
if enabled {
|
||||||
|
cfg.Status = "Enabled"
|
||||||
|
}
|
||||||
|
w, r := prepareTestRequest(t, bktName, "", cfg)
|
||||||
tc.Handler().PutBucketVersioningHandler(w, r)
|
tc.Handler().PutBucketVersioningHandler(w, r)
|
||||||
assertStatus(t, w, http.StatusOK)
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
func deleteObject(t *testing.T, tc *handlerContext, bktName, objName, version string) string {
|
||||||
|
query := make(url.Values)
|
||||||
|
query.Add(api.QueryVersionID, version)
|
||||||
|
|
||||||
|
w, r := prepareTestFullRequest(t, bktName, objName, query, nil)
|
||||||
tc.Handler().DeleteObjectHandler(w, r)
|
tc.Handler().DeleteObjectHandler(w, r)
|
||||||
assertStatus(t, w, http.StatusNoContent)
|
assertStatus(t, w, http.StatusNoContent)
|
||||||
|
|
||||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
return w.Header().Get(api.AmzVersionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNotFound(t *testing.T, tc *handlerContext, bktName, objName, version string) {
|
||||||
|
query := make(url.Values)
|
||||||
|
query.Add(api.QueryVersionID, version)
|
||||||
|
|
||||||
|
w, r := prepareTestFullRequest(t, bktName, objName, query, nil)
|
||||||
tc.Handler().HeadObjectHandler(w, r)
|
tc.Handler().HeadObjectHandler(w, r)
|
||||||
assertStatus(t, w, http.StatusNotFound)
|
assertStatus(t, w, http.StatusNotFound)
|
||||||
|
}
|
||||||
query := make(url.Values)
|
|
||||||
query.Add(api.QueryVersionID, objInfo.Version())
|
func checkFound(t *testing.T, tc *handlerContext, bktName, objName, version string) {
|
||||||
|
query := make(url.Values)
|
||||||
w, r = prepareTestFullRequest(t, bktName, objName, query, nil)
|
query.Add(api.QueryVersionID, version)
|
||||||
tc.Handler().HeadObjectHandler(w, r)
|
|
||||||
assertStatus(t, w, http.StatusNotFound) // because we remove null version
|
w, r := prepareTestFullRequest(t, bktName, objName, query, nil)
|
||||||
|
tc.Handler().HeadObjectHandler(w, r)
|
||||||
p := &layer.GetObjectParams{
|
assertStatus(t, w, http.StatusOK)
|
||||||
BucketInfo: bktInfo,
|
}
|
||||||
ObjectInfo: objInfo,
|
|
||||||
Writer: io.Discard,
|
func listVersions(t *testing.T, tc *handlerContext, bktName string) *ListObjectsVersionsResponse {
|
||||||
}
|
w, r := prepareTestRequest(t, bktName, "", nil)
|
||||||
err = tc.Layer().GetObject(ctx, p)
|
tc.Handler().ListBucketObjectVersionsHandler(w, r)
|
||||||
require.Error(t, err)
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
res := &ListObjectsVersionsResponse{}
|
||||||
|
parseTestResponse(t, w, res)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api/resolver"
|
"github.com/nspcc-dev/neofs-s3-gw/api/resolver"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
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/object"
|
||||||
|
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -27,8 +28,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type handlerContext struct {
|
type handlerContext struct {
|
||||||
h *handler
|
h *handler
|
||||||
tp *layer.TestNeoFS
|
tp *layer.TestNeoFS
|
||||||
|
context context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *handlerContext) Handler() *handler {
|
func (hc *handlerContext) Handler() *handler {
|
||||||
|
@ -43,6 +45,10 @@ func (hc *handlerContext) Layer() layer.Client {
|
||||||
return hc.h.obj
|
return hc.h.obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hc *handlerContext) Context() context.Context {
|
||||||
|
return hc.context
|
||||||
|
}
|
||||||
|
|
||||||
func prepareHandlerContext(t *testing.T) *handlerContext {
|
func prepareHandlerContext(t *testing.T) *handlerContext {
|
||||||
key, err := keys.NewPrivateKey()
|
key, err := keys.NewPrivateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -69,8 +75,9 @@ func prepareHandlerContext(t *testing.T) *handlerContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &handlerContext{
|
return &handlerContext{
|
||||||
h: h,
|
h: h,
|
||||||
tp: tp,
|
tp: tp,
|
||||||
|
context: context.Background(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,3 +174,29 @@ func parseTestResponse(t *testing.T, response *httptest.ResponseRecorder, body i
|
||||||
err := xml.NewDecoder(response.Result().Body).Decode(body)
|
err := xml.NewDecoder(response.Result().Body).Decode(body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func existInMockedNeoFS(tc *handlerContext, bktInfo *data.BucketInfo, objInfo *data.ObjectInfo) bool {
|
||||||
|
p := &layer.GetObjectParams{
|
||||||
|
BucketInfo: bktInfo,
|
||||||
|
ObjectInfo: objInfo,
|
||||||
|
Writer: io.Discard,
|
||||||
|
}
|
||||||
|
|
||||||
|
return tc.Layer().GetObject(tc.Context(), p) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listOIDsFromMockedNeoFS(t *testing.T, tc *handlerContext, bktName, objectName string) []oid.ID {
|
||||||
|
bktInfo, err := tc.Layer().GetBucketInfo(tc.Context(), bktName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
p := layer.PrmObjectSelect{
|
||||||
|
Container: bktInfo.CID,
|
||||||
|
ExactAttribute: [2]string{
|
||||||
|
object.AttributeFileName, objectName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ids, err := tc.MockedPool().SelectObjects(tc.Context(), p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
|
@ -115,8 +115,9 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteObjectParams struct {
|
DeleteObjectParams struct {
|
||||||
BktInfo *data.BucketInfo
|
BktInfo *data.BucketInfo
|
||||||
Objects []*VersionedObject
|
Objects []*VersionedObject
|
||||||
|
Settings *data.BucketSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutSettingsParams stores object copy request parameters.
|
// PutSettingsParams stores object copy request parameters.
|
||||||
|
@ -236,7 +237,7 @@ type (
|
||||||
ListObjectsV2(ctx context.Context, p *ListObjectsParamsV2) (*ListObjectsInfoV2, error)
|
ListObjectsV2(ctx context.Context, p *ListObjectsParamsV2) (*ListObjectsInfoV2, error)
|
||||||
ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error)
|
ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error)
|
||||||
|
|
||||||
DeleteObjects(ctx context.Context, p *DeleteObjectParams) ([]*VersionedObject, error)
|
DeleteObjects(ctx context.Context, p *DeleteObjectParams) []*VersionedObject
|
||||||
|
|
||||||
CreateMultipartUpload(ctx context.Context, p *CreateMultipartParams) error
|
CreateMultipartUpload(ctx context.Context, p *CreateMultipartParams) error
|
||||||
CompleteMultipartUpload(ctx context.Context, p *CompleteMultipartParams) (*UploadData, *data.ObjectInfo, error)
|
CompleteMultipartUpload(ctx context.Context, p *CompleteMultipartParams) (*UploadData, *data.ObjectInfo, error)
|
||||||
|
@ -473,55 +474,10 @@ func getRandomOID() (oid.ID, error) {
|
||||||
return objID, nil
|
return objID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObject removes all objects with the passed nice name.
|
func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, settings *data.BucketSettings, obj *VersionedObject) *VersionedObject {
|
||||||
func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, obj *VersionedObject) *VersionedObject {
|
if len(obj.VersionID) != 0 {
|
||||||
if len(obj.VersionID) == 0 {
|
var nodeVersion *data.NodeVersion
|
||||||
obj.VersionID = UnversionedObjectVersionID
|
if nodeVersion, obj.Error = n.getNodeVersionToDelete(ctx, bkt, obj); obj.Error != nil {
|
||||||
}
|
|
||||||
objVersion := &ObjectVersion{
|
|
||||||
BktInfo: bkt,
|
|
||||||
ObjectName: obj.Name,
|
|
||||||
VersionID: obj.VersionID,
|
|
||||||
NoErrorOnDeleteMarker: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeVersion *data.NodeVersion
|
|
||||||
nodeVersion, obj.Error = n.getNodeVersion(ctx, objVersion)
|
|
||||||
|
|
||||||
if obj.VersionID == UnversionedObjectVersionID {
|
|
||||||
if obj.Error == nil {
|
|
||||||
if obj.DeleteMarkVersion, obj.Error = n.removeOldVersion(ctx, bkt, nodeVersion, obj); obj.Error != nil {
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
} else if !errors.IsS3Error(obj.Error, errors.ErrNoSuchKey) {
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
randOID, err := getRandomOID()
|
|
||||||
if err != nil {
|
|
||||||
obj.Error = fmt.Errorf("couldn't get random oid: %w", err)
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
newVersion := &data.NodeVersion{
|
|
||||||
BaseNodeVersion: data.BaseNodeVersion{
|
|
||||||
OID: randOID,
|
|
||||||
FilePath: obj.Name,
|
|
||||||
},
|
|
||||||
DeleteMarker: &data.DeleteMarkerInfo{
|
|
||||||
Created: time.Now(),
|
|
||||||
Owner: n.Owner(ctx),
|
|
||||||
},
|
|
||||||
IsUnversioned: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.Error = n.treeService.AddVersion(ctx, bkt.CID, newVersion); obj.Error != nil {
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
n.namesCache.Delete(bkt.Name + "/" + obj.Name)
|
|
||||||
} else {
|
|
||||||
if obj.Error != nil {
|
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,16 +485,65 @@ func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, obj *Ver
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.Error = n.treeService.RemoveVersion(ctx, bkt.CID, nodeVersion.ID); obj.Error != nil {
|
obj.Error = n.treeService.RemoveVersion(ctx, bkt.CID, nodeVersion.ID)
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVersion *data.NodeVersion
|
||||||
|
|
||||||
|
if !settings.VersioningEnabled {
|
||||||
|
obj.VersionID = UnversionedObjectVersionID
|
||||||
|
|
||||||
|
var nodeVersion *data.NodeVersion
|
||||||
|
if nodeVersion, obj.Error = n.getNodeVersionToDelete(ctx, bkt, obj); obj.Error != nil {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Error == nil {
|
||||||
|
if obj.DeleteMarkVersion, obj.Error = n.removeOldVersion(ctx, bkt, nodeVersion, obj); obj.Error != nil {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
} else if !errors.IsS3Error(obj.Error, errors.ErrNoSuchKey) {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n.listsCache.CleanCacheEntriesContainingObject(obj.Name, bkt.CID)
|
randOID, err := getRandomOID()
|
||||||
|
if err != nil {
|
||||||
|
obj.Error = fmt.Errorf("couldn't get random oid: %w", err)
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
newVersion = &data.NodeVersion{
|
||||||
|
BaseNodeVersion: data.BaseNodeVersion{
|
||||||
|
OID: randOID,
|
||||||
|
FilePath: obj.Name,
|
||||||
|
},
|
||||||
|
DeleteMarker: &data.DeleteMarkerInfo{
|
||||||
|
Created: time.Now(),
|
||||||
|
Owner: n.Owner(ctx),
|
||||||
|
},
|
||||||
|
IsUnversioned: !settings.VersioningEnabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Error = n.treeService.AddVersion(ctx, bkt.CID, newVersion); obj.Error != nil {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
n.namesCache.Delete(bkt.Name + "/" + obj.Name)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *layer) getNodeVersionToDelete(ctx context.Context, bkt *data.BucketInfo, obj *VersionedObject) (*data.NodeVersion, error) {
|
||||||
|
objVersion := &ObjectVersion{
|
||||||
|
BktInfo: bkt,
|
||||||
|
ObjectName: obj.Name,
|
||||||
|
VersionID: obj.VersionID,
|
||||||
|
NoErrorOnDeleteMarker: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.getNodeVersion(ctx, objVersion)
|
||||||
|
}
|
||||||
|
|
||||||
func (n *layer) removeOldVersion(ctx context.Context, bkt *data.BucketInfo, nodeVersion *data.NodeVersion, obj *VersionedObject) (string, error) {
|
func (n *layer) removeOldVersion(ctx context.Context, bkt *data.BucketInfo, nodeVersion *data.NodeVersion, obj *VersionedObject) (string, error) {
|
||||||
if nodeVersion.DeleteMarker != nil {
|
if nodeVersion.DeleteMarker != nil {
|
||||||
return obj.VersionID, nil
|
return obj.VersionID, nil
|
||||||
|
@ -548,12 +553,12 @@ func (n *layer) removeOldVersion(ctx context.Context, bkt *data.BucketInfo, node
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObjects from the storage.
|
// DeleteObjects from the storage.
|
||||||
func (n *layer) DeleteObjects(ctx context.Context, p *DeleteObjectParams) ([]*VersionedObject, error) {
|
func (n *layer) DeleteObjects(ctx context.Context, p *DeleteObjectParams) []*VersionedObject {
|
||||||
for i, obj := range p.Objects {
|
for i, obj := range p.Objects {
|
||||||
p.Objects[i] = n.deleteObject(ctx, p.BktInfo, obj)
|
p.Objects[i] = n.deleteObject(ctx, p.BktInfo, p.Settings, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.Objects, nil
|
return p.Objects
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *layer) CreateBucket(ctx context.Context, p *CreateBucketParams) (*data.BucketInfo, error) {
|
func (n *layer) CreateBucket(ctx context.Context, p *CreateBucketParams) (*data.BucketInfo, error) {
|
||||||
|
|
|
@ -53,15 +53,15 @@ func (tc *testContext) getObject(objectName, versionID string, needError bool) (
|
||||||
return extendedInfo.ObjectInfo, content.Bytes()
|
return extendedInfo.ObjectInfo, content.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *testContext) deleteObject(objectName, versionID string) {
|
func (tc *testContext) deleteObject(objectName, versionID string, settings *data.BucketSettings) {
|
||||||
p := &DeleteObjectParams{
|
p := &DeleteObjectParams{
|
||||||
BktInfo: tc.bktInfo,
|
BktInfo: tc.bktInfo,
|
||||||
|
Settings: settings,
|
||||||
Objects: []*VersionedObject{
|
Objects: []*VersionedObject{
|
||||||
{Name: objectName, VersionID: versionID},
|
{Name: objectName, VersionID: versionID},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
deletedObjects, err := tc.layer.DeleteObjects(tc.ctx, p)
|
deletedObjects := tc.layer.DeleteObjects(tc.ctx, p)
|
||||||
require.NoError(tc.t, err)
|
|
||||||
for _, obj := range deletedObjects {
|
for _, obj := range deletedObjects {
|
||||||
require.NoError(tc.t, obj.Error)
|
require.NoError(tc.t, obj.Error)
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ func TestVersioningDeleteObject(t *testing.T) {
|
||||||
tc.putObject([]byte("content obj1 v1"))
|
tc.putObject([]byte("content obj1 v1"))
|
||||||
tc.putObject([]byte("content obj1 v2"))
|
tc.putObject([]byte("content obj1 v2"))
|
||||||
|
|
||||||
tc.deleteObject(tc.obj, "")
|
tc.deleteObject(tc.obj, "", settings)
|
||||||
tc.getObject(tc.obj, "", true)
|
tc.getObject(tc.obj, "", true)
|
||||||
|
|
||||||
tc.checkListObjects()
|
tc.checkListObjects()
|
||||||
|
@ -268,19 +268,19 @@ func TestVersioningDeleteSpecificObjectVersion(t *testing.T) {
|
||||||
objV3Content := []byte("content obj1 v3")
|
objV3Content := []byte("content obj1 v3")
|
||||||
objV3Info := tc.putObject(objV3Content)
|
objV3Info := tc.putObject(objV3Content)
|
||||||
|
|
||||||
tc.deleteObject(tc.obj, objV2Info.Version())
|
tc.deleteObject(tc.obj, objV2Info.Version(), settings)
|
||||||
tc.getObject(tc.obj, objV2Info.Version(), true)
|
tc.getObject(tc.obj, objV2Info.Version(), true)
|
||||||
|
|
||||||
_, buffer3 := tc.getObject(tc.obj, "", false)
|
_, buffer3 := tc.getObject(tc.obj, "", false)
|
||||||
require.Equal(t, objV3Content, buffer3)
|
require.Equal(t, objV3Content, buffer3)
|
||||||
|
|
||||||
tc.deleteObject(tc.obj, "")
|
tc.deleteObject(tc.obj, "", settings)
|
||||||
tc.getObject(tc.obj, "", true)
|
tc.getObject(tc.obj, "", true)
|
||||||
|
|
||||||
versions := tc.listVersions()
|
versions := tc.listVersions()
|
||||||
for _, ver := range versions.DeleteMarker {
|
for _, ver := range versions.DeleteMarker {
|
||||||
if ver.IsLatest {
|
if ver.IsLatest {
|
||||||
tc.deleteObject(tc.obj, ver.Object.Version())
|
tc.deleteObject(tc.obj, ver.Object.Version(), settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +295,10 @@ func TestNoVersioningDeleteObject(t *testing.T) {
|
||||||
tc.putObject([]byte("content obj1 v1"))
|
tc.putObject([]byte("content obj1 v1"))
|
||||||
tc.putObject([]byte("content obj1 v2"))
|
tc.putObject([]byte("content obj1 v2"))
|
||||||
|
|
||||||
tc.deleteObject(tc.obj, "")
|
settings, err := tc.layer.GetBucketSettings(tc.ctx, tc.bktInfo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tc.deleteObject(tc.obj, "", settings)
|
||||||
tc.getObject(tc.obj, "", true)
|
tc.getObject(tc.obj, "", true)
|
||||||
tc.checkListObjects()
|
tc.checkListObjects()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue