[#36] Add attribute parser
Attribute parser converts strings of attribute chain into one-dimension array of NodeInfo attributes, that used in network map. Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
parent
05f3963975
commit
28534a509d
2 changed files with 137 additions and 0 deletions
77
pkg/util/attributes/parser.go
Normal file
77
pkg/util/attributes/parser.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package attributes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
)
|
||||
|
||||
const (
|
||||
pairSeparator = "/"
|
||||
keyValueSeparator = ":"
|
||||
)
|
||||
|
||||
var (
|
||||
errEmptyChain = errors.New("empty attribute chain")
|
||||
errNonUniqueBucket = errors.New("attributes must contain unique keys")
|
||||
errUnexpectedKey = errors.New("attributes contain unexpected key")
|
||||
)
|
||||
|
||||
// ParseV2Attributes parses strings like "K1:V1/K2:V2/K3:V3" into netmap
|
||||
// attributes.
|
||||
func ParseV2Attributes(attrs []string, excl []string) ([]*netmap.Attribute, error) {
|
||||
restricted := make(map[string]struct{}, len(excl))
|
||||
for i := range excl {
|
||||
restricted[excl[i]] = struct{}{}
|
||||
}
|
||||
|
||||
cache := make(map[string]*netmap.Attribute, len(attrs))
|
||||
|
||||
for i := range attrs {
|
||||
line := strings.Trim(attrs[i], pairSeparator)
|
||||
chain := strings.Split(line, pairSeparator)
|
||||
if len(chain) == 0 {
|
||||
return nil, errEmptyChain
|
||||
}
|
||||
|
||||
var parentKey string // backtrack parents in next pairs
|
||||
|
||||
for j := range chain {
|
||||
pair := strings.Split(chain[j], keyValueSeparator)
|
||||
if len(pair) != 2 {
|
||||
return nil, fmt.Errorf("incorrect attribute pair %s", chain[j])
|
||||
}
|
||||
|
||||
key := pair[0]
|
||||
value := pair[1]
|
||||
|
||||
if at, ok := cache[key]; ok && at.GetValue() != value {
|
||||
return nil, errNonUniqueBucket
|
||||
}
|
||||
|
||||
if _, ok := restricted[key]; ok {
|
||||
return nil, errUnexpectedKey
|
||||
}
|
||||
|
||||
attribute := new(netmap.Attribute)
|
||||
attribute.SetKey(key)
|
||||
attribute.SetValue(value)
|
||||
|
||||
if parentKey != "" {
|
||||
attribute.SetParents([]string{parentKey})
|
||||
}
|
||||
|
||||
parentKey = key
|
||||
cache[key] = attribute
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]*netmap.Attribute, 0, len(cache))
|
||||
for _, v := range cache {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
60
pkg/util/attributes/parser_test.go
Normal file
60
pkg/util/attributes/parser_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package attributes_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-node/pkg/util/attributes"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseV2Attributes(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
attrs, err := attributes.ParseV2Attributes(nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, attrs, 0)
|
||||
})
|
||||
|
||||
t.Run("non unique bucket keys", func(t *testing.T) {
|
||||
good := []string{
|
||||
"StorageType:HDD/RPM:7200",
|
||||
"StorageType:HDD/SMR:True",
|
||||
}
|
||||
_, err := attributes.ParseV2Attributes(good, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
bad := append(good, "StorageType:SSD/Cell:QLC")
|
||||
_, err = attributes.ParseV2Attributes(bad, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
})
|
||||
|
||||
t.Run("malformed", func(t *testing.T) {
|
||||
_, err := attributes.ParseV2Attributes([]string{"..."}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = attributes.ParseV2Attributes([]string{"a:b", ""}, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = attributes.ParseV2Attributes([]string{"//"}, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("unexpected", func(t *testing.T) {
|
||||
unexpectedBucket := []string{
|
||||
"Location:Europe/City:Moscow",
|
||||
"Price:100",
|
||||
}
|
||||
_, err := attributes.ParseV2Attributes(unexpectedBucket, []string{"Price"})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("correct", func(t *testing.T) {
|
||||
from := []string{
|
||||
"/Location:Europe/Country:Sweden/City:Stockholm",
|
||||
"/StorageType:HDD/RPM:7200",
|
||||
}
|
||||
attrs, err := attributes.ParseV2Attributes(from, nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, attrs, 5)
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue