diff --git a/cmd/satellite/repair_segment.go b/cmd/satellite/repair_segment.go index 6e31a96d3..a7b21dc1e 100644 --- a/cmd/satellite/repair_segment.go +++ b/cmd/satellite/repair_segment.go @@ -94,7 +94,12 @@ func cmdRepairSegment(cmd *cobra.Command, args []string) (err error) { dialer := rpc.NewDefaultDialer(tlsOptions) - overlayService, err := overlay.NewService(log.Named("overlay"), db.OverlayCache(), db.NodeEvents(), config.Placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) + placement, err := config.Placement.Parse() + if err != nil { + return err + } + + overlayService, err := overlay.NewService(log.Named("overlay"), db.OverlayCache(), db.NodeEvents(), placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) if err != nil { return err } @@ -104,7 +109,7 @@ func cmdRepairSegment(cmd *cobra.Command, args []string) (err error) { signing.SignerFromFullIdentity(identity), overlayService, orders.NewNoopDB(), - config.Placement.CreateFilters, + placement.CreateFilters, config.Orders, ) if err != nil { @@ -126,7 +131,7 @@ func cmdRepairSegment(cmd *cobra.Command, args []string) (err error) { overlayService, nil, // TODO add noop version ecRepairer, - config.Placement.CreateFilters, + placement.CreateFilters, config.Checker.RepairOverrides, config.Repairer, ) diff --git a/cmd/tools/segment-verify/main.go b/cmd/tools/segment-verify/main.go index 7f90e08a7..b713c6ac5 100644 --- a/cmd/tools/segment-verify/main.go +++ b/cmd/tools/segment-verify/main.go @@ -203,12 +203,12 @@ func verifySegments(cmd *cobra.Command, args []string) error { dialer := rpc.NewDefaultDialer(tlsOptions) // setup dependencies for verification - overlayService, err := overlay.NewService(log.Named("overlay"), db.OverlayCache(), db.NodeEvents(), overlay.NewPlacementRules().CreateFilters, "", "", satelliteCfg.Overlay) + overlayService, err := overlay.NewService(log.Named("overlay"), db.OverlayCache(), db.NodeEvents(), overlay.NewPlacementDefinitions().CreateFilters, "", "", satelliteCfg.Overlay) if err != nil { return Error.Wrap(err) } - ordersService, err := orders.NewService(log.Named("orders"), signing.SignerFromFullIdentity(identity), overlayService, orders.NewNoopDB(), overlay.NewPlacementRules().CreateFilters, satelliteCfg.Orders) + ordersService, err := orders.NewService(log.Named("orders"), signing.SignerFromFullIdentity(identity), overlayService, orders.NewNoopDB(), overlay.NewPlacementDefinitions().CreateFilters, satelliteCfg.Orders) if err != nil { return Error.Wrap(err) } diff --git a/satellite/api.go b/satellite/api.go index 50cb7c8e8..c2f70b4de 100644 --- a/satellite/api.go +++ b/satellite/api.go @@ -281,10 +281,15 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, }) } + placements, err := config.Placement.Parse() + if err != nil { + return nil, err + } + { // setup overlay peer.Overlay.DB = peer.DB.OverlayCache() - peer.Overlay.Service, err = overlay.NewService(peer.Log.Named("overlay"), peer.Overlay.DB, peer.DB.NodeEvents(), config.Placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) + peer.Overlay.Service, err = overlay.NewService(peer.Log.Named("overlay"), peer.Overlay.DB, peer.DB.NodeEvents(), placements.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) if err != nil { return nil, errs.Combine(err, peer.Close()) } @@ -374,6 +379,11 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, peer.OIDC.Service = oidc.NewService(db.OIDC()) } + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + { // setup orders peer.Orders.DB = rollupsWriteCache peer.Orders.Chore = orders.NewChore(log.Named("orders:chore"), rollupsWriteCache, config.Orders) @@ -390,7 +400,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, signing.SignerFromFullIdentity(peer.Identity), peer.Overlay.Service, peer.Orders.DB, - config.Placement.CreateFilters, + placement.CreateFilters, config.Orders, ) if err != nil { diff --git a/satellite/auditor.go b/satellite/auditor.go index 453c6541b..3518dbd70 100644 --- a/satellite/auditor.go +++ b/satellite/auditor.go @@ -139,9 +139,14 @@ func NewAuditor(log *zap.Logger, full *identity.FullIdentity, peer.Dialer = rpc.NewDefaultDialer(tlsOptions) } + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + { // setup overlay var err error - peer.Overlay, err = overlay.NewService(log.Named("overlay"), overlayCache, nodeEvents, config.Placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) + peer.Overlay, err = overlay.NewService(log.Named("overlay"), overlayCache, nodeEvents, placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) if err != nil { return nil, errs.Combine(err, peer.Close()) } @@ -174,7 +179,12 @@ func NewAuditor(log *zap.Logger, full *identity.FullIdentity, } { // setup orders - var err error + + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + peer.Orders.Service, err = orders.NewService( log.Named("orders"), signing.SignerFromFullIdentity(peer.Identity), @@ -183,7 +193,7 @@ func NewAuditor(log *zap.Logger, full *identity.FullIdentity, // PUT and GET actions which are not used by // auditor so we can set noop implementation. orders.NewNoopDB(), - config.Placement.CreateFilters, + placement.CreateFilters, config.Orders, ) if err != nil { diff --git a/satellite/core.go b/satellite/core.go index 59287d765..96fd07960 100644 --- a/satellite/core.go +++ b/satellite/core.go @@ -248,8 +248,14 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, } { // setup overlay + + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + peer.Overlay.DB = peer.DB.OverlayCache() - peer.Overlay.Service, err = overlay.NewService(peer.Log.Named("overlay"), peer.Overlay.DB, peer.DB.NodeEvents(), config.Placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) + peer.Overlay.Service, err = overlay.NewService(peer.Log.Named("overlay"), peer.Overlay.DB, peer.DB.NodeEvents(), placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) if err != nil { return nil, errs.Combine(err, peer.Close()) } diff --git a/satellite/metabase/rangedloop/service_test.go b/satellite/metabase/rangedloop/service_test.go index f64463b72..0ba59f5a8 100644 --- a/satellite/metabase/rangedloop/service_test.go +++ b/satellite/metabase/rangedloop/service_test.go @@ -426,7 +426,7 @@ func TestAllInOne(t *testing.T) { log.Named("repair:checker"), satellite.DB.RepairQueue(), satellite.Overlay.Service, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, satellite.Config.Checker, ), }) diff --git a/satellite/metainfo/endpoint_bucket_test.go b/satellite/metainfo/endpoint_bucket_test.go index f7da593b2..e0a22d0f5 100644 --- a/satellite/metainfo/endpoint_bucket_test.go +++ b/satellite/metainfo/endpoint_bucket_test.go @@ -303,16 +303,14 @@ func TestBucketCreationWithDefaultPlacement(t *testing.T) { } func TestGetBucketLocation(t *testing.T) { - placementRules := overlay.NewPlacementRules() - err := placementRules.AddPlacementFromString(fmt.Sprintf(`40:annotated(annotated(country("PL"),annotation("%s","Poland")),annotation("%s","%s"))`, - nodeselection.Location, nodeselection.AutoExcludeSubnet, nodeselection.AutoExcludeSubnetOFF)) - require.NoError(t, err) - testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1, Reconfigure: testplanet.Reconfigure{ Satellite: func(log *zap.Logger, index int, config *satellite.Config) { - config.Placement = *placementRules + config.Placement = overlay.ConfigurablePlacementRule{ + PlacementRules: fmt.Sprintf(`40:annotated(annotated(country("PL"),annotation("%s","Poland")),annotation("%s","%s"))`, + nodeselection.Location, nodeselection.AutoExcludeSubnet, nodeselection.AutoExcludeSubnetOFF), + } }, }, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { diff --git a/satellite/nodeselection/filter.go b/satellite/nodeselection/filter.go index d0f69fb40..7a5706da5 100644 --- a/satellite/nodeselection/filter.go +++ b/satellite/nodeselection/filter.go @@ -4,6 +4,12 @@ package nodeselection import ( + "fmt" + "sort" + "strings" + + "github.com/zeebo/errs" + "storj.io/common/storj" "storj.io/common/storj/location" ) @@ -38,6 +44,10 @@ func (a Annotation) GetAnnotation(name string) string { return "" } +func (a Annotation) String() string { + return fmt.Sprintf(`annotation("%s","%s")`, a.Key, a.Value) +} + var _ NodeFilterWithAnnotation = Annotation{} // AnnotatedNodeFilter is just a NodeFilter with additional annotations. @@ -64,6 +74,14 @@ func (a AnnotatedNodeFilter) Match(node *SelectedNode) bool { return a.Filter.Match(node) } +func (a AnnotatedNodeFilter) String() string { + var annotationStr []string + for _, annotation := range a.Annotations { + annotationStr = append(annotationStr, annotation.String()) + } + return fmt.Sprintf("%s && %s", a.Filter, strings.Join(annotationStr, " && ")) +} + // WithAnnotation adds annotations to a NodeFilter. func WithAnnotation(filter NodeFilter, name string, value string) NodeFilterWithAnnotation { return AnnotatedNodeFilter{ @@ -124,6 +142,15 @@ func (n NodeFilters) WithExcludedIDs(ds []storj.NodeID) NodeFilters { return append(n, ExcludedIDs(ds)) } +func (n NodeFilters) String() string { + var res []string + for _, filter := range n { + res = append(res, fmt.Sprintf("%s", filter)) + } + sort.Strings(res) + return strings.Join(res, " && ") +} + // GetAnnotation implements NodeFilterWithAnnotation. func (n NodeFilters) GetAnnotation(name string) string { for _, filter := range n { @@ -145,17 +172,68 @@ type CountryFilter struct { } // NewCountryFilter creates a new CountryFilter. -func NewCountryFilter(permit location.Set) NodeFilter { +func NewCountryFilter(permit location.Set) *CountryFilter { return &CountryFilter{ permit: permit, } } +// NewCountryFilterFromString parses country definitions like 'hu','!hu','*','none' and creates a CountryFilter. +func NewCountryFilterFromString(countries []string) (*CountryFilter, error) { + var set location.Set + for _, country := range countries { + apply := func(modified location.Set, code ...location.CountryCode) location.Set { + return modified.With(code...) + } + if country[0] == '!' { + apply = func(modified location.Set, code ...location.CountryCode) location.Set { + return modified.Without(code...) + } + country = country[1:] + } + switch strings.ToLower(country) { + case "all", "*", "any": + set = location.NewFullSet() + case "none": + set = apply(set, location.None) + case "eu": + set = apply(set, EuCountries...) + case "eea": + set = apply(set, EuCountries...) + set = apply(set, EeaCountriesWithoutEu...) + default: + code := location.ToCountryCode(country) + if code == location.None { + return nil, errs.New("invalid country code %q", code) + } + set = apply(set, code) + } + } + return NewCountryFilter(set), nil +} + // Match implements NodeFilter interface. func (p *CountryFilter) Match(node *SelectedNode) bool { return p.permit.Contains(node.CountryCode) } +func (p *CountryFilter) String() string { + var included, excluded []string + for country, iso := range location.CountryISOCode { + if p.permit.Contains(location.CountryCode(country)) { + included = append(included, iso) + } else { + excluded = append(excluded, "!"+iso) + } + } + if len(excluded) < len(included) { + sort.Strings(excluded) + return fmt.Sprintf(`country("*","%s")`, strings.Join(excluded, `","`)) + } + sort.Strings(included) + return fmt.Sprintf(`country("%s")`, strings.Join(included, `","`)) +} + var _ NodeFilter = &CountryFilter{} // ExcludedNetworks will exclude nodes with specified networks. @@ -234,6 +312,10 @@ func (t TagFilter) Match(node *SelectedNode) bool { return false } +func (t TagFilter) String() string { + return fmt.Sprintf(`tag("%s","%s","%s")`, t.signer, t.name, string(t.value)) +} + var _ NodeFilter = TagFilter{} // ExcludeFilter excludes only the matched nodes. @@ -246,6 +328,10 @@ func (e ExcludeFilter) Match(node *SelectedNode) bool { return !e.matchToExclude.Match(node) } +func (e ExcludeFilter) String() string { + return fmt.Sprintf("exclude(%s)", e.matchToExclude) +} + // NewExcludeFilter creates filter, nodes matching the given filter will be excluded. func NewExcludeFilter(filter NodeFilter) ExcludeFilter { return ExcludeFilter{ diff --git a/satellite/nodeselection/filter_test.go b/satellite/nodeselection/filter_test.go index f06a47a13..36dc24214 100644 --- a/satellite/nodeselection/filter_test.go +++ b/satellite/nodeselection/filter_test.go @@ -5,6 +5,7 @@ package nodeselection import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -67,6 +68,12 @@ func TestAnnotations(t *testing.T) { Value: "bar", } require.Equal(t, "bar", k.GetAnnotation("foo")) + + // annotation can be used as pure filters + l := Annotation{Key: "foo", Value: "bar"} + require.True(t, l.Match(&SelectedNode{})) + + require.Equal(t, `annotation("foo","bar")`, l.String()) } func TestCriteria_Geofencing(t *testing.T) { @@ -120,6 +127,69 @@ func TestCriteria_Geofencing(t *testing.T) { } } +func TestCountryFilter_FromString(t *testing.T) { + cases := []struct { + definition []string + canonical string + mustIncluded []location.CountryCode + mustNotIncluded []location.CountryCode + }{ + { + definition: []string{"HU"}, + canonical: `country("HU")`, + mustIncluded: []location.CountryCode{location.Hungary}, + mustNotIncluded: []location.CountryCode{location.Germany, location.UnitedStates}, + }, + { + definition: []string{"EU"}, + canonical: "country(\"AT\",\"BE\",\"BG\",\"CY\",\"CZ\",\"DE\",\"DK\",\"EE\",\"ES\",\"FI\",\"FR\",\"GR\",\"HR\",\"HU\",\"IE\",\"IT\",\"LT\",\"LU\",\"LV\",\"MT\",\"NL\",\"PL\",\"PT\",\"RO\",\"SE\",\"SI\",\"SK\")", + mustIncluded: []location.CountryCode{location.Hungary, location.Germany, location.Austria}, + mustNotIncluded: []location.CountryCode{location.Iceland, location.UnitedStates}, + }, + { + definition: []string{"EEA"}, + canonical: "country(\"AT\",\"BE\",\"BG\",\"CY\",\"CZ\",\"DE\",\"DK\",\"EE\",\"ES\",\"FI\",\"FR\",\"GR\",\"HR\",\"HU\",\"IE\",\"IS\",\"IT\",\"LI\",\"LT\",\"LU\",\"LV\",\"MT\",\"NL\",\"NO\",\"PL\",\"PT\",\"RO\",\"SE\",\"SI\",\"SK\")", + mustIncluded: []location.CountryCode{location.Hungary, location.Germany, location.Austria, location.Iceland}, + mustNotIncluded: []location.CountryCode{location.UnitedStates}, + }, + { + definition: []string{"EU", "US"}, + canonical: "country(\"AT\",\"BE\",\"BG\",\"CY\",\"CZ\",\"DE\",\"DK\",\"EE\",\"ES\",\"FI\",\"FR\",\"GR\",\"HR\",\"HU\",\"IE\",\"IT\",\"LT\",\"LU\",\"LV\",\"MT\",\"NL\",\"PL\",\"PT\",\"RO\",\"SE\",\"SI\",\"SK\",\"US\")", + mustIncluded: []location.CountryCode{location.Hungary, location.Germany, location.UnitedStates}, + mustNotIncluded: []location.CountryCode{location.Russia}, + }, + { + definition: []string{"NONE"}, + canonical: "country(\"\")", + mustIncluded: []location.CountryCode{}, + mustNotIncluded: []location.CountryCode{location.Germany, location.UnitedStates, location.Hungary}, + }, + { + definition: []string{"*", "!RU", "!BY"}, + canonical: "country(\"*\",\"!BY\",\"!RU\")", + mustIncluded: []location.CountryCode{location.Hungary}, + mustNotIncluded: []location.CountryCode{location.Russia, location.Belarus}, + }, + } + for _, tc := range cases { + t.Run(strings.Join(tc.definition, "_"), func(t *testing.T) { + filter, err := NewCountryFilterFromString(tc.definition) + require.NoError(t, err) + for _, c := range tc.mustIncluded { + require.True(t, filter.Match(&SelectedNode{ + CountryCode: c, + }), "Country %s should be included", c.String()) + } + for _, c := range tc.mustNotIncluded { + require.False(t, filter.Match(&SelectedNode{ + CountryCode: c, + }), "Country %s shouldn't be included", c.String()) + } + require.Equal(t, tc.canonical, filter.String()) + }) + } +} + // BenchmarkNodeFilterFullTable checks performances of rule evaluation on ALL storage nodes. func BenchmarkNodeFilterFullTable(b *testing.B) { filters := NodeFilters{} diff --git a/satellite/orders/service_test.go b/satellite/orders/service_test.go index 7248be9fc..f7f1a6d2a 100644 --- a/satellite/orders/service_test.go +++ b/satellite/orders/service_test.go @@ -57,7 +57,7 @@ func TestGetOrderLimits(t *testing.T) { Return(nodes, nil).AnyTimes() service, err := orders.NewService(zaptest.NewLogger(t), k, overlayService, orders.NewNoopDB(), - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, orders.Config{ EncryptionKeys: orders.EncryptionKeys{ Default: orders.EncryptionKey{ diff --git a/satellite/overlay/benchmark_test.go b/satellite/overlay/benchmark_test.go index 54797268b..51b5415f3 100644 --- a/satellite/overlay/benchmark_test.go +++ b/satellite/overlay/benchmark_test.go @@ -362,7 +362,7 @@ func BenchmarkNodeSelection(b *testing.B) { } }) - service, err := overlay.NewService(zap.NewNop(), overlaydb, db.NodeEvents(), overlay.NewPlacementRules().CreateFilters, "", "", overlay.Config{ + service, err := overlay.NewService(zap.NewNop(), overlaydb, db.NodeEvents(), overlay.NewPlacementDefinitions().CreateFilters, "", "", overlay.Config{ Node: nodeSelectionConfig, NodeSelectionCache: overlay.UploadSelectionCacheConfig{ Staleness: time.Hour, diff --git a/satellite/overlay/downloadselection_test.go b/satellite/overlay/downloadselection_test.go index de4f1c024..afe7e1416 100644 --- a/satellite/overlay/downloadselection_test.go +++ b/satellite/overlay/downloadselection_test.go @@ -31,7 +31,7 @@ func TestDownloadSelectionCacheState_Refresh(t *testing.T) { satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) { cache, err := overlay.NewDownloadSelectionCache(zap.NewNop(), db.OverlayCache(), - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, downloadSelectionCacheConfig, ) require.NoError(t, err) @@ -64,7 +64,7 @@ func TestDownloadSelectionCacheState_GetNodeIPs(t *testing.T) { satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) { cache, err := overlay.NewDownloadSelectionCache(zap.NewNop(), db.OverlayCache(), - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, downloadSelectionCacheConfig, ) require.NoError(t, err) @@ -116,7 +116,7 @@ func TestDownloadSelectionCache_GetNodes(t *testing.T) { // create new cache and select nodes cache, err := overlay.NewDownloadSelectionCache(zap.NewNop(), db.OverlayCache(), - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, overlay.DownloadSelectionCacheConfig{ Staleness: time.Hour, OnlineWindow: time.Hour, diff --git a/satellite/overlay/placement.go b/satellite/overlay/placement.go index cede4b315..72b79201b 100644 --- a/satellite/overlay/placement.go +++ b/satellite/overlay/placement.go @@ -5,7 +5,6 @@ package overlay import ( "bytes" - "fmt" "strconv" "strings" @@ -21,52 +20,52 @@ import ( // PlacementRules can crate filter based on the placement identifier. type PlacementRules func(constraint storj.PlacementConstraint) (filter nodeselection.NodeFilter) -// ConfigurablePlacementRule can include the placement definitions for each known identifier. -type ConfigurablePlacementRule struct { +// PlacementDefinitions can include the placement definitions for each known identifier. +type PlacementDefinitions struct { placements map[storj.PlacementConstraint]nodeselection.NodeFilter } +// ConfigurablePlacementRule is a string configuration includes all placement rules in the form of id1:def1,id2:def2... +type ConfigurablePlacementRule struct { + PlacementRules string +} + // 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, ";") +func (c *ConfigurablePlacementRule) String() string { + return c.PlacementRules } // Set implements pflag.Value. -func (d *ConfigurablePlacementRule) Set(s string) error { - if d.placements == nil { - d.placements = map[storj.PlacementConstraint]nodeselection.NodeFilter{ - storj.EveryCountry: nodeselection.AnyFilter{}, - } - } - d.AddLegacyStaticRules() - return d.AddPlacementFromString(s) +func (c *ConfigurablePlacementRule) Set(s string) error { + c.PlacementRules = s + return nil } // Type implements pflag.Value. -func (d *ConfigurablePlacementRule) Type() string { - return "placement-rule" +func (c *ConfigurablePlacementRule) Type() string { + return "configurable-placement-rule" +} + +// Parse creates the PlacementDefinitions from the string rules. +func (c ConfigurablePlacementRule) Parse() (*PlacementDefinitions, error) { + d := NewPlacementDefinitions() + d.AddLegacyStaticRules() + err := d.AddPlacementFromString(c.PlacementRules) + return d, err } var _ pflag.Value = &ConfigurablePlacementRule{} -// NewPlacementRules creates a fully initialized NewPlacementRules. -func NewPlacementRules() *ConfigurablePlacementRule { - return &ConfigurablePlacementRule{ +// NewPlacementDefinitions creates a fully initialized NewPlacementDefinitions. +func NewPlacementDefinitions() *PlacementDefinitions { + return &PlacementDefinitions{ placements: map[storj.PlacementConstraint]nodeselection.NodeFilter{ storj.EveryCountry: nodeselection.AnyFilter{}}, } } // AddLegacyStaticRules initializes all the placement rules defined earlier in static golang code. -func (d *ConfigurablePlacementRule) AddLegacyStaticRules() { +func (d *PlacementDefinitions) AddLegacyStaticRules() { d.placements[storj.EEA] = nodeselection.NodeFilters{nodeselection.NewCountryFilter(location.NewSet(nodeselection.EeaCountriesWithoutEu...).With(nodeselection.EuCountries...))} d.placements[storj.EU] = nodeselection.NodeFilters{nodeselection.NewCountryFilter(location.NewSet(nodeselection.EuCountries...))} d.placements[storj.US] = nodeselection.NodeFilters{nodeselection.NewCountryFilter(location.NewSet(location.UnitedStates))} @@ -75,54 +74,25 @@ func (d *ConfigurablePlacementRule) AddLegacyStaticRules() { } // AddPlacementRule registers a new placement. -func (d *ConfigurablePlacementRule) AddPlacementRule(id storj.PlacementConstraint, filter nodeselection.NodeFilter) { +func (d *PlacementDefinitions) AddPlacementRule(id storj.PlacementConstraint, filter nodeselection.NodeFilter) { d.placements[id] = filter } type stringNotMatch string // AddPlacementFromString parses placement definition form string representations from id:definition;id:definition;... -func (d *ConfigurablePlacementRule) AddPlacementFromString(definitions string) error { +func (d *PlacementDefinitions) AddPlacementFromString(definitions string) error { env := map[any]any{ - "country": func(countries ...string) (nodeselection.NodeFilters, error) { - var set location.Set - for _, country := range countries { - apply := func(modified location.Set, code ...location.CountryCode) location.Set { - return modified.With(code...) - } - if country[0] == '!' { - apply = func(modified location.Set, code ...location.CountryCode) location.Set { - return modified.Without(code...) - } - country = country[1:] - } - switch strings.ToLower(country) { - case "all", "*", "any": - set = location.NewFullSet() - case "none": - set = apply(set, location.None) - case "eu": - set = apply(set, nodeselection.EuCountries...) - case "eea": - set = apply(set, nodeselection.EuCountries...) - set = apply(set, nodeselection.EeaCountriesWithoutEu...) - default: - code := location.ToCountryCode(country) - if code == location.None { - return nil, errs.New("invalid country code %q", code) - } - set = apply(set, code) - } - } - return nodeselection.NodeFilters{nodeselection.NewCountryFilter(set)}, nil + "country": func(countries ...string) (nodeselection.NodeFilter, error) { + return nodeselection.NewCountryFilterFromString(countries) }, "placement": func(ix int64) nodeselection.NodeFilter { return d.placements[storj.PlacementConstraint(ix)] }, - "all": func(filters ...nodeselection.NodeFilters) (nodeselection.NodeFilters, error) { + "all": func(filters ...nodeselection.NodeFilter) (nodeselection.NodeFilters, error) { res := nodeselection.NodeFilters{} for _, filter := range filters { - res = append(res, filter...) + res = append(res, filter) } return res, nil }, @@ -204,7 +174,7 @@ func (d *ConfigurablePlacementRule) AddPlacementFromString(definitions string) e } // CreateFilters implements PlacementCondition. -func (d *ConfigurablePlacementRule) CreateFilters(constraint storj.PlacementConstraint) (filter nodeselection.NodeFilter) { +func (d *PlacementDefinitions) CreateFilters(constraint storj.PlacementConstraint) (filter nodeselection.NodeFilter) { if filters, found := d.placements[constraint]; found { return filters } diff --git a/satellite/overlay/placement_test.go b/satellite/overlay/placement_test.go index f43a2bcc3..0b424af4e 100644 --- a/satellite/overlay/placement_test.go +++ b/satellite/overlay/placement_test.go @@ -20,14 +20,14 @@ func TestPlacementFromString(t *testing.T) { require.NoError(t, err) t.Run("invalid country-code", func(t *testing.T) { - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(`1:country("ZZZZ")`) require.Error(t, err) }) t.Run("country tests", func(t *testing.T) { countryTest := func(placementDef string, shouldBeIncluded []location.CountryCode, shouldBeExcluded []location.CountryCode) { - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString("11:" + placementDef) require.NoError(t, err) filters := p.placements[storj.PlacementConstraint(11)] @@ -53,15 +53,23 @@ func TestPlacementFromString(t *testing.T) { t.Run("tag rule", func(t *testing.T) { tagged := func(key string, value string) nodeselection.NodeTags { - return nodeselection.NodeTags{ - { - Signer: signer, - Name: key, - Value: []byte(value), - }, + return nodeselection.NodeTags{{ + Signer: signer, + Name: key, + Value: []byte(value), + }, } } + p := NewPlacementDefinitions() + err := p.AddPlacementFromString(`11:tag("12whfK1EDvHJtajBiAUeajQLYcWqxcQmdYQU5zX5cCf6bAxfgu4","foo","bar")`) + require.NoError(t, err) + filters := p.placements[storj.PlacementConstraint(11)] + require.NotNil(t, filters) + require.True(t, filters.Match(&nodeselection.SelectedNode{ + Tags: tagged("foo", "bar"), + })) + testCases := []struct { name string placement string @@ -122,7 +130,7 @@ func TestPlacementFromString(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(tc.placement) require.NoError(t, err) filters := p.placements[storj.PlacementConstraint(11)] @@ -138,7 +146,7 @@ func TestPlacementFromString(t *testing.T) { }) t.Run("placement reuse", func(t *testing.T) { - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(`1:tag("12whfK1EDvHJtajBiAUeajQLYcWqxcQmdYQU5zX5cCf6bAxfgu4","foo","bar");2:exclude(placement(1))`) require.NoError(t, err) @@ -173,7 +181,7 @@ func TestPlacementFromString(t *testing.T) { `11:all(country("GB"),tag("12whfK1EDvHJtajBiAUeajQLYcWqxcQmdYQU5zX5cCf6bAxfgu4","foo","bar"))`, `11:country("GB") && tag("12whfK1EDvHJtajBiAUeajQLYcWqxcQmdYQU5zX5cCf6bAxfgu4","foo","bar")`, } { - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(syntax) require.NoError(t, err) filters := p.placements[storj.PlacementConstraint(11)] @@ -203,14 +211,14 @@ func TestPlacementFromString(t *testing.T) { })) } t.Run("invalid", func(t *testing.T) { - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString("10:1 && 2") require.Error(t, err) }) }) t.Run("multi rule", func(t *testing.T) { - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(`11:country("GB");12:country("DE")`) require.NoError(t, err) @@ -222,6 +230,7 @@ func TestPlacementFromString(t *testing.T) { require.False(t, filters.Match(&nodeselection.SelectedNode{ CountryCode: location.Germany, })) + require.Equal(t, `country("GB")`, fmt.Sprintf("%s", filters)) filters = p.placements[storj.PlacementConstraint(12)] require.NotNil(t, filters) @@ -237,7 +246,7 @@ func TestPlacementFromString(t *testing.T) { t.Run("annotation usage", func(t *testing.T) { t.Run("normal", func(t *testing.T) { t.Parallel() - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(`11:annotated(country("GB"),annotation("autoExcludeSubnet","off"))`) require.NoError(t, err) filters := p.placements[storj.PlacementConstraint(11)] @@ -249,7 +258,7 @@ func TestPlacementFromString(t *testing.T) { }) t.Run("with &&", func(t *testing.T) { t.Parallel() - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(`11:country("GB") && annotation("foo","bar") && annotation("bar","foo")`) require.NoError(t, err) @@ -263,7 +272,7 @@ func TestPlacementFromString(t *testing.T) { }) t.Run("chained", func(t *testing.T) { t.Parallel() - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(`11:annotated(annotated(country("GB"),annotation("foo","bar")),annotation("bar","foo"))`) require.NoError(t, err) filters := p.placements[storj.PlacementConstraint(11)] @@ -276,7 +285,7 @@ func TestPlacementFromString(t *testing.T) { require.Equal(t, "", nodeselection.GetAnnotation(filters, "kossuth")) }) t.Run("location", func(t *testing.T) { - p := NewPlacementRules() + p := NewPlacementDefinitions() s := fmt.Sprintf(`11:annotated(annotated(country("GB"),annotation("%s","test-location")),annotation("%s","%s"))`, nodeselection.Location, nodeselection.AutoExcludeSubnet, nodeselection.AutoExcludeSubnetOFF) require.NoError(t, p.AddPlacementFromString(s)) filters := p.placements[storj.PlacementConstraint(11)] @@ -290,7 +299,7 @@ func TestPlacementFromString(t *testing.T) { }) t.Run("exclude", func(t *testing.T) { - p := NewPlacementRules() + p := NewPlacementDefinitions() err := p.AddPlacementFromString(`11:exclude(country("GB"))`) require.NoError(t, err) filters := p.placements[storj.PlacementConstraint(11)] @@ -303,7 +312,7 @@ func TestPlacementFromString(t *testing.T) { }) t.Run("legacy geofencing rules", func(t *testing.T) { - p := NewPlacementRules() + p := NewPlacementDefinitions() p.AddLegacyStaticRules() t.Run("nr", func(t *testing.T) { @@ -336,7 +345,7 @@ func TestPlacementFromString(t *testing.T) { t.Run("full example", func(t *testing.T) { // this is a realistic configuration, compatible with legacy rules + using one node tag for specific placement - rules1 := NewPlacementRules() + rules1 := NewPlacementDefinitions() err := rules1.AddPlacementFromString(` 10:tag("12whfK1EDvHJtajBiAUeajQLYcWqxcQmdYQU5zX5cCf6bAxfgu4","selected",notEmpty()); 11:placement(10) && annotation("autoExcludeSubnet","off") && annotation("location","do-not-use"); @@ -350,7 +359,7 @@ func TestPlacementFromString(t *testing.T) { require.NoError(t, err) // for countries, it should be the same as above - rules2 := NewPlacementRules() + rules2 := NewPlacementDefinitions() rules2.AddLegacyStaticRules() testCountries := []location.CountryCode{ @@ -434,5 +443,27 @@ func TestPlacementFromString(t *testing.T) { } }) - +} + +func TestStringSerialization(t *testing.T) { + placements := []string{ + `"10:country("GB")`, + } + for _, p := range placements { + // this flow is very similar to the logic of our flag parsing, + // where viper first parses the value, but later write it out to a string when viper.AllSettings() is called + // the string representation should be parseable, and have the same information. + + r := ConfigurablePlacementRule{} + err := r.Set(p) + require.NoError(t, err) + serialized := r.String() + + r2 := ConfigurablePlacementRule{} + err = r2.Set(serialized) + require.NoError(t, err) + + require.Equal(t, p, r2.String()) + + } } diff --git a/satellite/overlay/selection_test.go b/satellite/overlay/selection_test.go index 10c8ba32a..09293e392 100644 --- a/satellite/overlay/selection_test.go +++ b/satellite/overlay/selection_test.go @@ -638,7 +638,7 @@ func runServiceWithDB(ctx *testcontext.Context, log *zap.Logger, reputable int, db.reputable = append(db.reputable, &node) } } - service, _ := overlay.NewService(log, db, nil, overlay.NewPlacementRules().CreateFilters, "", "", config) + service, _ := overlay.NewService(log, db, nil, overlay.NewPlacementDefinitions().CreateFilters, "", "", config) serviceCtx, cancel := context.WithCancel(ctx) ctx.Go(func() error { return service.Run(serviceCtx) diff --git a/satellite/overlay/service_test.go b/satellite/overlay/service_test.go index f8484200c..91c6360ef 100644 --- a/satellite/overlay/service_test.go +++ b/satellite/overlay/service_test.go @@ -73,7 +73,7 @@ func testCache(ctx *testcontext.Context, t *testing.T, store overlay.DB, nodeEve serviceCtx, serviceCancel := context.WithCancel(ctx) defer serviceCancel() - service, err := overlay.NewService(zaptest.NewLogger(t), store, nodeEvents, overlay.NewPlacementRules().CreateFilters, "", "", serviceConfig) + service, err := overlay.NewService(zaptest.NewLogger(t), store, nodeEvents, overlay.NewPlacementDefinitions().CreateFilters, "", "", serviceConfig) require.NoError(t, err) ctx.Go(func() error { return service.Run(serviceCtx) }) defer ctx.Check(service.Close) diff --git a/satellite/overlay/uploadselection_test.go b/satellite/overlay/uploadselection_test.go index ae580109d..c86d7e5eb 100644 --- a/satellite/overlay/uploadselection_test.go +++ b/satellite/overlay/uploadselection_test.go @@ -65,7 +65,7 @@ func TestRefresh(t *testing.T) { lowStaleness, nodeSelectionConfig, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) @@ -162,7 +162,7 @@ func TestRefreshConcurrent(t *testing.T) { highStaleness, nodeSelectionConfig, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) @@ -189,7 +189,7 @@ func TestRefreshConcurrent(t *testing.T) { lowStaleness, nodeSelectionConfig, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) ctx.Go(func() error { return cache.Run(cacheCtx) }) @@ -214,7 +214,7 @@ func TestSelectNodes(t *testing.T) { DistinctIP: true, MinimumDiskSpace: 100 * memory.MiB, } - placementRules := overlay.NewPlacementRules() + placementRules := overlay.NewPlacementDefinitions() placementRules.AddPlacementRule(storj.PlacementConstraint(5), nodeselection.NodeFilters{}.WithCountryFilter(location.NewSet(location.Germany))) placementRules.AddPlacementRule(storj.PlacementConstraint(6), nodeselection.WithAnnotation(nodeselection.NodeFilters{}.WithCountryFilter(location.NewSet(location.Germany)), nodeselection.AutoExcludeSubnet, nodeselection.AutoExcludeSubnetOFF)) @@ -389,7 +389,7 @@ func TestGetNodesConcurrent(t *testing.T) { highStaleness, nodeSelectionConfig, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) @@ -436,7 +436,7 @@ func TestGetNodesConcurrent(t *testing.T) { lowStaleness, nodeSelectionConfig, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) @@ -528,7 +528,7 @@ func TestGetNodesDistinct(t *testing.T) { highStaleness, config, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) @@ -569,7 +569,7 @@ func TestGetNodesDistinct(t *testing.T) { highStaleness, config, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) @@ -594,7 +594,7 @@ func TestGetNodesError(t *testing.T) { highStaleness, nodeSelectionConfig, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) @@ -623,7 +623,7 @@ func TestNewNodeFraction(t *testing.T) { lowStaleness, nodeSelectionConfig, nodeselection.NodeFilters{}, - overlay.NewPlacementRules().CreateFilters, + overlay.NewPlacementDefinitions().CreateFilters, ) require.NoError(t, err) @@ -674,7 +674,7 @@ func BenchmarkGetNodes(b *testing.B) { defer cancel() log, err := zap.NewDevelopment() require.NoError(b, err) - placement := overlay.NewPlacementRules() + placement := overlay.NewPlacementDefinitions() placement.AddLegacyStaticRules() defaultFilter := nodeselection.NodeFilters{} diff --git a/satellite/rangedloop.go b/satellite/rangedloop.go index b86c4fef2..a345111e6 100644 --- a/satellite/rangedloop.go +++ b/satellite/rangedloop.go @@ -139,7 +139,12 @@ func NewRangedLoop(log *zap.Logger, db DB, metabaseDB *metabase.DB, config *Conf } { // setup overlay - peer.Overlay.Service, err = overlay.NewService(peer.Log.Named("overlay"), peer.DB.OverlayCache(), peer.DB.NodeEvents(), config.Placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + + peer.Overlay.Service, err = overlay.NewService(peer.Log.Named("overlay"), peer.DB.OverlayCache(), peer.DB.NodeEvents(), placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) if err != nil { return nil, errs.Combine(err, peer.Close()) } @@ -151,6 +156,11 @@ func NewRangedLoop(log *zap.Logger, db DB, metabaseDB *metabase.DB, config *Conf } { // setup repair + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + if len(config.Checker.RepairExcludedCountryCodes) == 0 { config.Checker.RepairExcludedCountryCodes = config.Overlay.RepairExcludedCountryCodes } @@ -159,7 +169,7 @@ func NewRangedLoop(log *zap.Logger, db DB, metabaseDB *metabase.DB, config *Conf peer.Log.Named("repair:checker"), peer.DB.RepairQueue(), peer.Overlay.Service, - config.Placement.CreateFilters, + placement.CreateFilters, config.Checker, ) } diff --git a/satellite/repair/checker/observer_test.go b/satellite/repair/checker/observer_test.go index 1e024ba2b..782789f91 100644 --- a/satellite/repair/checker/observer_test.go +++ b/satellite/repair/checker/observer_test.go @@ -557,7 +557,7 @@ func BenchmarkRemoteSegment(b *testing.B) { } observer := checker.NewObserver(zap.NewNop(), planet.Satellites[0].DB.RepairQueue(), - planet.Satellites[0].Auditor.Overlay, overlay.NewPlacementRules().CreateFilters, planet.Satellites[0].Config.Checker) + planet.Satellites[0].Auditor.Overlay, overlay.NewPlacementDefinitions().CreateFilters, planet.Satellites[0].Config.Checker) segments, err := planet.Satellites[0].Metabase.DB.TestingAllSegments(ctx) require.NoError(b, err) @@ -658,7 +658,7 @@ func TestObserver_PlacementCheck(t *testing.T) { } // confirm that some pieces are out of placement - ok, err := allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, overlay.NewPlacementRules().CreateFilters(segments[0].Placement)) + ok, err := allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, overlay.NewPlacementDefinitions().CreateFilters(segments[0].Placement)) require.NoError(t, err) require.False(t, ok) diff --git a/satellite/repair/checker/observer_unit_test.go b/satellite/repair/checker/observer_unit_test.go index 6f1497928..41d80d560 100644 --- a/satellite/repair/checker/observer_unit_test.go +++ b/satellite/repair/checker/observer_unit_test.go @@ -52,7 +52,7 @@ func TestObserverForkProcess(t *testing.T) { statsCollector: make(map[string]*observerRSStats), nodesCache: &ReliabilityCache{ staleness: time.Hour, - placementRules: overlay.NewPlacementRules().CreateFilters, + placementRules: overlay.NewPlacementDefinitions().CreateFilters, }, } @@ -144,11 +144,13 @@ func TestObserverForkProcess(t *testing.T) { placements := overlay.ConfigurablePlacementRule{} require.NoError(t, placements.Set(fmt.Sprintf(`10:annotated(country("DE"),annotation("%s","%s"))`, nodeselection.AutoExcludeSubnet, nodeselection.AutoExcludeSubnetOFF))) - o.nodesCache.placementRules = placements.CreateFilters + parsed, err := placements.Parse() + require.NoError(t, err) + o.nodesCache.placementRules = parsed.CreateFilters q := queue.MockRepairQueue{} fork := createFork(o, &q) - err := fork.process(ctx, &rangedloop.Segment{ + err = fork.process(ctx, &rangedloop.Segment{ Placement: 10, Pieces: createPieces(nodes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), Redundancy: storj.RedundancyScheme{ diff --git a/satellite/repair/checker/online_test.go b/satellite/repair/checker/online_test.go index b91f41619..6988d226b 100644 --- a/satellite/repair/checker/online_test.go +++ b/satellite/repair/checker/online_test.go @@ -29,7 +29,7 @@ func TestReliabilityCache_Concurrent(t *testing.T) { ctx := testcontext.New(t) defer ctx.Cleanup() - overlayCache, err := overlay.NewService(zap.NewNop(), fakeOverlayDB{}, fakeNodeEvents{}, overlay.NewPlacementRules().CreateFilters, "", "", overlay.Config{ + overlayCache, err := overlay.NewService(zap.NewNop(), fakeOverlayDB{}, fakeNodeEvents{}, overlay.NewPlacementDefinitions().CreateFilters, "", "", overlay.Config{ NodeSelectionCache: overlay.UploadSelectionCacheConfig{ Staleness: 2 * time.Nanosecond, }, @@ -40,7 +40,7 @@ func TestReliabilityCache_Concurrent(t *testing.T) { ctx.Go(func() error { return overlayCache.Run(cacheCtx) }) defer ctx.Check(overlayCache.Close) - cache := checker.NewReliabilityCache(overlayCache, time.Millisecond, overlay.NewPlacementRules().CreateFilters, []string{}) + cache := checker.NewReliabilityCache(overlayCache, time.Millisecond, overlay.NewPlacementDefinitions().CreateFilters, []string{}) var group errgroup.Group for i := 0; i < 10; i++ { group.Go(func() error { @@ -82,7 +82,7 @@ func TestReliabilityCache_OutOfPlacementPieces(t *testing.T) { overlayService := planet.Satellites[0].Overlay.Service config := planet.Satellites[0].Config.Checker - rules := overlay.NewPlacementRules() + rules := overlay.NewPlacementDefinitions() rules.AddLegacyStaticRules() cache := checker.NewReliabilityCache(overlayService, config.ReliabilityCacheStaleness, rules.CreateFilters, []string{}) diff --git a/satellite/repair/repairer/segments_test.go b/satellite/repair/repairer/segments_test.go index 5d06d42c2..c7bd43d27 100644 --- a/satellite/repair/repairer/segments_test.go +++ b/satellite/repair/repairer/segments_test.go @@ -110,7 +110,11 @@ func TestSegmentRepairPlacement(t *testing.T) { } // confirm that some pieces are out of placement - ok, err := allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, planet.Satellites[0].Config.Placement.CreateFilters) + + placement, err := planet.Satellites[0].Config.Placement.Parse() + require.NoError(t, err) + + ok, err := allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, placement.CreateFilters) require.NoError(t, err) require.False(t, ok) @@ -129,7 +133,7 @@ func TestSegmentRepairPlacement(t *testing.T) { require.NotNil(t, segments[0].RepairedAt) require.Len(t, segments[0].Pieces, tc.piecesAfterRepair) - ok, err = allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, planet.Satellites[0].Config.Placement.CreateFilters) + ok, err = allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, placement.CreateFilters) require.NoError(t, err) require.True(t, ok) @@ -236,8 +240,11 @@ func TestSegmentRepairWithNodeTags(t *testing.T) { require.NoError(t, err) require.Len(t, segments, 1) + placement, err := planet.Satellites[0].Config.Placement.Parse() + require.NoError(t, err) + require.Equal(t, storj.PlacementConstraint(10), segments[0].Placement) - ok, err := allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, planet.Satellites[0].Config.Placement.CreateFilters) + ok, err := allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, placement.CreateFilters) require.NoError(t, err) require.True(t, ok) @@ -347,8 +354,11 @@ func TestSegmentRepairPlacementAndClumped(t *testing.T) { require.NoError(t, err) } + placement, err := planet.Satellites[0].Config.Placement.Parse() + require.NoError(t, err) + // confirm that some pieces are out of placement - ok, err := allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, planet.Satellites[0].Config.Placement.CreateFilters) + ok, err := allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, placement.CreateFilters) require.NoError(t, err) require.False(t, ok) @@ -367,7 +377,7 @@ func TestSegmentRepairPlacementAndClumped(t *testing.T) { require.NotNil(t, segments[0].RepairedAt) require.Len(t, segments[0].Pieces, 4) - ok, err = allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, planet.Satellites[0].Config.Placement.CreateFilters) + ok, err = allPiecesInPlacement(ctx, planet.Satellites[0].Overlay.Service, segments[0].Pieces, segments[0].Placement, placement.CreateFilters) require.NoError(t, err) require.True(t, ok) }) diff --git a/satellite/repair/repairer/segments_unit_test.go b/satellite/repair/repairer/segments_unit_test.go index 69f35046a..1e135a12f 100644 --- a/satellite/repair/repairer/segments_unit_test.go +++ b/satellite/repair/repairer/segments_unit_test.go @@ -45,7 +45,7 @@ func TestClassify(t *testing.T) { c := &overlay.ConfigurablePlacementRule{} require.NoError(t, c.Set("")) s := SegmentRepairer{ - placementRules: c.CreateFilters, + placementRules: overlay.NewPlacementDefinitions().CreateFilters, } pieces := createPieces(selectedNodes, 0, 1, 2, 3, 4) result, err := s.classifySegmentPiecesWithNodes(ctx, metabase.Segment{Pieces: pieces}, allNodeIDs(pieces), selectedNodes) @@ -69,8 +69,11 @@ func TestClassify(t *testing.T) { }) - c := &overlay.ConfigurablePlacementRule{} - require.NoError(t, c.Set("10:country(\"GB\")")) + c, err := overlay.ConfigurablePlacementRule{ + PlacementRules: `10:country("GB")`, + }.Parse() + require.NoError(t, err) + s := SegmentRepairer{ placementRules: c.CreateFilters, doPlacementCheck: true, @@ -96,8 +99,11 @@ func TestClassify(t *testing.T) { node.CountryCode = location.Germany }) - c := &overlay.ConfigurablePlacementRule{} - require.NoError(t, c.Set("10:country(\"GB\")")) + c, err := overlay.ConfigurablePlacementRule{ + PlacementRules: `10:country("GB")`, + }.Parse() + require.NoError(t, err) + s := SegmentRepairer{ placementRules: c.CreateFilters, doPlacementCheck: true, @@ -124,7 +130,7 @@ func TestClassify(t *testing.T) { node.LastNet = fmt.Sprintf("127.0.%d.0", ix/2) }) - c := overlay.NewPlacementRules() + c := overlay.NewPlacementDefinitions() s := SegmentRepairer{ placementRules: c.CreateFilters, doDeclumping: true, @@ -154,8 +160,10 @@ func TestClassify(t *testing.T) { node.CountryCode = location.UnitedKingdom }) - c := overlay.NewPlacementRules() - require.NoError(t, c.Set(fmt.Sprintf(`10:annotated(country("GB"),annotation("%s","%s"))`, nodeselection.AutoExcludeSubnet, nodeselection.AutoExcludeSubnetOFF))) + c, err := overlay.ConfigurablePlacementRule{ + PlacementRules: fmt.Sprintf(`10:annotated(country("GB"),annotation("%s","%s"))`, nodeselection.AutoExcludeSubnet, nodeselection.AutoExcludeSubnetOFF), + }.Parse() + require.NoError(t, err) s := SegmentRepairer{ placementRules: c.CreateFilters, diff --git a/satellite/repairer.go b/satellite/repairer.go index 2a6efd622..e8a8860fd 100644 --- a/satellite/repairer.go +++ b/satellite/repairer.go @@ -140,8 +140,12 @@ func NewRepairer(log *zap.Logger, full *identity.FullIdentity, } { // setup overlay - var err error - peer.Overlay, err = overlay.NewService(log.Named("overlay"), overlayCache, nodeEvents, config.Placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + + peer.Overlay, err = overlay.NewService(log.Named("overlay"), overlayCache, nodeEvents, placement.CreateFilters, config.Console.ExternalAddress, config.Console.SatelliteName, config.Overlay) if err != nil { return nil, errs.Combine(err, peer.Close()) } @@ -174,7 +178,11 @@ func NewRepairer(log *zap.Logger, full *identity.FullIdentity, } { // setup orders - var err error + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + peer.Orders.Service, err = orders.NewService( log.Named("orders"), signing.SignerFromFullIdentity(peer.Identity), @@ -183,7 +191,7 @@ func NewRepairer(log *zap.Logger, full *identity.FullIdentity, // PUT and GET actions which are not used by // repairer so we can set noop implementation. orders.NewNoopDB(), - config.Placement.CreateFilters, + placement.CreateFilters, config.Orders, ) if err != nil { @@ -203,6 +211,11 @@ func NewRepairer(log *zap.Logger, full *identity.FullIdentity, } { // setup repairer + placement, err := config.Placement.Parse() + if err != nil { + return nil, err + } + peer.EcRepairer = repairer.NewECRepairer( log.Named("ec-repair"), peer.Dialer, @@ -222,7 +235,7 @@ func NewRepairer(log *zap.Logger, full *identity.FullIdentity, peer.Overlay, peer.Audit.Reporter, peer.EcRepairer, - config.Placement.CreateFilters, + placement.CreateFilters, config.Checker.RepairOverrides, config.Repairer, )