ee5f32b0f7
* ConcatRanger closes a segment readers as soon as data is read * Fix linter errors * Fix data race * Nicer call to CombineErrors
562 lines
16 KiB
Go
562 lines
16 KiB
Go
// Copyright (C) 2018 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package miniogw
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"path"
|
|
"testing"
|
|
"time"
|
|
|
|
"storj.io/storj/pkg/storage/meta"
|
|
"storj.io/storj/storage"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
minio "github.com/minio/minio/cmd"
|
|
"github.com/minio/minio/pkg/hash"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"storj.io/storj/pkg/paths"
|
|
"storj.io/storj/pkg/ranger"
|
|
"storj.io/storj/pkg/storage/buckets"
|
|
mock_buckets "storj.io/storj/pkg/storage/buckets/mocks"
|
|
"storj.io/storj/pkg/storage/objects"
|
|
)
|
|
|
|
var (
|
|
ctx = context.Background()
|
|
)
|
|
|
|
func TestCopyObject(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
mockOS := NewMockStore(ctrl)
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
for i, example := range []struct {
|
|
bucket, srcObject string
|
|
destObject string
|
|
data string
|
|
getErr error
|
|
putErr error
|
|
errString string
|
|
}{
|
|
// happy scenario
|
|
{"mybucket", "mySrcObj", "myDestObj", "abcdef", nil, nil, ""},
|
|
// error returned by the objects.Get()
|
|
{"mybucket", "mySrcObj", "myDestObj", "abcdef", errors.New("some Get err"), nil, "some Get err"},
|
|
// error returned by the objects.Put()
|
|
{"mybucket", "mySrcObj", "myDestObj", "abcdef", nil, errors.New("some Put err"), "some Put err"},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
|
|
metadata := map[string]string{
|
|
"content-type": "media/foo",
|
|
"userdef_key1": "userdef_val1",
|
|
"userdef_key2": "userdef_val2",
|
|
}
|
|
|
|
serMeta := objects.SerializableMeta{
|
|
ContentType: metadata["content-type"],
|
|
UserDefined: map[string]string{
|
|
"userdef_key1": metadata["userdef_key1"],
|
|
"userdef_key2": metadata["userdef_key2"],
|
|
},
|
|
}
|
|
|
|
meta := objects.Meta{
|
|
SerializableMeta: serMeta,
|
|
Modified: time.Time{},
|
|
Expiration: time.Time{},
|
|
Size: 1234,
|
|
Checksum: "test-checksum",
|
|
}
|
|
|
|
srcInfo := minio.ObjectInfo{
|
|
Bucket: example.bucket,
|
|
Name: example.srcObject,
|
|
Size: 1234,
|
|
ContentType: serMeta.ContentType,
|
|
UserDefined: serMeta.UserDefined,
|
|
}
|
|
|
|
rr := ranger.ByteRanger([]byte(example.data))
|
|
r, err := rr.Range(ctx, 0, rr.Size())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// if o.Get returns an error, only expect GetObjectStore once, do not expect Put
|
|
if example.errString != "some Get err" {
|
|
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil).Times(2)
|
|
mockOS.EXPECT().Get(gomock.Any(), paths.New(example.srcObject)).Return(rr, meta, example.getErr)
|
|
mockOS.EXPECT().Put(gomock.Any(), paths.New(example.destObject), r, serMeta, time.Time{}).Return(meta, example.putErr)
|
|
} else {
|
|
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
|
|
mockOS.EXPECT().Get(gomock.Any(), paths.New(example.srcObject)).Return(rr, meta, example.getErr)
|
|
}
|
|
|
|
objInfo, err := storjObj.CopyObject(ctx, example.bucket, example.srcObject, example.bucket, example.destObject, srcInfo)
|
|
|
|
if err != nil {
|
|
assert.EqualError(t, err, example.errString, errTag)
|
|
} else {
|
|
assert.NoError(t, err, errTag)
|
|
assert.NotNil(t, objInfo, errTag)
|
|
assert.Equal(t, example.bucket, objInfo.Bucket, errTag)
|
|
assert.Equal(t, example.destObject, objInfo.Name, errTag)
|
|
assert.Equal(t, meta.Modified, objInfo.ModTime, errTag)
|
|
assert.Equal(t, meta.Size, objInfo.Size, errTag)
|
|
assert.Equal(t, meta.Checksum, objInfo.ETag, errTag)
|
|
assert.Equal(t, meta.UserDefined, objInfo.UserDefined, errTag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetObject(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
mockOS := NewMockStore(ctrl)
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
meta := objects.Meta{}
|
|
|
|
for i, example := range []struct {
|
|
bucket, object string
|
|
data string
|
|
offset, length int64
|
|
substr string
|
|
err error
|
|
errString string
|
|
}{
|
|
// happy scenario
|
|
{"mybucket", "myobject1", "abcdef", 0, 5, "abcde", nil, ""},
|
|
// error returned by the ranger in the code
|
|
{"mybucket", "myobject1", "abcdef", -1, 7, "abcde", nil, "ranger error: negative offset"},
|
|
{"mybucket", "myobject1", "abcdef", 0, -1, "abcdef", nil, ""},
|
|
{"mybucket", "myobject1", "abcdef", 0, -2, "abcde", nil, "ranger error: negative length"},
|
|
{"mybucket", "myobject1", "abcdef", 1, 7, "bcde", nil, "ranger error: buffer runoff"},
|
|
// error returned by the objects.Get()
|
|
{"mybucket", "myobject1", "abcdef", 0, 6, "abcdef", errors.New("some err"), "some err"},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
|
|
rr := ranger.ByteRanger([]byte(example.data))
|
|
|
|
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
|
|
mockOS.EXPECT().Get(gomock.Any(), paths.New(example.object)).Return(rr, meta, example.err)
|
|
|
|
var buf bytes.Buffer
|
|
iowriter := io.Writer(&buf)
|
|
err := storjObj.GetObject(ctx, example.bucket, example.object, example.offset, example.length, iowriter, "etag")
|
|
|
|
if err != nil {
|
|
assert.EqualError(t, err, example.errString, errTag)
|
|
} else {
|
|
assert.Equal(t, example.substr, buf.String(), errTag)
|
|
assert.NoError(t, err, errTag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDeleteObject(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
mockOS := NewMockStore(ctrl)
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
for i, example := range []struct {
|
|
bucket, object string
|
|
err error
|
|
errString string
|
|
}{
|
|
// happy scenario
|
|
{"mybucket", "myobject1", nil, ""},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
|
|
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
|
|
mockOS.EXPECT().Delete(gomock.Any(), paths.New(example.object)).Return(example.err)
|
|
|
|
err := storjObj.DeleteObject(ctx, example.bucket, example.object)
|
|
assert.NoError(t, err, errTag)
|
|
}
|
|
}
|
|
|
|
func TestPutObject(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
mockOS := NewMockStore(ctrl)
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
data, err := hash.NewReader(bytes.NewReader([]byte("abcdefgiiuweriiwyrwyiywrywhti")),
|
|
int64(len("abcdefgiiuweriiwyrwyiywrywhti")),
|
|
"e2fc714c4727ee9395f324cd2e7f331f",
|
|
"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for i, example := range []struct {
|
|
bucket, object string
|
|
err error // used by mock function
|
|
errString string
|
|
}{
|
|
// happy scenario
|
|
{"mybucket", "myobject1", nil, ""},
|
|
// emulating objects.Put() returning err
|
|
{"mybucket", "myobject1", Error.New("some non nil error"), "Storj Gateway error: some non nil error"},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
|
|
metadata := map[string]string{
|
|
"content-type": "media/foo",
|
|
"userdef_key1": "userdef_val1",
|
|
"userdef_key2": "userdef_val2",
|
|
}
|
|
|
|
serMeta := objects.SerializableMeta{
|
|
ContentType: metadata["content-type"],
|
|
UserDefined: map[string]string{
|
|
"userdef_key1": metadata["userdef_key1"],
|
|
"userdef_key2": metadata["userdef_key2"],
|
|
},
|
|
}
|
|
|
|
meta := objects.Meta{
|
|
SerializableMeta: serMeta,
|
|
Modified: time.Now(),
|
|
Expiration: time.Time{},
|
|
Size: 1234,
|
|
Checksum: "test-checksum",
|
|
}
|
|
|
|
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
|
|
mockOS.EXPECT().Put(gomock.Any(), paths.New(example.object), data, serMeta, time.Time{}).Return(meta, example.err)
|
|
|
|
objInfo, err := storjObj.PutObject(ctx, example.bucket, example.object, data, metadata)
|
|
if err != nil {
|
|
assert.EqualError(t, err, example.errString, errTag)
|
|
} else {
|
|
assert.NoError(t, err, errTag)
|
|
}
|
|
|
|
assert.NotNil(t, objInfo, errTag)
|
|
assert.Equal(t, example.bucket, objInfo.Bucket, errTag)
|
|
assert.Equal(t, example.object, objInfo.Name, errTag)
|
|
assert.Equal(t, meta.Modified, objInfo.ModTime, errTag)
|
|
assert.Equal(t, meta.Size, objInfo.Size, errTag)
|
|
assert.Equal(t, meta.Checksum, objInfo.ETag, errTag)
|
|
assert.Equal(t, meta.UserDefined, objInfo.UserDefined, errTag)
|
|
|
|
}
|
|
}
|
|
|
|
func TestGetObjectInfo(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
mockOS := NewMockStore(ctrl)
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
meta := objects.Meta{
|
|
Modified: time.Now(),
|
|
Expiration: time.Time{},
|
|
Size: 1234,
|
|
Checksum: "test-checksum",
|
|
SerializableMeta: objects.SerializableMeta{
|
|
ContentType: "media/foo",
|
|
UserDefined: map[string]string{
|
|
"userdef_key1": "userdef_val1",
|
|
"userdef_key2": "userdef_val2",
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, example := range []struct {
|
|
bucket, object string
|
|
err error
|
|
errString string
|
|
}{
|
|
// happy scenario
|
|
{"mybucket", "myobject1", nil, ""},
|
|
// mock object.Meta function to return error
|
|
{"mybucket", "myobject1", Error.New("mock error"), "Storj Gateway error: mock error"},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
|
|
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
|
|
mockOS.EXPECT().Meta(gomock.Any(), paths.New(example.object)).Return(meta, example.err)
|
|
|
|
objInfo, err := storjObj.GetObjectInfo(ctx, example.bucket, example.object)
|
|
if err != nil {
|
|
assert.EqualError(t, err, example.errString, errTag)
|
|
if example.err != nil {
|
|
assert.Empty(t, objInfo, errTag)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err, errTag)
|
|
assert.NotNil(t, objInfo, errTag)
|
|
assert.Equal(t, example.bucket, objInfo.Bucket, errTag)
|
|
assert.Equal(t, example.object, objInfo.Name, errTag)
|
|
assert.Equal(t, meta.Modified, objInfo.ModTime, errTag)
|
|
assert.Equal(t, meta.Size, objInfo.Size, errTag)
|
|
assert.Equal(t, meta.Checksum, objInfo.ETag, errTag)
|
|
assert.Equal(t, meta.UserDefined, objInfo.UserDefined, errTag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestListObjects(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
mockOS := NewMockStore(ctrl)
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
bucket := "test-bucket"
|
|
prefix := "test-prefix"
|
|
maxKeys := 123
|
|
|
|
items := []objects.ListItem{
|
|
{Path: paths.New("test-file-1.txt")},
|
|
{Path: paths.New("test-file-2.txt")},
|
|
}
|
|
|
|
for i, example := range []struct {
|
|
more bool
|
|
startAfter string
|
|
nextMarker string
|
|
delimiter string
|
|
recursive bool
|
|
objInfos []minio.ObjectInfo
|
|
err error
|
|
errString string
|
|
}{
|
|
{
|
|
more: false, startAfter: "", nextMarker: "", delimiter: "", recursive: true,
|
|
objInfos: []minio.ObjectInfo{
|
|
{Bucket: bucket, Name: path.Join("test-prefix/test-file-1.txt")},
|
|
{Bucket: bucket, Name: path.Join("test-prefix/test-file-2.txt")},
|
|
}, err: nil, errString: "",
|
|
},
|
|
{
|
|
more: true, startAfter: "test-start-after", nextMarker: "test-file-2.txt", delimiter: "/", recursive: false,
|
|
objInfos: []minio.ObjectInfo{
|
|
{Bucket: bucket, Name: path.Join("test-file-1.txt")},
|
|
{Bucket: bucket, Name: path.Join("test-file-2.txt")},
|
|
}, err: nil, errString: "",
|
|
},
|
|
{
|
|
more: false, startAfter: "", nextMarker: "", delimiter: "", recursive: true,
|
|
objInfos: []minio.ObjectInfo{
|
|
{Bucket: bucket, Name: path.Join("test-prefix/test-file-1.txt")},
|
|
{Bucket: bucket, Name: path.Join("test-prefix/test-file-2.txt")},
|
|
}, err: Error.New("error"), errString: "Storj Gateway error: error",
|
|
},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
|
|
mockBS.EXPECT().GetObjectStore(gomock.Any(), bucket).Return(mockOS, nil)
|
|
mockOS.EXPECT().List(gomock.Any(), paths.New(prefix), paths.New(example.startAfter),
|
|
nil, example.recursive, maxKeys, meta.All).Return(items, example.more, example.err)
|
|
|
|
listInfo, err := storjObj.ListObjects(ctx, bucket, prefix, example.startAfter, example.delimiter, maxKeys)
|
|
|
|
if err != nil {
|
|
assert.EqualError(t, err, example.errString, errTag)
|
|
if example.err != nil {
|
|
assert.Empty(t, listInfo, errTag)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err, errTag)
|
|
assert.NotNil(t, listInfo, errTag)
|
|
assert.Equal(t, example.more, listInfo.IsTruncated, errTag)
|
|
assert.Equal(t, example.nextMarker, listInfo.NextMarker, errTag)
|
|
assert.Equal(t, example.objInfos, listInfo.Objects, errTag)
|
|
assert.Nil(t, listInfo.Prefixes, errTag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDeleteBucket(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockOS := NewMockStore(ctrl)
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
itemsInBucket := make([]objects.ListItem, 1)
|
|
itemsInBucket[0] = objects.ListItem{Path: paths.New("path1"), Meta: objects.Meta{}}
|
|
|
|
exp := time.Unix(0, 0).UTC()
|
|
var noItemsInBucket []objects.ListItem
|
|
|
|
for i, example := range []struct {
|
|
bucket string
|
|
items []objects.ListItem
|
|
bucketStatus error
|
|
err error
|
|
errString string
|
|
}{
|
|
{"mybucket", noItemsInBucket, nil, nil, ""},
|
|
{"mybucket", noItemsInBucket, storage.ErrKeyNotFound.New("mybucket"), nil, "Bucket not found: mybucket"},
|
|
{"mybucket", itemsInBucket, nil, minio.BucketNotEmpty{Bucket: "mybucket"}, "Bucket not empty: mybucket"},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
mockBS.EXPECT().Get(gomock.Any(), gomock.Any()).Return(buckets.Meta{Created: exp}, example.bucketStatus)
|
|
if !storage.ErrKeyNotFound.Has(example.bucketStatus) {
|
|
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
|
|
mockOS.EXPECT().List(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
|
|
gomock.Any(), gomock.Any(), gomock.Any()).Return(example.items, false, example.err)
|
|
if len(example.items) == 0 {
|
|
mockBS.EXPECT().Delete(gomock.Any(), example.bucket).Return(example.err)
|
|
}
|
|
}
|
|
|
|
err := storjObj.DeleteBucket(ctx, example.bucket)
|
|
if err != nil {
|
|
assert.EqualError(t, err, example.errString, errTag)
|
|
} else {
|
|
assert.NoError(t, err, errTag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetBucketInfo(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
exp := time.Unix(0, 0).UTC()
|
|
|
|
for i, example := range []struct {
|
|
bucket string
|
|
meta time.Time
|
|
err error
|
|
errString string
|
|
}{
|
|
// happy scenario
|
|
{"mybucket", exp, nil, ""},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
|
|
mockBS.EXPECT().Get(gomock.Any(), example.bucket).Return(buckets.Meta{Created: example.meta}, example.err)
|
|
|
|
_, err := storjObj.GetBucketInfo(ctx, example.bucket)
|
|
assert.NoError(t, err, errTag)
|
|
}
|
|
}
|
|
|
|
func TestMakeBucketWithLocation(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
exp := time.Unix(0, 0).UTC()
|
|
|
|
for i, example := range []struct {
|
|
bucket string
|
|
meta time.Time
|
|
retErr error
|
|
bucketStatus error
|
|
}{
|
|
{"mybucket", exp, minio.BucketAlreadyExists{Bucket: "mybucket"}, nil},
|
|
{"mybucket", exp, nil, storage.ErrKeyNotFound.New("mybucket")},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
mockBS.EXPECT().Get(gomock.Any(), gomock.Any()).Return(buckets.Meta{Created: exp}, example.bucketStatus)
|
|
if storage.ErrKeyNotFound.Has(example.bucketStatus) {
|
|
mockBS.EXPECT().Put(gomock.Any(), example.bucket).Return(buckets.Meta{Created: example.meta}, nil)
|
|
}
|
|
|
|
err := storjObj.MakeBucketWithLocation(ctx, example.bucket, "location")
|
|
if example.retErr != nil {
|
|
assert.NotNil(t, err, errTag)
|
|
assert.Equal(t, example.retErr, err, errTag)
|
|
} else {
|
|
assert.Nil(t, err, errTag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestListBuckets(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockBS := mock_buckets.NewMockStore(ctrl)
|
|
b := Storj{bs: mockBS}
|
|
|
|
storjObj := storjObjects{storj: &b}
|
|
|
|
exp := time.Unix(0, 0).UTC()
|
|
|
|
for i, example := range []struct {
|
|
bucket string
|
|
meta time.Time
|
|
more bool
|
|
err error
|
|
errString string
|
|
}{
|
|
// happy scenario
|
|
{"mybucket", exp, false, nil, ""},
|
|
{"mybucket", exp, true, nil, ""},
|
|
} {
|
|
errTag := fmt.Sprintf("Test case #%d", i)
|
|
|
|
b := make([]buckets.ListItem, 5)
|
|
for i, item := range b {
|
|
item.Bucket = fmt.Sprintf("bucket %d", i)
|
|
item.Meta = buckets.Meta{Created: exp}
|
|
}
|
|
mockBS.EXPECT().List(gomock.Any(), gomock.Any(), gomock.Any(), 0).Return(b, example.more, example.err).AnyTimes()
|
|
|
|
_, err := storjObj.ListBuckets(ctx)
|
|
assert.NoError(t, err, errTag)
|
|
}
|
|
}
|