refactor(storage): read from local disk for putObject
This commit is contained in:
1 parent
7263d08bc5
commit
39a07252b6
10 files changed
+471
-1213
No files matched your search
@@ -99,10 +99,6 @@ func (tpc *WrappingPluginStorageClient) GetObject(ctx context.Context, req *plgp
|
||||
return stream.client, nil
|
||||
}
|
||||
|
||||
func (tpc *WrappingPluginStorageClient) PutObject(ctx context.Context, opts ...grpc.CallOption) (plgpb.StoragePluginService_PutObjectClient, error) {
|
||||
stream := newPutObjectStream()
|
||||
if err := tpc.Server.PutObject(stream.server); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream.client, nil
|
||||
func (tpc *WrappingPluginStorageClient) PutObject(ctx context.Context, req *plgpb.PutObjectRequest, opts ...grpc.CallOption) (*plgpb.PutObjectResponse, error) {
|
||||
return tpc.Server.PutObject(ctx, req)
|
||||
}
|
||||
@@ -52,8 +52,9 @@ func NewLoopbackPlugin(opt ...TestOption) (*LoopbackPlugin, error) {
|
||||
hostMap: make(map[string][]*loopbackPluginHostInfo),
|
||||
},
|
||||
LoopbackStorage: &LoopbackStorage{
|
||||
buckets: map[BucketName]Bucket{"default": {}},
|
||||
errs: make([]PluginMockError, 0),
|
||||
chunksSize: opts.withChunkSize,
|
||||
buckets: map[BucketName]Bucket{"default": {}},
|
||||
errs: make([]PluginMockError, 0),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ type TestOption func(*TestOptions) error
|
||||
type TestOptions struct {
|
||||
withMockBuckets map[BucketName]Bucket
|
||||
withMockError []PluginMockError
|
||||
withChunkSize int
|
||||
}
|
||||
|
||||
// getTestOpts - iterate the inbound Options and return a struct
|
||||
@@ -49,7 +50,9 @@ func getTestOpts(opt ...TestOption) (TestOptions, error) {
|
||||
}
|
||||
|
||||
func getDefaultTestOptions() TestOptions {
|
||||
return TestOptions{}
|
||||
return TestOptions{
|
||||
withChunkSize: 8,
|
||||
}
|
||||
}
|
||||
|
||||
// WithMockBuckets provides an option to create mocked external object store buckets.
|
||||
@@ -67,3 +70,11 @@ func WithMockError(err PluginMockError) TestOption {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithChunkSize provides an option to set the chunkSize used for grpc streams.
|
||||
func WithChunkSize(size int) TestOption {
|
||||
return func(o *TestOptions) error {
|
||||
o.withChunkSize = size
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+109
-124
@@ -9,6 +9,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -29,7 +30,7 @@ type TestPluginStorageServer struct {
|
||||
ValidatePermissionsFn func(context.Context, *plgpb.ValidatePermissionsRequest) (*plgpb.ValidatePermissionsResponse, error)
|
||||
HeadObjectFn func(context.Context, *plgpb.HeadObjectRequest) (*plgpb.HeadObjectResponse, error)
|
||||
GetObjectFn func(*plgpb.GetObjectRequest, plgpb.StoragePluginService_GetObjectServer) error
|
||||
PutObjectFn func(plgpb.StoragePluginService_PutObjectServer) error
|
||||
PutObjectFn func(context.Context, *plgpb.PutObjectRequest) (*plgpb.PutObjectResponse, error)
|
||||
plgpb.UnimplementedStoragePluginServiceServer
|
||||
}
|
||||
|
||||
@@ -75,11 +76,11 @@ func (t TestPluginStorageServer) GetObject(req *plgpb.GetObjectRequest, stream p
|
||||
return t.GetObjectFn(req, stream)
|
||||
}
|
||||
|
||||
func (t TestPluginStorageServer) PutObject(stream plgpb.StoragePluginService_PutObjectServer) error {
|
||||
func (t TestPluginStorageServer) PutObject(ctx context.Context, req *plgpb.PutObjectRequest) (*plgpb.PutObjectResponse, error) {
|
||||
if t.PutObjectFn == nil {
|
||||
return t.UnimplementedStoragePluginServiceServer.PutObject(stream)
|
||||
return t.UnimplementedStoragePluginServiceServer.PutObject(ctx, req)
|
||||
}
|
||||
return t.PutObjectFn(stream)
|
||||
return t.PutObjectFn(ctx, req)
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -106,19 +107,20 @@ type storagePluginStorageInfo struct {
|
||||
type LoopbackStorage struct {
|
||||
m sync.Mutex
|
||||
|
||||
buckets map[BucketName]Bucket
|
||||
errs []PluginMockError
|
||||
chunksSize int
|
||||
buckets map[BucketName]Bucket
|
||||
errs []PluginMockError
|
||||
}
|
||||
|
||||
func (l *LoopbackStorage) onCreateStorageBucket(ctx context.Context, req *plgpb.OnCreateStorageBucketRequest) (*plgpb.OnCreateStorageBucketResponse, error) {
|
||||
const op = "loopback.(LoopbackPlugin).onCreateStorageBucket"
|
||||
const op = "loopback.(LoopbackStorage).onCreateStorageBucket"
|
||||
if req == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: request is nil", op)
|
||||
}
|
||||
if req.Bucket == nil {
|
||||
if req.GetBucket() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing storage bucket", op)
|
||||
}
|
||||
if req.GetBucket().Secrets == nil {
|
||||
if req.GetBucket().GetSecrets() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing secrets", op)
|
||||
}
|
||||
l.m.Lock()
|
||||
@@ -139,14 +141,14 @@ func (l *LoopbackStorage) onCreateStorageBucket(ctx context.Context, req *plgpb.
|
||||
}
|
||||
|
||||
func (l *LoopbackStorage) onUpdateStorageBucket(ctx context.Context, req *plgpb.OnUpdateStorageBucketRequest) (*plgpb.OnUpdateStorageBucketResponse, error) {
|
||||
const op = "loopback.(LoopbackPlugin).onUpdateStorageBucket"
|
||||
const op = "loopback.(LoopbackStorage).onUpdateStorageBucket"
|
||||
if req == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: request is nil", op)
|
||||
}
|
||||
if req.NewBucket == nil {
|
||||
if req.GetNewBucket() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing storage bucket", op)
|
||||
}
|
||||
if req.GetNewBucket().Secrets == nil {
|
||||
if req.GetNewBucket().GetSecrets() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing secrets", op)
|
||||
}
|
||||
l.m.Lock()
|
||||
@@ -167,28 +169,28 @@ func (l *LoopbackStorage) onUpdateStorageBucket(ctx context.Context, req *plgpb.
|
||||
}
|
||||
|
||||
func (l *LoopbackStorage) onDeleteStorageBucket(ctx context.Context, req *plgpb.OnDeleteStorageBucketRequest) (*plgpb.OnDeleteStorageBucketResponse, error) {
|
||||
const op = "loopback.(LoopbackPlugin).onDeleteStorageBucket"
|
||||
const op = "loopback.(LoopbackStorage).onDeleteStorageBucket"
|
||||
if req == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: request is nil", op)
|
||||
}
|
||||
if req.Bucket == nil {
|
||||
if req.GetBucket() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing storage bucket", op)
|
||||
}
|
||||
if req.GetBucket().Secrets == nil {
|
||||
if req.GetBucket().GetSecrets() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing secrets", op)
|
||||
}
|
||||
return &plgpb.OnDeleteStorageBucketResponse{}, nil
|
||||
}
|
||||
|
||||
func (l *LoopbackStorage) validatePermissions(ctx context.Context, req *plgpb.ValidatePermissionsRequest) (*plgpb.ValidatePermissionsResponse, error) {
|
||||
const op = "loopback.(LoopbackPlugin).validatePermissions"
|
||||
const op = "loopback.(LoopbackStorage).validatePermissions"
|
||||
if req == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: request is nil", op)
|
||||
}
|
||||
if req.Bucket == nil {
|
||||
if req.GetBucket() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing storage bucket", op)
|
||||
}
|
||||
if req.GetBucket().Secrets == nil {
|
||||
if req.GetBucket().GetSecrets() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing secrets", op)
|
||||
}
|
||||
l.m.Lock()
|
||||
@@ -205,17 +207,17 @@ func (l *LoopbackStorage) validatePermissions(ctx context.Context, req *plgpb.Va
|
||||
}
|
||||
|
||||
func (l *LoopbackStorage) headObject(ctx context.Context, req *plgpb.HeadObjectRequest) (*plgpb.HeadObjectResponse, error) {
|
||||
const op = "loopback.(LoopbackPlugin).headObject"
|
||||
const op = "loopback.(LoopbackStorage).headObject"
|
||||
if req == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: request is nil", op)
|
||||
}
|
||||
if req.Bucket == nil {
|
||||
if req.GetBucket() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "$%s: missing storage bucket", op)
|
||||
}
|
||||
if req.GetBucket().Secrets == nil {
|
||||
if req.GetBucket().GetSecrets() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing secrets", op)
|
||||
}
|
||||
if req.Key == "" {
|
||||
if req.GetKey() == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s; missing object key", op)
|
||||
}
|
||||
l.m.Lock()
|
||||
@@ -245,17 +247,17 @@ func (l *LoopbackStorage) headObject(ctx context.Context, req *plgpb.HeadObjectR
|
||||
}
|
||||
|
||||
func (l *LoopbackStorage) getObject(req *plgpb.GetObjectRequest, stream plgpb.StoragePluginService_GetObjectServer) error {
|
||||
const op = "loopback.(LoopbackPlugin).getObject"
|
||||
const op = "loopback.(LoopbackStorage).getObject"
|
||||
if req == nil {
|
||||
return status.Errorf(codes.InvalidArgument, "%s: request is nil", op)
|
||||
}
|
||||
if req.Bucket == nil {
|
||||
if req.GetBucket() == nil {
|
||||
return status.Errorf(codes.InvalidArgument, "%s: missing storage bucket", op)
|
||||
}
|
||||
if req.GetBucket().Secrets == nil {
|
||||
if req.GetBucket().GetSecrets() == nil {
|
||||
return status.Errorf(codes.InvalidArgument, "%s: missing secrets", op)
|
||||
}
|
||||
if req.Key == "" {
|
||||
if req.GetKey() == "" {
|
||||
return status.Errorf(codes.InvalidArgument, "%s; missing object key", op)
|
||||
}
|
||||
l.m.Lock()
|
||||
@@ -288,110 +290,93 @@ func (l *LoopbackStorage) getObject(req *plgpb.GetObjectRequest, stream plgpb.St
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoopbackStorage) putObject(stream plgpb.StoragePluginService_PutObjectServer) error {
|
||||
const op = "loopback.(LoopbackPlugin).putObject"
|
||||
if stream == nil {
|
||||
return status.Errorf(codes.Internal, "%s: missing stream", op)
|
||||
func (l *LoopbackStorage) putObject(ctx context.Context, req *plgpb.PutObjectRequest) (*plgpb.PutObjectResponse, error) {
|
||||
const op = "loopback.(LoopbackStorage).putObject"
|
||||
if req == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: request is nil", op)
|
||||
}
|
||||
go func() {
|
||||
var (
|
||||
request *plgpb.PutObjectRequest_Request
|
||||
objectChunks []Chunk
|
||||
objectData []byte
|
||||
)
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
_ = stream.SendMsg(status.Errorf(codes.Internal, "%s: failed to recv message from stream: %v", op, err))
|
||||
return
|
||||
}
|
||||
switch data := req.Data.(type) {
|
||||
case *plgpb.PutObjectRequest_Request:
|
||||
if data.Request == nil {
|
||||
_ = stream.SendMsg(status.Errorf(codes.InvalidArgument, "%s: missing request metadata", op))
|
||||
return
|
||||
}
|
||||
if data.Request.GetBucket() == nil {
|
||||
_ = stream.SendMsg(status.Errorf(codes.InvalidArgument, "%s: missing storage bucket", op))
|
||||
return
|
||||
}
|
||||
if data.Request.GetBucket().GetSecrets() == nil {
|
||||
_ = stream.SendMsg(status.Errorf(codes.InvalidArgument, "%s: missing secrets", op))
|
||||
return
|
||||
}
|
||||
if data.Request.Key == "" {
|
||||
_ = stream.SendMsg(status.Errorf(codes.InvalidArgument, "%s; missing object key", op))
|
||||
return
|
||||
}
|
||||
request = data
|
||||
case *plgpb.PutObjectRequest_FileChunk:
|
||||
objectChunks = append(objectChunks, data.FileChunk)
|
||||
objectData = append(objectData, data.FileChunk...)
|
||||
case nil:
|
||||
continue
|
||||
default:
|
||||
_ = stream.SendMsg(status.Errorf(codes.Internal, "%s: unknown message type: %v", op, data))
|
||||
return
|
||||
}
|
||||
if req.GetBucket() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing storage bucket", op)
|
||||
}
|
||||
if req.GetBucket().GetBucketName() == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing bucket name", op)
|
||||
}
|
||||
if req.GetBucket().GetSecrets() == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing secrets", op)
|
||||
}
|
||||
if req.GetKey() == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s; missing object key", op)
|
||||
}
|
||||
if req.GetPath() == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s; missing path", op)
|
||||
}
|
||||
info, err := os.Stat(req.Path)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: file check failed: %v", op, err)
|
||||
}
|
||||
if info == nil {
|
||||
return nil, status.Errorf(codes.Internal, "%s: failed to get file info", op)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: path is a directory", op)
|
||||
}
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
bucket, ok := l.buckets[BucketName(req.GetBucket().GetBucketName())]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: bucket not found", op)
|
||||
}
|
||||
for _, err := range l.errs {
|
||||
if err.match(req.GetBucket(), req.GetKey()) {
|
||||
return nil, status.Errorf(err.errCode, "%s: %s", op, err.errMsg)
|
||||
}
|
||||
if request == nil {
|
||||
_ = stream.SendMsg(status.Errorf(codes.InvalidArgument, "%s: request is nil", op))
|
||||
return
|
||||
}
|
||||
if len(objectData) <= 0 {
|
||||
_ = stream.SendMsg(status.Errorf(codes.InvalidArgument, "%s: missing object data", op))
|
||||
return
|
||||
}
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
bucket, ok := l.buckets[BucketName(request.Request.GetBucket().GetBucketName())]
|
||||
if !ok {
|
||||
_ = stream.SendMsg(status.Errorf(codes.NotFound, "%s: bucket not found", op))
|
||||
return
|
||||
}
|
||||
for _, err := range l.errs {
|
||||
if err.match(request.Request.GetBucket(), request.Request.GetKey()) {
|
||||
_ = stream.SendMsg(status.Errorf(err.errCode, "%s: %s", op, err.errMsg))
|
||||
return
|
||||
}
|
||||
}
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, bytes.NewReader(objectData)); err != nil {
|
||||
_ = stream.SendMsg(status.Errorf(codes.Internal, "%s: failed to hash object: %v", op, err))
|
||||
return
|
||||
}
|
||||
contentLength := int64(len(objectData))
|
||||
lastModified := time.Now()
|
||||
}
|
||||
|
||||
// Ensure all the directories within the object key exist
|
||||
parts := strings.Split(request.Request.GetKey(), "/")
|
||||
tempPath := request.Request.GetBucket().GetBucketPrefix()
|
||||
for _, p := range parts[:len(parts)-1] {
|
||||
// Directories should have trailing `/` in the key
|
||||
tempPath = fmt.Sprintf("%v%v/", tempPath, p)
|
||||
bucket[ObjectName(tempPath)] = &storagePluginStorageInfo{
|
||||
lastModified: &lastModified,
|
||||
}
|
||||
}
|
||||
lastModified := time.Now()
|
||||
objectData, err := os.ReadFile(req.Path)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "%s: failed to read file", op)
|
||||
}
|
||||
if len(objectData) == 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%s: missing object data", op)
|
||||
}
|
||||
contentLength := int64(len(objectData))
|
||||
|
||||
// Now insert the object
|
||||
objectPath := ObjectName(request.Request.GetBucket().GetBucketPrefix() + request.Request.GetKey())
|
||||
bucket[objectPath] = &storagePluginStorageInfo{
|
||||
DataChunks: objectChunks,
|
||||
contentLength: &contentLength,
|
||||
lastModified: &lastModified,
|
||||
parts := strings.Split(req.GetKey(), "/")
|
||||
tempPath := req.GetBucket().GetBucketPrefix()
|
||||
for _, p := range parts[:len(parts)-1] {
|
||||
// Directories should have trailing `/` in the key
|
||||
tempPath = fmt.Sprintf("%v%v/", tempPath, p)
|
||||
bucket[ObjectName(tempPath)] = &storagePluginStorageInfo{
|
||||
lastModified: &lastModified,
|
||||
}
|
||||
if err := stream.SendAndClose(&plgpb.PutObjectResponse{
|
||||
ChecksumSha_256: hash.Sum(nil),
|
||||
}); err != nil {
|
||||
_ = stream.SendMsg(status.Errorf(codes.Internal, "%s: failed to close stream: %v", op, err))
|
||||
return
|
||||
}
|
||||
|
||||
objectChunks := []Chunk{}
|
||||
for i := 0; i < len(objectData); i = i + l.chunksSize {
|
||||
j := i + l.chunksSize
|
||||
if j > len(objectData) {
|
||||
j = len(objectData)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
objectChunks = append(objectChunks, copyBytes(objectData[i:j]))
|
||||
}
|
||||
|
||||
// Now insert the object
|
||||
objectPath := ObjectName(req.GetBucket().GetBucketPrefix() + req.GetKey())
|
||||
bucket[objectPath] = &storagePluginStorageInfo{
|
||||
DataChunks: objectChunks,
|
||||
contentLength: &contentLength,
|
||||
lastModified: &lastModified,
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, bytes.NewReader(objectData)); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "%s: failed to hash object: %v", op, err)
|
||||
}
|
||||
|
||||
return &plgpb.PutObjectResponse{
|
||||
ChecksumSha_256: hash.Sum(nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func MockObject(data []Chunk) *storagePluginStorageInfo {
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -666,62 +670,68 @@ func TestLoopbackGetObject(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoopbackPutObject(t *testing.T) {
|
||||
require, assert := tr.New(t), ta.New(t)
|
||||
require := tr.New(t)
|
||||
td := t.TempDir()
|
||||
|
||||
mockStorageMapData := map[BucketName]Bucket{
|
||||
"aws_s3_mock": {},
|
||||
"aws_s3_err": {},
|
||||
"object_store": {},
|
||||
"object_store_err": {},
|
||||
}
|
||||
|
||||
plg, err := NewLoopbackPlugin(
|
||||
WithMockBuckets(mockStorageMapData),
|
||||
WithMockError(PluginMockError{
|
||||
bucketName: "aws_s3_err",
|
||||
bucketName: "object_store_err",
|
||||
objectKey: "mock_object",
|
||||
errMsg: "invalid credentials",
|
||||
errCode: codes.PermissionDenied,
|
||||
}),
|
||||
)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
require.NotNil(plg)
|
||||
|
||||
client := NewWrappingPluginStorageClient(plg)
|
||||
|
||||
secrets := &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"AWS_ACCESS_KEY_ID": structpb.NewStringValue("access_key_id"),
|
||||
"AWS_SECRET_ACCESS_KEY": structpb.NewStringValue("secret_access_key"),
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
request *plgpb.PutObjectRequest
|
||||
dataChunks []Chunk
|
||||
expectedErr codes.Code
|
||||
name string
|
||||
request *plgpb.PutObjectRequest
|
||||
file *os.File
|
||||
expectedErr codes.Code
|
||||
expectedContent string
|
||||
}{
|
||||
{
|
||||
name: "missing request metadata",
|
||||
name: "missing-request",
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "missing-bucket",
|
||||
request: &plgpb.PutObjectRequest{},
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "missing-bucket-name",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{},
|
||||
Bucket: &storagebuckets.StorageBucket{},
|
||||
},
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "missing storage bucket",
|
||||
name: "missing-secrets",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{},
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store",
|
||||
},
|
||||
},
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "missing secrets",
|
||||
name: "missing-key",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_mock",
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -729,210 +739,210 @@ func TestLoopbackPutObject(t *testing.T) {
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "missing object key",
|
||||
name: "missing-path",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_mock",
|
||||
Secrets: secrets,
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "test-file",
|
||||
},
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "empty object data",
|
||||
dataChunks: []Chunk{
|
||||
[]byte(""),
|
||||
},
|
||||
name: "file-not-found",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_mock",
|
||||
Secrets: secrets,
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
Key: "mock_object",
|
||||
},
|
||||
},
|
||||
Key: "test-file",
|
||||
Path: path.Join(td, "test-file"),
|
||||
},
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "bucket not found",
|
||||
dataChunks: []Chunk{
|
||||
[]byte("THIS IS A MOCKED OBJECT"),
|
||||
},
|
||||
name: "path-is-directory",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "invalid_bucket",
|
||||
Secrets: secrets,
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
Key: "mock_object",
|
||||
},
|
||||
},
|
||||
Key: "test-directory",
|
||||
Path: path.Join(td, "test-directory"),
|
||||
},
|
||||
expectedErr: codes.NotFound,
|
||||
file: func() *os.File {
|
||||
err := os.Mkdir(path.Join(td, "test-directory"), fs.ModeAppend)
|
||||
require.NoError(err)
|
||||
return nil
|
||||
}(),
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "mock error",
|
||||
dataChunks: []Chunk{
|
||||
[]byte("THIS IS A MOCKED OBJECT"),
|
||||
},
|
||||
name: "bucket-not-found",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_err",
|
||||
Secrets: secrets,
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store_dne",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
Key: "mock_object",
|
||||
},
|
||||
},
|
||||
Key: "test-bucket-not-found",
|
||||
Path: path.Join(td, "test-bucket-not-found"),
|
||||
},
|
||||
file: func() *os.File {
|
||||
file, err := os.Create(path.Join(td, "test-bucket-not-found"))
|
||||
require.NoError(err)
|
||||
return file
|
||||
}(),
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "mock-error",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store_err",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "mock_object",
|
||||
Path: path.Join(td, "test-bucket-not-found"),
|
||||
},
|
||||
file: func() *os.File {
|
||||
file, err := os.Create(path.Join(td, "test-mock-error"))
|
||||
require.NoError(err)
|
||||
return file
|
||||
}(),
|
||||
expectedErr: codes.PermissionDenied,
|
||||
},
|
||||
{
|
||||
name: "valid object",
|
||||
dataChunks: []Chunk{
|
||||
[]byte("THIS IS A MOCKED OBJECT"),
|
||||
},
|
||||
name: "emtpy-object-data",
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_mock",
|
||||
Secrets: secrets,
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
Key: "mock_object",
|
||||
},
|
||||
},
|
||||
Key: "test-empty-object-data",
|
||||
Path: path.Join(td, "test-empty-object-data"),
|
||||
},
|
||||
file: func() *os.File {
|
||||
file, err := os.Create(path.Join(td, "test-empty-object-data"))
|
||||
require.NoError(err)
|
||||
require.NoError(file.Close())
|
||||
return file
|
||||
}(),
|
||||
expectedErr: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "valid object with dir in key",
|
||||
dataChunks: []Chunk{
|
||||
[]byte("THIS IS A MOCKED OBJECT"),
|
||||
},
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_mock",
|
||||
Secrets: secrets,
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
Key: "foo/bar/zoo/mocked_object",
|
||||
},
|
||||
},
|
||||
Key: "foo/bar/zoo/mocked_object",
|
||||
Path: path.Join(td, "test-valid-object-dir-in-key"),
|
||||
},
|
||||
file: func() *os.File {
|
||||
file, err := os.Create(path.Join(td, "test-valid-object-dir-in-key"))
|
||||
require.NoError(err)
|
||||
n, err := file.WriteString("TEST OBJ WITH DIR IN KEY!")
|
||||
require.NoError(err)
|
||||
require.Equal(len("TEST OBJ WITH DIR IN KEY!"), n)
|
||||
require.NoError(file.Close())
|
||||
return file
|
||||
}(),
|
||||
expectedContent: "TEST OBJ WITH DIR IN KEY!",
|
||||
},
|
||||
{
|
||||
name: "valid object w/ prefix",
|
||||
dataChunks: []Chunk{
|
||||
[]byte("THIS IS A MOCKED OBJECT"),
|
||||
},
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_mock",
|
||||
BucketPrefix: "/filtered/",
|
||||
Secrets: secrets,
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "object_store",
|
||||
BucketPrefix: "/filtered/",
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"SECRET_KEY_ID": structpb.NewStringValue("secret_key_id"),
|
||||
},
|
||||
Key: "mock_object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid object w/ multiple chunks",
|
||||
dataChunks: []Chunk{
|
||||
[]byte("PART A: 1234567890"),
|
||||
[]byte("PART B: 0987654321"),
|
||||
[]byte("PART C: qwertyuiop"),
|
||||
[]byte("PART D: poiuytrewq"),
|
||||
[]byte("PART E: asdfghjkl"),
|
||||
[]byte("PART F: lkjhgfdsa"),
|
||||
[]byte("PART G: zxcvbnm"),
|
||||
[]byte("PART H: mnbvcxz"),
|
||||
},
|
||||
request: &plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_mock",
|
||||
Secrets: secrets,
|
||||
},
|
||||
Key: "multi_chunk_object",
|
||||
},
|
||||
},
|
||||
Key: "mocked_object",
|
||||
Path: path.Join(td, "test-valid-object-prefix"),
|
||||
},
|
||||
file: func() *os.File {
|
||||
file, err := os.Create(path.Join(td, "test-valid-object-prefix"))
|
||||
require.NoError(err)
|
||||
n, err := file.WriteString("TEST OBJ WITH PREFIX!")
|
||||
require.NoError(err)
|
||||
require.Equal(len("TEST OBJ WITH PREFIX!"), n)
|
||||
require.NoError(file.Close())
|
||||
return file
|
||||
}(),
|
||||
expectedContent: "TEST OBJ WITH PREFIX!",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
stream, err := client.PutObject(context.Background())
|
||||
assert.NoError(err)
|
||||
assert.NotNil(stream)
|
||||
|
||||
var objectData []byte
|
||||
var closeResponse *plgpb.PutObjectResponse
|
||||
var closeErr error
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err = stream.Send(tt.request)
|
||||
assert.NoError(err)
|
||||
|
||||
for _, chunk := range tt.dataChunks {
|
||||
objectData = append(objectData, chunk...)
|
||||
err = stream.Send(&plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_FileChunk{
|
||||
FileChunk: chunk,
|
||||
},
|
||||
})
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
closeResponse, closeErr = stream.CloseAndRecv()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
resp, err := client.PutObject(context.Background(), tt.request)
|
||||
if tt.expectedErr != codes.OK {
|
||||
assert.Error(closeErr)
|
||||
assert.Nil(closeResponse)
|
||||
s, ok := status.FromError(closeErr)
|
||||
require.Error(err)
|
||||
require.Nil(resp)
|
||||
s, ok := status.FromError(err)
|
||||
require.True(ok, "invalid error type")
|
||||
assert.Equal(s.Code(), tt.expectedErr)
|
||||
require.Equal(s.Code(), tt.expectedErr)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
require.NotNil(resp)
|
||||
|
||||
assert.NoError(closeErr)
|
||||
require.NotNil(closeResponse)
|
||||
|
||||
var actualObject *storagePluginStorageInfo
|
||||
objectPath := ObjectName(tt.request.GetRequest().GetBucket().GetBucketPrefix() + tt.request.GetRequest().GetKey())
|
||||
if obj, ok := mockStorageMapData[BucketName(tt.request.GetRequest().GetBucket().GetBucketName())][objectPath]; ok {
|
||||
actualObject = obj
|
||||
actualBucket, ok := plg.buckets[BucketName(tt.request.Bucket.BucketName)]
|
||||
require.True(ok)
|
||||
require.NotEmpty(actualBucket)
|
||||
actualObject, ok := actualBucket[ObjectName(path.Join(tt.request.Bucket.BucketPrefix, tt.request.Key))]
|
||||
require.True(ok)
|
||||
require.NotEmpty(actualObject)
|
||||
actualData := []byte{}
|
||||
for _, c := range actualObject.DataChunks {
|
||||
actualData = append(actualData, c...)
|
||||
}
|
||||
require.NotNil(actualObject)
|
||||
assert.EqualValues(tt.dataChunks, actualObject.DataChunks)
|
||||
assert.NotEmpty(closeResponse.ChecksumSha_256)
|
||||
require.Equal(tt.expectedContent, string(actualData))
|
||||
|
||||
hash := sha256.New()
|
||||
_, err = io.Copy(hash, bytes.NewReader(objectData))
|
||||
_, err = io.Copy(hash, bytes.NewReader(actualData))
|
||||
require.NoError(err)
|
||||
assert.Equal(hash.Sum(nil), closeResponse.ChecksumSha_256)
|
||||
require.ElementsMatch(hash.Sum(nil), resp.ChecksumSha_256)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoopbackStoragePlugin(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
|
||||
require, assert := tr.New(t), ta.New(t)
|
||||
|
||||
mockStorageMapData := map[BucketName]Bucket{
|
||||
@@ -951,38 +961,31 @@ func TestLoopbackStoragePlugin(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
objectData := "THIS IS A MOCKED OBJECT"
|
||||
bucket := &storagebuckets.StorageBucket{
|
||||
BucketName: "aws_s3_mock",
|
||||
BucketPrefix: "/filtered/path/",
|
||||
Secrets: secrets,
|
||||
}
|
||||
objectData := []byte("THIS IS A MOCKED OBJECT")
|
||||
|
||||
putObjectStream, err := client.PutObject(context.Background())
|
||||
assert.NoError(err)
|
||||
|
||||
err = putObjectStream.Send(&plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_Request{
|
||||
Request: &plgpb.PutObjectMetadata{
|
||||
Bucket: bucket,
|
||||
Key: "dir1/mock_object",
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NoError(err)
|
||||
err = putObjectStream.Send(&plgpb.PutObjectRequest{
|
||||
Data: &plgpb.PutObjectRequest_FileChunk{
|
||||
FileChunk: objectData,
|
||||
},
|
||||
})
|
||||
assert.NoError(err)
|
||||
|
||||
putResponse, err := putObjectStream.CloseAndRecv()
|
||||
file, err := os.Create(path.Join(td, "test-put-object"))
|
||||
require.NoError(err)
|
||||
n, err := file.WriteString(objectData)
|
||||
require.NoError(err)
|
||||
require.Equal(len(objectData), n)
|
||||
require.NoError(file.Close())
|
||||
|
||||
putResponse, err := client.PutObject(
|
||||
context.Background(), &plgpb.PutObjectRequest{
|
||||
Bucket: bucket,
|
||||
Key: "dir1/mock_object",
|
||||
Path: path.Join(td, "test-put-object"),
|
||||
})
|
||||
assert.NoError(err)
|
||||
require.NotNil(putResponse)
|
||||
assert.NotEmpty(putResponse.GetChecksumSha_256())
|
||||
hash := sha256.New()
|
||||
_, err = io.Copy(hash, bytes.NewReader(objectData))
|
||||
_, err = io.Copy(hash, strings.NewReader(objectData))
|
||||
require.NoError(err)
|
||||
assert.EqualValues(hash.Sum(nil), putResponse.GetChecksumSha_256())
|
||||
|
||||
|
||||
@@ -229,274 +229,3 @@ func newGetObjectStream() *getObjectStream {
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
// putObjectStreamRequest is used to mock a message sent from the client to the server.
|
||||
type putObjectStreamRequest struct {
|
||||
msg *plgpb.PutObjectRequest
|
||||
err error
|
||||
}
|
||||
|
||||
// puObjectStreamResponse is used to mock a message sent from the server to the client.
|
||||
type putObjectStreamResponse struct {
|
||||
msg *plgpb.PutObjectResponse
|
||||
err error
|
||||
}
|
||||
|
||||
// putObjectStream is used to mock the interactions between
|
||||
// the client and server for the PutObject method.
|
||||
type putObjectStream struct {
|
||||
client plgpb.StoragePluginService_PutObjectClient
|
||||
server plgpb.StoragePluginService_PutObjectServer
|
||||
|
||||
// requests is used to mock the client sending messages to the server.
|
||||
requests chan *putObjectStreamRequest
|
||||
|
||||
// responses is used to mock the server sending messages to the client.
|
||||
responses chan *putObjectStreamResponse
|
||||
|
||||
m *sync.Mutex
|
||||
clientClosed bool
|
||||
serverClosed bool
|
||||
}
|
||||
|
||||
func (s *putObjectStream) CloseClient() {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
if s.clientClosed {
|
||||
return
|
||||
}
|
||||
close(s.requests)
|
||||
s.clientClosed = true
|
||||
}
|
||||
|
||||
func (s *putObjectStream) IsClientClosed() bool {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
return s.clientClosed
|
||||
}
|
||||
|
||||
func (s *putObjectStream) CloseServer() {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
if s.serverClosed {
|
||||
return
|
||||
}
|
||||
close(s.responses)
|
||||
s.serverClosed = true
|
||||
}
|
||||
|
||||
func (s *putObjectStream) IsServerClosed() bool {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
return s.serverClosed
|
||||
}
|
||||
|
||||
// putObjectClient is used to mock the client stream
|
||||
// interactions for the PutObject method.
|
||||
type putObjectClient struct {
|
||||
sendToServer chan *putObjectStreamRequest
|
||||
sentFromServer chan *putObjectStreamResponse
|
||||
|
||||
// closeStream is used to close the channels of the stream.
|
||||
// This is used to prevent the channels from being closed multiple times.
|
||||
// This is needed because the channel can be closed by the client or the server.
|
||||
closeStream func()
|
||||
|
||||
// isStreamClosed is used to check if the stream is closed.
|
||||
// This is needed because the channel can be closed by the client or the server.
|
||||
isStreamClosed func() bool
|
||||
}
|
||||
|
||||
// Send will send a message to the server.
|
||||
// Send will return an error if the request is nil.
|
||||
// Send will return an error if the stream is closed.
|
||||
func (c *putObjectClient) Send(req *plgpb.PutObjectRequest) error {
|
||||
if req == nil {
|
||||
return fmt.Errorf(`parameter arg "req PutObjectRequest" cannot be nil`)
|
||||
}
|
||||
if c.isStreamClosed() {
|
||||
return fmt.Errorf("stream is closed")
|
||||
}
|
||||
c.sendToServer <- &putObjectStreamRequest{msg: req}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseAndRecv will return an PutObjectResponse if the server returns one.
|
||||
// CloseAndRecv will return an error if the server returns an error.
|
||||
// CloseAndRecv will return an io.EOF error if the server closes the stream.
|
||||
// CloseAndRecv will close the stream used to send messages to the server.
|
||||
func (c *putObjectClient) CloseAndRecv() (*plgpb.PutObjectResponse, error) {
|
||||
c.closeStream()
|
||||
resp, ok := <-c.sentFromServer
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return resp.msg, resp.err
|
||||
}
|
||||
|
||||
// Header should not be used.
|
||||
// Header is implemeted to satisfy the grpc.ClientStream interface.
|
||||
// Header will always return an empty metadata and an nil error.
|
||||
func (c *putObjectClient) Header() (metadata.MD, error) {
|
||||
return make(metadata.MD), nil
|
||||
}
|
||||
|
||||
// Trailer should not be used.
|
||||
// Trailer is implemeted to satisfy the grpc.ClientStream interface.
|
||||
// Trailer will always return an empty metadata.
|
||||
func (c *putObjectClient) Trailer() metadata.MD {
|
||||
return make(metadata.MD)
|
||||
}
|
||||
|
||||
// CloseSend will close the channel used to retrieve messages from
|
||||
// the server. This will cause the CloseAndRecv method to return io.EOF.
|
||||
// CloseSend will always return a nill error.
|
||||
func (c *putObjectClient) CloseSend() error {
|
||||
if c.isStreamClosed() {
|
||||
return fmt.Errorf("stream is closed")
|
||||
}
|
||||
c.closeStream()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Context will always return a Background context.
|
||||
func (c *putObjectClient) Context() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
// SendMsg should not be used.
|
||||
// SendMsg is implemeted to satisfy the grpc.ClientStream interface.
|
||||
// SendMsg will always return an nil error.
|
||||
func (c *putObjectClient) SendMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecvMsg should not be used.
|
||||
// RecvMsg is implemeted to satisfy the grpc.ClientStream interface.
|
||||
// RecvMsg will always return an nil error.
|
||||
func (c *putObjectClient) RecvMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// putObjectServer is used to mock the server stream
|
||||
// interactions for the PutObject method.
|
||||
type putObjectServer struct {
|
||||
sentFromClient chan *putObjectStreamRequest
|
||||
sentToClient chan *putObjectStreamResponse
|
||||
|
||||
// closeStream is used to close the channels of the stream.
|
||||
// This is used to prevent the channels from being closed multiple times.
|
||||
// This is needed because the channel can be closed by the client or the server.
|
||||
closeStream func()
|
||||
|
||||
// isStreamClosed is used to check if the stream is closed.
|
||||
// This is needed because the channel can be closed by the client or the server.
|
||||
isStreamClosed func() bool
|
||||
}
|
||||
|
||||
// SendAndClose will send a PutObjectResponse to the client.
|
||||
// SendAndClose will return an error if the response is nil.
|
||||
// SendAndClose will close the stream used to send messages from the client.
|
||||
// SendAndClose will return an error if the stream is closed.
|
||||
func (s *putObjectServer) SendAndClose(resp *plgpb.PutObjectResponse) error {
|
||||
if resp == nil {
|
||||
return fmt.Errorf(`parameter arg "resp PutObjectResponse" cannot be nil`)
|
||||
}
|
||||
if s.isStreamClosed() {
|
||||
return fmt.Errorf("stream is closed")
|
||||
}
|
||||
s.sentToClient <- &putObjectStreamResponse{msg: resp}
|
||||
s.closeStream()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recv will read a PutObjectRequest from the stream.
|
||||
// Recv will return an io.EOF error if the stream is closed.
|
||||
func (s *putObjectServer) Recv() (*plgpb.PutObjectRequest, error) {
|
||||
req, ok := <-s.sentFromClient
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return req.msg, req.err
|
||||
}
|
||||
|
||||
// SetHeader should not be used.
|
||||
// SetHeader is implemeted to satisfy the grpc.ServerStream interface.
|
||||
// SetHeader will always return an nil error.
|
||||
func (s *putObjectServer) SetHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendHeader should not be used.
|
||||
// SendHeader is implemeted to satisfy the grpc.ServerStream interface.
|
||||
// SendHeader will always return an nil error.
|
||||
func (s *putObjectServer) SendHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTrailer should not be used.
|
||||
// SetTrailer is implemeted to satisfy the grpc.ServerStream interface.
|
||||
func (s *putObjectServer) SetTrailer(metadata.MD) {
|
||||
}
|
||||
|
||||
// Context will always return a Background context.
|
||||
func (s *putObjectServer) Context() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
// SendMsg allows sending errors other than io.EOF to the client.
|
||||
// Sending an error message will close the stream.
|
||||
// SendMsg allows sending PutObjectResponse messages to the client.
|
||||
// SendMsg returns an invalid argument error if the message is not
|
||||
// an error or PutObjectResponse.
|
||||
// SendMsg will return an error if the stream is closed.
|
||||
func (s *putObjectServer) SendMsg(m interface{}) error {
|
||||
switch msg := m.(type) {
|
||||
case *plgpb.PutObjectResponse:
|
||||
if s.isStreamClosed() {
|
||||
return fmt.Errorf("stream is closed")
|
||||
}
|
||||
s.sentToClient <- &putObjectStreamResponse{msg: msg}
|
||||
case error:
|
||||
if s.isStreamClosed() {
|
||||
return fmt.Errorf("stream is closed")
|
||||
}
|
||||
s.sentToClient <- &putObjectStreamResponse{err: msg}
|
||||
defer s.closeStream()
|
||||
default:
|
||||
return fmt.Errorf("invalid argument %v", m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecvMsg should not be used.
|
||||
// RecvMsg is implemeted to satisfy the grpc.ServerStream interface.
|
||||
// RecvMsg will always return an nil error.
|
||||
func (s *putObjectServer) RecvMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPutObjectStream will create a mock stream for the PutObject method.
|
||||
// The client and server stream is mocked by creating a PutObjectRequest,
|
||||
// PutObjectResponse and an error channel that is shared between the client
|
||||
// and server.
|
||||
func newPutObjectStream() *putObjectStream {
|
||||
stream := &putObjectStream{
|
||||
m: new(sync.Mutex),
|
||||
requests: make(chan *putObjectStreamRequest),
|
||||
responses: make(chan *putObjectStreamResponse),
|
||||
}
|
||||
stream.client = &putObjectClient{
|
||||
sendToServer: stream.requests,
|
||||
sentFromServer: stream.responses,
|
||||
closeStream: stream.CloseClient,
|
||||
isStreamClosed: stream.IsClientClosed,
|
||||
}
|
||||
stream.server = &putObjectServer{
|
||||
sentFromClient: stream.requests,
|
||||
sentToClient: stream.responses,
|
||||
closeStream: stream.CloseServer,
|
||||
isStreamClosed: stream.IsServerClosed,
|
||||
}
|
||||
return stream
|
||||
}
|
||||
@@ -227,242 +227,6 @@ func TestNewGetObjectStream(t *testing.T) {
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
func Test_PutObjectStream_Client(t *testing.T) {
|
||||
require := tr.New(t)
|
||||
|
||||
stream := newPutObjectStream()
|
||||
require.NotNil(stream)
|
||||
require.NotNil(stream.client)
|
||||
|
||||
// Validate Header is not nil and does not return an error
|
||||
header, err := stream.client.Header()
|
||||
require.NotNil(header)
|
||||
require.NoError(err)
|
||||
|
||||
// Validate Trailer is not nil
|
||||
trailer := stream.client.Trailer()
|
||||
require.NotNil(trailer)
|
||||
|
||||
// Validate Context is not nil
|
||||
ctx := stream.client.Context()
|
||||
require.NotNil(ctx)
|
||||
|
||||
// Validate SendMsg does not write to the channel requests
|
||||
err = stream.client.SendMsg(nil)
|
||||
require.NoError(err)
|
||||
require.Empty(stream.requests)
|
||||
|
||||
// Validate RecvMsg does not read from the channel requests
|
||||
streamSize := len(stream.requests)
|
||||
var msg *putObjectStreamResponse
|
||||
err = stream.client.RecvMsg(msg)
|
||||
require.NoError(err)
|
||||
require.Nil(msg)
|
||||
require.Equal(streamSize, len(stream.requests))
|
||||
|
||||
// Validate Send returns an error when the request is nil
|
||||
err = stream.client.Send(nil)
|
||||
require.Error(err)
|
||||
|
||||
// Validate Send writes to the channel requests
|
||||
var wg sync.WaitGroup
|
||||
go func() {
|
||||
err = stream.client.Send(&plgpb.PutObjectRequest{})
|
||||
require.NoError(err)
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
req, ok := <-stream.requests
|
||||
require.True(ok)
|
||||
require.NotNil(req)
|
||||
require.NotNil(req.msg)
|
||||
require.Nil(req.err)
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
// Validate CloseSend does not return an error
|
||||
// Validate clientClosed is set to true
|
||||
// Validate the channel requests is closed
|
||||
var ok bool
|
||||
err = stream.client.CloseSend()
|
||||
require.NoError(err)
|
||||
require.True(stream.clientClosed)
|
||||
_, ok = <-stream.requests
|
||||
require.False(ok)
|
||||
|
||||
// Validate CloseSend does return an error when the channel is closed
|
||||
// Validate clientClosed is set to true
|
||||
// Validate the channel requests is closed
|
||||
err = stream.client.CloseSend()
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "stream is closed")
|
||||
require.True(stream.clientClosed)
|
||||
_, ok = <-stream.requests
|
||||
require.False(ok)
|
||||
|
||||
// Validate Send returns an error when the channel is closed
|
||||
err = stream.client.Send(&plgpb.PutObjectRequest{})
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "stream is closed")
|
||||
require.Empty(stream.requests)
|
||||
|
||||
// Reset stream
|
||||
stream = newPutObjectStream()
|
||||
|
||||
// Validate CloseAndRecv returns an EOF error when the channel responses is closed
|
||||
// Validate CloseAndRecv closes the channel requests
|
||||
stream.CloseServer()
|
||||
resp, err := stream.client.CloseAndRecv()
|
||||
require.Equal(io.EOF, err)
|
||||
require.Nil(resp)
|
||||
require.True(stream.clientClosed)
|
||||
require.True(stream.IsClientClosed())
|
||||
require.True(stream.serverClosed)
|
||||
require.True(stream.IsServerClosed())
|
||||
}
|
||||
|
||||
func Test_PutObjectStream_Server(t *testing.T) {
|
||||
require := tr.New(t)
|
||||
|
||||
stream := newPutObjectStream()
|
||||
require.NotNil(stream)
|
||||
require.NotNil(stream.server)
|
||||
|
||||
// Validate SetHeader does not return an error
|
||||
err := stream.server.SetHeader(nil)
|
||||
require.NoError(err)
|
||||
|
||||
// Validate SendHeader does not return an error
|
||||
err = stream.server.SendHeader(nil)
|
||||
require.NoError(err)
|
||||
|
||||
// Validate Context returns a context
|
||||
ctx := stream.server.Context()
|
||||
require.NotNil(ctx)
|
||||
|
||||
// Validate RecvMsg does not read from the channel requests
|
||||
streamSize := len(stream.requests)
|
||||
var msg *putObjectStreamRequest
|
||||
err = stream.server.RecvMsg(msg)
|
||||
require.NoError(err)
|
||||
require.Nil(msg)
|
||||
require.Equal(streamSize, len(stream.requests))
|
||||
|
||||
// Validate SendMsg returns an error for unknown message types
|
||||
err = stream.server.SendMsg(map[string]string{})
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "invalid argument")
|
||||
|
||||
// Validate SendMsg does write a message to the channel responses
|
||||
var wg sync.WaitGroup
|
||||
go func() {
|
||||
err = stream.server.SendMsg(&plgpb.PutObjectResponse{})
|
||||
require.NoError(err)
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
resp, ok := <-stream.responses
|
||||
require.True(ok)
|
||||
require.NotNil(resp)
|
||||
require.NotNil(resp.msg)
|
||||
require.Nil(resp.err)
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
// Validate SendMsg does write a error to the channel responses
|
||||
// Validate SendMsg closes the channel responses
|
||||
go func() {
|
||||
err = stream.server.SendMsg(fmt.Errorf("mock error"))
|
||||
require.NoError(err)
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
resp, ok := <-stream.responses
|
||||
require.True(ok)
|
||||
require.NotNil(resp)
|
||||
require.NotNil(resp.err)
|
||||
require.Nil(resp.msg)
|
||||
}()
|
||||
wg.Wait()
|
||||
_, ok := <-stream.responses
|
||||
require.False(ok)
|
||||
require.True(stream.serverClosed)
|
||||
require.True(stream.IsServerClosed())
|
||||
|
||||
// Validate SendMsg does return an error when the channel responses is closed
|
||||
err = stream.server.SendMsg(&plgpb.PutObjectResponse{})
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "stream is closed")
|
||||
err = stream.server.SendMsg(fmt.Errorf("mock error"))
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "stream is closed")
|
||||
|
||||
// Validate SendAndClose returns an error when response is nil
|
||||
err = stream.server.SendAndClose(nil)
|
||||
require.Error(err)
|
||||
|
||||
// Validate SendAndClose returns an error when the channel responses is closed
|
||||
err = stream.server.SendAndClose(&plgpb.PutObjectResponse{})
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "stream is closed")
|
||||
|
||||
// Reset stream
|
||||
stream = newPutObjectStream()
|
||||
|
||||
// Validate SendAndClose closes the responses channel
|
||||
go func() {
|
||||
err = stream.server.SendAndClose(&plgpb.PutObjectResponse{})
|
||||
require.NoError(err)
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
resp, ok := <-stream.responses
|
||||
require.True(ok)
|
||||
require.NotNil(resp)
|
||||
require.NotNil(resp.msg)
|
||||
require.Nil(resp.err)
|
||||
}()
|
||||
wg.Wait()
|
||||
require.True(stream.serverClosed)
|
||||
require.True(stream.IsServerClosed())
|
||||
_, ok = <-stream.responses
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
func TestNewPutObjectStream(t *testing.T) {
|
||||
require := tr.New(t)
|
||||
|
||||
stream := newPutObjectStream()
|
||||
require.NotNil(stream)
|
||||
require.NotNil(stream.m)
|
||||
require.NotNil(stream.client)
|
||||
require.NotNil(stream.server)
|
||||
require.NotNil(stream.requests)
|
||||
require.NotNil(stream.responses)
|
||||
require.False(stream.clientClosed)
|
||||
require.False(stream.IsClientClosed())
|
||||
require.False(stream.serverClosed)
|
||||
require.False(stream.IsServerClosed())
|
||||
|
||||
// Validate CloseClient() closes the requests channel
|
||||
stream.CloseClient()
|
||||
require.True(stream.clientClosed)
|
||||
require.True(stream.IsClientClosed())
|
||||
_, ok := <-stream.requests
|
||||
require.False(ok)
|
||||
|
||||
// Validate CloseServer() closes the responses channel
|
||||
stream.CloseServer()
|
||||
require.True(stream.serverClosed)
|
||||
require.True(stream.IsServerClosed())
|
||||
_, ok = <-stream.responses
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
func Test_GetObjectStream(t *testing.T) {
|
||||
require := tr.New(t)
|
||||
|
||||
@@ -508,95 +272,3 @@ func Test_GetObjectStream(t *testing.T) {
|
||||
_, ok = <-stream.messages
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
func Test_PutObjectStream(t *testing.T) {
|
||||
require := tr.New(t)
|
||||
|
||||
stream := newPutObjectStream()
|
||||
|
||||
// Validate the client recieves the response when the server closes the stream
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
resp, err := stream.client.CloseAndRecv()
|
||||
require.NotNil(resp)
|
||||
require.NoError(err)
|
||||
break
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := stream.server.SendAndClose(&plgpb.PutObjectResponse{})
|
||||
require.NoError(err)
|
||||
}()
|
||||
wg.Wait()
|
||||
require.True(stream.clientClosed)
|
||||
require.True(stream.IsClientClosed())
|
||||
require.True(stream.serverClosed)
|
||||
require.True(stream.IsServerClosed())
|
||||
_, ok := <-stream.requests
|
||||
require.False(ok)
|
||||
_, ok = <-stream.responses
|
||||
require.False(ok)
|
||||
|
||||
// Reset Stream
|
||||
stream = newPutObjectStream()
|
||||
|
||||
// Validate the client recieves an error when the server sends an error
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
resp, err := stream.client.CloseAndRecv()
|
||||
require.Nil(resp)
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "mock error")
|
||||
break
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := stream.server.SendMsg(fmt.Errorf("mock error"))
|
||||
require.NoError(err)
|
||||
}()
|
||||
wg.Wait()
|
||||
require.True(stream.clientClosed)
|
||||
require.True(stream.IsClientClosed())
|
||||
require.True(stream.serverClosed)
|
||||
require.True(stream.IsServerClosed())
|
||||
_, ok = <-stream.requests
|
||||
require.False(ok)
|
||||
_, ok = <-stream.responses
|
||||
require.False(ok)
|
||||
|
||||
// Reset Stream
|
||||
stream = newPutObjectStream()
|
||||
|
||||
// Validate the server recieves an EOF error when the client closes the stream
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
req, err := stream.server.Recv()
|
||||
if err != nil {
|
||||
require.Nil(req)
|
||||
require.Equal(io.EOF, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
wg.Done()
|
||||
err := stream.client.CloseSend()
|
||||
require.NoError(err)
|
||||
}()
|
||||
wg.Wait()
|
||||
require.True(stream.clientClosed)
|
||||
require.True(stream.IsClientClosed())
|
||||
require.False(stream.serverClosed)
|
||||
require.False(stream.IsServerClosed())
|
||||
_, ok = <-stream.requests
|
||||
require.False(ok)
|
||||
}
|
||||
Reference in new issue
Block a user