Skip to content

Commit f7ccdb8

Browse files
EC Policer (#3557)
2 parents 17a9b61 + a759fff commit f7ccdb8

File tree

27 files changed

+1180
-99
lines changed

27 files changed

+1180
-99
lines changed

cmd/neofs-lens/internal/storage/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func listFunc(cmd *cobra.Command, _ []string) error {
3434
defer storage.Close()
3535

3636
var (
37-
addrs []objectcore.AddressWithType
37+
addrs []objectcore.AddressWithAttributes
3838
cursor *engine.Cursor
3939
)
4040
for {

cmd/neofs-lens/internal/storage/sanity.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func sanityCheck(cmd *cobra.Command, _ []string) error {
142142

143143
func checkShard(cmd *cobra.Command, sh storageShard) (int, error) {
144144
var (
145-
addrs []objectcore.AddressWithType
145+
addrs []objectcore.AddressWithAttributes
146146
cursor *meta.Cursor
147147
err error
148148
objectsChecked int

cmd/neofs-node/object.go

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func initObjectService(c *cfg) {
204204
policer.WithMaxCapacity(c.appCfg.Policer.MaxWorkers),
205205
policer.WithPool(c.cfgObject.pool.replication),
206206
policer.WithNodeLoader(c),
207-
policer.WithNetwork(noEC{c}),
207+
policer.WithNetwork(c),
208208
policer.WithReplicationCooldown(c.appCfg.Policer.ReplicationCooldown),
209209
policer.WithObjectBatchSize(c.appCfg.Policer.ObjectBatchSize),
210210
)
@@ -695,32 +695,13 @@ func (o objectSource) SearchOne(ctx context.Context, cnr cid.ID, filters objectS
695695
// IsLocalNodePublicKey implements [getsvc.NeoFSNetwork].
696696
func (c *cfg) IsLocalNodePublicKey(b []byte) bool { return c.IsLocalKey(b) }
697697

698-
// temporary wrapper until [getsvc.NeoFSNetwork] and [policer.Network]
699-
// interfaces converge.
700-
type noEC struct {
701-
*cfg
702-
}
703-
704-
// GetNodesForObject reads storage policy of the referenced container from the
705-
// underlying container storage, reads network map at the specified epoch from
706-
// the underlying storage, applies the storage policy to it and returns sorted
707-
// lists of selected storage nodes along with the policy rules.
708-
//
709-
// Resulting slices must not be changed.
710-
//
711-
// GetNodesForObject implements [policer.Network].
712-
func (x noEC) GetNodesForObject(addr oid.Address) ([][]netmapsdk.NodeInfo, []uint, error) {
713-
nodeLists, repRules, _, err := x.cfg.GetNodesForObject(addr)
714-
return nodeLists[:len(repRules)], repRules, err
715-
}
716-
717698
// GetNodesForObject reads storage policy of the referenced container from the
718699
// underlying container storage, reads network map at the specified epoch from
719700
// the underlying storage, applies the storage policy to it and returns sorted
720701
// lists of selected storage nodes along with the per-list numbers of primary
721702
// object holders. Resulting slices must not be changed.
722703
//
723-
// GetNodesForObject implements [getsvc.NeoFSNetwork].
704+
// GetNodesForObject implements [getsvc.NeoFSNetwork], [policer.Network].
724705
func (c *cfg) GetNodesForObject(addr oid.Address) ([][]netmapsdk.NodeInfo, []uint, []iec.Rule, error) {
725706
nodeSets, repRules, ecRules, err := c.cfgObject.containerNodes.getNodesForObject(addr)
726707
return nodeSets, repRules, ecRules, err

internal/ec/object_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,54 @@ func TestFormObjectForECPart(t *testing.T) {
172172
require.Equal(t, checksum.NewTillichZemor(tz.Sum(part)), phh)
173173
})
174174
}
175+
176+
func TestDecodePartInfoFromAttributes(t *testing.T) {
177+
t.Run("missing", func(t *testing.T) {
178+
pi, err := iec.DecodePartInfoFromAttributes("", "")
179+
require.NoError(t, err)
180+
require.EqualValues(t, -1, pi.RuleIndex)
181+
})
182+
183+
t.Run("failure", func(t *testing.T) {
184+
for _, tc := range []struct {
185+
name string
186+
ruleIdx string
187+
partIdx string
188+
assertErr func(t *testing.T, err error)
189+
}{
190+
{name: "non-int rule index", ruleIdx: "not_an_int", partIdx: "34", assertErr: func(t *testing.T, err error) {
191+
require.EqualError(t, err, `decode rule index: strconv.ParseUint: parsing "not_an_int": invalid syntax`)
192+
}},
193+
{name: "negative rule index", ruleIdx: "-12", partIdx: "34", assertErr: func(t *testing.T, err error) {
194+
require.EqualError(t, err, `decode rule index: strconv.ParseUint: parsing "-12": invalid syntax`)
195+
}},
196+
{name: "rule index overflow", ruleIdx: "256", partIdx: "34", assertErr: func(t *testing.T, err error) {
197+
require.EqualError(t, err, "rule index out of range")
198+
}},
199+
{name: "non-int part index", ruleIdx: "12", partIdx: "not_an_int", assertErr: func(t *testing.T, err error) {
200+
require.EqualError(t, err, `decode part index: strconv.ParseUint: parsing "not_an_int": invalid syntax`)
201+
}},
202+
{name: "negative part index", ruleIdx: "12", partIdx: "-34", assertErr: func(t *testing.T, err error) {
203+
require.EqualError(t, err, `decode part index: strconv.ParseUint: parsing "-34": invalid syntax`)
204+
}},
205+
{name: "part index overflow", ruleIdx: "12", partIdx: "256", assertErr: func(t *testing.T, err error) {
206+
require.EqualError(t, err, "part index out of range")
207+
}},
208+
{name: "rule index without part index", ruleIdx: "12", partIdx: "", assertErr: func(t *testing.T, err error) {
209+
require.EqualError(t, err, "rule index is set, part index is not")
210+
}},
211+
{name: "part index without rule index", ruleIdx: "", partIdx: "34", assertErr: func(t *testing.T, err error) {
212+
require.EqualError(t, err, "part index is set, rule index is not")
213+
}},
214+
} {
215+
t.Run(tc.name, func(t *testing.T) {
216+
_, err := iec.DecodePartInfoFromAttributes(tc.ruleIdx, tc.partIdx)
217+
tc.assertErr(t, err)
218+
})
219+
}
220+
})
221+
222+
pi, err := iec.DecodePartInfoFromAttributes("12", "34")
223+
require.NoError(t, err)
224+
require.Equal(t, iec.PartInfo{RuleIndex: 12, Index: 34}, pi)
225+
}

internal/ec/objects.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package ec
22

33
import (
4+
"errors"
45
"fmt"
6+
"strconv"
57

68
iobject "github.com/nspcc-dev/neofs-node/internal/object"
79
"github.com/nspcc-dev/neofs-sdk-go/checksum"
@@ -73,3 +75,50 @@ func FormObjectForECPart(signer neofscrypto.Signer, parent object.Object, part [
7375

7476
return obj, nil
7577
}
78+
79+
// DecodePartInfoFromAttributes decodes EC part info from given object
80+
// attributes. It one of attributes is set, the other must be set too. If both
81+
// are missing, DecodePartInfoFromAttributes returns [PartInfo.RuleIndex] = -1
82+
// without error.
83+
func DecodePartInfoFromAttributes(ruleIdxAttr, partIdxAttr string) (PartInfo, error) {
84+
// TODO: sync with object GET server
85+
// TODO: sync with GetPartInfo, it does not check for numeric limits.
86+
if ruleIdxAttr == "" {
87+
if partIdxAttr != "" {
88+
return PartInfo{}, errors.New("part index is set, rule index is not")
89+
}
90+
return PartInfo{RuleIndex: -1}, nil
91+
}
92+
if partIdxAttr == "" {
93+
return PartInfo{}, errors.New("rule index is set, part index is not")
94+
}
95+
96+
ruleIdx, err := decodeUint8StringToInt(ruleIdxAttr)
97+
if err != nil {
98+
if errors.Is(err, strconv.ErrRange) {
99+
return PartInfo{}, errors.New("rule index out of range")
100+
}
101+
return PartInfo{}, fmt.Errorf("decode rule index: %w", err)
102+
}
103+
partIdx, err := decodeUint8StringToInt(partIdxAttr)
104+
if err != nil {
105+
if errors.Is(err, strconv.ErrRange) {
106+
return PartInfo{}, errors.New("part index out of range")
107+
}
108+
return PartInfo{}, fmt.Errorf("decode part index: %w", err)
109+
}
110+
111+
return PartInfo{
112+
RuleIndex: ruleIdx,
113+
Index: partIdx,
114+
}, nil
115+
}
116+
117+
// returns [strconv.ErrRange] if value >= 256.
118+
func decodeUint8StringToInt(s string) (int, error) {
119+
n, err := strconv.ParseUint(s, 10, 8)
120+
if err != nil {
121+
return 0, err
122+
}
123+
return int(n), nil
124+
}

internal/slices/index.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,12 @@ func Indexes(n int) []int {
1515
}
1616
return s
1717
}
18+
19+
// CollectIndex returns new slice of shallows copies of s elements with given indexes.
20+
func CollectIndex[E any, S []E](s S, idxs ...int) S {
21+
newS := make(S, len(idxs))
22+
for i, idx := range idxs {
23+
newS[i] = s[idx]
24+
}
25+
return newS
26+
}

internal/slices/index_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package slices_test
22

33
import (
4+
"slices"
45
"testing"
56

67
islices "github.com/nspcc-dev/neofs-node/internal/slices"
@@ -22,3 +23,23 @@ func TestIndexCombos(t *testing.T) {
2223
{2, 3},
2324
})
2425
}
26+
27+
func TestCollectIndex(t *testing.T) {
28+
t.Run("nil", func(t *testing.T) {
29+
require.Empty(t, islices.CollectIndex([]any(nil)))
30+
})
31+
t.Run("empty", func(t *testing.T) {
32+
require.Empty(t, islices.CollectIndex([]any{}))
33+
})
34+
35+
s := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
36+
sc := slices.Clone(s)
37+
38+
newS := islices.CollectIndex(s, 9, 7, 5, 3, 1)
39+
require.Equal(t, []string{"9", "7", "5", "3", "1"}, newS)
40+
require.Len(t, newS, 5)
41+
require.EqualValues(t, 5, cap(newS))
42+
43+
newS[0] += "_"
44+
require.Equal(t, sc, s)
45+
}

internal/slices/slices.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,17 @@ func RepeatElement[E any, S []E](n int, e E) S {
5656
}
5757
return s
5858
}
59+
60+
// MaxLen returns max length in s. Returns 0 if s is empty.
61+
func MaxLen(s []string) int {
62+
if len(s) == 0 {
63+
return 0
64+
}
65+
mx := len(s[0])
66+
for i := 1; i < len(s); i++ {
67+
if len(s[i]) > mx {
68+
mx = len(s[i])
69+
}
70+
}
71+
return mx
72+
}

internal/slices/slices_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package slices_test
22

33
import (
44
"errors"
5+
"math/rand/v2"
56
"slices"
67
"strconv"
78
"testing"
89

910
islices "github.com/nspcc-dev/neofs-node/internal/slices"
11+
"github.com/nspcc-dev/neofs-node/internal/testutil"
1012
"github.com/stretchr/testify/require"
1113
)
1214

@@ -93,3 +95,28 @@ func TestRepeatElements(t *testing.T) {
9395
})
9496
}
9597
}
98+
99+
func TestMaxLen(t *testing.T) {
100+
t.Run("nil", func(t *testing.T) {
101+
require.Zero(t, islices.MaxLen(nil))
102+
})
103+
t.Run("empty", func(t *testing.T) {
104+
require.Zero(t, islices.MaxLen([]string{}))
105+
})
106+
107+
s := make([]string, 100)
108+
for i := range s {
109+
s[i] = string(testutil.RandByteSlice(i))
110+
}
111+
require.EqualValues(t, 99, islices.MaxLen(s))
112+
113+
slices.Reverse(s)
114+
require.EqualValues(t, 99, islices.MaxLen(s))
115+
116+
rand.Shuffle(len(s), func(i, j int) { s[i], s[j] = s[j], s[i] })
117+
require.EqualValues(t, 99, islices.MaxLen(s))
118+
119+
e := s[50]
120+
s = islices.RepeatElement(len(s), e)
121+
require.EqualValues(t, len(e), islices.MaxLen(s))
122+
}

internal/testutil/neofs.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ import (
77
)
88

99
// Nodes returns n [netmap.NodeInfo] elements with unique public keys.
10+
//
11+
// Each node has two network addresses with localhost IP. Ports are sequential
12+
// starting from 10000.
1013
func Nodes(n int) []netmap.NodeInfo {
14+
const portsStart = 10_000
1115
nodes := make([]netmap.NodeInfo, n)
1216
for i := range nodes {
1317
nodes[i].SetPublicKey([]byte("public_key_" + strconv.Itoa(i)))
18+
nodes[i].SetNetworkEndpoints(
19+
"localhost:"+strconv.Itoa(portsStart+2*i),
20+
"localhost:"+strconv.Itoa(portsStart+2*i+1),
21+
)
1422
}
1523
return nodes
1624
}

0 commit comments

Comments
 (0)