lotus/cmd/lotus-storage-miner/storage.go

406 lines
9.6 KiB
Go
Raw Normal View History

2020-03-09 06:13:22 +00:00
package main
import (
"encoding/json"
2020-03-16 17:50:07 +00:00
"fmt"
2020-03-09 06:13:22 +00:00
"io/ioutil"
"os"
"path/filepath"
2020-03-20 22:39:07 +00:00
"sort"
2020-03-19 19:51:33 +00:00
"strconv"
2020-05-01 12:06:19 +00:00
"strings"
"time"
2020-03-09 06:13:22 +00:00
2020-05-01 12:06:19 +00:00
"github.com/fatih/color"
2020-03-09 06:13:22 +00:00
"github.com/google/uuid"
"github.com/mitchellh/go-homedir"
"github.com/urfave/cli/v2"
2020-06-05 22:59:01 +00:00
"golang.org/x/xerrors"
2020-03-09 06:13:22 +00:00
2020-03-19 19:51:33 +00:00
"github.com/filecoin-project/go-address"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/extern/sector-storage/fsutil"
"github.com/filecoin-project/lotus/extern/sector-storage/stores"
2020-03-16 17:50:07 +00:00
"github.com/filecoin-project/lotus/chain/types"
2020-03-09 06:13:22 +00:00
lcli "github.com/filecoin-project/lotus/cli"
)
const metaFile = "sectorstore.json"
var storageCmd = &cli.Command{
Name: "storage",
Usage: "manage sector storage",
Description: `Sectors can be stored across many filesystem paths. These commands provide ways to
manage the storage the miner will used to store sectors long term for proving (refernces as 'store')
as well as how sectors will be stored while moving through the sealing pipeline (references as 'seal').`,
2020-03-09 06:13:22 +00:00
Subcommands: []*cli.Command{
storageAttachCmd,
2020-03-16 17:50:07 +00:00
storageListCmd,
2020-03-19 19:51:33 +00:00
storageFindCmd,
2020-03-09 06:13:22 +00:00
},
}
var storageAttachCmd = &cli.Command{
Name: "attach",
Usage: "attach local storage path",
Description: `Storage can be attach to the miner using this command. The storage volume list is stored local
to the miner in $LOTUS_MINER_PATH/storage.json. We do not recommend modifying this value without further
understanding of the storage system.
Each storage volume contains a configuration file which descripbes the capabilities of the volume. When the
'--init' flag is provided, this file will be created using the additional flags.
Weight
A high weight value means data will be more likely to be stored
Seal
Intermittment data for the sealing process will be stored here
Store
Finalized sectors that will be proved over will be stored here
`,
2020-03-09 06:13:22 +00:00
Flags: []cli.Flag{
&cli.BoolFlag{
2020-03-09 06:13:45 +00:00
Name: "init",
2020-03-09 06:13:22 +00:00
Usage: "initialize the path first",
},
&cli.Uint64Flag{
2020-03-09 06:13:45 +00:00
Name: "weight",
2020-03-09 06:13:22 +00:00
Usage: "(for init) path weight",
Value: 10,
},
&cli.BoolFlag{
2020-03-09 06:13:45 +00:00
Name: "seal",
2020-03-09 06:13:22 +00:00
Usage: "(for init) use path for sealing",
},
&cli.BoolFlag{
2020-03-09 06:13:45 +00:00
Name: "store",
2020-03-09 06:13:22 +00:00
Usage: "(for init) use path for long-term storage",
},
},
Action: func(cctx *cli.Context) error {
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := lcli.ReqContext(cctx)
if !cctx.Args().Present() {
return xerrors.Errorf("must specify storage path to attach")
}
p, err := homedir.Expand(cctx.Args().First())
if err != nil {
return xerrors.Errorf("expanding path: %w", err)
}
if cctx.Bool("init") {
2020-03-09 22:00:29 +00:00
if err := os.MkdirAll(p, 0755); err != nil {
if !os.IsExist(err) {
return err
}
}
2020-03-09 06:13:22 +00:00
_, err := os.Stat(filepath.Join(p, metaFile))
if !os.IsNotExist(err) {
if err == nil {
return xerrors.Errorf("path is already initialized")
}
return err
}
2020-03-19 15:10:19 +00:00
cfg := &stores.LocalStorageMeta{
2020-03-13 11:59:19 +00:00
ID: stores.ID(uuid.New().String()),
2020-03-09 06:13:22 +00:00
Weight: cctx.Uint64("weight"),
CanSeal: cctx.Bool("seal"),
CanStore: cctx.Bool("store"),
}
if !(cfg.CanStore || cfg.CanSeal) {
return xerrors.Errorf("must specify at least one of --store of --seal")
}
b, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return xerrors.Errorf("marshaling storage config: %w", err)
}
if err := ioutil.WriteFile(filepath.Join(p, metaFile), b, 0644); err != nil {
return xerrors.Errorf("persisting storage metadata (%s): %w", filepath.Join(p, metaFile), err)
}
}
return nodeApi.StorageAddLocal(ctx, p)
},
}
2020-03-16 17:50:07 +00:00
var storageListCmd = &cli.Command{
2020-03-19 19:51:33 +00:00
Name: "list",
Usage: "list local storage paths",
2020-05-01 12:06:19 +00:00
Flags: []cli.Flag{
&cli.BoolFlag{Name: "color"},
},
2020-03-16 17:50:07 +00:00
Action: func(cctx *cli.Context) error {
2020-05-01 12:06:19 +00:00
color.NoColor = !cctx.Bool("color")
2020-03-16 17:50:07 +00:00
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := lcli.ReqContext(cctx)
st, err := nodeApi.StorageList(ctx)
if err != nil {
return err
}
2020-03-19 19:51:33 +00:00
local, err := nodeApi.StorageLocal(ctx)
if err != nil {
return err
}
type fsInfo struct {
stores.ID
sectors []stores.Decl
2020-07-08 15:23:27 +00:00
stat fsutil.FsStat
}
sorted := make([]fsInfo, 0, len(st))
2020-03-23 14:56:22 +00:00
for id, decls := range st {
2020-05-01 12:06:19 +00:00
st, err := nodeApi.StorageStat(ctx, id)
if err != nil {
sorted = append(sorted, fsInfo{ID: id, sectors: decls})
continue
2020-05-01 12:06:19 +00:00
}
sorted = append(sorted, fsInfo{id, decls, st})
2020-03-23 14:56:22 +00:00
}
sort.Slice(sorted, func(i, j int) bool {
2020-05-01 12:06:19 +00:00
if sorted[i].stat.Capacity != sorted[j].stat.Capacity {
return sorted[i].stat.Capacity > sorted[j].stat.Capacity
}
2020-03-23 14:56:22 +00:00
return sorted[i].ID < sorted[j].ID
})
for _, s := range sorted {
2020-03-22 21:39:06 +00:00
var cnt [3]int
2020-03-23 14:56:22 +00:00
for _, decl := range s.sectors {
2020-03-22 21:39:06 +00:00
for i := range cnt {
if decl.SectorFileType&(1<<i) != 0 {
cnt[i]++
}
2020-03-16 17:50:07 +00:00
}
}
fmt.Printf("%s:\n", s.ID)
pingStart := time.Now()
st, err := nodeApi.StorageStat(ctx, s.ID)
if err != nil {
fmt.Printf("\t%s: %s:\n", color.RedString("Error"), err)
continue
}
ping := time.Now().Sub(pingStart)
2020-05-01 19:51:31 +00:00
usedPercent := (st.Capacity - st.Available) * 100 / st.Capacity
2020-05-01 12:06:19 +00:00
percCol := color.FgGreen
switch {
case usedPercent > 98:
percCol = color.FgRed
case usedPercent > 90:
percCol = color.FgYellow
}
2020-07-08 15:23:27 +00:00
var barCols = int64(50)
2020-05-01 19:51:31 +00:00
set := (st.Capacity - st.Available) * barCols / st.Capacity
used := (st.Capacity - (st.Available + st.Reserved)) * barCols / st.Capacity
reserved := set - used
bar := strings.Repeat("#", int(used)) + strings.Repeat("*", int(reserved)) + strings.Repeat(" ", int(barCols-set))
2020-05-01 12:06:19 +00:00
fmt.Printf("\t[%s] %s/%s %s\n", color.New(percCol).Sprint(bar),
2020-07-08 15:23:27 +00:00
types.SizeStr(types.NewInt(uint64(st.Capacity-st.Available))),
types.SizeStr(types.NewInt(uint64(st.Capacity))),
2020-05-01 12:06:19 +00:00
color.New(percCol).Sprintf("%d%%", usedPercent))
fmt.Printf("\t%s; %s; %s; Reserved: %s\n",
2020-05-01 12:06:19 +00:00
color.YellowString("Unsealed: %d", cnt[0]),
color.GreenString("Sealed: %d", cnt[1]),
color.BlueString("Caches: %d", cnt[2]),
types.SizeStr(types.NewInt(uint64(st.Reserved))))
2020-03-16 17:50:07 +00:00
2020-03-23 14:56:22 +00:00
si, err := nodeApi.StorageInfo(ctx, s.ID)
2020-03-16 17:50:07 +00:00
if err != nil {
return err
}
2020-03-22 21:39:06 +00:00
fmt.Print("\t")
if si.CanSeal || si.CanStore {
fmt.Printf("Weight: %d; Use: ", si.Weight)
if si.CanSeal {
2020-05-01 12:06:19 +00:00
fmt.Print(color.MagentaString("Seal "))
2020-03-22 21:39:06 +00:00
}
if si.CanStore {
2020-05-01 12:06:19 +00:00
fmt.Print(color.CyanString("Store"))
2020-03-22 21:39:06 +00:00
}
fmt.Println("")
} else {
2020-05-01 12:06:19 +00:00
fmt.Print(color.HiYellowString("Use: ReadOnly"))
2020-03-22 21:39:06 +00:00
}
2020-03-23 14:56:22 +00:00
if localPath, ok := local[s.ID]; ok {
2020-05-01 12:06:19 +00:00
fmt.Printf("\tLocal: %s\n", color.GreenString(localPath))
2020-03-19 19:51:33 +00:00
}
for i, l := range si.URLs {
var rtt string
if _, ok := local[s.ID]; !ok && i == 0 {
rtt = " (latency: " + ping.Truncate(time.Microsecond*100).String() + ")"
}
fmt.Printf("\tURL: %s%s\n", l, rtt) // TODO; try pinging maybe?? print latency?
2020-03-19 19:51:33 +00:00
}
2020-05-01 12:06:19 +00:00
fmt.Println()
2020-03-19 19:51:33 +00:00
}
return nil
},
}
type storedSector struct {
2020-03-20 22:39:07 +00:00
id stores.ID
store stores.SectorStorageInfo
2020-03-20 22:39:07 +00:00
2020-03-19 19:51:33 +00:00
unsealed, sealed, cache bool
}
var storageFindCmd = &cli.Command{
Name: "find",
Usage: "find sector in the storage system",
ArgsUsage: "[sector number]",
Action: func(cctx *cli.Context) error {
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := lcli.ReqContext(cctx)
ma, err := nodeApi.ActorAddress(ctx)
if err != nil {
return err
}
mid, err := address.IDFromAddress(ma)
if err != nil {
return err
}
if !cctx.Args().Present() {
2020-07-08 10:38:59 +00:00
return xerrors.New("Usage: lotus-miner storage find [sector number]")
2020-03-19 19:51:33 +00:00
}
snum, err := strconv.ParseUint(cctx.Args().First(), 10, 64)
if err != nil {
return err
}
sid := abi.SectorID{
Miner: abi.ActorID(mid),
Number: abi.SectorNumber(snum),
}
2020-08-11 07:27:03 +00:00
u, err := nodeApi.StorageFindSector(ctx, sid, stores.FTUnsealed, 0, false)
2020-03-19 19:51:33 +00:00
if err != nil {
return xerrors.Errorf("finding unsealed: %w", err)
}
2020-08-11 07:27:03 +00:00
s, err := nodeApi.StorageFindSector(ctx, sid, stores.FTSealed, 0, false)
2020-03-19 19:51:33 +00:00
if err != nil {
return xerrors.Errorf("finding sealed: %w", err)
}
2020-08-11 07:27:03 +00:00
c, err := nodeApi.StorageFindSector(ctx, sid, stores.FTCache, 0, false)
2020-03-19 19:51:33 +00:00
if err != nil {
return xerrors.Errorf("finding cache: %w", err)
}
byId := map[stores.ID]*storedSector{}
for _, info := range u {
sts, ok := byId[info.ID]
if !ok {
sts = &storedSector{
2020-03-20 22:39:07 +00:00
id: info.ID,
2020-03-19 19:51:33 +00:00
store: info,
}
byId[info.ID] = sts
}
sts.unsealed = true
}
for _, info := range s {
sts, ok := byId[info.ID]
if !ok {
sts = &storedSector{
2020-03-20 22:39:07 +00:00
id: info.ID,
2020-03-19 19:51:33 +00:00
store: info,
}
byId[info.ID] = sts
}
sts.sealed = true
}
for _, info := range c {
sts, ok := byId[info.ID]
if !ok {
sts = &storedSector{
2020-03-20 22:39:07 +00:00
id: info.ID,
2020-03-19 19:51:33 +00:00
store: info,
}
byId[info.ID] = sts
}
sts.cache = true
}
local, err := nodeApi.StorageLocal(ctx)
if err != nil {
return err
}
2020-03-20 22:39:07 +00:00
var out []*storedSector
for _, sector := range byId {
out = append(out, sector)
}
sort.Slice(out, func(i, j int) bool {
return out[i].id < out[j].id
})
for _, info := range out {
var types string
if info.unsealed {
types += "Unsealed, "
}
if info.sealed {
types += "Sealed, "
}
if info.cache {
types += "Cache, "
}
fmt.Printf("In %s (%s)\n", info.id, types[:len(types)-2])
2020-09-19 07:48:02 +00:00
fmt.Printf("\tSealing: %t; Storage: %t\n", info.store.CanSeal, info.store.CanStore)
2020-03-20 22:39:07 +00:00
if localPath, ok := local[info.id]; ok {
fmt.Printf("\tLocal (%s)\n", localPath)
} else {
fmt.Printf("\tRemote\n")
2020-03-19 19:51:33 +00:00
}
for _, l := range info.store.URLs {
fmt.Printf("\tURL: %s\n", l)
2020-03-16 17:50:07 +00:00
}
}
return nil
},
}