Full node metadata backup

This commit is contained in:
Łukasz Magiera 2020-10-01 17:14:08 +02:00
parent 5c33982f72
commit e444977891
9 changed files with 235 additions and 165 deletions

View File

@ -475,6 +475,12 @@ type FullNode interface {
PaychVoucherAdd(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error)
PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error)
PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error)
// CreateBackup creates node backup onder the specified file name. The
// method requires that the lotus daemon 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 FileRef struct {

View File

@ -239,6 +239,8 @@ type FullNodeStruct struct {
PaychVoucherCreate func(context.Context, address.Address, big.Int, uint64) (*api.VoucherCreateResult, error) `perm:"sign"`
PaychVoucherList func(context.Context, address.Address) ([]*paych.SignedVoucher, error) `perm:"write"`
PaychVoucherSubmit func(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error) `perm:"sign"`
CreateBackup func(ctx context.Context, fpath string) error `perm:"admin"`
}
}
@ -1027,6 +1029,10 @@ func (c *FullNodeStruct) PaychVoucherSubmit(ctx context.Context, ch address.Addr
return c.Internal.PaychVoucherSubmit(ctx, ch, sv, secret, proof)
}
func (c *FullNodeStruct) CreateBackup(ctx context.Context, fpath string) error {
return c.Internal.CreateBackup(ctx, fpath)
}
// StorageMinerStruct
func (c *StorageMinerStruct) ActorAddress(ctx context.Context) (address.Address, error) {

125
cli/backup.go Normal file
View File

@ -0,0 +1,125 @@
package cli
import (
"context"
"fmt"
"os"
logging "github.com/ipfs/go-log/v2"
"github.com/mitchellh/go-homedir"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/lotus/lib/backupds"
"github.com/filecoin-project/lotus/node/repo"
)
type BackupAPI interface {
CreateBackup(ctx context.Context, fpath string) error
}
type BackupApiFn func(ctx *cli.Context) (BackupAPI, jsonrpc.ClientCloser, error)
func BackupCmd(repoFlag string, rt repo.RepoType, getApi BackupApiFn) *cli.Command {
var offlineBackup = func(cctx *cli.Context) error {
logging.SetLogLevel("badger", "ERROR") // nolint:errcheck
repoPath := cctx.String(repoFlag)
r, err := repo.NewFS(repoPath)
if err != nil {
return err
}
ok, err := r.Exists()
if err != nil {
return err
}
if !ok {
return xerrors.Errorf("repo at '%s' is not initialized", cctx.String(repoFlag))
}
lr, err := r.LockRO(rt)
if err != nil {
return xerrors.Errorf("locking repo: %w", err)
}
defer lr.Close() // nolint:errcheck
mds, err := lr.Datastore("/metadata")
if err != nil {
return xerrors.Errorf("getting metadata datastore: %w", err)
}
bds := backupds.Wrap(mds)
fpath, err := homedir.Expand(cctx.Args().First())
if err != nil {
return xerrors.Errorf("expanding file path: %w", err)
}
out, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return xerrors.Errorf("opening backup file %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 onlineBackup = func(cctx *cli.Context) error {
api, closer, err := getApi(cctx)
if err != nil {
return xerrors.Errorf("getting api: %w (if the node isn't running you can use the --offline flag)", err)
}
defer closer()
err = api.CreateBackup(ReqContext(cctx), cctx.Args().First())
if err != nil {
return err
}
fmt.Println("Success")
return nil
}
return &cli.Command{
Name: "backup",
Usage: "Create node metadata backup",
Description: `The backup command writes a copy of node metadata under the specified path
Online backups:
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`,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "offline",
Usage: "create backup without the node running",
},
},
ArgsUsage: "[backup file path]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return xerrors.Errorf("expected 1 argument")
}
if cctx.Bool("offline") {
return offlineBackup(cctx)
}
return onlineBackup(cctx)
},
}
}

View File

