325 lines
6.1 KiB
Go
325 lines
6.1 KiB
Go
|
package fsbucket
|
||
|
|
||
|
import (
|
||
|
"crypto/rand"
|
||
|
"crypto/sha256"
|
||
|
"encoding/hex"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/stretchr/testify/require"
|
||
|
"go.uber.org/atomic"
|
||
|
|
||
|
"github.com/nspcc-dev/neofs-node/lib/core"
|
||
|
)
|
||
|
|
||
|
func prepareTree(badFiles bool) (string, error) {
|
||
|
name := make([]byte, 32)
|
||
|
root, err := ioutil.TempDir("", "treeBucket_test")
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
// paths must contain strings with hex ascii symbols
|
||
|
paths := [][]string{
|
||
|
{root, "abcd"},
|
||
|
{root, "abcd", "cdef"},
|
||
|
{root, "abcd", "cd01"},
|
||
|
{root, "0123", "2345"},
|
||
|
{root, "0123", "2345", "4567"},
|
||
|
}
|
||
|
|
||
|
dirs := make([]string, len(paths))
|
||
|
|
||
|
for i := range paths {
|
||
|
dirs[i] = path.Join(paths[i]...)
|
||
|
|
||
|
err = os.MkdirAll(dirs[i], 0700)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
// create couple correct files
|
||
|
for j := 0; j < 2; j++ {
|
||
|
_, err := rand.Read(name)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
filePrefix := new(strings.Builder)
|
||
|
for k := 1; k < len(paths[i]); k++ {
|
||
|
filePrefix.WriteString(paths[i][k])
|
||
|
}
|
||
|
filePrefix.WriteString(hex.EncodeToString(name))
|
||
|
|
||
|
file, err := os.OpenFile(path.Join(dirs[i], filePrefix.String()), os.O_CREATE, 0700)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
file.Close()
|
||
|
}
|
||
|
|
||
|
if !badFiles {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// create one bad file
|
||
|
_, err := rand.Read(name)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
file, err := os.OpenFile(path.Join(dirs[i], "fff"+hex.EncodeToString(name)), os.O_CREATE, 0700)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
file.Close()
|
||
|
}
|
||
|
|
||
|
return root, nil
|
||
|
}
|
||
|
|
||
|
func TestTreebucket_List(t *testing.T) {
|
||
|
root, err := prepareTree(true)
|
||
|
require.NoError(t, err)
|
||
|
defer os.RemoveAll(root)
|
||
|
|
||
|
b := treeBucket{
|
||
|
dir: root,
|
||
|
perm: 0700,
|
||
|
depth: 1,
|
||
|
prefixLength: 4,
|
||
|
}
|
||
|
results, err := b.List()
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, results, 2)
|
||
|
|
||
|
b.depth = 2
|
||
|
results, err = b.List()
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, results, 6)
|
||
|
|
||
|
b.depth = 3
|
||
|
results, err = b.List()
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, results, 2)
|
||
|
|
||
|
b.depth = 4
|
||
|
results, err = b.List()
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, results, 0)
|
||
|
}
|
||
|
|
||
|
func TestTreebucket(t *testing.T) {
|
||
|
root, err := prepareTree(true)
|
||
|
require.NoError(t, err)
|
||
|
defer os.RemoveAll(root)
|
||
|
|
||
|
b := treeBucket{
|
||
|
dir: root,
|
||
|
perm: 0700,
|
||
|
depth: 2,
|
||
|
prefixLength: 4,
|
||
|
sz: atomic.NewInt64(0),
|
||
|
}
|
||
|
|
||
|
results, err := b.List()
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, results, 6)
|
||
|
|
||
|
t.Run("Get", func(t *testing.T) {
|
||
|
for i := range results {
|
||
|
_, err = b.Get(results[i])
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
_, err = b.Get([]byte("Hello world!"))
|
||
|
require.Error(t, err)
|
||
|
})
|
||
|
|
||
|
t.Run("Has", func(t *testing.T) {
|
||
|
for i := range results {
|
||
|
require.True(t, b.Has(results[i]))
|
||
|
}
|
||
|
require.False(t, b.Has([]byte("Unknown key")))
|
||
|
})
|
||
|
|
||
|
t.Run("Set", func(t *testing.T) {
|
||
|
keyHash := sha256.Sum256([]byte("Set this key"))
|
||
|
key := keyHash[:]
|
||
|
value := make([]byte, 32)
|
||
|
rand.Read(value)
|
||
|
|
||
|
// set sha256 key
|
||
|
err := b.Set(key, value)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
require.True(t, b.Has(key))
|
||
|
data, err := b.Get(key)
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, data, value)
|
||
|
|
||
|
filename := hex.EncodeToString(key)
|
||
|
_, err = os.Lstat(path.Join(root, filename[:4], filename[4:8], filename))
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// set key that cannot be placed in the required dir depth
|
||
|
key, err = hex.DecodeString("abcdef")
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
err = b.Set(key, value)
|
||
|
require.Error(t, err)
|
||
|
})
|
||
|
|
||
|
t.Run("Delete", func(t *testing.T) {
|
||
|
keyHash := sha256.Sum256([]byte("Delete this key"))
|
||
|
key := keyHash[:]
|
||
|
value := make([]byte, 32)
|
||
|
rand.Read(value)
|
||
|
|
||
|
err := b.Set(key, value)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// delete sha256 key
|
||
|
err = b.Del(key)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
_, err = b.Get(key)
|
||
|
require.Error(t, err)
|
||
|
filename := hex.EncodeToString(key)
|
||
|
_, err = os.Lstat(path.Join(root, filename[:4], filename[4:8], filename))
|
||
|
require.Error(t, err)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestTreebucket_Close(t *testing.T) {
|
||
|
root, err := prepareTree(true)
|
||
|
require.NoError(t, err)
|
||
|
defer os.RemoveAll(root)
|
||
|
|
||
|
b := treeBucket{
|
||
|
dir: root,
|
||
|
perm: 0700,
|
||
|
depth: 2,
|
||
|
prefixLength: 4,
|
||
|
}
|
||
|
err = b.Close()
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
_, err = os.Lstat(root)
|
||
|
require.Error(t, err)
|
||
|
}
|
||
|
|
||
|
func TestTreebucket_Size(t *testing.T) {
|
||
|
root, err := prepareTree(true)
|
||
|
require.NoError(t, err)
|
||
|
defer os.RemoveAll(root)
|
||
|
|
||
|
var size int64 = 1024
|
||
|
key := []byte("Set this key")
|
||
|
value := make([]byte, size)
|
||
|
rand.Read(value)
|
||
|
|
||
|
b := treeBucket{
|
||
|
dir: root,
|
||
|
perm: 0700,
|
||
|
depth: 2,
|
||
|
prefixLength: 4,
|
||
|
sz: atomic.NewInt64(0),
|
||
|
}
|
||
|
|
||
|
err = b.Set(key, value)
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, size, b.Size())
|
||
|
}
|
||
|
|
||
|
func BenchmarkTreebucket_List(b *testing.B) {
|
||
|
root, err := prepareTree(false)
|
||
|
defer os.RemoveAll(root)
|
||
|
if err != nil {
|
||
|
b.Error(err)
|
||
|
}
|
||
|
|
||
|
treeFSBucket := &treeBucket{
|
||
|
dir: root,
|
||
|
perm: 0755,
|
||
|
depth: 2,
|
||
|
prefixLength: 4,
|
||
|
}
|
||
|
|
||
|
b.ReportAllocs()
|
||
|
b.ResetTimer()
|
||
|
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
_, err := treeFSBucket.List()
|
||
|
if err != nil {
|
||
|
b.Error(err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkFilewalkBucket_List(b *testing.B) {
|
||
|
root, err := prepareTree(false)
|
||
|
defer os.RemoveAll(root)
|
||
|
if err != nil {
|
||
|
b.Error(err)
|
||
|
}
|
||
|
|
||
|
b.ReportAllocs()
|
||
|
b.ResetTimer()
|
||
|
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
buckets := make([]core.BucketItem, 0)
|
||
|
|
||
|
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||
|
if err != nil || info.IsDir() {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
val, err := ioutil.ReadFile(path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
key, err := decodeHexKey(info.Name())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
buckets = append(buckets, core.BucketItem{
|
||
|
Key: key,
|
||
|
Val: val,
|
||
|
})
|
||
|
|
||
|
return nil
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkTreeBucket_Size(b *testing.B) {
|
||
|
root, err := prepareTree(false)
|
||
|
defer os.RemoveAll(root)
|
||
|
if err != nil {
|
||
|
b.Error(err)
|
||
|
}
|
||
|
|
||
|
treeFSBucket := &treeBucket{
|
||
|
dir: root,
|
||
|
perm: 0755,
|
||
|
depth: 2,
|
||
|
prefixLength: 4,
|
||
|
}
|
||
|
|
||
|
treeFSBucket.sz = atomic.NewInt64(treeFSBucket.size())
|
||
|
|
||
|
b.ReportAllocs()
|
||
|
b.ResetTimer()
|
||
|
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
_ = treeFSBucket.Size()
|
||
|
}
|
||
|
}
|