From 51c5c227c26a2910ceae123c5d7bd854a44fbfab Mon Sep 17 00:00:00 2001
From: Pavel Pogodaev
Date: Fri, 19 Jul 2024 22:20:40 +0300
Subject: [PATCH] [#31] Add force bucket delete flag
Signed-off-by: Pavel Pogodaev
---
api/handler/delete.go | 9 +++++++
api/handler/delete_test.go | 25 ++++++++++++++++++
api/headers.go | 1 +
api/layer/layer.go | 21 ++++++++-------
docs/extensions.md | 52 ++++++++++++++++++++++++++++++++++++++
5 files changed, 99 insertions(+), 9 deletions(-)
create mode 100644 docs/extensions.md
diff --git a/api/handler/delete.go b/api/handler/delete.go
index 7fc081eb..dab05b1a 100644
--- a/api/handler/delete.go
+++ b/api/handler/delete.go
@@ -237,9 +237,18 @@ func (h *handler) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
sessionToken = boxData.Gate.SessionTokenForDelete()
}
+ skipObjCheck := false
+ if value, ok := r.Header[api.AmzForceBucketDelete]; ok {
+ s := value[0]
+ if s == "true" {
+ skipObjCheck = true
+ }
+ }
+
if err = h.obj.DeleteBucket(r.Context(), &layer.DeleteBucketParams{
BktInfo: bktInfo,
SessionToken: sessionToken,
+ SkipCheck: skipObjCheck,
}); err != nil {
h.logAndSendError(w, "couldn't delete bucket", reqInfo, err)
return
diff --git a/api/handler/delete_test.go b/api/handler/delete_test.go
index 2ad01544..2bf71e3a 100644
--- a/api/handler/delete_test.go
+++ b/api/handler/delete_test.go
@@ -85,6 +85,24 @@ func TestDeleteBucketOnNotFoundError(t *testing.T) {
deleteBucket(t, hc, bktName, http.StatusNoContent)
}
+func TestForceDeleteBucket(t *testing.T) {
+ hc := prepareHandlerContext(t)
+
+ bktName, objName := "bucket-for-removal", "object-to-delete"
+ bktInfo := createTestBucket(hc, bktName)
+
+ putObject(hc, bktName, objName)
+
+ nodeVersion, err := hc.tree.GetUnversioned(hc.context, bktInfo, objName)
+ require.NoError(t, err)
+ var addr oid.Address
+ addr.SetContainer(bktInfo.CID)
+ addr.SetObject(nodeVersion.OID)
+
+ deleteBucketForce(t, hc, bktName, http.StatusConflict, "false")
+ deleteBucketForce(t, hc, bktName, http.StatusNoContent, "true")
+}
+
func TestDeleteMultipleObjectCheckUniqueness(t *testing.T) {
hc := prepareHandlerContext(t)
@@ -517,6 +535,13 @@ func deleteObjectsBase(hc *handlerContext, bktName string, objVersions [][2]stri
return w
}
+func deleteBucketForce(t *testing.T, tc *handlerContext, bktName string, code int, value string) {
+ w, r := prepareTestRequest(tc, bktName, "", nil)
+ r.Header.Set(api.AmzForceBucketDelete, value)
+ tc.Handler().DeleteBucketHandler(w, r)
+ assertStatus(t, w, code)
+}
+
func deleteBucket(t *testing.T, tc *handlerContext, bktName string, code int) {
w, r := prepareTestRequest(tc, bktName, "", nil)
tc.Handler().DeleteBucketHandler(w, r)
diff --git a/api/headers.go b/api/headers.go
index a91149cf..540d3cc5 100644
--- a/api/headers.go
+++ b/api/headers.go
@@ -62,6 +62,7 @@ const (
AmzMaxParts = "X-Amz-Max-Parts"
AmzPartNumberMarker = "X-Amz-Part-Number-Marker"
AmzStorageClass = "X-Amz-Storage-Class"
+ AmzForceBucketDelete = "X-Amz-Force-Delete-Bucket"
AmzServerSideEncryptionCustomerAlgorithm = "x-amz-server-side-encryption-customer-algorithm"
AmzServerSideEncryptionCustomerKey = "x-amz-server-side-encryption-customer-key"
diff --git a/api/layer/layer.go b/api/layer/layer.go
index b62437cd..eddcda2d 100644
--- a/api/layer/layer.go
+++ b/api/layer/layer.go
@@ -170,6 +170,7 @@ type (
DeleteBucketParams struct {
BktInfo *data.BucketInfo
SessionToken *session.Container
+ SkipCheck bool
}
// ListObjectVersionsParams stores list objects versions parameters.
@@ -804,16 +805,18 @@ func (n *Layer) ResolveBucket(ctx context.Context, name string) (cid.ID, error)
}
func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
- res, _, err := n.getAllObjectsVersions(ctx, commonVersionsListingParams{
- BktInfo: p.BktInfo,
- MaxKeys: 1,
- })
+ if !p.SkipCheck {
+ res, _, err := n.getAllObjectsVersions(ctx, commonVersionsListingParams{
+ BktInfo: p.BktInfo,
+ MaxKeys: 1,
+ })
- if err != nil {
- return err
- }
- if len(res) != 0 {
- return errors.GetAPIError(errors.ErrBucketNotEmpty)
+ if err != nil {
+ return err
+ }
+ if len(res) != 0 {
+ return errors.GetAPIError(errors.ErrBucketNotEmpty)
+ }
}
n.cache.DeleteBucket(p.BktInfo)
diff --git a/docs/extensions.md b/docs/extensions.md
new file mode 100644
index 00000000..72cc93aa
--- /dev/null
+++ b/docs/extensions.md
@@ -0,0 +1,52 @@
+# S3 API Extension
+
+## Bucket operations management
+
+### Action to delete bucket (DeleteBucket)
+
+Deletes bucket with all objects in it.
+
+#### Request Parameters
+
+- **Bucket**
+
+ Specifies the bucket being deleted.
+
+
+#### Errors
+
+- **NoSuchEntity**
+
+ The request was rejected because it referenced a resource entity that does not exist.
+
+ HTTP Status Code: 404
+
+- **ServiceFailure**
+
+ The request processing has failed because of an unknown error, exception or failure.
+
+ HTTP Status Code: 500
+
+
+#### Example
+
+Sample Request
+
+```text
+DELETE / HTTP/1.1
+X-Amz-Force-Delete-Bucket: true
+Host: data.s3..frostfs-s3-gw.com
+Date: Wed, 01 Mar 2024 12:00:00 GMT
+Authorization: authorization string
+```
+
+Sample Response
+
+```text
+HTTP/1.1 204 No Content
+ x-amz-id-2: JuKZqmXuiwFeDQxhD7M8KtsKobSzWA1QEjLbTMTagkKdBX2z7Il/jGhDeJ3j6s80
+ x-amz-request-id: 32FE2CEB32F5EE25
+ Date: Wed, 01 Mar 2006 12:00:00 GMT
+ Connection: close
+ Server: AmazonS3
+```