forked from TrueCloudLab/frostfs-node
[#302] Remove unused FSBucket component
FSBucket became obsolete when storage object engine has been implemented. Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
parent
b62b19cb5a
commit
1d56e60589
6 changed files with 0 additions and 876 deletions
|
@ -1,41 +0,0 @@
|
||||||
package bucket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterHandler where you receive key/val in your closure.
|
|
||||||
type FilterHandler func(key, val []byte) bool
|
|
||||||
|
|
||||||
// BucketItem used in filter.
|
|
||||||
type BucketItem struct {
|
|
||||||
Key []byte
|
|
||||||
Val []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bucket is sub-store interface.
|
|
||||||
type Bucket interface {
|
|
||||||
Get(key []byte) ([]byte, error)
|
|
||||||
Set(key, value []byte) error
|
|
||||||
Del(key []byte) error
|
|
||||||
Has(key []byte) bool
|
|
||||||
Size() int64
|
|
||||||
List() ([][]byte, error)
|
|
||||||
Iterate(FilterHandler) error
|
|
||||||
// Steam can be implemented by badger.Stream, but not for now
|
|
||||||
// Stream(ctx context.Context, key []byte, cb func(io.ReadWriter) error) error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNilFilterHandler when FilterHandler is empty
|
|
||||||
ErrNilFilterHandler = errors.New("handler can't be nil")
|
|
||||||
|
|
||||||
// ErrNotFound is returned by key-value storage methods
|
|
||||||
// that could not find element by key.
|
|
||||||
ErrNotFound = errors.New("key not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrIteratingAborted is returned by storage iterator
|
|
||||||
// after iteration has been interrupted.
|
|
||||||
var ErrIteratingAborted = errors.New("iteration aborted")
|
|
|
@ -1,100 +0,0 @@
|
||||||
package fsbucket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/mr-tron/base58"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Bucket struct {
|
|
||||||
dir string
|
|
||||||
perm os.FileMode
|
|
||||||
}
|
|
||||||
|
|
||||||
treeBucket struct {
|
|
||||||
dir string
|
|
||||||
perm os.FileMode
|
|
||||||
|
|
||||||
depth int
|
|
||||||
prefixLength int
|
|
||||||
sz *atomic.Int64
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const Name = "filesystem"
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultDirectory = "fsbucket"
|
|
||||||
defaultPermissions = 0755
|
|
||||||
defaultDepth = 2
|
|
||||||
defaultPrefixLen = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
var errShortKey = errors.New("key is too short for tree fs bucket")
|
|
||||||
|
|
||||||
func stringifyKey(key []byte) string {
|
|
||||||
return base58.Encode(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeKey(key string) []byte {
|
|
||||||
k, err := base58.Decode(key)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // it can fail only for not base58 strings
|
|
||||||
}
|
|
||||||
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBucket creates new file system bucket instance.
|
|
||||||
func NewBucket(prefix string, v *viper.Viper) (bucket.Bucket, error) {
|
|
||||||
prefix = prefix + "." + Name
|
|
||||||
var (
|
|
||||||
dir string
|
|
||||||
perm os.FileMode
|
|
||||||
|
|
||||||
prefixLen int
|
|
||||||
depth int
|
|
||||||
)
|
|
||||||
|
|
||||||
if dir = v.GetString(prefix + ".directory"); dir == "" {
|
|
||||||
dir = defaultDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
if perm = os.FileMode(v.GetInt(prefix + ".permissions")); perm == 0 {
|
|
||||||
perm = defaultPermissions
|
|
||||||
}
|
|
||||||
|
|
||||||
if depth = v.GetInt(prefix + ".depth"); depth <= 0 {
|
|
||||||
depth = defaultDepth
|
|
||||||
}
|
|
||||||
|
|
||||||
if prefixLen = v.GetInt(prefix + ".prefix_len"); prefixLen <= 0 {
|
|
||||||
prefixLen = defaultPrefixLen
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(dir, perm); err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "could not create bucket %s", Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.GetBool(prefix + ".tree_enabled") {
|
|
||||||
b := &treeBucket{
|
|
||||||
dir: dir,
|
|
||||||
perm: perm,
|
|
||||||
depth: depth,
|
|
||||||
prefixLength: prefixLen,
|
|
||||||
}
|
|
||||||
b.sz = atomic.NewInt64(b.size())
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Bucket{
|
|
||||||
dir: dir,
|
|
||||||
perm: perm,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
package fsbucket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get value by key.
|
|
||||||
func (b *Bucket) Get(key []byte) ([]byte, error) {
|
|
||||||
p := path.Join(b.dir, stringifyKey(key))
|
|
||||||
if _, err := os.Stat(p); os.IsNotExist(err) {
|
|
||||||
return nil, bucket.ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.ReadFile(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set value by key.
|
|
||||||
func (b *Bucket) Set(key, value []byte) error {
|
|
||||||
p := path.Join(b.dir, stringifyKey(key))
|
|
||||||
|
|
||||||
return ioutil.WriteFile(p, value, b.perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Del value by key.
|
|
||||||
func (b *Bucket) Del(key []byte) error {
|
|
||||||
p := path.Join(b.dir, stringifyKey(key))
|
|
||||||
if _, err := os.Stat(p); os.IsNotExist(err) {
|
|
||||||
return bucket.ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Remove(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has checks key exists.
|
|
||||||
func (b *Bucket) Has(key []byte) bool {
|
|
||||||
p := path.Join(b.dir, stringifyKey(key))
|
|
||||||
_, err := os.Stat(p)
|
|
||||||
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listing(root string, fn func(path string, info os.FileInfo) error) error {
|
|
||||||
return filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil || info.IsDir() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(p, info)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size of bucket.
|
|
||||||
func (b *Bucket) Size() (size int64) {
|
|
||||||
err := listing(b.dir, func(_ string, info os.FileInfo) error {
|
|
||||||
size += info.Size()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
size = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all bucket items.
|
|
||||||
func (b *Bucket) List() ([][]byte, error) {
|
|
||||||
buckets := make([][]byte, 0)
|
|
||||||
|
|
||||||
err := listing(b.dir, func(p string, info os.FileInfo) error {
|
|
||||||
buckets = append(buckets, decodeKey(info.Name()))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return buckets, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter bucket items by closure.
|
|
||||||
func (b *Bucket) Iterate(handler bucket.FilterHandler) error {
|
|
||||||
return listing(b.dir, func(p string, info os.FileInfo) error {
|
|
||||||
key := decodeKey(info.Name())
|
|
||||||
val, err := ioutil.ReadFile(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !handler(key, val) {
|
|
||||||
return bucket.ErrIteratingAborted
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close bucket (just empty).
|
|
||||||
func (b *Bucket) Close() error {
|
|
||||||
return os.RemoveAll(b.dir)
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package fsbucket
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
type (
|
|
||||||
queue struct {
|
|
||||||
*sync.RWMutex
|
|
||||||
buf []elem
|
|
||||||
}
|
|
||||||
|
|
||||||
elem struct {
|
|
||||||
depth int
|
|
||||||
prefix string
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newQueue(n int) *queue {
|
|
||||||
return &queue{
|
|
||||||
RWMutex: new(sync.RWMutex),
|
|
||||||
buf: make([]elem, 0, n),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *queue) Len() int {
|
|
||||||
return len(q.buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *queue) Push(s elem) {
|
|
||||||
q.Lock()
|
|
||||||
q.buf = append(q.buf, s)
|
|
||||||
q.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *queue) Pop() (s elem) {
|
|
||||||
q.Lock()
|
|
||||||
if len(q.buf) > 0 {
|
|
||||||
s = q.buf[0]
|
|
||||||
q.buf = q.buf[1:]
|
|
||||||
}
|
|
||||||
q.Unlock()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,261 +0,0 @@
|
||||||
package fsbucket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket"
|
|
||||||
)
|
|
||||||
|
|
||||||
const queueCap = 1000
|
|
||||||
|
|
||||||
func stringifyHexKey(key []byte) string {
|
|
||||||
return hex.EncodeToString(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeHexKey(key string) ([]byte, error) {
|
|
||||||
k, err := hex.DecodeString(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// treePath returns slice of the dir names that contain the path
|
|
||||||
// and filename, e.g. 0xabcdef => []string{"ab", "cd"}, "abcdef".
|
|
||||||
// In case of errors - return nil slice.
|
|
||||||
func (b *treeBucket) treePath(key []byte) ([]string, string) {
|
|
||||||
filename := stringifyHexKey(key)
|
|
||||||
if len(filename) <= b.prefixLength*b.depth {
|
|
||||||
return nil, filename
|
|
||||||
}
|
|
||||||
|
|
||||||
filepath := filename
|
|
||||||
dirs := make([]string, 0, b.depth)
|
|
||||||
|
|
||||||
for i := 0; i < b.depth; i++ {
|
|
||||||
dirs = append(dirs, filepath[:b.prefixLength])
|
|
||||||
filepath = filepath[b.prefixLength:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return dirs, filename
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get value by key.
|
|
||||||
func (b *treeBucket) Get(key []byte) ([]byte, error) {
|
|
||||||
dirPaths, filename := b.treePath(key)
|
|
||||||
if dirPaths == nil {
|
|
||||||
return nil, errShortKey
|
|
||||||
}
|
|
||||||
|
|
||||||
p := path.Join(b.dir, path.Join(dirPaths...), filename)
|
|
||||||
|
|
||||||
if _, err := os.Stat(p); os.IsNotExist(err) {
|
|
||||||
return nil, bucket.ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.ReadFile(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set value by key.
|
|
||||||
func (b *treeBucket) Set(key, value []byte) error {
|
|
||||||
dirPaths, filename := b.treePath(key)
|
|
||||||
if dirPaths == nil {
|
|
||||||
return errShortKey
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
dirPath = path.Join(dirPaths...)
|
|
||||||
p = path.Join(b.dir, dirPath, filename)
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := os.MkdirAll(path.Join(b.dir, dirPath), b.perm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err := ioutil.WriteFile(p, value, b.perm)
|
|
||||||
if err == nil {
|
|
||||||
b.sz.Add(int64(len(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Del value by key.
|
|
||||||
func (b *treeBucket) Del(key []byte) error {
|
|
||||||
dirPaths, filename := b.treePath(key)
|
|
||||||
if dirPaths == nil {
|
|
||||||
return errShortKey
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
fi os.FileInfo
|
|
||||||
p = path.Join(b.dir, path.Join(dirPaths...), filename)
|
|
||||||
)
|
|
||||||
|
|
||||||
if fi, err = os.Stat(p); os.IsNotExist(err) {
|
|
||||||
return bucket.ErrNotFound
|
|
||||||
} else if err = os.Remove(p); err == nil {
|
|
||||||
b.sz.Sub(fi.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has checks if key exists.
|
|
||||||
func (b *treeBucket) Has(key []byte) bool {
|
|
||||||
dirPaths, filename := b.treePath(key)
|
|
||||||
if dirPaths == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
p := path.Join(b.dir, path.Join(dirPaths...), filename)
|
|
||||||
|
|
||||||
_, err := os.Stat(p)
|
|
||||||
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// There might be two implementation of listing method: simple with `filepath.Walk()`
|
|
||||||
// or more complex implementation with path checks, BFS etc. `filepath.Walk()` might
|
|
||||||
// be slow in large dirs due to sorting operations and non controllable depth.
|
|
||||||
func (b *treeBucket) listing(root string, fn func(path string, info os.FileInfo) error) error {
|
|
||||||
// todo: DFS might be better since it won't store many files in queue.
|
|
||||||
// todo: queue length can be specified as a parameter
|
|
||||||
q := newQueue(queueCap)
|
|
||||||
q.Push(elem{path: root})
|
|
||||||
|
|
||||||
for q.Len() > 0 {
|
|
||||||
e := q.Pop()
|
|
||||||
|
|
||||||
s, err := os.Lstat(e.path)
|
|
||||||
if err != nil {
|
|
||||||
// might be better to log and ignore
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if it is correct file
|
|
||||||
if !s.IsDir() {
|
|
||||||
// we accept files that located in excepted depth and have correct prefix
|
|
||||||
// e.g. file 'abcdef0123' => /ab/cd/abcdef0123
|
|
||||||
if e.depth == b.depth+1 && strings.HasPrefix(s.Name(), e.prefix) {
|
|
||||||
err = fn(e.path, s)
|
|
||||||
if err != nil {
|
|
||||||
// might be better to log and ignore
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore dirs with inappropriate length or depth
|
|
||||||
if e.depth > b.depth || (e.depth > 0 && len(s.Name()) > b.prefixLength) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := readDirNames(e.path)
|
|
||||||
if err != nil {
|
|
||||||
// might be better to log and ignore
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range files {
|
|
||||||
// add prefix of all dirs in path except root dir
|
|
||||||
var prefix string
|
|
||||||
if e.depth > 0 {
|
|
||||||
prefix = e.prefix + s.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
q.Push(elem{
|
|
||||||
depth: e.depth + 1,
|
|
||||||
prefix: prefix,
|
|
||||||
path: path.Join(e.path, files[i]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the size of the bucket in bytes.
|
|
||||||
func (b *treeBucket) Size() int64 {
|
|
||||||
return b.sz.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *treeBucket) size() (size int64) {
|
|
||||||
err := b.listing(b.dir, func(_ string, info os.FileInfo) error {
|
|
||||||
size += info.Size()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
size = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all bucket items.
|
|
||||||
func (b *treeBucket) List() ([][]byte, error) {
|
|
||||||
buckets := make([][]byte, 0)
|
|
||||||
|
|
||||||
err := b.listing(b.dir, func(p string, info os.FileInfo) error {
|
|
||||||
key, err := decodeHexKey(info.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buckets = append(buckets, key)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return buckets, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter bucket items by closure.
|
|
||||||
func (b *treeBucket) Iterate(handler bucket.FilterHandler) error {
|
|
||||||
return b.listing(b.dir, func(p string, info os.FileInfo) error {
|
|
||||||
val, err := ioutil.ReadFile(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := decodeHexKey(info.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !handler(key, val) {
|
|
||||||
return bucket.ErrIteratingAborted
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close bucket (remove all available data).
|
|
||||||
func (b *treeBucket) Close() error {
|
|
||||||
return os.RemoveAll(b.dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readDirNames copies `filepath.readDirNames()` without sorting the output.
|
|
||||||
func readDirNames(dirname string) ([]string, error) {
|
|
||||||
f, err := os.Open(dirname)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
names, err := f.Readdirnames(-1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
return names, nil
|
|
||||||
}
|
|
|
@ -1,323 +0,0 @@
|
||||||
package fsbucket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
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([]bucket.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, bucket.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()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue