[#227] netmap: Refactor and document package functionality
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
5bfdb64251
commit
ca523f1ff1
51 changed files with 2460 additions and 3819 deletions
|
@ -2,6 +2,8 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||||
|
@ -96,7 +98,22 @@ func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEnd
|
||||||
ver.ReadFromV2(*v2ver)
|
ver.ReadFromV2(*v2ver)
|
||||||
}
|
}
|
||||||
res.setLatestVersion(&ver)
|
res.setLatestVersion(&ver)
|
||||||
res.setNodeInfo(netmap.NewNodeInfoFromV2(body.GetNodeInfo()))
|
|
||||||
|
nodeV2 := body.GetNodeInfo()
|
||||||
|
if nodeV2 == nil {
|
||||||
|
cc.err = errors.New("missing node info in response body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var node netmap.NodeInfo
|
||||||
|
|
||||||
|
cc.err = node.ReadFromV2(*nodeV2)
|
||||||
|
if cc.err != nil {
|
||||||
|
cc.err = fmt.Errorf("invalid node info: %w", cc.err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setNodeInfo(&node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// process call
|
// process call
|
||||||
|
@ -171,7 +188,21 @@ func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetwo
|
||||||
cc.result = func(r responseV2) {
|
cc.result = func(r responseV2) {
|
||||||
resp := r.(*v2netmap.NetworkInfoResponse)
|
resp := r.(*v2netmap.NetworkInfoResponse)
|
||||||
|
|
||||||
res.setInfo(netmap.NewNetworkInfoFromV2(resp.GetBody().GetNetworkInfo()))
|
netInfoV2 := resp.GetBody().GetNetworkInfo()
|
||||||
|
if netInfoV2 == nil {
|
||||||
|
cc.err = errors.New("missing network info in response body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var netInfo netmap.NetworkInfo
|
||||||
|
|
||||||
|
cc.err = netInfo.ReadFromV2(*netInfoV2)
|
||||||
|
if cc.err != nil {
|
||||||
|
cc.err = fmt.Errorf("invalid network info: %w", cc.err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setInfo(&netInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// process call
|
// process call
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||||
|
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/acl"
|
"github.com/nspcc-dev/neofs-sdk-go/acl"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
|
@ -158,11 +159,30 @@ func (c *Container) SetAttributes(v Attributes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) PlacementPolicy() *netmap.PlacementPolicy {
|
func (c *Container) PlacementPolicy() *netmap.PlacementPolicy {
|
||||||
return netmap.NewPlacementPolicyFromV2(c.v2.GetPlacementPolicy())
|
m := c.v2.GetPlacementPolicy()
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var p netmap.PlacementPolicy
|
||||||
|
// FIXME(@cthulhu-rider): #225 handle error
|
||||||
|
err := p.ReadFromV2(*m)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) SetPlacementPolicy(v *netmap.PlacementPolicy) {
|
func (c *Container) SetPlacementPolicy(v *netmap.PlacementPolicy) {
|
||||||
c.v2.SetPlacementPolicy(v.ToV2())
|
var m *v2netmap.PlacementPolicy
|
||||||
|
|
||||||
|
if v != nil {
|
||||||
|
m = new(v2netmap.PlacementPolicy)
|
||||||
|
v.WriteToV2(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.v2.SetPlacementPolicy(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionToken returns token of the session within
|
// SessionToken returns token of the session within
|
||||||
|
|
|
@ -29,7 +29,7 @@ func TestNewContainer(t *testing.T) {
|
||||||
attrs := containertest.Attributes()
|
attrs := containertest.Attributes()
|
||||||
c.SetAttributes(attrs)
|
c.SetAttributes(attrs)
|
||||||
|
|
||||||
c.SetPlacementPolicy(policy)
|
c.SetPlacementPolicy(&policy)
|
||||||
c.SetNonceUUID(nonce)
|
c.SetNonceUUID(nonce)
|
||||||
c.SetOwnerID(ownerID)
|
c.SetOwnerID(ownerID)
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ func TestNewContainer(t *testing.T) {
|
||||||
v2 := c.ToV2()
|
v2 := c.ToV2()
|
||||||
newContainer := container.NewContainerFromV2(v2)
|
newContainer := container.NewContainerFromV2(v2)
|
||||||
|
|
||||||
require.EqualValues(t, newContainer.PlacementPolicy(), policy)
|
require.EqualValues(t, newContainer.PlacementPolicy(), &policy)
|
||||||
require.EqualValues(t, newContainer.Attributes(), attrs)
|
require.EqualValues(t, newContainer.Attributes(), attrs)
|
||||||
require.EqualValues(t, newContainer.BasicACL(), acl.PublicBasicRule)
|
require.EqualValues(t, newContainer.BasicACL(), acl.PublicBasicRule)
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,8 @@ func Container() *container.Container {
|
||||||
x.SetAttributes(Attributes())
|
x.SetAttributes(Attributes())
|
||||||
x.SetOwnerID(usertest.ID())
|
x.SetOwnerID(usertest.ID())
|
||||||
x.SetBasicACL(123)
|
x.SetBasicACL(123)
|
||||||
x.SetPlacementPolicy(netmaptest.PlacementPolicy())
|
p := netmaptest.PlacementPolicy()
|
||||||
|
x.SetPlacementPolicy(&p)
|
||||||
|
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ var (
|
||||||
// capacity and price.
|
// capacity and price.
|
||||||
func newWeightFunc(capNorm, priceNorm normalizer) weightFunc {
|
func newWeightFunc(capNorm, priceNorm normalizer) weightFunc {
|
||||||
return func(n NodeInfo) float64 {
|
return func(n NodeInfo) float64 {
|
||||||
return capNorm.Normalize(float64(n.capacity())) * priceNorm.Normalize(float64(n.price()))
|
return capNorm.Normalize(float64(n.capacity())) * priceNorm.Normalize(float64(n.Price()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
package netmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Clause is an enumeration of selector modifiers
|
|
||||||
// that shows how the node set will be formed.
|
|
||||||
type Clause uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
ClauseUnspecified Clause = iota
|
|
||||||
|
|
||||||
// ClauseSame is a selector modifier to select only nodes having the same value of bucket attribute.
|
|
||||||
ClauseSame
|
|
||||||
|
|
||||||
// ClauseDistinct is a selector modifier to select nodes having different values of bucket attribute.
|
|
||||||
ClauseDistinct
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClauseFromV2 converts v2 Clause to Clause.
|
|
||||||
func ClauseFromV2(c netmap.Clause) Clause {
|
|
||||||
switch c {
|
|
||||||
default:
|
|
||||||
return ClauseUnspecified
|
|
||||||
case netmap.Same:
|
|
||||||
return ClauseSame
|
|
||||||
case netmap.Distinct:
|
|
||||||
return ClauseDistinct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts Clause to v2 Clause.
|
|
||||||
func (c Clause) ToV2() netmap.Clause {
|
|
||||||
switch c {
|
|
||||||
default:
|
|
||||||
return netmap.UnspecifiedClause
|
|
||||||
case ClauseDistinct:
|
|
||||||
return netmap.Distinct
|
|
||||||
case ClauseSame:
|
|
||||||
return netmap.Same
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of Clause.
|
|
||||||
//
|
|
||||||
// String mapping:
|
|
||||||
// * ClauseDistinct: DISTINCT;
|
|
||||||
// * ClauseSame: SAME;
|
|
||||||
// * ClauseUnspecified, default: CLAUSE_UNSPECIFIED.
|
|
||||||
func (c Clause) String() string {
|
|
||||||
return c.ToV2().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromString parses Clause from a string representation.
|
|
||||||
// It is a reverse action to String().
|
|
||||||
//
|
|
||||||
// Returns true if s was parsed successfully.
|
|
||||||
func (c *Clause) FromString(s string) bool {
|
|
||||||
var g netmap.Clause
|
|
||||||
|
|
||||||
ok := g.FromString(s)
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
*c = ClauseFromV2(g)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package netmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClauseFromV2(t *testing.T) {
|
|
||||||
for _, item := range []struct {
|
|
||||||
c Clause
|
|
||||||
cV2 netmap.Clause
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
c: ClauseUnspecified,
|
|
||||||
cV2: netmap.UnspecifiedClause,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
c: ClauseSame,
|
|
||||||
cV2: netmap.Same,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
c: ClauseDistinct,
|
|
||||||
cV2: netmap.Distinct,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
require.Equal(t, item.c, ClauseFromV2(item.cV2))
|
|
||||||
require.Equal(t, item.cV2, item.c.ToV2())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClause_String(t *testing.T) {
|
|
||||||
toPtr := func(v Clause) *Clause {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
testEnumStrings(t, new(Clause), []enumStringItem{
|
|
||||||
{val: toPtr(ClauseDistinct), str: "DISTINCT"},
|
|
||||||
{val: toPtr(ClauseSame), str: "SAME"},
|
|
||||||
{val: toPtr(ClauseUnspecified), str: "CLAUSE_UNSPECIFIED"},
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -4,88 +4,85 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/nspcc-dev/hrw"
|
"github.com/nspcc-dev/hrw"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// context contains references to named filters and cached numeric values.
|
// context of a placement build process.
|
||||||
type context struct {
|
type context struct {
|
||||||
// Netmap is a netmap structure to operate on.
|
// network map to operate on
|
||||||
Netmap *Netmap
|
netMap NetMap
|
||||||
// Filters stores processed filters.
|
|
||||||
Filters map[string]*Filter
|
|
||||||
// Selectors stores processed selectors.
|
|
||||||
Selectors map[string]*Selector
|
|
||||||
// Selections stores result of selector processing.
|
|
||||||
Selections map[string][]nodes
|
|
||||||
|
|
||||||
// numCache stores parsed numeric values.
|
// cache of processed filters
|
||||||
|
processedFilters map[string]*netmap.Filter
|
||||||
|
|
||||||
|
// cache of processed selectors
|
||||||
|
processedSelectors map[string]*netmap.Selector
|
||||||
|
|
||||||
|
// stores results of selector processing
|
||||||
|
selections map[string][]nodes
|
||||||
|
|
||||||
|
// cache of parsed numeric values
|
||||||
numCache map[string]uint64
|
numCache map[string]uint64
|
||||||
// pivot is a seed for HRW.
|
|
||||||
pivot []byte
|
hrwSeed []byte
|
||||||
// pivotHash is a saved HRW hash of pivot
|
|
||||||
pivotHash uint64
|
// hrw.Hash of hrwSeed
|
||||||
// aggregator is returns aggregator determining bucket weight.
|
hrwSeedHash uint64
|
||||||
// By default it returns mean value from IQR interval.
|
|
||||||
aggregator func() aggregator
|
// weightFunc is a weighting function for determining node priority
|
||||||
// weightFunc is a weighting function for determining node priority.
|
// which combines low price and high performance
|
||||||
// By default in combines favours low price and high capacity.
|
|
||||||
weightFunc weightFunc
|
weightFunc weightFunc
|
||||||
// container backup factor is a factor for selector counters that expand
|
|
||||||
// amount of chosen nodes.
|
// container backup factor
|
||||||
cbf uint32
|
cbf uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Various validation errors.
|
// Various validation errors.
|
||||||
var (
|
var (
|
||||||
ErrMissingField = errors.New("netmap: nil field")
|
errInvalidFilterName = errors.New("filter name is invalid")
|
||||||
ErrInvalidFilterName = errors.New("netmap: filter name is invalid")
|
errInvalidNumber = errors.New("invalid number")
|
||||||
ErrInvalidNumber = errors.New("netmap: number value expected")
|
errInvalidFilterOp = errors.New("invalid filter operation")
|
||||||
ErrInvalidFilterOp = errors.New("netmap: invalid filter operation")
|
errFilterNotFound = errors.New("filter not found")
|
||||||
ErrFilterNotFound = errors.New("netmap: filter not found")
|
errNonEmptyFilters = errors.New("simple filter contains sub-filters")
|
||||||
ErrNonEmptyFilters = errors.New("netmap: simple filter must no contain sub-filters")
|
errNotEnoughNodes = errors.New("not enough nodes to SELECT from")
|
||||||
ErrNotEnoughNodes = errors.New("netmap: not enough nodes to SELECT from")
|
errUnnamedTopFilter = errors.New("unnamed top-level filter")
|
||||||
ErrSelectorNotFound = errors.New("netmap: selector not found")
|
|
||||||
ErrUnnamedTopFilter = errors.New("netmap: all filters on top level must be named")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// newContext creates new context. It contains various caches.
|
// newContext returns initialized context.
|
||||||
// In future it may create hierarchical netmap structure to work with.
|
func newContext(nm NetMap) *context {
|
||||||
func newContext(nm *Netmap) *context {
|
|
||||||
return &context{
|
return &context{
|
||||||
Netmap: nm,
|
netMap: nm,
|
||||||
Filters: make(map[string]*Filter),
|
processedFilters: make(map[string]*netmap.Filter),
|
||||||
Selectors: make(map[string]*Selector),
|
processedSelectors: make(map[string]*netmap.Selector),
|
||||||
Selections: make(map[string][]nodes),
|
selections: make(map[string][]nodes),
|
||||||
|
|
||||||
numCache: make(map[string]uint64),
|
numCache: make(map[string]uint64),
|
||||||
aggregator: newMeanIQRAgg,
|
|
||||||
weightFunc: defaultWeightFunc(nm.nodes),
|
weightFunc: defaultWeightFunc(nm.nodes),
|
||||||
cbf: defaultCBF,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) setPivot(pivot []byte) {
|
func (c *context) setPivot(pivot []byte) {
|
||||||
if len(pivot) != 0 {
|
if len(pivot) != 0 {
|
||||||
c.pivot = pivot
|
c.hrwSeed = pivot
|
||||||
c.pivotHash = hrw.Hash(pivot)
|
c.hrwSeedHash = hrw.Hash(pivot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) setCBF(cbf uint32) {
|
func (c *context) setCBF(cbf uint32) {
|
||||||
if cbf == 0 {
|
if cbf == 0 {
|
||||||
c.cbf = defaultCBF
|
c.cbf = 3
|
||||||
} else {
|
} else {
|
||||||
c.cbf = cbf
|
c.cbf = cbf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultWeightFunc returns default weighting function.
|
|
||||||
func defaultWeightFunc(ns nodes) weightFunc {
|
func defaultWeightFunc(ns nodes) weightFunc {
|
||||||
mean := newMeanAgg()
|
mean := newMeanAgg()
|
||||||
min := newMinAgg()
|
min := newMinAgg()
|
||||||
|
|
||||||
for i := range ns {
|
for i := range ns {
|
||||||
mean.Add(float64(ns[i].capacity()))
|
mean.Add(float64(ns[i].capacity()))
|
||||||
min.Add(float64(ns[i].price()))
|
min.Add(float64(ns[i].Price()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return newWeightFunc(
|
return newWeightFunc(
|
||||||
|
|
|
@ -1,11 +1,42 @@
|
||||||
/*
|
/*
|
||||||
Package netmap provides routines for working with netmap and placement policy.
|
Package netmap provides functionality for working with information about the
|
||||||
Work is done in 4 steps:
|
NeoFS network, primarily a layer of storage nodes.
|
||||||
1. Create context containing results shared between steps.
|
|
||||||
2. Processing filters.
|
The package concentrates all the characteristics of NeoFS networks.
|
||||||
3. Processing selectors.
|
|
||||||
4. Processing replicas.
|
NetMap represents NeoFS network map - one of the main technologies used to
|
||||||
|
store data in the system. It is composed of information about all storage nodes
|
||||||
|
(NodeInfo type) in a particular network. NetMap methods allow you to impose
|
||||||
|
container storage policies (PlacementPolicy type) on a fixed composition of
|
||||||
|
nodes for selecting nodes corresponding to the placement rules chosen by the
|
||||||
|
container creator.
|
||||||
|
|
||||||
|
NetworkInfo type is dedicated to descriptive characterization of network state
|
||||||
|
and settings.
|
||||||
|
|
||||||
|
Instances can be also used to process NeoFS API V2 protocol messages
|
||||||
|
(see neo.fs.v2.netmap package in https://github.com/nspcc-dev/neofs-api).
|
||||||
|
|
||||||
|
On client side:
|
||||||
|
import "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
|
|
||||||
|
var msg netmap.NodeInfo
|
||||||
|
msg.WriteToV2(&msg)
|
||||||
|
|
||||||
|
// send msg
|
||||||
|
|
||||||
|
On server side:
|
||||||
|
// recv msg
|
||||||
|
|
||||||
|
var info netmap.NodeInfo
|
||||||
|
|
||||||
|
err := info.ReadFromV2(msg)
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// process dec
|
||||||
|
|
||||||
|
Using package types in an application is recommended to potentially work with
|
||||||
|
different protocol versions with which these types are compatible.
|
||||||
|
|
||||||
Each step depends only on previous ones.
|
|
||||||
*/
|
*/
|
||||||
package netmap
|
package netmap
|
||||||
|
|
257
netmap/filter.go
257
netmap/filter.go
|
@ -7,77 +7,68 @@ import (
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Filter represents v2-compatible netmap filter.
|
// mainFilterName is a name of the filter
|
||||||
type Filter netmap.Filter
|
|
||||||
|
|
||||||
// MainFilterName is a name of the filter
|
|
||||||
// which points to the whole netmap.
|
// which points to the whole netmap.
|
||||||
const MainFilterName = "*"
|
const mainFilterName = "*"
|
||||||
|
|
||||||
// applyFilter applies named filter to b.
|
|
||||||
func (c *context) applyFilter(name string, b NodeInfo) bool {
|
|
||||||
return name == MainFilterName || c.match(c.Filters[name], b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// processFilters processes filters and returns error is any of them is invalid.
|
// processFilters processes filters and returns error is any of them is invalid.
|
||||||
func (c *context) processFilters(p *PlacementPolicy) error {
|
func (c *context) processFilters(p PlacementPolicy) error {
|
||||||
filters := p.Filters()
|
for i := range p.filters {
|
||||||
for i := range filters {
|
if err := c.processFilter(p.filters[i], true); err != nil {
|
||||||
if err := c.processFilter(&filters[i], true); err != nil {
|
return fmt.Errorf("process filter #%d (%s): %w", i, p.filters[i].GetName(), err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) processFilter(f *Filter, top bool) error {
|
func (c *context) processFilter(f netmap.Filter, top bool) error {
|
||||||
if f == nil {
|
fName := f.GetName()
|
||||||
return fmt.Errorf("%w: FILTER", ErrMissingField)
|
if fName == mainFilterName {
|
||||||
|
return fmt.Errorf("%w: '%s' is reserved", errInvalidFilterName, mainFilterName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Name() == MainFilterName {
|
if top && fName == "" {
|
||||||
return fmt.Errorf("%w: '*' is reserved", ErrInvalidFilterName)
|
return errUnnamedTopFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if top && f.Name() == "" {
|
if !top && fName != "" && c.processedFilters[fName] == nil {
|
||||||
return ErrUnnamedTopFilter
|
return errFilterNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if !top && f.Name() != "" && c.Filters[f.Name()] == nil {
|
inner := f.GetFilters()
|
||||||
return fmt.Errorf("%w: '%s'", ErrFilterNotFound, f.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
switch f.Operation() {
|
switch op := f.GetOp(); op {
|
||||||
case OpAND, OpOR:
|
case netmap.AND, netmap.OR:
|
||||||
for _, flt := range f.InnerFilters() {
|
for i := range inner {
|
||||||
if err := c.processFilter(&flt, false); err != nil {
|
if err := c.processFilter(inner[i], false); err != nil {
|
||||||
return err
|
return fmt.Errorf("process inner filter #%d: %w", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if len(f.InnerFilters()) != 0 {
|
if len(inner) != 0 {
|
||||||
return ErrNonEmptyFilters
|
return errNonEmptyFilters
|
||||||
} else if !top && f.Name() != "" { // named reference
|
} else if !top && fName != "" { // named reference
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch f.Operation() {
|
switch op {
|
||||||
case OpEQ, OpNE:
|
case netmap.EQ, netmap.NE:
|
||||||
case OpGT, OpGE, OpLT, OpLE:
|
case netmap.GT, netmap.GE, netmap.LT, netmap.LE:
|
||||||
n, err := strconv.ParseUint(f.Value(), 10, 64)
|
val := f.GetValue()
|
||||||
|
n, err := strconv.ParseUint(val, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: '%s'", ErrInvalidNumber, f.Value())
|
return fmt.Errorf("%w: '%s'", errInvalidNumber, f.GetValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
c.numCache[f.Value()] = n
|
c.numCache[val] = n
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: %s", ErrInvalidFilterOp, f.Operation())
|
return fmt.Errorf("%w: %s", errInvalidFilterOp, op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if top {
|
if top {
|
||||||
c.Filters[f.Name()] = f
|
c.processedFilters[fName] = &f
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -86,44 +77,46 @@ func (c *context) processFilter(f *Filter, top bool) error {
|
||||||
// match matches f against b. It returns no errors because
|
// match matches f against b. It returns no errors because
|
||||||
// filter should have been parsed during context creation
|
// filter should have been parsed during context creation
|
||||||
// and missing node properties are considered as a regular fail.
|
// and missing node properties are considered as a regular fail.
|
||||||
func (c *context) match(f *Filter, b NodeInfo) bool {
|
func (c *context) match(f *netmap.Filter, b NodeInfo) bool {
|
||||||
switch f.Operation() {
|
switch f.GetOp() {
|
||||||
case OpAND, OpOR:
|
case netmap.AND, netmap.OR:
|
||||||
for _, lf := range f.InnerFilters() {
|
inner := f.GetFilters()
|
||||||
if lf.Name() != "" {
|
for i := range inner {
|
||||||
lf = *c.Filters[lf.Name()]
|
fSub := &inner[i]
|
||||||
|
if name := inner[i].GetName(); name != "" {
|
||||||
|
fSub = c.processedFilters[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := c.match(&lf, b)
|
ok := c.match(fSub, b)
|
||||||
if ok == (f.Operation() == OpOR) {
|
if ok == (f.GetOp() == netmap.OR) {
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Operation() == OpAND
|
return f.GetOp() == netmap.AND
|
||||||
default:
|
default:
|
||||||
return c.matchKeyValue(f, b)
|
return c.matchKeyValue(f, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) matchKeyValue(f *Filter, b NodeInfo) bool {
|
func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool {
|
||||||
switch f.Operation() {
|
switch op := f.GetOp(); op {
|
||||||
case OpEQ:
|
case netmap.EQ:
|
||||||
return b.attribute(f.Key()) == f.Value()
|
return b.Attribute(f.GetKey()) == f.GetValue()
|
||||||
case OpNE:
|
case netmap.NE:
|
||||||
return b.attribute(f.Key()) != f.Value()
|
return b.Attribute(f.GetKey()) != f.GetValue()
|
||||||
default:
|
default:
|
||||||
var attr uint64
|
var attr uint64
|
||||||
|
|
||||||
switch f.Key() {
|
switch f.GetKey() {
|
||||||
case AttrPrice:
|
case attrPrice:
|
||||||
attr = b.price()
|
attr = b.Price()
|
||||||
case AttrCapacity:
|
case attrCapacity:
|
||||||
attr = b.capacity()
|
attr = b.capacity()
|
||||||
default:
|
default:
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
attr, err = strconv.ParseUint(b.attribute(f.Key()), 10, 64)
|
attr, err = strconv.ParseUint(b.Attribute(f.GetKey()), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Note: because filters are somewhat independent from nodes attributes,
|
// Note: because filters are somewhat independent from nodes attributes,
|
||||||
// We don't report an error here, and fail filter instead.
|
// We don't report an error here, and fail filter instead.
|
||||||
|
@ -131,15 +124,15 @@ func (c *context) matchKeyValue(f *Filter, b NodeInfo) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch f.Operation() {
|
switch op {
|
||||||
case OpGT:
|
case netmap.GT:
|
||||||
return attr > c.numCache[f.Value()]
|
return attr > c.numCache[f.GetValue()]
|
||||||
case OpGE:
|
case netmap.GE:
|
||||||
return attr >= c.numCache[f.Value()]
|
return attr >= c.numCache[f.GetValue()]
|
||||||
case OpLT:
|
case netmap.LT:
|
||||||
return attr < c.numCache[f.Value()]
|
return attr < c.numCache[f.GetValue()]
|
||||||
case OpLE:
|
case netmap.LE:
|
||||||
return attr <= c.numCache[f.Value()]
|
return attr <= c.numCache[f.GetValue()]
|
||||||
default:
|
default:
|
||||||
// do nothing and return false
|
// do nothing and return false
|
||||||
}
|
}
|
||||||
|
@ -147,127 +140,3 @@ func (c *context) matchKeyValue(f *Filter, b NodeInfo) bool {
|
||||||
// will not happen if context was created from f (maybe panic?)
|
// will not happen if context was created from f (maybe panic?)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFilter creates and returns new Filter instance.
|
|
||||||
//
|
|
||||||
// Defaults:
|
|
||||||
// - name: "";
|
|
||||||
// - key: "";
|
|
||||||
// - value: "";
|
|
||||||
// - operation: 0;
|
|
||||||
// - filters: nil.
|
|
||||||
func NewFilter() *Filter {
|
|
||||||
return NewFilterFromV2(new(netmap.Filter))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFilterFromV2 converts v2 Filter to Filter.
|
|
||||||
//
|
|
||||||
// Nil netmap.Filter converts to nil.
|
|
||||||
func NewFilterFromV2(f *netmap.Filter) *Filter {
|
|
||||||
return (*Filter)(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts Filter to v2 Filter.
|
|
||||||
//
|
|
||||||
// Nil Filter converts to nil.
|
|
||||||
func (f *Filter) ToV2() *netmap.Filter {
|
|
||||||
return (*netmap.Filter)(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key returns key to filter.
|
|
||||||
func (f *Filter) Key() string {
|
|
||||||
return (*netmap.Filter)(f).GetKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKey sets key to filter.
|
|
||||||
func (f *Filter) SetKey(key string) {
|
|
||||||
(*netmap.Filter)(f).SetKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns value to match.
|
|
||||||
func (f *Filter) Value() string {
|
|
||||||
return (*netmap.Filter)(f).GetValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetValue sets value to match.
|
|
||||||
func (f *Filter) SetValue(val string) {
|
|
||||||
(*netmap.Filter)(f).SetValue(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns filter name.
|
|
||||||
func (f *Filter) Name() string {
|
|
||||||
return (*netmap.Filter)(f).GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetName sets filter name.
|
|
||||||
func (f *Filter) SetName(name string) {
|
|
||||||
(*netmap.Filter)(f).SetName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operation returns filtering operation.
|
|
||||||
func (f *Filter) Operation() Operation {
|
|
||||||
return OperationFromV2(
|
|
||||||
(*netmap.Filter)(f).GetOp())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOperation sets filtering operation.
|
|
||||||
func (f *Filter) SetOperation(op Operation) {
|
|
||||||
(*netmap.Filter)(f).SetOp(op.ToV2())
|
|
||||||
}
|
|
||||||
|
|
||||||
func filtersFromV2(fs []netmap.Filter) []Filter {
|
|
||||||
if fs == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res := make([]Filter, len(fs))
|
|
||||||
|
|
||||||
for i := range fs {
|
|
||||||
res[i] = *NewFilterFromV2(&fs[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// InnerFilters returns list of inner filters.
|
|
||||||
func (f *Filter) InnerFilters() []Filter {
|
|
||||||
return filtersFromV2((*netmap.Filter)(f).GetFilters())
|
|
||||||
}
|
|
||||||
|
|
||||||
func filtersToV2(fs []Filter) (fsV2 []netmap.Filter) {
|
|
||||||
if fs != nil {
|
|
||||||
fsV2 = make([]netmap.Filter, len(fs))
|
|
||||||
|
|
||||||
for i := range fs {
|
|
||||||
fsV2[i] = *fs[i].ToV2()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetInnerFilters sets list of inner filters.
|
|
||||||
func (f *Filter) SetInnerFilters(fs ...Filter) {
|
|
||||||
(*netmap.Filter)(f).
|
|
||||||
SetFilters(filtersToV2(fs))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals Filter into a protobuf binary form.
|
|
||||||
func (f *Filter) Marshal() ([]byte, error) {
|
|
||||||
return (*netmap.Filter)(f).StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of Filter.
|
|
||||||
func (f *Filter) Unmarshal(data []byte) error {
|
|
||||||
return (*netmap.Filter)(f).Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes Filter to protobuf JSON format.
|
|
||||||
func (f *Filter) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*netmap.Filter)(f).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes Filter from protobuf JSON format.
|
|
||||||
func (f *Filter) UnmarshalJSON(data []byte) error {
|
|
||||||
return (*netmap.Filter)(f).UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,24 +10,24 @@ import (
|
||||||
|
|
||||||
func TestContext_ProcessFilters(t *testing.T) {
|
func TestContext_ProcessFilters(t *testing.T) {
|
||||||
fs := []Filter{
|
fs := []Filter{
|
||||||
newFilter("StorageSSD", "Storage", "SSD", OpEQ),
|
newFilter("StorageSSD", "Storage", "SSD", netmap.EQ),
|
||||||
newFilter("GoodRating", "Rating", "4", OpGE),
|
newFilter("GoodRating", "Rating", "4", netmap.GE),
|
||||||
newFilter("Main", "", "", OpAND,
|
newFilter("Main", "", "", netmap.AND,
|
||||||
newFilter("StorageSSD", "", "", 0),
|
newFilter("StorageSSD", "", "", 0),
|
||||||
newFilter("", "IntField", "123", OpLT),
|
newFilter("", "IntField", "123", netmap.LT),
|
||||||
newFilter("GoodRating", "", "", 0)),
|
newFilter("GoodRating", "", "", 0)),
|
||||||
}
|
}
|
||||||
|
|
||||||
c := newContext(new(Netmap))
|
c := newContext(NetMap{})
|
||||||
p := newPlacementPolicy(1, nil, nil, fs)
|
p := newPlacementPolicy(1, nil, nil, fs)
|
||||||
require.NoError(t, c.processFilters(p))
|
require.NoError(t, c.processFilters(p))
|
||||||
require.Equal(t, 3, len(c.Filters))
|
require.Equal(t, 3, len(c.processedFilters))
|
||||||
for _, f := range fs {
|
for _, f := range fs {
|
||||||
require.Equal(t, f, *c.Filters[f.Name()])
|
require.Equal(t, f.m, *c.processedFilters[f.m.GetName()])
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(t, uint64(4), c.numCache[fs[1].Value()])
|
require.Equal(t, uint64(4), c.numCache[fs[1].m.GetValue()])
|
||||||
require.Equal(t, uint64(123), c.numCache[fs[2].InnerFilters()[1].Value()])
|
require.Equal(t, uint64(123), c.numCache[fs[2].m.GetFilters()[1].GetValue()])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext_ProcessFiltersInvalid(t *testing.T) {
|
func TestContext_ProcessFiltersInvalid(t *testing.T) {
|
||||||
|
@ -38,40 +38,40 @@ func TestContext_ProcessFiltersInvalid(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"UnnamedTop",
|
"UnnamedTop",
|
||||||
newFilter("", "Storage", "SSD", OpEQ),
|
newFilter("", "Storage", "SSD", netmap.EQ),
|
||||||
ErrUnnamedTopFilter,
|
errUnnamedTopFilter,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"InvalidReference",
|
"InvalidReference",
|
||||||
newFilter("Main", "", "", OpAND,
|
newFilter("Main", "", "", netmap.AND,
|
||||||
newFilter("StorageSSD", "", "", 0)),
|
newFilter("StorageSSD", "", "", 0)),
|
||||||
ErrFilterNotFound,
|
errFilterNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NonEmptyKeyed",
|
"NonEmptyKeyed",
|
||||||
newFilter("Main", "Storage", "SSD", OpEQ,
|
newFilter("Main", "Storage", "SSD", netmap.EQ,
|
||||||
newFilter("StorageSSD", "", "", 0)),
|
newFilter("StorageSSD", "", "", 0)),
|
||||||
ErrNonEmptyFilters,
|
errNonEmptyFilters,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"InvalidNumber",
|
"InvalidNumber",
|
||||||
newFilter("Main", "Rating", "three", OpGE),
|
newFilter("Main", "Rating", "three", netmap.GE),
|
||||||
ErrInvalidNumber,
|
errInvalidNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"InvalidOp",
|
"InvalidOp",
|
||||||
newFilter("Main", "Rating", "3", 0),
|
newFilter("Main", "Rating", "3", 0),
|
||||||
ErrInvalidFilterOp,
|
errInvalidFilterOp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"InvalidName",
|
"InvalidName",
|
||||||
newFilter("*", "Rating", "3", OpGE),
|
newFilter("*", "Rating", "3", netmap.GE),
|
||||||
ErrInvalidFilterName,
|
errInvalidFilterName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range errTestCases {
|
for _, tc := range errTestCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
c := newContext(new(Netmap))
|
c := newContext(NetMap{})
|
||||||
p := newPlacementPolicy(1, nil, nil, []Filter{tc.filter})
|
p := newPlacementPolicy(1, nil, nil, []Filter{tc.filter})
|
||||||
err := c.processFilters(p)
|
err := c.processFilters(p)
|
||||||
require.True(t, errors.Is(err, tc.err), "got: %v", err)
|
require.True(t, errors.Is(err, tc.err), "got: %v", err)
|
||||||
|
@ -80,151 +80,16 @@ func TestContext_ProcessFiltersInvalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilter_MatchSimple_InvalidOp(t *testing.T) {
|
func TestFilter_MatchSimple_InvalidOp(t *testing.T) {
|
||||||
var aRating NodeAttribute
|
|
||||||
aRating.SetKey("Rating")
|
|
||||||
aRating.SetValue("4")
|
|
||||||
|
|
||||||
var aCountry NodeAttribute
|
|
||||||
aRating.SetKey("Country")
|
|
||||||
aRating.SetValue("Germany")
|
|
||||||
|
|
||||||
var b NodeInfo
|
var b NodeInfo
|
||||||
b.SetAttributes(aRating, aCountry)
|
b.SetAttribute("Rating", "4")
|
||||||
|
b.SetAttribute("Country", "Germany")
|
||||||
|
|
||||||
f := newFilter("Main", "Rating", "5", OpEQ)
|
f := newFilter("Main", "Rating", "5", netmap.EQ)
|
||||||
c := newContext(new(Netmap))
|
c := newContext(NetMap{})
|
||||||
p := newPlacementPolicy(1, nil, nil, []Filter{f})
|
p := newPlacementPolicy(1, nil, nil, []Filter{f})
|
||||||
require.NoError(t, c.processFilters(p))
|
require.NoError(t, c.processFilters(p))
|
||||||
|
|
||||||
// just for the coverage
|
// just for the coverage
|
||||||
f.SetOperation(0)
|
f.m.SetOp(0)
|
||||||
require.False(t, c.match(&f, b))
|
require.False(t, c.match(&f.m, b))
|
||||||
}
|
|
||||||
|
|
||||||
func testFilter() *Filter {
|
|
||||||
f := NewFilter()
|
|
||||||
f.SetOperation(OpGE)
|
|
||||||
f.SetName("name")
|
|
||||||
f.SetKey("key")
|
|
||||||
f.SetValue("value")
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilterFromV2(t *testing.T) {
|
|
||||||
t.Run("nil from V2", func(t *testing.T) {
|
|
||||||
var x *netmap.Filter
|
|
||||||
|
|
||||||
require.Nil(t, NewFilterFromV2(x))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nil to V2", func(t *testing.T) {
|
|
||||||
var x *Filter
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
|
|
||||||
fV2 := new(netmap.Filter)
|
|
||||||
fV2.SetOp(netmap.GE)
|
|
||||||
fV2.SetName("name")
|
|
||||||
fV2.SetKey("key")
|
|
||||||
fV2.SetValue("value")
|
|
||||||
|
|
||||||
f := NewFilterFromV2(fV2)
|
|
||||||
|
|
||||||
require.Equal(t, fV2, f.ToV2())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilter_Key(t *testing.T) {
|
|
||||||
f := NewFilter()
|
|
||||||
key := "some key"
|
|
||||||
|
|
||||||
f.SetKey(key)
|
|
||||||
|
|
||||||
require.Equal(t, key, f.Key())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilter_Value(t *testing.T) {
|
|
||||||
f := NewFilter()
|
|
||||||
val := "some value"
|
|
||||||
|
|
||||||
f.SetValue(val)
|
|
||||||
|
|
||||||
require.Equal(t, val, f.Value())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilter_Name(t *testing.T) {
|
|
||||||
f := NewFilter()
|
|
||||||
name := "some name"
|
|
||||||
|
|
||||||
f.SetName(name)
|
|
||||||
|
|
||||||
require.Equal(t, name, f.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilter_Operation(t *testing.T) {
|
|
||||||
f := NewFilter()
|
|
||||||
op := OpGE
|
|
||||||
|
|
||||||
f.SetOperation(op)
|
|
||||||
|
|
||||||
require.Equal(t, op, f.Operation())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilter_InnerFilters(t *testing.T) {
|
|
||||||
f := NewFilter()
|
|
||||||
|
|
||||||
f1, f2 := *testFilter(), *testFilter()
|
|
||||||
|
|
||||||
f.SetInnerFilters(f1, f2)
|
|
||||||
|
|
||||||
require.Equal(t, []Filter{f1, f2}, f.InnerFilters())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilterEncoding(t *testing.T) {
|
|
||||||
f := newFilter("name", "key", "value", OpEQ,
|
|
||||||
newFilter("name2", "key2", "value", OpOR),
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
|
||||||
data, err := f.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
f2 := *NewFilter()
|
|
||||||
require.NoError(t, f2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, f, f2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
|
||||||
data, err := f.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
f2 := *NewFilter()
|
|
||||||
require.NoError(t, f2.UnmarshalJSON(data))
|
|
||||||
|
|
||||||
require.Equal(t, f, f2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewFilter(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
filter := NewFilter()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Empty(t, filter.Name())
|
|
||||||
require.Empty(t, filter.Key())
|
|
||||||
require.Empty(t, filter.Value())
|
|
||||||
require.Zero(t, filter.Operation())
|
|
||||||
require.Nil(t, filter.InnerFilters())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
filterV2 := filter.ToV2()
|
|
||||||
|
|
||||||
require.Empty(t, filterV2.GetName())
|
|
||||||
require.Empty(t, filterV2.GetKey())
|
|
||||||
require.Empty(t, filterV2.GetValue())
|
|
||||||
require.Equal(t, netmap.UnspecifiedOperation, filterV2.GetOp())
|
|
||||||
require.Nil(t, filterV2.GetFilters())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +1,49 @@
|
||||||
package netmap
|
package netmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newFilter(name string, k, v string, op Operation, fs ...Filter) (f Filter) {
|
func newFilter(name string, k, v string, op netmap.Operation, fs ...Filter) (f Filter) {
|
||||||
f.SetName(name)
|
f.SetName(name)
|
||||||
f.SetKey(k)
|
f.m.SetKey(k)
|
||||||
f.SetOperation(op)
|
f.m.SetOp(op)
|
||||||
f.SetValue(v)
|
f.m.SetValue(v)
|
||||||
f.SetInnerFilters(fs...)
|
inner := make([]netmap.Filter, len(fs))
|
||||||
|
for i := range fs {
|
||||||
|
inner[i] = fs[i].m
|
||||||
|
}
|
||||||
|
f.m.SetFilters(inner)
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSelector(name string, attr string, c Clause, count uint32, filter string) (s Selector) {
|
func newSelector(name string, attr string, count uint32, filter string, clause func(*Selector)) (s Selector) {
|
||||||
s.SetName(name)
|
s.SetName(name)
|
||||||
s.SetAttribute(attr)
|
s.SelectByBucketAttribute(attr)
|
||||||
s.SetCount(count)
|
s.SetNodeAmount(count)
|
||||||
s.SetClause(c)
|
clause(&s)
|
||||||
s.SetFilter(filter)
|
s.SetFilterName(filter)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPlacementPolicy(bf uint32, rs []Replica, ss []Selector, fs []Filter) *PlacementPolicy {
|
func newPlacementPolicy(bf uint32, rs []ReplicaDescriptor, ss []Selector, fs []Filter) (p PlacementPolicy) {
|
||||||
p := NewPlacementPolicy()
|
|
||||||
p.SetContainerBackupFactor(bf)
|
p.SetContainerBackupFactor(bf)
|
||||||
p.SetReplicas(rs...)
|
p.AddReplicas(rs...)
|
||||||
p.SetSelectors(ss...)
|
p.AddSelectors(ss...)
|
||||||
p.SetFilters(fs...)
|
p.AddFilters(fs...)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReplica(c uint32, s string) (r Replica) {
|
func newReplica(c uint32, s string) (r ReplicaDescriptor) {
|
||||||
r.SetCount(c)
|
r.SetAmount(c)
|
||||||
r.SetSelector(s)
|
r.SetSelectorName(s)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeInfoFromAttributes(props ...string) NodeInfo {
|
func nodeInfoFromAttributes(props ...string) (n NodeInfo) {
|
||||||
attrs := make([]NodeAttribute, len(props)/2)
|
for i := 0; i < len(props); i += 2 {
|
||||||
for i := range attrs {
|
n.SetAttribute(props[i], props[i+1])
|
||||||
attrs[i].SetKey(props[i*2])
|
|
||||||
attrs[i].SetValue(props[i*2+1])
|
|
||||||
}
|
|
||||||
n := NewNodeInfo()
|
|
||||||
n.SetAttributes(attrs...)
|
|
||||||
return *n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type enumIface interface {
|
return
|
||||||
FromString(string) bool
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type enumStringItem struct {
|
|
||||||
val enumIface
|
|
||||||
str string
|
|
||||||
}
|
|
||||||
|
|
||||||
func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
|
|
||||||
for _, item := range items {
|
|
||||||
require.Equal(t, item.str, item.val.String())
|
|
||||||
|
|
||||||
s := item.val.String()
|
|
||||||
|
|
||||||
require.True(t, e.FromString(s), s)
|
|
||||||
|
|
||||||
require.EqualValues(t, item.val, e, item.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// incorrect strings
|
|
||||||
for _, str := range []string{
|
|
||||||
"some string",
|
|
||||||
"undefined",
|
|
||||||
} {
|
|
||||||
require.False(t, e.FromString(str))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ type TestCase struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _, _ json.Unmarshaler = new(NodeInfo), new(PlacementPolicy)
|
||||||
|
|
||||||
func compareNodes(t testing.TB, expected [][]int, nodes nodes, actual [][]NodeInfo) {
|
func compareNodes(t testing.TB, expected [][]int, nodes nodes, actual [][]NodeInfo) {
|
||||||
require.Equal(t, len(expected), len(actual))
|
require.Equal(t, len(expected), len(actual))
|
||||||
for i := range expected {
|
for i := range expected {
|
||||||
|
@ -56,12 +58,12 @@ func TestPlacementPolicy_Interopability(t *testing.T) {
|
||||||
copy(srcNodes, tc.Nodes)
|
copy(srcNodes, tc.Nodes)
|
||||||
|
|
||||||
t.Run(tc.Name, func(t *testing.T) {
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
var nm Netmap
|
var nm NetMap
|
||||||
nm.SetNodes(tc.Nodes)
|
nm.SetNodes(tc.Nodes)
|
||||||
|
|
||||||
for name, tt := range tc.Tests {
|
for name, tt := range tc.Tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
v, err := nm.GetContainerNodes(&tt.Policy, tt.Pivot)
|
v, err := nm.ContainerNodes(tt.Policy, tt.Pivot)
|
||||||
if tt.Result == nil {
|
if tt.Result == nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), tt.Error)
|
require.Contains(t, err.Error(), tt.Error)
|
||||||
|
@ -72,7 +74,7 @@ func TestPlacementPolicy_Interopability(t *testing.T) {
|
||||||
compareNodes(t, tt.Result, tc.Nodes, v)
|
compareNodes(t, tt.Result, tc.Nodes, v)
|
||||||
|
|
||||||
if tt.Placement.Result != nil {
|
if tt.Placement.Result != nil {
|
||||||
res, err := nm.GetPlacementVectors(v, tt.Placement.Pivot)
|
res, err := nm.PlacementVectors(v, tt.Placement.Pivot)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
compareNodes(t, tt.Placement.Result, tc.Nodes, res)
|
compareNodes(t, tt.Placement.Result, tc.Nodes, res)
|
||||||
require.Equal(t, srcNodes, tc.Nodes)
|
require.Equal(t, srcNodes, tc.Nodes)
|
||||||
|
@ -101,7 +103,7 @@ func BenchmarkPlacementPolicyInteropability(b *testing.B) {
|
||||||
require.NoError(b, json.Unmarshal(bs, &tc), "cannot unmarshal %s", ds[i].Name())
|
require.NoError(b, json.Unmarshal(bs, &tc), "cannot unmarshal %s", ds[i].Name())
|
||||||
|
|
||||||
b.Run(tc.Name, func(b *testing.B) {
|
b.Run(tc.Name, func(b *testing.B) {
|
||||||
var nm Netmap
|
var nm NetMap
|
||||||
nm.SetNodes(tc.Nodes)
|
nm.SetNodes(tc.Nodes)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
@ -111,7 +113,7 @@ func BenchmarkPlacementPolicyInteropability(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
v, err := nm.GetContainerNodes(&tt.Policy, tt.Pivot)
|
v, err := nm.ContainerNodes(tt.Policy, tt.Pivot)
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
if tt.Result == nil {
|
if tt.Result == nil {
|
||||||
require.Error(b, err)
|
require.Error(b, err)
|
||||||
|
@ -123,7 +125,7 @@ func BenchmarkPlacementPolicyInteropability(b *testing.B) {
|
||||||
|
|
||||||
if tt.Placement.Result != nil {
|
if tt.Placement.Result != nil {
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
res, err := nm.GetPlacementVectors(v, tt.Placement.Pivot)
|
res, err := nm.PlacementVectors(v, tt.Placement.Pivot)
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
compareNodes(b, tt.Placement.Result, tc.Nodes, res)
|
compareNodes(b, tt.Placement.Result, tc.Nodes, res)
|
||||||
|
@ -146,14 +148,14 @@ func BenchmarkManySelects(b *testing.B) {
|
||||||
tt, ok := tc.Tests["Select"]
|
tt, ok := tc.Tests["Select"]
|
||||||
require.True(b, ok)
|
require.True(b, ok)
|
||||||
|
|
||||||
var nm Netmap
|
var nm NetMap
|
||||||
nm.SetNodes(tc.Nodes)
|
nm.SetNodes(tc.Nodes)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err = nm.GetContainerNodes(&tt.Policy, tt.Pivot)
|
_, err = nm.ContainerNodes(tt.Policy, tt.Pivot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.FailNow()
|
b.FailNow()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{
|
||||||
"key": "IntegerField",
|
"key": "IntegerField",
|
||||||
"value": ""
|
"value": "true"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
158
netmap/netmap.go
158
netmap/netmap.go
|
@ -1,112 +1,40 @@
|
||||||
package netmap
|
package netmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/hrw"
|
"github.com/nspcc-dev/hrw"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultCBF = 3
|
// NetMap represents NeoFS network map. It includes information about all
|
||||||
|
// storage nodes registered in NeoFS the network.
|
||||||
var _, _ hrw.Hasher = NodeInfo{}, nodes{}
|
type NetMap struct {
|
||||||
|
|
||||||
// Hash implements hrw.Hasher interface.
|
|
||||||
//
|
|
||||||
// Hash is needed to support weighted HRW therefore sort function sorts nodes
|
|
||||||
// based on their public key.
|
|
||||||
func (i NodeInfo) Hash() uint64 {
|
|
||||||
return hrw.Hash(i.m.GetPublicKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i NodeInfo) less(i2 NodeInfo) bool {
|
|
||||||
return bytes.Compare(i.PublicKey(), i2.PublicKey()) < 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// attribute returns value of the node attribute by the given key. Returns empty
|
|
||||||
// string if attribute is missing.
|
|
||||||
//
|
|
||||||
// Method is needed to internal placement needs.
|
|
||||||
func (i NodeInfo) attribute(key string) string {
|
|
||||||
as := i.m.GetAttributes()
|
|
||||||
for j := range as {
|
|
||||||
if as[j].GetKey() == key {
|
|
||||||
return as[j].GetValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *NodeInfo) syncAttributes() {
|
|
||||||
as := i.m.GetAttributes()
|
|
||||||
for j := range as {
|
|
||||||
switch as[j].GetKey() {
|
|
||||||
case AttrPrice:
|
|
||||||
i.priceAttr, _ = strconv.ParseUint(as[j].GetValue(), 10, 64)
|
|
||||||
case AttrCapacity:
|
|
||||||
i.capAttr, _ = strconv.ParseUint(as[j].GetValue(), 10, 64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *NodeInfo) setPrice(price uint64) {
|
|
||||||
i.priceAttr = price
|
|
||||||
|
|
||||||
as := i.m.GetAttributes()
|
|
||||||
for j := range as {
|
|
||||||
if as[j].GetKey() == AttrPrice {
|
|
||||||
as[j].SetValue(strconv.FormatUint(i.capAttr, 10))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
as = append(as, netmap.Attribute{})
|
|
||||||
as[len(as)-1].SetKey(AttrPrice)
|
|
||||||
as[len(as)-1].SetValue(strconv.FormatUint(i.capAttr, 10))
|
|
||||||
|
|
||||||
i.m.SetAttributes(as)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *NodeInfo) price() uint64 {
|
|
||||||
return i.priceAttr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *NodeInfo) setCapacity(capacity uint64) {
|
|
||||||
i.capAttr = capacity
|
|
||||||
|
|
||||||
as := i.m.GetAttributes()
|
|
||||||
for j := range as {
|
|
||||||
if as[j].GetKey() == AttrCapacity {
|
|
||||||
as[j].SetValue(strconv.FormatUint(i.capAttr, 10))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
as = append(as, netmap.Attribute{})
|
|
||||||
as[len(as)-1].SetKey(AttrCapacity)
|
|
||||||
as[len(as)-1].SetValue(strconv.FormatUint(i.capAttr, 10))
|
|
||||||
|
|
||||||
i.m.SetAttributes(as)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i NodeInfo) capacity() uint64 {
|
|
||||||
return i.capAttr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Netmap represents netmap which contains preprocessed nodes.
|
|
||||||
type Netmap struct {
|
|
||||||
nodes []NodeInfo
|
nodes []NodeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Netmap) SetNodes(nodes []NodeInfo) {
|
// SetNodes sets information list about all storage nodes from the NeoFS network.
|
||||||
|
//
|
||||||
|
// Argument MUST NOT be mutated, make a copy first.
|
||||||
|
//
|
||||||
|
// See also Nodes.
|
||||||
|
func (m *NetMap) SetNodes(nodes []NodeInfo) {
|
||||||
m.nodes = nodes
|
m.nodes = nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nodes returns nodes set using SetNodes.
|
||||||
|
//
|
||||||
|
// Return value MUST not be mutated, make a copy first.
|
||||||
|
func (m NetMap) Nodes() []NodeInfo {
|
||||||
|
return m.nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodes is a slice of NodeInfo instances needed for HRW sorting.
|
||||||
type nodes []NodeInfo
|
type nodes []NodeInfo
|
||||||
|
|
||||||
|
// assert nodes type provides hrw.Hasher required for HRW sorting.
|
||||||
|
var _ hrw.Hasher = nodes{}
|
||||||
|
|
||||||
// Hash is a function from hrw.Hasher interface. It is implemented
|
// Hash is a function from hrw.Hasher interface. It is implemented
|
||||||
// to support weighted hrw sorting of buckets. Each bucket is already sorted by hrw,
|
// to support weighted hrw sorting of buckets. Each bucket is already sorted by hrw,
|
||||||
// thus giving us needed "randomness".
|
// thus giving us needed "randomness".
|
||||||
|
@ -143,8 +71,12 @@ func flattenNodes(ns []nodes) nodes {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlacementVectors returns placement vectors for an object given containerNodes cnt.
|
// PlacementVectors sorts container nodes returned by ContainerNodes method
|
||||||
func (m *Netmap) GetPlacementVectors(vectors [][]NodeInfo, pivot []byte) ([][]NodeInfo, error) {
|
// and returns placement vectors for the entity identified by the given pivot.
|
||||||
|
// For example,in order to build node list to store the object, binary-encoded
|
||||||
|
// object identifier can be used as pivot. Result is deterministic for
|
||||||
|
// the fixed NetMap and parameters.
|
||||||
|
func (m NetMap) PlacementVectors(vectors [][]NodeInfo, pivot []byte) ([][]NodeInfo, error) {
|
||||||
h := hrw.Hash(pivot)
|
h := hrw.Hash(pivot)
|
||||||
wf := defaultWeightFunc(m.nodes)
|
wf := defaultWeightFunc(m.nodes)
|
||||||
result := make([][]NodeInfo, len(vectors))
|
result := make([][]NodeInfo, len(vectors))
|
||||||
|
@ -158,13 +90,18 @@ func (m *Netmap) GetPlacementVectors(vectors [][]NodeInfo, pivot []byte) ([][]No
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainerNodes returns nodes corresponding to each replica.
|
// ContainerNodes returns two-dimensional list of nodes as a result of applying
|
||||||
// Order of returned nodes corresponds to order of replicas in p.
|
// given PlacementPolicy to the NetMap. Each line of the list corresponds to a
|
||||||
// pivot is a seed for HRW sorting.
|
// replica descriptor. Line order corresponds to order of ReplicaDescriptor list
|
||||||
func (m *Netmap) GetContainerNodes(p *PlacementPolicy, pivot []byte) ([][]NodeInfo, error) {
|
// in the policy. Nodes are pre-filtered according to the Filter list from
|
||||||
|
// the policy, and then selected by Selector list. Result is deterministic for
|
||||||
|
// the fixed NetMap and parameters.
|
||||||
|
//
|
||||||
|
// Result can be used in PlacementVectors.
|
||||||
|
func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, error) {
|
||||||
c := newContext(m)
|
c := newContext(m)
|
||||||
c.setPivot(pivot)
|
c.setPivot(pivot)
|
||||||
c.setCBF(p.ContainerBackupFactor())
|
c.setCBF(p.backupFactor)
|
||||||
|
|
||||||
if err := c.processFilters(p); err != nil {
|
if err := c.processFilters(p); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -174,14 +111,15 @@ func (m *Netmap) GetContainerNodes(p *PlacementPolicy, pivot []byte) ([][]NodeIn
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([][]NodeInfo, len(p.Replicas()))
|
result := make([][]NodeInfo, len(p.replicas))
|
||||||
|
|
||||||
for i, r := range p.Replicas() {
|
for i := range p.replicas {
|
||||||
if r.Selector() == "" {
|
sName := p.replicas[i].GetSelector()
|
||||||
if len(p.Selectors()) == 0 {
|
if sName == "" {
|
||||||
s := new(Selector)
|
if len(p.selectors) == 0 {
|
||||||
s.SetCount(r.Count())
|
var s netmap.Selector
|
||||||
s.SetFilter(MainFilterName)
|
s.SetCount(p.replicas[i].GetCount())
|
||||||
|
s.SetFilter(mainFilterName)
|
||||||
|
|
||||||
nodes, err := c.getSelection(p, s)
|
nodes, err := c.getSelection(p, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -191,16 +129,16 @@ func (m *Netmap) GetContainerNodes(p *PlacementPolicy, pivot []byte) ([][]NodeIn
|
||||||
result[i] = flattenNodes(nodes)
|
result[i] = flattenNodes(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range p.Selectors() {
|
for i := range p.selectors {
|
||||||
result[i] = append(result[i], flattenNodes(c.Selections[s.Name()])...)
|
result[i] = append(result[i], flattenNodes(c.selections[p.selectors[i].GetName()])...)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes, ok := c.Selections[r.Selector()]
|
nodes, ok := c.selections[sName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("%w: REPLICA '%s'", ErrSelectorNotFound, r.Selector())
|
return nil, fmt.Errorf("selector not found: REPLICA '%s'", sName)
|
||||||
}
|
}
|
||||||
|
|
||||||
result[i] = append(result[i], flattenNodes(nodes)...)
|
result[i] = append(result[i], flattenNodes(nodes)...)
|
||||||
|
|
|
@ -1,204 +1,462 @@
|
||||||
package netmap
|
package netmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NetworkInfo represents v2-compatible structure
|
// NetworkInfo groups information about the NeoFS network state. Mainly used to
|
||||||
// with information about NeoFS network.
|
// describe the current state of the network.
|
||||||
type NetworkInfo netmap.NetworkInfo
|
|
||||||
|
|
||||||
// NewNetworkInfoFromV2 wraps v2 NetworkInfo message to NetworkInfo.
|
|
||||||
//
|
//
|
||||||
// Nil netmap.NetworkInfo converts to nil.
|
// NetworkInfo is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/netmap.NetworkInfo
|
||||||
func NewNetworkInfoFromV2(iV2 *netmap.NetworkInfo) *NetworkInfo {
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
return (*NetworkInfo)(iV2)
|
//
|
||||||
|
// Instances can be created using built-in var declaration.
|
||||||
|
type NetworkInfo struct {
|
||||||
|
m netmap.NetworkInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNetworkInfo creates and initializes blank NetworkInfo.
|
// reads NetworkInfo from netmap.NetworkInfo message. If checkFieldPresence is set,
|
||||||
//
|
// returns an error on absence of any protocol-required field. Verifies format of any
|
||||||
// Defaults:
|
// presented field according to NeoFS API V2 protocol.
|
||||||
// - curEpoch: 0;
|
func (x *NetworkInfo) readFromV2(m netmap.NetworkInfo, checkFieldPresence bool) error {
|
||||||
// - magicNum: 0;
|
c := m.GetNetworkConfig()
|
||||||
// - msPerBlock: 0;
|
if checkFieldPresence && c == nil {
|
||||||
// - network config: nil.
|
return errors.New("missing network config")
|
||||||
func NewNetworkInfo() *NetworkInfo {
|
|
||||||
return NewNetworkInfoFromV2(new(netmap.NetworkInfo))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV2 converts NetworkInfo to v2 NetworkInfo.
|
if checkFieldPresence && c.NumberOfParameters() <= 0 {
|
||||||
//
|
return fmt.Errorf("missing network parameters")
|
||||||
// Nil NetworkInfo converts to nil.
|
|
||||||
func (i *NetworkInfo) ToV2() *netmap.NetworkInfo {
|
|
||||||
return (*netmap.NetworkInfo)(i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentEpoch returns current epoch of the NeoFS network.
|
var err error
|
||||||
func (i *NetworkInfo) CurrentEpoch() uint64 {
|
mNames := make(map[string]struct{}, c.NumberOfParameters())
|
||||||
return (*netmap.NetworkInfo)(i).GetCurrentEpoch()
|
|
||||||
|
c.IterateParameters(func(prm *netmap.NetworkParameter) bool {
|
||||||
|
name := string(prm.GetKey())
|
||||||
|
|
||||||
|
_, was := mNames[name]
|
||||||
|
if was {
|
||||||
|
err = fmt.Errorf("duplicated parameter name: %s", name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
mNames[name] = struct{}{}
|
||||||
|
|
||||||
|
switch name {
|
||||||
|
default:
|
||||||
|
if len(prm.GetValue()) == 0 {
|
||||||
|
err = fmt.Errorf("empty attribute value %s", name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case configEigenTrustAlpha:
|
||||||
|
var num uint64
|
||||||
|
|
||||||
|
num, err = decodeConfigValueUint64(prm.GetValue())
|
||||||
|
if err == nil {
|
||||||
|
if alpha := math.Float64frombits(num); alpha < 0 && alpha > 1 {
|
||||||
|
err = fmt.Errorf("EigenTrust alpha value %0.2f is out of range [0, 1]", alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case
|
||||||
|
configAuditFee,
|
||||||
|
configStoragePrice,
|
||||||
|
configContainerFee,
|
||||||
|
configNamedContainerFee,
|
||||||
|
configEigenTrustIterationsAmount,
|
||||||
|
configEpochDuration,
|
||||||
|
configIRCandidateFee,
|
||||||
|
configMaxObjSize,
|
||||||
|
configWithdrawalFee:
|
||||||
|
_, err = decodeConfigValueUint64(prm.GetValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("invalid %s parameter: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err != nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
x.m = m
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFromV2 reads NetworkInfo from the netmap.NetworkInfo message. Checks if the
|
||||||
|
// message conforms to NeoFS API V2 protocol.
|
||||||
|
//
|
||||||
|
// See also WriteToV2.
|
||||||
|
func (x *NetworkInfo) ReadFromV2(m netmap.NetworkInfo) error {
|
||||||
|
return x.readFromV2(m, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToV2 writes NetworkInfo to the netmap.NetworkInfo message. The message
|
||||||
|
// MUST NOT be nil.
|
||||||
|
//
|
||||||
|
// See also ReadFromV2.
|
||||||
|
func (x NetworkInfo) WriteToV2(m *netmap.NetworkInfo) {
|
||||||
|
*m = x.m
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentEpoch returns epoch set using SetCurrentEpoch.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero current epoch.
|
||||||
|
func (x NetworkInfo) CurrentEpoch() uint64 {
|
||||||
|
return x.m.GetCurrentEpoch()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCurrentEpoch sets current epoch of the NeoFS network.
|
// SetCurrentEpoch sets current epoch of the NeoFS network.
|
||||||
func (i *NetworkInfo) SetCurrentEpoch(epoch uint64) {
|
func (x *NetworkInfo) SetCurrentEpoch(epoch uint64) {
|
||||||
(*netmap.NetworkInfo)(i).SetCurrentEpoch(epoch)
|
x.m.SetCurrentEpoch(epoch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MagicNumber returns magic number of the sidechain.
|
// MagicNumber returns magic number set using SetMagicNumber.
|
||||||
func (i *NetworkInfo) MagicNumber() uint64 {
|
|
||||||
return (*netmap.NetworkInfo)(i).GetMagicNumber()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMagicNumber sets magic number of the sidechain.
|
|
||||||
func (i *NetworkInfo) SetMagicNumber(epoch uint64) {
|
|
||||||
(*netmap.NetworkInfo)(i).SetMagicNumber(epoch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MsPerBlock returns MillisecondsPerBlock network parameter.
|
|
||||||
func (i *NetworkInfo) MsPerBlock() int64 {
|
|
||||||
return (*netmap.NetworkInfo)(i).
|
|
||||||
GetMsPerBlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMsPerBlock sets MillisecondsPerBlock network parameter.
|
|
||||||
func (i *NetworkInfo) SetMsPerBlock(v int64) {
|
|
||||||
(*netmap.NetworkInfo)(i).
|
|
||||||
SetMsPerBlock(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkConfig returns NeoFS network configuration.
|
|
||||||
func (i *NetworkInfo) NetworkConfig() *NetworkConfig {
|
|
||||||
return NewNetworkConfigFromV2(
|
|
||||||
(*netmap.NetworkInfo)(i).
|
|
||||||
GetNetworkConfig(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNetworkConfig sets NeoFS network configuration.
|
|
||||||
func (i *NetworkInfo) SetNetworkConfig(v *NetworkConfig) {
|
|
||||||
(*netmap.NetworkInfo)(i).
|
|
||||||
SetNetworkConfig(v.ToV2())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals NetworkInfo into a protobuf binary form.
|
|
||||||
func (i *NetworkInfo) Marshal() ([]byte, error) {
|
|
||||||
return (*netmap.NetworkInfo)(i).StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of NetworkInfo.
|
|
||||||
func (i *NetworkInfo) Unmarshal(data []byte) error {
|
|
||||||
return (*netmap.NetworkInfo)(i).Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes NetworkInfo to protobuf JSON format.
|
|
||||||
func (i *NetworkInfo) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*netmap.NetworkInfo)(i).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes NetworkInfo from protobuf JSON format.
|
|
||||||
func (i *NetworkInfo) UnmarshalJSON(data []byte) error {
|
|
||||||
return (*netmap.NetworkInfo)(i).UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkParameter represents v2-compatible NeoFS network parameter.
|
|
||||||
type NetworkParameter netmap.NetworkParameter
|
|
||||||
|
|
||||||
// NewNetworkParameterFromV2 wraps v2 NetworkParameter message to NetworkParameter.
|
|
||||||
//
|
//
|
||||||
// Nil netmap.NetworkParameter converts to nil.
|
// Zero NetworkInfo has zero magic.
|
||||||
func NewNetworkParameterFromV2(pv2 *netmap.NetworkParameter) *NetworkParameter {
|
func (x NetworkInfo) MagicNumber() uint64 {
|
||||||
return (*NetworkParameter)(pv2)
|
return x.m.GetMagicNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNetworkParameter creates and initializes blank NetworkParameter.
|
// SetMagicNumber sets magic number of the NeoFS Sidechain.
|
||||||
//
|
//
|
||||||
// Defaults:
|
// See also MagicNumber.
|
||||||
// - key: nil;
|
func (x *NetworkInfo) SetMagicNumber(epoch uint64) {
|
||||||
// - value: nil.
|
x.m.SetMagicNumber(epoch)
|
||||||
func NewNetworkParameter() *NetworkParameter {
|
|
||||||
return NewNetworkParameterFromV2(new(netmap.NetworkParameter))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV2 converts NetworkParameter to v2 NetworkParameter.
|
// MsPerBlock returns network parameter set using SetMsPerBlock.
|
||||||
|
func (x NetworkInfo) MsPerBlock() int64 {
|
||||||
|
return x.m.GetMsPerBlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMsPerBlock sets MillisecondsPerBlock network parameter of the NeoFS Sidechain.
|
||||||
//
|
//
|
||||||
// Nil NetworkParameter converts to nil.
|
// See also MsPerBlock.
|
||||||
func (x *NetworkParameter) ToV2() *netmap.NetworkParameter {
|
func (x *NetworkInfo) SetMsPerBlock(v int64) {
|
||||||
return (*netmap.NetworkParameter)(x)
|
x.m.SetMsPerBlock(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key returns key to network parameter.
|
func (x *NetworkInfo) setConfig(name string, val []byte) {
|
||||||
func (x *NetworkParameter) Key() []byte {
|
c := x.m.GetNetworkConfig()
|
||||||
return (*netmap.NetworkParameter)(x).GetKey()
|
if c == nil {
|
||||||
|
c = new(netmap.NetworkConfig)
|
||||||
|
|
||||||
|
var prm netmap.NetworkParameter
|
||||||
|
prm.SetKey([]byte(name))
|
||||||
|
prm.SetValue(val)
|
||||||
|
|
||||||
|
c.SetParameters(prm)
|
||||||
|
|
||||||
|
x.m.SetNetworkConfig(c)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetKey sets key to the network parameter.
|
found := false
|
||||||
func (x *NetworkParameter) SetKey(key []byte) {
|
prms := make([]netmap.NetworkParameter, 0, c.NumberOfParameters())
|
||||||
(*netmap.NetworkParameter)(x).SetKey(key)
|
|
||||||
|
c.IterateParameters(func(prm *netmap.NetworkParameter) bool {
|
||||||
|
found = bytes.Equal(prm.GetKey(), []byte(name))
|
||||||
|
if found {
|
||||||
|
prm.SetValue(val)
|
||||||
|
} else {
|
||||||
|
prms = append(prms, *prm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value returns value of the network parameter.
|
return found
|
||||||
func (x *NetworkParameter) Value() []byte {
|
})
|
||||||
return (*netmap.NetworkParameter)(x).GetValue()
|
|
||||||
|
if !found {
|
||||||
|
prms = append(prms, netmap.NetworkParameter{})
|
||||||
|
prms[len(prms)-1].SetKey([]byte(name))
|
||||||
|
prms[len(prms)-1].SetValue(val)
|
||||||
|
|
||||||
|
c.SetParameters(prms...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetValue sets value of the network parameter.
|
func (x NetworkInfo) configValue(name string) (res []byte) {
|
||||||
func (x *NetworkParameter) SetValue(val []byte) {
|
x.m.GetNetworkConfig().IterateParameters(func(prm *netmap.NetworkParameter) bool {
|
||||||
(*netmap.NetworkParameter)(x).SetValue(val)
|
if string(prm.GetKey()) == name {
|
||||||
|
res = prm.GetValue()
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkConfig represents v2-compatible NeoFS network configuration.
|
return false
|
||||||
type NetworkConfig netmap.NetworkConfig
|
})
|
||||||
|
|
||||||
// NewNetworkConfigFromV2 wraps v2 NetworkConfig message to NetworkConfig.
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawNetworkParameter sets named NeoFS network parameter whose value is
|
||||||
|
// transmitted but not interpreted by the NeoFS API protocol.
|
||||||
//
|
//
|
||||||
// Nil netmap.NetworkConfig converts to nil.
|
// Argument MUST NOT be mutated, make a copy first.
|
||||||
func NewNetworkConfigFromV2(cv2 *netmap.NetworkConfig) *NetworkConfig {
|
|
||||||
return (*NetworkConfig)(cv2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNetworkConfig creates and initializes blank NetworkConfig.
|
|
||||||
//
|
//
|
||||||
// Defaults:
|
// See also RawNetworkParameter, IterateRawNetworkParameters.
|
||||||
// - parameters num: 0.
|
func (x *NetworkInfo) SetRawNetworkParameter(name string, value []byte) {
|
||||||
func NewNetworkConfig() *NetworkConfig {
|
x.setConfig(name, value)
|
||||||
return NewNetworkConfigFromV2(new(netmap.NetworkConfig))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV2 converts NetworkConfig to v2 NetworkConfig.
|
// RawNetworkParameter reads raw network parameter set using SetRawNetworkParameter
|
||||||
|
// by its name. Returns nil if value is missing.
|
||||||
//
|
//
|
||||||
// Nil NetworkConfig converts to nil.
|
// Return value MUST NOT be mutated, make a copy first.
|
||||||
func (x *NetworkConfig) ToV2() *netmap.NetworkConfig {
|
|
||||||
return (*netmap.NetworkConfig)(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumberOfParameters returns number of network parameters.
|
|
||||||
func (x *NetworkConfig) NumberOfParameters() int {
|
|
||||||
return (*netmap.NetworkConfig)(x).NumberOfParameters()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IterateAddresses iterates over network parameters.
|
|
||||||
// Breaks iteration on f's true return.
|
|
||||||
//
|
//
|
||||||
// Handler should not be nil.
|
// Zero NetworkInfo has no network parameters.
|
||||||
func (x *NetworkConfig) IterateParameters(f func(*NetworkParameter) bool) {
|
func (x *NetworkInfo) RawNetworkParameter(name string) []byte {
|
||||||
(*netmap.NetworkConfig)(x).
|
return x.configValue(name)
|
||||||
IterateParameters(func(p *netmap.NetworkParameter) bool {
|
}
|
||||||
return f(NewNetworkParameterFromV2(p))
|
|
||||||
|
// IterateRawNetworkParameters iterates over all raw networks parameters set
|
||||||
|
// using SetRawNetworkParameter and passes them into f.
|
||||||
|
//
|
||||||
|
// Handler MUST NOT be nil. Handler MUST NOT mutate value parameter.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has no network parameters.
|
||||||
|
func (x *NetworkInfo) IterateRawNetworkParameters(f func(name string, value []byte)) {
|
||||||
|
c := x.m.GetNetworkConfig()
|
||||||
|
|
||||||
|
c.IterateParameters(func(prm *netmap.NetworkParameter) bool {
|
||||||
|
name := string(prm.GetKey())
|
||||||
|
switch name {
|
||||||
|
default:
|
||||||
|
f(name, prm.GetValue())
|
||||||
|
case
|
||||||
|
configEigenTrustAlpha,
|
||||||
|
configAuditFee,
|
||||||
|
configStoragePrice,
|
||||||
|
configContainerFee,
|
||||||
|
configNamedContainerFee,
|
||||||
|
configEigenTrustIterationsAmount,
|
||||||
|
configEpochDuration,
|
||||||
|
configIRCandidateFee,
|
||||||
|
configMaxObjSize,
|
||||||
|
configWithdrawalFee:
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value returns value of the network parameter.
|
func (x *NetworkInfo) setConfigUint64(name string, num uint64) {
|
||||||
func (x *NetworkConfig) SetParameters(ps ...NetworkParameter) {
|
val := make([]byte, 8)
|
||||||
var psV2 []netmap.NetworkParameter
|
binary.LittleEndian.PutUint64(val, num)
|
||||||
|
|
||||||
if ps != nil {
|
x.setConfig(name, val)
|
||||||
ln := len(ps)
|
|
||||||
|
|
||||||
psV2 = make([]netmap.NetworkParameter, ln)
|
|
||||||
|
|
||||||
for i := 0; i < ln; i++ {
|
|
||||||
psV2[i] = *ps[i].ToV2()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(*netmap.NetworkConfig)(x).SetParameters(psV2...)
|
func decodeConfigValueUint64(val []byte) (uint64, error) {
|
||||||
|
if ln := len(val); ln != 8 {
|
||||||
|
return 0, fmt.Errorf("invalid uint64 parameter length %d", ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
return binary.LittleEndian.Uint64(val), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x NetworkInfo) configUint64(name string) uint64 {
|
||||||
|
val := x.configValue(name)
|
||||||
|
if val == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := decodeConfigValueUint64(val)
|
||||||
|
if err != nil {
|
||||||
|
// potential panic is OK since value MUST be correct since it is
|
||||||
|
// verified in ReadFromV2 or set by provided method.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const configAuditFee = "AuditFee"
|
||||||
|
|
||||||
|
// SetAuditFee sets the configuration value of the audit fee for the Inner Ring.
|
||||||
|
//
|
||||||
|
// See also AuditFee.
|
||||||
|
func (x *NetworkInfo) SetAuditFee(fee uint64) {
|
||||||
|
x.setConfigUint64(configAuditFee, fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditFee returns audit fee set using SetAuditFee.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero audit fee.
|
||||||
|
func (x NetworkInfo) AuditFee() uint64 {
|
||||||
|
return x.configUint64(configAuditFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configStoragePrice = "BasicIncomeRate"
|
||||||
|
|
||||||
|
// SetStoragePrice sets the price per gigabyte of data storage that data owners
|
||||||
|
// pay to storage nodes.
|
||||||
|
//
|
||||||
|
// See also StoragePrice.
|
||||||
|
func (x *NetworkInfo) SetStoragePrice(price uint64) {
|
||||||
|
x.setConfigUint64(configStoragePrice, price)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoragePrice returns storage price set using SetStoragePrice.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero storage price.
|
||||||
|
func (x NetworkInfo) StoragePrice() uint64 {
|
||||||
|
return x.configUint64(configStoragePrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configContainerFee = "ContainerFee"
|
||||||
|
|
||||||
|
// SetContainerFee sets fee for the container creation that creator pays to
|
||||||
|
// each Alphabet node.
|
||||||
|
//
|
||||||
|
// See also ContainerFee.
|
||||||
|
func (x *NetworkInfo) SetContainerFee(fee uint64) {
|
||||||
|
x.setConfigUint64(configContainerFee, fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerFee returns container fee set using SetContainerFee.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero container fee.
|
||||||
|
func (x NetworkInfo) ContainerFee() uint64 {
|
||||||
|
return x.configUint64(configContainerFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configNamedContainerFee = "ContainerAliasFee"
|
||||||
|
|
||||||
|
// SetNamedContainerFee sets fee for creation of the named container creation
|
||||||
|
// that creator pays to each Alphabet node.
|
||||||
|
//
|
||||||
|
// See also NamedContainerFee.
|
||||||
|
func (x *NetworkInfo) SetNamedContainerFee(fee uint64) {
|
||||||
|
x.setConfigUint64(configNamedContainerFee, fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedContainerFee returns container fee set using SetNamedContainerFee.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero container fee.
|
||||||
|
func (x NetworkInfo) NamedContainerFee() uint64 {
|
||||||
|
return x.configUint64(configNamedContainerFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configEigenTrustAlpha = "EigenTrustAlpha"
|
||||||
|
|
||||||
|
// SetEigenTrustAlpha sets alpha parameter for EigenTrust algorithm used in
|
||||||
|
// reputation system of the storage nodes. Value MUST be in range [0, 1].
|
||||||
|
//
|
||||||
|
// See also EigenTrustAlpha.
|
||||||
|
func (x *NetworkInfo) SetEigenTrustAlpha(alpha float64) {
|
||||||
|
if alpha < 0 || alpha > 1 {
|
||||||
|
panic(fmt.Sprintf("EigenTrust alpha parameter MUST be in range [0, 1], got %.2f", alpha))
|
||||||
|
}
|
||||||
|
|
||||||
|
x.setConfigUint64(configEigenTrustAlpha, math.Float64bits(alpha))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EigenTrustAlpha returns EigenTrust parameter set using SetEigenTrustAlpha.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero alpha parameter.
|
||||||
|
func (x NetworkInfo) EigenTrustAlpha() float64 {
|
||||||
|
alpha := math.Float64frombits(x.configUint64(configEigenTrustAlpha))
|
||||||
|
if alpha < 0 || alpha > 1 {
|
||||||
|
panic(fmt.Sprintf("unexpected invalid %s parameter value %.2f", configEigenTrustAlpha, alpha))
|
||||||
|
}
|
||||||
|
|
||||||
|
return alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
const configEigenTrustIterationsAmount = "EigenTrustIterations"
|
||||||
|
|
||||||
|
// SetEigenTrustIterationAmount sets number of iterations of the EigenTrust
|
||||||
|
// algorithm to perform. The algorithm is used by the storage nodes for
|
||||||
|
// calculating the reputation values.
|
||||||
|
//
|
||||||
|
// See also EigenTrustIterationAmount.
|
||||||
|
func (x *NetworkInfo) SetEigenTrustIterationAmount(amount uint64) {
|
||||||
|
x.setConfigUint64(configEigenTrustIterationsAmount, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EigenTrustIterationAmount returns EigenTrust iteration amount set using
|
||||||
|
// SetEigenTrustIterationAmount.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero iteration number.
|
||||||
|
func (x NetworkInfo) EigenTrustIterationAmount() uint64 {
|
||||||
|
return x.configUint64(configEigenTrustIterationsAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configEpochDuration = "EpochDuration"
|
||||||
|
|
||||||
|
// SetEpochDuration sets NeoFS epoch duration measured in block amount of the
|
||||||
|
// NeoFS Sidechain.
|
||||||
|
//
|
||||||
|
// See also EpochDuration.
|
||||||
|
func (x *NetworkInfo) SetEpochDuration(blocks uint64) {
|
||||||
|
x.setConfigUint64(configEpochDuration, blocks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EpochDuration returns epoch duration set using SetEpochDuration.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero iteration number.
|
||||||
|
func (x NetworkInfo) EpochDuration() uint64 {
|
||||||
|
return x.configUint64(configEpochDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configIRCandidateFee = "InnerRingCandidateFee"
|
||||||
|
|
||||||
|
// SetIRCandidateFee sets fee for Inner Ring entrance paid by a new member.
|
||||||
|
//
|
||||||
|
// See also IRCandidateFee.
|
||||||
|
func (x *NetworkInfo) SetIRCandidateFee(fee uint64) {
|
||||||
|
x.setConfigUint64(configIRCandidateFee, fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IRCandidateFee returns IR entrance fee set using SetIRCandidateFee.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero fee.
|
||||||
|
func (x NetworkInfo) IRCandidateFee() uint64 {
|
||||||
|
return x.configUint64(configIRCandidateFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configMaxObjSize = "MaxObjectSize"
|
||||||
|
|
||||||
|
// SetMaxObjectSize sets maximum size of the object stored locally on the
|
||||||
|
// storage nodes (physical objects). Binary representation of any physically
|
||||||
|
// stored object MUST NOT overflow the limit.
|
||||||
|
//
|
||||||
|
// See also MaxObjectSize.
|
||||||
|
func (x *NetworkInfo) SetMaxObjectSize(sz uint64) {
|
||||||
|
x.setConfigUint64(configMaxObjSize, sz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxObjectSize returns maximum object size set using SetMaxObjectSize.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero maximum size.
|
||||||
|
func (x NetworkInfo) MaxObjectSize() uint64 {
|
||||||
|
return x.configUint64(configMaxObjSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configWithdrawalFee = "WithdrawFee"
|
||||||
|
|
||||||
|
// SetWithdrawalFee sets fee for withdrawals from the NeoFS accounts that
|
||||||
|
// account owners pay to each Alphabet node.
|
||||||
|
//
|
||||||
|
// See also WithdrawalFee.
|
||||||
|
func (x *NetworkInfo) SetWithdrawalFee(sz uint64) {
|
||||||
|
x.setConfigUint64(configWithdrawalFee, sz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithdrawalFee returns withdrawal fee set using SetWithdrawalFee.
|
||||||
|
//
|
||||||
|
// Zero NetworkInfo has zero fee.
|
||||||
|
func (x NetworkInfo) WithdrawalFee() uint64 {
|
||||||
|
return x.configUint64(configWithdrawalFee)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,214 +1,214 @@
|
||||||
package netmap_test
|
package netmap_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
. "github.com/nspcc-dev/neofs-sdk-go/netmap"
|
. "github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||||
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNetworkParameter_Key(t *testing.T) {
|
|
||||||
i := NewNetworkParameter()
|
|
||||||
|
|
||||||
k := []byte("key")
|
|
||||||
|
|
||||||
i.SetKey(k)
|
|
||||||
|
|
||||||
require.Equal(t, k, i.Key())
|
|
||||||
require.Equal(t, k, i.ToV2().GetKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNetworkParameter_Value(t *testing.T) {
|
|
||||||
i := NewNetworkParameter()
|
|
||||||
|
|
||||||
v := []byte("value")
|
|
||||||
|
|
||||||
i.SetValue(v)
|
|
||||||
|
|
||||||
require.Equal(t, v, i.Value())
|
|
||||||
require.Equal(t, v, i.ToV2().GetValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewNetworkParameterFromV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
require.Nil(t, NewNetworkParameterFromV2(nil))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNetworkParameter_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *NetworkParameter
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewNetworkParameter(t *testing.T) {
|
|
||||||
x := NewNetworkParameter()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Nil(t, x.Key())
|
|
||||||
require.Nil(t, x.Value())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
xV2 := x.ToV2()
|
|
||||||
|
|
||||||
require.Nil(t, xV2.GetKey())
|
|
||||||
require.Nil(t, xV2.GetValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNetworkConfig_SetParameters(t *testing.T) {
|
|
||||||
x := NewNetworkConfig()
|
|
||||||
|
|
||||||
require.Zero(t, x.NumberOfParameters())
|
|
||||||
|
|
||||||
called := 0
|
|
||||||
|
|
||||||
x.IterateParameters(func(p *NetworkParameter) bool {
|
|
||||||
called++
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
require.Zero(t, called)
|
|
||||||
|
|
||||||
pps := []NetworkParameter{
|
|
||||||
*netmaptest.NetworkParameter(),
|
|
||||||
*netmaptest.NetworkParameter(),
|
|
||||||
}
|
|
||||||
|
|
||||||
x.SetParameters(pps...)
|
|
||||||
|
|
||||||
require.EqualValues(t, len(pps), x.NumberOfParameters())
|
|
||||||
|
|
||||||
var dst []NetworkParameter
|
|
||||||
|
|
||||||
x.IterateParameters(func(p *NetworkParameter) bool {
|
|
||||||
dst = append(dst, *p)
|
|
||||||
called++
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
require.Equal(t, pps, dst)
|
|
||||||
require.Equal(t, len(pps), called)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewNetworkConfigFromV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
require.Nil(t, NewNetworkConfigFromV2(nil))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNetworkConfig_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *NetworkConfig
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewNetworkConfig(t *testing.T) {
|
|
||||||
x := NewNetworkConfig()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Zero(t, x.NumberOfParameters())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
xV2 := x.ToV2()
|
|
||||||
|
|
||||||
require.Zero(t, xV2.NumberOfParameters())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNetworkInfo_CurrentEpoch(t *testing.T) {
|
func TestNetworkInfo_CurrentEpoch(t *testing.T) {
|
||||||
i := NewNetworkInfo()
|
var x NetworkInfo
|
||||||
e := uint64(13)
|
|
||||||
|
|
||||||
i.SetCurrentEpoch(e)
|
require.Zero(t, x.CurrentEpoch())
|
||||||
|
|
||||||
require.Equal(t, e, i.CurrentEpoch())
|
const e = 13
|
||||||
require.Equal(t, e, i.ToV2().GetCurrentEpoch())
|
|
||||||
|
x.SetCurrentEpoch(e)
|
||||||
|
|
||||||
|
require.EqualValues(t, e, x.CurrentEpoch())
|
||||||
|
|
||||||
|
var m netmap.NetworkInfo
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
|
require.EqualValues(t, e, m.GetCurrentEpoch())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNetworkInfo_MagicNumber(t *testing.T) {
|
func TestNetworkInfo_MagicNumber(t *testing.T) {
|
||||||
i := NewNetworkInfo()
|
var x NetworkInfo
|
||||||
m := uint64(666)
|
|
||||||
|
|
||||||
i.SetMagicNumber(m)
|
require.Zero(t, x.MagicNumber())
|
||||||
|
|
||||||
require.Equal(t, m, i.MagicNumber())
|
const magic = 321
|
||||||
require.Equal(t, m, i.ToV2().GetMagicNumber())
|
|
||||||
|
x.SetMagicNumber(magic)
|
||||||
|
|
||||||
|
require.EqualValues(t, magic, x.MagicNumber())
|
||||||
|
|
||||||
|
var m netmap.NetworkInfo
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
|
require.EqualValues(t, magic, m.GetMagicNumber())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNetworkInfo_MsPerBlock(t *testing.T) {
|
func TestNetworkInfo_MsPerBlock(t *testing.T) {
|
||||||
i := NewNetworkInfo()
|
var x NetworkInfo
|
||||||
|
|
||||||
const ms = 987
|
require.Zero(t, x.MsPerBlock())
|
||||||
|
|
||||||
i.SetMsPerBlock(ms)
|
const ms = 789
|
||||||
|
|
||||||
require.EqualValues(t, ms, i.MsPerBlock())
|
x.SetMsPerBlock(ms)
|
||||||
require.EqualValues(t, ms, i.ToV2().GetMsPerBlock())
|
|
||||||
|
require.EqualValues(t, ms, x.MsPerBlock())
|
||||||
|
|
||||||
|
var m netmap.NetworkInfo
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
|
require.EqualValues(t, ms, m.GetMsPerBlock())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNetworkInfo_Config(t *testing.T) {
|
func testConfigValue(t *testing.T,
|
||||||
i := NewNetworkInfo()
|
getter func(x NetworkInfo) interface{},
|
||||||
|
setter func(x *NetworkInfo, val interface{}),
|
||||||
|
val1, val2 interface{},
|
||||||
|
v2Key string, v2Val func(val interface{}) []byte,
|
||||||
|
) {
|
||||||
|
var x NetworkInfo
|
||||||
|
|
||||||
c := netmaptest.NetworkConfig()
|
require.Zero(t, getter(x))
|
||||||
|
|
||||||
i.SetNetworkConfig(c)
|
checkVal := func(exp interface{}) {
|
||||||
|
require.EqualValues(t, exp, getter(x))
|
||||||
|
|
||||||
require.Equal(t, c, i.NetworkConfig())
|
var m netmap.NetworkInfo
|
||||||
}
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
func TestNetworkInfoEncoding(t *testing.T) {
|
require.EqualValues(t, 1, m.GetNetworkConfig().NumberOfParameters())
|
||||||
i := netmaptest.NetworkInfo()
|
found := false
|
||||||
|
m.GetNetworkConfig().IterateParameters(func(prm *netmap.NetworkParameter) bool {
|
||||||
t.Run("binary", func(t *testing.T) {
|
require.False(t, found)
|
||||||
data, err := i.Marshal()
|
require.Equal(t, []byte(v2Key), prm.GetKey())
|
||||||
require.NoError(t, err)
|
require.Equal(t, v2Val(exp), prm.GetValue())
|
||||||
|
found = true
|
||||||
i2 := NewNetworkInfo()
|
return false
|
||||||
require.NoError(t, i2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, i, i2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
|
||||||
data, err := i.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
i2 := NewNetworkInfo()
|
|
||||||
require.NoError(t, i2.UnmarshalJSON(data))
|
|
||||||
|
|
||||||
require.Equal(t, i, i2)
|
|
||||||
})
|
})
|
||||||
|
require.True(t, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewNetworkInfoFromV2(t *testing.T) {
|
setter(&x, val1)
|
||||||
t.Run("nil", func(t *testing.T) {
|
checkVal(val1)
|
||||||
require.Nil(t, NewNetworkInfoFromV2(nil))
|
|
||||||
})
|
setter(&x, val2)
|
||||||
|
checkVal(val2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNetworkInfo_ToV2(t *testing.T) {
|
func TestNetworkInfo_AuditFee(t *testing.T) {
|
||||||
t.Run("nil", func(t *testing.T) {
|
testConfigValue(t,
|
||||||
var x *NetworkInfo
|
func(x NetworkInfo) interface{} { return x.AuditFee() },
|
||||||
|
func(info *NetworkInfo, val interface{}) { info.SetAuditFee(val.(uint64)) },
|
||||||
require.Nil(t, x.ToV2())
|
uint64(1), uint64(2),
|
||||||
})
|
"AuditFee", func(val interface{}) []byte {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewNetworkInfo(t *testing.T) {
|
func TestNetworkInfo_StoragePrice(t *testing.T) {
|
||||||
ni := NewNetworkInfo()
|
testConfigValue(t,
|
||||||
|
func(x NetworkInfo) interface{} { return x.StoragePrice() },
|
||||||
// check initial values
|
func(info *NetworkInfo, val interface{}) { info.SetStoragePrice(val.(uint64)) },
|
||||||
require.Zero(t, ni.CurrentEpoch())
|
uint64(1), uint64(2),
|
||||||
require.Zero(t, ni.MagicNumber())
|
"BasicIncomeRate", func(val interface{}) []byte {
|
||||||
require.Zero(t, ni.MsPerBlock())
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||||
// convert to v2 message
|
return data
|
||||||
niV2 := ni.ToV2()
|
},
|
||||||
|
)
|
||||||
require.Zero(t, niV2.GetCurrentEpoch())
|
}
|
||||||
require.Zero(t, niV2.GetMagicNumber())
|
|
||||||
require.Zero(t, niV2.GetMsPerBlock())
|
func TestNetworkInfo_ContainerFee(t *testing.T) {
|
||||||
|
testConfigValue(t,
|
||||||
|
func(x NetworkInfo) interface{} { return x.ContainerFee() },
|
||||||
|
func(info *NetworkInfo, val interface{}) { info.SetContainerFee(val.(uint64)) },
|
||||||
|
uint64(1), uint64(2),
|
||||||
|
"ContainerFee", func(val interface{}) []byte {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkInfo_NamedContainerFee(t *testing.T) {
|
||||||
|
testConfigValue(t,
|
||||||
|
func(x NetworkInfo) interface{} { return x.NamedContainerFee() },
|
||||||
|
func(info *NetworkInfo, val interface{}) { info.SetNamedContainerFee(val.(uint64)) },
|
||||||
|
uint64(1), uint64(2),
|
||||||
|
"ContainerAliasFee", func(val interface{}) []byte {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkInfo_EigenTrustAlpha(t *testing.T) {
|
||||||
|
testConfigValue(t,
|
||||||
|
func(x NetworkInfo) interface{} { return x.EigenTrustAlpha() },
|
||||||
|
func(info *NetworkInfo, val interface{}) { info.SetEigenTrustAlpha(val.(float64)) },
|
||||||
|
0.1, 0.2,
|
||||||
|
"EigenTrustAlpha", func(val interface{}) []byte {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, math.Float64bits(val.(float64)))
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkInfo_EigenTrustIterationAmount(t *testing.T) {
|
||||||
|
testConfigValue(t,
|
||||||
|
func(x NetworkInfo) interface{} { return x.EigenTrustIterationAmount() },
|
||||||
|
func(info *NetworkInfo, val interface{}) { info.SetEigenTrustIterationAmount(val.(uint64)) },
|
||||||
|
uint64(1), uint64(2),
|
||||||
|
"EigenTrustIterations", func(val interface{}) []byte {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkInfo_IRCandidateFee(t *testing.T) {
|
||||||
|
testConfigValue(t,
|
||||||
|
func(x NetworkInfo) interface{} { return x.IRCandidateFee() },
|
||||||
|
func(info *NetworkInfo, val interface{}) { info.SetIRCandidateFee(val.(uint64)) },
|
||||||
|
uint64(1), uint64(2),
|
||||||
|
"InnerRingCandidateFee", func(val interface{}) []byte {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkInfo_MaxObjectSize(t *testing.T) {
|
||||||
|
testConfigValue(t,
|
||||||
|
func(x NetworkInfo) interface{} { return x.MaxObjectSize() },
|
||||||
|
func(info *NetworkInfo, val interface{}) { info.SetMaxObjectSize(val.(uint64)) },
|
||||||
|
uint64(1), uint64(2),
|
||||||
|
"MaxObjectSize", func(val interface{}) []byte {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkInfo_WithdrawalFee(t *testing.T) {
|
||||||
|
testConfigValue(t,
|
||||||
|
func(x NetworkInfo) interface{} { return x.WithdrawalFee() },
|
||||||
|
func(info *NetworkInfo, val interface{}) { info.SetWithdrawalFee(val.(uint64)) },
|
||||||
|
uint64(1), uint64(2),
|
||||||
|
"WithdrawFee", func(val interface{}) []byte {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,377 +1,560 @@
|
||||||
package netmap
|
package netmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/hrw"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
|
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeState is an enumeration of various states of the NeoFS node.
|
// NodeInfo groups information about NeoFS storage node which is reflected
|
||||||
type NodeState uint32
|
// in the NeoFS network map. Storage nodes advertise this information when
|
||||||
|
// registering with the NeoFS network. After successful registration, information
|
||||||
// NodeAttribute represents v2 compatible attribute of the NeoFS Storage Node.
|
// about the nodes is available to all network participants to work with the network
|
||||||
type NodeAttribute netmap.Attribute
|
// map (mainly to comply with container storage policies).
|
||||||
|
//
|
||||||
// NodeInfo represents v2 compatible descriptor of the NeoFS node.
|
// NodeInfo is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/netmap.NodeInfo
|
||||||
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
|
//
|
||||||
|
// Instances can be created using built-in var declaration.
|
||||||
type NodeInfo struct {
|
type NodeInfo struct {
|
||||||
priceAttr uint64
|
m netmap.NodeInfo
|
||||||
|
|
||||||
capAttr uint64
|
|
||||||
|
|
||||||
m *netmap.NodeInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
// reads NodeInfo from netmap.NodeInfo message. If checkFieldPresence is set,
|
||||||
_ NodeState = iota
|
// returns an error on absence of any protocol-required field. Verifies format of any
|
||||||
|
// presented field according to NeoFS API V2 protocol.
|
||||||
|
func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
// NodeStateOffline is network unavailable state.
|
binPublicKey := m.GetPublicKey()
|
||||||
NodeStateOffline
|
if checkFieldPresence && len(binPublicKey) == 0 {
|
||||||
|
return errors.New("missing public key")
|
||||||
// NodeStateOnline is an active state in the network.
|
|
||||||
NodeStateOnline
|
|
||||||
)
|
|
||||||
|
|
||||||
// Enumeration of well-known attributes.
|
|
||||||
const (
|
|
||||||
// AttrPrice is a key to the node attribute that indicates the
|
|
||||||
// price in GAS tokens for storing one GB of data during one Epoch.
|
|
||||||
AttrPrice = "Price"
|
|
||||||
|
|
||||||
// AttrCapacity is a key to the node attribute that indicates the
|
|
||||||
// total available disk space in Gigabytes.
|
|
||||||
AttrCapacity = "Capacity"
|
|
||||||
|
|
||||||
// AttrSubnet is a key to the node attribute that indicates the
|
|
||||||
// string ID of node's storage subnet.
|
|
||||||
AttrSubnet = "Subnet"
|
|
||||||
|
|
||||||
// AttrUNLOCODE is a key to the node attribute that indicates the
|
|
||||||
// node's geographic location in UN/LOCODE format.
|
|
||||||
AttrUNLOCODE = "UN-LOCODE"
|
|
||||||
|
|
||||||
// AttrCountryCode is a key to the node attribute that indicates the
|
|
||||||
// Country code in ISO 3166-1_alpha-2 format.
|
|
||||||
AttrCountryCode = "CountryCode"
|
|
||||||
|
|
||||||
// AttrCountry is a key to the node attribute that indicates the
|
|
||||||
// country short name in English, as defined in ISO-3166.
|
|
||||||
AttrCountry = "Country"
|
|
||||||
|
|
||||||
// AttrLocation is a key to the node attribute that indicates the
|
|
||||||
// place name of the node location.
|
|
||||||
AttrLocation = "Location"
|
|
||||||
|
|
||||||
// AttrSubDivCode is a key to the node attribute that indicates the
|
|
||||||
// country's administrative subdivision where node is located
|
|
||||||
// in ISO 3166-2 format.
|
|
||||||
AttrSubDivCode = "SubDivCode"
|
|
||||||
|
|
||||||
// AttrSubDiv is a key to the node attribute that indicates the
|
|
||||||
// country's administrative subdivision name, as defined in
|
|
||||||
// ISO 3166-2.
|
|
||||||
AttrSubDiv = "SubDiv"
|
|
||||||
|
|
||||||
// AttrContinent is a key to the node attribute that indicates the
|
|
||||||
// node's continent name according to the Seven-Continent model.
|
|
||||||
AttrContinent = "Continent"
|
|
||||||
)
|
|
||||||
|
|
||||||
// calcBucketWeight computes weight for a Bucket.
|
|
||||||
func calcBucketWeight(ns nodes, a aggregator, wf weightFunc) float64 {
|
|
||||||
for i := range ns {
|
|
||||||
a.Add(wf(ns[i]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.Compute()
|
if checkFieldPresence && m.NumberOfAddresses() <= 0 {
|
||||||
|
return errors.New("missing network endpoints")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeStateFromV2 converts v2 NodeState to NodeState.
|
attributes := m.GetAttributes()
|
||||||
func NodeStateFromV2(s netmap.NodeState) NodeState {
|
mAttr := make(map[string]struct{}, len(attributes))
|
||||||
switch s {
|
for i := range attributes {
|
||||||
|
key := attributes[i].GetKey()
|
||||||
|
if key == "" {
|
||||||
|
return fmt.Errorf("empty key of the attribute #%d", i)
|
||||||
|
} else if _, ok := mAttr[key]; ok {
|
||||||
|
return fmt.Errorf("duplicated attbiuted %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const subnetPrefix = "__NEOFS__SUBNET_"
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case key == attrCapacity:
|
||||||
|
_, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid %s attribute: %w", attrCapacity, err)
|
||||||
|
}
|
||||||
|
case key == attrPrice:
|
||||||
|
var err error
|
||||||
|
_, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid %s attribute: %w", attrPrice, err)
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(key, subnetPrefix):
|
||||||
|
var id subnetid.ID
|
||||||
|
|
||||||
|
err = id.DecodeString(strings.TrimPrefix(key, subnetPrefix))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid key to the subnet attribute %s: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := attributes[i].GetValue(); val != "True" && val != "False" {
|
||||||
|
return fmt.Errorf("invalid value of the subnet attribute %s: %w", val, err)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return 0
|
if attributes[i].GetValue() == "" {
|
||||||
case netmap.Online:
|
return fmt.Errorf("empty value of the attribute #%d", i)
|
||||||
return NodeStateOnline
|
}
|
||||||
case netmap.Offline:
|
|
||||||
return NodeStateOffline
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV2 converts NodeState to v2 NodeState.
|
x.m = m
|
||||||
func (s NodeState) ToV2() netmap.NodeState {
|
|
||||||
switch s {
|
|
||||||
default:
|
|
||||||
return netmap.UnspecifiedState
|
|
||||||
case NodeStateOffline:
|
|
||||||
return netmap.Offline
|
|
||||||
case NodeStateOnline:
|
|
||||||
return netmap.Online
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of NodeState.
|
|
||||||
//
|
|
||||||
// String mapping:
|
|
||||||
// * NodeStateOnline: ONLINE;
|
|
||||||
// * NodeStateOffline: OFFLINE;
|
|
||||||
// * default: UNSPECIFIED.
|
|
||||||
func (s NodeState) String() string {
|
|
||||||
return s.ToV2().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromString parses NodeState from a string representation.
|
|
||||||
// It is a reverse action to String().
|
|
||||||
//
|
|
||||||
// Returns true if s was parsed successfully.
|
|
||||||
func (s *NodeState) FromString(str string) bool {
|
|
||||||
var g netmap.NodeState
|
|
||||||
|
|
||||||
ok := g.FromString(str)
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
*s = NodeStateFromV2(g)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNodeAttribute creates and returns new NodeAttribute instance.
|
|
||||||
//
|
|
||||||
// Defaults:
|
|
||||||
// - key: "";
|
|
||||||
// - value: "";
|
|
||||||
// - parents: nil.
|
|
||||||
func NewNodeAttribute() *NodeAttribute {
|
|
||||||
return NewNodeAttributeFromV2(new(netmap.Attribute))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeAttributeFromV2 converts v2 node Attribute to NodeAttribute.
|
|
||||||
//
|
|
||||||
// Nil netmap.Attribute converts to nil.
|
|
||||||
func NewNodeAttributeFromV2(a *netmap.Attribute) *NodeAttribute {
|
|
||||||
return (*NodeAttribute)(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts NodeAttribute to v2 node Attribute.
|
|
||||||
//
|
|
||||||
// Nil NodeAttribute converts to nil.
|
|
||||||
func (a *NodeAttribute) ToV2() *netmap.Attribute {
|
|
||||||
return (*netmap.Attribute)(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key returns key to the node attribute.
|
|
||||||
func (a *NodeAttribute) Key() string {
|
|
||||||
return (*netmap.Attribute)(a).
|
|
||||||
GetKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKey sets key to the node attribute.
|
|
||||||
func (a *NodeAttribute) SetKey(key string) {
|
|
||||||
(*netmap.Attribute)(a).
|
|
||||||
SetKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns value of the node attribute.
|
|
||||||
func (a *NodeAttribute) Value() string {
|
|
||||||
return (*netmap.Attribute)(a).
|
|
||||||
GetValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetValue sets value of the node attribute.
|
|
||||||
func (a *NodeAttribute) SetValue(val string) {
|
|
||||||
(*netmap.Attribute)(a).
|
|
||||||
SetValue(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParentKeys returns list of parent keys.
|
|
||||||
func (a *NodeAttribute) ParentKeys() []string {
|
|
||||||
return (*netmap.Attribute)(a).
|
|
||||||
GetParents()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetParentKeys sets list of parent keys.
|
|
||||||
func (a *NodeAttribute) SetParentKeys(keys ...string) {
|
|
||||||
(*netmap.Attribute)(a).
|
|
||||||
SetParents(keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals NodeAttribute into a protobuf binary form.
|
|
||||||
func (a *NodeAttribute) Marshal() ([]byte, error) {
|
|
||||||
return (*netmap.Attribute)(a).StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of NodeAttribute.
|
|
||||||
func (a *NodeAttribute) Unmarshal(data []byte) error {
|
|
||||||
return (*netmap.Attribute)(a).Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes NodeAttribute to protobuf JSON format.
|
|
||||||
func (a *NodeAttribute) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*netmap.Attribute)(a).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes NodeAttribute from protobuf JSON format.
|
|
||||||
func (a *NodeAttribute) UnmarshalJSON(data []byte) error {
|
|
||||||
return (*netmap.Attribute)(a).UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNodeInfo creates and returns new NodeInfo instance.
|
|
||||||
//
|
|
||||||
// Defaults:
|
|
||||||
// - publicKey: nil;
|
|
||||||
// - address: "";
|
|
||||||
// - attributes nil;
|
|
||||||
// - state: 0.
|
|
||||||
func NewNodeInfo() *NodeInfo {
|
|
||||||
return NewNodeInfoFromV2(new(netmap.NodeInfo))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNodeInfoFromV2 converts v2 NodeInfo to NodeInfo.
|
|
||||||
//
|
|
||||||
// Nil netmap.NodeInfo converts to nil.
|
|
||||||
func NewNodeInfoFromV2(i *netmap.NodeInfo) *NodeInfo {
|
|
||||||
var res NodeInfo
|
|
||||||
res.m = i
|
|
||||||
res.syncAttributes()
|
|
||||||
|
|
||||||
return &res
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts NodeInfo to v2 NodeInfo.
|
|
||||||
//
|
|
||||||
// Nil NodeInfo converts to nil.
|
|
||||||
func (i *NodeInfo) ToV2() *netmap.NodeInfo {
|
|
||||||
if i == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return i.m
|
// ReadFromV2 reads NodeInfo from the netmap.NodeInfo message. Checks if the
|
||||||
}
|
// message conforms to NeoFS API V2 protocol.
|
||||||
|
|
||||||
// PublicKey returns public key of the node in a binary format.
|
|
||||||
func (i *NodeInfo) PublicKey() []byte {
|
|
||||||
return i.m.GetPublicKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPublicKey sets public key of the node in a binary format.
|
|
||||||
func (i *NodeInfo) SetPublicKey(key []byte) {
|
|
||||||
if i.m == nil {
|
|
||||||
i.m = new(netmap.NodeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
i.m.SetPublicKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumberOfAddresses returns number of network addresses of the node.
|
|
||||||
func (i *NodeInfo) NumberOfAddresses() int {
|
|
||||||
return i.m.NumberOfAddresses()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IterateAddresses iterates over network addresses of the node.
|
|
||||||
// Breaks iteration on f's true return.
|
|
||||||
//
|
//
|
||||||
// Handler should not be nil.
|
// See also WriteToV2.
|
||||||
func (i *NodeInfo) IterateAddresses(f func(string) bool) {
|
func (x *NodeInfo) ReadFromV2(m netmap.NodeInfo) error {
|
||||||
i.m.IterateAddresses(f)
|
return x.readFromV2(m, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterateAllAddresses is a helper function to unconditionally
|
// WriteToV2 writes NodeInfo to the netmap.NodeInfo message. The message MUST NOT
|
||||||
// iterate over all node addresses.
|
// be nil.
|
||||||
func IterateAllAddresses(i *NodeInfo, f func(string)) {
|
//
|
||||||
i.IterateAddresses(func(addr string) bool {
|
// See also ReadFromV2.
|
||||||
|
func (x NodeInfo) WriteToV2(m *netmap.NodeInfo) {
|
||||||
|
*m = x.m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal encodes NodeInfo into a binary format of the NeoFS API protocol
|
||||||
|
// (Protocol Buffers with direct field order).
|
||||||
|
//
|
||||||
|
// See also Unmarshal.
|
||||||
|
func (x NodeInfo) Marshal() []byte {
|
||||||
|
var m netmap.NodeInfo
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
|
return m.StableMarshal(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal decodes NeoFS API protocol binary format into the NodeInfo
|
||||||
|
// (Protocol Buffers with direct field order). Returns an error describing
|
||||||
|
// a format violation.
|
||||||
|
//
|
||||||
|
// See also Marshal.
|
||||||
|
func (x *NodeInfo) Unmarshal(data []byte) error {
|
||||||
|
var m netmap.NodeInfo
|
||||||
|
|
||||||
|
err := m.Unmarshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.readFromV2(m, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes NodeInfo into a JSON format of the NeoFS API protocol
|
||||||
|
// (Protocol Buffers JSON).
|
||||||
|
//
|
||||||
|
// See also UnmarshalJSON.
|
||||||
|
func (x NodeInfo) MarshalJSON() ([]byte, error) {
|
||||||
|
var m netmap.NodeInfo
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
|
return m.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON decodes NeoFS API protocol JSON format into the NodeInfo
|
||||||
|
// (Protocol Buffers JSON). Returns an error describing a format violation.
|
||||||
|
//
|
||||||
|
// See also MarshalJSON.
|
||||||
|
func (x *NodeInfo) UnmarshalJSON(data []byte) error {
|
||||||
|
var m netmap.NodeInfo
|
||||||
|
|
||||||
|
err := m.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.readFromV2(m, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPublicKey sets binary-encoded public key bound to the node. The key
|
||||||
|
// authenticates the storage node, so it MUST be unique within the network.
|
||||||
|
//
|
||||||
|
// Argument MUST NOT be mutated, make a copy first.
|
||||||
|
//
|
||||||
|
// See also PublicKey.
|
||||||
|
func (x *NodeInfo) SetPublicKey(key []byte) {
|
||||||
|
x.m.SetPublicKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey returns value set using SetPublicKey.
|
||||||
|
//
|
||||||
|
// Zero NodeInfo has no public key, which is incorrect according to
|
||||||
|
// NeoFS system requirements.
|
||||||
|
//
|
||||||
|
// Return value MUST not be mutated, make a copy first.
|
||||||
|
func (x NodeInfo) PublicKey() []byte {
|
||||||
|
return x.m.GetPublicKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetworkEndpoints sets list to the announced node's network endpoints.
|
||||||
|
// Node MUSt have at least one announced endpoint. List MUST be unique.
|
||||||
|
// Endpoints are used for communication with the storage node within NeoFS
|
||||||
|
// network. It is expected that node serves storage node services on these
|
||||||
|
// endpoints (it also adds a wait on their network availability).
|
||||||
|
//
|
||||||
|
// Argument MUST NOT be mutated, make a copy first.
|
||||||
|
//
|
||||||
|
// See also IterateNetworkEndpoints.
|
||||||
|
func (x *NodeInfo) SetNetworkEndpoints(v ...string) {
|
||||||
|
x.m.SetAddresses(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumberOfNetworkEndpoints returns number of network endpoints announced by the node.
|
||||||
|
//
|
||||||
|
// See also SetNetworkEndpoints.
|
||||||
|
func (x NodeInfo) NumberOfNetworkEndpoints() int {
|
||||||
|
return x.m.NumberOfAddresses()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateNetworkEndpoints iterates over network endpoints announced by the
|
||||||
|
// node and pass them into f. Breaks iteration on f's true return. Handler
|
||||||
|
// MUST NOT be nil.
|
||||||
|
//
|
||||||
|
// Zero NodeInfo contains no endpoints which is incorrect according to
|
||||||
|
// NeoFS system requirements.
|
||||||
|
//
|
||||||
|
// See also SetNetworkEndpoints.
|
||||||
|
func (x NodeInfo) IterateNetworkEndpoints(f func(string) bool) {
|
||||||
|
x.m.IterateAddresses(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateNetworkEndpoints is an extra-sugared function over IterateNetworkEndpoints
|
||||||
|
// method which allows to unconditionally iterate over all node's network endpoints.
|
||||||
|
func IterateNetworkEndpoints(node NodeInfo, f func(string)) {
|
||||||
|
node.IterateNetworkEndpoints(func(addr string) bool {
|
||||||
f(addr)
|
f(addr)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAddresses sets list of network addresses of the node.
|
// assert NodeInfo type provides hrw.Hasher required for HRW sorting.
|
||||||
func (i *NodeInfo) SetAddresses(v ...string) {
|
var _ hrw.Hasher = NodeInfo{}
|
||||||
if i.m == nil {
|
|
||||||
i.m = new(netmap.NodeInfo)
|
// Hash implements hrw.Hasher interface.
|
||||||
|
//
|
||||||
|
// Hash is needed to support weighted HRW therefore sort function sorts nodes
|
||||||
|
// based on their public key. Hash isn't expected to be used directly.
|
||||||
|
func (x NodeInfo) Hash() uint64 {
|
||||||
|
return hrw.Hash(x.m.GetPublicKey())
|
||||||
}
|
}
|
||||||
|
|
||||||
i.m.SetAddresses(v...)
|
// less declares "less than" comparison between two NodeInfo instances:
|
||||||
|
// x1 is less than x2 if it has less Hash().
|
||||||
|
//
|
||||||
|
// Method is needed for internal placement needs.
|
||||||
|
func less(x1, x2 NodeInfo) bool {
|
||||||
|
return x1.Hash() < x2.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes returns list of the node attributes.
|
func (x *NodeInfo) setNumericAttribute(key string, num uint64) {
|
||||||
func (i *NodeInfo) Attributes() []NodeAttribute {
|
x.SetAttribute(key, strconv.FormatUint(num, 10))
|
||||||
if i == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
as := i.m.GetAttributes()
|
// SetPrice sets the storage cost declared by the node. By default, zero
|
||||||
|
// price is announced.
|
||||||
if as == nil {
|
func (x *NodeInfo) SetPrice(price uint64) {
|
||||||
return nil
|
x.setNumericAttribute(attrPrice, price)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make([]NodeAttribute, len(as))
|
// Price returns price set using SetPrice.
|
||||||
|
//
|
||||||
for ind := range as {
|
// Zero NodeInfo has zero price.
|
||||||
res[ind] = *NewNodeAttributeFromV2(&as[ind])
|
func (x NodeInfo) Price() uint64 {
|
||||||
|
val := x.Attribute(attrPrice)
|
||||||
|
if val == "" {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
price, err := strconv.ParseUint(val, 10, 64)
|
||||||
}
|
|
||||||
|
|
||||||
// SetAttributes sets list of the node attributes.
|
|
||||||
func (i *NodeInfo) SetAttributes(as ...NodeAttribute) {
|
|
||||||
asV2 := make([]netmap.Attribute, len(as))
|
|
||||||
|
|
||||||
for ind := range as {
|
|
||||||
asV2[ind] = *as[ind].ToV2()
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.m == nil {
|
|
||||||
i.m = new(netmap.NodeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
i.m.SetAttributes(asV2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// State returns node state.
|
|
||||||
func (i *NodeInfo) State() NodeState {
|
|
||||||
return NodeStateFromV2(i.m.GetState())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetState sets node state.
|
|
||||||
func (i *NodeInfo) SetState(s NodeState) {
|
|
||||||
if i.m == nil {
|
|
||||||
i.m = new(netmap.NodeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
i.m.SetState(s.ToV2())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals NodeInfo into a protobuf binary form.
|
|
||||||
func (i *NodeInfo) Marshal() ([]byte, error) {
|
|
||||||
return i.m.StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of NodeInfo.
|
|
||||||
func (i *NodeInfo) Unmarshal(data []byte) error {
|
|
||||||
if i.m == nil {
|
|
||||||
i.m = new(netmap.NodeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.m.Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes NodeInfo to protobuf JSON format.
|
|
||||||
func (i *NodeInfo) MarshalJSON() ([]byte, error) {
|
|
||||||
return i.m.MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes NodeInfo from protobuf JSON format.
|
|
||||||
func (i *NodeInfo) UnmarshalJSON(data []byte) error {
|
|
||||||
if i.m == nil {
|
|
||||||
i.m = new(netmap.NodeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := i.m.UnmarshalJSON(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
panic(fmt.Sprintf("unexpected price parsing error %s: %v", val, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
i.syncAttributes()
|
return price
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCapacity sets the storage capacity declared by the node. By default, zero
|
||||||
|
// capacity is announced.
|
||||||
|
func (x *NodeInfo) SetCapacity(capacity uint64) {
|
||||||
|
x.setNumericAttribute(attrCapacity, capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// capacity returns capacity set using SetCapacity.
|
||||||
|
//
|
||||||
|
// Zero NodeInfo has zero capacity.
|
||||||
|
func (x NodeInfo) capacity() uint64 {
|
||||||
|
val := x.Attribute(attrCapacity)
|
||||||
|
if val == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
capacity, err := strconv.ParseUint(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected capacity parsing error %s: %v", val, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrUNLOCODE = "UN-LOCODE"
|
||||||
|
|
||||||
|
// SetLOCODE specifies node's geographic location in UN/LOCODE format. Each
|
||||||
|
// storage node MUST declare it for entrance to the NeoFS network. Node MAY
|
||||||
|
// declare the code of the nearest location as needed, for example, when it is
|
||||||
|
// impossible to unambiguously attribute the node to any location from UN/LOCODE
|
||||||
|
// database.
|
||||||
|
//
|
||||||
|
// See also LOCODE.
|
||||||
|
func (x *NodeInfo) SetLOCODE(locode string) {
|
||||||
|
x.SetAttribute(attrUNLOCODE, locode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOCODE returns node's location code set using SetLOCODE.
|
||||||
|
//
|
||||||
|
// Zero NodeInfo has empty location code which is invalid according to
|
||||||
|
// NeoFS API system requirement.
|
||||||
|
func (x NodeInfo) LOCODE() string {
|
||||||
|
return x.Attribute(attrUNLOCODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCountryCode sets code of the country in ISO 3166-1_alpha-2 to which
|
||||||
|
// storage node belongs (or the closest one).
|
||||||
|
//
|
||||||
|
// SetCountryCode is intended only for processing the network registration
|
||||||
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
||||||
|
func (x *NodeInfo) SetCountryCode(countryCode string) {
|
||||||
|
x.SetAttribute("CountryCode", countryCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCountryName sets short name of the country in ISO-3166 format to which
|
||||||
|
// storage node belongs (or the closest one).
|
||||||
|
//
|
||||||
|
// SetCountryName is intended only for processing the network registration
|
||||||
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
||||||
|
func (x *NodeInfo) SetCountryName(country string) {
|
||||||
|
x.SetAttribute("Country", country)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLocationName sets storage node's location name from "NameWoDiacritics"
|
||||||
|
// column in the UN/LOCODE record corresponding to the specified LOCODE.
|
||||||
|
//
|
||||||
|
// SetLocationName is intended only for processing the network registration
|
||||||
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
||||||
|
func (x *NodeInfo) SetLocationName(location string) {
|
||||||
|
x.SetAttribute("Location", location)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSubdivisionCode sets storage node's subdivision code from "SubDiv" column in
|
||||||
|
// the UN/LOCODE record corresponding to the specified LOCODE.
|
||||||
|
//
|
||||||
|
// SetSubdivisionCode is intended only for processing the network registration
|
||||||
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
||||||
|
func (x *NodeInfo) SetSubdivisionCode(subDiv string) {
|
||||||
|
x.SetAttribute("SubDivCode", subDiv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSubdivisionName sets storage node's subdivision name in ISO 3166-2 format.
|
||||||
|
//
|
||||||
|
// SetSubdivisionName is intended only for processing the network registration
|
||||||
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
||||||
|
func (x *NodeInfo) SetSubdivisionName(subDiv string) {
|
||||||
|
x.SetAttribute("SubDiv", subDiv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContinentName sets name of the storage node's continent from
|
||||||
|
// Seven-Continent model.
|
||||||
|
//
|
||||||
|
// SetContinentName is intended only for processing the network registration
|
||||||
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
||||||
|
func (x *NodeInfo) SetContinentName(continent string) {
|
||||||
|
x.SetAttribute("Continent", continent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enumeration of well-known attributes.
|
||||||
|
const (
|
||||||
|
// attrPrice is a key to the node attribute that indicates the
|
||||||
|
// price in GAS tokens for storing one GB of data during one Epoch.
|
||||||
|
attrPrice = "Price"
|
||||||
|
|
||||||
|
// attrCapacity is a key to the node attribute that indicates the
|
||||||
|
// total available disk space in Gigabytes.
|
||||||
|
attrCapacity = "Capacity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NumberOfAttributes returns number of attributes announced by the node.
|
||||||
|
//
|
||||||
|
// See also SetAttribute.
|
||||||
|
func (x NodeInfo) NumberOfAttributes() int {
|
||||||
|
return len(x.m.GetAttributes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateAttributes iterates over all node attributes and passes the into f.
|
||||||
|
// Handler MUST NOT be nil.
|
||||||
|
func (x NodeInfo) IterateAttributes(f func(key, value string)) {
|
||||||
|
a := x.m.GetAttributes()
|
||||||
|
for i := range a {
|
||||||
|
f(a[i].GetKey(), a[i].GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAttribute sets value of the node attribute value by the given key.
|
||||||
|
// Both key and value MUST NOT be empty.
|
||||||
|
func (x *NodeInfo) SetAttribute(key, value string) {
|
||||||
|
if key == "" {
|
||||||
|
panic("empty key in SetAttribute")
|
||||||
|
} else if value == "" {
|
||||||
|
panic("empty value in SetAttribute")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := x.m.GetAttributes()
|
||||||
|
for i := range a {
|
||||||
|
if a[i].GetKey() == key {
|
||||||
|
a[i].SetValue(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a = append(a, netmap.Attribute{})
|
||||||
|
a[len(a)-1].SetKey(key)
|
||||||
|
a[len(a)-1].SetValue(value)
|
||||||
|
|
||||||
|
x.m.SetAttributes(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attribute returns value of the node attribute set using SetAttribute by the
|
||||||
|
// given key. Returns empty string if attribute is missing.
|
||||||
|
func (x NodeInfo) Attribute(key string) string {
|
||||||
|
a := x.m.GetAttributes()
|
||||||
|
for i := range a {
|
||||||
|
if a[i].GetKey() == key {
|
||||||
|
return a[i].GetValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortAttributes sorts node attributes set using SetAttribute lexicographically.
|
||||||
|
// The method is only needed to make NodeInfo consistent, e.g. for signing.
|
||||||
|
func (x *NodeInfo) SortAttributes() {
|
||||||
|
as := x.m.GetAttributes()
|
||||||
|
if len(as) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(as, func(i, j int) bool {
|
||||||
|
switch strings.Compare(as[i].GetKey(), as[j].GetKey()) {
|
||||||
|
case -1:
|
||||||
|
return true
|
||||||
|
case 1:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return as[i].GetValue() < as[j].GetValue()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
x.m.SetAttributes(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnterSubnet writes storage node's intention to enter the given subnet.
|
||||||
|
//
|
||||||
|
// Zero NodeInfo belongs to zero subnet.
|
||||||
|
func (x *NodeInfo) EnterSubnet(id subnetid.ID) {
|
||||||
|
x.changeSubnet(id, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitSubnet writes storage node's intention to exit the given subnet.
|
||||||
|
func (x *NodeInfo) ExitSubnet(id subnetid.ID) {
|
||||||
|
x.changeSubnet(id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NodeInfo) changeSubnet(id subnetid.ID, isMember bool) {
|
||||||
|
var (
|
||||||
|
idv2 refs.SubnetID
|
||||||
|
info netmap.NodeSubnetInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
id.WriteToV2(&idv2)
|
||||||
|
|
||||||
|
info.SetID(&idv2)
|
||||||
|
info.SetEntryFlag(isMember)
|
||||||
|
|
||||||
|
netmap.WriteSubnetInfo(&x.m, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRemoveSubnet is returned when a node needs to leave the subnet.
|
||||||
|
var ErrRemoveSubnet = netmap.ErrRemoveSubnet
|
||||||
|
|
||||||
|
// IterateSubnets iterates over all subnets the node belongs to and passes the IDs to f.
|
||||||
|
// Handler MUST NOT be nil.
|
||||||
|
//
|
||||||
|
// If f returns ErrRemoveSubnet, then removes subnet entry. Note that this leads to an
|
||||||
|
// instant mutation of NodeInfo. Breaks on any other non-nil error and returns it.
|
||||||
|
//
|
||||||
|
// Returns an error if subnet incorrectly enabled/disabled.
|
||||||
|
// Returns an error if the node is not included to any subnet by the end of the loop.
|
||||||
|
//
|
||||||
|
// See also EnterSubnet, ExitSubnet.
|
||||||
|
func (x NodeInfo) IterateSubnets(f func(subnetid.ID) error) error {
|
||||||
|
var id subnetid.ID
|
||||||
|
|
||||||
|
return netmap.IterateSubnets(&x.m, func(idv2 refs.SubnetID) error {
|
||||||
|
err := id.ReadFromV2(idv2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid subnet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f(id)
|
||||||
|
if errors.Is(err, ErrRemoveSubnet) {
|
||||||
|
return netmap.ErrRemoveSubnet
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var errAbortSubnetIter = errors.New("abort subnet iterator")
|
||||||
|
|
||||||
|
// BelongsToSubnet is a helper function over the IterateSubnets method which
|
||||||
|
// checks whether a node belongs to a subnet.
|
||||||
|
//
|
||||||
|
// Zero NodeInfo belongs to zero subnet only.
|
||||||
|
func BelongsToSubnet(node NodeInfo, id subnetid.ID) bool {
|
||||||
|
err := node.IterateSubnets(func(id_ subnetid.ID) error {
|
||||||
|
if id.Equals(id_) {
|
||||||
|
return errAbortSubnetIter
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return errors.Is(err, errAbortSubnetIter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOffline sets the state of the node to "offline". When a node updates
|
||||||
|
// information about itself in the network map, this action is interpreted as
|
||||||
|
// an intention to leave the network.
|
||||||
|
func (x *NodeInfo) SetOffline() {
|
||||||
|
x.m.SetState(netmap.Offline)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOffline checks if the node is in the "offline" state.
|
||||||
|
//
|
||||||
|
// Zero NodeInfo has undefined state which is not offline (note that it does not
|
||||||
|
// mean online).
|
||||||
|
//
|
||||||
|
// See also SetOffline.
|
||||||
|
func (x NodeInfo) IsOffline() bool {
|
||||||
|
return x.m.GetState() == netmap.Offline
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOnline sets the state of the node to "online". When a node updates
|
||||||
|
// information about itself in the network map, this
|
||||||
|
// action is interpreted as an intention to enter the network.
|
||||||
|
//
|
||||||
|
// See also IsOnline.
|
||||||
|
func (x *NodeInfo) SetOnline() {
|
||||||
|
x.m.SetState(netmap.Online)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOnline checks if the node is in the "online" state.
|
||||||
|
//
|
||||||
|
// Zero NodeInfo has undefined state which is not online (note that it does not
|
||||||
|
// mean offline).
|
||||||
|
//
|
||||||
|
// See also SetOnline.
|
||||||
|
func (x NodeInfo) IsOnline() bool {
|
||||||
|
return x.m.GetState() == netmap.Online
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,261 +3,22 @@ package netmap
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
testv2 "github.com/nspcc-dev/neofs-api-go/v2/netmap/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNodeStateFromV2(t *testing.T) {
|
func TestNodeInfo_SetAttribute(t *testing.T) {
|
||||||
for _, item := range []struct {
|
var n NodeInfo
|
||||||
s NodeState
|
|
||||||
sV2 netmap.NodeState
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
s: 0,
|
|
||||||
sV2: netmap.UnspecifiedState,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
s: NodeStateOnline,
|
|
||||||
sV2: netmap.Online,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
s: NodeStateOffline,
|
|
||||||
sV2: netmap.Offline,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
require.Equal(t, item.s, NodeStateFromV2(item.sV2))
|
|
||||||
require.Equal(t, item.sV2, item.s.ToV2())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeAttributeFromV2(t *testing.T) {
|
const key = "some key"
|
||||||
t.Run("from nil", func(t *testing.T) {
|
|
||||||
var x *netmap.Attribute
|
|
||||||
|
|
||||||
require.Nil(t, NewNodeAttributeFromV2(x))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("from non-nil", func(t *testing.T) {
|
|
||||||
aV2 := testv2.GenerateAttribute(false)
|
|
||||||
|
|
||||||
a := NewNodeAttributeFromV2(aV2)
|
|
||||||
|
|
||||||
require.Equal(t, aV2, a.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeAttribute_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *NodeAttribute
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeAttribute_Key(t *testing.T) {
|
|
||||||
a := NewNodeAttribute()
|
|
||||||
key := "some key"
|
|
||||||
|
|
||||||
a.SetKey(key)
|
|
||||||
|
|
||||||
require.Equal(t, key, a.Key())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeAttribute_Value(t *testing.T) {
|
|
||||||
a := NewNodeAttribute()
|
|
||||||
val := "some value"
|
val := "some value"
|
||||||
|
|
||||||
a.SetValue(val)
|
require.Zero(t, n.Attribute(val))
|
||||||
|
|
||||||
require.Equal(t, val, a.Value())
|
n.SetAttribute(key, val)
|
||||||
}
|
require.Equal(t, val, n.Attribute(key))
|
||||||
|
|
||||||
func TestNodeAttribute_ParentKeys(t *testing.T) {
|
val = "some other value"
|
||||||
a := NewNodeAttribute()
|
|
||||||
keys := []string{"par1", "par2"}
|
n.SetAttribute(key, val)
|
||||||
|
require.Equal(t, val, n.Attribute(key))
|
||||||
a.SetParentKeys(keys...)
|
|
||||||
|
|
||||||
require.Equal(t, keys, a.ParentKeys())
|
|
||||||
}
|
|
||||||
|
|
||||||
func testNodeAttribute() *NodeAttribute {
|
|
||||||
a := new(NodeAttribute)
|
|
||||||
a.SetKey("key")
|
|
||||||
a.SetValue("value")
|
|
||||||
a.SetParentKeys("par1", "par2")
|
|
||||||
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeInfoFromV2(t *testing.T) {
|
|
||||||
t.Run("from nil", func(t *testing.T) {
|
|
||||||
var x *netmap.NodeInfo
|
|
||||||
|
|
||||||
require.Nil(t, NewNodeInfoFromV2(x).m)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("from non-nil", func(t *testing.T) {
|
|
||||||
iV2 := testv2.GenerateNodeInfo(false)
|
|
||||||
|
|
||||||
i := NewNodeInfoFromV2(iV2)
|
|
||||||
|
|
||||||
require.Equal(t, iV2, i.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeInfo_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *NodeInfo
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeInfo_PublicKey(t *testing.T) {
|
|
||||||
i := new(NodeInfo)
|
|
||||||
key := []byte{1, 2, 3}
|
|
||||||
|
|
||||||
i.SetPublicKey(key)
|
|
||||||
|
|
||||||
require.Equal(t, key, i.PublicKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeInfo_IterateAddresses(t *testing.T) {
|
|
||||||
i := new(NodeInfo)
|
|
||||||
|
|
||||||
as := []string{"127.0.0.1:8080", "127.0.0.1:8081"}
|
|
||||||
|
|
||||||
i.SetAddresses(as...)
|
|
||||||
|
|
||||||
as2 := make([]string, 0, i.NumberOfAddresses())
|
|
||||||
|
|
||||||
IterateAllAddresses(i, func(addr string) {
|
|
||||||
as2 = append(as2, addr)
|
|
||||||
})
|
|
||||||
|
|
||||||
require.Equal(t, as, as2)
|
|
||||||
require.EqualValues(t, len(as), i.NumberOfAddresses())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeInfo_State(t *testing.T) {
|
|
||||||
i := new(NodeInfo)
|
|
||||||
s := NodeStateOnline
|
|
||||||
|
|
||||||
i.SetState(s)
|
|
||||||
|
|
||||||
require.Equal(t, s, i.State())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeInfo_Attributes(t *testing.T) {
|
|
||||||
i := new(NodeInfo)
|
|
||||||
as := []NodeAttribute{*testNodeAttribute(), *testNodeAttribute()}
|
|
||||||
|
|
||||||
i.SetAttributes(as...)
|
|
||||||
|
|
||||||
require.Equal(t, as, i.Attributes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeAttributeEncoding(t *testing.T) {
|
|
||||||
a := testNodeAttribute()
|
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
|
||||||
data, err := a.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
a2 := NewNodeAttribute()
|
|
||||||
require.NoError(t, a2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, a, a2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
|
||||||
data, err := a.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
a2 := NewNodeAttribute()
|
|
||||||
require.NoError(t, a2.UnmarshalJSON(data))
|
|
||||||
|
|
||||||
require.Equal(t, a, a2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeInfoEncoding(t *testing.T) {
|
|
||||||
i := NewNodeInfo()
|
|
||||||
i.SetPublicKey([]byte{1, 2, 3})
|
|
||||||
i.SetAddresses("192.168.0.1", "192.168.0.2")
|
|
||||||
i.SetState(NodeStateOnline)
|
|
||||||
i.SetAttributes(*testNodeAttribute())
|
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
|
||||||
data, err := i.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
i2 := NewNodeInfo()
|
|
||||||
require.NoError(t, i2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, i, i2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
|
||||||
data, err := i.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
i2 := NewNodeInfo()
|
|
||||||
require.NoError(t, i2.UnmarshalJSON(data))
|
|
||||||
|
|
||||||
require.Equal(t, i, i2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewNodeAttribute(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
attr := NewNodeAttribute()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Empty(t, attr.Key())
|
|
||||||
require.Empty(t, attr.Value())
|
|
||||||
require.Nil(t, attr.ParentKeys())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
attrV2 := attr.ToV2()
|
|
||||||
|
|
||||||
require.Empty(t, attrV2.GetKey())
|
|
||||||
require.Empty(t, attrV2.GetValue())
|
|
||||||
require.Nil(t, attrV2.GetParents())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewNodeInfo(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
ni := NewNodeInfo()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Nil(t, ni.PublicKey())
|
|
||||||
|
|
||||||
require.Zero(t, ni.NumberOfAddresses())
|
|
||||||
require.Nil(t, ni.Attributes())
|
|
||||||
require.Zero(t, ni.State())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
niV2 := ni.ToV2()
|
|
||||||
|
|
||||||
require.Nil(t, niV2.GetPublicKey())
|
|
||||||
require.Zero(t, niV2.NumberOfAddresses())
|
|
||||||
require.Nil(t, niV2.GetAttributes())
|
|
||||||
require.EqualValues(t, netmap.UnspecifiedState, niV2.GetState())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeState_String(t *testing.T) {
|
|
||||||
toPtr := func(v NodeState) *NodeState {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
testEnumStrings(t, new(NodeState), []enumStringItem{
|
|
||||||
{val: toPtr(NodeStateOnline), str: "ONLINE"},
|
|
||||||
{val: toPtr(NodeStateOffline), str: "OFFLINE"},
|
|
||||||
{val: toPtr(0), str: "UNSPECIFIED"},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
package netmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Operation is an enumeration of v2-compatible filtering operations.
|
|
||||||
type Operation uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
_ Operation = iota
|
|
||||||
|
|
||||||
// OpEQ is an "Equal" operation.
|
|
||||||
OpEQ
|
|
||||||
|
|
||||||
// OpNE is a "Not equal" operation.
|
|
||||||
OpNE
|
|
||||||
|
|
||||||
// OpGT is a "Greater than" operation.
|
|
||||||
OpGT
|
|
||||||
|
|
||||||
// OpGE is a "Greater than or equal to" operation.
|
|
||||||
OpGE
|
|
||||||
|
|
||||||
// OpLT is a "Less than" operation.
|
|
||||||
OpLT
|
|
||||||
|
|
||||||
// OpLE is a "Less than or equal to" operation.
|
|
||||||
OpLE
|
|
||||||
|
|
||||||
// OpOR is an "OR" operation.
|
|
||||||
OpOR
|
|
||||||
|
|
||||||
// OpAND is an "AND" operation.
|
|
||||||
OpAND
|
|
||||||
)
|
|
||||||
|
|
||||||
// OperationFromV2 converts v2 Operation to Operation.
|
|
||||||
func OperationFromV2(op netmap.Operation) Operation {
|
|
||||||
switch op {
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
case netmap.OR:
|
|
||||||
return OpOR
|
|
||||||
case netmap.AND:
|
|
||||||
return OpAND
|
|
||||||
case netmap.GE:
|
|
||||||
return OpGE
|
|
||||||
case netmap.GT:
|
|
||||||
return OpGT
|
|
||||||
case netmap.LE:
|
|
||||||
return OpLE
|
|
||||||
case netmap.LT:
|
|
||||||
return OpLT
|
|
||||||
case netmap.EQ:
|
|
||||||
return OpEQ
|
|
||||||
case netmap.NE:
|
|
||||||
return OpNE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts Operation to v2 Operation.
|
|
||||||
func (op Operation) ToV2() netmap.Operation {
|
|
||||||
switch op {
|
|
||||||
default:
|
|
||||||
return netmap.UnspecifiedOperation
|
|
||||||
case OpOR:
|
|
||||||
return netmap.OR
|
|
||||||
case OpAND:
|
|
||||||
return netmap.AND
|
|
||||||
case OpGE:
|
|
||||||
return netmap.GE
|
|
||||||
case OpGT:
|
|
||||||
return netmap.GT
|
|
||||||
case OpLE:
|
|
||||||
return netmap.LE
|
|
||||||
case OpLT:
|
|
||||||
return netmap.LT
|
|
||||||
case OpEQ:
|
|
||||||
return netmap.EQ
|
|
||||||
case OpNE:
|
|
||||||
return netmap.NE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of Operation.
|
|
||||||
//
|
|
||||||
// String mapping:
|
|
||||||
// * OpNE: NE;
|
|
||||||
// * OpEQ: EQ;
|
|
||||||
// * OpLT: LT;
|
|
||||||
// * OpLE: LE;
|
|
||||||
// * OpGT: GT;
|
|
||||||
// * OpGE: GE;
|
|
||||||
// * OpAND: AND;
|
|
||||||
// * OpOR: OR;
|
|
||||||
// * default: OPERATION_UNSPECIFIED.
|
|
||||||
func (op Operation) String() string {
|
|
||||||
return op.ToV2().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromString parses Operation from a string representation.
|
|
||||||
// It is a reverse action to String().
|
|
||||||
//
|
|
||||||
// Returns true if s was parsed successfully.
|
|
||||||
func (op *Operation) FromString(s string) bool {
|
|
||||||
var g netmap.Operation
|
|
||||||
|
|
||||||
ok := g.FromString(s)
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
*op = OperationFromV2(g)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
package netmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOperationFromV2(t *testing.T) {
|
|
||||||
for _, item := range []struct {
|
|
||||||
op Operation
|
|
||||||
opV2 netmap.Operation
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
op: 0,
|
|
||||||
opV2: netmap.UnspecifiedOperation,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: OpEQ,
|
|
||||||
opV2: netmap.EQ,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: OpNE,
|
|
||||||
opV2: netmap.NE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: OpOR,
|
|
||||||
opV2: netmap.OR,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: OpAND,
|
|
||||||
opV2: netmap.AND,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: OpLE,
|
|
||||||
opV2: netmap.LE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: OpLT,
|
|
||||||
opV2: netmap.LT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: OpGT,
|
|
||||||
opV2: netmap.GT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: OpGE,
|
|
||||||
opV2: netmap.GE,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
require.Equal(t, item.op, OperationFromV2(item.opV2))
|
|
||||||
require.Equal(t, item.opV2, item.op.ToV2())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOperation_String(t *testing.T) {
|
|
||||||
toPtr := func(v Operation) *Operation {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
testEnumStrings(t, new(Operation), []enumStringItem{
|
|
||||||
{val: toPtr(OpEQ), str: "EQ"},
|
|
||||||
{val: toPtr(OpNE), str: "NE"},
|
|
||||||
{val: toPtr(OpGT), str: "GT"},
|
|
||||||
{val: toPtr(OpGE), str: "GE"},
|
|
||||||
{val: toPtr(OpLT), str: "LT"},
|
|
||||||
{val: toPtr(OpLE), str: "LE"},
|
|
||||||
{val: toPtr(OpAND), str: "AND"},
|
|
||||||
{val: toPtr(OpOR), str: "OR"},
|
|
||||||
{val: toPtr(0), str: "OPERATION_UNSPECIFIED"},
|
|
||||||
})
|
|
||||||
}
|
|
919
netmap/policy.go
919
netmap/policy.go
|
@ -1,176 +1,787 @@
|
||||||
package netmap
|
package netmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/antlr/antlr4/runtime/Go/antlr"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/netmap/parser"
|
||||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PlacementPolicy represents v2-compatible placement policy.
|
// PlacementPolicy declares policy to store objects in the NeoFS container.
|
||||||
type PlacementPolicy netmap.PlacementPolicy
|
// Within itself, PlacementPolicy represents a set of rules to select a subset
|
||||||
|
// of nodes from NeoFS network map - node-candidates for object storage.
|
||||||
// NewPlacementPolicy creates and returns new PlacementPolicy instance.
|
|
||||||
//
|
//
|
||||||
// Defaults:
|
// PlacementPolicy is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/netmap.PlacementPolicy
|
||||||
// - backupFactor: 0;
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
// - replicas nil;
|
|
||||||
// - selectors nil;
|
|
||||||
// - filters nil.
|
|
||||||
func NewPlacementPolicy() *PlacementPolicy {
|
|
||||||
return NewPlacementPolicyFromV2(new(netmap.PlacementPolicy))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPlacementPolicyFromV2 converts v2 PlacementPolicy to PlacementPolicy.
|
|
||||||
//
|
//
|
||||||
// Nil netmap.PlacementPolicy converts to nil.
|
// Instances can be created using built-in var declaration.
|
||||||
func NewPlacementPolicyFromV2(f *netmap.PlacementPolicy) *PlacementPolicy {
|
type PlacementPolicy struct {
|
||||||
return (*PlacementPolicy)(f)
|
backupFactor uint32
|
||||||
|
|
||||||
|
subnet subnetid.ID
|
||||||
|
|
||||||
|
filters []netmap.Filter
|
||||||
|
|
||||||
|
selectors []netmap.Selector
|
||||||
|
|
||||||
|
replicas []netmap.Replica
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV2 converts PlacementPolicy to v2 PlacementPolicy.
|
func (p *PlacementPolicy) readFromV2(m netmap.PlacementPolicy) error {
|
||||||
//
|
p.backupFactor = m.GetContainerBackupFactor()
|
||||||
// Nil PlacementPolicy converts to nil.
|
p.filters = m.GetFilters()
|
||||||
func (p *PlacementPolicy) ToV2() *netmap.PlacementPolicy {
|
p.selectors = m.GetSelectors()
|
||||||
return (*netmap.PlacementPolicy)(p)
|
p.replicas = m.GetReplicas()
|
||||||
}
|
|
||||||
|
|
||||||
// SubnetID returns subnet to select nodes from.
|
subnetV2 := m.GetSubnetID()
|
||||||
func (p *PlacementPolicy) SubnetID() *subnetid.ID {
|
if subnetV2 != nil {
|
||||||
idv2 := (*netmap.PlacementPolicy)(p).GetSubnetID()
|
err := p.subnet.ReadFromV2(*subnetV2)
|
||||||
if idv2 == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var id subnetid.ID
|
|
||||||
|
|
||||||
err := id.ReadFromV2(*idv2)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // will disappear after netmap package refactor
|
return fmt.Errorf("invalid subnet: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.subnet = subnetid.ID{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &id
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSubnetID sets subnet to select nodes from.
|
|
||||||
func (p *PlacementPolicy) SetSubnetID(subnet *subnetid.ID) {
|
|
||||||
var idv2 *refs.SubnetID
|
|
||||||
|
|
||||||
if subnet != nil {
|
|
||||||
idv2 = new(refs.SubnetID)
|
|
||||||
subnet.WriteToV2(idv2)
|
|
||||||
}
|
|
||||||
|
|
||||||
(*netmap.PlacementPolicy)(p).SetSubnetID(idv2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replicas returns list of object replica descriptors.
|
|
||||||
func (p *PlacementPolicy) Replicas() []Replica {
|
|
||||||
rs := (*netmap.PlacementPolicy)(p).
|
|
||||||
GetReplicas()
|
|
||||||
|
|
||||||
if rs == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make([]Replica, len(rs))
|
|
||||||
|
|
||||||
for i := range rs {
|
|
||||||
res[i] = *NewReplicaFromV2(&rs[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReplicas sets list of object replica descriptors.
|
|
||||||
func (p *PlacementPolicy) SetReplicas(rs ...Replica) {
|
|
||||||
var rsV2 []netmap.Replica
|
|
||||||
|
|
||||||
if rs != nil {
|
|
||||||
rsV2 = make([]netmap.Replica, len(rs))
|
|
||||||
|
|
||||||
for i := range rs {
|
|
||||||
rsV2[i] = *rs[i].ToV2()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(*netmap.PlacementPolicy)(p).SetReplicas(rsV2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerBackupFactor returns container backup factor.
|
|
||||||
func (p *PlacementPolicy) ContainerBackupFactor() uint32 {
|
|
||||||
return (*netmap.PlacementPolicy)(p).
|
|
||||||
GetContainerBackupFactor()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetContainerBackupFactor sets container backup factor.
|
|
||||||
func (p *PlacementPolicy) SetContainerBackupFactor(f uint32) {
|
|
||||||
(*netmap.PlacementPolicy)(p).
|
|
||||||
SetContainerBackupFactor(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selector returns set of selectors to form the container's nodes subset.
|
|
||||||
func (p *PlacementPolicy) Selectors() []Selector {
|
|
||||||
rs := (*netmap.PlacementPolicy)(p).
|
|
||||||
GetSelectors()
|
|
||||||
|
|
||||||
if rs == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res := make([]Selector, len(rs))
|
|
||||||
|
|
||||||
for i := range rs {
|
|
||||||
res[i] = *NewSelectorFromV2(&rs[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSelectors sets set of selectors to form the container's nodes subset.
|
|
||||||
func (p *PlacementPolicy) SetSelectors(ss ...Selector) {
|
|
||||||
var ssV2 []netmap.Selector
|
|
||||||
|
|
||||||
if ss != nil {
|
|
||||||
ssV2 = make([]netmap.Selector, len(ss))
|
|
||||||
|
|
||||||
for i := range ss {
|
|
||||||
ssV2[i] = *ss[i].ToV2()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(*netmap.PlacementPolicy)(p).SetSelectors(ssV2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters returns list of named filters to reference in selectors.
|
|
||||||
func (p *PlacementPolicy) Filters() []Filter {
|
|
||||||
return filtersFromV2(
|
|
||||||
(*netmap.PlacementPolicy)(p).
|
|
||||||
GetFilters(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFilters sets list of named filters to reference in selectors.
|
|
||||||
func (p *PlacementPolicy) SetFilters(fs ...Filter) {
|
|
||||||
(*netmap.PlacementPolicy)(p).
|
|
||||||
SetFilters(filtersToV2(fs))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals PlacementPolicy into a protobuf binary form.
|
|
||||||
func (p *PlacementPolicy) Marshal() ([]byte, error) {
|
|
||||||
return (*netmap.PlacementPolicy)(p).StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of PlacementPolicy.
|
|
||||||
func (p *PlacementPolicy) Unmarshal(data []byte) error {
|
|
||||||
return (*netmap.PlacementPolicy)(p).Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes PlacementPolicy to protobuf JSON format.
|
|
||||||
func (p *PlacementPolicy) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*netmap.PlacementPolicy)(p).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes PlacementPolicy from protobuf JSON format.
|
// UnmarshalJSON decodes PlacementPolicy from protobuf JSON format.
|
||||||
func (p *PlacementPolicy) UnmarshalJSON(data []byte) error {
|
func (p *PlacementPolicy) UnmarshalJSON(data []byte) error {
|
||||||
return (*netmap.PlacementPolicy)(p).UnmarshalJSON(data)
|
var m netmap.PlacementPolicy
|
||||||
|
|
||||||
|
err := m.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.readFromV2(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFromV2 reads PlacementPolicy from the netmap.PlacementPolicy message.
|
||||||
|
// Checks if the message conforms to NeoFS API V2 protocol.
|
||||||
|
//
|
||||||
|
// See also WriteToV2.
|
||||||
|
func (p *PlacementPolicy) ReadFromV2(m netmap.PlacementPolicy) error {
|
||||||
|
return p.readFromV2(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToV2 writes PlacementPolicy to the session.Token message.
|
||||||
|
// The message must not be nil.
|
||||||
|
//
|
||||||
|
// See also ReadFromV2.
|
||||||
|
func (p PlacementPolicy) WriteToV2(m *netmap.PlacementPolicy) {
|
||||||
|
var subnetV2 refs.SubnetID
|
||||||
|
p.subnet.WriteToV2(&subnetV2)
|
||||||
|
|
||||||
|
m.SetContainerBackupFactor(p.backupFactor)
|
||||||
|
m.SetSubnetID(&subnetV2)
|
||||||
|
m.SetFilters(p.filters)
|
||||||
|
m.SetSelectors(p.selectors)
|
||||||
|
m.SetReplicas(p.replicas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestrictSubnet sets a rule to select nodes from the given subnet only.
|
||||||
|
// By default, nodes from zero subnet are selected (whole network map).
|
||||||
|
func (p *PlacementPolicy) RestrictSubnet(subnet subnetid.ID) {
|
||||||
|
p.subnet = subnet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subnet returns subnet set using RestrictSubnet.
|
||||||
|
//
|
||||||
|
// Zero PlacementPolicy returns zero subnet meaning unlimited.
|
||||||
|
func (p PlacementPolicy) Subnet() subnetid.ID {
|
||||||
|
return p.subnet
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplicaDescriptor replica descriptor characterizes replicas of objects from
|
||||||
|
// the subset selected by a particular Selector.
|
||||||
|
type ReplicaDescriptor struct {
|
||||||
|
m netmap.Replica
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAmount sets number of object replicas.
|
||||||
|
func (r *ReplicaDescriptor) SetAmount(c uint32) {
|
||||||
|
r.m.SetCount(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amount returns number set using SetAmount.
|
||||||
|
//
|
||||||
|
// Zero ReplicaDescriptor has zero object amount.
|
||||||
|
func (r ReplicaDescriptor) Amount() uint32 {
|
||||||
|
return r.m.GetCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSelectorName sets name of the related Selector.
|
||||||
|
//
|
||||||
|
// Zero ReplicaDescriptor references to the root bucket's selector: it contains
|
||||||
|
// all possible nodes to store the object.
|
||||||
|
func (r *ReplicaDescriptor) SetSelectorName(s string) {
|
||||||
|
r.m.SetSelector(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReplicas adds a bunch object replica's characteristics.
|
||||||
|
//
|
||||||
|
// Zero PlacementPolicy does not declare replicas ???.
|
||||||
|
//
|
||||||
|
// See also IterateReplicas.
|
||||||
|
func (p *PlacementPolicy) AddReplicas(rs ...ReplicaDescriptor) {
|
||||||
|
off := len(p.replicas)
|
||||||
|
|
||||||
|
p.replicas = append(p.replicas, make([]netmap.Replica, len(rs))...)
|
||||||
|
|
||||||
|
for i := range rs {
|
||||||
|
p.replicas[off+i] = rs[i].m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumberOfReplicas returns amount of replica descriptors set using AddReplicas.
|
||||||
|
//
|
||||||
|
// Zero PlacementPolicy has no replicas.
|
||||||
|
func (p PlacementPolicy) NumberOfReplicas() int {
|
||||||
|
return len(p.replicas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplicaAmountByIndex returns amount of object replicas from the i-th replica
|
||||||
|
// descriptor. Index MUST be in range [0; NumberOfReplicas()).
|
||||||
|
//
|
||||||
|
// Zero PlacementPolicy has no replicas.
|
||||||
|
func (p PlacementPolicy) ReplicaAmountByIndex(i int) uint32 {
|
||||||
|
return p.replicas[i].GetCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContainerBackupFactor sets container backup factor: it controls how deep
|
||||||
|
// NeoFS will search for nodes alternatives to include into container's nodes subset.
|
||||||
|
//
|
||||||
|
// Zero PlacementPolicy has zero container backup factor.
|
||||||
|
func (p *PlacementPolicy) SetContainerBackupFactor(f uint32) {
|
||||||
|
p.backupFactor = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selector describes the bucket selection operator: choose a number of nodes
|
||||||
|
// from the bucket taking the nearest nodes to the related container by hash distance.
|
||||||
|
type Selector struct {
|
||||||
|
m netmap.Selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetName sets name with which the Selector can be referenced.
|
||||||
|
//
|
||||||
|
// Zero Selector is unnamed.
|
||||||
|
func (s *Selector) SetName(name string) {
|
||||||
|
s.m.SetName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeAmount sets number of nodes to select from the bucket.
|
||||||
|
//
|
||||||
|
// Zero Selector selects nothing.
|
||||||
|
func (s *Selector) SetNodeAmount(amount uint32) {
|
||||||
|
s.m.SetCount(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectByBucketAttribute sets attribute of the bucket to select nodes from.
|
||||||
|
//
|
||||||
|
// Zero Selector ???.
|
||||||
|
func (s *Selector) SelectByBucketAttribute(bucket string) {
|
||||||
|
s.m.SetAttribute(bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectSame makes selection algorithm to select only nodes having the same values
|
||||||
|
// of the bucket attribute.
|
||||||
|
//
|
||||||
|
// Zero Selector doesn't specify selection modifier so nodes are selected randomly.
|
||||||
|
//
|
||||||
|
// See also SelectByBucketAttribute.
|
||||||
|
func (s *Selector) SelectSame() {
|
||||||
|
s.m.SetClause(netmap.Same)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectDistinct makes selection algorithm to select only nodes having the different values
|
||||||
|
// of the bucket attribute.
|
||||||
|
//
|
||||||
|
// Zero Selector doesn't specify selection modifier so nodes are selected randomly.
|
||||||
|
//
|
||||||
|
// See also SelectByBucketAttribute.
|
||||||
|
func (s *Selector) SelectDistinct() {
|
||||||
|
s.m.SetClause(netmap.Distinct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFilterName sets name of filter to reference to select from ???.
|
||||||
|
//
|
||||||
|
// Zero Selector ???.
|
||||||
|
func (s *Selector) SetFilterName(f string) {
|
||||||
|
s.m.SetFilter(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSelectors adds a Selector bunch to form the subset of the nodes
|
||||||
|
// to store container objects.
|
||||||
|
//
|
||||||
|
// Zero PlacementPolicy does not declare replicas ???.
|
||||||
|
func (p *PlacementPolicy) AddSelectors(ss ...Selector) {
|
||||||
|
off := len(p.selectors)
|
||||||
|
|
||||||
|
p.selectors = append(p.selectors, make([]netmap.Selector, len(ss))...)
|
||||||
|
|
||||||
|
for i := range ss {
|
||||||
|
p.selectors[off+i] = ss[i].m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter contains rules for filtering the node sets.
|
||||||
|
type Filter struct {
|
||||||
|
m netmap.Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetName sets name with which the Filter can be referenced or, for inner filters,
|
||||||
|
// to which the Filter references. Top-level filters MUST have be named. The name
|
||||||
|
// MUST NOT be '*'.
|
||||||
|
//
|
||||||
|
// Zero Filter is unnamed.
|
||||||
|
func (x *Filter) SetName(name string) {
|
||||||
|
x.m.SetName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Filter) setAttribute(key string, op netmap.Operation, val string) {
|
||||||
|
x.m.SetKey(key)
|
||||||
|
x.m.SetOp(op)
|
||||||
|
x.m.SetValue(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal applies the rule to accept only nodes with the same attribute value.
|
||||||
|
//
|
||||||
|
// Method SHOULD NOT be called along with other similar methods.
|
||||||
|
func (x *Filter) Equal(key, value string) {
|
||||||
|
x.setAttribute(key, netmap.EQ, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEqual applies the rule to accept only nodes with the distinct attribute value.
|
||||||
|
//
|
||||||
|
// Method SHOULD NOT be called along with other similar methods.
|
||||||
|
func (x *Filter) NotEqual(key, value string) {
|
||||||
|
x.setAttribute(key, netmap.NE, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumericGT applies the rule to accept only nodes with the numeric attribute
|
||||||
|
// greater than given number.
|
||||||
|
//
|
||||||
|
// Method SHOULD NOT be called along with other similar methods.
|
||||||
|
func (x *Filter) NumericGT(key string, num int64) {
|
||||||
|
x.setAttribute(key, netmap.GT, strconv.FormatInt(num, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumericGE applies the rule to accept only nodes with the numeric attribute
|
||||||
|
// greater than or equal to given number.
|
||||||
|
//
|
||||||
|
// Method SHOULD NOT be called along with other similar methods.
|
||||||
|
func (x *Filter) NumericGE(key string, num int64) {
|
||||||
|
x.setAttribute(key, netmap.GE, strconv.FormatInt(num, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumericLT applies the rule to accept only nodes with the numeric attribute
|
||||||
|
// less than given number.
|
||||||
|
//
|
||||||
|
// Method SHOULD NOT be called along with other similar methods.
|
||||||
|
func (x *Filter) NumericLT(key string, num int64) {
|
||||||
|
x.setAttribute(key, netmap.LT, strconv.FormatInt(num, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumericLE applies the rule to accept only nodes with the numeric attribute
|
||||||
|
// less than or equal to given number.
|
||||||
|
//
|
||||||
|
// Method SHOULD NOT be called along with other similar methods.
|
||||||
|
func (x *Filter) NumericLE(key string, num int64) {
|
||||||
|
x.setAttribute(key, netmap.LE, strconv.FormatInt(num, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Filter) setInnerFilters(op netmap.Operation, filters []Filter) {
|
||||||
|
x.setAttribute("", op, "")
|
||||||
|
|
||||||
|
inner := x.m.GetFilters()
|
||||||
|
if rem := len(filters) - len(inner); rem > 0 {
|
||||||
|
inner = append(inner, make([]netmap.Filter, rem)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range filters {
|
||||||
|
inner[i] = filters[i].m
|
||||||
|
}
|
||||||
|
|
||||||
|
x.m.SetFilters(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogicalOR applies the rule to accept only nodes which satisfy at least one
|
||||||
|
// of the given filters.
|
||||||
|
//
|
||||||
|
// Method SHOULD NOT be called along with other similar methods.
|
||||||
|
func (x *Filter) LogicalOR(filters ...Filter) {
|
||||||
|
x.setInnerFilters(netmap.OR, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogicalAND applies the rule to accept only nodes which satisfy all the given
|
||||||
|
// filters.
|
||||||
|
//
|
||||||
|
// Method SHOULD NOT be called along with other similar methods.
|
||||||
|
func (x *Filter) LogicalAND(filters ...Filter) {
|
||||||
|
x.setInnerFilters(netmap.AND, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFilters adds a Filter bunch that will be applied when selecting nodes.
|
||||||
|
//
|
||||||
|
// Zero PlacementPolicy has no filters ???.
|
||||||
|
func (p *PlacementPolicy) AddFilters(fs ...Filter) {
|
||||||
|
off := len(p.filters)
|
||||||
|
|
||||||
|
p.filters = append(p.filters, make([]netmap.Filter, len(fs))...)
|
||||||
|
|
||||||
|
for i := range fs {
|
||||||
|
p.filters[off+i] = fs[i].m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStringTo encodes PlacementPolicy into human-readably query and writes
|
||||||
|
// the result into w. Returns w's errors directly.
|
||||||
|
//
|
||||||
|
// See also DecodeString.
|
||||||
|
func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) {
|
||||||
|
writtenSmth := false
|
||||||
|
|
||||||
|
writeLnIfNeeded := func() error {
|
||||||
|
if writtenSmth {
|
||||||
|
_, err = w.WriteString("\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writtenSmth = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range p.replicas {
|
||||||
|
err = writeLnIfNeeded()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := p.replicas[i].GetCount()
|
||||||
|
s := p.replicas[i].GetSelector()
|
||||||
|
|
||||||
|
if s != "" {
|
||||||
|
_, err = w.WriteString(fmt.Sprintf("REP %d IN %s", c, s))
|
||||||
|
} else {
|
||||||
|
_, err = w.WriteString(fmt.Sprintf("REP %d", c))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.backupFactor > 0 {
|
||||||
|
err = writeLnIfNeeded()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.WriteString(fmt.Sprintf("CBF %d", p.backupFactor))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
for i := range p.selectors {
|
||||||
|
err = writeLnIfNeeded()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.WriteString(fmt.Sprintf("SELECT %d", p.selectors[i].GetCount()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s = p.selectors[i].GetAttribute(); s != "" {
|
||||||
|
var clause string
|
||||||
|
|
||||||
|
switch p.selectors[i].GetClause() {
|
||||||
|
case netmap.Same:
|
||||||
|
clause = "SAME "
|
||||||
|
case netmap.Distinct:
|
||||||
|
clause = "DISTINCT "
|
||||||
|
default:
|
||||||
|
clause = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.WriteString(fmt.Sprintf(" IN %s%s", clause, s))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s = p.selectors[i].GetFilter(); s != "" {
|
||||||
|
_, err = w.WriteString(" FROM " + s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s = p.selectors[i].GetName(); s != "" {
|
||||||
|
_, err = w.WriteString(" AS " + s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range p.filters {
|
||||||
|
err = writeLnIfNeeded()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.WriteString("FILTER ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeFilterStringTo(w, p.filters[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error {
|
||||||
|
var err error
|
||||||
|
var s string
|
||||||
|
op := f.GetOp()
|
||||||
|
unspecified := op == 0
|
||||||
|
|
||||||
|
if s = f.GetKey(); s != "" {
|
||||||
|
_, err = w.WriteString(fmt.Sprintf("%s %s %s", s, op, f.GetValue()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if s = f.GetName(); unspecified && s != "" {
|
||||||
|
_, err = w.WriteString(fmt.Sprintf("@%s", s))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner := f.GetFilters()
|
||||||
|
for i := range inner {
|
||||||
|
if i != 0 {
|
||||||
|
_, err = w.WriteString(" " + op.String() + " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeFilterStringTo(w, inner[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s = f.GetName(); s != "" && !unspecified {
|
||||||
|
_, err = w.WriteString(" AS " + s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeString decodes PlacementPolicy from the string composed using
|
||||||
|
// WriteStringTo. Returns error if s is malformed.
|
||||||
|
func (p *PlacementPolicy) DecodeString(s string) error {
|
||||||
|
input := antlr.NewInputStream(s)
|
||||||
|
lexer := parser.NewQueryLexer(input)
|
||||||
|
stream := antlr.NewCommonTokenStream(lexer, 0)
|
||||||
|
|
||||||
|
pp := parser.NewQuery(stream)
|
||||||
|
pp.BuildParseTrees = true
|
||||||
|
|
||||||
|
var v policyVisitor
|
||||||
|
|
||||||
|
pp.RemoveErrorListeners()
|
||||||
|
pp.AddErrorListener(&v)
|
||||||
|
pl := pp.Policy().Accept(&v)
|
||||||
|
|
||||||
|
if len(v.errors) != 0 {
|
||||||
|
return v.errors[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, ok := pl.(*PlacementPolicy)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected parsed instance type %T", pl)
|
||||||
|
} else if parsed == nil {
|
||||||
|
return errors.New("parsed nil value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validatePolicy(*p); err != nil {
|
||||||
|
return fmt.Errorf("invalid policy: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = *parsed
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// errUnknownFilter is returned when a value of FROM in a query is unknown.
|
||||||
|
errUnknownFilter = errors.New("filter not found")
|
||||||
|
// errUnknownSelector is returned when a value of IN is unknown.
|
||||||
|
errUnknownSelector = errors.New("policy: selector not found")
|
||||||
|
// errSyntaxError is returned for errors found by ANTLR parser.
|
||||||
|
errSyntaxError = errors.New("policy: syntax error")
|
||||||
|
)
|
||||||
|
|
||||||
|
type policyVisitor struct {
|
||||||
|
errors []error
|
||||||
|
parser.BaseQueryVisitor
|
||||||
|
antlr.DefaultErrorListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *policyVisitor) SyntaxError(_ antlr.Recognizer, _ interface{}, line, column int, msg string, _ antlr.RecognitionException) {
|
||||||
|
p.reportError(fmt.Errorf("%w: line %d:%d %s", errSyntaxError, line, column, msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *policyVisitor) reportError(err error) interface{} {
|
||||||
|
p.errors = append(p.errors, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitPolicy implements parser.QueryVisitor interface.
|
||||||
|
func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) interface{} {
|
||||||
|
if len(p.errors) != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pl := new(PlacementPolicy)
|
||||||
|
repStmts := ctx.AllRepStmt()
|
||||||
|
pl.replicas = make([]netmap.Replica, 0, len(repStmts))
|
||||||
|
|
||||||
|
for _, r := range repStmts {
|
||||||
|
res, ok := r.Accept(p).(*netmap.Replica)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pl.replicas = append(pl.replicas, *res)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cbfStmt := ctx.CbfStmt(); cbfStmt != nil {
|
||||||
|
cbf, ok := cbfStmt.(*parser.CbfStmtContext).Accept(p).(uint32)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pl.SetContainerBackupFactor(cbf)
|
||||||
|
}
|
||||||
|
|
||||||
|
selStmts := ctx.AllSelectStmt()
|
||||||
|
pl.selectors = make([]netmap.Selector, 0, len(selStmts))
|
||||||
|
|
||||||
|
for _, s := range selStmts {
|
||||||
|
res, ok := s.Accept(p).(*netmap.Selector)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pl.selectors = append(pl.selectors, *res)
|
||||||
|
}
|
||||||
|
|
||||||
|
filtStmts := ctx.AllFilterStmt()
|
||||||
|
pl.filters = make([]netmap.Filter, 0, len(filtStmts))
|
||||||
|
|
||||||
|
for _, f := range filtStmts {
|
||||||
|
pl.filters = append(pl.filters, *f.Accept(p).(*netmap.Filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
return pl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) interface{} {
|
||||||
|
cbf, err := strconv.ParseUint(ctx.GetBackupFactor().GetText(), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return p.reportError(errInvalidNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint32(cbf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitRepStmt implements parser.QueryVisitor interface.
|
||||||
|
func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) interface{} {
|
||||||
|
num, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return p.reportError(errInvalidNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := new(netmap.Replica)
|
||||||
|
rs.SetCount(uint32(num))
|
||||||
|
|
||||||
|
if sel := ctx.GetSelector(); sel != nil {
|
||||||
|
rs.SetSelector(sel.GetText())
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitSelectStmt implements parser.QueryVisitor interface.
|
||||||
|
func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) interface{} {
|
||||||
|
res, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return p.reportError(errInvalidNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := new(netmap.Selector)
|
||||||
|
s.SetCount(uint32(res))
|
||||||
|
|
||||||
|
if clStmt := ctx.Clause(); clStmt != nil {
|
||||||
|
s.SetClause(clauseFromString(clStmt.GetText()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if bStmt := ctx.GetBucket(); bStmt != nil {
|
||||||
|
s.SetAttribute(ctx.GetBucket().GetText())
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SetFilter(ctx.GetFilter().GetText()) // either ident or wildcard
|
||||||
|
|
||||||
|
if ctx.AS() != nil {
|
||||||
|
s.SetName(ctx.GetName().GetText())
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitFilterStmt implements parser.QueryVisitor interface.
|
||||||
|
func (p *policyVisitor) VisitFilterStmt(ctx *parser.FilterStmtContext) interface{} {
|
||||||
|
f := p.VisitFilterExpr(ctx.GetExpr().(*parser.FilterExprContext)).(*netmap.Filter)
|
||||||
|
f.SetName(ctx.GetName().GetText())
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface{} {
|
||||||
|
if eCtx := ctx.Expr(); eCtx != nil {
|
||||||
|
return eCtx.Accept(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inner := ctx.GetInner(); inner != nil {
|
||||||
|
return inner.Accept(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
f := new(netmap.Filter)
|
||||||
|
op := operationFromString(ctx.GetOp().GetText())
|
||||||
|
f.SetOp(op)
|
||||||
|
|
||||||
|
f1 := *ctx.GetF1().Accept(p).(*netmap.Filter)
|
||||||
|
f2 := *ctx.GetF2().Accept(p).(*netmap.Filter)
|
||||||
|
|
||||||
|
// Consider f1=(.. AND ..) AND f2. This can be merged because our AND operation
|
||||||
|
// is of arbitrary arity. ANTLR generates left-associative parse-tree by default.
|
||||||
|
if f1.GetOp() == op {
|
||||||
|
f.SetFilters(append(f1.GetFilters(), f2))
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
f.SetFilters([]netmap.Filter{f1, f2})
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitFilterKey implements parser.QueryVisitor interface.
|
||||||
|
func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) interface{} {
|
||||||
|
if id := ctx.Ident(); id != nil {
|
||||||
|
return id.GetText()
|
||||||
|
}
|
||||||
|
|
||||||
|
str := ctx.STRING().GetText()
|
||||||
|
return str[1 : len(str)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) interface{} {
|
||||||
|
if id := ctx.Ident(); id != nil {
|
||||||
|
return id.GetText()
|
||||||
|
}
|
||||||
|
|
||||||
|
if num := ctx.Number(); num != nil {
|
||||||
|
return num.GetText()
|
||||||
|
}
|
||||||
|
|
||||||
|
str := ctx.STRING().GetText()
|
||||||
|
return str[1 : len(str)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitExpr implements parser.QueryVisitor interface.
|
||||||
|
func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) interface{} {
|
||||||
|
f := new(netmap.Filter)
|
||||||
|
if flt := ctx.GetFilter(); flt != nil {
|
||||||
|
f.SetName(flt.GetText())
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ctx.GetKey().Accept(p)
|
||||||
|
opStr := ctx.SIMPLE_OP().GetText()
|
||||||
|
value := ctx.GetValue().Accept(p)
|
||||||
|
|
||||||
|
f.SetKey(key.(string))
|
||||||
|
f.SetOp(operationFromString(opStr))
|
||||||
|
f.SetValue(value.(string))
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// validatePolicy checks high-level constraints such as filter link in SELECT
|
||||||
|
// being actually defined in FILTER section.
|
||||||
|
func validatePolicy(p PlacementPolicy) error {
|
||||||
|
seenFilters := map[string]bool{}
|
||||||
|
|
||||||
|
for i := range p.filters {
|
||||||
|
seenFilters[p.filters[i].GetName()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
seenSelectors := map[string]bool{}
|
||||||
|
|
||||||
|
for i := range p.selectors {
|
||||||
|
if flt := p.selectors[i].GetFilter(); flt != mainFilterName && !seenFilters[flt] {
|
||||||
|
return fmt.Errorf("%w: '%s'", errUnknownFilter, flt)
|
||||||
|
}
|
||||||
|
|
||||||
|
seenSelectors[p.selectors[i].GetName()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range p.replicas {
|
||||||
|
if sel := p.replicas[i].GetSelector(); sel != "" && !seenSelectors[sel] {
|
||||||
|
return fmt.Errorf("%w: '%s'", errUnknownSelector, sel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clauseFromString(s string) (c netmap.Clause) {
|
||||||
|
if !c.FromString(strings.ToUpper(s)) {
|
||||||
|
// Such errors should be handled by ANTLR code thus this panic.
|
||||||
|
panic(fmt.Errorf("BUG: invalid clause: %s", c))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func operationFromString(s string) (op netmap.Operation) {
|
||||||
|
if !op.FromString(strings.ToUpper(s)) {
|
||||||
|
// Such errors should be handled by ANTLR code thus this panic.
|
||||||
|
panic(fmt.Errorf("BUG: invalid operation: %s", op))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,127 +1,123 @@
|
||||||
package netmap
|
package netmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPlacementPolicyFromV2(t *testing.T) {
|
func TestEncode(t *testing.T) {
|
||||||
pV2 := new(netmap.PlacementPolicy)
|
testCases := []string{
|
||||||
|
`REP 1 IN X
|
||||||
|
CBF 1
|
||||||
|
SELECT 2 IN SAME Location FROM * AS X`,
|
||||||
|
|
||||||
pV2.SetReplicas([]netmap.Replica{
|
`REP 1
|
||||||
*testReplica().ToV2(),
|
SELECT 2 IN City FROM Good
|
||||||
*testReplica().ToV2(),
|
FILTER Country EQ RU AS FromRU
|
||||||
})
|
FILTER @FromRU AND Rating GT 7 AS Good`,
|
||||||
|
|
||||||
pV2.SetContainerBackupFactor(3)
|
`REP 7 IN SPB
|
||||||
|
SELECT 1 IN City FROM SPBSSD AS SPB
|
||||||
pV2.SetSelectors([]netmap.Selector{
|
FILTER City EQ SPB AND SSD EQ true OR City EQ SPB AND Rating GE 5 AS SPBSSD`,
|
||||||
*testSelector().ToV2(),
|
|
||||||
*testSelector().ToV2(),
|
|
||||||
})
|
|
||||||
|
|
||||||
pV2.SetFilters([]netmap.Filter{
|
|
||||||
*testFilter().ToV2(),
|
|
||||||
*testFilter().ToV2(),
|
|
||||||
})
|
|
||||||
|
|
||||||
p := NewPlacementPolicyFromV2(pV2)
|
|
||||||
|
|
||||||
require.Equal(t, pV2, p.ToV2())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlacementPolicy_Replicas(t *testing.T) {
|
for _, testCase := range testCases {
|
||||||
p := NewPlacementPolicy()
|
var p PlacementPolicy
|
||||||
rs := []Replica{*testReplica(), *testReplica()}
|
|
||||||
|
|
||||||
p.SetReplicas(rs...)
|
require.NoError(t, p.DecodeString(testCase))
|
||||||
|
|
||||||
require.Equal(t, rs, p.Replicas())
|
var b strings.Builder
|
||||||
|
require.NoError(t, p.WriteStringTo(&b))
|
||||||
|
|
||||||
|
require.Equal(t, testCase, b.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlacementPolicy_ContainerBackupFactor(t *testing.T) {
|
type cache struct {
|
||||||
p := NewPlacementPolicy()
|
mtx sync.RWMutex
|
||||||
f := uint32(3)
|
|
||||||
|
|
||||||
p.SetContainerBackupFactor(f)
|
item map[string]struct{}
|
||||||
|
|
||||||
require.Equal(t, f, p.ContainerBackupFactor())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlacementPolicy_Selectors(t *testing.T) {
|
func (x *cache) add(key string) {
|
||||||
p := NewPlacementPolicy()
|
x.mtx.Lock()
|
||||||
ss := []Selector{*testSelector(), *testSelector()}
|
x.item[key] = struct{}{}
|
||||||
|
x.mtx.Unlock()
|
||||||
p.SetSelectors(ss...)
|
|
||||||
|
|
||||||
require.Equal(t, ss, p.Selectors())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlacementPolicy_Filters(t *testing.T) {
|
func (x *cache) has(key string) bool {
|
||||||
p := NewPlacementPolicy()
|
x.mtx.RLock()
|
||||||
fs := []Filter{*testFilter(), *testFilter()}
|
_, ok := x.item[key]
|
||||||
|
x.mtx.RUnlock()
|
||||||
|
|
||||||
p.SetFilters(fs...)
|
return ok
|
||||||
|
|
||||||
require.Equal(t, fs, p.Filters())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlacementPolicyEncoding(t *testing.T) {
|
func BenchmarkCache(b *testing.B) {
|
||||||
p := newPlacementPolicy(3, nil, nil, nil)
|
c := cache{
|
||||||
|
item: make(map[string]struct{}),
|
||||||
t.Run("binary", func(t *testing.T) {
|
|
||||||
data, err := p.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
p2 := NewPlacementPolicy()
|
|
||||||
require.NoError(t, p2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, p, p2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
|
||||||
data, err := p.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
p2 := NewPlacementPolicy()
|
|
||||||
require.NoError(t, p2.UnmarshalJSON(data))
|
|
||||||
|
|
||||||
require.Equal(t, p, p2)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPlacementPolicy(t *testing.T) {
|
var key string
|
||||||
t.Run("nil", func(t *testing.T) {
|
buf := make([]byte, 32)
|
||||||
var x *PlacementPolicy
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
b.ReportAllocs()
|
||||||
})
|
b.ResetTimer()
|
||||||
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
for i := 0; i < b.N; i++ {
|
||||||
pp := NewPlacementPolicy()
|
b.StopTimer()
|
||||||
|
rand.Read(buf)
|
||||||
|
key = string(buf)
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
// check initial values
|
c.add(key)
|
||||||
require.Nil(t, pp.Replicas())
|
c.has(key)
|
||||||
require.Nil(t, pp.Filters())
|
}
|
||||||
require.Nil(t, pp.Selectors())
|
|
||||||
require.Zero(t, pp.ContainerBackupFactor())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
ppV2 := pp.ToV2()
|
|
||||||
|
|
||||||
require.Nil(t, ppV2.GetReplicas())
|
|
||||||
require.Nil(t, ppV2.GetFilters())
|
|
||||||
require.Nil(t, ppV2.GetSelectors())
|
|
||||||
require.Zero(t, ppV2.GetContainerBackupFactor())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPlacementPolicyFromV2(t *testing.T) {
|
type cacheP struct {
|
||||||
t.Run("from nil", func(t *testing.T) {
|
mtx *sync.RWMutex
|
||||||
var x *netmap.PlacementPolicy
|
|
||||||
|
|
||||||
require.Nil(t, NewPlacementPolicyFromV2(x))
|
item map[string]struct{}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
func (x cacheP) add(key string) {
|
||||||
|
x.mtx.Lock()
|
||||||
|
x.item[key] = struct{}{}
|
||||||
|
x.mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x cacheP) has(key string) bool {
|
||||||
|
x.mtx.RLock()
|
||||||
|
_, ok := x.item[key]
|
||||||
|
x.mtx.RUnlock()
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheP(b *testing.B) {
|
||||||
|
c := cacheP{
|
||||||
|
mtx: &sync.RWMutex{},
|
||||||
|
item: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
var key string
|
||||||
|
buf := make([]byte, 32)
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
b.StopTimer()
|
||||||
|
rand.Read(buf)
|
||||||
|
key = string(buf)
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
c.add(key)
|
||||||
|
c.has(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
package netmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Replica represents v2-compatible object replica descriptor.
|
|
||||||
type Replica netmap.Replica
|
|
||||||
|
|
||||||
// NewReplica creates and returns new Replica instance.
|
|
||||||
//
|
|
||||||
// Defaults:
|
|
||||||
// - count: 0;
|
|
||||||
// - selector: "".
|
|
||||||
func NewReplica() *Replica {
|
|
||||||
return NewReplicaFromV2(new(netmap.Replica))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReplicaFromV2 converts v2 Replica to Replica.
|
|
||||||
//
|
|
||||||
// Nil netmap.Replica converts to nil.
|
|
||||||
func NewReplicaFromV2(f *netmap.Replica) *Replica {
|
|
||||||
return (*Replica)(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts Replica to v2 Replica.
|
|
||||||
//
|
|
||||||
// Nil Replica converts to nil.
|
|
||||||
func (r *Replica) ToV2() *netmap.Replica {
|
|
||||||
return (*netmap.Replica)(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count returns number of object replicas.
|
|
||||||
func (r *Replica) Count() uint32 {
|
|
||||||
return (*netmap.Replica)(r).GetCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCount sets number of object replicas.
|
|
||||||
func (r *Replica) SetCount(c uint32) {
|
|
||||||
(*netmap.Replica)(r).SetCount(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selector returns name of selector bucket to put replicas.
|
|
||||||
func (r *Replica) Selector() string {
|
|
||||||
return (*netmap.Replica)(r).GetSelector()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSelector sets name of selector bucket to put replicas.
|
|
||||||
func (r *Replica) SetSelector(s string) {
|
|
||||||
(*netmap.Replica)(r).SetSelector(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals Replica into a protobuf binary form.
|
|
||||||
func (r *Replica) Marshal() ([]byte, error) {
|
|
||||||
return (*netmap.Replica)(r).StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of Replica.
|
|
||||||
func (r *Replica) Unmarshal(data []byte) error {
|
|
||||||
return (*netmap.Replica)(r).Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes Replica to protobuf JSON format.
|
|
||||||
func (r *Replica) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*netmap.Replica)(r).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes Replica from protobuf JSON format.
|
|
||||||
func (r *Replica) UnmarshalJSON(data []byte) error {
|
|
||||||
return (*netmap.Replica)(r).UnmarshalJSON(data)
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
package netmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
testv2 "github.com/nspcc-dev/neofs-api-go/v2/netmap/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testReplica() *Replica {
|
|
||||||
r := new(Replica)
|
|
||||||
r.SetCount(3)
|
|
||||||
r.SetSelector("selector")
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplicaFromV2(t *testing.T) {
|
|
||||||
t.Run("from nil", func(t *testing.T) {
|
|
||||||
var x *netmap.Replica
|
|
||||||
|
|
||||||
require.Nil(t, NewReplicaFromV2(x))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("from non-nil", func(t *testing.T) {
|
|
||||||
rV2 := testv2.GenerateReplica(false)
|
|
||||||
|
|
||||||
r := NewReplicaFromV2(rV2)
|
|
||||||
|
|
||||||
require.Equal(t, rV2, r.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplica_Count(t *testing.T) {
|
|
||||||
r := NewReplica()
|
|
||||||
c := uint32(3)
|
|
||||||
|
|
||||||
r.SetCount(c)
|
|
||||||
|
|
||||||
require.Equal(t, c, r.Count())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplica_Selector(t *testing.T) {
|
|
||||||
r := NewReplica()
|
|
||||||
s := "some selector"
|
|
||||||
|
|
||||||
r.SetSelector(s)
|
|
||||||
|
|
||||||
require.Equal(t, s, r.Selector())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplicaEncoding(t *testing.T) {
|
|
||||||
r := newReplica(3, "selector")
|
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
|
||||||
data, err := r.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
r2 := *NewReplica()
|
|
||||||
require.NoError(t, r2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, r, r2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
|
||||||
data, err := r.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
r2 := *NewReplica()
|
|
||||||
require.NoError(t, r2.UnmarshalJSON(data))
|
|
||||||
|
|
||||||
require.Equal(t, r, r2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplica_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *Replica
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewReplica(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
r := NewReplica()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Zero(t, r.Count())
|
|
||||||
require.Empty(t, r.Selector())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
rV2 := r.ToV2()
|
|
||||||
|
|
||||||
require.Zero(t, rV2.GetCount())
|
|
||||||
require.Empty(t, rV2.GetSelector())
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -9,62 +9,70 @@ import (
|
||||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Selector represents v2-compatible netmap selector.
|
|
||||||
type Selector netmap.Selector
|
|
||||||
|
|
||||||
// processSelectors processes selectors and returns error is any of them is invalid.
|
// processSelectors processes selectors and returns error is any of them is invalid.
|
||||||
func (c *context) processSelectors(p *PlacementPolicy) error {
|
func (c *context) processSelectors(p PlacementPolicy) error {
|
||||||
selectors := p.Selectors()
|
for i := range p.selectors {
|
||||||
for i, s := range selectors {
|
fName := p.selectors[i].GetFilter()
|
||||||
if s.Filter() != MainFilterName {
|
if fName != mainFilterName {
|
||||||
_, ok := c.Filters[s.Filter()]
|
_, ok := c.processedFilters[p.selectors[i].GetFilter()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%w: SELECT FROM '%s'", ErrFilterNotFound, s.Filter())
|
return fmt.Errorf("%w: SELECT FROM '%s'", errFilterNotFound, fName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Selectors[s.Name()] = &selectors[i]
|
sName := p.selectors[i].GetName()
|
||||||
|
|
||||||
result, err := c.getSelection(p, &s)
|
c.processedSelectors[sName] = &p.selectors[i]
|
||||||
|
|
||||||
|
result, err := c.getSelection(p, p.selectors[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Selections[s.Name()] = result
|
c.selections[sName] = result
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNodesCount returns amount of buckets and minimum number of nodes in every bucket
|
// calcNodesCount returns amount of buckets and minimum number of nodes in every bucket
|
||||||
// for the given selector.
|
// for the given selector.
|
||||||
func GetNodesCount(_ *PlacementPolicy, s *Selector) (int, int) {
|
func calcNodesCount(s netmap.Selector) (int, int) {
|
||||||
switch s.Clause() {
|
switch s.GetClause() {
|
||||||
case ClauseSame:
|
case netmap.Same:
|
||||||
return 1, int(s.Count())
|
return 1, int(s.GetCount())
|
||||||
default:
|
default:
|
||||||
return int(s.Count()), 1
|
return int(s.GetCount()), 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calcBucketWeight computes weight for a node bucket.
|
||||||
|
func calcBucketWeight(ns nodes, a aggregator, wf weightFunc) float64 {
|
||||||
|
for i := range ns {
|
||||||
|
a.Add(wf(ns[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Compute()
|
||||||
|
}
|
||||||
|
|
||||||
// getSelection returns nodes grouped by s.attribute.
|
// getSelection returns nodes grouped by s.attribute.
|
||||||
// Last argument specifies if more buckets can be used to fulfill CBF.
|
// Last argument specifies if more buckets can be used to fulfill CBF.
|
||||||
func (c *context) getSelection(p *PlacementPolicy, s *Selector) ([]nodes, error) {
|
func (c *context) getSelection(p PlacementPolicy, s netmap.Selector) ([]nodes, error) {
|
||||||
bucketCount, nodesInBucket := GetNodesCount(p, s)
|
bucketCount, nodesInBucket := calcNodesCount(s)
|
||||||
buckets := c.getSelectionBase(p.SubnetID(), s)
|
buckets := c.getSelectionBase(p.subnet, s)
|
||||||
|
|
||||||
if len(buckets) < bucketCount {
|
if len(buckets) < bucketCount {
|
||||||
return nil, fmt.Errorf("%w: '%s'", ErrNotEnoughNodes, s.Name())
|
return nil, fmt.Errorf("%w: '%s'", errNotEnoughNodes, s.GetName())
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need deterministic output in case there is no pivot.
|
// We need deterministic output in case there is no pivot.
|
||||||
// If pivot is set, buckets are sorted by HRW.
|
// If pivot is set, buckets are sorted by HRW.
|
||||||
// However, because initial order influences HRW order for buckets with equal weights,
|
// However, because initial order influences HRW order for buckets with equal weights,
|
||||||
// we also need to have deterministic input to HRW sorting routine.
|
// we also need to have deterministic input to HRW sorting routine.
|
||||||
if len(c.pivot) == 0 {
|
if len(c.hrwSeed) == 0 {
|
||||||
if s.Attribute() == "" {
|
if s.GetAttribute() == "" {
|
||||||
sort.Slice(buckets, func(i, j int) bool {
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
return buckets[i].nodes[0].less(buckets[j].nodes[0])
|
return less(buckets[i].nodes[0], buckets[j].nodes[0])
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
sort.Slice(buckets, func(i, j int) bool {
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
|
@ -90,20 +98,20 @@ func (c *context) getSelection(p *PlacementPolicy, s *Selector) ([]nodes, error)
|
||||||
// Fallback to using minimum allowed backup factor (1).
|
// Fallback to using minimum allowed backup factor (1).
|
||||||
res = append(res, fallback...)
|
res = append(res, fallback...)
|
||||||
if len(res) < bucketCount {
|
if len(res) < bucketCount {
|
||||||
return nil, fmt.Errorf("%w: '%s'", ErrNotEnoughNodes, s.Name())
|
return nil, fmt.Errorf("%w: '%s'", errNotEnoughNodes, s.GetName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.pivot) != 0 {
|
if len(c.hrwSeed) != 0 {
|
||||||
weights := make([]float64, len(res))
|
weights := make([]float64, len(res))
|
||||||
for i := range res {
|
for i := range res {
|
||||||
weights[i] = calcBucketWeight(res[i], c.aggregator(), c.weightFunc)
|
weights[i] = calcBucketWeight(res[i], newMeanIQRAgg(), c.weightFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
hrw.SortSliceByWeightValue(res, weights, c.pivotHash)
|
hrw.SortSliceByWeightValue(res, weights, c.hrwSeedHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Attribute() == "" {
|
if s.GetAttribute() == "" {
|
||||||
res, fallback = res[:bucketCount], res[bucketCount:]
|
res, fallback = res[:bucketCount], res[bucketCount:]
|
||||||
for i := range fallback {
|
for i := range fallback {
|
||||||
index := i % bucketCount
|
index := i % bucketCount
|
||||||
|
@ -124,29 +132,26 @@ type nodeAttrPair struct {
|
||||||
|
|
||||||
// getSelectionBase returns nodes grouped by selector attribute.
|
// getSelectionBase returns nodes grouped by selector attribute.
|
||||||
// It it guaranteed that each pair will contain at least one node.
|
// It it guaranteed that each pair will contain at least one node.
|
||||||
func (c *context) getSelectionBase(subnetID *subnetid.ID, s *Selector) []nodeAttrPair {
|
func (c *context) getSelectionBase(subnetID subnetid.ID, s netmap.Selector) []nodeAttrPair {
|
||||||
f := c.Filters[s.Filter()]
|
fName := s.GetFilter()
|
||||||
isMain := s.Filter() == MainFilterName
|
f := c.processedFilters[fName]
|
||||||
|
isMain := fName == mainFilterName
|
||||||
result := []nodeAttrPair{}
|
result := []nodeAttrPair{}
|
||||||
nodeMap := map[string][]NodeInfo{}
|
nodeMap := map[string][]NodeInfo{}
|
||||||
attr := s.Attribute()
|
attr := s.GetAttribute()
|
||||||
|
|
||||||
for i := range c.Netmap.nodes {
|
for i := range c.netMap.nodes {
|
||||||
var sid subnetid.ID
|
|
||||||
if subnetID != nil {
|
|
||||||
sid = *subnetID
|
|
||||||
}
|
|
||||||
// TODO(fyrchik): make `BelongsToSubnet` to accept pointer
|
// TODO(fyrchik): make `BelongsToSubnet` to accept pointer
|
||||||
if !BelongsToSubnet(&c.Netmap.nodes[i], sid) {
|
if !BelongsToSubnet(c.netMap.nodes[i], subnetID) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if isMain || c.match(f, c.Netmap.nodes[i]) {
|
if isMain || c.match(f, c.netMap.nodes[i]) {
|
||||||
if attr == "" {
|
if attr == "" {
|
||||||
// Default attribute is transparent identifier which is different for every node.
|
// Default attribute is transparent identifier which is different for every node.
|
||||||
result = append(result, nodeAttrPair{attr: "", nodes: nodes{c.Netmap.nodes[i]}})
|
result = append(result, nodeAttrPair{attr: "", nodes: nodes{c.netMap.nodes[i]}})
|
||||||
} else {
|
} else {
|
||||||
v := c.Netmap.nodes[i].attribute(attr)
|
v := c.netMap.nodes[i].Attribute(attr)
|
||||||
nodeMap[v] = append(nodeMap[v], c.Netmap.nodes[i])
|
nodeMap[v] = append(nodeMap[v], c.netMap.nodes[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,119 +162,11 @@ func (c *context) getSelectionBase(subnetID *subnetid.ID, s *Selector) []nodeAtt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.pivot) != 0 {
|
if len(c.hrwSeed) != 0 {
|
||||||
for i := range result {
|
for i := range result {
|
||||||
hrw.SortSliceByWeightValue(result[i].nodes, result[i].nodes.weights(c.weightFunc), c.pivotHash)
|
hrw.SortSliceByWeightValue(result[i].nodes, result[i].nodes.weights(c.weightFunc), c.hrwSeedHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSelector creates and returns new Selector instance.
|
|
||||||
//
|
|
||||||
// Defaults:
|
|
||||||
// - name: "";
|
|
||||||
// - attribute: "";
|
|
||||||
// - filter: "";
|
|
||||||
// - clause: ClauseUnspecified;
|
|
||||||
// - count: 0.
|
|
||||||
func NewSelector() *Selector {
|
|
||||||
return NewSelectorFromV2(new(netmap.Selector))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSelectorFromV2 converts v2 Selector to Selector.
|
|
||||||
//
|
|
||||||
// Nil netmap.Selector converts to nil.
|
|
||||||
func NewSelectorFromV2(f *netmap.Selector) *Selector {
|
|
||||||
return (*Selector)(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts Selector to v2 Selector.
|
|
||||||
//
|
|
||||||
// Nil Selector converts to nil.
|
|
||||||
func (s *Selector) ToV2() *netmap.Selector {
|
|
||||||
return (*netmap.Selector)(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns selector name.
|
|
||||||
func (s *Selector) Name() string {
|
|
||||||
return (*netmap.Selector)(s).
|
|
||||||
GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetName sets selector name.
|
|
||||||
func (s *Selector) SetName(name string) {
|
|
||||||
(*netmap.Selector)(s).
|
|
||||||
SetName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count returns count of nodes to select from bucket.
|
|
||||||
func (s *Selector) Count() uint32 {
|
|
||||||
return (*netmap.Selector)(s).
|
|
||||||
GetCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCount sets count of nodes to select from bucket.
|
|
||||||
func (s *Selector) SetCount(c uint32) {
|
|
||||||
(*netmap.Selector)(s).
|
|
||||||
SetCount(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clause returns modifier showing how to form a bucket.
|
|
||||||
func (s *Selector) Clause() Clause {
|
|
||||||
return ClauseFromV2(
|
|
||||||
(*netmap.Selector)(s).
|
|
||||||
GetClause(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClause sets modifier showing how to form a bucket.
|
|
||||||
func (s *Selector) SetClause(c Clause) {
|
|
||||||
(*netmap.Selector)(s).
|
|
||||||
SetClause(c.ToV2())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute returns attribute bucket to select from.
|
|
||||||
func (s *Selector) Attribute() string {
|
|
||||||
return (*netmap.Selector)(s).
|
|
||||||
GetAttribute()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAttribute sets attribute bucket to select from.
|
|
||||||
func (s *Selector) SetAttribute(a string) {
|
|
||||||
(*netmap.Selector)(s).
|
|
||||||
SetAttribute(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter returns filter reference to select from.
|
|
||||||
func (s *Selector) Filter() string {
|
|
||||||
return (*netmap.Selector)(s).
|
|
||||||
GetFilter()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFilter sets filter reference to select from.
|
|
||||||
func (s *Selector) SetFilter(f string) {
|
|
||||||
(*netmap.Selector)(s).
|
|
||||||
SetFilter(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals Selector into a protobuf binary form.
|
|
||||||
func (s *Selector) Marshal() ([]byte, error) {
|
|
||||||
return (*netmap.Selector)(s).StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of Selector.
|
|
||||||
func (s *Selector) Unmarshal(data []byte) error {
|
|
||||||
return (*netmap.Selector)(s).Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes Selector to protobuf JSON format.
|
|
||||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*netmap.Selector)(s).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes Selector from protobuf JSON format.
|
|
||||||
func (s *Selector) UnmarshalJSON(data []byte) error {
|
|
||||||
return (*netmap.Selector)(s).UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/hrw"
|
"github.com/nspcc-dev/hrw"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
testv2 "github.com/nspcc-dev/neofs-api-go/v2/netmap/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,8 +22,8 @@ func BenchmarkHRWSort(b *testing.B) {
|
||||||
rand.Read(key)
|
rand.Read(key)
|
||||||
|
|
||||||
var node NodeInfo
|
var node NodeInfo
|
||||||
node.setPrice(1)
|
node.SetPrice(1)
|
||||||
node.setCapacity(100)
|
node.SetCapacity(100)
|
||||||
node.SetPublicKey(key)
|
node.SetPublicKey(key)
|
||||||
|
|
||||||
vectors[i] = nodes{node}
|
vectors[i] = nodes{node}
|
||||||
|
@ -85,7 +84,7 @@ func BenchmarkHRWSort(b *testing.B) {
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
|
|
||||||
sort.Slice(vectors, func(i, j int) bool {
|
sort.Slice(vectors, func(i, j int) bool {
|
||||||
return vectors[i][0].less(vectors[j][0])
|
return less(vectors[i][0], vectors[j][0])
|
||||||
})
|
})
|
||||||
hrw.SortSliceByWeightIndex(realNodes, weights, pivot)
|
hrw.SortSliceByWeightIndex(realNodes, weights, pivot)
|
||||||
}
|
}
|
||||||
|
@ -96,15 +95,15 @@ func BenchmarkPolicyHRWType(b *testing.B) {
|
||||||
const netmapSize = 100
|
const netmapSize = 100
|
||||||
|
|
||||||
p := newPlacementPolicy(1,
|
p := newPlacementPolicy(1,
|
||||||
[]Replica{
|
[]ReplicaDescriptor{
|
||||||
newReplica(1, "loc1"),
|
newReplica(1, "loc1"),
|
||||||
newReplica(1, "loc2")},
|
newReplica(1, "loc2")},
|
||||||
[]Selector{
|
[]Selector{
|
||||||
newSelector("loc1", "Location", ClauseSame, 1, "loc1"),
|
newSelector("loc1", "Location", 1, "loc1", (*Selector).SelectSame),
|
||||||
newSelector("loc2", "Location", ClauseSame, 1, "loc2")},
|
newSelector("loc2", "Location", 1, "loc2", (*Selector).SelectSame)},
|
||||||
[]Filter{
|
[]Filter{
|
||||||
newFilter("loc1", "Location", "Shanghai", OpEQ),
|
newFilter("loc1", "Location", "Shanghai", netmap.EQ),
|
||||||
newFilter("loc2", "Location", "Shanghai", OpNE),
|
newFilter("loc2", "Location", "Shanghai", netmap.NE),
|
||||||
})
|
})
|
||||||
|
|
||||||
nodes := make([]NodeInfo, netmapSize)
|
nodes := make([]NodeInfo, netmapSize)
|
||||||
|
@ -125,12 +124,12 @@ func BenchmarkPolicyHRWType(b *testing.B) {
|
||||||
nodes[i].SetPublicKey(pub)
|
nodes[i].SetPublicKey(pub)
|
||||||
}
|
}
|
||||||
|
|
||||||
var nm Netmap
|
var nm NetMap
|
||||||
nm.SetNodes(nodes)
|
nm.SetNodes(nodes)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := nm.GetContainerNodes(p, []byte{1})
|
_, err := nm.ContainerNodes(p, []byte{1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal()
|
b.Fatal()
|
||||||
}
|
}
|
||||||
|
@ -141,15 +140,15 @@ func TestPlacementPolicy_DeterministicOrder(t *testing.T) {
|
||||||
const netmapSize = 100
|
const netmapSize = 100
|
||||||
|
|
||||||
p := newPlacementPolicy(1,
|
p := newPlacementPolicy(1,
|
||||||
[]Replica{
|
[]ReplicaDescriptor{
|
||||||
newReplica(1, "loc1"),
|
newReplica(1, "loc1"),
|
||||||
newReplica(1, "loc2")},
|
newReplica(1, "loc2")},
|
||||||
[]Selector{
|
[]Selector{
|
||||||
newSelector("loc1", "Location", ClauseSame, 1, "loc1"),
|
newSelector("loc1", "Location", 1, "loc1", (*Selector).SelectSame),
|
||||||
newSelector("loc2", "Location", ClauseSame, 1, "loc2")},
|
newSelector("loc2", "Location", 1, "loc2", (*Selector).SelectSame)},
|
||||||
[]Filter{
|
[]Filter{
|
||||||
newFilter("loc1", "Location", "Shanghai", OpEQ),
|
newFilter("loc1", "Location", "Shanghai", netmap.EQ),
|
||||||
newFilter("loc2", "Location", "Shanghai", OpNE),
|
newFilter("loc2", "Location", "Shanghai", netmap.NE),
|
||||||
})
|
})
|
||||||
|
|
||||||
nodeList := make([]NodeInfo, netmapSize)
|
nodeList := make([]NodeInfo, netmapSize)
|
||||||
|
@ -170,11 +169,11 @@ func TestPlacementPolicy_DeterministicOrder(t *testing.T) {
|
||||||
nodeList[i].SetPublicKey(pub)
|
nodeList[i].SetPublicKey(pub)
|
||||||
}
|
}
|
||||||
|
|
||||||
var nm Netmap
|
var nm NetMap
|
||||||
nm.SetNodes(nodeList)
|
nm.SetNodes(nodeList)
|
||||||
|
|
||||||
getIndices := func(t *testing.T) (uint64, uint64) {
|
getIndices := func(t *testing.T) (uint64, uint64) {
|
||||||
v, err := nm.GetContainerNodes(p, []byte{1})
|
v, err := nm.ContainerNodes(p, []byte{1})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
nss := make([]nodes, len(v))
|
nss := make([]nodes, len(v))
|
||||||
|
@ -198,14 +197,14 @@ func TestPlacementPolicy_DeterministicOrder(t *testing.T) {
|
||||||
func TestPlacementPolicy_ProcessSelectors(t *testing.T) {
|
func TestPlacementPolicy_ProcessSelectors(t *testing.T) {
|
||||||
p := newPlacementPolicy(2, nil,
|
p := newPlacementPolicy(2, nil,
|
||||||
[]Selector{
|
[]Selector{
|
||||||
newSelector("SameRU", "City", ClauseSame, 2, "FromRU"),
|
newSelector("SameRU", "City", 2, "FromRU", (*Selector).SelectSame),
|
||||||
newSelector("DistinctRU", "City", ClauseDistinct, 2, "FromRU"),
|
newSelector("DistinctRU", "City", 2, "FromRU", (*Selector).SelectDistinct),
|
||||||
newSelector("Good", "Country", ClauseDistinct, 2, "Good"),
|
newSelector("Good", "Country", 2, "Good", (*Selector).SelectDistinct),
|
||||||
newSelector("Main", "Country", ClauseDistinct, 3, "*"),
|
newSelector("Main", "Country", 3, "*", (*Selector).SelectDistinct),
|
||||||
},
|
},
|
||||||
[]Filter{
|
[]Filter{
|
||||||
newFilter("FromRU", "Country", "Russia", OpEQ),
|
newFilter("FromRU", "Country", "Russia", netmap.EQ),
|
||||||
newFilter("Good", "Rating", "4", OpGE),
|
newFilter("Good", "Rating", "4", netmap.GE),
|
||||||
})
|
})
|
||||||
nodes := []NodeInfo{
|
nodes := []NodeInfo{
|
||||||
nodeInfoFromAttributes("Country", "Russia", "Rating", "1", "City", "SPB"),
|
nodeInfoFromAttributes("Country", "Russia", "Rating", "1", "City", "SPB"),
|
||||||
|
@ -221,151 +220,79 @@ func TestPlacementPolicy_ProcessSelectors(t *testing.T) {
|
||||||
nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
|
nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var nm Netmap
|
var nm NetMap
|
||||||
nm.SetNodes(nodes)
|
nm.SetNodes(nodes)
|
||||||
c := newContext(&nm)
|
c := newContext(nm)
|
||||||
c.setCBF(p.ContainerBackupFactor())
|
c.setCBF(p.backupFactor)
|
||||||
require.NoError(t, c.processFilters(p))
|
require.NoError(t, c.processFilters(p))
|
||||||
require.NoError(t, c.processSelectors(p))
|
require.NoError(t, c.processSelectors(p))
|
||||||
|
|
||||||
for _, s := range p.Selectors() {
|
for _, s := range p.selectors {
|
||||||
sel := c.Selections[s.Name()]
|
sel := c.selections[s.GetName()]
|
||||||
s := c.Selectors[s.Name()]
|
s := c.processedSelectors[s.GetName()]
|
||||||
bucketCount, nodesInBucket := GetNodesCount(p, s)
|
bucketCount, nodesInBucket := calcNodesCount(*s)
|
||||||
nodesInBucket *= int(c.cbf)
|
nodesInBucket *= int(c.cbf)
|
||||||
targ := fmt.Sprintf("selector '%s'", s.Name())
|
targ := fmt.Sprintf("selector '%s'", s.GetName())
|
||||||
require.Equal(t, bucketCount, len(sel), targ)
|
require.Equal(t, bucketCount, len(sel), targ)
|
||||||
|
fName := s.GetFilter()
|
||||||
for _, res := range sel {
|
for _, res := range sel {
|
||||||
require.Equal(t, nodesInBucket, len(res), targ)
|
require.Equal(t, nodesInBucket, len(res), targ)
|
||||||
for j := range res {
|
for j := range res {
|
||||||
require.True(t, c.applyFilter(s.Filter(), res[j]), targ)
|
require.True(t, fName == mainFilterName || c.match(c.processedFilters[fName], res[j]), targ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSelector() *Selector {
|
func TestSelector_SetName(t *testing.T) {
|
||||||
s := new(Selector)
|
const name = "some name"
|
||||||
s.SetName("name")
|
var s Selector
|
||||||
s.SetCount(3)
|
|
||||||
s.SetFilter("filter")
|
|
||||||
s.SetAttribute("attribute")
|
|
||||||
s.SetClause(ClauseDistinct)
|
|
||||||
|
|
||||||
return s
|
require.Zero(t, s.m.GetName())
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelector_Name(t *testing.T) {
|
|
||||||
s := NewSelector()
|
|
||||||
name := "some name"
|
|
||||||
|
|
||||||
s.SetName(name)
|
s.SetName(name)
|
||||||
|
require.Equal(t, name, s.m.GetName())
|
||||||
require.Equal(t, name, s.Name())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelector_Count(t *testing.T) {
|
func TestSelector_SetNodeAmount(t *testing.T) {
|
||||||
s := NewSelector()
|
const amount = 3
|
||||||
c := uint32(3)
|
var s Selector
|
||||||
|
|
||||||
s.SetCount(c)
|
require.Zero(t, s.m.GetCount())
|
||||||
|
|
||||||
require.Equal(t, c, s.Count())
|
s.SetNodeAmount(amount)
|
||||||
|
|
||||||
|
require.EqualValues(t, amount, s.m.GetCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelector_Clause(t *testing.T) {
|
func TestSelectorClauses(t *testing.T) {
|
||||||
s := NewSelector()
|
var s Selector
|
||||||
c := ClauseSame
|
|
||||||
|
|
||||||
s.SetClause(c)
|
require.Equal(t, netmap.UnspecifiedClause, s.m.GetClause())
|
||||||
|
|
||||||
require.Equal(t, c, s.Clause())
|
s.SelectDistinct()
|
||||||
|
require.Equal(t, netmap.Distinct, s.m.GetClause())
|
||||||
|
|
||||||
|
s.SelectSame()
|
||||||
|
require.Equal(t, netmap.Same, s.m.GetClause())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelector_Attribute(t *testing.T) {
|
func TestSelector_SelectByBucketAttribute(t *testing.T) {
|
||||||
s := NewSelector()
|
const attr = "some attribute"
|
||||||
a := "some attribute"
|
var s Selector
|
||||||
|
|
||||||
s.SetAttribute(a)
|
require.Zero(t, s.m.GetAttribute())
|
||||||
|
|
||||||
require.Equal(t, a, s.Attribute())
|
s.SelectByBucketAttribute(attr)
|
||||||
|
require.Equal(t, attr, s.m.GetAttribute())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelector_Filter(t *testing.T) {
|
func TestSelector_SetFilterName(t *testing.T) {
|
||||||
s := NewSelector()
|
const fName = "some filter"
|
||||||
f := "some filter"
|
var s Selector
|
||||||
|
|
||||||
s.SetFilter(f)
|
require.Zero(t, s.m.GetFilter())
|
||||||
|
|
||||||
require.Equal(t, f, s.Filter())
|
s.SetFilterName(fName)
|
||||||
}
|
require.Equal(t, fName, s.m.GetFilter())
|
||||||
|
|
||||||
func TestSelectorEncoding(t *testing.T) {
|
|
||||||
s := newSelector("name", "atte", ClauseSame, 1, "filter")
|
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
|
||||||
data, err := s.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s2 := *NewSelector()
|
|
||||||
require.NoError(t, s2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, s, s2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
|
||||||
data, err := s.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s2 := *NewSelector()
|
|
||||||
require.NoError(t, s2.UnmarshalJSON(data))
|
|
||||||
|
|
||||||
require.Equal(t, s, s2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelector_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *Selector
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewSelectorFromV2(t *testing.T) {
|
|
||||||
t.Run("from nil", func(t *testing.T) {
|
|
||||||
var x *netmap.Selector
|
|
||||||
|
|
||||||
require.Nil(t, NewSelectorFromV2(x))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("from non-nil", func(t *testing.T) {
|
|
||||||
sV2 := testv2.GenerateSelector(false)
|
|
||||||
|
|
||||||
s := NewSelectorFromV2(sV2)
|
|
||||||
|
|
||||||
require.Equal(t, sV2, s.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewSelector(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
s := NewSelector()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Zero(t, s.Count())
|
|
||||||
require.Equal(t, ClauseUnspecified, s.Clause())
|
|
||||||
require.Empty(t, s.Attribute())
|
|
||||||
require.Empty(t, s.Name())
|
|
||||||
require.Empty(t, s.Filter())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
sV2 := s.ToV2()
|
|
||||||
|
|
||||||
require.Zero(t, sV2.GetCount())
|
|
||||||
require.Equal(t, netmap.UnspecifiedClause, sV2.GetClause())
|
|
||||||
require.Empty(t, sV2.GetAttribute())
|
|
||||||
require.Empty(t, sV2.GetName())
|
|
||||||
require.Empty(t, sV2.GetFilter())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
package netmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
|
||||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EnterSubnet writes to NodeInfo the intention to enter the subnet. Must not be called on nil.
|
|
||||||
// Zero NodeInfo belongs to zero subnet.
|
|
||||||
func (i *NodeInfo) EnterSubnet(id subnetid.ID) {
|
|
||||||
i.changeSubnet(id, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitSubnet writes to NodeInfo the intention to exit subnet. Must not be called on nil.
|
|
||||||
func (i *NodeInfo) ExitSubnet(id subnetid.ID) {
|
|
||||||
i.changeSubnet(id, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *NodeInfo) changeSubnet(id subnetid.ID, isMember bool) {
|
|
||||||
var (
|
|
||||||
idv2 refs.SubnetID
|
|
||||||
info netmap.NodeSubnetInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
id.WriteToV2(&idv2)
|
|
||||||
|
|
||||||
info.SetID(&idv2)
|
|
||||||
info.SetEntryFlag(isMember)
|
|
||||||
|
|
||||||
if i.m == nil {
|
|
||||||
i.m = new(netmap.NodeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
netmap.WriteSubnetInfo(i.m, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrRemoveSubnet is returned when a node needs to leave the subnet.
|
|
||||||
var ErrRemoveSubnet = netmap.ErrRemoveSubnet
|
|
||||||
|
|
||||||
// IterateSubnets iterates over all subnets the node belongs to and passes the IDs to f.
|
|
||||||
// Must not be called on nil. Handler must not be nil.
|
|
||||||
//
|
|
||||||
// If f returns ErrRemoveSubnet, then removes subnet entry. Note that this leads to an instant mutation of NodeInfo.
|
|
||||||
// Breaks on any other non-nil error and returns it.
|
|
||||||
//
|
|
||||||
// Returns an error if subnet incorrectly enabled/disabled.
|
|
||||||
// Returns an error if the node is not included in any subnet by the end of the loop.
|
|
||||||
func (i *NodeInfo) IterateSubnets(f func(subnetid.ID) error) error {
|
|
||||||
var id subnetid.ID
|
|
||||||
|
|
||||||
return netmap.IterateSubnets(i.m, func(idv2 refs.SubnetID) error {
|
|
||||||
err := id.ReadFromV2(idv2)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid subnet: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f(id)
|
|
||||||
if errors.Is(err, ErrRemoveSubnet) {
|
|
||||||
return netmap.ErrRemoveSubnet
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var errAbortSubnetIter = errors.New("abort subnet iterator")
|
|
||||||
|
|
||||||
// BelongsToSubnet checks if node belongs to subnet by ID.
|
|
||||||
//
|
|
||||||
// Function is NPE-safe: nil NodeInfo always belongs to zero subnet only.
|
|
||||||
func BelongsToSubnet(node *NodeInfo, id subnetid.ID) bool {
|
|
||||||
err := node.IterateSubnets(func(id_ subnetid.ID) error {
|
|
||||||
if id.Equals(id_) {
|
|
||||||
return errAbortSubnetIter
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return errors.Is(err, errAbortSubnetIter)
|
|
||||||
}
|
|
|
@ -92,22 +92,22 @@ func TestEnterSubnet(t *testing.T) {
|
||||||
node netmap.NodeInfo
|
node netmap.NodeInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
require.True(t, netmap.BelongsToSubnet(&node, id))
|
require.True(t, netmap.BelongsToSubnet(node, id))
|
||||||
|
|
||||||
node.EnterSubnet(id)
|
node.EnterSubnet(id)
|
||||||
require.True(t, netmap.BelongsToSubnet(&node, id))
|
require.True(t, netmap.BelongsToSubnet(node, id))
|
||||||
|
|
||||||
node.ExitSubnet(id)
|
node.ExitSubnet(id)
|
||||||
require.False(t, netmap.BelongsToSubnet(&node, id))
|
require.False(t, netmap.BelongsToSubnet(node, id))
|
||||||
|
|
||||||
id.SetNumeric(10)
|
id.SetNumeric(10)
|
||||||
node.EnterSubnet(id)
|
node.EnterSubnet(id)
|
||||||
require.True(t, netmap.BelongsToSubnet(&node, id))
|
require.True(t, netmap.BelongsToSubnet(node, id))
|
||||||
require.False(t, netmap.BelongsToSubnet(&node, subnetid.ID{}))
|
require.False(t, netmap.BelongsToSubnet(node, subnetid.ID{}))
|
||||||
|
|
||||||
node.ExitSubnet(id)
|
node.ExitSubnet(id)
|
||||||
require.False(t, netmap.BelongsToSubnet(&node, id))
|
require.False(t, netmap.BelongsToSubnet(node, id))
|
||||||
require.False(t, netmap.BelongsToSubnet(&node, subnetid.ID{}))
|
require.False(t, netmap.BelongsToSubnet(node, subnetid.ID{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBelongsToSubnet(t *testing.T) {
|
func TestBelongsToSubnet(t *testing.T) {
|
||||||
|
@ -120,7 +120,7 @@ func TestBelongsToSubnet(t *testing.T) {
|
||||||
|
|
||||||
node.EnterSubnet(id)
|
node.EnterSubnet(id)
|
||||||
|
|
||||||
require.True(t, netmap.BelongsToSubnet(&node, idZero))
|
require.True(t, netmap.BelongsToSubnet(node, idZero))
|
||||||
require.True(t, netmap.BelongsToSubnet(&node, id))
|
require.True(t, netmap.BelongsToSubnet(node, id))
|
||||||
require.False(t, netmap.BelongsToSubnet(&node, idMiss))
|
require.False(t, netmap.BelongsToSubnet(node, idMiss))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,92 +1,70 @@
|
||||||
package netmaptest
|
package netmaptest
|
||||||
|
|
||||||
import "github.com/nspcc-dev/neofs-sdk-go/netmap"
|
import (
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||||
func filter(withInner bool) *netmap.Filter {
|
subnetidtest "github.com/nspcc-dev/neofs-sdk-go/subnet/id/test"
|
||||||
x := netmap.NewFilter()
|
)
|
||||||
|
|
||||||
|
func filter(withInner bool) (x netmap.Filter) {
|
||||||
x.SetName("name")
|
x.SetName("name")
|
||||||
x.SetKey("key")
|
|
||||||
x.SetValue("value")
|
|
||||||
x.SetOperation(netmap.OpAND)
|
|
||||||
|
|
||||||
if withInner {
|
if withInner {
|
||||||
x.SetInnerFilters(*filter(false), *filter(false))
|
x.LogicalOR(filter(false), filter(false))
|
||||||
|
} else {
|
||||||
|
x.NumericGE("epoch", 13)
|
||||||
}
|
}
|
||||||
|
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter returns random netmap.Filter.
|
// Filter returns random netmap.Filter.
|
||||||
func Filter() *netmap.Filter {
|
func Filter() netmap.Filter {
|
||||||
return filter(true)
|
return filter(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replica returns random netmap.Replica.
|
// Replica returns random netmap.ReplicaDescriptor.
|
||||||
func Replica() *netmap.Replica {
|
func Replica() (x netmap.ReplicaDescriptor) {
|
||||||
x := netmap.NewReplica()
|
x.SetAmount(666)
|
||||||
|
x.SetSelectorName("selector")
|
||||||
|
|
||||||
x.SetCount(666)
|
return
|
||||||
x.SetSelector("selector")
|
|
||||||
|
|
||||||
return x
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selector returns random netmap.Selector.
|
// Selector returns random netmap.Selector.
|
||||||
func Selector() *netmap.Selector {
|
func Selector() (x netmap.Selector) {
|
||||||
x := netmap.NewSelector()
|
x.SetNodeAmount(11)
|
||||||
|
|
||||||
x.SetCount(11)
|
|
||||||
x.SetName("name")
|
x.SetName("name")
|
||||||
x.SetFilter("filter")
|
x.SetFilterName("filter")
|
||||||
x.SetAttribute("attribute")
|
x.SelectByBucketAttribute("attribute")
|
||||||
x.SetClause(netmap.ClauseDistinct)
|
x.SelectDistinct()
|
||||||
|
|
||||||
return x
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlacementPolicy returns random netmap.PlacementPolicy.
|
// PlacementPolicy returns random netmap.PlacementPolicy.
|
||||||
func PlacementPolicy() *netmap.PlacementPolicy {
|
func PlacementPolicy() (p netmap.PlacementPolicy) {
|
||||||
x := netmap.NewPlacementPolicy()
|
p.SetContainerBackupFactor(9)
|
||||||
|
p.AddFilters(Filter(), Filter())
|
||||||
|
p.AddReplicas(Replica(), Replica())
|
||||||
|
p.AddSelectors(Selector(), Selector())
|
||||||
|
p.RestrictSubnet(subnetidtest.ID())
|
||||||
|
|
||||||
x.SetContainerBackupFactor(9)
|
return
|
||||||
x.SetFilters(*Filter(), *Filter())
|
|
||||||
x.SetReplicas(*Replica(), *Replica())
|
|
||||||
x.SetSelectors(*Selector(), *Selector())
|
|
||||||
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkParameter returns random netmap.NetworkParameter.
|
|
||||||
func NetworkParameter() *netmap.NetworkParameter {
|
|
||||||
x := netmap.NewNetworkParameter()
|
|
||||||
|
|
||||||
x.SetKey([]byte("key"))
|
|
||||||
x.SetValue([]byte("value"))
|
|
||||||
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkConfig returns random netmap.NetworkConfig.
|
|
||||||
func NetworkConfig() *netmap.NetworkConfig {
|
|
||||||
x := netmap.NewNetworkConfig()
|
|
||||||
|
|
||||||
x.SetParameters(
|
|
||||||
*NetworkParameter(),
|
|
||||||
*NetworkParameter(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return x
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkInfo returns random netmap.NetworkInfo.
|
// NetworkInfo returns random netmap.NetworkInfo.
|
||||||
func NetworkInfo() *netmap.NetworkInfo {
|
func NetworkInfo() (x netmap.NetworkInfo) {
|
||||||
x := netmap.NewNetworkInfo()
|
|
||||||
|
|
||||||
x.SetCurrentEpoch(21)
|
x.SetCurrentEpoch(21)
|
||||||
x.SetMagicNumber(32)
|
x.SetMagicNumber(32)
|
||||||
x.SetMsPerBlock(43)
|
x.SetMsPerBlock(43)
|
||||||
x.SetNetworkConfig(NetworkConfig())
|
x.SetAuditFee(1)
|
||||||
|
x.SetStoragePrice(2)
|
||||||
|
x.SetContainerFee(3)
|
||||||
|
x.SetEigenTrustAlpha(0.4)
|
||||||
|
x.SetEigenTrustIterationAmount(5)
|
||||||
|
x.SetEpochDuration(6)
|
||||||
|
x.SetIRCandidateFee(7)
|
||||||
|
x.SetMaxObjectSize(8)
|
||||||
|
x.SetWithdrawalFee(9)
|
||||||
|
|
||||||
return x
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
// Package policy provides facilities for creating policy from SQL-like language.
|
|
||||||
// ANTLRv4 grammar is provided in `parser/Query.g4` and `parser/QueryLexer.g4`.
|
|
||||||
//
|
|
||||||
// Current limitations:
|
|
||||||
// 1. Filters must be defined before they are used.
|
|
||||||
// This requirement may be relaxed in future.
|
|
||||||
// 2. Keywords are key-sensitive. This can be changed if necessary
|
|
||||||
// https://github.com/antlr/antlr4/blob/master/doc/case-insensitive-lexing.md .
|
|
||||||
//
|
|
||||||
// Example query:
|
|
||||||
// REP 1 IN SPB
|
|
||||||
// REP 2 IN Americas
|
|
||||||
// CBF 4
|
|
||||||
// SELECT 1 IN City FROM SPBSSD AS SPB
|
|
||||||
// SELECT 2 IN SAME City FROM Americas AS Americas
|
|
||||||
// FILTER SSD EQ true AS IsSSD
|
|
||||||
// FILTER @IsSSD AND Country EQ "RU" AND City EQ "St.Petersburg" AS SPBSSD
|
|
||||||
// FILTER 'Continent' EQ 'North America' OR Continent EQ 'South America' AS Americas
|
|
||||||
package policy
|
|
141
policy/encode.go
141
policy/encode.go
|
@ -1,141 +0,0 @@
|
||||||
package policy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encode parses data of PlacementPolicy to a string.
|
|
||||||
func Encode(p *netmap.PlacementPolicy) []string {
|
|
||||||
if p == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
replicas = p.Replicas()
|
|
||||||
selectors = p.Selectors()
|
|
||||||
filters = p.Filters()
|
|
||||||
)
|
|
||||||
|
|
||||||
// 1 for container backup factor
|
|
||||||
result := make([]string, 0, len(replicas)+len(selectors)+len(filters)+1)
|
|
||||||
|
|
||||||
// first print replicas
|
|
||||||
encodeReplicas(replicas, &result)
|
|
||||||
|
|
||||||
// then backup factor
|
|
||||||
if backupFactor := p.ContainerBackupFactor(); backupFactor != 0 {
|
|
||||||
result = append(result, fmt.Sprintf("CBF %d", backupFactor))
|
|
||||||
}
|
|
||||||
|
|
||||||
// then selectors
|
|
||||||
encodeSelectors(selectors, &result)
|
|
||||||
|
|
||||||
// then filters
|
|
||||||
encodeFilters(filters, &result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeReplicas(replicas []netmap.Replica, dst *[]string) {
|
|
||||||
builder := new(strings.Builder)
|
|
||||||
|
|
||||||
for _, replica := range replicas {
|
|
||||||
builder.WriteString("REP ")
|
|
||||||
builder.WriteString(strconv.FormatUint(uint64(replica.Count()), 10))
|
|
||||||
|
|
||||||
if s := replica.Selector(); s != "" {
|
|
||||||
builder.WriteString(" IN ")
|
|
||||||
builder.WriteString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
*dst = append(*dst, builder.String())
|
|
||||||
builder.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeSelectors(selectors []netmap.Selector, dst *[]string) {
|
|
||||||
builder := new(strings.Builder)
|
|
||||||
|
|
||||||
for _, selector := range selectors {
|
|
||||||
builder.WriteString("SELECT ")
|
|
||||||
builder.WriteString(strconv.FormatUint(uint64(selector.Count()), 10))
|
|
||||||
|
|
||||||
if a := selector.Attribute(); a != "" {
|
|
||||||
builder.WriteString(" IN")
|
|
||||||
|
|
||||||
switch selector.Clause() {
|
|
||||||
case netmap.ClauseSame:
|
|
||||||
builder.WriteString(" SAME ")
|
|
||||||
case netmap.ClauseDistinct:
|
|
||||||
builder.WriteString(" DISTINCT ")
|
|
||||||
default:
|
|
||||||
builder.WriteString(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.WriteString(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f := selector.Filter(); f != "" {
|
|
||||||
builder.WriteString(" FROM ")
|
|
||||||
builder.WriteString(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n := selector.Name(); n != "" {
|
|
||||||
builder.WriteString(" AS ")
|
|
||||||
builder.WriteString(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
*dst = append(*dst, builder.String())
|
|
||||||
builder.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeFilters(filters []netmap.Filter, dst *[]string) {
|
|
||||||
builder := new(strings.Builder)
|
|
||||||
|
|
||||||
for _, filter := range filters {
|
|
||||||
builder.WriteString("FILTER ")
|
|
||||||
|
|
||||||
builder.WriteString(encodeFilter(&filter))
|
|
||||||
|
|
||||||
*dst = append(*dst, builder.String())
|
|
||||||
builder.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeFilter(filter *netmap.Filter) string {
|
|
||||||
builder := new(strings.Builder)
|
|
||||||
unspecified := filter.Operation() == 0
|
|
||||||
|
|
||||||
if k := filter.Key(); k != "" {
|
|
||||||
builder.WriteString(k)
|
|
||||||
builder.WriteString(" ")
|
|
||||||
builder.WriteString(filter.Operation().String())
|
|
||||||
builder.WriteString(" ")
|
|
||||||
builder.WriteString(filter.Value())
|
|
||||||
} else if n := filter.Name(); unspecified && n != "" {
|
|
||||||
builder.WriteString("@")
|
|
||||||
builder.WriteString(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, subfilter := range filter.InnerFilters() {
|
|
||||||
if i != 0 {
|
|
||||||
builder.WriteString(" ")
|
|
||||||
builder.WriteString(filter.Operation().String())
|
|
||||||
builder.WriteString(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.WriteString(encodeFilter(&subfilter))
|
|
||||||
}
|
|
||||||
|
|
||||||
if n := filter.Name(); n != "" && !unspecified {
|
|
||||||
builder.WriteString(" AS ")
|
|
||||||
builder.WriteString(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package policy_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/policy"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEncode(t *testing.T) {
|
|
||||||
testCases := []string{
|
|
||||||
`REP 1 IN X
|
|
||||||
CBF 1
|
|
||||||
SELECT 2 IN SAME Location FROM * AS X`,
|
|
||||||
|
|
||||||
`REP 1
|
|
||||||
SELECT 2 IN City FROM Good
|
|
||||||
FILTER Country EQ RU AS FromRU
|
|
||||||
FILTER @FromRU AND Rating GT 7 AS Good`,
|
|
||||||
|
|
||||||
`REP 7 IN SPB
|
|
||||||
SELECT 1 IN City FROM SPBSSD AS SPB
|
|
||||||
FILTER City EQ SPB AND SSD EQ true OR City EQ SPB AND Rating GE 5 AS SPBSSD`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
q, err := policy.Parse(testCase)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got := policy.Encode(q)
|
|
||||||
require.Equal(t, testCase, strings.Join(got, "\n"))
|
|
||||||
}
|
|
||||||
}
|
|
225
policy/json.go
225
policy/json.go
|
@ -1,225 +0,0 @@
|
||||||
package policy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
filter struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Key string `json:"key,omitempty"`
|
|
||||||
Op string `json:"op,omitempty"`
|
|
||||||
Value string `json:"value,omitempty"`
|
|
||||||
Filters []filter `json:"filters,omitempty"`
|
|
||||||
}
|
|
||||||
replica struct {
|
|
||||||
Count uint32 `json:"count"`
|
|
||||||
Selector string `json:"selector,omitempty"`
|
|
||||||
}
|
|
||||||
selector struct {
|
|
||||||
Count uint32 `json:"count"`
|
|
||||||
Attribute string `json:"attribute"`
|
|
||||||
Filter string `json:"filter,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Clause string `json:"clause,omitempty"`
|
|
||||||
}
|
|
||||||
placement struct {
|
|
||||||
Replicas []replica `json:"replicas"`
|
|
||||||
CBF uint32 `json:"container_backup_factor,omitempty"`
|
|
||||||
Selectors []selector `json:"selectors,omitempty"`
|
|
||||||
Filters []filter `json:"filters,omitempty"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToJSON converts placement policy to JSON.
|
|
||||||
func ToJSON(np *netmap.PlacementPolicy) ([]byte, error) {
|
|
||||||
p := new(placement)
|
|
||||||
p.CBF = np.ContainerBackupFactor()
|
|
||||||
p.Filters = make([]filter, len(np.Filters()))
|
|
||||||
for i, f := range np.Filters() {
|
|
||||||
p.Filters[i].fromNetmap(&f)
|
|
||||||
}
|
|
||||||
p.Selectors = make([]selector, len(np.Selectors()))
|
|
||||||
for i, s := range np.Selectors() {
|
|
||||||
p.Selectors[i].fromNetmap(&s)
|
|
||||||
}
|
|
||||||
p.Replicas = make([]replica, len(np.Replicas()))
|
|
||||||
for i, r := range np.Replicas() {
|
|
||||||
p.Replicas[i].fromNetmap(&r)
|
|
||||||
}
|
|
||||||
return json.Marshal(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromJSON creates placement policy from JSON.
|
|
||||||
func FromJSON(data []byte) (*netmap.PlacementPolicy, error) {
|
|
||||||
p := new(placement)
|
|
||||||
if err := json.Unmarshal(data, p); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rs := make([]netmap.Replica, len(p.Replicas))
|
|
||||||
for i := range p.Replicas {
|
|
||||||
rs[i] = *p.Replicas[i].toNetmap()
|
|
||||||
}
|
|
||||||
|
|
||||||
var fs []netmap.Filter
|
|
||||||
if len(p.Filters) != 0 {
|
|
||||||
fs = make([]netmap.Filter, len(p.Filters))
|
|
||||||
for i := range p.Filters {
|
|
||||||
f, err := p.Filters[i].toNetmap()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fs[i] = *f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ss []netmap.Selector
|
|
||||||
if len(p.Selectors) != 0 {
|
|
||||||
ss = make([]netmap.Selector, len(p.Selectors))
|
|
||||||
for i := range p.Selectors {
|
|
||||||
s, err := p.Selectors[i].toNetmap()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ss[i] = *s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pp := new(netmap.PlacementPolicy)
|
|
||||||
pp.SetReplicas(rs...)
|
|
||||||
pp.SetContainerBackupFactor(p.CBF)
|
|
||||||
pp.SetFilters(fs...)
|
|
||||||
pp.SetSelectors(ss...)
|
|
||||||
return pp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *replica) toNetmap() *netmap.Replica {
|
|
||||||
nr := new(netmap.Replica)
|
|
||||||
nr.SetCount(r.Count)
|
|
||||||
nr.SetSelector(r.Selector)
|
|
||||||
return nr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *replica) fromNetmap(nr *netmap.Replica) {
|
|
||||||
r.Count = nr.Count()
|
|
||||||
r.Selector = nr.Selector()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filter) toNetmap() (*netmap.Filter, error) {
|
|
||||||
var op netmap.Operation
|
|
||||||
switch strings.ToUpper(f.Op) {
|
|
||||||
case "EQ":
|
|
||||||
op = netmap.OpEQ
|
|
||||||
case "NE":
|
|
||||||
op = netmap.OpNE
|
|
||||||
case "GT":
|
|
||||||
op = netmap.OpGT
|
|
||||||
case "GE":
|
|
||||||
op = netmap.OpGE
|
|
||||||
case "LT":
|
|
||||||
op = netmap.OpLT
|
|
||||||
case "LE":
|
|
||||||
op = netmap.OpLE
|
|
||||||
case "AND":
|
|
||||||
op = netmap.OpAND
|
|
||||||
case "OR":
|
|
||||||
op = netmap.OpOR
|
|
||||||
case "":
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("%w: '%s'", ErrUnknownOp, f.Op)
|
|
||||||
}
|
|
||||||
|
|
||||||
var fs []netmap.Filter
|
|
||||||
if len(f.Filters) != 0 {
|
|
||||||
fs = make([]netmap.Filter, len(f.Filters))
|
|
||||||
for i := range f.Filters {
|
|
||||||
var err error
|
|
||||||
fsp, err := f.Filters[i].toNetmap()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fs[i] = *fsp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nf := new(netmap.Filter)
|
|
||||||
nf.SetInnerFilters(fs...)
|
|
||||||
nf.SetOperation(op)
|
|
||||||
nf.SetName(f.Name)
|
|
||||||
nf.SetValue(f.Value)
|
|
||||||
nf.SetKey(f.Key)
|
|
||||||
return nf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filter) fromNetmap(nf *netmap.Filter) {
|
|
||||||
f.Name = nf.Name()
|
|
||||||
f.Key = nf.Key()
|
|
||||||
f.Value = nf.Value()
|
|
||||||
switch nf.Operation() {
|
|
||||||
case netmap.OpEQ:
|
|
||||||
f.Op = "EQ"
|
|
||||||
case netmap.OpNE:
|
|
||||||
f.Op = "NE"
|
|
||||||
case netmap.OpGT:
|
|
||||||
f.Op = "GT"
|
|
||||||
case netmap.OpGE:
|
|
||||||
f.Op = "GE"
|
|
||||||
case netmap.OpLT:
|
|
||||||
f.Op = "LT"
|
|
||||||
case netmap.OpLE:
|
|
||||||
f.Op = "LE"
|
|
||||||
case netmap.OpAND:
|
|
||||||
f.Op = "AND"
|
|
||||||
case netmap.OpOR:
|
|
||||||
f.Op = "OR"
|
|
||||||
default:
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
if nf.InnerFilters() != nil {
|
|
||||||
f.Filters = make([]filter, len(nf.InnerFilters()))
|
|
||||||
for i, sf := range nf.InnerFilters() {
|
|
||||||
f.Filters[i].fromNetmap(&sf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *selector) toNetmap() (*netmap.Selector, error) {
|
|
||||||
var c netmap.Clause
|
|
||||||
switch strings.ToUpper(s.Clause) {
|
|
||||||
case "SAME":
|
|
||||||
c = netmap.ClauseSame
|
|
||||||
case "DISTINCT":
|
|
||||||
c = netmap.ClauseDistinct
|
|
||||||
case "":
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("%w: '%s'", ErrUnknownClause, s.Clause)
|
|
||||||
}
|
|
||||||
ns := new(netmap.Selector)
|
|
||||||
ns.SetName(s.Name)
|
|
||||||
ns.SetAttribute(s.Attribute)
|
|
||||||
ns.SetCount(s.Count)
|
|
||||||
ns.SetClause(c)
|
|
||||||
ns.SetFilter(s.Filter)
|
|
||||||
return ns, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *selector) fromNetmap(ns *netmap.Selector) {
|
|
||||||
s.Name = ns.Name()
|
|
||||||
s.Filter = ns.Filter()
|
|
||||||
s.Count = ns.Count()
|
|
||||||
s.Attribute = ns.Attribute()
|
|
||||||
switch ns.Clause() {
|
|
||||||
case netmap.ClauseSame:
|
|
||||||
s.Clause = "same"
|
|
||||||
case netmap.ClauseDistinct:
|
|
||||||
s.Clause = "distinct"
|
|
||||||
default:
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
s.Name = ns.Name()
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
package policy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestToJSON(t *testing.T) {
|
|
||||||
check := func(t *testing.T, p *netmap.PlacementPolicy, json string) {
|
|
||||||
data, err := ToJSON(p)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.JSONEq(t, json, string(data))
|
|
||||||
|
|
||||||
np, err := FromJSON(data)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, p, np)
|
|
||||||
}
|
|
||||||
t.Run("SimpleREP", func(t *testing.T) {
|
|
||||||
p := new(netmap.PlacementPolicy)
|
|
||||||
p.SetReplicas(newReplica("", 3))
|
|
||||||
check(t, p, `{"replicas":[{"count":3}]}`)
|
|
||||||
})
|
|
||||||
t.Run("REPWithCBF", func(t *testing.T) {
|
|
||||||
p := new(netmap.PlacementPolicy)
|
|
||||||
p.SetReplicas(newReplica("", 3))
|
|
||||||
p.SetContainerBackupFactor(3)
|
|
||||||
check(t, p, `{"replicas":[{"count":3}],"container_backup_factor":3}`)
|
|
||||||
})
|
|
||||||
t.Run("REPFromSelector", func(t *testing.T) {
|
|
||||||
p := new(netmap.PlacementPolicy)
|
|
||||||
p.SetReplicas(newReplica("Nodes", 3))
|
|
||||||
p.SetContainerBackupFactor(3)
|
|
||||||
p.SetSelectors(
|
|
||||||
newSelector(1, netmap.ClauseDistinct, "City", "", "Nodes"))
|
|
||||||
check(t, p, `{
|
|
||||||
"replicas":[{"count":3,"selector":"Nodes"}],
|
|
||||||
"container_backup_factor":3,
|
|
||||||
"selectors": [{
|
|
||||||
"name":"Nodes",
|
|
||||||
"attribute":"City",
|
|
||||||
"clause":"distinct",
|
|
||||||
"count":1
|
|
||||||
}]}`)
|
|
||||||
})
|
|
||||||
t.Run("FilterOps", func(t *testing.T) {
|
|
||||||
p := new(netmap.PlacementPolicy)
|
|
||||||
p.SetReplicas(newReplica("Nodes", 3))
|
|
||||||
p.SetContainerBackupFactor(3)
|
|
||||||
p.SetSelectors(
|
|
||||||
newSelector(1, netmap.ClauseSame, "City", "Good", "Nodes"))
|
|
||||||
p.SetFilters(
|
|
||||||
newFilter("GoodRating", "Rating", "5", netmap.OpGE),
|
|
||||||
newFilter("Good", "", "", netmap.OpOR,
|
|
||||||
newFilter("GoodRating", "", "", 0),
|
|
||||||
newFilter("", "Attr1", "Val1", netmap.OpEQ),
|
|
||||||
newFilter("", "Attr2", "Val2", netmap.OpNE),
|
|
||||||
newFilter("", "", "", netmap.OpAND,
|
|
||||||
newFilter("", "Attr4", "2", netmap.OpLT),
|
|
||||||
newFilter("", "Attr5", "3", netmap.OpLE)),
|
|
||||||
newFilter("", "Attr3", "1", netmap.OpGT)),
|
|
||||||
)
|
|
||||||
check(t, p, `{
|
|
||||||
"replicas":[{"count":3,"selector":"Nodes"}],
|
|
||||||
"container_backup_factor":3,
|
|
||||||
"selectors": [{"name":"Nodes","attribute":"City","clause":"same","count":1,"filter":"Good"}],
|
|
||||||
"filters": [
|
|
||||||
{"name":"GoodRating","key":"Rating","op":"GE","value":"5"},
|
|
||||||
{"name":"Good","op":"OR","filters":[
|
|
||||||
{"name":"GoodRating"},
|
|
||||||
{"key":"Attr1","op":"EQ","value":"Val1"},
|
|
||||||
{"key":"Attr2","op":"NE","value":"Val2"},
|
|
||||||
{"op":"AND","filters":[
|
|
||||||
{"key":"Attr4","op":"LT","value":"2"},
|
|
||||||
{"key":"Attr5","op":"LE","value":"3"}
|
|
||||||
]},
|
|
||||||
{"key":"Attr3","op":"GT","value":"1"}
|
|
||||||
]}
|
|
||||||
]}`)
|
|
||||||
})
|
|
||||||
}
|
|
311
policy/query.go
311
policy/query.go
|
@ -1,311 +0,0 @@
|
||||||
package policy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/antlr/antlr4/runtime/Go/antlr"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/policy/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidNumber is returned when a value of SELECT is 0.
|
|
||||||
ErrInvalidNumber = errors.New("policy: expected positive integer")
|
|
||||||
// ErrUnknownClause is returned when a statement(clause) in a query is unknown.
|
|
||||||
ErrUnknownClause = errors.New("policy: unknown clause")
|
|
||||||
// ErrUnknownOp is returned when an operation in a query is unknown.
|
|
||||||
ErrUnknownOp = errors.New("policy: unknown operation")
|
|
||||||
// ErrUnknownFilter is returned when a value of FROM in a query is unknown.
|
|
||||||
ErrUnknownFilter = errors.New("policy: filter not found")
|
|
||||||
// ErrUnknownSelector is returned when a value of IN is unknown.
|
|
||||||
ErrUnknownSelector = errors.New("policy: selector not found")
|
|
||||||
// ErrSyntaxError is returned for errors found by ANTLR parser.
|
|
||||||
ErrSyntaxError = errors.New("policy: syntax error")
|
|
||||||
)
|
|
||||||
|
|
||||||
type policyVisitor struct {
|
|
||||||
errors []error
|
|
||||||
parser.BaseQueryVisitor
|
|
||||||
antlr.DefaultErrorListener
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses s into a placement policy.
|
|
||||||
func Parse(s string) (*netmap.PlacementPolicy, error) {
|
|
||||||
return parse(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPolicyVisitor() *policyVisitor {
|
|
||||||
return &policyVisitor{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(s string) (*netmap.PlacementPolicy, error) {
|
|
||||||
input := antlr.NewInputStream(s)
|
|
||||||
lexer := parser.NewQueryLexer(input)
|
|
||||||
stream := antlr.NewCommonTokenStream(lexer, 0)
|
|
||||||
|
|
||||||
p := parser.NewQuery(stream)
|
|
||||||
p.BuildParseTrees = true
|
|
||||||
|
|
||||||
v := newPolicyVisitor()
|
|
||||||
p.RemoveErrorListeners()
|
|
||||||
p.AddErrorListener(v)
|
|
||||||
pl := p.Policy().Accept(v)
|
|
||||||
|
|
||||||
if len(v.errors) != 0 {
|
|
||||||
return nil, v.errors[0]
|
|
||||||
}
|
|
||||||
if err := validatePolicy(pl.(*netmap.PlacementPolicy)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pl.(*netmap.PlacementPolicy), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *policyVisitor) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) {
|
|
||||||
p.reportError(fmt.Errorf("%w: line %d:%d %s", ErrSyntaxError, line, column, msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *policyVisitor) reportError(err error) interface{} {
|
|
||||||
p.errors = append(p.errors, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitPolicy implements parser.QueryVisitor interface.
|
|
||||||
func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) interface{} {
|
|
||||||
if len(p.errors) != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pl := new(netmap.PlacementPolicy)
|
|
||||||
|
|
||||||
repStmts := ctx.AllRepStmt()
|
|
||||||
rs := make([]netmap.Replica, 0, len(repStmts))
|
|
||||||
for _, r := range repStmts {
|
|
||||||
res, ok := r.Accept(p).(*netmap.Replica)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rs = append(rs, *res)
|
|
||||||
}
|
|
||||||
pl.SetReplicas(rs...)
|
|
||||||
|
|
||||||
if cbfStmt := ctx.CbfStmt(); cbfStmt != nil {
|
|
||||||
cbf, ok := cbfStmt.(*parser.CbfStmtContext).Accept(p).(uint32)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
pl.SetContainerBackupFactor(cbf)
|
|
||||||
}
|
|
||||||
|
|
||||||
selStmts := ctx.AllSelectStmt()
|
|
||||||
ss := make([]netmap.Selector, 0, len(selStmts))
|
|
||||||
for _, s := range selStmts {
|
|
||||||
res, ok := s.Accept(p).(*netmap.Selector)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ss = append(ss, *res)
|
|
||||||
}
|
|
||||||
pl.SetSelectors(ss...)
|
|
||||||
|
|
||||||
filtStmts := ctx.AllFilterStmt()
|
|
||||||
fs := make([]netmap.Filter, 0, len(filtStmts))
|
|
||||||
for _, f := range filtStmts {
|
|
||||||
fs = append(fs, *f.Accept(p).(*netmap.Filter))
|
|
||||||
}
|
|
||||||
pl.SetFilters(fs...)
|
|
||||||
|
|
||||||
return pl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) interface{} {
|
|
||||||
cbf, err := strconv.ParseUint(ctx.GetBackupFactor().GetText(), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return p.reportError(ErrInvalidNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint32(cbf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitRepStmt implements parser.QueryVisitor interface.
|
|
||||||
func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) interface{} {
|
|
||||||
num, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return p.reportError(ErrInvalidNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
rs := new(netmap.Replica)
|
|
||||||
rs.SetCount(uint32(num))
|
|
||||||
|
|
||||||
if sel := ctx.GetSelector(); sel != nil {
|
|
||||||
rs.SetSelector(sel.GetText())
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitSelectStmt implements parser.QueryVisitor interface.
|
|
||||||
func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) interface{} {
|
|
||||||
res, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return p.reportError(ErrInvalidNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := new(netmap.Selector)
|
|
||||||
s.SetCount(uint32(res))
|
|
||||||
|
|
||||||
if clStmt := ctx.Clause(); clStmt != nil {
|
|
||||||
s.SetClause(clauseFromString(clStmt.GetText()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if bStmt := ctx.GetBucket(); bStmt != nil {
|
|
||||||
s.SetAttribute(ctx.GetBucket().GetText())
|
|
||||||
}
|
|
||||||
|
|
||||||
s.SetFilter(ctx.GetFilter().GetText()) // either ident or wildcard
|
|
||||||
|
|
||||||
if ctx.AS() != nil {
|
|
||||||
s.SetName(ctx.GetName().GetText())
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitFilterStmt implements parser.QueryVisitor interface.
|
|
||||||
func (p *policyVisitor) VisitFilterStmt(ctx *parser.FilterStmtContext) interface{} {
|
|
||||||
f := p.VisitFilterExpr(ctx.GetExpr().(*parser.FilterExprContext)).(*netmap.Filter)
|
|
||||||
f.SetName(ctx.GetName().GetText())
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface{} {
|
|
||||||
if eCtx := ctx.Expr(); eCtx != nil {
|
|
||||||
return eCtx.Accept(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inner := ctx.GetInner(); inner != nil {
|
|
||||||
return inner.Accept(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
f := new(netmap.Filter)
|
|
||||||
op := operationFromString(ctx.GetOp().GetText())
|
|
||||||
f.SetOperation(op)
|
|
||||||
|
|
||||||
f1 := *ctx.GetF1().Accept(p).(*netmap.Filter)
|
|
||||||
f2 := *ctx.GetF2().Accept(p).(*netmap.Filter)
|
|
||||||
|
|
||||||
// Consider f1=(.. AND ..) AND f2. This can be merged because our AND operation
|
|
||||||
// is of arbitrary arity. ANTLR generates left-associative parse-tree by default.
|
|
||||||
if f1.Operation() == op {
|
|
||||||
f.SetInnerFilters(append(f1.InnerFilters(), f2)...)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
f.SetInnerFilters(f1, f2)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitFilterKey implements parser.QueryVisitor interface.
|
|
||||||
func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) interface{} {
|
|
||||||
if id := ctx.Ident(); id != nil {
|
|
||||||
return id.GetText()
|
|
||||||
}
|
|
||||||
|
|
||||||
str := ctx.STRING().GetText()
|
|
||||||
return str[1 : len(str)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) interface{} {
|
|
||||||
if id := ctx.Ident(); id != nil {
|
|
||||||
return id.GetText()
|
|
||||||
}
|
|
||||||
|
|
||||||
if num := ctx.Number(); num != nil {
|
|
||||||
return num.GetText()
|
|
||||||
}
|
|
||||||
|
|
||||||
str := ctx.STRING().GetText()
|
|
||||||
return str[1 : len(str)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisitExpr implements parser.QueryVisitor interface.
|
|
||||||
func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) interface{} {
|
|
||||||
f := new(netmap.Filter)
|
|
||||||
if flt := ctx.GetFilter(); flt != nil {
|
|
||||||
f.SetName(flt.GetText())
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
key := ctx.GetKey().Accept(p)
|
|
||||||
opStr := ctx.SIMPLE_OP().GetText()
|
|
||||||
value := ctx.GetValue().Accept(p)
|
|
||||||
|
|
||||||
f.SetKey(key.(string))
|
|
||||||
f.SetOperation(operationFromString(opStr))
|
|
||||||
f.SetValue(value.(string))
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// validatePolicy checks high-level constraints such as filter link in SELECT
|
|
||||||
// being actually defined in FILTER section.
|
|
||||||
func validatePolicy(p *netmap.PlacementPolicy) error {
|
|
||||||
seenFilters := map[string]bool{}
|
|
||||||
for _, f := range p.Filters() {
|
|
||||||
seenFilters[f.Name()] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
seenSelectors := map[string]bool{}
|
|
||||||
for _, s := range p.Selectors() {
|
|
||||||
if flt := s.Filter(); flt != netmap.MainFilterName && !seenFilters[flt] {
|
|
||||||
return fmt.Errorf("%w: '%s'", ErrUnknownFilter, flt)
|
|
||||||
}
|
|
||||||
seenSelectors[s.Name()] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range p.Replicas() {
|
|
||||||
if sel := r.Selector(); sel != "" && !seenSelectors[sel] {
|
|
||||||
return fmt.Errorf("%w: '%s'", ErrUnknownSelector, sel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clauseFromString(s string) netmap.Clause {
|
|
||||||
switch strings.ToUpper(s) {
|
|
||||||
case "SAME":
|
|
||||||
return netmap.ClauseSame
|
|
||||||
case "DISTINCT":
|
|
||||||
return netmap.ClauseDistinct
|
|
||||||
default:
|
|
||||||
// Such errors should be handled by ANTLR code thus this panic.
|
|
||||||
panic(fmt.Errorf("BUG: invalid clause: %s", s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func operationFromString(op string) netmap.Operation {
|
|
||||||
switch strings.ToUpper(op) {
|
|
||||||
case "AND":
|
|
||||||
return netmap.OpAND
|
|
||||||
case "OR":
|
|
||||||
return netmap.OpOR
|
|
||||||
case "EQ":
|
|
||||||
return netmap.OpEQ
|
|
||||||
case "NE":
|
|
||||||
return netmap.OpNE
|
|
||||||
case "GE":
|
|
||||||
return netmap.OpGE
|
|
||||||
case "GT":
|
|
||||||
return netmap.OpGT
|
|
||||||
case "LE":
|
|
||||||
return netmap.OpLE
|
|
||||||
case "LT":
|
|
||||||
return netmap.OpLT
|
|
||||||
default:
|
|
||||||
// Such errors should be handled by ANTLR code thus this panic.
|
|
||||||
panic(fmt.Errorf("BUG: invalid operation: %s", op))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,324 +0,0 @@
|
||||||
package policy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSimple(t *testing.T) {
|
|
||||||
q := `REP 3`
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetFilters([]netmap.Filter{}...)
|
|
||||||
expected.SetSelectors([]netmap.Selector{}...)
|
|
||||||
expected.SetReplicas(newReplica("", 3))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimpleWithHRWB(t *testing.T) {
|
|
||||||
q := `REP 3 CBF 4`
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetFilters([]netmap.Filter{}...)
|
|
||||||
expected.SetSelectors([]netmap.Selector{}...)
|
|
||||||
expected.SetReplicas(newReplica("", 3))
|
|
||||||
expected.SetContainerBackupFactor(4)
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFromSelect(t *testing.T) {
|
|
||||||
q := `REP 1 IN SPB
|
|
||||||
SELECT 1 IN City FROM * AS SPB`
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetFilters([]netmap.Filter{}...)
|
|
||||||
expected.SetSelectors(newSelector(1, netmap.ClauseUnspecified, "City", "*", "SPB"))
|
|
||||||
expected.SetReplicas(newReplica("SPB", 1))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/nspcc-dev/neofs-node/issues/46
|
|
||||||
func TestFromSelectNoAttribute(t *testing.T) {
|
|
||||||
t.Run("Simple", func(t *testing.T) {
|
|
||||||
q := `REP 2
|
|
||||||
SELECT 6 FROM *`
|
|
||||||
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetFilters([]netmap.Filter{}...)
|
|
||||||
expected.SetSelectors(newSelector(6, netmap.ClauseUnspecified, "", "*", ""))
|
|
||||||
expected.SetReplicas(newReplica("", 2))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("with filter", func(t *testing.T) {
|
|
||||||
q := `REP 2
|
|
||||||
SELECT 6 FROM F
|
|
||||||
FILTER StorageType EQ SSD AS F`
|
|
||||||
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetFilters(newFilter("F", "StorageType", "SSD", netmap.OpEQ))
|
|
||||||
expected.SetSelectors(newSelector(6, netmap.ClauseUnspecified, "", "F", ""))
|
|
||||||
expected.SetReplicas(newReplica("", 2))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestString(t *testing.T) {
|
|
||||||
qTemplate := `REP 1
|
|
||||||
SELECT 1 IN City FROM Filt
|
|
||||||
FILTER Property EQ %s AND Something NE 7 AS Filt`
|
|
||||||
|
|
||||||
testCases := []string{
|
|
||||||
`"double-quoted"`,
|
|
||||||
`"with ' single"`,
|
|
||||||
`'single-quoted'`,
|
|
||||||
`'with " double'`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range testCases {
|
|
||||||
t.Run(s, func(t *testing.T) {
|
|
||||||
q := fmt.Sprintf(qTemplate, s)
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := newFilter("Filt", "", "", netmap.OpAND,
|
|
||||||
newFilter("", "Property", s[1:len(s)-1], netmap.OpEQ),
|
|
||||||
newFilter("", "Something", "7", netmap.OpNE))
|
|
||||||
require.EqualValues(t, []netmap.Filter{expected}, r.Filters())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFromSelectClause(t *testing.T) {
|
|
||||||
q := `REP 4
|
|
||||||
SELECT 3 IN Country FROM *
|
|
||||||
SELECT 2 IN SAME City FROM *
|
|
||||||
SELECT 1 IN DISTINCT Continent FROM *`
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetFilters([]netmap.Filter{}...)
|
|
||||||
expected.SetSelectors(
|
|
||||||
newSelector(3, netmap.ClauseUnspecified, "Country", "*", ""),
|
|
||||||
newSelector(2, netmap.ClauseSame, "City", "*", ""),
|
|
||||||
newSelector(1, netmap.ClauseDistinct, "Continent", "*", ""))
|
|
||||||
expected.SetReplicas(newReplica("", 4))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimpleFilter(t *testing.T) {
|
|
||||||
q := `REP 1
|
|
||||||
SELECT 1 IN City FROM Good
|
|
||||||
FILTER Rating GT 7 AS Good`
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetReplicas(newReplica("", 1))
|
|
||||||
expected.SetSelectors(
|
|
||||||
newSelector(1, netmap.ClauseUnspecified, "City", "Good", ""))
|
|
||||||
expected.SetFilters(newFilter("Good", "Rating", "7", netmap.OpGT))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilterReference(t *testing.T) {
|
|
||||||
q := `REP 1
|
|
||||||
SELECT 2 IN City FROM Good
|
|
||||||
FILTER Country EQ "RU" AS FromRU
|
|
||||||
FILTER @FromRU AND Rating GT 7 AS Good`
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetReplicas(newReplica("", 1))
|
|
||||||
expected.SetSelectors(
|
|
||||||
newSelector(2, netmap.ClauseUnspecified, "City", "Good", ""))
|
|
||||||
expected.SetFilters(
|
|
||||||
newFilter("FromRU", "Country", "RU", netmap.OpEQ),
|
|
||||||
newFilter("Good", "", "", netmap.OpAND,
|
|
||||||
newFilter("FromRU", "", "", 0),
|
|
||||||
newFilter("", "Rating", "7", netmap.OpGT)),
|
|
||||||
)
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilterOps(t *testing.T) {
|
|
||||||
q := `REP 1
|
|
||||||
SELECT 2 IN City FROM Good
|
|
||||||
FILTER A GT 1 AND B GE 2 AND C LT 3 AND D LE 4
|
|
||||||
AND E EQ 5 AND F NE 6 AS Good`
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetReplicas(newReplica("", 1))
|
|
||||||
expected.SetSelectors(
|
|
||||||
newSelector(2, netmap.ClauseUnspecified, "City", "Good", ""))
|
|
||||||
expected.SetFilters(newFilter("Good", "", "", netmap.OpAND,
|
|
||||||
newFilter("", "A", "1", netmap.OpGT),
|
|
||||||
newFilter("", "B", "2", netmap.OpGE),
|
|
||||||
newFilter("", "C", "3", netmap.OpLT),
|
|
||||||
newFilter("", "D", "4", netmap.OpLE),
|
|
||||||
newFilter("", "E", "5", netmap.OpEQ),
|
|
||||||
newFilter("", "F", "6", netmap.OpNE)))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithFilterPrecedence(t *testing.T) {
|
|
||||||
q := `REP 7 IN SPB
|
|
||||||
SELECT 1 IN City FROM SPBSSD AS SPB
|
|
||||||
FILTER City EQ "SPB" AND SSD EQ true OR City EQ "SPB" AND Rating GE 5 AS SPBSSD`
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetReplicas(newReplica("SPB", 7))
|
|
||||||
expected.SetSelectors(
|
|
||||||
newSelector(1, netmap.ClauseUnspecified, "City", "SPBSSD", "SPB"))
|
|
||||||
expected.SetFilters(
|
|
||||||
newFilter("SPBSSD", "", "", netmap.OpOR,
|
|
||||||
newFilter("", "", "", netmap.OpAND,
|
|
||||||
newFilter("", "City", "SPB", netmap.OpEQ),
|
|
||||||
newFilter("", "SSD", "true", netmap.OpEQ)),
|
|
||||||
newFilter("", "", "", netmap.OpAND,
|
|
||||||
newFilter("", "City", "SPB", netmap.OpEQ),
|
|
||||||
newFilter("", "Rating", "5", netmap.OpGE))))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBrackets(t *testing.T) {
|
|
||||||
q := `REP 7 IN SPB
|
|
||||||
SELECT 1 IN City FROM SPBSSD AS SPB
|
|
||||||
FILTER ( City EQ "SPB" OR SSD EQ true ) AND (City EQ "SPB" OR Rating GE 5) AS SPBSSD`
|
|
||||||
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetReplicas(newReplica("SPB", 7))
|
|
||||||
expected.SetSelectors(
|
|
||||||
newSelector(1, netmap.ClauseUnspecified, "City", "SPBSSD", "SPB"))
|
|
||||||
expected.SetFilters(
|
|
||||||
newFilter("SPBSSD", "", "", netmap.OpAND,
|
|
||||||
newFilter("", "", "", netmap.OpOR,
|
|
||||||
newFilter("", "City", "SPB", netmap.OpEQ),
|
|
||||||
newFilter("", "SSD", "true", netmap.OpEQ)),
|
|
||||||
newFilter("", "", "", netmap.OpOR,
|
|
||||||
newFilter("", "City", "SPB", netmap.OpEQ),
|
|
||||||
newFilter("", "Rating", "5", netmap.OpGE))))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidation(t *testing.T) {
|
|
||||||
t.Run("MissingSelector", func(t *testing.T) {
|
|
||||||
q := `REP 3 IN RU`
|
|
||||||
_, err := Parse(q)
|
|
||||||
require.True(t, errors.Is(err, ErrUnknownSelector), "got: %v", err)
|
|
||||||
})
|
|
||||||
t.Run("MissingFilter", func(t *testing.T) {
|
|
||||||
q := `REP 3
|
|
||||||
SELECT 1 IN City FROM MissingFilter`
|
|
||||||
_, err := Parse(q)
|
|
||||||
require.True(t, errors.Is(err, ErrUnknownFilter), "got: %v", err)
|
|
||||||
})
|
|
||||||
t.Run("UnknownOp", func(t *testing.T) {
|
|
||||||
q := `REP 3
|
|
||||||
SELECT 1 IN City FROM F
|
|
||||||
FILTER Country KEK RU AS F`
|
|
||||||
_, err := Parse(q)
|
|
||||||
require.True(t, errors.Is(err, ErrSyntaxError), "got: %v", err)
|
|
||||||
})
|
|
||||||
t.Run("TypoInREP", func(t *testing.T) {
|
|
||||||
q := `REK 3`
|
|
||||||
_, err := Parse(q)
|
|
||||||
require.True(t, errors.Is(err, ErrSyntaxError))
|
|
||||||
})
|
|
||||||
t.Run("InvalidFilterName", func(t *testing.T) {
|
|
||||||
q := `REP 3
|
|
||||||
SELECT 1 IN City FROM F
|
|
||||||
FILTER Good AND Country EQ RU AS F
|
|
||||||
FILTER Rating EQ 5 AS Good`
|
|
||||||
_, err := Parse(q)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks that an error is returned in cases when positive 32-bit integer is expected.
|
|
||||||
func TestInvalidNumbers(t *testing.T) {
|
|
||||||
tmpls := []string{
|
|
||||||
"REP %d",
|
|
||||||
"REP 1 CBF %d",
|
|
||||||
"REP 1 SELECT %d FROM *",
|
|
||||||
}
|
|
||||||
for i := range tmpls {
|
|
||||||
zero := fmt.Sprintf(tmpls[i], 0)
|
|
||||||
t.Run(zero, func(t *testing.T) {
|
|
||||||
_, err := Parse(zero)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
big := fmt.Sprintf(tmpls[i], int64(math.MaxUint32)+1)
|
|
||||||
t.Run(big, func(t *testing.T) {
|
|
||||||
_, err := Parse(big)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilterStringSymbols(t *testing.T) {
|
|
||||||
q := `REP 1 IN S
|
|
||||||
SELECT 1 FROM F AS S
|
|
||||||
FILTER "UN-LOCODE" EQ "RU LED" AS F`
|
|
||||||
|
|
||||||
expected := new(netmap.PlacementPolicy)
|
|
||||||
expected.SetReplicas(newReplica("S", 1))
|
|
||||||
expected.SetSelectors(
|
|
||||||
newSelector(1, netmap.ClauseUnspecified, "", "F", "S"))
|
|
||||||
expected.SetFilters(newFilter("F", "UN-LOCODE", "RU LED", netmap.OpEQ))
|
|
||||||
|
|
||||||
r, err := Parse(q)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFilter(name, key, value string, op netmap.Operation, sub ...netmap.Filter) (f netmap.Filter) {
|
|
||||||
f.SetName(name)
|
|
||||||
f.SetKey(key)
|
|
||||||
f.SetValue(value)
|
|
||||||
f.SetOperation(op)
|
|
||||||
f.SetInnerFilters(sub...)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func newReplica(s string, c uint32) (r netmap.Replica) {
|
|
||||||
r.SetSelector(s)
|
|
||||||
r.SetCount(c)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSelector(count uint32, c netmap.Clause, attr, f, name string) (s netmap.Selector) {
|
|
||||||
s.SetCount(count)
|
|
||||||
s.SetClause(c)
|
|
||||||
s.SetAttribute(attr)
|
|
||||||
s.SetFilter(f)
|
|
||||||
s.SetName(name)
|
|
||||||
return s
|
|
||||||
}
|
|
|
@ -45,7 +45,7 @@ func TestBuildPoolCreateSessionFailed(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
ni := &netmap.NodeInfo{}
|
ni := &netmap.NodeInfo{}
|
||||||
ni.SetAddresses("addr1", "addr2")
|
ni.SetNetworkEndpoints("addr1", "addr2")
|
||||||
|
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
mockClient := NewMockClient(ctrl)
|
mockClient := NewMockClient(ctrl)
|
||||||
|
|
Loading…
Reference in a new issue