aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bulk.go2
-rw-r--r--largeobject.go23
-rw-r--r--object.go40
-rw-r--r--tests/largeobject_test.go71
-rw-r--r--tests/object_test.go4
-rw-r--r--tests/shared_test.go2
6 files changed, 129 insertions, 13 deletions
diff --git a/bulk.go b/bulk.go
index 42fb0b7..a0f458e 100644
--- a/bulk.go
+++ b/bulk.go
@@ -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
diff --git a/object.go b/object.go
index 4ff1f4f..69d2e95 100644
--- a/object.go
+++ b/object.go
@@ -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)