diff options
| -rw-r--r-- | bulk.go | 2 | ||||
| -rw-r--r-- | largeobject.go | 23 | ||||
| -rw-r--r-- | object.go | 40 | ||||
| -rw-r--r-- | tests/largeobject_test.go | 71 | ||||
| -rw-r--r-- | tests/object_test.go | 4 | ||||
| -rw-r--r-- | tests/shared_test.go | 2 |
6 files changed, 129 insertions, 13 deletions
@@ -231,7 +231,7 @@ func (a *Account) bulkDeleteSingle(objects []*Object, containers []*Container, o } for _, obj := range objects { - err := obj.Delete(opts) //this implies Invalidate() + err := obj.Delete(nil, opts) //this implies Invalidate() err = handleSingleError(obj.Container().Name(), obj.Name(), err) if err != nil { return numDeleted, numNotFound, err diff --git a/largeobject.go b/largeobject.go index 493671d..4175a70 100644 --- a/largeobject.go +++ b/largeobject.go @@ -349,11 +349,7 @@ func (lo *LargeObject) Open(mode LargeObjectOpenMode) (io.WriteCloser, error) { if mode&OpenAppend == 0 { if mode&OpenKeepSegments == 0 { - segmentObjects := make([]*Object, len(lo.segments)) - for idx, segment := range lo.segments { - segmentObjects[idx] = segment.Object - } - _, _, err := lo.Object.c.a.BulkDelete(segmentObjects, nil, nil) + _, _, err := lo.Object.c.a.BulkDelete(lo.segmentObjects(), nil, nil) if err != nil { return nil, err } @@ -371,6 +367,23 @@ func (lo *LargeObject) Segments() ([]SegmentInfo, error) { return lo.segments, nil } +func (lo *LargeObject) segmentObjects() []*Object { + seen := make(map[string]bool) + result := make([]*Object, 0, len(lo.segments)) + for _, segment := range lo.segments { + if segment.Object == nil { //can happen because of data segments + continue + } + fullName := segment.Object.FullName() + if !seen[fullName] { + result = append(result, segment.Object) + } + seen[fullName] = true + } + + return result +} + //NextSegmentObject suggests where to upload the next segment. // //WARNING: This is a low-level function. Most callers will want to use the @@ -292,22 +292,54 @@ func (o *Object) UploadWithWriter(opts *RequestOptions, callback func(io.Writer) return <-errChan } +//DeleteOptions invokes advanced behavior in the Object.Delete() method. +type DeleteOptions struct { + //When deleting a large object, also delete its segments. This will cause + //Delete() to call into BulkDelete(), so a BulkError may be returned. + DeleteSegments bool +} + //Delete deletes the object using a DELETE request. To add URL parameters, //pass a non-nil *RequestOptions. // //This operation fails with http.StatusNotFound if the object does not exist. // //A successful DELETE request implies Invalidate(). -func (o *Object) Delete(opts *RequestOptions) error { +func (o *Object) Delete(opts *DeleteOptions, ropts *RequestOptions) error { + if opts == nil { + opts = &DeleteOptions{} + } + if opts.DeleteSegments { + exists, err := o.Exists() + if err != nil { + return err + } + if exists { + lo, err := o.AsLargeObject() + switch err { + case nil: + //is large object - delete segments and the object itself in one step + _, _, err := o.c.a.BulkDelete(append(lo.segmentObjects(), o), nil, nil) + o.Invalidate() + return err + case ErrNotLarge: + //not a large object - use regular DELETE request + default: + //unexpected error + return err + } + } + } + _, err := Request{ Method: "DELETE", ContainerName: o.c.name, ObjectName: o.name, - Options: opts, + Options: ropts, ExpectStatusCodes: []int{204}, }.Do(o.c.a.backend) if err == nil { - o.c.Invalidate() + o.Invalidate() } return err } @@ -393,5 +425,5 @@ func (o *Object) MoveTo(target *Object, copyOpts *RequestOptions, deleteOpts *Re if err != nil { return err } - return o.Delete(deleteOpts) + return o.Delete(nil, deleteOpts) } diff --git a/tests/largeobject_test.go b/tests/largeobject_test.go index 8e57e0d..758083d 100644 --- a/tests/largeobject_test.go +++ b/tests/largeobject_test.go @@ -300,6 +300,77 @@ func TestSLOGuessSegmentPrefix(t *testing.T) { }) } +func TestDeleteLargeObjectAndKeepSegments(t *testing.T) { + foreachLargeObjectStrategy(func(strategy schwift.LargeObjectStrategy, strategyStr string) { + testWithContainer(t, func(c *schwift.Container) { + obj := c.Object("largeobject") + + //setup phase: create an SLO + lo, err := obj.AsLargeObject() + expectSuccess(t, err) + lo.SegmentContainer = c + lo.SegmentPrefix = "foo/bar/baz/" + w, err := lo.Open(schwift.OpenTruncate) + expectSuccess(t, err) + + segment1 := getRandomSegmentContent(128) + segment2 := getRandomSegmentContent(128) + _, err = w.Write([]byte(segment1)) + expectSuccess(t, err) + _, err = w.Write([]byte(segment2)) + expectSuccess(t, err) + expectSuccess(t, w.Close()) + + //test deletion that keeps segments + expectSuccess(t, obj.Delete(nil, nil)) + + iter := c.Objects() + iter.Prefix = lo.SegmentPrefix + names, err := iter.Collect() + expectSuccess(t, err) + expectObjectNames(t, names, + "foo/bar/baz/0000000000000001", + "foo/bar/baz/0000000000000002") + }) + }) +} + +func TestDeleteLargeObjectIncludingSegments(t *testing.T) { + foreachLargeObjectStrategy(func(strategy schwift.LargeObjectStrategy, strategyStr string) { + testWithContainer(t, func(c *schwift.Container) { + obj := c.Object("largeobject") + + //setup phase: create an SLO + lo, err := obj.AsLargeObject() + expectSuccess(t, err) + lo.SegmentContainer = c + lo.SegmentPrefix = "foo/bar/baz/" + w, err := lo.Open(schwift.OpenTruncate) + expectSuccess(t, err) + + segment1 := getRandomSegmentContent(128) + segment2 := getRandomSegmentContent(128) + _, err = w.Write([]byte(segment1)) + expectSuccess(t, err) + _, err = w.Write([]byte(segment2)) + expectSuccess(t, err) + expectSuccess(t, w.Close()) + + //test deletion that keeps segments + expectSuccess(t, obj.Delete(&schwift.DeleteOptions{DeleteSegments: true}, nil)) + + iter := c.Objects() + iter.Prefix = lo.SegmentPrefix + names, err := iter.Collect() + expectSuccess(t, err) + expectObjectNames(t, names) + }) + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// helpers + func expectLargeObject(t *testing.T, obj *schwift.Object, expected []schwift.SegmentInfo) { t.Helper() expectObjectExistence(t, obj, true) diff --git a/tests/object_test.go b/tests/object_test.go index 39fdba2..6291527 100644 --- a/tests/object_test.go +++ b/tests/object_test.go @@ -48,7 +48,7 @@ func TestObjectLifecycle(t *testing.T) { //DELETE should be idempotent and not return success on non-existence, but //OpenStack LOVES to be inconsistent with everything (including, notably, itself) - err = o.Delete(nil) + err = o.Delete(nil, nil) expectError(t, err, "expected 204 response, got 404 instead: <html><h1>Not Found</h1><p>The resource could not be found.</p></html>") err = o.Upload(bytes.NewReader([]byte("test")), nil) @@ -56,7 +56,7 @@ func TestObjectLifecycle(t *testing.T) { expectObjectExistence(t, o, true) - err = o.Delete(nil) + err = o.Delete(nil, nil) expectSuccess(t, err) }) } diff --git a/tests/shared_test.go b/tests/shared_test.go index 7867d70..c6b15e4 100644 --- a/tests/shared_test.go +++ b/tests/shared_test.go @@ -99,7 +99,7 @@ func testWithContainer(t *testing.T, testCode func(c *schwift.Container)) { expectSuccess(t, err) if exists { expectSuccess(t, container.Objects().Foreach(func(o *schwift.Object) error { - return o.Delete(nil) + return o.Delete(nil, nil) })) err = container.Delete(nil) expectSuccess(t, err) |
