2020-06-01 18:11:55 +00:00
|
|
|
package repo
|
|
|
|
|
|
|
|
import (
|
2020-07-06 20:03:37 +00:00
|
|
|
"fmt"
|
2020-06-01 18:11:55 +00:00
|
|
|
"github.com/ipfs/go-datastore"
|
|
|
|
"golang.org/x/xerrors"
|
2020-07-06 20:03:37 +00:00
|
|
|
"io/ioutil"
|
2020-06-01 18:11:55 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-07-06 20:03:37 +00:00
|
|
|
"strconv"
|
2020-06-01 18:11:55 +00:00
|
|
|
|
|
|
|
badger "github.com/ipfs/go-ds-badger2"
|
|
|
|
levelds "github.com/ipfs/go-ds-leveldb"
|
|
|
|
"github.com/ipfs/go-ds-measure"
|
|
|
|
ldbopts "github.com/syndtr/goleveldb/leveldb/opt"
|
|
|
|
)
|
|
|
|
|
2020-07-06 20:03:37 +00:00
|
|
|
type dsCtor func(path string) (datastore.Batching, error)
|
|
|
|
|
|
|
|
var fsDatastores = map[string]dsCtor{
|
2020-06-01 18:11:55 +00:00
|
|
|
"chain": badgerDs,
|
|
|
|
"metadata": levelDs,
|
|
|
|
|
|
|
|
// Those need to be fast for large writes... but also need a really good GC :c
|
|
|
|
"staging": badgerDs, // miner specific
|
2020-07-06 20:03:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var fsMultiDatastores = map[string]dsCtor{
|
|
|
|
"client": badgerDs, // client specific
|
2020-06-01 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func badgerDs(path string) (datastore.Batching, error) {
|
|
|
|
opts := badger.DefaultOptions
|
|
|
|
opts.Truncate = true
|
|
|
|
|
|
|
|
return badger.NewDatastore(path, &opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func levelDs(path string) (datastore.Batching, error) {
|
|
|
|
return levelds.NewDatastore(path, &levelds.Options{
|
|
|
|
Compression: ldbopts.NoCompression,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:03:37 +00:00
|
|
|
func (fsr *fsLockedRepo) openDatastores() (map[string]datastore.Batching, error) {
|
2020-06-01 18:11:55 +00:00
|
|
|
if err := os.MkdirAll(fsr.join(fsDatastore), 0755); err != nil {
|
|
|
|
return nil, xerrors.Errorf("mkdir %s: %w", fsr.join(fsDatastore), err)
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:03:37 +00:00
|
|
|
out := map[string]datastore.Batching{}
|
2020-06-01 18:11:55 +00:00
|
|
|
|
|
|
|
for p, ctor := range fsDatastores {
|
|
|
|
prefix := datastore.NewKey(p)
|
|
|
|
|
|
|
|
// TODO: optimization: don't init datastores we don't need
|
|
|
|
ds, err := ctor(fsr.join(filepath.Join(fsDatastore, p)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("opening datastore %s: %w", prefix, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ds = measure.New("fsrepo."+p, ds)
|
|
|
|
|
2020-07-06 20:03:37 +00:00
|
|
|
out[datastore.NewKey(p).String()] = ds
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fsr *fsLockedRepo) openMultiDatastores() (map[string]map[int64]datastore.Batching, error) {
|
|
|
|
out := map[string]map[int64]datastore.Batching{}
|
|
|
|
|
|
|
|
for p, ctor := range fsMultiDatastores {
|
|
|
|
path := fsr.join(filepath.Join(fsDatastore, p))
|
|
|
|
if err := os.MkdirAll(path, 0755); err != nil {
|
|
|
|
return nil, xerrors.Errorf("mkdir %s: %w", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
di, err := ioutil.ReadDir(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("readdir '%s': %w", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
out[p] = map[int64]datastore.Batching{}
|
|
|
|
|
|
|
|
for _, info := range di {
|
2020-07-07 11:48:13 +00:00
|
|
|
path := filepath.Join(path, info.Name())
|
2020-07-06 20:03:37 +00:00
|
|
|
|
|
|
|
prefix := datastore.NewKey(p)
|
|
|
|
|
|
|
|
id, err := strconv.ParseInt(info.Name(), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error parsing multi-datastore id for '%s': %w", path, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: optimization: don't init datastores we don't need
|
|
|
|
ds, err := ctor(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("opening datastore %s: %w", prefix, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ds = measure.New("fsrepo."+p+"."+info.Name(), ds)
|
|
|
|
|
|
|
|
out[p][id] = ds
|
|
|
|
}
|
2020-06-01 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:03:37 +00:00
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fsr *fsLockedRepo) openMultiDatastore(ns string, idx int64) (datastore.Batching, error) {
|
|
|
|
ctor, ok := fsMultiDatastores[ns]
|
|
|
|
if !ok {
|
|
|
|
return nil, xerrors.Errorf("no multi-datastore with namespace '%s'", ns)
|
|
|
|
}
|
|
|
|
|
|
|
|
si := fmt.Sprintf("%d", idx)
|
|
|
|
path := fsr.join(filepath.Join(fsDatastore, ns, si))
|
|
|
|
|
|
|
|
ds, err := ctor(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("opening datastore %s: %w", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ds = measure.New("fsrepo."+ns+"."+si, ds)
|
|
|
|
|
|
|
|
return ds, nil
|
2020-06-01 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (fsr *fsLockedRepo) Datastore(ns string) (datastore.Batching, error) {
|
|
|
|
fsr.dsOnce.Do(func() {
|
2020-07-06 20:03:37 +00:00
|
|
|
var err error
|
|
|
|
fsr.ds, err = fsr.openDatastores()
|
|
|
|
if err != nil {
|
|
|
|
fsr.dsErr = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fsr.multiDs, fsr.dsErr = fsr.openMultiDatastores()
|
2020-06-01 18:11:55 +00:00
|
|
|
})
|
|
|
|
if fsr.dsErr != nil {
|
|
|
|
return nil, fsr.dsErr
|
|
|
|
}
|
2020-07-06 20:03:37 +00:00
|
|
|
ds, ok := fsr.ds[ns]
|
|
|
|
if ok {
|
|
|
|
return ds, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
k := datastore.NewKey(ns)
|
|
|
|
parts := k.List()
|
|
|
|
if len(parts) != 2 {
|
|
|
|
return nil, xerrors.Errorf("expected multi-datastore namespace to have 2 parts")
|
|
|
|
}
|
|
|
|
|
|
|
|
fsr.dsLk.Lock()
|
|
|
|
defer fsr.dsLk.Unlock()
|
|
|
|
|
|
|
|
mds, ok := fsr.multiDs[parts[0]]
|
|
|
|
if !ok {
|
|
|
|
return nil, xerrors.Errorf("no multi-datastore with namespace %s", ns)
|
|
|
|
}
|
|
|
|
|
|
|
|
idx, err := strconv.ParseInt(parts[1], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("parsing mult-datastore index('%s'): %w", parts[1], err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ds, ok = mds[idx]
|
|
|
|
if !ok {
|
|
|
|
ds, err = fsr.openMultiDatastore(parts[0], idx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("opening multi-datastore: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mds[idx] = ds
|
|
|
|
}
|
|
|
|
|
|
|
|
return ds, nil
|
2020-06-01 18:11:55 +00:00
|
|
|
}
|
2020-07-06 20:03:37 +00:00
|
|
|
|
2020-07-06 23:39:30 +00:00
|
|
|
func (fsr *fsLockedRepo) ListDatastores(ns string) ([]int64, error) {
|
|
|
|
k := datastore.NewKey(ns)
|
|
|
|
parts := k.List()
|
|
|
|
if len(parts) != 1 {
|
|
|
|
return nil, xerrors.Errorf("expected multi-datastore namespace to have 1 part")
|
|
|
|
}
|
|
|
|
|
|
|
|
fsr.dsLk.Lock()
|
|
|
|
defer fsr.dsLk.Unlock()
|
|
|
|
|
|
|
|
mds, ok := fsr.multiDs[parts[0]]
|
|
|
|
if !ok {
|
|
|
|
return nil, xerrors.Errorf("no multi-datastore with namespace %s", ns)
|
|
|
|
}
|
|
|
|
|
|
|
|
out := make([]int64, 0, len(mds))
|
|
|
|
for i := range mds {
|
|
|
|
out = append(out, i)
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:03:37 +00:00
|
|
|
func (fsr *fsLockedRepo) DeleteDatastore(ns string) error {
|
|
|
|
k := datastore.NewKey(ns)
|
|
|
|
parts := k.List()
|
|
|
|
if len(parts) != 2 {
|
|
|
|
return xerrors.Errorf("expected multi-datastore namespace to have 2 parts")
|
|
|
|
}
|
|
|
|
|
|
|
|
mds, ok := fsr.multiDs[parts[0]]
|
|
|
|
if !ok {
|
|
|
|
return xerrors.Errorf("no multi-datastore with namespace %s", ns)
|
|
|
|
}
|
|
|
|
|
|
|
|
idx, err := strconv.ParseInt(parts[1], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("parsing mult-datastore index('%s'): %w", parts[1], err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fsr.dsLk.Lock()
|
|
|
|
defer fsr.dsLk.Unlock()
|
|
|
|
|
|
|
|
ds, ok := mds[idx]
|
|
|
|
if !ok {
|
|
|
|
return xerrors.Errorf("no multi-datastore with at index (namespace %s)", ns)
|
|
|
|
}
|
|
|
|
|
2020-07-07 11:45:02 +00:00
|
|
|
delete(mds, idx)
|
|
|
|
|
2020-07-06 20:03:37 +00:00
|
|
|
if err := ds.Close(); err != nil {
|
|
|
|
return xerrors.Errorf("closing datastore: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
path := fsr.join(filepath.Join(fsDatastore, parts[0], parts[1]))
|
|
|
|
|
|
|
|
log.Warnw("removing sub-datastore", "path", path, "namespace", ns)
|
|
|
|
if err := os.RemoveAll(path); err != nil {
|
|
|
|
return xerrors.Errorf("remove '%s': %w", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-07-07 09:38:22 +00:00
|
|
|
}
|