storj/satellite/nodeselection/state.go

159 lines
4.4 KiB
Go
Raw Normal View History

// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package nodeselection
import (
"context"
"sync"
"github.com/zeebo/errs"
"storj.io/common/storj"
)
const (
// AutoExcludeSubnet is placement annotation key to turn off subnet restrictions.
AutoExcludeSubnet = "autoExcludeSubnet"
// AutoExcludeSubnetOFF is the value of AutoExcludeSubnet to disable subnet restrictions.
AutoExcludeSubnetOFF = "off"
)
// AllowSameSubnet is a short to check if Subnet exclusion is disabled == allow pick nodes from the same subnet.
func AllowSameSubnet(filter NodeFilter) bool {
return GetAnnotation(filter, AutoExcludeSubnet) == AutoExcludeSubnetOFF
}
// ErrNotEnoughNodes is when selecting nodes failed with the given parameters.
var ErrNotEnoughNodes = errs.Class("not enough nodes")
// State defines a node selector state that allows for selection.
type State struct {
mu sync.RWMutex
// netByID returns subnet based on storj.NodeID
netByID map[storj.NodeID]string
// byNetwork contains selectors for distinct selection.
byNetwork struct {
Reputable SelectBySubnet
New SelectBySubnet
}
byID struct {
Reputable SelectByID
New SelectByID
}
}
// Selector defines interface for selecting nodes.
type Selector interface {
// Select selects up-to n nodes which are included by the criteria.
// empty criteria includes all the nodes
Select(n int, nodeFilter NodeFilter) []*SelectedNode
}
// NewState returns a state based on the input.
func NewState(reputableNodes, newNodes []*SelectedNode) *State {
state := &State{}
state.netByID = map[storj.NodeID]string{}
for _, node := range reputableNodes {
state.netByID[node.ID] = node.LastNet
}
for _, node := range newNodes {
state.netByID[node.ID] = node.LastNet
}
state.byNetwork.Reputable = SelectBySubnetFromNodes(reputableNodes)
state.byNetwork.New = SelectBySubnetFromNodes(newNodes)
state.byID.Reputable = SelectByID(reputableNodes)
state.byID.New = SelectByID(newNodes)
return state
}
// SelectionType defines how to select nodes randomly.
type SelectionType int8
const (
// SelectionTypeByNetwork chooses subnets randomly, and one node from each subnet.
SelectionTypeByNetwork = iota
// SelectionTypeByID chooses nodes randomly.
SelectionTypeByID
)
// Request contains arguments for State.Request.
type Request struct {
Count int
NewFraction float64
NodeFilters NodeFilters
SelectionType SelectionType
}
// Select selects requestedCount nodes where there will be newFraction nodes.
func (state *State) Select(ctx context.Context, request Request) (_ []*SelectedNode, err error) {
defer mon.Task()(&ctx)(&err)
state.mu.RLock()
defer state.mu.RUnlock()
totalCount := request.Count
newCount := int(float64(totalCount) * request.NewFraction)
var selected []*SelectedNode
var reputableNodes Selector
var newNodes Selector
switch request.SelectionType {
case SelectionTypeByNetwork:
reputableNodes = state.byNetwork.Reputable
newNodes = state.byNetwork.New
case SelectionTypeByID:
reputableNodes = state.byID.Reputable
newNodes = state.byID.New
default:
return nil, errs.New("Unsupported selection type: %d", request.SelectionType)
}
// Get a random selection of new nodes out of the cache first so that if there aren't
// enough new nodes on the network, we can fall back to using reputable nodes instead.
selected = append(selected,
newNodes.Select(newCount, request.NodeFilters)...)
// Get all the remaining reputable nodes.
reputableCount := totalCount - len(selected)
filters := request.NodeFilters
if GetAnnotation(filters, AutoExcludeSubnet) != AutoExcludeSubnetOFF {
filters = append(append(NodeFilters{}, request.NodeFilters...), ExcludedNodeNetworks(selected))
}
selected = append(selected, reputableNodes.Select(reputableCount, filters)...)
if len(selected) < totalCount {
return selected, ErrNotEnoughNodes.New("requested from cache %d, found %d", totalCount, len(selected))
}
return selected, nil
}
// ExcludeNetworksBasedOnNodes will create a NodeFilter which exclude all nodes which shares subnet with the specified ones.
func (state *State) ExcludeNetworksBasedOnNodes(ds []storj.NodeID) NodeFilter {
uniqueExcludedNet := make(map[string]struct{}, len(ds))
for _, id := range ds {
net := state.netByID[id]
uniqueExcludedNet[net] = struct{}{}
}
excludedNet := make([]string, len(uniqueExcludedNet))
i := 0
for net := range uniqueExcludedNet {
excludedNet[i] = net
i++
}
return ExcludedNetworks(excludedNet)
}