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{
|
||||
BktInfo: bktInfo,
|
||||
Objects: versionedObject,
|
||||
BktInfo: bktInfo,
|
||||
Objects: versionedObject,
|
||||
Settings: bktSettings,
|
||||
}
|
||||
deletedObjects, err := h.obj.DeleteObjects(r.Context(), p)
|
||||
deletedObjects := h.obj.DeleteObjects(r.Context(), p)
|
||||
deletedObject := deletedObjects[0]
|
||||
if err == nil {
|
||||
err = deletedObject.Error
|
||||
}
|
||||
if err != nil {
|
||||
if isErrObjectLocked(err) {
|
||||
if deletedObject.Error != nil {
|
||||
if isErrObjectLocked(deletedObject.Error) {
|
||||
h.logAndSendError(w, "object is locked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
||||
return
|
||||
}
|
||||
|
@ -95,7 +93,7 @@ func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
zap.String("request_id", reqInfo.RequestID),
|
||||
zap.String("bucket_name", reqInfo.BucketName),
|
||||
zap.String("object_name", reqInfo.ObjectName),
|
||||
zap.Error(err))
|
||||
zap.Error(deletedObject.Error))
|
||||
}
|
||||
|
||||
var m *SendNotificationParams
|
||||
|
@ -198,6 +196,12 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
|
|||
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 {
|
||||
for _, obj := range toRemove {
|
||||
encoder.AppendString(obj.String())
|
||||
|
@ -206,14 +210,11 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
|
|||
})
|
||||
|
||||
p := &layer.DeleteObjectParams{
|
||||
BktInfo: bktInfo,
|
||||
Objects: toRemove,
|
||||
}
|
||||
deletedObjects, err := h.obj.DeleteObjects(r.Context(), p)
|
||||
if !requested.Quiet && err != nil {
|
||||
h.logAndSendError(w, "couldn't delete objects", reqInfo, err)
|
||||
return
|
||||
BktInfo: bktInfo,
|
||||
Objects: toRemove,
|
||||
Settings: bktSettings,
|
||||
}
|
||||
deletedObjects := h.obj.DeleteObjects(r.Context(), p)
|
||||
|
||||
var errs []error
|
||||
for _, obj := range deletedObjects {
|
||||
|
|
|
@ -1,142 +1,187 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
emptyVersion = ""
|
||||
)
|
||||
|
||||
func TestDeleteObject(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tc := prepareHandlerContext(t)
|
||||
|
||||
bktName := "bucket-for-removal"
|
||||
createTestBucket(ctx, t, tc, bktName)
|
||||
bktInfo, err := tc.Layer().GetBucketInfo(ctx, bktName)
|
||||
require.NoError(t, err)
|
||||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
bktInfo, objInfo := createBucketAndObject(t, tc, bktName, objName)
|
||||
|
||||
objName := "object"
|
||||
objInfo := createTestObject(ctx, t, tc, bktInfo, objName)
|
||||
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||
|
||||
w, r := prepareTestRequest(t, bktName, objName, nil)
|
||||
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)
|
||||
require.False(t, existInMockedNeoFS(tc, bktInfo, objInfo))
|
||||
}
|
||||
|
||||
func TestDeleteObjectVersioned(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tc := prepareHandlerContext(t)
|
||||
|
||||
bktName := "bucket-for-removal"
|
||||
createTestBucket(ctx, t, tc, bktName)
|
||||
bktInfo, err := tc.Layer().GetBucketInfo(ctx, bktName)
|
||||
require.NoError(t, err)
|
||||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
bktInfo, objInfo := createVersionedBucketAndObject(t, tc, bktName, objName)
|
||||
|
||||
cfg := &VersioningConfiguration{Status: "Enabled"}
|
||||
w, r := prepareTestRequest(t, bktName, "", cfg)
|
||||
tc.Handler().PutBucketVersioningHandler(w, r)
|
||||
assertStatus(t, w, http.StatusOK)
|
||||
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||
|
||||
objName := "object"
|
||||
objInfo := createTestObject(ctx, t, tc, bktInfo, objName)
|
||||
checkFound(t, tc, bktName, objName, objInfo.Version())
|
||||
deleteObject(t, tc, bktName, objName, objInfo.Version())
|
||||
checkNotFound(t, tc, bktName, objName, objInfo.Version())
|
||||
|
||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
||||
tc.Handler().HeadObjectHandler(w, r)
|
||||
assertStatus(t, w, http.StatusOK)
|
||||
require.False(t, existInMockedNeoFS(tc, bktInfo, objInfo), "object exists but shouldn't")
|
||||
}
|
||||
|
||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
||||
tc.Handler().DeleteObjectHandler(w, r)
|
||||
assertStatus(t, w, http.StatusNoContent)
|
||||
func TestRemoveDeleteMarker(t *testing.T) {
|
||||
tc := prepareHandlerContext(t)
|
||||
|
||||
w, r = prepareTestRequest(t, bktName, objName, nil)
|
||||
tc.Handler().HeadObjectHandler(w, r)
|
||||
assertStatus(t, w, http.StatusNotFound)
|
||||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
bktInfo, objInfo := createVersionedBucketAndObject(t, tc, bktName, objName)
|
||||
|
||||
query := make(url.Values)
|
||||
query.Add(api.QueryVersionID, objInfo.Version())
|
||||
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||
deleteMarkerVersion := deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||
|
||||
w, r = prepareTestFullRequest(t, bktName, objName, query, nil)
|
||||
tc.Handler().HeadObjectHandler(w, r)
|
||||
assertStatus(t, w, http.StatusOK)
|
||||
checkFound(t, tc, bktName, objName, objInfo.Version())
|
||||
deleteObject(t, tc, bktName, objName, deleteMarkerVersion)
|
||||
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||
|
||||
w, r = prepareTestFullRequest(t, bktName, objName, query, nil)
|
||||
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)
|
||||
require.True(t, existInMockedNeoFS(tc, bktInfo, objInfo), "object doesn't exist but should")
|
||||
}
|
||||
|
||||
func TestDeleteObjectCombined(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tc := prepareHandlerContext(t)
|
||||
|
||||
bktName := "bucket-for-removal"
|
||||
createTestBucket(ctx, t, tc, bktName)
|
||||
bktInfo, err := tc.Layer().GetBucketInfo(ctx, bktName)
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
objName := "object"
|
||||
objInfo := createTestObject(ctx, t, tc, bktInfo, objName)
|
||||
objInfo := createTestObject(tc.Context(), t, tc, bktInfo, objName)
|
||||
|
||||
w, r := prepareTestRequest(t, bktName, objName, nil)
|
||||
tc.Handler().HeadObjectHandler(w, r)
|
||||
assertStatus(t, w, http.StatusOK)
|
||||
return bktInfo, objInfo
|
||||
}
|
||||
|
||||
cfg := &VersioningConfiguration{Status: "Enabled"}
|
||||
w, r = prepareTestRequest(t, bktName, objName, cfg)
|
||||
func createVersionedBucketAndObject(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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
assertStatus(t, w, http.StatusNotFound)
|
||||
|
||||
query := make(url.Values)
|
||||
query.Add(api.QueryVersionID, objInfo.Version())
|
||||
|
||||
w, r = prepareTestFullRequest(t, bktName, objName, query, nil)
|
||||
tc.Handler().HeadObjectHandler(w, r)
|
||||
assertStatus(t, w, http.StatusNotFound) // because we remove null version
|
||||
|
||||
p := &layer.GetObjectParams{
|
||||
BucketInfo: bktInfo,
|
||||
ObjectInfo: objInfo,
|
||||
Writer: io.Discard,
|
||||
}
|
||||
err = tc.Layer().GetObject(ctx, p)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func checkFound(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)
|
||||
assertStatus(t, w, http.StatusOK)
|
||||
}
|
||||
|
||||
func listVersions(t *testing.T, tc *handlerContext, bktName string) *ListObjectsVersionsResponse {
|
||||
w, r := prepareTestRequest(t, bktName, "", nil)
|
||||
tc.Handler().ListBucketObjectVersionsHandler(w, r)
|
||||
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"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"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"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -27,8 +28,9 @@ import (
|
|||
)
|
||||
|
||||
type handlerContext struct {
|
||||
h *handler
|
||||
tp *layer.TestNeoFS
|
||||
h *handler
|
||||
tp *layer.TestNeoFS
|
||||
context context.Context
|
||||
}
|
||||
|
||||
func (hc *handlerContext) Handler() *handler {
|
||||
|
@ -43,6 +45,10 @@ func (hc *handlerContext) Layer() layer.Client {
|
|||
return hc.h.obj
|
||||
}
|
||||
|
||||
func (hc *handlerContext) Context() context.Context {
|
||||
return hc.context
|
||||
}
|
||||
|
||||
func prepareHandlerContext(t *testing.T) *handlerContext {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
@ -69,8 +75,9 @@ func prepareHandlerContext(t *testing.T) *handlerContext {
|
|||
}
|
||||
|
||||
return &handlerContext{
|
||||
h: h,
|
||||
tp: tp,
|
||||
h: h,
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue