lotus-miner backup command
This commit is contained in:
parent
d20ebe93b9
commit
2dc9a1ee4e
@ -101,6 +101,12 @@ type StorageMiner interface {
|
||||
PiecesListCidInfos(ctx context.Context) ([]cid.Cid, error)
|
||||
PiecesGetPieceInfo(ctx context.Context, pieceCid cid.Cid) (*piecestore.PieceInfo, error)
|
||||
PiecesGetCIDInfo(ctx context.Context, payloadCid cid.Cid) (*piecestore.CIDInfo, error)
|
||||
|
||||
// CreateBackup creates node backup onder the specified file name. The
|
||||
// method requires that the lotus-miner is running with the
|
||||
// LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that
|
||||
// the path specified when calling CreateBackup is within the base path
|
||||
CreateBackup(ctx context.Context, fpath string) error
|
||||
}
|
||||
|
||||
type SealRes struct {
|
||||
|
@ -319,6 +319,8 @@ type StorageMinerStruct struct {
|
||||
PiecesListCidInfos func(ctx context.Context) ([]cid.Cid, error) `perm:"read"`
|
||||
PiecesGetPieceInfo func(ctx context.Context, pieceCid cid.Cid) (*piecestore.PieceInfo, error) `perm:"read"`
|
||||
PiecesGetCIDInfo func(ctx context.Context, payloadCid cid.Cid) (*piecestore.CIDInfo, error) `perm:"read"`
|
||||
|
||||
CreateBackup func(ctx context.Context, fpath string) error `perm:"admin"`
|
||||
}
|
||||
}
|
||||
|
||||
@ -1265,6 +1267,10 @@ func (c *StorageMinerStruct) PiecesGetCIDInfo(ctx context.Context, payloadCid ci
|
||||
return c.Internal.PiecesGetCIDInfo(ctx, payloadCid)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) CreateBackup(ctx context.Context, fpath string) error {
|
||||
return c.Internal.CreateBackup(ctx, fpath)
|
||||
}
|
||||
|
||||
// WorkerStruct
|
||||
|
||||
func (w *WorkerStruct) Version(ctx context.Context) (build.Version, error) {
|
||||
|
42
cmd/lotus-storage-miner/backup.go
Normal file
42
cmd/lotus-storage-miner/backup.go
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
var backupCmd = &cli.Command{
|
||||
Name: "backup",
|
||||
Usage: "Create node metadata backup",
|
||||
Description: `The backup command writes a copy of node metadata under the specified path
|
||||
|
||||
For security reasons, the daemon must be have LOTUS_BACKUP_BASE_PATH env var set
|
||||
to a path where backup files are supposed to be saved, and the path specified in
|
||||
this command must be within this base path`,
|
||||
ArgsUsage: "[backup file path]",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if cctx.Args().Len() != 1 {
|
||||
return xerrors.Errorf("expected 1 argument")
|
||||
}
|
||||
|
||||
err = api.CreateBackup(lcli.ReqContext(cctx), cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Success")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
@ -35,6 +35,7 @@ func main() {
|
||||
runCmd,
|
||||
stopCmd,
|
||||
configCmd,
|
||||
backupCmd,
|
||||
lcli.WithCategory("chain", actorCmd),
|
||||
lcli.WithCategory("chain", infoCmd),
|
||||
lcli.WithCategory("market", storageDealsCmd),
|
||||
|
165
lib/backupds/backupds.go
Normal file
165
lib/backupds/backupds.go
Normal file
@ -0,0 +1,165 @@
|
||||
package backupds
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-datastore/query"
|
||||
)
|
||||
|
||||
var log = logging.Logger("backupds")
|
||||
|
||||
type Datastore struct {
|
||||
child datastore.Batching
|
||||
|
||||
backupLk sync.RWMutex
|
||||
}
|
||||
|
||||
func Wrap(child datastore.Batching) *Datastore {
|
||||
return &Datastore{
|
||||
child: child,
|
||||
}
|
||||
}
|
||||
|
||||
// Writes a datastore dump into the provided writer as indefinite length cbor
|
||||
// array of [key, value] tuples
|
||||
func (d *Datastore) Backup(out io.Writer) error {
|
||||
// write indefinite length array header
|
||||
if _, err := out.Write([]byte{0x9f}); err != nil {
|
||||
return xerrors.Errorf("writing header: %w", err)
|
||||
}
|
||||
|
||||
d.backupLk.Lock()
|
||||
defer d.backupLk.Unlock()
|
||||
|
||||
log.Info("Starting datastore backup")
|
||||
defer log.Info("Datastore backup done")
|
||||
|
||||
qr, err := d.child.Query(query.Query{})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("query: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := qr.Close(); err != nil {
|
||||
log.Errorf("query close error: %+v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
for result := range qr.Next() {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, out, cbg.MajArray, 2); err != nil {
|
||||
return xerrors.Errorf("writing tuple header: %w", err)
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, out, cbg.MajByteString, uint64(len([]byte(result.Key)))); err != nil {
|
||||
return xerrors.Errorf("writing key header: %w", err)
|
||||
}
|
||||
|
||||
if _, err := out.Write([]byte(result.Key)[:]); err != nil {
|
||||
return xerrors.Errorf("writing key: %w", err)
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, out, cbg.MajByteString, uint64(len(result.Value))); err != nil {
|
||||
return xerrors.Errorf("writing value header: %w", err)
|
||||
}
|
||||
|
||||
if _, err := out.Write(result.Value[:]); err != nil {
|
||||
return xerrors.Errorf("writing value: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// array break
|
||||
if _, err := out.Write([]byte{0xff}); err != nil {
|
||||
return xerrors.Errorf("writing array 'break': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// proxy
|
||||
|
||||
func (d *Datastore) Get(key datastore.Key) (value []byte, err error) {
|
||||
return d.child.Get(key)
|
||||
}
|
||||
|
||||
func (d *Datastore) Has(key datastore.Key) (exists bool, err error) {
|
||||
return d.child.Has(key)
|
||||
}
|
||||
|
||||
func (d *Datastore) GetSize(key datastore.Key) (size int, err error) {
|
||||
return d.child.GetSize(key)
|
||||
}
|
||||
|
||||
func (d *Datastore) Query(q query.Query) (query.Results, error) {
|
||||
return d.child.Query(q)
|
||||
}
|
||||
|
||||
func (d *Datastore) Put(key datastore.Key, value []byte) error {
|
||||
d.backupLk.RLock()
|
||||
defer d.backupLk.RUnlock()
|
||||
|
||||
return d.child.Put(key, value)
|
||||
}
|
||||
|
||||
func (d *Datastore) Delete(key datastore.Key) error {
|
||||
d.backupLk.RLock()
|
||||
defer d.backupLk.RUnlock()
|
||||
|
||||
return d.child.Delete(key)
|
||||
}
|
||||
|
||||
func (d *Datastore) Sync(prefix datastore.Key) error {
|
||||
d.backupLk.RLock()
|
||||
defer d.backupLk.RUnlock()
|
||||
|
||||
return d.child.Sync(prefix)
|
||||
}
|
||||
|
||||
func (d *Datastore) Close() error {
|
||||
d.backupLk.RLock()
|
||||
defer d.backupLk.RUnlock()
|
||||
|
||||
return d.child.Close()
|
||||
}
|
||||
|
||||
func (d *Datastore) Batch() (datastore.Batch, error) {
|
||||
b, err := d.child.Batch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bbatch{
|
||||
b: b,
|
||||
rlk: d.backupLk.RLocker(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type bbatch struct {
|
||||
b datastore.Batch
|
||||
rlk sync.Locker
|
||||
}
|
||||
|
||||
func (b *bbatch) Put(key datastore.Key, value []byte) error {
|
||||
return b.b.Put(key, value)
|
||||
}
|
||||
|
||||
func (b *bbatch) Delete(key datastore.Key) error {
|
||||
return b.b.Delete(key)
|
||||
}
|
||||
|
||||
func (b *bbatch) Commit() error {
|
||||
b.rlk.Lock()
|
||||
defer b.rlk.Unlock()
|
||||
|
||||
return b.b.Commit()
|
||||
}
|
||||
|
||||
var _ datastore.Batch = &bbatch{}
|
||||
var _ datastore.Batching = &Datastore{}
|
@ -5,21 +5,24 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
datatransfer "github.com/filecoin-project/go-data-transfer"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
datatransfer "github.com/filecoin-project/go-data-transfer"
|
||||
"github.com/filecoin-project/go-fil-markets/piecestore"
|
||||
retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket"
|
||||
storagemarket "github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
"github.com/filecoin-project/go-jsonrpc/auth"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
|
||||
sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage"
|
||||
"github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper"
|
||||
@ -31,9 +34,11 @@ import (
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/api/apistruct"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/backupds"
|
||||
"github.com/filecoin-project/lotus/miner"
|
||||
"github.com/filecoin-project/lotus/node/impl/common"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
"github.com/filecoin-project/lotus/node/repo"
|
||||
"github.com/filecoin-project/lotus/storage"
|
||||
"github.com/filecoin-project/lotus/storage/sectorblocks"
|
||||
)
|
||||
@ -56,6 +61,9 @@ type StorageMinerAPI struct {
|
||||
DataTransfer dtypes.ProviderDataTransfer
|
||||
Host host.Host
|
||||
|
||||
DS dtypes.MetadataDS
|
||||
Repo repo.LockedRepo
|
||||
|
||||
ConsiderOnlineStorageDealsConfigFunc dtypes.ConsiderOnlineStorageDealsConfigFunc
|
||||
SetConsiderOnlineStorageDealsConfigFunc dtypes.SetConsiderOnlineStorageDealsConfigFunc
|
||||
ConsiderOnlineRetrievalDealsConfigFunc dtypes.ConsiderOnlineRetrievalDealsConfigFunc
|
||||
@ -516,4 +524,59 @@ func (sm *StorageMinerAPI) PiecesGetCIDInfo(ctx context.Context, payloadCid cid.
|
||||
return &ci, nil
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) CreateBackup(ctx context.Context, fpath string) error {
|
||||
// TODO: Config
|
||||
bb, ok := os.LookupEnv("LOTUS_BACKUP_BASE_PATH")
|
||||
if !ok {
|
||||
return xerrors.Errorf("LOTUS_BACKUP_BASE_PATH env var not set")
|
||||
}
|
||||
|
||||
bds, ok := sm.DS.(*backupds.Datastore)
|
||||
if !ok {
|
||||
return xerrors.Errorf("expected a backup datastore")
|
||||
}
|
||||
|
||||
bb, err := homedir.Expand(bb)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("expanding base path: %w", err)
|
||||
}
|
||||
|
||||
bb, err = filepath.Abs(bb)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting absolute base path: %w", err)
|
||||
}
|
||||
|
||||
fpath, err = homedir.Expand(fpath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("expanding file path: %w", err)
|
||||
}
|
||||
|
||||
fpath, err = filepath.Abs(fpath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting absolute file path: %w", err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(fpath, bb) {
|
||||
return xerrors.Errorf("backup file name (%s) must be inside base path (%s)", fpath, bb)
|
||||
}
|
||||
|
||||
out, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("open %s: %w", fpath, err)
|
||||
}
|
||||
|
||||
if err := bds.Backup(out); err != nil {
|
||||
if cerr := out.Close(); cerr != nil {
|
||||
log.Errorw("error closing backup file while handling backup error", "closeErr", cerr, "backupErr", err)
|
||||
}
|
||||
return xerrors.Errorf("backup error: %w", err)
|
||||
}
|
||||
|
||||
if err := out.Close(); err != nil {
|
||||
return xerrors.Errorf("closing backup file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ api.StorageMiner = &StorageMinerAPI{}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"go.uber.org/fx"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/backupds"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
"github.com/filecoin-project/lotus/node/repo"
|
||||
)
|
||||
@ -27,5 +28,10 @@ func KeyStore(lr repo.LockedRepo) (types.KeyStore, error) {
|
||||
}
|
||||
|
||||
func Datastore(r repo.LockedRepo) (dtypes.MetadataDS, error) {
|
||||
return r.Datastore("/metadata")
|
||||
mds, err := r.Datastore("/metadata")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return backupds.Wrap(mds), nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user