test(storage): improve loopback mock err & update storage container closed err msg

pull/3251/head
Damian Debkowski 3 years ago committed by Timothy Messier
parent cfe0dc08d9
commit 95c3b2d56a
No known key found for this signature in database
GPG Key ID: EFD2F184F7600572

@ -56,7 +56,7 @@ const (
// Note: Storage errors are currently unused in OSS
StorageFileClosed = 126 // StorageFileClose represents an error when a file has been closed and a read/write operation is attempted on it
StorageContainerClosed = 127 // StorageContainerClosed represents an error when a container has been closed and a read/write operation is attempted on it
StorageContainerClosed = 127 // StorageContainerClosed represents an error when a container has been closed and a I/O operation is attempted on it
StorageFileReadOnly = 128 // StorageFileReadOnly represents an error when a file is readonly and a write operation is attempted on it
StorageFileWriteOnly = 129 // StorageFileWriteOnly represents an error when a file is write only and a read operation is attempted on it
StorageFileAlreadyExists = 130 // StorageFileAlreadyExists represents an error when a file already exists during an attempt to create it

@ -8,25 +8,77 @@ import (
"google.golang.org/grpc/codes"
)
// Method is used to determine if an error should be returned to a specific storage plugin method.
type Method uint8
const (
// Any will return an error for any of the method defined in the storage plugin.
Any Method = iota
// Will return an error for the OnCreateStorageBucket method
OnCreateStorageBucket
// Will return an error for the OnUpdateStorageBucket method
OnUpdateStorageBucket
// Will return an error for the OnDeleteStorageBucket method
OnDeleteStorageBucket
// Will return an error for the ValidatePermissions method
ValidatePermissions
// Will return an error for the HeadObject method
HeadObject
// Will return an error for the GetObject method
GetObject
// Will return an error for the PutObject method
PutObject
)
// PluginMockError is used to mock an error when interacting with an external object store.
type PluginMockError struct {
bucketName string
bucketPrefix string
objectKey string
errMsg string
errCode codes.Code
BucketName string
BucketPrefix string
ObjectKey string
ErrMsg string
ErrCode codes.Code
ErrMethod Method
}
func (e PluginMockError) match(bucket *storagebuckets.StorageBucket, key string) bool {
if key != "" && e.objectKey != key {
// match compares the given values from the parameters to the values provided in the mocked error.
// The bucket and key parameter values should be provided from the plugin request. The method
// value should be based on the plugin method that is calling this function.
//
// When match returns false, the mocked error should not be used for the plugin response.
// When match returns true, the mocked error should be used for the plugin response.
func (e PluginMockError) match(bucket *storagebuckets.StorageBucket, key string, method Method) bool {
// if the mocked error object key does not match the request's object key, return false.
// the object key comparison is ignored when the given key is empty because the following
// plugin methods do not provide key values: onCreateStorageBucket, onUpdateStorageBucket,
// onDeleteStorageBucket
if key != "" && e.ObjectKey != key {
return false
}
if e.bucketName != bucket.BucketName {
// if the mocked error bucket name does not match the request's bucket name, return false.
if e.BucketName != bucket.BucketName {
return false
}
if bucket.BucketPrefix != "" && e.bucketPrefix != bucket.BucketPrefix {
// if the request has a bucket prefix and it does not match the mocked error bucket prefix, return false.
if bucket.BucketPrefix != "" && e.BucketPrefix != bucket.BucketPrefix {
return false
}
// if the mocked error method is set to Any, return true. This means that the mocked error response
// will be utilized by all the plugin methods.
if e.ErrMethod == Any {
return true
}
// if the mocked error method does match the given method, return false.
if e.ErrMethod != method {
return false
}
// all checks comparison checks passed, return true.
return true
}

@ -10,6 +10,7 @@ import (
"fmt"
"io"
"os"
"path"
"strings"
"sync"
"time"
@ -130,8 +131,8 @@ func (l *LoopbackStorage) onCreateStorageBucket(ctx context.Context, req *plgpb.
return nil, status.Errorf(codes.NotFound, "%s: bucket not found", op)
}
for _, err := range l.errs {
if err.match(req.GetBucket(), "") {
return nil, status.Errorf(err.errCode, err.errMsg)
if err.match(req.GetBucket(), "", OnCreateStorageBucket) {
return nil, status.Errorf(err.ErrCode, err.ErrMsg)
}
}
return &plgpb.OnCreateStorageBucketResponse{
@ -158,8 +159,8 @@ func (l *LoopbackStorage) onUpdateStorageBucket(ctx context.Context, req *plgpb.
return nil, status.Errorf(codes.NotFound, "%s: bucket not found", op)
}
for _, err := range l.errs {
if err.match(req.GetNewBucket(), "") {
return nil, status.Errorf(err.errCode, "%s: %s", op, err.errMsg)
if err.match(req.GetNewBucket(), "", OnUpdateStorageBucket) {
return nil, status.Errorf(err.ErrCode, "%s: %s", op, err.ErrMsg)
}
}
var sec *storagebuckets.StorageBucketPersisted
@ -206,8 +207,8 @@ func (l *LoopbackStorage) validatePermissions(ctx context.Context, req *plgpb.Va
return nil, status.Errorf(codes.NotFound, "%s: bucket not found", op)
}
for _, err := range l.errs {
if err.match(req.GetBucket(), "") {
return nil, status.Errorf(err.errCode, "%s: %s", op, err.errMsg)
if err.match(req.GetBucket(), "", ValidatePermissions) {
return nil, status.Errorf(err.ErrCode, "%s: %s", op, err.ErrMsg)
}
}
return &plgpb.ValidatePermissionsResponse{}, nil
@ -239,8 +240,8 @@ func (l *LoopbackStorage) headObject(ctx context.Context, req *plgpb.HeadObjectR
return nil, status.Errorf(codes.NotFound, "%s: object %s not found", op, objectPath)
}
for _, err := range l.errs {
if err.match(req.GetBucket(), req.GetKey()) {
return nil, status.Errorf(err.errCode, "%s: %s", op, err.errMsg)
if err.match(req.GetBucket(), req.GetKey(), HeadObject) {
return nil, status.Errorf(err.ErrCode, "%s: %s", op, err.ErrMsg)
}
}
var contentLength int64
@ -279,8 +280,8 @@ func (l *LoopbackStorage) getObject(req *plgpb.GetObjectRequest, stream plgpb.St
return status.Errorf(codes.NotFound, "%s: object %s not found", op, objectPath)
}
for _, err := range l.errs {
if err.match(req.GetBucket(), req.GetKey()) {
return status.Errorf(err.errCode, "%s: %s", op, err.errMsg)
if err.match(req.GetBucket(), req.GetKey(), GetObject) {
return status.Errorf(err.ErrCode, "%s: %s", op, err.ErrMsg)
}
}
go func() {
@ -334,8 +335,8 @@ func (l *LoopbackStorage) putObject(ctx context.Context, req *plgpb.PutObjectReq
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 err.match(req.GetBucket(), req.GetKey(), PutObject) {
return nil, status.Errorf(err.ErrCode, "%s: %s", op, err.ErrMsg)
}
}
@ -354,8 +355,11 @@ func (l *LoopbackStorage) putObject(ctx context.Context, req *plgpb.PutObjectReq
for _, p := range parts[:len(parts)-1] {
// Directories should have trailing `/` in the key
tempPath = fmt.Sprintf("%v%v/", tempPath, p)
emptyContent := int64(0)
bucket[ObjectName(tempPath)] = &storagePluginStorageInfo{
lastModified: &lastModified,
lastModified: &lastModified,
contentLength: &emptyContent,
DataChunks: []Chunk{},
}
}
@ -369,7 +373,7 @@ func (l *LoopbackStorage) putObject(ctx context.Context, req *plgpb.PutObjectReq
}
// Now insert the object
objectPath := ObjectName(req.GetBucket().GetBucketPrefix() + req.GetKey())
objectPath := ObjectName(path.Join(req.GetBucket().GetBucketPrefix(), req.GetKey()))
bucket[objectPath] = &storagePluginStorageInfo{
DataChunks: objectChunks,
contentLength: &contentLength,
@ -386,6 +390,53 @@ func (l *LoopbackStorage) putObject(ctx context.Context, req *plgpb.PutObjectReq
}, nil
}
// CloneBucket returns a clone of the bucket.
// returns nil when the bucket is not found.
func (l *LoopbackStorage) CloneBucket(name string) Bucket {
l.m.Lock()
defer l.m.Unlock()
if _, ok := l.buckets[BucketName(name)]; !ok {
return nil
}
bucket := Bucket{}
for objName, obj := range l.buckets[BucketName(name)] {
if obj != nil {
bucket[objName] = copyStorageInfo(obj)
}
}
return bucket
}
// CloneStorageInfo returns a clone of the object stored in memory.
// Returns nil when the bucket or object is not found.
func (l *LoopbackStorage) CloneStorageInfo(bucketName, objectName string) *storagePluginStorageInfo {
l.m.Lock()
defer l.m.Unlock()
bucket, ok := l.buckets[BucketName(bucketName)]
if !ok {
return nil
}
obj, ok := bucket[ObjectName(objectName)]
if !ok {
return nil
}
return copyStorageInfo(obj)
}
func copyStorageInfo(obj *storagePluginStorageInfo) *storagePluginStorageInfo {
chunks := make([]Chunk, len(obj.DataChunks))
for i, c := range obj.DataChunks {
chunks[i] = copyBytes(c)
}
contentLength := *obj.contentLength
lastModified := *obj.lastModified
return &storagePluginStorageInfo{
DataChunks: chunks,
lastModified: &lastModified,
contentLength: &contentLength,
}
}
func MockObject(data []Chunk) *storagePluginStorageInfo {
lastModified := time.Now()
dataChunks := make([]Chunk, len(data))

@ -33,9 +33,9 @@ func TestLoopbackOnCreateStorageBucket(t *testing.T) {
"aws_s3_err": {},
}),
WithMockError(PluginMockError{
bucketName: "aws_s3_err",
errMsg: "invalid credentials",
errCode: codes.PermissionDenied,
BucketName: "aws_s3_err",
ErrMsg: "invalid credentials",
ErrCode: codes.PermissionDenied,
}),
)
assert.NoError(err)
@ -131,9 +131,9 @@ func TestLoopbackOnUpdateStorageBucket(t *testing.T) {
"aws_s3_err": {},
}),
WithMockError(PluginMockError{
bucketName: "aws_s3_err",
errMsg: "invalid credentials",
errCode: codes.PermissionDenied,
BucketName: "aws_s3_err",
ErrMsg: "invalid credentials",
ErrCode: codes.PermissionDenied,
}),
)
assert.NoError(err)
@ -306,9 +306,9 @@ func TestLoopbackValidatePermissions(t *testing.T) {
"aws_s3_err": {},
}),
WithMockError(PluginMockError{
bucketName: "aws_s3_err",
errMsg: "invalid credentials",
errCode: codes.PermissionDenied,
BucketName: "aws_s3_err",
ErrMsg: "invalid credentials",
ErrCode: codes.PermissionDenied,
}),
)
assert.NoError(err)
@ -411,10 +411,10 @@ func TestLoopbackHeadObject(t *testing.T) {
plg, err := NewLoopbackPlugin(
WithMockBuckets(mockStorageMapData),
WithMockError(PluginMockError{
bucketName: "aws_s3_err",
objectKey: "mock_object",
errMsg: "invalid credentials",
errCode: codes.PermissionDenied,
BucketName: "aws_s3_err",
ObjectKey: "mock_object",
ErrMsg: "invalid credentials",
ErrCode: codes.PermissionDenied,
}),
)
assert.NoError(err)
@ -545,10 +545,10 @@ func TestLoopbackGetObject(t *testing.T) {
plg, err := NewLoopbackPlugin(
WithMockBuckets(mockStorageMapData),
WithMockError(PluginMockError{
bucketName: "aws_s3_err",
objectKey: "mock_object",
errMsg: "invalid credentials",
errCode: codes.PermissionDenied,
BucketName: "aws_s3_err",
ObjectKey: "mock_object",
ErrMsg: "invalid credentials",
ErrCode: codes.PermissionDenied,
}),
)
assert.NoError(err)
@ -693,10 +693,10 @@ func TestLoopbackPutObject(t *testing.T) {
plg, err := NewLoopbackPlugin(
WithMockBuckets(mockStorageMapData),
WithMockError(PluginMockError{
bucketName: "object_store_err",
objectKey: "mock_object",
errMsg: "invalid credentials",
errCode: codes.PermissionDenied,
BucketName: "object_store_err",
ObjectKey: "mock_object",
ErrMsg: "invalid credentials",
ErrCode: codes.PermissionDenied,
}),
)
require.NoError(err)

Loading…
Cancel
Save