storj/satellite/buckets/db_test.go

309 lines
10 KiB
Go
Raw Normal View History

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package buckets_test
import (
"sort"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"storj.io/common/macaroon"
"storj.io/common/storj"
"storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/common/uuid"
"storj.io/storj/private/testplanet"
"storj.io/storj/satellite/buckets"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/metabase"
)
func newTestBucket(name string, projectID uuid.UUID) buckets.Bucket {
return buckets.Bucket{
ID: testrand.UUID(),
Name: name,
ProjectID: projectID,
PathCipher: storj.EncAESGCM,
DefaultSegmentsSize: 65536,
DefaultRedundancyScheme: storj.RedundancyScheme{
Algorithm: storj.ReedSolomon,
ShareSize: 9,
RequiredShares: 10,
RepairShares: 11,
OptimalShares: 12,
TotalShares: 13,
},
DefaultEncryptionParameters: storj.EncryptionParameters{
CipherSuite: storj.EncAESGCM,
BlockSize: 9 * 10,
},
Placement: storj.EU,
}
}
func TestBasicBucketOperations(t *testing.T) {
testplanet.Run(t, testplanet.Config{SatelliteCount: 1}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
db := sat.DB
consoleDB := db.Console()
project, err := consoleDB.Projects().Insert(ctx, &console.Project{Name: "testproject1"})
require.NoError(t, err)
bucketsDB := sat.API.Buckets.Service
expectedBucket := newTestBucket("testbucket", project.ID)
count, err := bucketsDB.CountBuckets(ctx, project.ID)
require.NoError(t, err)
require.Equal(t, 0, count)
// CreateBucket
_, err = bucketsDB.CreateBucket(ctx, expectedBucket)
require.NoError(t, err)
// GetBucket
bucket, err := bucketsDB.GetBucket(ctx, []byte("testbucket"), project.ID)
require.NoError(t, err)
require.Equal(t, expectedBucket.ID, bucket.ID)
require.Equal(t, expectedBucket.Name, bucket.Name)
require.Equal(t, expectedBucket.ProjectID, bucket.ProjectID)
require.Equal(t, expectedBucket.PathCipher, bucket.PathCipher)
require.Equal(t, expectedBucket.DefaultSegmentsSize, bucket.DefaultSegmentsSize)
require.Equal(t, expectedBucket.DefaultRedundancyScheme, bucket.DefaultRedundancyScheme)
require.Equal(t, expectedBucket.DefaultEncryptionParameters, bucket.DefaultEncryptionParameters)
require.Equal(t, expectedBucket.Placement, bucket.Placement)
// GetMinimalBucket
minimalBucket, err := bucketsDB.GetMinimalBucket(ctx, []byte("testbucket"), project.ID)
require.NoError(t, err)
require.Equal(t, []byte("testbucket"), minimalBucket.Name)
require.False(t, minimalBucket.CreatedAt.IsZero())
_, err = bucketsDB.GetMinimalBucket(ctx, []byte("not-existing-bucket"), project.ID)
require.True(t, buckets.ErrBucketNotFound.Has(err), err)
// GetBucketPlacement
placement, err := bucketsDB.GetBucketPlacement(ctx, []byte("testbucket"), project.ID)
require.NoError(t, err)
require.Equal(t, expectedBucket.Placement, placement)
_, err = bucketsDB.GetBucketPlacement(ctx, []byte("not-existing-bucket"), project.ID)
require.True(t, buckets.ErrBucketNotFound.Has(err), err)
// CountBuckets
count, err = bucketsDB.CountBuckets(ctx, project.ID)
require.NoError(t, err)
require.Equal(t, 1, count)
_, err = bucketsDB.CreateBucket(ctx, newTestBucket("testbucket2", project.ID))
require.NoError(t, err)
count, err = bucketsDB.CountBuckets(ctx, project.ID)
require.NoError(t, err)
require.Equal(t, 2, count)
// DeleteBucket
err = bucketsDB.DeleteBucket(ctx, []byte("testbucket"), project.ID)
require.NoError(t, err)
})
}
func TestListBucketsAllAllowed(t *testing.T) {
testCases := []struct {
name string
cursor string
limit int
expectedItems int
expectedMore bool
}{
{"empty string cursor", "", 10, 10, false},
{"last bucket cursor", "zzz", 2, 1, false},
{"non matching cursor", "ccc", 10, 5, false},
{"first bucket cursor", "0test", 10, 10, false},
{"empty string cursor, more", "", 5, 5, true},
{"non matching cursor, more", "ccc", 3, 3, true},
{"first bucket cursor, more", "0test", 5, 5, true},
}
testplanet.Run(t, testplanet.Config{SatelliteCount: 1}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
db := sat.DB
consoleDB := db.Console()
project, err := consoleDB.Projects().Insert(ctx, &console.Project{Name: "testproject1"})
require.NoError(t, err)
bucketsDB := sat.API.Buckets.Service
allowedBuckets := macaroon.AllowedBuckets{
Buckets: map[string]struct{}{},
}
{ // setup some test buckets
var testBucketNames = []string{"aaa", "bbb", "mmm", "qqq", "zzz",
"test.bucket", "123", "0test", "999", "test-bucket.thing",
}
for _, bucket := range testBucketNames {
testBucket := newTestBucket(bucket, project.ID)
_, err := bucketsDB.CreateBucket(ctx, testBucket)
allowedBuckets.Buckets[bucket] = struct{}{}
if err != nil {
require.NoError(t, err)
}
}
}
for _, tt := range testCases {
tt := tt // avoid scopelint error
t.Run(tt.name, func(t *testing.T) {
listOpts := buckets.ListOptions{
Cursor: tt.cursor,
Direction: buckets.DirectionForward,
Limit: tt.limit,
}
bucketList, err := bucketsDB.ListBuckets(ctx, project.ID, listOpts, allowedBuckets)
require.NoError(t, err)
require.Equal(t, tt.expectedItems, len(bucketList.Items))
require.Equal(t, tt.expectedMore, bucketList.More)
})
}
})
}
func TestListBucketsNotAllowed(t *testing.T) {
testCases := []struct {
name string
cursor string
limit int
expectedItems int
expectedMore bool
allowAll bool
allowedBuckets map[string]struct{}
expectedNames []string
}{
{"empty string cursor, 2 allowed", "", 10, 1, false, false, map[string]struct{}{"aaa": {}, "ddd": {}}, []string{"aaa"}},
{"empty string cursor, more", "", 2, 2, true, false, map[string]struct{}{"aaa": {}, "bbb": {}, "zzz": {}}, []string{"aaa", "bbb"}},
{"empty string cursor, 3 allowed", "", 4, 3, false, false, map[string]struct{}{"aaa": {}, "bbb": {}, "zzz": {}}, []string{"aaa", "bbb", "zzz"}},
{"last bucket cursor", "zzz", 2, 1, false, false, map[string]struct{}{"zzz": {}}, []string{"zzz"}},
{"last bucket cursor, allow all", "zzz", 2, 1, false, true, map[string]struct{}{"zzz": {}}, []string{"zzz"}},
{"empty string cursor, allow all, more", "", 5, 5, true, true, map[string]struct{}{"": {}}, []string{"123", "0test", "999", "aaa", "bbb"}},
}
testplanet.Run(t, testplanet.Config{SatelliteCount: 1}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
db := sat.DB
consoleDB := db.Console()
project, err := consoleDB.Projects().Insert(ctx, &console.Project{Name: "testproject1"})
require.NoError(t, err)
bucketsDB := sat.API.Buckets.Service
{ // setup some test buckets
var testBucketNames = []string{"aaa", "bbb", "mmm", "qqq", "zzz",
"test.bucket", "123", "0test", "999", "test-bucket.thing",
}
for _, bucket := range testBucketNames {
testBucket := newTestBucket(bucket, project.ID)
_, err := bucketsDB.CreateBucket(ctx, testBucket)
if err != nil {
require.NoError(t, err)
}
}
}
for _, tt := range testCases {
tt := tt // avoid scopelint error
listOpts := buckets.ListOptions{
Cursor: tt.cursor,
Direction: buckets.DirectionForward,
Limit: tt.limit,
}
t.Run(tt.name, func(t *testing.T) {
allowed := macaroon.AllowedBuckets{
Buckets: tt.allowedBuckets,
All: tt.allowAll,
}
bucketList, err := bucketsDB.ListBuckets(ctx,
project.ID,
listOpts,
allowed,
)
require.NoError(t, err)
require.Equal(t, tt.expectedItems, len(bucketList.Items))
require.Equal(t, tt.expectedMore, bucketList.More)
for _, actualItem := range bucketList.Items {
require.Contains(t, tt.expectedNames, actualItem.Name)
}
})
}
})
}
func TestBatchBuckets(t *testing.T) {
testplanet.Run(t, testplanet.Config{SatelliteCount: 1}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
db := sat.DB
consoleDB := db.Console()
var testBucketNames = []string{"aaa", "bbb", "mmm", "qqq", "zzz",
"test.bucket", "123", "0test", "999", "test-bucket.thing",
}
bucketsService := sat.API.Buckets.Service
var expectedBucketLocations []metabase.BucketLocation
for i := 1; i < 4; i++ {
project, err := consoleDB.Projects().Insert(ctx, &console.Project{Name: "testproject" + strconv.Itoa(i)})
require.NoError(t, err)
for _, bucket := range testBucketNames {
testBucket := newTestBucket(bucket, project.ID)
_, err := bucketsService.CreateBucket(ctx, testBucket)
require.NoError(t, err)
expectedBucketLocations = append(expectedBucketLocations, metabase.BucketLocation{
ProjectID: project.ID,
BucketName: bucket,
})
}
}
sortBucketLocations(expectedBucketLocations)
testLimits := []int{1, 3, 30, 1000, len(expectedBucketLocations)}
for _, testLimit := range testLimits {
more, err := db.Buckets().IterateBucketLocations(ctx, uuid.UUID{}, "", testLimit, func(bucketLocations []metabase.BucketLocation) (err error) {
if testLimit > len(expectedBucketLocations) {
testLimit = len(expectedBucketLocations)
}
expectedResult := expectedBucketLocations[:testLimit]
require.Equal(t, expectedResult, bucketLocations)
return nil
})
require.NoError(t, err)
if testLimit < len(expectedBucketLocations) {
require.True(t, more)
} else {
require.False(t, more)
}
}
// check if invalid bucket name 'a\' won't throw an error
_, err := db.Buckets().IterateBucketLocations(ctx, uuid.UUID{}, "a\\", 1, func(bucketLocations []metabase.BucketLocation) (err error) {
return nil
})
require.NoError(t, err)
})
}
func sortBucketLocations(locations []metabase.BucketLocation) {
sort.Slice(locations, func(i, j int) bool {
if locations[i].ProjectID == locations[j].ProjectID {
return locations[i].BucketName < locations[j].BucketName
}
return locations[i].ProjectID.Less(locations[j].ProjectID)
})
}