2023-06-30 11:13:18 +01:00
|
|
|
// Copyright (C) 2023 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package overlay
|
|
|
|
|
|
|
|
import (
|
2023-07-06 13:35:26 +01:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/jtolio/mito"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
"github.com/zeebo/errs"
|
2023-07-10 16:01:48 +01:00
|
|
|
"golang.org/x/exp/slices"
|
2023-07-06 13:35:26 +01:00
|
|
|
|
2023-06-30 11:13:18 +01:00
|
|
|
"storj.io/common/storj"
|
|
|
|
"storj.io/common/storj/location"
|
2023-07-07 09:31:58 +01:00
|
|
|
"storj.io/storj/satellite/nodeselection"
|
2023-06-30 11:13:18 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// PlacementRules can crate filter based on the placement identifier.
|
2023-07-07 09:31:58 +01:00
|
|
|
type PlacementRules func(constraint storj.PlacementConstraint) (filter nodeselection.NodeFilters)
|
2023-06-30 11:13:18 +01:00
|
|
|
|
|
|
|
// ConfigurablePlacementRule can include the placement definitions for each known identifier.
|
|
|
|
type ConfigurablePlacementRule struct {
|
2023-07-07 09:31:58 +01:00
|
|
|
placements map[storj.PlacementConstraint]nodeselection.NodeFilters
|
2023-06-30 11:13:18 +01:00
|
|
|
}
|
|
|
|
|
2023-07-06 13:35:26 +01:00
|
|
|
// String implements pflag.Value.
|
|
|
|
func (d *ConfigurablePlacementRule) String() string {
|
|
|
|
parts := []string{}
|
|
|
|
for id, filter := range d.placements {
|
|
|
|
// we can hide the internal rules...
|
|
|
|
if id > 9 {
|
|
|
|
// TODO: we need proper String implementation for all the used filters
|
|
|
|
parts = append(parts, fmt.Sprintf("%d:%s", id, filter))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return strings.Join(parts, ";")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set implements pflag.Value.
|
|
|
|
func (d *ConfigurablePlacementRule) Set(s string) error {
|
|
|
|
if d.placements == nil {
|
|
|
|
d.placements = make(map[storj.PlacementConstraint]nodeselection.NodeFilters)
|
|
|
|
}
|
|
|
|
d.AddLegacyStaticRules()
|
|
|
|
return d.AddPlacementFromString(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type implements pflag.Value.
|
|
|
|
func (d *ConfigurablePlacementRule) Type() string {
|
|
|
|
return "placement-rule"
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ pflag.Value = &ConfigurablePlacementRule{}
|
|
|
|
|
2023-06-30 11:13:18 +01:00
|
|
|
// NewPlacementRules creates a fully initialized NewPlacementRules.
|
|
|
|
func NewPlacementRules() *ConfigurablePlacementRule {
|
|
|
|
return &ConfigurablePlacementRule{
|
2023-07-07 09:31:58 +01:00
|
|
|
placements: map[storj.PlacementConstraint]nodeselection.NodeFilters{},
|
2023-06-30 11:13:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddLegacyStaticRules initializes all the placement rules defined earlier in static golang code.
|
|
|
|
func (d *ConfigurablePlacementRule) AddLegacyStaticRules() {
|
2023-07-07 09:31:58 +01:00
|
|
|
d.placements[storj.EEA] = nodeselection.NodeFilters{}.WithCountryFilter(func(isoCountryCode location.CountryCode) bool {
|
2023-07-10 16:01:48 +01:00
|
|
|
for _, c := range nodeselection.EeaCountries {
|
2023-06-30 11:13:18 +01:00
|
|
|
if c == isoCountryCode {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
})
|
2023-07-07 09:31:58 +01:00
|
|
|
d.placements[storj.EU] = nodeselection.NodeFilters{}.WithCountryFilter(func(isoCountryCode location.CountryCode) bool {
|
2023-07-10 16:01:48 +01:00
|
|
|
for _, c := range nodeselection.EuCountries {
|
2023-06-30 11:13:18 +01:00
|
|
|
if c == isoCountryCode {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
})
|
2023-07-07 09:31:58 +01:00
|
|
|
d.placements[storj.US] = nodeselection.NodeFilters{}.WithCountryFilter(func(isoCountryCode location.CountryCode) bool {
|
2023-06-30 11:13:18 +01:00
|
|
|
return isoCountryCode == location.UnitedStates
|
|
|
|
})
|
2023-07-07 09:31:58 +01:00
|
|
|
d.placements[storj.DE] = nodeselection.NodeFilters{}.WithCountryFilter(func(isoCountryCode location.CountryCode) bool {
|
2023-06-30 11:13:18 +01:00
|
|
|
return isoCountryCode == location.Germany
|
|
|
|
})
|
2023-07-07 09:31:58 +01:00
|
|
|
d.placements[storj.NR] = nodeselection.NodeFilters{}.WithCountryFilter(func(isoCountryCode location.CountryCode) bool {
|
2023-06-30 11:13:18 +01:00
|
|
|
return isoCountryCode != location.Russia && isoCountryCode != location.Belarus
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddPlacementRule registers a new placement.
|
2023-07-07 09:31:58 +01:00
|
|
|
func (d *ConfigurablePlacementRule) AddPlacementRule(id storj.PlacementConstraint, filters nodeselection.NodeFilters) {
|
2023-06-30 11:13:18 +01:00
|
|
|
d.placements[id] = filters
|
|
|
|
}
|
|
|
|
|
2023-07-06 13:35:26 +01:00
|
|
|
// AddPlacementFromString parses placement definition form string representations from id:definition;id:definition;...
|
|
|
|
func (d *ConfigurablePlacementRule) AddPlacementFromString(definitions string) error {
|
|
|
|
env := map[any]any{
|
|
|
|
"country": func(countries ...string) (nodeselection.NodeFilters, error) {
|
|
|
|
countryCodes := make([]location.CountryCode, len(countries))
|
|
|
|
for i, country := range countries {
|
2023-07-10 16:01:48 +01:00
|
|
|
code := location.ToCountryCode(country)
|
|
|
|
if code == location.None {
|
|
|
|
return nil, errs.New("invalid country code %q", code)
|
|
|
|
}
|
|
|
|
countryCodes[i] = code
|
2023-07-06 13:35:26 +01:00
|
|
|
}
|
|
|
|
return nodeselection.NodeFilters{}.WithCountryFilter(func(code location.CountryCode) bool {
|
|
|
|
for _, expectedCode := range countryCodes {
|
|
|
|
if code == expectedCode {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}), nil
|
|
|
|
},
|
|
|
|
"all": func(filters ...nodeselection.NodeFilters) (nodeselection.NodeFilters, error) {
|
|
|
|
res := nodeselection.NodeFilters{}
|
|
|
|
for _, filter := range filters {
|
|
|
|
res = append(res, filter...)
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
},
|
|
|
|
"tag": func(nodeIDstr string, key string, value any) (nodeselection.NodeFilters, error) {
|
|
|
|
nodeID, err := storj.NodeIDFromString(nodeIDstr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var rawValue []byte
|
|
|
|
switch v := value.(type) {
|
|
|
|
case string:
|
|
|
|
rawValue = []byte(v)
|
|
|
|
case []byte:
|
|
|
|
rawValue = v
|
|
|
|
default:
|
|
|
|
return nil, errs.New("3rd argument of tag() should be string or []byte")
|
|
|
|
}
|
|
|
|
res := nodeselection.NodeFilters{
|
|
|
|
nodeselection.NewTagFilter(nodeID, key, rawValue),
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, definition := range strings.Split(definitions, ";") {
|
|
|
|
definition = strings.TrimSpace(definition)
|
|
|
|
if definition == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
idDef := strings.SplitN(definition, ":", 2)
|
|
|
|
|
|
|
|
val, err := mito.Eval(idDef[1], env)
|
|
|
|
if err != nil {
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
|
|
|
id, err := strconv.Atoi(idDef[0])
|
|
|
|
if err != nil {
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
|
|
|
d.placements[storj.PlacementConstraint(id)] = val.(nodeselection.NodeFilters)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-30 11:13:18 +01:00
|
|
|
// CreateFilters implements PlacementCondition.
|
2023-07-07 09:31:58 +01:00
|
|
|
func (d *ConfigurablePlacementRule) CreateFilters(constraint storj.PlacementConstraint) (filter nodeselection.NodeFilters) {
|
2023-06-30 11:13:18 +01:00
|
|
|
if constraint == 0 {
|
2023-07-07 09:31:58 +01:00
|
|
|
return nodeselection.NodeFilters{}
|
2023-06-30 11:13:18 +01:00
|
|
|
}
|
|
|
|
if filters, found := d.placements[constraint]; found {
|
2023-07-10 16:01:48 +01:00
|
|
|
return slices.Clone(filters)
|
|
|
|
}
|
|
|
|
return nodeselection.NodeFilters{
|
|
|
|
nodeselection.ExcludeAllFilter{},
|
2023-06-30 11:13:18 +01:00
|
|
|
}
|
|
|
|
}
|