lotus/storage/paths/index.go

570 lines
15 KiB
Go
Raw Normal View History

package paths
2020-03-23 11:40:02 +00:00
import (
"context"
2020-11-12 09:29:42 +00:00
"errors"
2021-05-11 16:14:01 +00:00
"fmt"
2020-03-23 11:40:02 +00:00
"net/url"
gopath "path"
"sort"
"sync"
2020-05-08 16:08:48 +00:00
"time"
2020-03-23 11:40:02 +00:00
"go.opencensus.io/stats"
"go.opencensus.io/tag"
2020-03-23 11:40:02 +00:00
"golang.org/x/xerrors"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
2020-09-30 17:32:19 +00:00
"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"
2020-03-23 11:40:02 +00:00
)
var HeartbeatInterval = 10 * time.Second
var SkippedHeartbeatThresh = HeartbeatInterval * 5
2020-05-08 16:08:48 +00:00
//go:generate go run github.com/golang/mock/mockgen -destination=mocks/index.go -package=mocks . SectorIndex
2020-03-23 11:40:02 +00:00
type SectorIndex interface { // part of storage-miner api
2022-01-18 10:57:04 +00:00
StorageAttach(context.Context, storiface.StorageInfo, fsutil.FsStat) error
StorageInfo(context.Context, storiface.ID) (storiface.StorageInfo, error)
StorageReportHealth(context.Context, storiface.ID, storiface.HealthReport) error
2020-03-23 11:40:02 +00:00
2022-01-18 10:57:04 +00:00
StorageDeclareSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType, primary bool) error
StorageDropSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType) error
StorageFindSector(ctx context.Context, sector abi.SectorID, ft storiface.SectorFileType, ssize abi.SectorSize, allowFetch bool) ([]storiface.SectorStorageInfo, error)
2020-03-23 11:40:02 +00:00
2022-01-18 10:57:04 +00:00
StorageBestAlloc(ctx context.Context, allocate storiface.SectorFileType, ssize abi.SectorSize, pathType storiface.PathType) ([]storiface.StorageInfo, error)
// atomically acquire locks on all sector file types. close ctx to unlock
2020-09-06 16:54:00 +00:00
StorageLock(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) error
StorageTryLock(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) (bool, error)
2021-12-03 11:33:23 +00:00
StorageGetLocks(ctx context.Context) (storiface.SectorLocks, error)
2021-05-20 11:01:14 +00:00
2022-01-18 10:57:04 +00:00
StorageList(ctx context.Context) (map[storiface.ID][]storiface.Decl, error)
2020-03-23 11:40:02 +00:00
}
type declMeta struct {
2022-01-18 10:57:04 +00:00
storage storiface.ID
primary bool
}
2020-03-23 11:40:02 +00:00
type storageEntry struct {
2022-01-18 10:57:04 +00:00
info *storiface.StorageInfo
2020-07-08 14:58:09 +00:00
fsi fsutil.FsStat
2020-05-08 16:08:48 +00:00
lastHeartbeat time.Time
heartbeatErr error
2020-03-23 11:40:02 +00:00
}
type Index struct {
*indexLocks
2020-03-23 11:40:02 +00:00
lk sync.RWMutex
// optional
alerting *alerting.Alerting
pathAlerts map[storiface.ID]alerting.AlertType
2022-01-18 10:57:04 +00:00
sectors map[storiface.Decl][]*declMeta
stores map[storiface.ID]*storageEntry
2020-03-23 11:40:02 +00:00
}
func NewIndex(al *alerting.Alerting) *Index {
2020-03-23 11:40:02 +00:00
return &Index{
indexLocks: &indexLocks{
locks: map[abi.SectorID]*sectorLock{},
},
alerting: al,
pathAlerts: map[storiface.ID]alerting.AlertType{},
2022-01-18 10:57:04 +00:00
sectors: map[storiface.Decl][]*declMeta{},
stores: map[storiface.ID]*storageEntry{},
2020-03-23 11:40:02 +00:00
}
}
2022-01-18 10:57:04 +00:00
func (i *Index) StorageList(ctx context.Context) (map[storiface.ID][]storiface.Decl, error) {
2020-04-29 12:13:21 +00:00
i.lk.RLock()
defer i.lk.RUnlock()
2022-01-18 10:57:04 +00:00
byID := map[storiface.ID]map[abi.SectorID]storiface.SectorFileType{}
2020-03-23 11:40:02 +00:00
for id := range i.stores {
2020-09-06 16:54:00 +00:00
byID[id] = map[abi.SectorID]storiface.SectorFileType{}
2020-03-23 11:40:02 +00:00
}
for decl, ids := range i.sectors {
for _, id := range ids {
byID[id.storage][decl.SectorID] |= decl.SectorFileType
2020-03-23 11:40:02 +00:00
}
}
2022-01-18 10:57:04 +00:00
out := map[storiface.ID][]storiface.Decl{}
2020-03-23 11:40:02 +00:00
for id, m := range byID {
2022-01-18 10:57:04 +00:00
out[id] = []storiface.Decl{}
2020-03-23 11:40:02 +00:00
for sectorID, fileType := range m {
2022-01-18 10:57:04 +00:00
out[id] = append(out[id], storiface.Decl{
2020-03-23 11:40:02 +00:00
SectorID: sectorID,
SectorFileType: fileType,
})
}
}
return out, nil
}
2022-01-18 10:57:04 +00:00
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 {
2022-07-01 16:02:10 +00:00
_, err := storiface.TypeFromString(typ)
if err != nil {
// No need to hard-fail here, just warn the user
2022-07-01 16:02:10 +00:00
// (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
2022-07-01 16:02:10 +00:00
}
allow = append(allow, typ)
2022-07-01 16:02:10 +00:00
}
for id, typ := range si.DenyTypes {
2022-07-01 16:02:10 +00:00
_, 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
2022-07-01 16:02:10 +00:00
}
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",
})
2022-07-01 16:02:10 +00:00
}
2020-03-23 11:40:02 +00:00
i.lk.Lock()
defer i.lk.Unlock()
log.Infof("New sector storage: %s", si.ID)
if _, ok := i.stores[si.ID]; ok {
for _, u := range si.URLs {
if _, err := url.Parse(u); err != nil {
return xerrors.Errorf("failed to parse url %s: %w", si.URLs, err)
}
}
uloop:
for _, u := range si.URLs {
for _, l := range i.stores[si.ID].info.URLs {
if u == l {
continue uloop
}
}
i.stores[si.ID].info.URLs = append(i.stores[si.ID].info.URLs, u)
}
i.stores[si.ID].info.Weight = si.Weight
i.stores[si.ID].info.MaxStorage = si.MaxStorage
i.stores[si.ID].info.CanSeal = si.CanSeal
i.stores[si.ID].info.CanStore = si.CanStore
2021-10-06 11:54:25 +00:00
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
2020-03-23 11:40:02 +00:00
return nil
}
i.stores[si.ID] = &storageEntry{
info: &si,
fsi: st,
2020-05-08 16:08:48 +00:00
lastHeartbeat: time.Now(),
2020-03-23 11:40:02 +00:00
}
return nil
}
2022-01-18 10:57:04 +00:00
func (i *Index) StorageReportHealth(ctx context.Context, id storiface.ID, report storiface.HealthReport) error {
2020-05-08 16:08:48 +00:00
i.lk.Lock()
defer i.lk.Unlock()
ent, ok := i.stores[id]
if !ok {
return xerrors.Errorf("health report for unknown storage: %s", id)
}
ent.fsi = report.Stat
2020-11-12 09:29:42 +00:00
if report.Err != "" {
ent.heartbeatErr = errors.New(report.Err)
2021-04-11 06:47:04 +00:00
} else {
ent.heartbeatErr = nil
2020-11-12 09:29:42 +00:00
}
2020-05-08 16:08:48 +00:00
ent.lastHeartbeat = time.Now()
if report.Stat.Capacity > 0 {
ctx, _ = tag.New(ctx, tag.Upsert(metrics.StorageID, string(id)))
stats.Record(ctx, metrics.StorageFSAvailable.M(float64(report.Stat.FSAvailable)/float64(report.Stat.Capacity)))
stats.Record(ctx, metrics.StorageAvailable.M(float64(report.Stat.Available)/float64(report.Stat.Capacity)))
stats.Record(ctx, metrics.StorageReserved.M(float64(report.Stat.Reserved)/float64(report.Stat.Capacity)))
stats.Record(ctx, metrics.StorageCapacityBytes.M(report.Stat.Capacity))
stats.Record(ctx, metrics.StorageFSAvailableBytes.M(report.Stat.FSAvailable))
stats.Record(ctx, metrics.StorageAvailableBytes.M(report.Stat.Available))
stats.Record(ctx, metrics.StorageReservedBytes.M(report.Stat.Reserved))
if report.Stat.Max > 0 {
stats.Record(ctx, metrics.StorageLimitUsed.M(float64(report.Stat.Used)/float64(report.Stat.Max)))
stats.Record(ctx, metrics.StorageLimitUsedBytes.M(report.Stat.Used))
stats.Record(ctx, metrics.StorageLimitMaxBytes.M(report.Stat.Max))
}
}
2020-05-08 16:08:48 +00:00
return nil
}
2022-01-18 10:57:04 +00:00
func (i *Index) StorageDeclareSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType, primary bool) error {
2020-03-23 11:40:02 +00:00
i.lk.Lock()
defer i.lk.Unlock()
loop:
2020-09-06 16:54:00 +00:00
for _, fileType := range storiface.PathTypes {
2020-03-23 11:40:02 +00:00
if fileType&ft == 0 {
continue
}
2022-01-18 11:11:59 +00:00
d := storiface.Decl{SectorID: s, SectorFileType: fileType}
2020-03-23 11:40:02 +00:00
for _, sid := range i.sectors[d] {
2020-08-16 10:40:35 +00:00
if sid.storage == storageID {
if !sid.primary && primary {
sid.primary = true
} else {
2020-08-16 10:40:35 +00:00
log.Warnf("sector %v redeclared in %s", s, storageID)
}
continue loop
2020-03-23 11:40:02 +00:00
}
}
i.sectors[d] = append(i.sectors[d], &declMeta{
2020-08-16 10:40:35 +00:00
storage: storageID,
primary: primary,
})
2020-03-23 11:40:02 +00:00
}
return nil
}
2022-01-18 10:57:04 +00:00
func (i *Index) StorageDropSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType) error {
2020-03-23 11:40:02 +00:00
i.lk.Lock()
defer i.lk.Unlock()
2020-09-06 16:54:00 +00:00
for _, fileType := range storiface.PathTypes {
2020-03-23 11:40:02 +00:00
if fileType&ft == 0 {
continue
}
2022-01-18 11:11:59 +00:00
d := storiface.Decl{SectorID: s, SectorFileType: fileType}
2020-03-23 11:40:02 +00:00
if len(i.sectors[d]) == 0 {
2021-02-18 04:00:28 +00:00
continue
2020-03-23 11:40:02 +00:00
}
rewritten := make([]*declMeta, 0, len(i.sectors[d])-1)
2020-03-23 11:40:02 +00:00
for _, sid := range i.sectors[d] {
2020-08-16 10:40:35 +00:00
if sid.storage == storageID {
2020-03-23 11:40:02 +00:00
continue
}
rewritten = append(rewritten, sid)
}
if len(rewritten) == 0 {
delete(i.sectors, d)
2021-02-18 04:00:28 +00:00
continue
2020-03-23 11:40:02 +00:00
}
i.sectors[d] = rewritten
}
return nil
}
2022-01-18 10:57:04 +00:00
func (i *Index) StorageFindSector(ctx context.Context, s abi.SectorID, ft storiface.SectorFileType, ssize abi.SectorSize, allowFetch bool) ([]storiface.SectorStorageInfo, error) {
2020-03-23 11:40:02 +00:00
i.lk.RLock()
defer i.lk.RUnlock()
2022-01-18 10:57:04 +00:00
storageIDs := map[storiface.ID]uint64{}
isprimary := map[storiface.ID]bool{}
2020-03-23 11:40:02 +00:00
2022-01-18 10:57:04 +00:00
allowTo := map[storiface.Group]struct{}{}
2020-09-06 16:54:00 +00:00
for _, pathType := range storiface.PathTypes {
2020-03-23 11:40:02 +00:00
if ft&pathType == 0 {
continue
}
2022-01-18 11:11:59 +00:00
for _, id := range i.sectors[storiface.Decl{SectorID: s, SectorFileType: pathType}] {
storageIDs[id.storage]++
isprimary[id.storage] = isprimary[id.storage] || id.primary
2020-03-23 11:40:02 +00:00
}
}
2022-01-18 10:57:04 +00:00
out := make([]storiface.SectorStorageInfo, 0, len(storageIDs))
2020-03-23 11:40:02 +00:00
for id, n := range storageIDs {
st, ok := i.stores[id]
if !ok {
log.Warnf("storage %s is not present in sector index (referenced by sector %v)", id, s)
continue
}
2022-01-14 13:11:04 +00:00
urls, burls := make([]string, len(st.info.URLs)), make([]string, len(st.info.URLs))
2020-03-23 11:40:02 +00:00
for k, u := range st.info.URLs {
rl, err := url.Parse(u)
if err != nil {
return nil, xerrors.Errorf("failed to parse url: %w", err)
}
2020-09-06 16:54:00 +00:00
rl.Path = gopath.Join(rl.Path, ft.String(), storiface.SectorName(s))
2020-03-23 11:40:02 +00:00
urls[k] = rl.String()
2022-01-14 13:11:04 +00:00
burls[k] = u
2020-03-23 11:40:02 +00:00
}
if allowTo != nil && len(st.info.AllowTo) > 0 {
for _, group := range st.info.AllowTo {
allowTo[group] = struct{}{}
}
} else {
allowTo = nil // allow to any
}
2022-01-18 10:57:04 +00:00
out = append(out, storiface.SectorStorageInfo{
2022-01-14 13:11:04 +00:00
ID: id,
URLs: urls,
BaseURLs: burls,
Weight: st.info.Weight * n, // storage with more sector types is better
2020-03-23 11:40:02 +00:00
CanSeal: st.info.CanSeal,
CanStore: st.info.CanStore,
Primary: isprimary[id],
2022-07-01 16:02:10 +00:00
AllowTypes: st.info.AllowTypes,
DenyTypes: st.info.DenyTypes,
2020-03-23 11:40:02 +00:00
})
}
if allowFetch {
spaceReq, err := ft.SealSpaceUse(ssize)
2020-08-11 07:27:03 +00:00
if err != nil {
return nil, xerrors.Errorf("estimating required space: %w", err)
}
2020-03-23 11:40:02 +00:00
for id, st := range i.stores {
2020-08-11 07:27:03 +00:00
if !st.info.CanSeal {
continue
}
if spaceReq > uint64(st.fsi.Available) {
log.Debugf("not selecting on %s, out of space (available: %d, need: %d)", st.info.ID, st.fsi.Available, spaceReq)
continue
}
if time.Since(st.lastHeartbeat) > SkippedHeartbeatThresh {
log.Debugf("not selecting on %s, didn't receive heartbeats for %s", st.info.ID, time.Since(st.lastHeartbeat))
continue
}
if st.heartbeatErr != nil {
log.Debugf("not selecting on %s, heartbeat error: %s", st.info.ID, st.heartbeatErr)
continue
}
2020-03-23 11:40:02 +00:00
if _, ok := storageIDs[id]; ok {
continue
}
2022-07-01 16:02:10 +00:00
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 {
if _, found := allowTo[group]; found {
log.Debugf("path %s in allowed group %s", st.info.ID, group)
allow = true
break
}
}
if !allow {
log.Debugf("not selecting on %s, not in allowed group, allow %+v; path has %+v", st.info.ID, allowTo, st.info.Groups)
continue
}
}
2022-01-14 13:11:04 +00:00
urls, burls := make([]string, len(st.info.URLs)), make([]string, len(st.info.URLs))
2020-03-23 11:40:02 +00:00
for k, u := range st.info.URLs {
rl, err := url.Parse(u)
if err != nil {
return nil, xerrors.Errorf("failed to parse url: %w", err)
}
2020-09-06 16:54:00 +00:00
rl.Path = gopath.Join(rl.Path, ft.String(), storiface.SectorName(s))
2020-03-23 11:40:02 +00:00
urls[k] = rl.String()
2022-01-14 13:11:04 +00:00
burls[k] = u
2020-03-23 11:40:02 +00:00
}
2022-01-18 10:57:04 +00:00
out = append(out, storiface.SectorStorageInfo{
2022-01-14 13:11:04 +00:00
ID: id,
URLs: urls,
BaseURLs: burls,
Weight: st.info.Weight * 0, // TODO: something better than just '0'
2020-03-23 11:40:02 +00:00
CanSeal: st.info.CanSeal,
CanStore: st.info.CanStore,
Primary: false,
2022-07-01 16:02:10 +00:00
AllowTypes: st.info.AllowTypes,
DenyTypes: st.info.DenyTypes,
2020-03-23 11:40:02 +00:00
})
}
}
return out, nil
}
2022-01-18 10:57:04 +00:00
func (i *Index) StorageInfo(ctx context.Context, id storiface.ID) (storiface.StorageInfo, error) {
2020-03-23 11:40:02 +00:00
i.lk.RLock()
defer i.lk.RUnlock()
si, found := i.stores[id]
if !found {
2022-01-18 10:57:04 +00:00
return storiface.StorageInfo{}, xerrors.Errorf("sector store not found")
2020-03-23 11:40:02 +00:00
}
return *si.info, nil
}
2022-01-18 10:57:04 +00:00
func (i *Index) StorageBestAlloc(ctx context.Context, allocate storiface.SectorFileType, ssize abi.SectorSize, pathType storiface.PathType) ([]storiface.StorageInfo, error) {
2020-03-23 11:40:02 +00:00
i.lk.RLock()
defer i.lk.RUnlock()
var candidates []storageEntry
var err error
2021-05-11 16:14:01 +00:00
var spaceReq uint64
switch pathType {
case storiface.PathSealing:
spaceReq, err = allocate.SealSpaceUse(ssize)
case storiface.PathStorage:
spaceReq, err = allocate.StoreSpaceUse(ssize)
2021-05-11 16:14:01 +00:00
default:
panic(fmt.Sprintf("unexpected pathType: %s", pathType))
}
if err != nil {
return nil, xerrors.Errorf("estimating required space: %w", err)
}
2020-03-23 11:40:02 +00:00
for _, p := range i.stores {
2020-09-06 16:54:00 +00:00
if (pathType == storiface.PathSealing) && !p.info.CanSeal {
2020-03-23 11:40:02 +00:00
continue
}
2020-09-06 16:54:00 +00:00
if (pathType == storiface.PathStorage) && !p.info.CanStore {
2020-03-23 11:40:02 +00:00
continue
}
2020-07-06 16:36:44 +00:00
if spaceReq > uint64(p.fsi.Available) {
log.Debugf("not allocating on %s, out of space (available: %d, need: %d)", p.info.ID, p.fsi.Available, spaceReq)
continue
}
if time.Since(p.lastHeartbeat) > SkippedHeartbeatThresh {
log.Debugf("not allocating on %s, didn't receive heartbeats for %s", p.info.ID, time.Since(p.lastHeartbeat))
continue
}
if p.heartbeatErr != nil {
log.Debugf("not allocating on %s, heartbeat error: %s", p.info.ID, p.heartbeatErr)
continue
}
2020-03-23 11:40:02 +00:00
candidates = append(candidates, *p)
}
if len(candidates) == 0 {
return nil, xerrors.New("no good path found")
}
sort.Slice(candidates, func(i, j int) bool {
2020-08-16 10:40:35 +00:00
iw := big.Mul(big.NewInt(candidates[i].fsi.Available), big.NewInt(int64(candidates[i].info.Weight)))
jw := big.Mul(big.NewInt(candidates[j].fsi.Available), big.NewInt(int64(candidates[j].info.Weight)))
2020-03-23 11:40:02 +00:00
return iw.GreaterThan(jw)
})
2022-01-18 10:57:04 +00:00
out := make([]storiface.StorageInfo, len(candidates))
2020-03-23 11:40:02 +00:00
for i, candidate := range candidates {
out[i] = *candidate.info
}
return out, nil
}
2022-01-18 10:57:04 +00:00
func (i *Index) FindSector(id abi.SectorID, typ storiface.SectorFileType) ([]storiface.ID, error) {
2020-03-23 11:40:02 +00:00
i.lk.RLock()
defer i.lk.RUnlock()
2022-01-18 10:57:04 +00:00
f, ok := i.sectors[storiface.Decl{
2020-03-23 11:40:02 +00:00
SectorID: id,
SectorFileType: typ,
}]
if !ok {
return nil, nil
}
2022-01-18 10:57:04 +00:00
out := make([]storiface.ID, 0, len(f))
for _, meta := range f {
out = append(out, meta.storage)
}
return out, nil
2020-03-23 11:40:02 +00:00
}
var _ SectorIndex = &Index{}