diff --git a/.circleci/config.yml b/.circleci/config.yml index 07a132a87..a22de28af 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -903,6 +903,11 @@ workflows: suite: itest-nonce target: "./itests/nonce_test.go" + - test: + name: test-itest-path_type_filters + suite: itest-path_type_filters + target: "./itests/path_type_filters_test.go" + - test: name: test-itest-paych_api suite: itest-paych_api @@ -978,6 +983,11 @@ workflows: suite: itest-wdpost_dispute target: "./itests/wdpost_dispute_test.go" + - test: + name: test-itest-wdpost_no_miner_storage + suite: itest-wdpost_no_miner_storage + target: "./itests/wdpost_no_miner_storage_test.go" + - test: name: test-itest-wdpost suite: itest-wdpost diff --git a/api/api_storage.go b/api/api_storage.go index 197bc71de..e9135e30d 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -146,18 +146,29 @@ type StorageMiner interface { SealingSchedDiag(ctx context.Context, doSched bool) (interface{}, error) //perm:admin SealingAbort(ctx context.Context, call storiface.CallID) error //perm:admin - // SectorIndex - StorageAttach(context.Context, storiface.StorageInfo, fsutil.FsStat) error //perm:admin - StorageInfo(context.Context, storiface.ID) (storiface.StorageInfo, error) //perm:admin - StorageReportHealth(context.Context, storiface.ID, storiface.HealthReport) error //perm:admin - StorageDeclareSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType, primary bool) error //perm:admin - StorageDropSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType) error //perm:admin + // paths.SectorIndex + StorageAttach(context.Context, storiface.StorageInfo, fsutil.FsStat) error //perm:admin + StorageInfo(context.Context, storiface.ID) (storiface.StorageInfo, error) //perm:admin + StorageReportHealth(context.Context, storiface.ID, storiface.HealthReport) error //perm:admin + StorageDeclareSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType, primary bool) error //perm:admin + StorageDropSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType) error //perm:admin + // StorageFindSector returns list of paths where the specified sector files exist. + // + // If allowFetch is set, list of paths to which the sector can be fetched will also be returned. + // - Paths which have sector files locally (don't require fetching) will be listed first. + // - Paths which have sector files locally will not be filtered based on based on AllowTypes/DenyTypes. + // - Paths which require fetching will be filtered based on AllowTypes/DenyTypes. If multiple + // file types are specified, each type will be considered individually, and a union of all paths + // which can accommodate each file type will be returned. StorageFindSector(ctx context.Context, sector abi.SectorID, ft storiface.SectorFileType, ssize abi.SectorSize, allowFetch bool) ([]storiface.SectorStorageInfo, error) //perm:admin - StorageBestAlloc(ctx context.Context, allocate storiface.SectorFileType, ssize abi.SectorSize, pathType storiface.PathType) ([]storiface.StorageInfo, error) //perm:admin - StorageLock(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) error //perm:admin - StorageTryLock(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) (bool, error) //perm:admin - StorageList(ctx context.Context) (map[storiface.ID][]storiface.Decl, error) //perm:admin - StorageGetLocks(ctx context.Context) (storiface.SectorLocks, error) //perm:admin + // StorageBestAlloc returns list of paths where sector files of the specified type can be allocated, ordered by preference. + // Paths with more weight and more % of free space are preferred. + // Note: This method doesn't filter paths based on AllowTypes/DenyTypes. + StorageBestAlloc(ctx context.Context, allocate storiface.SectorFileType, ssize abi.SectorSize, pathType storiface.PathType) ([]storiface.StorageInfo, error) //perm:admin + StorageLock(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) error //perm:admin + StorageTryLock(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) (bool, error) //perm:admin + StorageList(ctx context.Context) (map[storiface.ID][]storiface.Decl, error) //perm:admin + StorageGetLocks(ctx context.Context) (storiface.SectorLocks, error) //perm:admin StorageLocal(ctx context.Context) (map[storiface.ID]string, error) //perm:admin StorageStat(ctx context.Context, id storiface.ID) (fsutil.FsStat, error) //perm:admin diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index e0537ab24..0eca81421 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/cmd/lotus-miner/init.go b/cmd/lotus-miner/init.go index 2daced42e..fc7dc03f6 100644 --- a/cmd/lotus-miner/init.go +++ b/cmd/lotus-miner/init.go @@ -458,7 +458,7 @@ func storageMinerInit(ctx context.Context, cctx *cli.Context, api v1api.FullNode wsts := statestore.New(namespace.Wrap(mds, modules.WorkerCallsPrefix)) smsts := statestore.New(namespace.Wrap(mds, modules.ManagerWorkPrefix)) - si := paths.NewIndex() + si := paths.NewIndex(nil) lstor, err := paths.NewLocal(ctx, lr, si, nil) if err != nil { diff --git a/cmd/lotus-miner/storage.go b/cmd/lotus-miner/storage.go index 7823f484d..035af80c6 100644 --- a/cmd/lotus-miner/storage.go +++ b/cmd/lotus-miner/storage.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math/bits" "os" "path/filepath" "sort" @@ -345,6 +346,20 @@ var storageListCmd = &cli.Command{ fmt.Printf("\tAllowTo: %s\n", strings.Join(si.AllowTo, ", ")) } + if len(si.AllowTypes) > 0 || len(si.DenyTypes) > 0 { + denied := storiface.FTAll.SubAllowed(si.AllowTypes, si.DenyTypes) + allowed := storiface.FTAll ^ denied + + switch { + case bits.OnesCount64(uint64(allowed)) == 0: + fmt.Printf("\tAllow Types: %s\n", color.RedString("None")) + case bits.OnesCount64(uint64(allowed)) < bits.OnesCount64(uint64(denied)): + fmt.Printf("\tAllow Types: %s\n", color.GreenString(strings.Join(allowed.Strings(), " "))) + default: + fmt.Printf("\tDeny Types: %s\n", color.RedString(strings.Join(denied.Strings(), " "))) + } + } + if localPath, ok := local[s.ID]; ok { fmt.Printf("\tLocal: %s\n", color.GreenString(localPath)) } diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index e565ff1ab..2c781f5b0 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -3271,7 +3271,7 @@ Inputs: Response: `{}` ### StorageAttach -SectorIndex +paths.SectorIndex Perms: admin @@ -3293,6 +3293,12 @@ Inputs: ], "AllowTo": [ "string value" + ], + "AllowTypes": [ + "string value" + ], + "DenyTypes": [ + "string value" ] }, { @@ -3328,6 +3334,9 @@ Response: ``` ### StorageBestAlloc +StorageBestAlloc returns list of paths where sector files of the specified type can be allocated, ordered by preference. +Paths with more weight and more % of free space are preferred. +Note: This method doesn't filter paths based on AllowTypes/DenyTypes. Perms: admin @@ -3358,6 +3367,12 @@ Response: ], "AllowTo": [ "string value" + ], + "AllowTypes": [ + "string value" + ], + "DenyTypes": [ + "string value" ] } ] @@ -3403,6 +3418,14 @@ Inputs: Response: `{}` ### StorageFindSector +StorageFindSector returns list of paths where the specified sector files exist. + +If allowFetch is set, list of paths to which the sector can be fetched will also be returned. +- Paths which have sector files locally (don't require fetching) will be listed first. +- Paths which have sector files locally will not be filtered based on based on AllowTypes/DenyTypes. +- Paths which require fetching will be filtered based on AllowTypes/DenyTypes. If multiple + file types are specified, each type will be considered individually, and a union of all paths + which can accommodate each file type will be returned. Perms: admin @@ -3434,7 +3457,13 @@ Response: "Weight": 42, "CanSeal": true, "CanStore": true, - "Primary": true + "Primary": true, + "AllowTypes": [ + "string value" + ], + "DenyTypes": [ + "string value" + ] } ] ``` @@ -3502,6 +3531,12 @@ Response: ], "AllowTo": [ "string value" + ], + "AllowTypes": [ + "string value" + ], + "DenyTypes": [ + "string value" ] } ``` diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go index bbcc5a8d4..8f2d06b35 100644 --- a/itests/kit/blockminer.go +++ b/itests/kit/blockminer.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "strings" "sync" "sync/atomic" "testing" @@ -184,7 +185,12 @@ func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Dur var target abi.ChainEpoch reportSuccessFn := func(success bool, epoch abi.ChainEpoch, err error) { - require.NoError(bm.t, err) + // if api shuts down before mining, we may get an error which we should probably just ignore + // (fixing it will require rewriting most of the mining loop) + if err != nil && !strings.Contains(err.Error(), "websocket connection closed") { + require.NoError(bm.t, err) + } + target = epoch wait <- success } diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index dbd68ee01..124e8ddb2 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -546,7 +546,12 @@ func (n *Ensemble) Start() *Ensemble { // using real proofs, therefore need real sectors. if !n.bootstrapped && !n.options.mockProofs { psd := m.PresealDir + noPaths := m.options.noStorage + err := lr.SetStorage(func(sc *paths.StorageConfig) { + if noPaths { + sc.StoragePaths = []paths.LocalPath{} + } sc.StoragePaths = append(sc.StoragePaths, paths.LocalPath{Path: psd}) }) @@ -693,6 +698,13 @@ func (n *Ensemble) Start() *Ensemble { lr, err := r.Lock(repo.Worker) require.NoError(n.t, err) + if m.options.noStorage { + err := lr.SetStorage(func(sc *paths.StorageConfig) { + sc.StoragePaths = []paths.LocalPath{} + }) + require.NoError(n.t, err) + } + ds, err := lr.Datastore(context.Background(), "/metadata") require.NoError(n.t, err) diff --git a/itests/kit/node_miner.go b/itests/kit/node_miner.go index cedbb9204..65c7abe38 100644 --- a/itests/kit/node_miner.go +++ b/itests/kit/node_miner.go @@ -167,9 +167,8 @@ func (tm *TestMiner) FlushSealingBatches(ctx context.Context) { const metaFile = "sectorstore.json" -func (tm *TestMiner) AddStorage(ctx context.Context, t *testing.T, weight uint64, seal, store bool) { - p, err := ioutil.TempDir("", "lotus-testsectors-") - require.NoError(t, err) +func (tm *TestMiner) AddStorage(ctx context.Context, t *testing.T, conf func(*paths.LocalStorageMeta)) storiface.ID { + p := t.TempDir() if err := os.MkdirAll(p, 0755); err != nil { if !os.IsExist(err) { @@ -177,18 +176,20 @@ func (tm *TestMiner) AddStorage(ctx context.Context, t *testing.T, weight uint64 } } - _, err = os.Stat(filepath.Join(p, metaFile)) + _, err := os.Stat(filepath.Join(p, metaFile)) if !os.IsNotExist(err) { require.NoError(t, err) } cfg := &paths.LocalStorageMeta{ ID: storiface.ID(uuid.New().String()), - Weight: weight, - CanSeal: seal, - CanStore: store, + Weight: 10, + CanSeal: false, + CanStore: false, } + conf(cfg) + if !(cfg.CanStore || cfg.CanSeal) { t.Fatal("must specify at least one of CanStore or cfg.CanSeal") } @@ -201,4 +202,6 @@ func (tm *TestMiner) AddStorage(ctx context.Context, t *testing.T, weight uint64 err = tm.StorageAddLocal(ctx, p) require.NoError(t, err) + + return cfg.ID } diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index 69e6acbb2..0fe66eaea 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -43,6 +43,7 @@ type nodeOpts struct { minerNoLocalSealing bool // use worker minerAssigner string disallowRemoteFinalize bool + noStorage bool workerTasks []sealtasks.TaskType workerStorageOpt func(paths.Store) paths.Store @@ -154,6 +155,14 @@ func PresealSectors(sectors int) NodeOpt { } } +// NoStorage initializes miners with no writable storage paths (just read-only preseal paths) +func NoStorage() NodeOpt { + return func(opts *nodeOpts) error { + opts.noStorage = true + return nil + } +} + // ThroughRPC makes interactions with this node throughout the test flow through // the JSON-RPC API. func ThroughRPC() NodeOpt { @@ -210,6 +219,8 @@ func WithTaskTypes(tt []sealtasks.TaskType) NodeOpt { } } +var WithSealWorkerTasks = WithTaskTypes([]sealtasks.TaskType{sealtasks.TTFetch, sealtasks.TTCommit1, sealtasks.TTFinalize, sealtasks.TTAddPiece, sealtasks.TTPreCommit1, sealtasks.TTPreCommit2, sealtasks.TTCommit2, sealtasks.TTUnseal}) + func WithWorkerStorage(transform func(paths.Store) paths.Store) NodeOpt { return func(opts *nodeOpts) error { opts.workerStorageOpt = transform diff --git a/itests/path_type_filters_test.go b/itests/path_type_filters_test.go new file mode 100644 index 000000000..03dd5ea16 --- /dev/null +++ b/itests/path_type_filters_test.go @@ -0,0 +1,199 @@ +package itests + +import ( + "context" + "strings" + "testing" + "time" + + logging "github.com/ipfs/go-log/v2" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/storage/paths" + "github.com/filecoin-project/lotus/storage/sealer/sealtasks" + "github.com/filecoin-project/lotus/storage/sealer/storiface" +) + +func TestPathTypeFilters(t *testing.T) { + runTest := func(t *testing.T, name string, asserts func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func())) { + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _ = logging.SetLogLevel("storageminer", "INFO") + + var ( + client kit.TestFullNode + miner kit.TestMiner + wiw, wdw kit.TestWorker + ) + ens := kit.NewEnsemble(t, kit.LatestActorsAt(-1)). + FullNode(&client, kit.ThroughRPC()). + Miner(&miner, &client, kit.WithAllSubsystems(), kit.ThroughRPC(), kit.PresealSectors(2), kit.NoStorage()). + Worker(&miner, &wiw, kit.ThroughRPC(), kit.NoStorage(), kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTGenerateWinningPoSt})). + Worker(&miner, &wdw, kit.ThroughRPC(), kit.NoStorage(), kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTGenerateWindowPoSt})). + Start() + + ens.InterconnectAll().BeginMiningMustPost(2 * time.Millisecond) + + asserts(t, ctx, &miner, func() { + dh := kit.NewDealHarness(t, &client, &miner, &miner) + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1}) + }) + }) + } + + runTest(t, "invalid-type-alert", func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func()) { + slU := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanSeal = true + meta.AllowTypes = []string{"unsealed", "seeled"} + }) + + storlist, err := miner.StorageList(ctx) + require.NoError(t, err) + + require.Len(t, storlist, 2) // 1 path we've added + preseal + + si, err := miner.StorageInfo(ctx, slU) + require.NoError(t, err) + + // check that bad entries are filtered + require.Len(t, si.DenyTypes, 0) + require.Len(t, si.AllowTypes, 1) + require.Equal(t, "unsealed", si.AllowTypes[0]) + + as, err := miner.LogAlerts(ctx) + require.NoError(t, err) + + var found bool + for _, a := range as { + if a.Active && a.Type.System == "sector-index" && strings.HasPrefix(a.Type.Subsystem, "pathconf-") { + require.False(t, found) + require.Contains(t, string(a.LastActive.Message), "unknown sector file type 'seeled'") + found = true + } + } + require.True(t, found) + }) + + runTest(t, "seal-to-stor-unseal-allowdeny", func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func()) { + // allow all types in the sealing path + sealScratch := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanSeal = true + }) + + // unsealed storage + unsStor := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanStore = true + meta.AllowTypes = []string{"unsealed"} + }) + + // other storage + sealStor := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanStore = true + meta.DenyTypes = []string{"unsealed"} + }) + + storlist, err := miner.StorageList(ctx) + require.NoError(t, err) + + require.Len(t, storlist, 4) // 3 paths we've added + preseal + + run() + + storlist, err = miner.StorageList(ctx) + require.NoError(t, err) + + require.Len(t, storlist[sealScratch], 0) + require.Len(t, storlist[unsStor], 1) + require.Len(t, storlist[sealStor], 1) + + require.Equal(t, storiface.FTUnsealed, storlist[unsStor][0].SectorFileType) + require.Equal(t, storiface.FTSealed|storiface.FTCache, storlist[sealStor][0].SectorFileType) + }) + + runTest(t, "sealstor-unseal-allowdeny", func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func()) { + // unsealed storage + unsStor := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanStore = true + meta.CanSeal = true + meta.AllowTypes = []string{"unsealed"} + }) + + // other storage + sealStor := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanStore = true + meta.CanSeal = true + meta.DenyTypes = []string{"unsealed"} + }) + + storlist, err := miner.StorageList(ctx) + require.NoError(t, err) + + require.Len(t, storlist, 3) // 2 paths we've added + preseal + + run() + + storlist, err = miner.StorageList(ctx) + require.NoError(t, err) + + require.Len(t, storlist[unsStor], 1) + require.Len(t, storlist[sealStor], 1) + + require.Equal(t, storiface.FTUnsealed, storlist[unsStor][0].SectorFileType) + require.Equal(t, storiface.FTSealed|storiface.FTCache, storlist[sealStor][0].SectorFileType) + }) + + runTest(t, "seal-store-allseparate", func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func()) { + // sealing stores + slU := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanSeal = true + meta.AllowTypes = []string{"unsealed"} + }) + slS := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanSeal = true + meta.AllowTypes = []string{"sealed"} + }) + slC := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanSeal = true + meta.AllowTypes = []string{"cache"} + }) + + // storage stores + stU := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanStore = true + meta.AllowTypes = []string{"unsealed"} + }) + stS := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanStore = true + meta.AllowTypes = []string{"sealed"} + }) + stC := miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.CanStore = true + meta.AllowTypes = []string{"cache"} + }) + + storlist, err := miner.StorageList(ctx) + require.NoError(t, err) + + require.Len(t, storlist, 7) // 6 paths we've added + preseal + + run() + + storlist, err = miner.StorageList(ctx) + require.NoError(t, err) + + require.Len(t, storlist[slU], 0) + require.Len(t, storlist[slS], 0) + require.Len(t, storlist[slC], 0) + + require.Len(t, storlist[stU], 1) + require.Len(t, storlist[stS], 1) + require.Len(t, storlist[stC], 1) + + require.Equal(t, storiface.FTUnsealed, storlist[stU][0].SectorFileType) + require.Equal(t, storiface.FTSealed, storlist[stS][0].SectorFileType) + require.Equal(t, storiface.FTCache, storlist[stC][0].SectorFileType) + }) +} diff --git a/itests/sector_finalize_early_test.go b/itests/sector_finalize_early_test.go index 7974870b6..e0b2cd815 100644 --- a/itests/sector_finalize_early_test.go +++ b/itests/sector_finalize_early_test.go @@ -11,6 +11,7 @@ import ( "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/storage/paths" ) func TestDealsWithFinalizeEarly(t *testing.T) { @@ -35,8 +36,14 @@ func TestDealsWithFinalizeEarly(t *testing.T) { ctx := context.Background() - miner.AddStorage(ctx, t, 1000000000, true, false) - miner.AddStorage(ctx, t, 1000000000, false, true) + miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.Weight = 1000000000 + meta.CanSeal = true + }) + miner.AddStorage(ctx, t, func(meta *paths.LocalStorageMeta) { + meta.Weight = 1000000000 + meta.CanStore = true + }) //stm: @STORAGE_LIST_001 sl, err := miner.StorageList(ctx) diff --git a/itests/wdpost_no_miner_storage_test.go b/itests/wdpost_no_miner_storage_test.go new file mode 100644 index 000000000..ac0eb8505 --- /dev/null +++ b/itests/wdpost_no_miner_storage_test.go @@ -0,0 +1,66 @@ +package itests + +import ( + "context" + "testing" + "time" + + logging "github.com/ipfs/go-log/v2" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/storage/sealer/sealtasks" +) + +func TestWindowPostNoMinerStorage(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _ = logging.SetLogLevel("storageminer", "INFO") + + sealSectors := 2 + presealSectors := 2*48*2 - sealSectors + + sectors := presealSectors + sealSectors + + var ( + client kit.TestFullNode + miner kit.TestMiner + wiw, wdw, sealw kit.TestWorker + ) + ens := kit.NewEnsemble(t, kit.LatestActorsAt(-1)). + FullNode(&client, kit.ThroughRPC()). + Miner(&miner, &client, kit.WithAllSubsystems(), kit.ThroughRPC(), kit.PresealSectors(presealSectors), kit.NoStorage()). + Worker(&miner, &wiw, kit.ThroughRPC(), kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTGenerateWinningPoSt})). + Worker(&miner, &wdw, kit.ThroughRPC(), kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTGenerateWindowPoSt})). + Worker(&miner, &sealw, kit.ThroughRPC(), kit.WithSealWorkerTasks). + Start() + + ens.InterconnectAll().BeginMiningMustPost(2 * time.Millisecond) + + miner.PledgeSectors(ctx, sealSectors, 0, nil) + + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + di = di.NextNotElapsed() + + // wait for new sectors to become active + waitUntil := di.Close + di.WPoStChallengeWindow*2 + di.WPoStProvingPeriod + t.Logf("Wait Height > %d", waitUntil) + + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now Height = %d", ts.Height()) + + p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + ssz, err := miner.ActorSectorSize(ctx, maddr) + require.NoError(t, err) + + require.Equal(t, p.MinerPower, p.TotalPower) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(sectors))) +} diff --git a/itests/worker_test.go b/itests/worker_test.go index 28e4cc31a..7328bd50f 100644 --- a/itests/worker_test.go +++ b/itests/worker_test.go @@ -29,7 +29,7 @@ import ( func TestWorkerPledge(t *testing.T) { ctx := context.Background() _, miner, worker, ens := kit.EnsembleWorker(t, kit.WithAllSubsystems(), kit.ThroughRPC(), kit.WithNoLocalSealing(true), - kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTFetch, sealtasks.TTCommit1, sealtasks.TTFinalize, sealtasks.TTAddPiece, sealtasks.TTPreCommit1, sealtasks.TTPreCommit2, sealtasks.TTCommit2, sealtasks.TTUnseal})) // no mock proofs + kit.WithSealWorkerTasks) // no mock proofs ens.InterconnectAll().BeginMining(50 * time.Millisecond) @@ -43,7 +43,7 @@ func TestWorkerPledge(t *testing.T) { func TestWorkerPledgeSpread(t *testing.T) { ctx := context.Background() _, miner, worker, ens := kit.EnsembleWorker(t, kit.WithAllSubsystems(), kit.ThroughRPC(), - kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTFetch, sealtasks.TTCommit1, sealtasks.TTFinalize, sealtasks.TTAddPiece, sealtasks.TTPreCommit1, sealtasks.TTPreCommit2, sealtasks.TTCommit2, sealtasks.TTUnseal}), + kit.WithSealWorkerTasks, kit.WithAssigner("spread"), ) // no mock proofs @@ -59,7 +59,7 @@ func TestWorkerPledgeSpread(t *testing.T) { func TestWorkerPledgeLocalFin(t *testing.T) { ctx := context.Background() _, miner, worker, ens := kit.EnsembleWorker(t, kit.WithAllSubsystems(), kit.ThroughRPC(), - kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTFetch, sealtasks.TTCommit1, sealtasks.TTFinalize, sealtasks.TTAddPiece, sealtasks.TTPreCommit1, sealtasks.TTPreCommit2, sealtasks.TTCommit2, sealtasks.TTUnseal}), + kit.WithSealWorkerTasks, kit.WithDisallowRemoteFinalize(true), ) // no mock proofs diff --git a/journal/alerting/alerts.go b/journal/alerting/alerts.go index b99638f77..290d06e2a 100644 --- a/journal/alerting/alerts.go +++ b/journal/alerting/alerts.go @@ -160,3 +160,10 @@ func (a *Alerting) GetAlerts() []Alert { return out } + +func (a *Alerting) IsRaised(at AlertType) bool { + a.lk.Lock() + defer a.lk.Unlock() + + return a.alerts[at].Active +} diff --git a/storage/paths/index.go b/storage/paths/index.go index 634c7b623..84646d016 100644 --- a/storage/paths/index.go +++ b/storage/paths/index.go @@ -17,6 +17,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/journal/alerting" "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/storage/sealer/fsutil" "github.com/filecoin-project/lotus/storage/sealer/storiface" @@ -63,15 +64,23 @@ type Index struct { *indexLocks lk sync.RWMutex + // optional + alerting *alerting.Alerting + pathAlerts map[storiface.ID]alerting.AlertType + sectors map[storiface.Decl][]*declMeta stores map[storiface.ID]*storageEntry } -func NewIndex() *Index { +func NewIndex(al *alerting.Alerting) *Index { return &Index{ indexLocks: &indexLocks{ locks: map[abi.SectorID]*sectorLock{}, }, + + alerting: al, + pathAlerts: map[storiface.ID]alerting.AlertType{}, + sectors: map[storiface.Decl][]*declMeta{}, stores: map[storiface.ID]*storageEntry{}, } @@ -107,6 +116,64 @@ func (i *Index) StorageList(ctx context.Context) (map[storiface.ID][]storiface.D } func (i *Index) StorageAttach(ctx context.Context, si storiface.StorageInfo, st fsutil.FsStat) error { + var allow, deny = make([]string, 0, len(si.AllowTypes)), make([]string, 0, len(si.DenyTypes)) + + if _, hasAlert := i.pathAlerts[si.ID]; i.alerting != nil && !hasAlert { + i.pathAlerts[si.ID] = i.alerting.AddAlertType("sector-index", "pathconf-"+string(si.ID)) + } + + var hasConfigIsses bool + + for id, typ := range si.AllowTypes { + _, err := storiface.TypeFromString(typ) + if err != nil { + // No need to hard-fail here, just warn the user + // (note that even with all-invalid entries we'll deny all types, so nothing unexpected should enter the path) + hasConfigIsses = true + + if i.alerting != nil { + i.alerting.Raise(i.pathAlerts[si.ID], map[string]interface{}{ + "message": "bad path type in AllowTypes", + "path": string(si.ID), + "idx": id, + "path_type": typ, + "error": err.Error(), + }) + } + + continue + } + allow = append(allow, typ) + } + for id, typ := range si.DenyTypes { + _, err := storiface.TypeFromString(typ) + if err != nil { + // No need to hard-fail here, just warn the user + hasConfigIsses = true + + if i.alerting != nil { + i.alerting.Raise(i.pathAlerts[si.ID], map[string]interface{}{ + "message": "bad path type in DenyTypes", + "path": string(si.ID), + "idx": id, + "path_type": typ, + "error": err.Error(), + }) + } + + continue + } + deny = append(deny, typ) + } + si.AllowTypes = allow + si.DenyTypes = deny + + if i.alerting != nil && !hasConfigIsses && i.alerting.IsRaised(i.pathAlerts[si.ID]) { + i.alerting.Resolve(i.pathAlerts[si.ID], map[string]string{ + "message": "path config is now correct", + }) + } + i.lk.Lock() defer i.lk.Unlock() @@ -136,6 +203,8 @@ func (i *Index) StorageAttach(ctx context.Context, si storiface.StorageInfo, st i.stores[si.ID].info.CanStore = si.CanStore i.stores[si.ID].info.Groups = si.Groups i.stores[si.ID].info.AllowTo = si.AllowTo + i.stores[si.ID].info.AllowTypes = allow + i.stores[si.ID].info.DenyTypes = deny return nil } @@ -312,6 +381,9 @@ func (i *Index) StorageFindSector(ctx context.Context, s abi.SectorID, ft storif CanStore: st.info.CanStore, Primary: isprimary[id], + + AllowTypes: st.info.AllowTypes, + DenyTypes: st.info.DenyTypes, }) } @@ -345,6 +417,11 @@ func (i *Index) StorageFindSector(ctx context.Context, s abi.SectorID, ft storif continue } + if !ft.AnyAllowed(st.info.AllowTypes, st.info.DenyTypes) { + log.Debugf("not selecting on %s, not allowed by file type filters", st.info.ID) + continue + } + if allowTo != nil { allow := false for _, group := range st.info.Groups { @@ -383,6 +460,9 @@ func (i *Index) StorageFindSector(ctx context.Context, s abi.SectorID, ft storif CanStore: st.info.CanStore, Primary: false, + + AllowTypes: st.info.AllowTypes, + DenyTypes: st.info.DenyTypes, }) } } diff --git a/storage/paths/index_test.go b/storage/paths/index_test.go index 3eb2dbec4..9a241da23 100644 --- a/storage/paths/index_test.go +++ b/storage/paths/index_test.go @@ -42,7 +42,7 @@ const s32g = 32 << 30 func TestFindSimple(t *testing.T) { ctx := context.Background() - i := NewIndex() + i := NewIndex(nil) stor1 := newTestStorage() stor2 := newTestStorage() @@ -79,7 +79,7 @@ func TestFindSimple(t *testing.T) { func TestFindNoAllow(t *testing.T) { ctx := context.Background() - i := NewIndex() + i := NewIndex(nil) stor1 := newTestStorage() stor1.AllowTo = []storiface.Group{"grp1"} stor2 := newTestStorage() @@ -111,7 +111,7 @@ func TestFindNoAllow(t *testing.T) { func TestFindAllow(t *testing.T) { ctx := context.Background() - i := NewIndex() + i := NewIndex(nil) stor1 := newTestStorage() stor1.AllowTo = []storiface.Group{"grp1"} diff --git a/storage/paths/local.go b/storage/paths/local.go index 4e5c934ad..698da1e75 100644 --- a/storage/paths/local.go +++ b/storage/paths/local.go @@ -44,6 +44,30 @@ type LocalStorageMeta struct { // List of storage groups to which data from this path can be moved. If none // are specified, allow to all AllowTo []string + + // AllowTypes lists sector file types which are allowed to be put into this + // path. If empty, all file types are allowed. + // + // Valid values: + // - "unsealed" + // - "sealed" + // - "cache" + // - "update" + // - "update-cache" + // Any other value will generate a warning and be ignored. + AllowTypes []string + + // DenyTypes lists sector file types which aren't allowed to be put into this + // path. + // + // Valid values: + // - "unsealed" + // - "sealed" + // - "cache" + // - "update" + // - "update-cache" + // Any other value will generate a warning and be ignored. + DenyTypes []string } // StorageConfig .lotusstorage/storage.json @@ -218,6 +242,8 @@ func (st *Local) OpenPath(ctx context.Context, p string) error { CanStore: meta.CanStore, Groups: meta.Groups, AllowTo: meta.AllowTo, + AllowTypes: meta.AllowTypes, + DenyTypes: meta.DenyTypes, }, fst) if err != nil { return xerrors.Errorf("declaring storage in index: %w", err) @@ -284,6 +310,8 @@ func (st *Local) Redeclare(ctx context.Context) error { CanStore: meta.CanStore, Groups: meta.Groups, AllowTo: meta.AllowTo, + AllowTypes: meta.AllowTypes, + DenyTypes: meta.DenyTypes, }, fst) if err != nil { return xerrors.Errorf("redeclaring storage in index: %w", err) @@ -506,6 +534,10 @@ func (st *Local) AcquireSector(ctx context.Context, sid storiface.SectorRef, exi continue } + if !fileType.Allowed(si.AllowTypes, si.DenyTypes) { + continue + } + // TODO: Check free space best = p.sectorPath(sid.ID, fileType) diff --git a/storage/paths/local_test.go b/storage/paths/local_test.go index c4891811a..83e8e27fd 100644 --- a/storage/paths/local_test.go +++ b/storage/paths/local_test.go @@ -81,7 +81,7 @@ func TestLocalStorage(t *testing.T) { root: root, } - index := NewIndex() + index := NewIndex(nil) st, err := NewLocal(ctx, tstor, index, nil) require.NoError(t, err) diff --git a/storage/paths/remote.go b/storage/paths/remote.go index 8375b66d4..331f2cf7a 100644 --- a/storage/paths/remote.go +++ b/storage/paths/remote.go @@ -140,20 +140,21 @@ func (r *Remote) AcquireSector(ctx context.Context, s storiface.SectorRef, exist } } - apaths, ids, err := r.local.AcquireSector(ctx, s, storiface.FTNone, toFetch, pathType, op) + // get a list of paths to fetch data into. Note: file type filters will apply inside this call. + fetchPaths, ids, err := r.local.AcquireSector(ctx, s, storiface.FTNone, toFetch, pathType, op) if err != nil { return storiface.SectorPaths{}, storiface.SectorPaths{}, xerrors.Errorf("allocate local sector for fetching: %w", err) } - odt := storiface.FSOverheadSeal + overheadTable := storiface.FSOverheadSeal if pathType == storiface.PathStorage { - odt = storiface.FsOverheadFinalized + overheadTable = storiface.FsOverheadFinalized } // If any path types weren't found in local storage, try fetching them // First reserve storage - releaseStorage, err := r.local.Reserve(ctx, s, toFetch, ids, odt) + releaseStorage, err := r.local.Reserve(ctx, s, toFetch, ids, overheadTable) if err != nil { return storiface.SectorPaths{}, storiface.SectorPaths{}, xerrors.Errorf("reserving storage space: %w", err) } @@ -168,7 +169,7 @@ func (r *Remote) AcquireSector(ctx context.Context, s storiface.SectorRef, exist continue } - dest := storiface.PathByType(apaths, fileType) + dest := storiface.PathByType(fetchPaths, fileType) storageID := storiface.PathByType(ids, fileType) url, err := r.acquireFromRemote(ctx, s.ID, fileType, dest) diff --git a/storage/paths/remote_test.go b/storage/paths/remote_test.go index d59b2c270..ec58fb84d 100644 --- a/storage/paths/remote_test.go +++ b/storage/paths/remote_test.go @@ -60,7 +60,7 @@ func createTestStorage(t *testing.T, p string, seal bool, att ...*paths.Local) s func TestMoveShared(t *testing.T) { logging.SetAllLoggers(logging.LevelDebug) - index := paths.NewIndex() + index := paths.NewIndex(nil) ctx := context.Background() diff --git a/storage/sealer/manager_test.go b/storage/sealer/manager_test.go index 75f12261c..d287462e0 100644 --- a/storage/sealer/manager_test.go +++ b/storage/sealer/manager_test.go @@ -100,7 +100,7 @@ var _ paths.LocalStorage = &testStorage{} func newTestMgr(ctx context.Context, t *testing.T, ds datastore.Datastore) (*Manager, *paths.Local, *paths.Remote, *paths.Index, func()) { st := newTestStorage(t) - si := paths.NewIndex() + si := paths.NewIndex(nil) lstor, err := paths.NewLocal(ctx, st, si, nil) require.NoError(t, err) diff --git a/storage/sealer/piece_provider_test.go b/storage/sealer/piece_provider_test.go index 8da155604..c4c71bc53 100644 --- a/storage/sealer/piece_provider_test.go +++ b/storage/sealer/piece_provider_test.go @@ -206,7 +206,7 @@ func newPieceProviderTestHarness(t *testing.T, mgrConfig Config, sectorProofType require.NoError(t, err) // create index, storage, local store & remote store. - index := paths.NewIndex() + index := paths.NewIndex(nil) storage := newTestStorage(t) localStore, err := paths.NewLocal(ctx, storage, index, []string{"http://" + nl.Addr().String() + "/remote"}) require.NoError(t, err) diff --git a/storage/sealer/sched_test.go b/storage/sealer/sched_test.go index 258383e90..bca8ace6e 100644 --- a/storage/sealer/sched_test.go +++ b/storage/sealer/sched_test.go @@ -226,7 +226,7 @@ func TestSchedStartStop(t *testing.T) { require.NoError(t, err) go sched.runSched() - addTestWorker(t, sched, paths.NewIndex(), "fred", nil, decentWorkerResources, false) + addTestWorker(t, sched, paths.NewIndex(nil), "fred", nil, decentWorkerResources, false) require.NoError(t, sched.Close(context.TODO())) } @@ -350,7 +350,7 @@ func TestSched(t *testing.T) { testFunc := func(workers []workerSpec, tasks []task) func(t *testing.T) { return func(t *testing.T) { - index := paths.NewIndex() + index := paths.NewIndex(nil) sched, err := newScheduler("") require.NoError(t, err) diff --git a/storage/sealer/selector_alloc.go b/storage/sealer/selector_alloc.go index 6418dcb85..ce64820f7 100644 --- a/storage/sealer/selector_alloc.go +++ b/storage/sealer/selector_alloc.go @@ -55,13 +55,20 @@ func (s *allocSelector) Ok(ctx context.Context, task sealtasks.TaskType, spt abi return false, false, xerrors.Errorf("finding best alloc storage: %w", err) } + requested := s.alloc + for _, info := range best { if _, ok := have[info.ID]; ok { - return true, false, nil + requested = requested.SubAllowed(info.AllowTypes, info.DenyTypes) + + // got all paths + if requested == storiface.FTNone { + break + } } } - return false, false, nil + return requested == storiface.FTNone, false, nil } func (s *allocSelector) Cmp(ctx context.Context, task sealtasks.TaskType, a, b *WorkerHandle) (bool, error) { diff --git a/storage/sealer/selector_existing.go b/storage/sealer/selector_existing.go index 02f5eb55a..830213f1e 100644 --- a/storage/sealer/selector_existing.go +++ b/storage/sealer/selector_existing.go @@ -15,7 +15,7 @@ import ( type existingSelector struct { index paths.SectorIndex sector abi.SectorID - alloc storiface.SectorFileType + fileType storiface.SectorFileType allowFetch bool } @@ -23,7 +23,7 @@ func newExistingSelector(index paths.SectorIndex, sector abi.SectorID, alloc sto return &existingSelector{ index: index, sector: sector, - alloc: alloc, + fileType: alloc, allowFetch: allowFetch, } } @@ -52,18 +52,30 @@ func (s *existingSelector) Ok(ctx context.Context, task sealtasks.TaskType, spt return false, false, xerrors.Errorf("getting sector size: %w", err) } - best, err := s.index.StorageFindSector(ctx, s.sector, s.alloc, ssize, s.allowFetch) + best, err := s.index.StorageFindSector(ctx, s.sector, s.fileType, ssize, s.allowFetch) if err != nil { return false, false, xerrors.Errorf("finding best storage: %w", err) } + requested := s.fileType + for _, info := range best { if _, ok := have[info.ID]; ok { - return true, false, nil + // we're not putting new sector files anywhere + if !s.allowFetch { + return true, false, nil + } + + requested = requested.SubAllowed(info.AllowTypes, info.DenyTypes) + + // got all paths + if requested == storiface.FTNone { + break + } } } - return false, false, nil + return requested == storiface.FTNone, false, nil } func (s *existingSelector) Cmp(ctx context.Context, task sealtasks.TaskType, a, b *WorkerHandle) (bool, error) { diff --git a/storage/sealer/selector_move.go b/storage/sealer/selector_move.go index 63fd2deb5..c1f402456 100644 --- a/storage/sealer/selector_move.go +++ b/storage/sealer/selector_move.go @@ -72,7 +72,8 @@ func (s *moveSelector) Ok(ctx context.Context, task sealtasks.TaskType, spt abi. return false, false, xerrors.Errorf("finding best dest storage: %w", err) } - var ok bool + var ok, pref bool + requested := s.alloc for _, info := range best { if n, has := workerPaths[info.ID]; has { @@ -83,12 +84,19 @@ func (s *moveSelector) Ok(ctx context.Context, task sealtasks.TaskType, spt abi. // either a no-op because the sector is already in the correct path, // or the move a local move. if n > 0 { - return true, true, nil + pref = true + } + + requested = requested.SubAllowed(info.AllowTypes, info.DenyTypes) + + // got all paths + if requested == storiface.FTNone { + break } } } - return ok && s.allowRemote, false, nil + return (ok && s.allowRemote) || pref, pref, nil } func (s *moveSelector) Cmp(ctx context.Context, task sealtasks.TaskType, a, b *WorkerHandle) (bool, error) { diff --git a/storage/sealer/storiface/filetype.go b/storage/sealer/storiface/filetype.go index ce6f2e8e1..5aca9f4db 100644 --- a/storage/sealer/storiface/filetype.go +++ b/storage/sealer/storiface/filetype.go @@ -24,6 +24,13 @@ const ( FTNone SectorFileType = 0 ) +var FTAll = func() (out SectorFileType) { + for _, pathType := range PathTypes { + out |= pathType + } + return out +}() + const FSOverheadDen = 10 var FSOverheadSeal = map[SectorFileType]int{ // 10x overheads @@ -46,6 +53,23 @@ var FsOverheadFinalized = map[SectorFileType]int{ type SectorFileType int +func TypeFromString(s string) (SectorFileType, error) { + switch s { + case "unsealed": + return FTUnsealed, nil + case "sealed": + return FTSealed, nil + case "cache": + return FTCache, nil + case "update": + return FTUpdate, nil + case "update-cache": + return FTUpdateCache, nil + default: + return 0, xerrors.Errorf("unknown sector file type '%s'", s) + } +} + func (t SectorFileType) String() string { switch t { case FTUnsealed: @@ -63,6 +87,18 @@ func (t SectorFileType) String() string { } } +func (t SectorFileType) Strings() []string { + var out []string + for _, fileType := range PathTypes { + if fileType&t == 0 { + continue + } + + out = append(out, fileType.String()) + } + return out +} + func (t SectorFileType) Has(singleType SectorFileType) bool { return t&singleType == singleType } @@ -85,6 +121,43 @@ func (t SectorFileType) SealSpaceUse(ssize abi.SectorSize) (uint64, error) { return need, nil } +func (t SectorFileType) SubAllowed(allowTypes []string, denyTypes []string) SectorFileType { + var denyMask SectorFileType // 1s deny + + if len(allowTypes) > 0 { + denyMask = ^denyMask + + for _, allowType := range allowTypes { + pt, err := TypeFromString(allowType) + if err != nil { + // we've told the user about this already, don't spam logs and ignore + continue + } + + denyMask = denyMask & (^pt) // unset allowed types + } + } + + for _, denyType := range denyTypes { + pt, err := TypeFromString(denyType) + if err != nil { + // we've told the user about this already, don't spam logs and ignore + continue + } + denyMask |= pt + } + + return t & denyMask +} + +func (t SectorFileType) AnyAllowed(allowTypes []string, denyTypes []string) bool { + return t.SubAllowed(allowTypes, denyTypes) != t +} + +func (t SectorFileType) Allowed(allowTypes []string, denyTypes []string) bool { + return t.SubAllowed(allowTypes, denyTypes) == 0 +} + func (t SectorFileType) StoreSpaceUse(ssize abi.SectorSize) (uint64, error) { var need uint64 for _, pathType := range PathTypes { diff --git a/storage/sealer/storiface/filetype_test.go b/storage/sealer/storiface/filetype_test.go new file mode 100644 index 000000000..62ba59e92 --- /dev/null +++ b/storage/sealer/storiface/filetype_test.go @@ -0,0 +1,39 @@ +package storiface + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFileTypeAllow(t *testing.T) { + // no filters = allow all + require.True(t, FTCache.Allowed(nil, nil)) + + // allow allows matching type + require.True(t, FTCache.Allowed((FTCache).Strings(), nil)) + + // deny denies matching type + require.False(t, FTCache.Allowed(nil, (FTCache).Strings())) + + // deny has precedence over allow + require.False(t, FTCache.Allowed((FTCache).Strings(), (FTCache).Strings())) + + // deny allows non-matching types + require.True(t, FTUnsealed.Allowed(nil, (FTCache).Strings())) + + // allow denies non-matching types + require.False(t, FTUnsealed.Allowed((FTCache).Strings(), nil)) +} + +func TestFileTypeAnyAllow(t *testing.T) { + // no filters = allow all + require.True(t, FTCache.AnyAllowed(nil, nil)) + + // one denied + require.False(t, FTCache.AnyAllowed(nil, (FTCache).Strings())) + require.True(t, FTCache.AnyAllowed(nil, (FTUnsealed).Strings())) + + // one denied, one allowed = allowed + require.True(t, (FTCache|FTUpdateCache).AnyAllowed(nil, (FTCache).Strings())) +} diff --git a/storage/sealer/storiface/index.go b/storage/sealer/storiface/index.go index fdac7057f..a8f1f2f4a 100644 --- a/storage/sealer/storiface/index.go +++ b/storage/sealer/storiface/index.go @@ -36,16 +36,55 @@ func ParseIDList(s string) IDList { type Group = string type StorageInfo struct { - ID ID - URLs []string // TODO: Support non-http transports - Weight uint64 + // ID is the UUID of the storage path + ID ID + + // URLs for remote access + URLs []string // TODO: Support non-http transports + + // Storage path weight; higher number means that the path will be preferred more often + Weight uint64 + + // MaxStorage is the number of bytes allowed to be used by files in the + // storage path MaxStorage uint64 - CanSeal bool + // CanStore is true when the path is allowed to be used for io-intensive + // sealing operations + CanSeal bool + + // CanStore is true when the path is allowed to be used for long-term storage CanStore bool - Groups []Group + // Groups is the list of path groups this path belongs to + Groups []Group + + // AllowTo is the list of paths to which data from this path can be moved to AllowTo []Group + + // AllowTypes lists sector file types which are allowed to be put into this + // path. If empty, all file types are allowed. + // + // Valid values: + // - "unsealed" + // - "sealed" + // - "cache" + // - "update" + // - "update-cache" + // Any other value will generate a warning and be ignored. + AllowTypes []string + + // DenyTypes lists sector file types which aren't allowed to be put into this + // path. + // + // Valid values: + // - "unsealed" + // - "sealed" + // - "cache" + // - "update" + // - "update-cache" + // Any other value will generate a warning and be ignored. + DenyTypes []string } type HealthReport struct { @@ -63,6 +102,9 @@ type SectorStorageInfo struct { CanStore bool Primary bool + + AllowTypes []string + DenyTypes []string } type Decl struct {