feat(plugin): Implement GetObject
This commit is contained in:
1 parent
b335b9336b
commit
4ffe4d227f
2 files changed
+516
-2
No files matched your search
@@ -4,6 +4,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@@ -27,6 +28,10 @@ import (
|
|||||||
|
|
||||||
var _ pb.StoragePluginServiceServer = (*StoragePlugin)(nil)
|
var _ pb.StoragePluginServiceServer = (*StoragePlugin)(nil)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultGetObjectChunkSize = uint32(65536)
|
||||||
|
)
|
||||||
|
|
||||||
// StoragePlugin implements the StoragePluginServiceServer interface for the
|
// StoragePlugin implements the StoragePluginServiceServer interface for the
|
||||||
// MinIO storage service plugin.
|
// MinIO storage service plugin.
|
||||||
type StoragePlugin struct {
|
type StoragePlugin struct {
|
||||||
@@ -246,7 +251,71 @@ func (sp *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReque
|
|||||||
|
|
||||||
// GetObject is a hook that retrieves objects.
|
// GetObject is a hook that retrieves objects.
|
||||||
func (sp *StoragePlugin) GetObject(req *pb.GetObjectRequest, objServer pb.StoragePluginService_GetObjectServer) error {
|
func (sp *StoragePlugin) GetObject(req *pb.GetObjectRequest, objServer pb.StoragePluginService_GetObjectServer) error {
|
||||||
return fmt.Errorf("unimplemented")
|
bucket := req.GetBucket()
|
||||||
|
if bucket == nil {
|
||||||
|
return status.Error(codes.InvalidArgument, "no storage bucket information found")
|
||||||
|
}
|
||||||
|
if bucket.GetBucketName() == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "storage bucket name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.GetKey() == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "object key is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
sa, err := getStorageAttributes(bucket.GetAttributes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sec, err := getStorageSecrets(bucket.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := minio.New(sa.EndpointUrl, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(sec.AccessKeyId, sec.SecretAccessKey, ""),
|
||||||
|
Secure: sa.UseSSL,
|
||||||
|
Region: sa.Region,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
||||||
|
obj, err := cl.GetObject(objServer.Context(), bucket.GetBucketName(), objKey, minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return status.Errorf(codes.Unknown, "failed to get object: %v", err)
|
||||||
|
}
|
||||||
|
defer obj.Close()
|
||||||
|
|
||||||
|
chunkSize := req.GetChunkSize()
|
||||||
|
if chunkSize == 0 {
|
||||||
|
chunkSize = defaultGetObjectChunkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
rd := bufio.NewReader(obj)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-objServer.Context().Done():
|
||||||
|
return status.Errorf(codes.Canceled, "server context done while streaming: %v", objServer.Context().Err())
|
||||||
|
default:
|
||||||
|
buffer := make([]byte, chunkSize)
|
||||||
|
n, err := rd.Read(buffer)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return status.Errorf(codes.Unknown, "failed to read chunk from minio object: %v", err)
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
err := objServer.Send(&pb.GetObjectResponse{FileChunk: buffer[:n]})
|
||||||
|
if err != nil {
|
||||||
|
return status.Errorf(codes.Unknown, "failed to send chunk to client: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObject is a hook that reads a file stored on local disk and stores it to
|
// PutObject is a hook that reads a file stored on local disk and stores it to
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -18,11 +19,13 @@ import (
|
|||||||
internaltest "github.com/hashicorp/boundary-plugin-minio/internal/testing"
|
internaltest "github.com/hashicorp/boundary-plugin-minio/internal/testing"
|
||||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
|
||||||
"github.com/hashicorp/boundary/sdk/pbs/plugin"
|
"github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/testing/protocmp"
|
"google.golang.org/protobuf/testing/protocmp"
|
||||||
@@ -125,6 +128,57 @@ var (
|
|||||||
}`)
|
}`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type getObjectServerMock struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
ctx context.Context
|
||||||
|
sendErr error
|
||||||
|
clientCtxDone bool
|
||||||
|
chunks []*pb.GetObjectResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context returns the object server's context.
|
||||||
|
func (s *getObjectServerMock) Context() context.Context {
|
||||||
|
if s.ctx == nil {
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
return s.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send streams a response message to the client.
|
||||||
|
func (g *getObjectServerMock) Send(rsp *pb.GetObjectResponse) error {
|
||||||
|
if g.sendErr != nil {
|
||||||
|
return g.sendErr
|
||||||
|
}
|
||||||
|
if rsp == nil {
|
||||||
|
return fmt.Errorf("getobject stream mock: nil response")
|
||||||
|
}
|
||||||
|
if g.chunks == nil {
|
||||||
|
g.chunks = []*pb.GetObjectResponse{}
|
||||||
|
}
|
||||||
|
g.chunks = append(g.chunks, rsp)
|
||||||
|
if g.clientCtxDone {
|
||||||
|
ctx, cancel := context.WithCancel(g.ctx)
|
||||||
|
g.ctx = ctx
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recv streams a response message from the server.
|
||||||
|
func (g *getObjectServerMock) Recv() (*pb.GetObjectResponse, error) {
|
||||||
|
if g.chunks == nil {
|
||||||
|
return nil, fmt.Errorf("getobject stream mock: nil chunks")
|
||||||
|
}
|
||||||
|
if len(g.chunks) == 0 {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop a chunk off and return it.
|
||||||
|
chunk := g.chunks[0]
|
||||||
|
g.chunks = g.chunks[1:]
|
||||||
|
return chunk, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestOnCreateStorageBucket(t *testing.T) {
|
func TestOnCreateStorageBucket(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
server := internaltest.NewMinioServer(t)
|
server := internaltest.NewMinioServer(t)
|
||||||
@@ -993,7 +1047,398 @@ func TestHeadObject(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetObject(t *testing.T) {}
|
func TestGetObject(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
server := internaltest.NewMinioServer(t)
|
||||||
|
|
||||||
|
bucketName := "test-bucket"
|
||||||
|
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in func(t *testing.T) (*plugin.GetObjectRequest, []byte)
|
||||||
|
mockSendErr error
|
||||||
|
mockCtxDone bool
|
||||||
|
expErrMsg string
|
||||||
|
expErrCode codes.Code
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nilBucket",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
return &pb.GetObjectRequest{Bucket: nil}, []byte{}
|
||||||
|
},
|
||||||
|
expErrMsg: "no storage bucket information found",
|
||||||
|
expErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noBucketName",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{BucketName: ""},
|
||||||
|
}, []byte{}
|
||||||
|
},
|
||||||
|
expErrMsg: "storage bucket name is required",
|
||||||
|
expErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptyObjectKey",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
},
|
||||||
|
Key: "",
|
||||||
|
}, []byte{}
|
||||||
|
},
|
||||||
|
expErrMsg: "object key is required",
|
||||||
|
expErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "badStorageAttributes",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
},
|
||||||
|
Key: "object-name",
|
||||||
|
}, []byte{}
|
||||||
|
},
|
||||||
|
expErrMsg: "empty attributes input",
|
||||||
|
expErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "badStorageSecrets",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: "object-name",
|
||||||
|
}, []byte{}
|
||||||
|
},
|
||||||
|
expErrMsg: "empty secrets input",
|
||||||
|
expErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getObjectErr",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: "invalid..bucket..name",
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://localhost:1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue("foo"),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: "object-name",
|
||||||
|
}, []byte{}
|
||||||
|
},
|
||||||
|
expErrMsg: "failed to get object: Bucket name contains invalid characters",
|
||||||
|
expErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "readObjectErr",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://localhost:1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue("foo"),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: "object-name",
|
||||||
|
}, []byte{}
|
||||||
|
},
|
||||||
|
expErrMsg: "failed to read chunk from minio object",
|
||||||
|
expErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "objectDoesntExist",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: "object-name",
|
||||||
|
}, []byte{}
|
||||||
|
},
|
||||||
|
expErrMsg: "failed to read chunk from minio object: The specified key does not exist.",
|
||||||
|
expErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "objServerSendErr",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
objName := uuid.New().String()
|
||||||
|
rd := bytes.NewReader([]byte("test"))
|
||||||
|
_, err := server.Client.PutObject(ctx, bucketName, objName, rd, rd.Size(), minio.PutObjectOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: objName,
|
||||||
|
}, []byte{}
|
||||||
|
},
|
||||||
|
mockSendErr: fmt.Errorf("oops error"),
|
||||||
|
expErrMsg: "failed to send chunk to client: oops error",
|
||||||
|
expErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contextCancellation",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
objName := uuid.New().String()
|
||||||
|
|
||||||
|
data := []byte{}
|
||||||
|
for i := 0; i < 8192; i += len(objName) {
|
||||||
|
data = append(data, []byte(objName)...)
|
||||||
|
}
|
||||||
|
rd := bytes.NewReader(data)
|
||||||
|
_, err := server.Client.PutObject(ctx, bucketName, objName, rd, rd.Size(), minio.PutObjectOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: objName,
|
||||||
|
ChunkSize: 2048,
|
||||||
|
}, data
|
||||||
|
},
|
||||||
|
mockCtxDone: true,
|
||||||
|
expErrMsg: "server context done while streaming: context canceled",
|
||||||
|
expErrCode: codes.Canceled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successSingleChunk",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
objName := uuid.New().String()
|
||||||
|
|
||||||
|
data := []byte{}
|
||||||
|
for i := 0; i < 8192; i += len(objName) {
|
||||||
|
data = append(data, []byte(objName)...)
|
||||||
|
}
|
||||||
|
rd := bytes.NewReader(data)
|
||||||
|
_, err := server.Client.PutObject(ctx, bucketName, objName, rd, rd.Size(), minio.PutObjectOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: objName,
|
||||||
|
ChunkSize: 16384,
|
||||||
|
}, data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successCustomPrefix",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
objName := uuid.New().String()
|
||||||
|
|
||||||
|
data := []byte{}
|
||||||
|
for i := 0; i < 8192; i += len(objName) {
|
||||||
|
data = append(data, []byte(objName)...)
|
||||||
|
}
|
||||||
|
rd := bytes.NewReader(data)
|
||||||
|
|
||||||
|
prefix := "prefix"
|
||||||
|
_, err := server.Client.PutObject(ctx, bucketName, path.Join(prefix, objName), rd, rd.Size(), minio.PutObjectOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
BucketPrefix: prefix,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: objName,
|
||||||
|
}, data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successMultipleChunksDefaultChunkSize",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
objName := uuid.New().String()
|
||||||
|
|
||||||
|
data := []byte{}
|
||||||
|
for i := 0; i < 102_400; i += len(objName) {
|
||||||
|
data = append(data, []byte(objName)...)
|
||||||
|
}
|
||||||
|
rd := bytes.NewReader(data)
|
||||||
|
_, err := server.Client.PutObject(ctx, bucketName, objName, rd, rd.Size(), minio.PutObjectOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: objName,
|
||||||
|
}, data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successMultipleChunksCustomChunkSize",
|
||||||
|
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
|
||||||
|
objName := uuid.New().String()
|
||||||
|
|
||||||
|
data := []byte{}
|
||||||
|
for i := 0; i < 10_420; i += len(objName) {
|
||||||
|
data = append(data, []byte(objName)...)
|
||||||
|
}
|
||||||
|
rd := bytes.NewReader(data)
|
||||||
|
_, err := server.Client.PutObject(ctx, bucketName, objName, rd, rd.Size(), minio.PutObjectOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &pb.GetObjectRequest{
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Key: objName,
|
||||||
|
ChunkSize: 4096,
|
||||||
|
}, data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
objServer := &getObjectServerMock{
|
||||||
|
ctx: ctx,
|
||||||
|
clientCtxDone: tt.mockCtxDone,
|
||||||
|
sendErr: tt.mockSendErr,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, expFileBytes := tt.in(t)
|
||||||
|
sp := new(StoragePlugin)
|
||||||
|
err := sp.GetObject(req, objServer)
|
||||||
|
if tt.expErrMsg != "" {
|
||||||
|
require.ErrorContains(t, err, tt.expErrMsg)
|
||||||
|
require.Equal(t, tt.expErrCode.String(), status.Code(err).String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expChunkSize := defaultGetObjectChunkSize
|
||||||
|
if req.ChunkSize > 0 {
|
||||||
|
expChunkSize = req.ChunkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
bb := bytes.NewBuffer([]byte{})
|
||||||
|
for {
|
||||||
|
rsp, err := objServer.Recv()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.LessOrEqual(t, uint32(len(rsp.GetFileChunk())), expChunkSize)
|
||||||
|
|
||||||
|
n, err := bb.Write(rsp.GetFileChunk())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, len(rsp.GetFileChunk()), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, expFileBytes, bb.Bytes())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPutObject(t *testing.T) {
|
func TestPutObject(t *testing.T) {
|
||||||
td := t.TempDir()
|
td := t.TempDir()
|
||||||
|
|||||||
Reference in new issue
Block a user