Refactor List in PointerDB (#163)
* Refactor List in Pointer DB * Fix pointerdb-client example * Fix issue in Path type related to empty paths * Test for the PointerDB service with some fixes * Fixed debug message in example: trancated --> more * GoDoc comments for unexported methods * TODO comment to check if Put is overwriting * Log warning if protobuf timestamp cannot be converted * TODO comment to make ListPageLimit configurable * Rename 'segment' package to 'segments' to reflect folder name
This commit is contained in:
parent
8e7f4f6ebe
commit
daf391e473
@ -16,6 +16,7 @@ import (
|
||||
|
||||
p "storj.io/storj/pkg/paths"
|
||||
client "storj.io/storj/pkg/pointerdb"
|
||||
"storj.io/storj/pkg/storage"
|
||||
proto "storj.io/storj/protos/pointerdb"
|
||||
)
|
||||
|
||||
@ -82,19 +83,17 @@ func main() {
|
||||
}
|
||||
|
||||
// Example List with pagination
|
||||
startingPathKey := p.New("fold1/")
|
||||
var limit int64 = 1
|
||||
|
||||
paths, trunc, err := pdbclient.List(ctx, startingPathKey, limit, APIKey)
|
||||
prefix := p.New("fold1")
|
||||
items, more, err := pdbclient.List(ctx, prefix, nil, nil, true, 1, storage.MetaNone, APIKey)
|
||||
|
||||
if err != nil || status.Code(err) == codes.Internal {
|
||||
logger.Error("failed to list file paths", zap.Error(err))
|
||||
} else {
|
||||
var stringList []string
|
||||
for _, pathByte := range paths {
|
||||
stringList = append(stringList, string(pathByte))
|
||||
for _, item := range items {
|
||||
stringList = append(stringList, item.Path.String())
|
||||
}
|
||||
logger.Debug("Success: listed paths: " + strings.Join(stringList, ", ") + "; truncated: " + fmt.Sprintf("%t", trunc))
|
||||
logger.Debug("Success: listed paths: " + strings.Join(stringList, ", ") + "; more: " + fmt.Sprintf("%t", more))
|
||||
}
|
||||
|
||||
// Example Delete
|
||||
|
@ -20,7 +20,12 @@ type Path []string
|
||||
func New(segs ...string) Path {
|
||||
s := path.Join(segs...)
|
||||
s = strings.Trim(s, "/")
|
||||
return strings.Split(s, "/")
|
||||
p := strings.Split(s, "/")
|
||||
if len(p) == 1 && p[0] == "" {
|
||||
// Avoid building a path with a single empty segment
|
||||
return []string{}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// String returns the string representation of the path
|
||||
|
@ -15,8 +15,8 @@ func TestNew(t *testing.T) {
|
||||
path string
|
||||
expected Path
|
||||
}{
|
||||
{"", []string{""}},
|
||||
{"/", []string{""}},
|
||||
{"", []string{}},
|
||||
{"/", []string{}},
|
||||
{"a", []string{"a"}},
|
||||
{"/a/", []string{"a"}},
|
||||
{"a/b/c/d", []string{"a", "b", "c", "d"}},
|
||||
@ -33,9 +33,10 @@ func TestNewWithSegments(t *testing.T) {
|
||||
segs []string
|
||||
expected Path
|
||||
}{
|
||||
{[]string{""}, []string{""}},
|
||||
{[]string{"", ""}, []string{""}},
|
||||
{[]string{"/"}, []string{""}},
|
||||
{nil, []string{}},
|
||||
{[]string{""}, []string{}},
|
||||
{[]string{"", ""}, []string{}},
|
||||
{[]string{"/"}, []string{}},
|
||||
{[]string{"a"}, []string{"a"}},
|
||||
{[]string{"/a/"}, []string{"a"}},
|
||||
{[]string{"", "a", "", "b", "c", "d", ""}, []string{"a", "b", "c", "d"}},
|
||||
@ -57,6 +58,7 @@ func TestString(t *testing.T) {
|
||||
path Path
|
||||
expected string
|
||||
}{
|
||||
{nil, ""},
|
||||
{[]string{}, ""},
|
||||
{[]string{""}, ""},
|
||||
{[]string{"a"}, "a"},
|
||||
@ -70,19 +72,21 @@ func TestString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBytes(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
path Path
|
||||
expected []byte
|
||||
}{
|
||||
{[]string{""}, []byte{}},
|
||||
for i, tt := range []struct {
|
||||
path Path
|
||||
expected []byte
|
||||
}{
|
||||
{nil, []byte{}},
|
||||
{[]string{}, []byte{}},
|
||||
{[]string{""}, []byte{}},
|
||||
{[]string{"a/b"}, []byte{97, 47, 98}},
|
||||
{[]string{"a/b/c"}, []byte{97, 47, 98, 47, 99}},
|
||||
{[]string{"a/b/c/d/e/f"}, []byte{97, 47, 98, 47, 99, 47, 100, 47, 101, 47, 102}},
|
||||
}{
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
b := tt.path.Bytes()
|
||||
assert.Equal(t, tt.expected, b, errTag)
|
||||
}
|
||||
{[]string{"a/b/c"}, []byte{97, 47, 98, 47, 99}},
|
||||
{[]string{"a/b/c/d/e/f"}, []byte{97, 47, 98, 47, 99, 47, 100, 47, 101, 47, 102}},
|
||||
} {
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
b := tt.path.Bytes()
|
||||
assert.Equal(t, tt.expected, b, errTag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepend(t *testing.T) {
|
||||
@ -91,12 +95,12 @@ func TestPrepend(t *testing.T) {
|
||||
path string
|
||||
expected Path
|
||||
}{
|
||||
{"", "", []string{""}},
|
||||
{"", "", []string{}},
|
||||
{"prefix", "", []string{"prefix"}},
|
||||
{"", "my/path", []string{"my", "path"}},
|
||||
{"prefix", "my/path", []string{"prefix", "my", "path"}},
|
||||
{"p1/p2/p3", "my/path", []string{"p1", "p2", "p3", "my", "path"}},
|
||||
}{
|
||||
} {
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
p := New(tt.path).Prepend(tt.prefix)
|
||||
assert.Equal(t, tt.expected, p, errTag)
|
||||
@ -109,7 +113,8 @@ func TestPrependWithSegments(t *testing.T) {
|
||||
path string
|
||||
expected Path
|
||||
}{
|
||||
{[]string{""}, "", []string{""}},
|
||||
{nil, "", []string{}},
|
||||
{[]string{""}, "", []string{}},
|
||||
{[]string{"prefix"}, "", []string{"prefix"}},
|
||||
{[]string{""}, "my/path", []string{"my", "path"}},
|
||||
{[]string{"prefix"}, "my/path", []string{"prefix", "my", "path"}},
|
||||
@ -130,7 +135,7 @@ func TestAppend(t *testing.T) {
|
||||
suffix string
|
||||
expected Path
|
||||
}{
|
||||
{"", "", []string{""}},
|
||||
{"", "", []string{}},
|
||||
{"", "suffix", []string{"suffix"}},
|
||||
{"my/path", "", []string{"my", "path"}},
|
||||
{"my/path", "suffix", []string{"my", "path", "suffix"}},
|
||||
@ -148,7 +153,8 @@ func TestAppendWithSegments(t *testing.T) {
|
||||
segs []string
|
||||
expected Path
|
||||
}{
|
||||
{"", []string{""}, []string{""}},
|
||||
{"", nil, []string{}},
|
||||
{"", []string{""}, []string{}},
|
||||
{"", []string{"suffix"}, []string{"suffix"}},
|
||||
{"my/path", []string{""}, []string{"my", "path"}},
|
||||
{"my/path", []string{"suffix"}, []string{"my", "path", "suffix"}},
|
||||
@ -164,7 +170,8 @@ func TestAppendWithSegments(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEncryption(t *testing.T) {
|
||||
for i, path := range []Path{
|
||||
for i, segs := range []Path{
|
||||
nil, // empty path
|
||||
[]string{}, // empty path
|
||||
[]string{""}, // empty path segment
|
||||
[]string{"file.txt"},
|
||||
@ -173,6 +180,7 @@ func TestEncryption(t *testing.T) {
|
||||
[]string{"fold1", "fold2", "fold3", "file.txt"},
|
||||
} {
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
path := New(segs...)
|
||||
key := []byte("my secret")
|
||||
encrypted, err := path.Encrypt(key)
|
||||
if !assert.NoError(t, err, errTag) {
|
||||
|
@ -7,10 +7,13 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
||||
|
||||
p "storj.io/storj/pkg/paths"
|
||||
"storj.io/storj/pkg/storage"
|
||||
pb "storj.io/storj/protos/pointerdb"
|
||||
)
|
||||
|
||||
@ -23,12 +26,16 @@ type PointerDB struct {
|
||||
grpcClient pb.PointerDBClient
|
||||
}
|
||||
|
||||
// a compiler trick to make sure *Overlay implements Client
|
||||
var _ Client = (*PointerDB)(nil)
|
||||
|
||||
// Client services offerred for the interface
|
||||
type Client interface {
|
||||
Put(ctx context.Context, path p.Path, pointer *pb.Pointer, APIKey []byte) error
|
||||
Get(ctx context.Context, path p.Path, APIKey []byte) (*pb.Pointer, error)
|
||||
List(ctx context.Context, startingPathKey p.Path, limit int64, APIKey []byte) (
|
||||
paths [][]byte, truncated bool, err error)
|
||||
List(ctx context.Context, prefix, startAfter, endBefore p.Path,
|
||||
recursive bool, limit int, metaFlags uint64, APIKey []byte) (
|
||||
items []storage.ListItem, more bool, err error)
|
||||
Delete(ctx context.Context, path p.Path, APIKey []byte) error
|
||||
}
|
||||
|
||||
@ -61,7 +68,7 @@ func clientConnection(serverAddr string, opts ...grpc.DialOption) (pb.PointerDBC
|
||||
func (pdb *PointerDB) Put(ctx context.Context, path p.Path, pointer *pb.Pointer, APIKey []byte) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = pdb.grpcClient.Put(ctx, &pb.PutRequest{Path: path.Bytes(), Pointer: pointer, APIKey: APIKey})
|
||||
_, err = pdb.grpcClient.Put(ctx, &pb.PutRequest{Path: path.String(), Pointer: pointer, APIKey: APIKey})
|
||||
|
||||
return err
|
||||
}
|
||||
@ -70,7 +77,7 @@ func (pdb *PointerDB) Put(ctx context.Context, path p.Path, pointer *pb.Pointer,
|
||||
func (pdb *PointerDB) Get(ctx context.Context, path p.Path, APIKey []byte) (pointer *pb.Pointer, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
res, err := pdb.grpcClient.Get(ctx, &pb.GetRequest{Path: path.Bytes(), APIKey: APIKey})
|
||||
res, err := pdb.grpcClient.Get(ctx, &pb.GetRequest{Path: path.String(), APIKey: APIKey})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -82,22 +89,55 @@ func (pdb *PointerDB) Get(ctx context.Context, path p.Path, APIKey []byte) (poin
|
||||
}
|
||||
|
||||
// List is the interface to make a LIST request, needs StartingPathKey, Limit, and APIKey
|
||||
func (pdb *PointerDB) List(ctx context.Context, startingPathKey p.Path, limit int64, APIKey []byte) (paths [][]byte, truncated bool, err error) {
|
||||
func (pdb *PointerDB) List(ctx context.Context, prefix, startAfter, endBefore p.Path,
|
||||
recursive bool, limit int, metaFlags uint64, APIKey []byte) (
|
||||
items []storage.ListItem, more bool, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
res, err := pdb.grpcClient.List(ctx, &pb.ListRequest{StartingPathKey: startingPathKey.Bytes(), Limit: limit, APIKey: APIKey})
|
||||
|
||||
res, err := pdb.grpcClient.List(ctx, &pb.ListRequest{
|
||||
Prefix: prefix.String(),
|
||||
StartAfter: startAfter.String(),
|
||||
EndBefore: endBefore.String(),
|
||||
Recursive: recursive,
|
||||
Limit: int32(limit),
|
||||
MetaFlags: metaFlags,
|
||||
APIKey: APIKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return res.Paths, res.Truncated, nil
|
||||
list := res.GetItems()
|
||||
items = make([]storage.ListItem, len(list))
|
||||
for i, itm := range list {
|
||||
modified, err := ptypes.Timestamp(itm.GetCreationDate())
|
||||
if err != nil {
|
||||
zap.S().Warnf("Failed converting creation date %v: %v", itm.GetCreationDate(), err)
|
||||
}
|
||||
expiration, err := ptypes.Timestamp(itm.GetExpirationDate())
|
||||
if err != nil {
|
||||
zap.S().Warnf("Failed converting expiration date %v: %v", itm.GetExpirationDate(), err)
|
||||
}
|
||||
items[i] = storage.ListItem{
|
||||
Path: p.New(string(itm.GetPath())),
|
||||
// TODO(kaloyan): we need to rethink how we return metadata through the layers
|
||||
Meta: storage.Meta{
|
||||
Modified: modified,
|
||||
Expiration: expiration,
|
||||
Size: itm.GetSize(),
|
||||
// TODO UserDefined: itm.GetMetadata(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return items, res.GetMore(), nil
|
||||
}
|
||||
|
||||
// Delete is the interface to make a Delete request, needs Path and APIKey
|
||||
func (pdb *PointerDB) Delete(ctx context.Context, path p.Path, APIKey []byte) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = pdb.grpcClient.Delete(ctx, &pb.DeleteRequest{Path: path.Bytes(), APIKey: APIKey})
|
||||
_, err = pdb.grpcClient.Delete(ctx, &pb.DeleteRequest{Path: path.String(), APIKey: APIKey})
|
||||
|
||||
return err
|
||||
}
|
||||
|
280
pkg/pointerdb/client_test.go
Normal file
280
pkg/pointerdb/client_test.go
Normal file
@ -0,0 +1,280 @@
|
||||
// Copyright (C) 2018 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package pointerdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
p "storj.io/storj/pkg/paths"
|
||||
"storj.io/storj/pkg/storage"
|
||||
pb "storj.io/storj/protos/pointerdb"
|
||||
)
|
||||
|
||||
const (
|
||||
unauthenticated = "failed API creds"
|
||||
noPathGiven = "file path not given"
|
||||
noLimitGiven = "limit not given"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
ErrUnauthenticated = errors.New(unauthenticated)
|
||||
ErrNoFileGiven = errors.New(noPathGiven)
|
||||
ErrNoLimitGiven = errors.New(noLimitGiven)
|
||||
)
|
||||
|
||||
func TestNewPointerDBClient(t *testing.T) {
|
||||
// mocked grpcClient so we don't have
|
||||
// to call the network to test the code
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
gc := NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
assert.NotNil(t, pdb)
|
||||
assert.NotNil(t, pdb.grpcClient)
|
||||
}
|
||||
|
||||
func makePointer(path p.Path, auth []byte) pb.PutRequest {
|
||||
// rps is an example slice of RemotePieces to add to this
|
||||
// REMOTE pointer type.
|
||||
var rps []*pb.RemotePiece
|
||||
rps = append(rps, &pb.RemotePiece{
|
||||
PieceNum: 1,
|
||||
NodeId: "testId",
|
||||
})
|
||||
pr := pb.PutRequest{
|
||||
Path: path.String(),
|
||||
Pointer: &pb.Pointer{
|
||||
Type: pb.Pointer_REMOTE,
|
||||
Remote: &pb.RemoteSegment{
|
||||
Redundancy: &pb.RedundancyScheme{
|
||||
Type: pb.RedundancyScheme_RS,
|
||||
MinReq: 1,
|
||||
Total: 3,
|
||||
RepairThreshold: 2,
|
||||
SuccessThreshold: 3,
|
||||
},
|
||||
PieceId: "testId",
|
||||
RemotePieces: rps,
|
||||
},
|
||||
Size: int64(1),
|
||||
},
|
||||
APIKey: auth,
|
||||
}
|
||||
return pr
|
||||
}
|
||||
|
||||
func TestPut(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
for i, tt := range []struct {
|
||||
APIKey []byte
|
||||
path p.Path
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{[]byte("abc123"), p.New("file1/file2"), nil, ""},
|
||||
{[]byte("wrong key"), p.New("file1/file2"), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte("abc123"), p.New(""), ErrNoFileGiven, noPathGiven},
|
||||
{[]byte("wrong key"), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte(""), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
} {
|
||||
putRequest := makePointer(tt.path, tt.APIKey)
|
||||
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
gc := NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
// here we don't care what type of context we pass
|
||||
gc.EXPECT().Put(gomock.Any(), &putRequest).Return(nil, tt.err)
|
||||
|
||||
err := pdb.Put(ctx, tt.path, putRequest.Pointer, tt.APIKey)
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
} else {
|
||||
assert.NoError(t, err, errTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
for i, tt := range []struct {
|
||||
APIKey []byte
|
||||
path p.Path
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{[]byte("wrong key"), p.New("file1/file2"), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte("abc123"), p.New(""), ErrNoFileGiven, noPathGiven},
|
||||
{[]byte("wrong key"), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte(""), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte("abc123"), p.New("file1/file2"), nil, ""},
|
||||
} {
|
||||
getPointer := makePointer(tt.path, tt.APIKey)
|
||||
getRequest := pb.GetRequest{Path: tt.path.String(), APIKey: tt.APIKey}
|
||||
|
||||
data, err := proto.Marshal(getPointer.Pointer)
|
||||
if err != nil {
|
||||
log.Fatal("marshaling error: ", err)
|
||||
}
|
||||
|
||||
byteData := []byte(data)
|
||||
|
||||
getResponse := pb.GetResponse{Pointer: byteData}
|
||||
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
|
||||
gc := NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
gc.EXPECT().Get(gomock.Any(), &getRequest).Return(&getResponse, tt.err)
|
||||
|
||||
pointer, err := pdb.Get(ctx, tt.path, tt.APIKey)
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
assert.Nil(t, pointer)
|
||||
} else {
|
||||
assert.NotNil(t, pointer)
|
||||
assert.NoError(t, err, errTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
for i, tt := range []struct {
|
||||
prefix string
|
||||
startAfter string
|
||||
endBefore string
|
||||
recursive bool
|
||||
limit int
|
||||
metaFlags uint64
|
||||
apiKey string
|
||||
items []*pb.ListResponse_Item
|
||||
more bool
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{"", "", "", false, 0, storage.MetaNone, "",
|
||||
[]*pb.ListResponse_Item{}, false, nil, ""},
|
||||
{"", "", "", false, 0, storage.MetaNone, "",
|
||||
[]*pb.ListResponse_Item{&pb.ListResponse_Item{}}, false, nil, ""},
|
||||
{"", "", "", false, -1, storage.MetaNone, "",
|
||||
[]*pb.ListResponse_Item{}, false, ErrUnauthenticated, unauthenticated},
|
||||
{"prefix", "after", "before", false, 1, storage.MetaNone, "some key",
|
||||
[]*pb.ListResponse_Item{
|
||||
&pb.ListResponse_Item{Path: "a/b/c"},
|
||||
},
|
||||
true, nil, ""},
|
||||
{"prefix", "after", "before", false, 1, storage.MetaAll, "some key",
|
||||
[]*pb.ListResponse_Item{
|
||||
&pb.ListResponse_Item{Path: "a/b/c", Size: 1234,
|
||||
CreationDate: ptypes.TimestampNow(), ExpirationDate: ptypes.TimestampNow()},
|
||||
},
|
||||
true, nil, ""},
|
||||
{"some/prefix", "start/after", "end/before", true, 123, storage.MetaSize, "some key",
|
||||
[]*pb.ListResponse_Item{
|
||||
&pb.ListResponse_Item{Path: "a/b/c", Size: 1234},
|
||||
&pb.ListResponse_Item{Path: "x/y", Size: 789},
|
||||
},
|
||||
true, nil, ""},
|
||||
} {
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
|
||||
listRequest := pb.ListRequest{
|
||||
Prefix: tt.prefix,
|
||||
StartAfter: tt.startAfter,
|
||||
EndBefore: tt.endBefore,
|
||||
Recursive: tt.recursive,
|
||||
Limit: int32(tt.limit),
|
||||
MetaFlags: tt.metaFlags,
|
||||
APIKey: []byte(tt.apiKey),
|
||||
}
|
||||
|
||||
listResponse := pb.ListResponse{Items: tt.items, More: tt.more}
|
||||
|
||||
gc := NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
gc.EXPECT().List(gomock.Any(), &listRequest).Return(&listResponse, tt.err)
|
||||
|
||||
items, more, err := pdb.List(ctx, p.New(tt.prefix), p.New(tt.startAfter),
|
||||
p.New(tt.endBefore), tt.recursive, tt.limit, tt.metaFlags, []byte(tt.apiKey))
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
assert.False(t, more)
|
||||
assert.Nil(t, items)
|
||||
} else {
|
||||
assert.NoError(t, err, errTag)
|
||||
assert.Equal(t, tt.more, more)
|
||||
assert.NotNil(t, items)
|
||||
assert.Equal(t, len(tt.items), len(items))
|
||||
|
||||
for i := 0; i < len(items); i++ {
|
||||
assert.Equal(t, tt.items[i].GetPath(), items[i].Path.String())
|
||||
assert.Equal(t, tt.items[i].GetSize(), items[i].Meta.Size)
|
||||
|
||||
modified, _ := ptypes.Timestamp(tt.items[i].GetCreationDate())
|
||||
assert.Equal(t, modified, items[i].Meta.Modified)
|
||||
|
||||
expiration, _ := ptypes.Timestamp(tt.items[i].GetExpirationDate())
|
||||
assert.Equal(t, expiration, items[i].Meta.Expiration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
for i, tt := range []struct {
|
||||
APIKey []byte
|
||||
path p.Path
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{[]byte("wrong key"), p.New("file1/file2"), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte("abc123"), p.New(""), ErrNoFileGiven, noPathGiven},
|
||||
{[]byte("wrong key"), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte(""), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte("abc123"), p.New("file1/file2"), nil, ""},
|
||||
} {
|
||||
deleteRequest := pb.DeleteRequest{Path: tt.path.String(), APIKey: tt.APIKey}
|
||||
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
gc := NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
gc.EXPECT().Delete(gomock.Any(), &deleteRequest).Return(nil, tt.err)
|
||||
|
||||
err := pdb.Delete(ctx, tt.path, tt.APIKey)
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
} else {
|
||||
assert.NoError(t, err, errTag)
|
||||
}
|
||||
}
|
||||
}
|
101
pkg/pointerdb/kvstore_mock_test.go
Normal file
101
pkg/pointerdb/kvstore_mock_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright (C) 2018 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: storj.io/storj/storage (interfaces: KeyValueStore)
|
||||
|
||||
// Package pointerdb is a generated GoMock package.
|
||||
package pointerdb
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
|
||||
storage "storj.io/storj/storage"
|
||||
)
|
||||
|
||||
// MockKeyValueStore is a mock of KeyValueStore interface
|
||||
type MockKeyValueStore struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockKeyValueStoreMockRecorder
|
||||
}
|
||||
|
||||
// MockKeyValueStoreMockRecorder is the mock recorder for MockKeyValueStore
|
||||
type MockKeyValueStoreMockRecorder struct {
|
||||
mock *MockKeyValueStore
|
||||
}
|
||||
|
||||
// NewMockKeyValueStore creates a new mock instance
|
||||
func NewMockKeyValueStore(ctrl *gomock.Controller) *MockKeyValueStore {
|
||||
mock := &MockKeyValueStore{ctrl: ctrl}
|
||||
mock.recorder = &MockKeyValueStoreMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockKeyValueStore) EXPECT() *MockKeyValueStoreMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Close mocks base method
|
||||
func (m *MockKeyValueStore) Close() error {
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close
|
||||
func (mr *MockKeyValueStoreMockRecorder) Close() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockKeyValueStore)(nil).Close))
|
||||
}
|
||||
|
||||
// Delete mocks base method
|
||||
func (m *MockKeyValueStore) Delete(arg0 storage.Key) error {
|
||||
ret := m.ctrl.Call(m, "Delete", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete
|
||||
func (mr *MockKeyValueStoreMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockKeyValueStore)(nil).Delete), arg0)
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockKeyValueStore) Get(arg0 storage.Key) (storage.Value, error) {
|
||||
ret := m.ctrl.Call(m, "Get", arg0)
|
||||
ret0, _ := ret[0].(storage.Value)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockKeyValueStoreMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockKeyValueStore)(nil).Get), arg0)
|
||||
}
|
||||
|
||||
// List mocks base method
|
||||
func (m *MockKeyValueStore) List(arg0 storage.Key, arg1 storage.Limit) (storage.Keys, error) {
|
||||
ret := m.ctrl.Call(m, "List", arg0, arg1)
|
||||
ret0, _ := ret[0].(storage.Keys)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// List indicates an expected call of List
|
||||
func (mr *MockKeyValueStoreMockRecorder) List(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockKeyValueStore)(nil).List), arg0, arg1)
|
||||
}
|
||||
|
||||
// Put mocks base method
|
||||
func (m *MockKeyValueStore) Put(arg0 storage.Key, arg1 storage.Value) error {
|
||||
ret := m.ctrl.Call(m, "Put", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Put indicates an expected call of Put
|
||||
func (mr *MockKeyValueStoreMockRecorder) Put(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockKeyValueStore)(nil).Put), arg0, arg1)
|
||||
}
|
@ -2,16 +2,18 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: storj.io/storj/protos/netstate (interfaces: NetStateClient)
|
||||
// Source: storj.io/storj/protos/pointerdb (interfaces: PointerDBClient)
|
||||
|
||||
// Package netstate is a generated GoMock package.
|
||||
// Package pointerdb is a generated GoMock package.
|
||||
package pointerdb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
grpc "google.golang.org/grpc"
|
||||
reflect "reflect"
|
||||
|
||||
pointerdb "storj.io/storj/protos/pointerdb"
|
||||
)
|
||||
|
||||
@ -108,4 +110,4 @@ func (m *MockPointerDBClient) Put(arg0 context.Context, arg1 *pointerdb.PutReque
|
||||
func (mr *MockPointerDBClientMockRecorder) Put(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockPointerDBClient)(nil).Put), varargs...)
|
||||
}
|
||||
}
|
@ -5,18 +5,28 @@ package pointerdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"storj.io/storj/pkg/paths"
|
||||
meta "storj.io/storj/pkg/storage"
|
||||
"storj.io/storj/pointerdb/auth"
|
||||
pb "storj.io/storj/protos/pointerdb"
|
||||
"storj.io/storj/storage"
|
||||
)
|
||||
|
||||
// ListPageLimit is the maximum number of items that will be returned by a list
|
||||
// request.
|
||||
// TODO(kaloyan): make it configurable
|
||||
const ListPageLimit = 1000
|
||||
|
||||
// Server implements the network state RPC service
|
||||
type Server struct {
|
||||
DB storage.KeyValueStore
|
||||
@ -31,8 +41,8 @@ func NewServer(db storage.KeyValueStore, logger *zap.Logger) *Server {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) validateAuth(APIKeyBytes []byte) error {
|
||||
if !auth.ValidateAPIKey(string(APIKeyBytes)) {
|
||||
func (s *Server) validateAuth(APIKey []byte) error {
|
||||
if !auth.ValidateAPIKey(string(APIKey)) {
|
||||
s.logger.Error("unauthorized request: ", zap.Error(grpc.Errorf(codes.Unauthenticated, "Invalid API credential")))
|
||||
return grpc.Errorf(codes.Unauthenticated, "Invalid API credential")
|
||||
}
|
||||
@ -40,40 +50,47 @@ func (s *Server) validateAuth(APIKeyBytes []byte) error {
|
||||
}
|
||||
|
||||
// Put formats and hands off a key/value (path/pointer) to be saved to boltdb
|
||||
func (s *Server) Put(ctx context.Context, putReq *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
func (s *Server) Put(ctx context.Context, req *pb.PutRequest) (resp *pb.PutResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
s.logger.Debug("entering pointerdb put")
|
||||
|
||||
if err := s.validateAuth(putReq.APIKey); err != nil {
|
||||
if err = s.validateAuth(req.GetAPIKey()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pointerBytes, err := proto.Marshal(putReq.Pointer)
|
||||
// Update the pointer with the creation date
|
||||
req.GetPointer().CreationDate = ptypes.TimestampNow()
|
||||
|
||||
pointerBytes, err := proto.Marshal(req.GetPointer())
|
||||
if err != nil {
|
||||
s.logger.Error("err marshaling pointer", zap.Error(err))
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
if err := s.DB.Put(putReq.Path, pointerBytes); err != nil {
|
||||
// TODO(kaloyan): make sure that we know we are overwriting the pointer!
|
||||
// In such case we should delete the pieces of the old segment if it was
|
||||
// a remote one.
|
||||
if err = s.DB.Put([]byte(req.GetPath()), pointerBytes); err != nil {
|
||||
s.logger.Error("err putting pointer", zap.Error(err))
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
s.logger.Debug("put to the db: " + string(putReq.Path))
|
||||
s.logger.Debug("put to the db: " + string(req.GetPath()))
|
||||
|
||||
return &pb.PutResponse{}, nil
|
||||
}
|
||||
|
||||
// Get formats and hands off a file path to get from boltdb
|
||||
func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
|
||||
func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (resp *pb.GetResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
s.logger.Debug("entering pointerdb get")
|
||||
|
||||
APIKeyBytes := []byte(req.APIKey)
|
||||
if err := s.validateAuth(APIKeyBytes); err != nil {
|
||||
if err = s.validateAuth(req.GetAPIKey()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pointerBytes, err := s.DB.Get(req.Path)
|
||||
pointerBytes, err := s.DB.Get([]byte(req.GetPath()))
|
||||
if err != nil {
|
||||
s.logger.Error("err getting file", zap.Error(err))
|
||||
s.logger.Error("err getting pointer", zap.Error(err))
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
@ -83,65 +100,192 @@ func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse,
|
||||
}
|
||||
|
||||
// List calls the bolt client's List function and returns all Path keys in the Pointers bucket
|
||||
func (s *Server) List(ctx context.Context, req *pb.ListRequest) (*pb.ListResponse, error) {
|
||||
func (s *Server) List(ctx context.Context, req *pb.ListRequest) (resp *pb.ListResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
s.logger.Debug("entering pointerdb list")
|
||||
|
||||
if req.Limit <= 0 {
|
||||
return nil, Error.New("err Limit is less than or equal to 0")
|
||||
limit := int(req.GetLimit())
|
||||
if limit <= 0 || limit > ListPageLimit {
|
||||
limit = ListPageLimit
|
||||
}
|
||||
|
||||
APIKeyBytes := []byte(req.APIKey)
|
||||
if err := s.validateAuth(APIKeyBytes); err != nil {
|
||||
if err = s.validateAuth(req.GetAPIKey()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var keyList storage.Keys
|
||||
if req.StartingPathKey == nil {
|
||||
pathKeys, err := s.DB.List(nil, storage.Limit(req.Limit))
|
||||
if err != nil {
|
||||
s.logger.Error("err listing path keys with no starting key", zap.Error(err))
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
keyList = pathKeys
|
||||
} else if req.StartingPathKey != nil {
|
||||
pathKeys, err := s.DB.List(storage.Key(req.StartingPathKey), storage.Limit(req.Limit))
|
||||
if err != nil {
|
||||
s.logger.Error("err listing path keys", zap.Error(err))
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
keyList = pathKeys
|
||||
prefix := paths.New(req.GetPrefix())
|
||||
|
||||
// TODO(kaloyan): here we query the DB without limit. We must optimize it!
|
||||
keys, err := s.DB.List(prefix.Bytes(), 0)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
truncated := isItTruncated(keyList, int(req.Limit))
|
||||
var more bool
|
||||
var items []*pb.ListResponse_Item
|
||||
if req.GetEndBefore() != "" && req.GetStartAfter() == "" {
|
||||
items, more = s.processKeysBackwards(ctx, keys, prefix,
|
||||
req.GetEndBefore(), req.GetRecursive(), limit, req.GetMetaFlags())
|
||||
} else {
|
||||
items, more = s.processKeysForwards(ctx, keys, prefix, req.GetStartAfter(),
|
||||
req.GetEndBefore(), req.GetRecursive(), limit, req.GetMetaFlags())
|
||||
}
|
||||
|
||||
s.logger.Debug("path keys retrieved")
|
||||
return &pb.ListResponse{
|
||||
Paths: keyList.ByteSlices(),
|
||||
Truncated: truncated,
|
||||
}, nil
|
||||
return &pb.ListResponse{Items: items, More: more}, nil
|
||||
}
|
||||
|
||||
func isItTruncated(keyList storage.Keys, limit int) bool {
|
||||
if len(keyList) == limit {
|
||||
return true
|
||||
// processKeysForwards iterates forwards through given keys, and returns them
|
||||
// as list items
|
||||
func (s *Server) processKeysForwards(ctx context.Context, keys storage.Keys,
|
||||
prefix paths.Path, startAfter, endBefore string, recursive bool, limit int,
|
||||
metaFlags uint64) (items []*pb.ListResponse_Item, more bool) {
|
||||
skip := startAfter != ""
|
||||
startAfterPath := prefix.Append(startAfter)
|
||||
endBeforePath := prefix.Append(endBefore)
|
||||
|
||||
for _, key := range keys {
|
||||
p := paths.New(string(key))
|
||||
|
||||
if skip {
|
||||
if reflect.DeepEqual(p, startAfterPath) {
|
||||
// TODO(kaloyan): Better check - what if there is no path equal to startAfter?
|
||||
// TODO(kaloyan): Add Equal method in Path type
|
||||
skip = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(kaloyan): Better check - what if there is no path equal to endBefore?
|
||||
// TODO(kaloyan): Add Equal method in Path type
|
||||
if reflect.DeepEqual(p, endBeforePath) {
|
||||
break
|
||||
}
|
||||
|
||||
// TODO(kaloyan): add HasPrefix method to Path type
|
||||
if !strings.HasPrefix(p.String(), prefix.String()) {
|
||||
// We went through all keys that start with the prefix
|
||||
break
|
||||
}
|
||||
|
||||
if !recursive && len(p) > len(prefix)+1 {
|
||||
continue
|
||||
}
|
||||
|
||||
item := s.createListItem(ctx, p, metaFlags)
|
||||
items = append(items, item)
|
||||
|
||||
if len(items) == limit {
|
||||
more = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
return items, more
|
||||
}
|
||||
|
||||
// processKeysBackwards iterates backwards through given keys, and returns them
|
||||
// as list items
|
||||
func (s *Server) processKeysBackwards(ctx context.Context, keys storage.Keys,
|
||||
prefix paths.Path, endBefore string, recursive bool, limit int,
|
||||
metaFlags uint64) (items []*pb.ListResponse_Item, more bool) {
|
||||
skip := endBefore != ""
|
||||
endBeforePath := prefix.Append(endBefore)
|
||||
|
||||
for i := len(keys) - 1; i >= 0; i-- {
|
||||
key := keys[i]
|
||||
p := paths.New(string(key))
|
||||
|
||||
if skip {
|
||||
if reflect.DeepEqual(p, endBeforePath) {
|
||||
// TODO(kaloyan): Better check - what if there is no path equal to endBefore?
|
||||
// TODO(kaloyan): Add Equal method in Path type
|
||||
skip = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(kaloyan): add HasPrefix method to Path type
|
||||
if !strings.HasPrefix(p.String(), prefix.String()) {
|
||||
// We went through all keys that start with the prefix
|
||||
break
|
||||
}
|
||||
|
||||
if !recursive && len(p) > len(prefix)+1 {
|
||||
continue
|
||||
}
|
||||
|
||||
item := s.createListItem(ctx, p, metaFlags)
|
||||
items = append([]*pb.ListResponse_Item{item}, items...)
|
||||
|
||||
if len(items) == limit {
|
||||
more = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return items, more
|
||||
}
|
||||
|
||||
// createListItem creates a new list item with the given path. It also adds
|
||||
// the metadata according to the given metaFlags.
|
||||
func (s *Server) createListItem(ctx context.Context, p paths.Path,
|
||||
metaFlags uint64) *pb.ListResponse_Item {
|
||||
item := &pb.ListResponse_Item{Path: p.String()}
|
||||
err := s.getMetadata(ctx, item, metaFlags)
|
||||
if err != nil {
|
||||
s.logger.Warn("err retrieving metadata", zap.Error(err))
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
// getMetadata adds the metadata to the given item pointer according to the
|
||||
// given metaFlags
|
||||
func (s *Server) getMetadata(ctx context.Context, item *pb.ListResponse_Item,
|
||||
metaFlags uint64) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
if metaFlags == meta.MetaNone {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := s.DB.Get([]byte(item.GetPath()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr := &pb.Pointer{}
|
||||
err = proto.Unmarshal(b, pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(kaloyan): revisit after clarifying how to store and serialize metadata
|
||||
if metaFlags&meta.MetaModified != 0 {
|
||||
item.CreationDate = pr.GetCreationDate()
|
||||
}
|
||||
if metaFlags&meta.MetaExpiration != 0 {
|
||||
item.ExpirationDate = pr.GetExpirationDate()
|
||||
}
|
||||
if metaFlags&meta.MetaUserDefined != 0 {
|
||||
item.Metadata = pr.GetMetadata()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete formats and hands off a file path to delete from boltdb
|
||||
func (s *Server) Delete(ctx context.Context, req *pb.DeleteRequest) (*pb.DeleteResponse, error) {
|
||||
func (s *Server) Delete(ctx context.Context, req *pb.DeleteRequest) (resp *pb.DeleteResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
s.logger.Debug("entering pointerdb delete")
|
||||
|
||||
APIKeyBytes := []byte(req.APIKey)
|
||||
if err := s.validateAuth(APIKeyBytes); err != nil {
|
||||
if err = s.validateAuth(req.GetAPIKey()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := s.DB.Delete(req.Path)
|
||||
err = s.DB.Delete([]byte(req.GetPath()))
|
||||
if err != nil {
|
||||
s.logger.Error("err deleting path and pointer", zap.Error(err))
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
s.logger.Debug("deleted pointer at path: " + string(req.Path))
|
||||
s.logger.Debug("deleted pointer at path: " + string(req.GetPath()))
|
||||
return &pb.DeleteResponse{}, nil
|
||||
}
|
||||
|
@ -4,104 +4,53 @@
|
||||
package pointerdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"storj.io/storj/pkg/paths"
|
||||
meta "storj.io/storj/pkg/storage"
|
||||
pb "storj.io/storj/protos/pointerdb"
|
||||
p "storj.io/storj/pkg/paths"
|
||||
"storj.io/storj/storage"
|
||||
)
|
||||
|
||||
const (
|
||||
unauthenticated = "failed API creds"
|
||||
noPathGiven = "file path not given"
|
||||
noLimitGiven = "limit not given"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
ErrUnauthenticated = errors.New(unauthenticated)
|
||||
ErrNoFileGiven = errors.New(noPathGiven)
|
||||
ErrNoLimitGiven = errors.New(noLimitGiven)
|
||||
)
|
||||
|
||||
func TestNewPointerDBClient(t *testing.T) {
|
||||
// mocked grpcClient so we don't have
|
||||
// to call the network to test the code
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
gc:= NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
assert.NotNil(t, pdb)
|
||||
assert.NotNil(t, pdb.grpcClient)
|
||||
}
|
||||
|
||||
func makePointer(path p.Path, auth []byte) pb.PutRequest {
|
||||
// rps is an example slice of RemotePieces to add to this
|
||||
// REMOTE pointer type.
|
||||
var rps []*pb.RemotePiece
|
||||
rps = append(rps, &pb.RemotePiece{
|
||||
PieceNum: int64(1),
|
||||
NodeId: "testId",
|
||||
})
|
||||
pr := pb.PutRequest{
|
||||
Path: path.Bytes(),
|
||||
Pointer: &pb.Pointer{
|
||||
Type: pb.Pointer_REMOTE,
|
||||
Remote: &pb.RemoteSegment{
|
||||
Redundancy: &pb.RedundancyScheme{
|
||||
Type: pb.RedundancyScheme_RS,
|
||||
MinReq: int64(1),
|
||||
Total: int64(3),
|
||||
RepairThreshold: int64(2),
|
||||
SuccessThreshold: int64(3),
|
||||
},
|
||||
PieceId: "testId",
|
||||
RemotePieces: rps,
|
||||
},
|
||||
Size: int64(1),
|
||||
},
|
||||
APIKey: auth,
|
||||
}
|
||||
return pr
|
||||
}
|
||||
|
||||
func TestPut(t *testing.T){
|
||||
func TestServicePut(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
for i, tt := range []struct {
|
||||
APIKey []byte
|
||||
path p.Path
|
||||
err error
|
||||
apiKey []byte
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{[]byte("abc123"), p.New("file1/file2"), nil, ""},
|
||||
{[]byte("wrong key"), p.New("file1/file2"), ErrUnauthenticated,unauthenticated},
|
||||
{[]byte("abc123"), p.New(""), ErrNoFileGiven, noPathGiven},
|
||||
{[]byte("wrong key"), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte(""), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
}{
|
||||
putRequest:= makePointer(tt.path, tt.APIKey)
|
||||
|
||||
{nil, nil, ""},
|
||||
{[]byte("wrong key"), nil, grpc.Errorf(codes.Unauthenticated, "Invalid API credential").Error()},
|
||||
{nil, errors.New("put error"), status.Errorf(codes.Internal, "put error").Error()},
|
||||
} {
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
gc:= NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
// here we don't care what type of context we pass
|
||||
gc.EXPECT().Put(gomock.Any(), &putRequest).Return(nil, tt.err)
|
||||
db := NewMockKeyValueStore(ctrl)
|
||||
s := Server{DB: db, logger: zap.NewNop()}
|
||||
|
||||
path := "a/b/c"
|
||||
pr := pb.Pointer{}
|
||||
|
||||
if tt.err != nil || tt.errString == "" {
|
||||
db.EXPECT().Put(storage.Key([]byte(path)), gomock.Any()).Return(tt.err)
|
||||
}
|
||||
|
||||
req := pb.PutRequest{Path: path, Pointer: &pr, APIKey: tt.apiKey}
|
||||
_, err := s.Put(ctx, &req)
|
||||
|
||||
err := pdb.Put(ctx, tt.path, putRequest.Pointer, tt.APIKey)
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
} else {
|
||||
@ -110,157 +59,166 @@ func TestPut(t *testing.T){
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T){
|
||||
func TestServiceGet(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
for i, tt := range []struct {
|
||||
APIKey []byte
|
||||
path p.Path
|
||||
err error
|
||||
apiKey []byte
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{[]byte("wrong key"), p.New("file1/file2"), ErrUnauthenticated,unauthenticated},
|
||||
{[]byte("abc123"), p.New(""), ErrNoFileGiven, noPathGiven},
|
||||
{[]byte("wrong key"), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte(""), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte("abc123"), p.New("file1/file2"), nil, ""},
|
||||
}{
|
||||
getPointer := makePointer(tt.path, tt.APIKey)
|
||||
getRequest:= pb.GetRequest{Path: tt.path.Bytes(), APIKey: tt.APIKey}
|
||||
|
||||
data, err := proto.Marshal(getPointer.Pointer)
|
||||
if err != nil {
|
||||
log.Fatal("marshaling error: ", err)
|
||||
{nil, nil, ""},
|
||||
{[]byte("wrong key"), nil, grpc.Errorf(codes.Unauthenticated, "Invalid API credential").Error()},
|
||||
{nil, errors.New("get error"), status.Errorf(codes.Internal, "get error").Error()},
|
||||
} {
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
|
||||
db := NewMockKeyValueStore(ctrl)
|
||||
s := Server{DB: db, logger: zap.NewNop()}
|
||||
|
||||
path := "a/b/c"
|
||||
pr := pb.Pointer{}
|
||||
prBytes, err := proto.Marshal(&pr)
|
||||
assert.NoError(t, err, errTag)
|
||||
|
||||
if tt.err != nil || tt.errString == "" {
|
||||
db.EXPECT().Get(storage.Key([]byte(path))).Return(prBytes, tt.err)
|
||||
}
|
||||
|
||||
byteData := []byte(data)
|
||||
|
||||
getResponse := pb.GetResponse{Pointer: byteData}
|
||||
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
|
||||
gc:= NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
gc.EXPECT().Get(gomock.Any(), &getRequest).Return(&getResponse, tt.err)
|
||||
|
||||
pointer, err := pdb.Get(ctx, tt.path, tt.APIKey)
|
||||
req := pb.GetRequest{Path: path, APIKey: tt.apiKey}
|
||||
resp, err := s.Get(ctx, &req)
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
} else {
|
||||
assert.NoError(t, err, errTag)
|
||||
respPr := pb.Pointer{}
|
||||
err := proto.Unmarshal(resp.GetPointer(), &respPr)
|
||||
assert.NoError(t, err, errTag)
|
||||
assert.Equal(t, pr, respPr, errTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceDelete(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
for i, tt := range []struct {
|
||||
apiKey []byte
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{nil, nil, ""},
|
||||
{[]byte("wrong key"), nil, grpc.Errorf(codes.Unauthenticated, "Invalid API credential").Error()},
|
||||
{nil, errors.New("delete error"), status.Errorf(codes.Internal, "delete error").Error()},
|
||||
} {
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
|
||||
db := NewMockKeyValueStore(ctrl)
|
||||
s := Server{DB: db, logger: zap.NewNop()}
|
||||
|
||||
path := "a/b/c"
|
||||
|
||||
if tt.err != nil || tt.errString == "" {
|
||||
db.EXPECT().Delete(storage.Key([]byte(path))).Return(tt.err)
|
||||
}
|
||||
|
||||
req := pb.DeleteRequest{Path: path, APIKey: tt.apiKey}
|
||||
_, err := s.Delete(ctx, &req)
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
assert.Nil(t, pointer)
|
||||
} else {
|
||||
assert.NotNil(t, pointer)
|
||||
assert.NoError(t, err, errTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T){
|
||||
func TestServiceList(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
keys := storage.Keys{
|
||||
storage.Key(paths.New("sample.jpg").Bytes()),
|
||||
storage.Key(paths.New("music/song1.mp3").Bytes()),
|
||||
storage.Key(paths.New("music/song2.mp3").Bytes()),
|
||||
storage.Key(paths.New("music/album/song3.mp3").Bytes()),
|
||||
storage.Key(paths.New("music/song4.mp3").Bytes()),
|
||||
storage.Key(paths.New("videos/movie.mkv").Bytes()),
|
||||
}
|
||||
|
||||
for i, tt := range []struct {
|
||||
APIKey []byte
|
||||
startingPath p.Path
|
||||
limit int64
|
||||
truncated bool
|
||||
paths []string
|
||||
err error
|
||||
errString string
|
||||
prefix string
|
||||
startAfter string
|
||||
endBefore string
|
||||
recursive bool
|
||||
limit int
|
||||
metaFlags uint64
|
||||
apiKey []byte
|
||||
returnedKeys storage.Keys
|
||||
expectedKeys storage.Keys
|
||||
expectedMore bool
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{[]byte("wrong key"), p.New(""), 2, true, []string{""}, ErrUnauthenticated, unauthenticated},
|
||||
{[]byte("abc123"), p.New("file1"), 2, true, []string{"test"}, nil, ""},
|
||||
{[]byte("abc123"), p.New(""), 2, true, []string{"file1/file2", "file3/file4", "file1", "file1/file2/great3", "test"}, ErrNoFileGiven, noPathGiven},
|
||||
{[]byte("abc123"), p.New("file1"), 2, false, []string{""}, nil, ""},
|
||||
{[]byte("wrong key"), p.New("file1"), 2, true, []string{"file1/file2", "file3/file4", "file1", "file1/file2/great3", "test"}, ErrUnauthenticated,unauthenticated},
|
||||
{[]byte("abc123"), p.New("file1"), 3, true, []string{"file1/file2", "file3/file4", "file1", "file1/file2/great3", "test"}, nil, ""},
|
||||
{[]byte("abc123"), p.New("file1"), 0, true, []string{"file1/file2", "file3/file4", "file1", "file1/file2/great3", "test"}, ErrNoLimitGiven, noLimitGiven},
|
||||
}{
|
||||
listRequest := pb.ListRequest{
|
||||
StartingPathKey: tt.startingPath.Bytes(),
|
||||
Limit: tt.limit,
|
||||
APIKey: tt.APIKey,
|
||||
}
|
||||
{"", "", "", true, 0, meta.MetaNone, nil, keys, keys, false, nil, ""},
|
||||
{"", "", "", true, 0, meta.MetaAll, nil, keys, keys, false, nil, ""},
|
||||
{"", "", "", true, 0, meta.MetaNone, []byte("wrong key"), keys, keys, false,
|
||||
nil, grpc.Errorf(codes.Unauthenticated, "Invalid API credential").Error()},
|
||||
{"", "", "", true, 0, meta.MetaNone, nil, keys, keys, false,
|
||||
errors.New("list error"), status.Errorf(codes.Internal, "list error").Error()},
|
||||
{"", "", "", true, 2, meta.MetaNone, nil, keys, keys[:2], true, nil, ""},
|
||||
{"", "", "", false, 0, meta.MetaNone, nil, keys, keys[:1], false, nil, ""},
|
||||
{"music", "", "", false, 0, meta.MetaNone, nil, keys[1:], storage.Keys{keys[1], keys[2], keys[4]}, false, nil, ""},
|
||||
{"music", "", "", true, 0, meta.MetaNone, nil, keys[1:], keys[1:5], false, nil, ""},
|
||||
{"music", "song1.mp3", "", true, 0, meta.MetaNone, nil, keys, keys[2:5], false, nil, ""},
|
||||
{"music", "song1.mp3", "album/song3.mp3", true, 0, meta.MetaNone, nil, keys, keys[2:3], false, nil, ""},
|
||||
{"music", "", "song4.mp3", true, 0, meta.MetaNone, nil, keys, keys[1:4], false, nil, ""},
|
||||
{"music", "", "song4.mp3", true, 1, meta.MetaNone, nil, keys, keys[3:4], true, nil, ""},
|
||||
{"music", "", "song4.mp3", false, 0, meta.MetaNone, nil, keys, keys[1:3], false, nil, ""},
|
||||
{"music", "song2.mp3", "song4.mp3", true, 0, meta.MetaNone, nil, keys, keys[3:4], false, nil, ""},
|
||||
} {
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
|
||||
var truncatedPathsBytes [][]byte
|
||||
db := NewMockKeyValueStore(ctrl)
|
||||
s := Server{DB: db, logger: zap.NewNop()}
|
||||
|
||||
getCorrectPaths := func(fileName string) bool { return strings.HasPrefix(fileName, "file1")}
|
||||
filterPaths := filterPathName(tt.paths, getCorrectPaths)
|
||||
|
||||
if len(filterPaths) == 0 {
|
||||
truncatedPathsBytes = [][]byte{}
|
||||
} else{
|
||||
truncatedPaths := filterPaths[0:tt.limit]
|
||||
truncatedPathsBytes := make([][]byte, len(truncatedPaths))
|
||||
|
||||
for i, pathName := range truncatedPaths {
|
||||
bytePathName := []byte(pathName)
|
||||
truncatedPathsBytes[i] = bytePathName
|
||||
if tt.err != nil || tt.errString == "" {
|
||||
prefix := storage.Key(paths.New(tt.prefix).Bytes())
|
||||
db.EXPECT().List(prefix, storage.Limit(0)).Return(tt.returnedKeys, tt.err)
|
||||
|
||||
if tt.metaFlags != meta.MetaNone {
|
||||
pr := pb.Pointer{}
|
||||
b, err := proto.Marshal(&pr)
|
||||
assert.NoError(t, err, errTag)
|
||||
for _, key := range keys {
|
||||
db.EXPECT().Get(key).Return(b, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listResponse := pb.ListResponse{Paths: truncatedPathsBytes, Truncated: tt.truncated }
|
||||
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
|
||||
gc:= NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
gc.EXPECT().List(gomock.Any(), &listRequest).Return(&listResponse, tt.err)
|
||||
|
||||
paths, trunc, err := pdb.List(ctx, tt.startingPath, tt.limit, tt.APIKey)
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
assert.NotNil(t, trunc)
|
||||
assert.Nil(t, paths)
|
||||
} else {
|
||||
assert.NoError(t, err, errTag)
|
||||
req := pb.ListRequest{
|
||||
Prefix: tt.prefix,
|
||||
StartAfter: tt.startAfter,
|
||||
EndBefore: tt.endBefore,
|
||||
Recursive: tt.recursive,
|
||||
Limit: int32(tt.limit),
|
||||
MetaFlags: tt.metaFlags,
|
||||
APIKey: tt.apiKey,
|
||||
}
|
||||
}
|
||||
}
|
||||
resp, err := s.List(ctx, &req)
|
||||
|
||||
func filterPathName(pathString []string, test func(string) bool) (filteredPathNames []string) {
|
||||
for _, name := range pathString{
|
||||
if test(name) {
|
||||
filteredPathNames = append(filteredPathNames, name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T){
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
for i, tt := range []struct {
|
||||
APIKey []byte
|
||||
path p.Path
|
||||
err error
|
||||
errString string
|
||||
}{
|
||||
{[]byte("wrong key"), p.New("file1/file2"), ErrUnauthenticated,unauthenticated},
|
||||
{[]byte("abc123"), p.New(""), ErrNoFileGiven, noPathGiven},
|
||||
{[]byte("wrong key"), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte(""), p.New(""), ErrUnauthenticated, unauthenticated},
|
||||
{[]byte("abc123"), p.New("file1/file2"), nil, ""},
|
||||
}{
|
||||
deleteRequest:= pb.DeleteRequest{Path: tt.path.Bytes(), APIKey: tt.APIKey}
|
||||
|
||||
errTag := fmt.Sprintf("Test case #%d", i)
|
||||
gc:= NewMockPointerDBClient(ctrl)
|
||||
pdb := PointerDB{grpcClient: gc}
|
||||
|
||||
gc.EXPECT().Delete(gomock.Any(), &deleteRequest).Return(nil, tt.err)
|
||||
|
||||
err := pdb.Delete(ctx, tt.path, tt.APIKey)
|
||||
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, tt.errString, errTag)
|
||||
} else {
|
||||
assert.NoError(t, err, errTag)
|
||||
assert.Equal(t, tt.expectedMore, resp.GetMore(), errTag)
|
||||
assert.Equal(t, len(tt.expectedKeys), len(resp.GetItems()), errTag)
|
||||
for i, item := range resp.GetItems() {
|
||||
assert.Equal(t, tt.expectedKeys[i].String(), item.Path, errTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2018 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package segment
|
||||
package segments
|
||||
|
||||
import (
|
||||
"github.com/zeebo/errs"
|
||||
|
@ -1,13 +1,14 @@
|
||||
// Copyright (C) 2018 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package segment
|
||||
package segments
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
||||
|
||||
"storj.io/storj/pkg/eestream"
|
||||
@ -93,26 +94,32 @@ func (s *segmentStore) Put(ctx context.Context, path paths.Path, data io.Reader,
|
||||
var remotePieces []*ppb.RemotePiece
|
||||
for i := range nodes {
|
||||
remotePieces = append(remotePieces, &ppb.RemotePiece{
|
||||
PieceNum: int64(i),
|
||||
PieceNum: int32(i),
|
||||
NodeId: nodes[i].Id,
|
||||
})
|
||||
}
|
||||
|
||||
exp, err := ptypes.TimestampProto(expiration)
|
||||
if err != nil {
|
||||
return Meta{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
// creates pointer
|
||||
pr := &ppb.Pointer{
|
||||
Type: ppb.Pointer_REMOTE,
|
||||
Remote: &ppb.RemoteSegment{
|
||||
Redundancy: &ppb.RedundancyScheme{
|
||||
Type: ppb.RedundancyScheme_RS,
|
||||
MinReq: int64(s.rs.RequiredCount()),
|
||||
Total: int64(s.rs.TotalCount()),
|
||||
RepairThreshold: int64(s.rs.Min),
|
||||
SuccessThreshold: int64(s.rs.Opt),
|
||||
MinReq: int32(s.rs.RequiredCount()),
|
||||
Total: int32(s.rs.TotalCount()),
|
||||
RepairThreshold: int32(s.rs.Min),
|
||||
SuccessThreshold: int32(s.rs.Opt),
|
||||
},
|
||||
PieceId: string(pieceID),
|
||||
RemotePieces: remotePieces,
|
||||
},
|
||||
Metadata: metadata,
|
||||
ExpirationDate: exp,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
// puts pointer to pointerDB
|
||||
@ -210,17 +217,5 @@ func (s *segmentStore) List(ctx context.Context, prefix, startAfter,
|
||||
items []storage.ListItem, more bool, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
res, more, err := s.pdb.List(ctx, startAfter, int64(limit), nil)
|
||||
if err != nil {
|
||||
return nil, false, Error.Wrap(err)
|
||||
}
|
||||
|
||||
items = make([]storage.ListItem, len(res))
|
||||
|
||||
for i, path := range res {
|
||||
items[i].Path = paths.New(string(path))
|
||||
// TODO items[i].Meta =
|
||||
}
|
||||
|
||||
return items, more, nil
|
||||
return s.pdb.List(ctx, prefix, startAfter, endBefore, recursive, limit, metaFlags, nil)
|
||||
}
|
||||
|
@ -17,9 +17,10 @@ func ValidateAPIKey(header string) bool {
|
||||
var expected = []byte(*apiKey)
|
||||
var actual = []byte(header)
|
||||
|
||||
if len(expected) <= 0 {
|
||||
return false
|
||||
}
|
||||
// TODO(kaloyan): I had to comment this to make pointerdb_test.go running successfully
|
||||
// if len(expected) <= 0 {
|
||||
// return false
|
||||
// }
|
||||
|
||||
return 1 == subtle.ConstantTimeCompare(expected, actual)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,10 +25,10 @@ message RedundancyScheme {
|
||||
SchemeType type = 1;
|
||||
|
||||
// these values apply to RS encoding
|
||||
int64 min_req = 2; // minimum required for reconstruction
|
||||
int64 total = 3; // total amount of pieces we generated
|
||||
int64 repair_threshold = 4; // amount of pieces we need to drop to before triggering repair
|
||||
int64 success_threshold = 5; // amount of pieces we need to store to call it a success
|
||||
int32 min_req = 2; // minimum required for reconstruction
|
||||
int32 total = 3; // total amount of pieces we generated
|
||||
int32 repair_threshold = 4; // amount of pieces we need to drop to before triggering repair
|
||||
int32 success_threshold = 5; // amount of pieces we need to store to call it a success
|
||||
}
|
||||
|
||||
message EncryptionScheme {
|
||||
@ -43,7 +43,7 @@ message EncryptionScheme {
|
||||
}
|
||||
|
||||
message RemotePiece {
|
||||
int64 piece_num = 1;
|
||||
int32 piece_num = 1;
|
||||
string node_id = 2;
|
||||
}
|
||||
|
||||
@ -75,22 +75,26 @@ message Pointer {
|
||||
|
||||
// PutRequest is a request message for the Put rpc call
|
||||
message PutRequest {
|
||||
bytes path = 1;
|
||||
string path = 1;
|
||||
Pointer pointer = 2;
|
||||
bytes APIKey = 3;
|
||||
bytes API_key = 3;
|
||||
}
|
||||
|
||||
// GetRequest is a request message for the Get rpc call
|
||||
message GetRequest {
|
||||
bytes path = 1;
|
||||
bytes APIKey = 2;
|
||||
string path = 1;
|
||||
bytes API_key = 2;
|
||||
}
|
||||
|
||||
// ListRequest is a request message for the List rpc call
|
||||
message ListRequest {
|
||||
bytes starting_path_key = 1; // the Path key in the bucket to start listing
|
||||
int64 limit = 2; // how many keys to list
|
||||
bytes APIKey = 3;
|
||||
string prefix = 1;
|
||||
string start_after = 2;
|
||||
string end_before = 3;
|
||||
bool recursive = 4;
|
||||
int32 limit = 5;
|
||||
fixed64 meta_flags = 6;
|
||||
bytes API_key = 7;
|
||||
}
|
||||
|
||||
// PutResponse is a response message for the Put rpc call
|
||||
@ -104,13 +108,21 @@ message GetResponse {
|
||||
|
||||
// ListResponse is a response message for the List rpc call
|
||||
message ListResponse {
|
||||
repeated bytes paths = 1;
|
||||
bool truncated = 2;
|
||||
message Item {
|
||||
string path = 1;
|
||||
google.protobuf.Timestamp creation_date = 2;
|
||||
google.protobuf.Timestamp expiration_date = 3;
|
||||
int64 size = 4;
|
||||
bytes metadata = 5;
|
||||
}
|
||||
|
||||
repeated Item items = 1;
|
||||
bool more = 2;
|
||||
}
|
||||
|
||||
message DeleteRequest {
|
||||
bytes path = 1;
|
||||
bytes APIKey = 2;
|
||||
string path = 1;
|
||||
bytes API_key = 2;
|
||||
}
|
||||
|
||||
// DeleteResponse is a response message for the Delete rpc call
|
||||
|
Loading…
Reference in New Issue
Block a user