From 91cf54f895095dc0ec7ca6e41186790cac46876e Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Tue, 28 Feb 2017 19:07:28 -0800 Subject: [PATCH] web-handlers: Support removal of multiple objects at once. (#3810) --- appveyor.yml | 2 +- cmd/web-handlers.go | 86 ++++++++++++++++++++++++++++------------ cmd/web-handlers_test.go | 11 ++++- 3 files changed, 71 insertions(+), 28 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index cdb5719cb..5a87b661a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,7 +36,7 @@ test_script: # Unit tests - ps: Add-AppveyorTest "Unit Tests" -Outcome Running - mkdir build\coverage - - go test -timeout 15m -race github.com/minio/minio/cmd... + - go test -timeout 17m -race github.com/minio/minio/cmd... - go test -race github.com/minio/minio/pkg... - go test -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 420338cb0..0170adc6a 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -249,10 +249,12 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r } // RemoveObjectArgs - args to remove an object +// JSON will look like: +// '{"bucketname":"testbucket","prefix":"john/pics/","objects":["hawaii/","maldives/","sanjose.jpg"]}' type RemoveObjectArgs struct { - TargetHost string `json:"targetHost"` - BucketName string `json:"bucketName"` - ObjectName string `json:"objectName"` + Objects []string `json:"objects"` // can be files or sub-directories + Prefix string `json:"prefix"` // current directory in the browser-ui + BucketName string `json:"bucketname"` // bucket name. } // RemoveObject - removes an object. @@ -264,31 +266,65 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, if !isHTTPRequestValid(r) { return toJSONError(errAuthentication) } - - objectLock := globalNSMutex.NewNSLock(args.BucketName, args.ObjectName) - objectLock.Lock() - defer objectLock.Unlock() - - if err := objectAPI.DeleteObject(args.BucketName, args.ObjectName); err != nil { - if isErrObjectNotFound(err) { - // Ignore object not found error. - reply.UIVersion = browser.UIVersion - return nil + if args.BucketName == "" || len(args.Objects) == 0 { + return toJSONError(errUnexpected) + } + var err error +objectLoop: + for _, object := range args.Objects { + remove := func(objectName string) error { + objectLock := globalNSMutex.NewNSLock(args.BucketName, objectName) + objectLock.Lock() + defer objectLock.Unlock() + err = objectAPI.DeleteObject(args.BucketName, objectName) + if err == nil { + // Notify object deleted event. + eventNotify(eventData{ + Type: ObjectRemovedDelete, + Bucket: args.BucketName, + ObjInfo: ObjectInfo{ + Name: objectName, + }, + ReqParams: map[string]string{ + "sourceIPAddress": r.RemoteAddr, + }, + }) + } + return err + } + if !hasSuffix(object, slashSeparator) { + // If not a directory, compress the file and write it to response. + err = remove(pathJoin(args.Prefix, object)) + if err != nil { + break objectLoop + } + continue + } + // For directories, list the contents recursively and remove. + marker := "" + for { + var lo ListObjectsInfo + lo, err = objectAPI.ListObjects(args.BucketName, pathJoin(args.Prefix, object), marker, "", 1000) + if err != nil { + break objectLoop + } + marker = lo.NextMarker + for _, obj := range lo.Objects { + err = remove(obj.Name) + if err != nil { + break objectLoop + } + } + if !lo.IsTruncated { + break + } } - return toJSONError(err, args.BucketName, args.ObjectName) } - // Notify object deleted event. - eventNotify(eventData{ - Type: ObjectRemovedDelete, - Bucket: args.BucketName, - ObjInfo: ObjectInfo{ - Name: args.ObjectName, - }, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, - }) + if err != nil && !isErrObjectNotFound(err) { + // Ignore object not found error. + return toJSONError(err, args.BucketName, "") + } reply.UIVersion = browser.UIVersion return nil diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 912200624..15ec48a4f 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -468,7 +468,14 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH t.Fatalf("Was not able to upload an object, %v", err) } - removeObjectRequest := RemoveObjectArgs{BucketName: bucketName, ObjectName: objectName} + objectName = "a/object" + _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), + map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}, "") + if err != nil { + t.Fatalf("Was not able to upload an object, %v", err) + } + + removeObjectRequest := RemoveObjectArgs{BucketName: bucketName, Prefix: "", Objects: []string{"a/", "object"}} removeObjectReply := &WebGenericRep{} req, err := newTestWebRPCRequest("Web.RemoveObject", authorization, removeObjectRequest) if err != nil { @@ -483,7 +490,7 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH t.Fatalf("Failed, %v", err) } - removeObjectRequest = RemoveObjectArgs{BucketName: bucketName, ObjectName: objectName} + removeObjectRequest = RemoveObjectArgs{BucketName: bucketName, Prefix: "", Objects: []string{"a/", "object"}} removeObjectReply = &WebGenericRep{} req, err = newTestWebRPCRequest("Web.RemoveObject", authorization, removeObjectRequest) if err != nil {