2021-01-28 14:33:53 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Incache.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package overlay
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"storj.io/common/storj"
|
2022-06-28 12:53:39 +01:00
|
|
|
"storj.io/common/sync2"
|
2021-01-28 14:33:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// DownloadSelectionDB implements the database for download selection cache.
|
|
|
|
//
|
|
|
|
// architecture: Database
|
|
|
|
type DownloadSelectionDB interface {
|
|
|
|
// SelectAllStorageNodesDownload returns nodes that are ready for downloading
|
|
|
|
SelectAllStorageNodesDownload(ctx context.Context, onlineWindow time.Duration, asOf AsOfSystemTimeConfig) ([]*SelectedNode, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DownloadSelectionCacheConfig contains configuration for the selection cache.
|
|
|
|
type DownloadSelectionCacheConfig struct {
|
|
|
|
Staleness time.Duration
|
|
|
|
OnlineWindow time.Duration
|
|
|
|
AsOfSystemTime AsOfSystemTimeConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
// DownloadSelectionCache keeps a list of all the storage nodes that are qualified to download data from.
|
|
|
|
// The cache will sync with the nodes table in the database and get refreshed once the staleness time has past.
|
|
|
|
type DownloadSelectionCache struct {
|
|
|
|
log *zap.Logger
|
|
|
|
db DownloadSelectionDB
|
|
|
|
config DownloadSelectionCacheConfig
|
|
|
|
|
2023-04-20 14:08:26 +01:00
|
|
|
cache sync2.ReadCacheOf[*DownloadSelectionCacheState]
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewDownloadSelectionCache creates a new cache that keeps a list of all the storage nodes that are qualified to download data from.
|
2022-06-28 12:53:39 +01:00
|
|
|
func NewDownloadSelectionCache(log *zap.Logger, db DownloadSelectionDB, config DownloadSelectionCacheConfig) (*DownloadSelectionCache, error) {
|
|
|
|
cache := &DownloadSelectionCache{
|
2021-01-28 14:33:53 +00:00
|
|
|
log: log,
|
|
|
|
db: db,
|
|
|
|
config: config,
|
|
|
|
}
|
2022-06-28 12:53:39 +01:00
|
|
|
return cache, cache.cache.Init(config.Staleness/2, config.Staleness, cache.read)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run runs the background task for cache.
|
|
|
|
func (cache *DownloadSelectionCache) Run(ctx context.Context) (err error) {
|
|
|
|
return cache.cache.Run(ctx)
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh populates the cache with all of the reputableNodes and newNode nodes
|
|
|
|
// This method is useful for tests.
|
|
|
|
func (cache *DownloadSelectionCache) Refresh(ctx context.Context) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2022-06-28 12:53:39 +01:00
|
|
|
_, err = cache.cache.RefreshAndGet(ctx, time.Now())
|
2021-01-28 14:33:53 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-06-28 12:53:39 +01:00
|
|
|
// read loads the latest download selection state.
|
2023-04-20 14:08:26 +01:00
|
|
|
func (cache *DownloadSelectionCache) read(ctx context.Context) (_ *DownloadSelectionCacheState, err error) {
|
2021-01-28 14:33:53 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
onlineNodes, err := cache.db.SelectAllStorageNodesDownload(ctx, cache.config.OnlineWindow, cache.config.AsOfSystemTime)
|
|
|
|
if err != nil {
|
2022-06-28 12:53:39 +01:00
|
|
|
return nil, Error.Wrap(err)
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mon.IntVal("refresh_cache_size_online").Observe(int64(len(onlineNodes)))
|
2022-06-28 12:53:39 +01:00
|
|
|
|
|
|
|
return NewDownloadSelectionCacheState(onlineNodes), nil
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
2023-06-07 12:58:25 +01:00
|
|
|
// GetNodeIPsFromPlacement gets the last node ip:port from the cache, refreshing when needed. Results are filtered out by placement.
|
|
|
|
func (cache *DownloadSelectionCache) GetNodeIPsFromPlacement(ctx context.Context, nodes []storj.NodeID, placement storj.PlacementConstraint) (_ map[storj.NodeID]string, err error) {
|
2021-01-28 14:33:53 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2023-04-20 14:08:26 +01:00
|
|
|
state, err := cache.cache.Get(ctx, time.Now())
|
2022-06-28 12:53:39 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
2023-06-07 12:58:25 +01:00
|
|
|
return state.IPsFromPlacement(nodes, placement), nil
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
2022-06-22 21:26:53 +01:00
|
|
|
// GetNodes gets nodes by ID from the cache, and refreshes the cache if it is stale.
|
|
|
|
func (cache *DownloadSelectionCache) GetNodes(ctx context.Context, nodes []storj.NodeID) (_ map[storj.NodeID]*SelectedNode, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2023-04-20 14:08:26 +01:00
|
|
|
state, err := cache.cache.Get(ctx, time.Now())
|
2022-06-22 21:26:53 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
return state.Nodes(nodes), nil
|
|
|
|
}
|
|
|
|
|
2021-01-28 14:33:53 +00:00
|
|
|
// Size returns how many nodes are in the cache.
|
2022-06-28 12:53:39 +01:00
|
|
|
func (cache *DownloadSelectionCache) Size(ctx context.Context) (int, error) {
|
2023-04-20 14:08:26 +01:00
|
|
|
state, err := cache.cache.Get(ctx, time.Now())
|
|
|
|
if state == nil || err != nil {
|
2022-06-28 12:53:39 +01:00
|
|
|
return 0, Error.Wrap(err)
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
2022-06-28 12:53:39 +01:00
|
|
|
return state.Size(), nil
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DownloadSelectionCacheState contains state of download selection cache.
|
|
|
|
type DownloadSelectionCacheState struct {
|
2022-06-22 21:26:53 +01:00
|
|
|
// byID returns IP based on storj.NodeID
|
|
|
|
byID map[storj.NodeID]*SelectedNode // TODO: optimize, avoid pointery structures for performance
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewDownloadSelectionCacheState creates a new state from the nodes.
|
|
|
|
func NewDownloadSelectionCacheState(nodes []*SelectedNode) *DownloadSelectionCacheState {
|
2022-06-22 21:26:53 +01:00
|
|
|
byID := map[storj.NodeID]*SelectedNode{}
|
2021-01-28 14:33:53 +00:00
|
|
|
for _, n := range nodes {
|
2022-06-22 21:26:53 +01:00
|
|
|
byID[n.ID] = n
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
return &DownloadSelectionCacheState{
|
2022-06-22 21:26:53 +01:00
|
|
|
byID: byID,
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size returns how many nodes are in the state.
|
|
|
|
func (state *DownloadSelectionCacheState) Size() int {
|
2022-06-22 21:26:53 +01:00
|
|
|
return len(state.byID)
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IPs returns node ip:port for nodes that are in state.
|
|
|
|
func (state *DownloadSelectionCacheState) IPs(nodes []storj.NodeID) map[storj.NodeID]string {
|
|
|
|
xs := make(map[storj.NodeID]string, len(nodes))
|
|
|
|
for _, nodeID := range nodes {
|
2022-06-22 21:26:53 +01:00
|
|
|
if n, exists := state.byID[nodeID]; exists {
|
|
|
|
xs[nodeID] = n.LastIPPort
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return xs
|
|
|
|
}
|
|
|
|
|
2023-06-07 12:58:25 +01:00
|
|
|
// IPsFromPlacement returns node ip:port for nodes that are in state. Results are filtered out by placement.
|
|
|
|
func (state *DownloadSelectionCacheState) IPsFromPlacement(nodes []storj.NodeID, placement storj.PlacementConstraint) map[storj.NodeID]string {
|
|
|
|
xs := make(map[storj.NodeID]string, len(nodes))
|
|
|
|
for _, nodeID := range nodes {
|
|
|
|
if n, exists := state.byID[nodeID]; exists && placement.AllowedCountry(n.CountryCode) {
|
|
|
|
xs[nodeID] = n.LastIPPort
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return xs
|
|
|
|
}
|
|
|
|
|
2022-06-22 21:26:53 +01:00
|
|
|
// Nodes returns node ip:port for nodes that are in state.
|
|
|
|
func (state *DownloadSelectionCacheState) Nodes(nodes []storj.NodeID) map[storj.NodeID]*SelectedNode {
|
|
|
|
xs := make(map[storj.NodeID]*SelectedNode, len(nodes))
|
|
|
|
for _, nodeID := range nodes {
|
|
|
|
if n, exists := state.byID[nodeID]; exists {
|
|
|
|
xs[nodeID] = n.Clone() // TODO: optimize the clones
|
2021-01-28 14:33:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return xs
|
|
|
|
}
|