@ -1,115 +1,14 @@
package main
import (
"fmt"
"os"
logging "github.com/ipfs/go-log/v2"
"github.com/mitchellh/go-homedir"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-jsonrpc"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/lotus/lib/backupds"
"github.com/filecoin-project/lotus/node/repo"
)
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
Online backups:
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`,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "offline",
Usage: "create backup without the node running",
},
},
ArgsUsage: "[backup file path]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return xerrors.Errorf("expected 1 argument")
}
if cctx.Bool("offline") {
return offlineBackup(cctx)
}
return onlineBackup(cctx)
},
}
func offlineBackup(cctx *cli.Context) error {
logging.SetLogLevel("badger", "ERROR") // nolint:errcheck
repoPath := cctx.String(FlagMinerRepo)
r, err := repo.NewFS(repoPath)
if err != nil {
return err
}
ok, err := r.Exists()
if err != nil {
return err
}
if !ok {
return xerrors.Errorf("repo at '%s' is not initialized", cctx.String(FlagMinerRepo))
}
lr, err := r.LockRO(repo.StorageMiner)
if err != nil {
return xerrors.Errorf("locking repo: %w", err)
}
defer lr.Close() // nolint:errcheck
mds, err := lr.Datastore("/metadata")
if err != nil {
return xerrors.Errorf("getting metadata datastore: %w", err)
}
bds := backupds.Wrap(mds)
fpath, err := homedir.Expand(cctx.Args().First())
if err != nil {
return xerrors.Errorf("expanding file path: %w", err)
}
out, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return xerrors.Errorf("opening backup file %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
}
func onlineBackup(cctx *cli.Context) error {
api, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return xerrors.Errorf("getting api: %w (if the node isn't running you can use the --offline flag)", err)
}
defer closer()
err = api.CreateBackup(lcli.ReqContext(cctx), cctx.Args().First())
if err != nil {
return err
}
fmt.Println("Success")
return nil
}
var backupCmd = lcli.BackupCmd(FlagMinerRepo, repo.StorageMiner, func(cctx *cli.Context) (lcli.BackupAPI, jsonrpc.ClientCloser, error) {
return lcli.GetStorageMinerAPI(cctx)
})

14
cmd/lotus/backup.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"github.com/urfave/cli/v2"
"github.com/filecoin-project/go-jsonrpc"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/lotus/node/repo"
)
var backupCmd = lcli.BackupCmd("repo", repo.FullNode, func(cctx *cli.Context) (lcli.BackupAPI, jsonrpc.ClientCloser, error) {
return lcli.GetFullNodeAPI(cctx)
})

View File

@ -22,6 +22,7 @@ func main() {
local := []*cli.Command{
DaemonCmd,
backupCmd,
}
if AdvanceBlockCmd != nil {
local = append(local, AdvanceBlockCmd)

67
node/impl/backup.go Normal file
View File

@ -0,0 +1,67 @@
package impl
import (
"os"
"path/filepath"
"strings"
"github.com/mitchellh/go-homedir"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/lib/backupds"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
func backup(mds dtypes.MetadataDS, fpath string) error {
bb, ok := os.LookupEnv("LOTUS_BACKUP_BASE_PATH")
if !ok {
return xerrors.Errorf("LOTUS_BACKUP_BASE_PATH env var not set")
}
bds, ok := mds.(*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
}

View File

@ -1,6 +1,8 @@
package impl
import (
"context"
logging "github.com/ipfs/go-log/v2"
"github.com/filecoin-project/lotus/api"
@ -9,6 +11,7 @@ import (
"github.com/filecoin-project/lotus/node/impl/full"
"github.com/filecoin-project/lotus/node/impl/market"
"github.com/filecoin-project/lotus/node/impl/paych"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
var log = logging.Logger("node")
@ -26,6 +29,12 @@ type FullNodeAPI struct {
full.WalletAPI
full.SyncAPI
full.BeaconAPI
DS dtypes.MetadataDS
}
func (n *FullNodeAPI) CreateBackup(ctx context.Context, fpath string) error {
return backup(n.DS, fpath)
}
var _ api.FullNode = &FullNodeAPI{}

View File

@ -5,14 +5,11 @@ import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"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"
@ -34,11 +31,9 @@ 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"
)
@ -61,8 +56,7 @@ type StorageMinerAPI struct {
DataTransfer dtypes.ProviderDataTransfer
Host host.Host
DS dtypes.MetadataDS
Repo repo.LockedRepo
DS dtypes.MetadataDS
ConsiderOnlineStorageDealsConfigFunc dtypes.ConsiderOnlineStorageDealsConfigFunc
SetConsiderOnlineStorageDealsConfigFunc dtypes.SetConsiderOnlineStorageDealsConfigFunc
@ -525,58 +519,7 @@ func (sm *StorageMinerAPI) PiecesGetCIDInfo(ctx context.Context, payloadCid cid.
}
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
return backup(sm.DS, fpath)
}
var _ api.StorageMiner = &StorageMinerAPI{}