Swift driver now bulk deletes in chunks specified by the server (#1915)
Swift driver now bulk deletes in chunks specified by the server Signed-off-by: Matthew Green <matthew.green@uk.ibm.com>
This commit is contained in:
parent
c24e10f70a
commit
dea554fc7c
2 changed files with 108 additions and 17 deletions
|
@ -91,6 +91,9 @@ type swiftInfo struct {
|
||||||
Tempurl struct {
|
Tempurl struct {
|
||||||
Methods []string `mapstructure:"methods"`
|
Methods []string `mapstructure:"methods"`
|
||||||
}
|
}
|
||||||
|
BulkDelete struct {
|
||||||
|
MaxDeletesPerRequest int `mapstructure:"max_deletes_per_request"`
|
||||||
|
} `mapstructure:"bulk_delete"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -109,6 +112,7 @@ type driver struct {
|
||||||
Container string
|
Container string
|
||||||
Prefix string
|
Prefix string
|
||||||
BulkDeleteSupport bool
|
BulkDeleteSupport bool
|
||||||
|
BulkDeleteMaxDeletes int
|
||||||
ChunkSize int
|
ChunkSize int
|
||||||
SecretKey string
|
SecretKey string
|
||||||
AccessKey string
|
AccessKey string
|
||||||
|
@ -221,6 +225,9 @@ func New(params Parameters) (*Driver, error) {
|
||||||
if err := mapstructure.Decode(config, &info); err == nil {
|
if err := mapstructure.Decode(config, &info); err == nil {
|
||||||
d.TempURLContainerKey = info.Swift.Version >= "2.3.0"
|
d.TempURLContainerKey = info.Swift.Version >= "2.3.0"
|
||||||
d.TempURLMethods = info.Tempurl.Methods
|
d.TempURLMethods = info.Tempurl.Methods
|
||||||
|
if d.BulkDeleteSupport {
|
||||||
|
d.BulkDeleteMaxDeletes = info.BulkDelete.MaxDeletesPerRequest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
d.TempURLContainerKey = params.TempURLContainerKey
|
d.TempURLContainerKey = params.TempURLContainerKey
|
||||||
|
@ -532,12 +539,18 @@ func (d *driver) Delete(ctx context.Context, path string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.BulkDeleteSupport && len(objects) > 0 {
|
if d.BulkDeleteSupport && len(objects) > 0 && d.BulkDeleteMaxDeletes > 0 {
|
||||||
filenames := make([]string, len(objects))
|
filenames := make([]string, len(objects))
|
||||||
for i, obj := range objects {
|
for i, obj := range objects {
|
||||||
filenames[i] = obj.Name
|
filenames[i] = obj.Name
|
||||||
}
|
}
|
||||||
_, err = d.Conn.BulkDelete(d.Container, filenames)
|
|
||||||
|
chunks, err := chunkFilenames(filenames, d.BulkDeleteMaxDeletes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, chunk := range chunks {
|
||||||
|
_, err := d.Conn.BulkDelete(d.Container, chunk)
|
||||||
// Don't fail on ObjectNotFound because eventual consistency
|
// Don't fail on ObjectNotFound because eventual consistency
|
||||||
// makes this situation normal.
|
// makes this situation normal.
|
||||||
if err != nil && err != swift.Forbidden && err != swift.ObjectNotFound {
|
if err != nil && err != swift.Forbidden && err != swift.ObjectNotFound {
|
||||||
|
@ -546,6 +559,7 @@ func (d *driver) Delete(ctx context.Context, path string) error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, obj := range objects {
|
for _, obj := range objects {
|
||||||
if err := d.Conn.ObjectDelete(d.Container, obj.Name); err != nil {
|
if err := d.Conn.ObjectDelete(d.Container, obj.Name); err != nil {
|
||||||
|
@ -712,6 +726,21 @@ func (d *driver) createManifest(path string, segments string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func chunkFilenames(slice []string, maxSize int) (chunks [][]string, err error) {
|
||||||
|
if maxSize > 0 {
|
||||||
|
for offset := 0; offset < len(slice); offset += maxSize {
|
||||||
|
chunkSize := maxSize
|
||||||
|
if offset+chunkSize > len(slice) {
|
||||||
|
chunkSize = len(slice) - offset
|
||||||
|
}
|
||||||
|
chunks = append(chunks, slice[offset:offset+chunkSize])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Max chunk size must be > 0")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func parseManifest(manifest string) (container string, prefix string) {
|
func parseManifest(manifest string) (container string, prefix string) {
|
||||||
components := strings.SplitN(manifest, "/", 2)
|
components := strings.SplitN(manifest, "/", 2)
|
||||||
container = components[0]
|
container = components[0]
|
||||||
|
|
|
@ -3,6 +3,7 @@ package swift
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -181,3 +182,64 @@ func TestEmptyRootList(t *testing.T) {
|
||||||
t.Fatal("delete did not remove nested objects")
|
t.Fatal("delete did not remove nested objects")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilenameChunking(t *testing.T) {
|
||||||
|
// Test valid input and sizes
|
||||||
|
input := []string{"a", "b", "c", "d", "e"}
|
||||||
|
expecteds := [][][]string{
|
||||||
|
{
|
||||||
|
{"a"},
|
||||||
|
{"b"},
|
||||||
|
{"c"},
|
||||||
|
{"d"},
|
||||||
|
{"e"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "b"},
|
||||||
|
{"c", "d"},
|
||||||
|
{"e"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "b", "c"},
|
||||||
|
{"d", "e"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "b", "c", "d"},
|
||||||
|
{"e"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "b", "c", "d", "e"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "b", "c", "d", "e"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, expected := range expecteds {
|
||||||
|
actual, err := chunkFilenames(input, i+1)
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("chunk %v didn't match expected value %v", actual, expected)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error chunking filenames: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test nil input
|
||||||
|
actual, err := chunkFilenames(nil, 5)
|
||||||
|
if len(actual) != 0 {
|
||||||
|
t.Fatal("chunks were returned when passed nil")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error chunking filenames: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 0 and < 0 sizes
|
||||||
|
actual, err = chunkFilenames(nil, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for size = 0")
|
||||||
|
}
|
||||||
|
actual, err = chunkFilenames(nil, -1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for size = -1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue