satellite/nodeselection: improve annotation composability
We would like to make it easier to accept multiple annotations. Examples: ``` country("GB") && annotation(...) annotated(annotated(X,...),...) ``` Change-Id: I92e622e8b985b314dadddf83b17976c245eb2069
This commit is contained in:
parent
2cdc1a973f
commit
5c12a3406d
@ -15,10 +15,50 @@ type NodeFilter interface {
|
|||||||
MatchInclude(node *SelectedNode) bool
|
MatchInclude(node *SelectedNode) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NodeFilterWithAnnotation is a NodeFilter with additional annotations.
|
||||||
|
type NodeFilterWithAnnotation interface {
|
||||||
|
NodeFilter
|
||||||
|
GetAnnotation(name string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotation can be used as node filters in 'XX && annotation('...')' like struct.
|
||||||
|
type Annotation struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchInclude implements NodeFilter.
|
||||||
|
func (a Annotation) MatchInclude(node *SelectedNode) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAnnotation implements NodeFilterWithAnnotation.
|
||||||
|
func (a Annotation) GetAnnotation(name string) string {
|
||||||
|
if a.Key == name {
|
||||||
|
return a.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ NodeFilterWithAnnotation = Annotation{}
|
||||||
|
|
||||||
// AnnotatedNodeFilter is just a NodeFilter with additional annotations.
|
// AnnotatedNodeFilter is just a NodeFilter with additional annotations.
|
||||||
type AnnotatedNodeFilter struct {
|
type AnnotatedNodeFilter struct {
|
||||||
Filter NodeFilter
|
Filter NodeFilter
|
||||||
Annotations map[string]string
|
Annotations []Annotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAnnotation implements NodeFilterWithAnnotation.
|
||||||
|
func (a AnnotatedNodeFilter) GetAnnotation(name string) string {
|
||||||
|
for _, a := range a.Annotations {
|
||||||
|
if a.Key == name {
|
||||||
|
return a.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if annotated, ok := a.Filter.(NodeFilterWithAnnotation); ok {
|
||||||
|
return annotated.GetAnnotation(name)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchInclude implements NodeFilter.
|
// MatchInclude implements NodeFilter.
|
||||||
@ -27,35 +67,27 @@ func (a AnnotatedNodeFilter) MatchInclude(node *SelectedNode) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithAnnotation adds annotations to a NodeFilter.
|
// WithAnnotation adds annotations to a NodeFilter.
|
||||||
func WithAnnotation(filter NodeFilter, name string, value string) NodeFilter {
|
func WithAnnotation(filter NodeFilter, name string, value string) NodeFilterWithAnnotation {
|
||||||
if anf, ok := filter.(AnnotatedNodeFilter); ok {
|
|
||||||
anf.Annotations[name] = value
|
|
||||||
return anf
|
|
||||||
}
|
|
||||||
return AnnotatedNodeFilter{
|
return AnnotatedNodeFilter{
|
||||||
Filter: filter,
|
Filter: filter,
|
||||||
Annotations: map[string]string{
|
Annotations: []Annotation{
|
||||||
name: value,
|
{
|
||||||
|
Key: name,
|
||||||
|
Value: value,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAnnotation retrieves annotation from AnnotatedNodeFilter.
|
// GetAnnotation retrieves annotation from AnnotatedNodeFilter.
|
||||||
func GetAnnotation(filter NodeFilter, name string) string {
|
func GetAnnotation(filter NodeFilter, name string) string {
|
||||||
if annotated, ok := filter.(AnnotatedNodeFilter); ok {
|
if annotated, ok := filter.(NodeFilterWithAnnotation); ok {
|
||||||
return annotated.Annotations[name]
|
return annotated.GetAnnotation(name)
|
||||||
}
|
|
||||||
if filters, ok := filter.(NodeFilters); ok {
|
|
||||||
for _, filter := range filters {
|
|
||||||
if annotated, ok := filter.(AnnotatedNodeFilter); ok {
|
|
||||||
return annotated.Annotations[name]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ NodeFilter = AnnotatedNodeFilter{}
|
var _ NodeFilterWithAnnotation = AnnotatedNodeFilter{}
|
||||||
|
|
||||||
// NodeFilters is a collection of multiple node filters (all should vote with true).
|
// NodeFilters is a collection of multiple node filters (all should vote with true).
|
||||||
type NodeFilters []NodeFilter
|
type NodeFilters []NodeFilter
|
||||||
@ -94,7 +126,20 @@ func (n NodeFilters) WithExcludedIDs(ds []storj.NodeID) NodeFilters {
|
|||||||
return append(n, ExcludedIDs(ds))
|
return append(n, ExcludedIDs(ds))
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ NodeFilter = NodeFilters{}
|
// GetAnnotation implements NodeFilterWithAnnotation.
|
||||||
|
func (n NodeFilters) GetAnnotation(name string) string {
|
||||||
|
for _, filter := range n {
|
||||||
|
if annotated, ok := filter.(NodeFilterWithAnnotation); ok {
|
||||||
|
value := annotated.GetAnnotation(name)
|
||||||
|
if value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ NodeFilterWithAnnotation = NodeFilters{}
|
||||||
|
|
||||||
// CountryFilter can select nodes based on the condition of the country code.
|
// CountryFilter can select nodes based on the condition of the country code.
|
||||||
type CountryFilter struct {
|
type CountryFilter struct {
|
||||||
|
@ -55,6 +55,20 @@ func TestCriteria_ExcludedNodeNetworks(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnnotations(t *testing.T) {
|
||||||
|
k := WithAnnotation(NodeFilters{}, "foo", "bar")
|
||||||
|
require.Equal(t, "bar", k.GetAnnotation("foo"))
|
||||||
|
|
||||||
|
k = NodeFilters{WithAnnotation(NodeFilters{}, "foo", "bar")}
|
||||||
|
require.Equal(t, "bar", k.GetAnnotation("foo"))
|
||||||
|
|
||||||
|
k = Annotation{
|
||||||
|
Key: "foo",
|
||||||
|
Value: "bar",
|
||||||
|
}
|
||||||
|
require.Equal(t, "bar", k.GetAnnotation("foo"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestCriteria_Geofencing(t *testing.T) {
|
func TestCriteria_Geofencing(t *testing.T) {
|
||||||
eu := NodeFilters{}.WithCountryFilter(location.NewSet(EuCountries...))
|
eu := NodeFilters{}.WithCountryFilter(location.NewSet(EuCountries...))
|
||||||
us := NodeFilters{}.WithCountryFilter(location.NewSet(location.UnitedStates))
|
us := NodeFilters{}.WithCountryFilter(location.NewSet(location.UnitedStates))
|
||||||
|
@ -151,15 +151,16 @@ func (d *ConfigurablePlacementRule) AddPlacementFromString(definitions string) e
|
|||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
},
|
},
|
||||||
"annotated": func(filter nodeselection.NodeFilter, kv map[string]string) (nodeselection.AnnotatedNodeFilter, error) {
|
"annotated": func(filter nodeselection.NodeFilter, kv ...nodeselection.Annotation) (nodeselection.AnnotatedNodeFilter, error) {
|
||||||
return nodeselection.AnnotatedNodeFilter{
|
return nodeselection.AnnotatedNodeFilter{
|
||||||
Filter: filter,
|
Filter: filter,
|
||||||
Annotations: kv,
|
Annotations: kv,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
"annotation": func(key string, value string) (map[string]string, error) {
|
"annotation": func(key string, value string) (nodeselection.Annotation, error) {
|
||||||
return map[string]string{
|
return nodeselection.Annotation{
|
||||||
key: value,
|
Key: key,
|
||||||
|
Value: value,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
"exclude": func(filter nodeselection.NodeFilter) (nodeselection.NodeFilter, error) {
|
"exclude": func(filter nodeselection.NodeFilter) (nodeselection.NodeFilter, error) {
|
||||||
|
@ -166,16 +166,47 @@ func TestPlacementFromString(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
t.Run("annotated", func(t *testing.T) {
|
t.Run("annotation usage", func(t *testing.T) {
|
||||||
p := NewPlacementRules()
|
t.Run("normal", func(t *testing.T) {
|
||||||
err := p.AddPlacementFromString(`11:annotated(country("GB"),annotation("autoExcludeSubnet","off"))`)
|
t.Parallel()
|
||||||
require.NoError(t, err)
|
p := NewPlacementRules()
|
||||||
filters := p.placements[storj.PlacementConstraint(11)]
|
err := p.AddPlacementFromString(`11:annotated(country("GB"),annotation("autoExcludeSubnet","off"))`)
|
||||||
require.True(t, filters.MatchInclude(&nodeselection.SelectedNode{
|
require.NoError(t, err)
|
||||||
CountryCode: location.UnitedKingdom,
|
filters := p.placements[storj.PlacementConstraint(11)]
|
||||||
}))
|
require.True(t, filters.MatchInclude(&nodeselection.SelectedNode{
|
||||||
|
CountryCode: location.UnitedKingdom,
|
||||||
|
}))
|
||||||
|
|
||||||
require.Equal(t, nodeselection.GetAnnotation(filters, "autoExcludeSubnet"), "off")
|
require.Equal(t, nodeselection.GetAnnotation(filters, "autoExcludeSubnet"), "off")
|
||||||
|
})
|
||||||
|
t.Run("with &&", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
p := NewPlacementRules()
|
||||||
|
err := p.AddPlacementFromString(`11:country("GB") && annotation("foo","bar") && annotation("bar","foo")`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
filters := p.placements[storj.PlacementConstraint(11)]
|
||||||
|
require.True(t, filters.MatchInclude(&nodeselection.SelectedNode{
|
||||||
|
CountryCode: location.UnitedKingdom,
|
||||||
|
}))
|
||||||
|
require.Equal(t, "bar", nodeselection.GetAnnotation(filters, "foo"))
|
||||||
|
require.Equal(t, "foo", nodeselection.GetAnnotation(filters, "bar"))
|
||||||
|
require.Equal(t, "", nodeselection.GetAnnotation(filters, "kossuth"))
|
||||||
|
})
|
||||||
|
t.Run("chained", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
p := NewPlacementRules()
|
||||||
|
err := p.AddPlacementFromString(`11:annotated(annotated(country("GB"),annotation("foo","bar")),annotation("bar","foo"))`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
filters := p.placements[storj.PlacementConstraint(11)]
|
||||||
|
require.True(t, filters.MatchInclude(&nodeselection.SelectedNode{
|
||||||
|
CountryCode: location.UnitedKingdom,
|
||||||
|
}))
|
||||||
|
|
||||||
|
require.Equal(t, "bar", nodeselection.GetAnnotation(filters, "foo"))
|
||||||
|
require.Equal(t, "foo", nodeselection.GetAnnotation(filters, "bar"))
|
||||||
|
require.Equal(t, "", nodeselection.GetAnnotation(filters, "kossuth"))
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
t.Run("exclude", func(t *testing.T) {
|
t.Run("exclude", func(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user