[#1442] pilorama: Generate timestamp based on node position in the container

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
support/v0.30
Evgenii Stratonikov 2022-05-27 15:55:02 +03:00 committed by fyrchik
parent 3caa982283
commit 4437cd7113
13 changed files with 333 additions and 169 deletions

View File

@ -9,14 +9,14 @@ import (
var _ pilorama.Forest = (*StorageEngine)(nil) var _ pilorama.Forest = (*StorageEngine)(nil)
// TreeMove implements the pilorama.Forest interface. // TreeMove implements the pilorama.Forest interface.
func (e *StorageEngine) TreeMove(cid cidSDK.ID, treeID string, m *pilorama.Move) (*pilorama.LogMove, error) { func (e *StorageEngine) TreeMove(d pilorama.CIDDescriptor, treeID string, m *pilorama.Move) (*pilorama.LogMove, error) {
var err error var err error
var lm *pilorama.LogMove var lm *pilorama.LogMove
for _, sh := range e.sortShardsByWeight(cid) { for _, sh := range e.sortShardsByWeight(d.CID) {
lm, err = sh.TreeMove(cid, treeID, m) lm, err = sh.TreeMove(d, treeID, m)
if err != nil { if err != nil {
e.log.Debug("can't put node in a tree", e.log.Debug("can't put node in a tree",
zap.Stringer("cid", cid), zap.Stringer("cid", d.CID),
zap.String("tree", treeID), zap.String("tree", treeID),
zap.String("err", err.Error())) zap.String("err", err.Error()))
continue continue
@ -27,14 +27,14 @@ func (e *StorageEngine) TreeMove(cid cidSDK.ID, treeID string, m *pilorama.Move)
} }
// TreeAddByPath implements the pilorama.Forest interface. // TreeAddByPath implements the pilorama.Forest interface.
func (e *StorageEngine) TreeAddByPath(cid cidSDK.ID, treeID string, attr string, path []string, m []pilorama.KeyValue) ([]pilorama.LogMove, error) { func (e *StorageEngine) TreeAddByPath(d pilorama.CIDDescriptor, treeID string, attr string, path []string, m []pilorama.KeyValue) ([]pilorama.LogMove, error) {
var err error var err error
var lm []pilorama.LogMove var lm []pilorama.LogMove
for _, sh := range e.sortShardsByWeight(cid) { for _, sh := range e.sortShardsByWeight(d.CID) {
lm, err = sh.TreeAddByPath(cid, treeID, attr, path, m) lm, err = sh.TreeAddByPath(d, treeID, attr, path, m)
if err != nil { if err != nil {
e.log.Debug("can't put node in a tree", e.log.Debug("can't put node in a tree",
zap.Stringer("cid", cid), zap.Stringer("cid", d.CID),
zap.String("tree", treeID), zap.String("tree", treeID),
zap.String("err", err.Error())) zap.String("err", err.Error()))
continue continue
@ -45,13 +45,13 @@ func (e *StorageEngine) TreeAddByPath(cid cidSDK.ID, treeID string, attr string,
} }
// TreeApply implements the pilorama.Forest interface. // TreeApply implements the pilorama.Forest interface.
func (e *StorageEngine) TreeApply(cid cidSDK.ID, treeID string, m *pilorama.Move) error { func (e *StorageEngine) TreeApply(d pilorama.CIDDescriptor, treeID string, m *pilorama.Move) error {
var err error var err error
for _, sh := range e.sortShardsByWeight(cid) { for _, sh := range e.sortShardsByWeight(d.CID) {
err = sh.TreeApply(cid, treeID, m) err = sh.TreeApply(d, treeID, m)
if err != nil { if err != nil {
e.log.Debug("can't put node in a tree", e.log.Debug("can't put node in a tree",
zap.Stringer("cid", cid), zap.Stringer("cid", d.CID),
zap.String("tree", treeID), zap.String("tree", treeID),
zap.String("err", err.Error())) zap.String("err", err.Error()))
continue continue

View File

@ -24,6 +24,7 @@ func BenchmarkTreeVsSearch(b *testing.B) {
func benchmarkTreeVsSearch(b *testing.B, objCount int) { func benchmarkTreeVsSearch(b *testing.B, objCount int) {
e, _, _ := newEngineWithErrorThreshold(b, "", 0) e, _, _ := newEngineWithErrorThreshold(b, "", 0)
cid := cidtest.ID() cid := cidtest.ID()
d := pilorama.CIDDescriptor{CID: cid, Position: 0, Size: 1}
treeID := "someTree" treeID := "someTree"
for i := 0; i < objCount; i++ { for i := 0; i < objCount; i++ {
@ -33,7 +34,7 @@ func benchmarkTreeVsSearch(b *testing.B, objCount int) {
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
_, err = e.TreeAddByPath(cid, treeID, pilorama.AttributeFilename, nil, _, err = e.TreeAddByPath(d, treeID, pilorama.AttributeFilename, nil,
[]pilorama.KeyValue{{pilorama.AttributeFilename, []byte(strconv.Itoa(i))}}) []pilorama.KeyValue{{pilorama.AttributeFilename, []byte(strconv.Itoa(i))}})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)

View File

@ -74,15 +74,19 @@ func (t *boltForest) Open() error {
func (t *boltForest) Close() error { return t.db.Close() } func (t *boltForest) Close() error { return t.db.Close() }
// TreeMove implements the Forest interface. // TreeMove implements the Forest interface.
func (t *boltForest) TreeMove(cid cidSDK.ID, treeID string, m *Move) (*LogMove, error) { func (t *boltForest) TreeMove(d CIDDescriptor, treeID string, m *Move) (*LogMove, error) {
if !d.checkValid() {
return nil, ErrInvalidCIDDescriptor
}
var lm *LogMove var lm *LogMove
return lm, t.db.Batch(func(tx *bbolt.Tx) error { return lm, t.db.Batch(func(tx *bbolt.Tx) error {
bLog, bTree, err := t.getTreeBuckets(tx, cid, treeID) bLog, bTree, err := t.getTreeBuckets(tx, d.CID, treeID)
if err != nil { if err != nil {
return err return err
} }
m.Time = t.getLatestTimestamp(bLog) m.Time = t.getLatestTimestamp(bLog, d.Position, d.Size)
if m.Child == RootID { if m.Child == RootID {
m.Child = t.findSpareID(bTree) m.Child = t.findSpareID(bTree)
} }
@ -92,7 +96,10 @@ func (t *boltForest) TreeMove(cid cidSDK.ID, treeID string, m *Move) (*LogMove,
} }
// TreeAddByPath implements the Forest interface. // TreeAddByPath implements the Forest interface.
func (t *boltForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string, path []string, meta []KeyValue) ([]LogMove, error) { func (t *boltForest) TreeAddByPath(d CIDDescriptor, treeID string, attr string, path []string, meta []KeyValue) ([]LogMove, error) {
if !d.checkValid() {
return nil, ErrInvalidCIDDescriptor
}
if !isAttributeInternal(attr) { if !isAttributeInternal(attr) {
return nil, ErrNotPathAttribute return nil, ErrNotPathAttribute
} }
@ -101,7 +108,7 @@ func (t *boltForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string, pa
var key [17]byte var key [17]byte
err := t.db.Batch(func(tx *bbolt.Tx) error { err := t.db.Batch(func(tx *bbolt.Tx) error {
bLog, bTree, err := t.getTreeBuckets(tx, cid, treeID) bLog, bTree, err := t.getTreeBuckets(tx, d.CID, treeID)
if err != nil { if err != nil {
return err return err
} }
@ -111,12 +118,13 @@ func (t *boltForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string, pa
return err return err
} }
ts := t.getLatestTimestamp(bLog, d.Position, d.Size)
lm = make([]LogMove, len(path)-i+1) lm = make([]LogMove, len(path)-i+1)
for j := i; j < len(path); j++ { for j := i; j < len(path); j++ {
lm[j-i].Move = Move{ lm[j-i].Move = Move{
Parent: node, Parent: node,
Meta: Meta{ Meta: Meta{
Time: t.getLatestTimestamp(bLog), Time: ts,
Items: []KeyValue{{Key: attr, Value: []byte(path[j])}}, Items: []KeyValue{{Key: attr, Value: []byte(path[j])}},
}, },
Child: t.findSpareID(bTree), Child: t.findSpareID(bTree),
@ -127,13 +135,14 @@ func (t *boltForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string, pa
return err return err
} }
ts = nextTimestamp(ts, uint64(d.Position), uint64(d.Size))
node = lm[j-i].Child node = lm[j-i].Child
} }
lm[len(lm)-1].Move = Move{ lm[len(lm)-1].Move = Move{
Parent: node, Parent: node,
Meta: Meta{ Meta: Meta{
Time: t.getLatestTimestamp(bLog), Time: ts,
Items: meta, Items: meta,
}, },
Child: t.findSpareID(bTree), Child: t.findSpareID(bTree),
@ -145,14 +154,15 @@ func (t *boltForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string, pa
// getLatestTimestamp returns timestamp for a new operation which is guaranteed to be bigger than // getLatestTimestamp returns timestamp for a new operation which is guaranteed to be bigger than
// all timestamps corresponding to already stored operations. // all timestamps corresponding to already stored operations.
// FIXME timestamp should be based on a node position in the container. func (t *boltForest) getLatestTimestamp(bLog *bbolt.Bucket, pos, size int) uint64 {
func (t *boltForest) getLatestTimestamp(bLog *bbolt.Bucket) uint64 { var ts uint64
c := bLog.Cursor() c := bLog.Cursor()
key, _ := c.Last() key, _ := c.Last()
if len(key) == 0 { if len(key) != 0 {
return 1 ts = binary.BigEndian.Uint64(key)
} }
return binary.BigEndian.Uint64(key) + 1 return nextTimestamp(ts, uint64(pos), uint64(size))
} }
// findSpareID returns random unused ID. // findSpareID returns random unused ID.
@ -173,9 +183,13 @@ func (t *boltForest) findSpareID(bTree *bbolt.Bucket) uint64 {
} }
// TreeApply implements the Forest interface. // TreeApply implements the Forest interface.
func (t *boltForest) TreeApply(cid cidSDK.ID, treeID string, m *Move) error { func (t *boltForest) TreeApply(d CIDDescriptor, treeID string, m *Move) error {
if !d.checkValid() {
return ErrInvalidCIDDescriptor
}
return t.db.Batch(func(tx *bbolt.Tx) error { return t.db.Batch(func(tx *bbolt.Tx) error {
bLog, bTree, err := t.getTreeBuckets(tx, cid, treeID) bLog, bTree, err := t.getTreeBuckets(tx, d.CID, treeID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -23,15 +23,19 @@ func NewMemoryForest() ForestStorage {
} }
// TreeMove implements the Forest interface. // TreeMove implements the Forest interface.
func (f *memoryForest) TreeMove(cid cidSDK.ID, treeID string, op *Move) (*LogMove, error) { func (f *memoryForest) TreeMove(d CIDDescriptor, treeID string, op *Move) (*LogMove, error) {
fullID := cid.String() + "/" + treeID if !d.checkValid() {
return nil, ErrInvalidCIDDescriptor
}
fullID := d.CID.String() + "/" + treeID
s, ok := f.treeMap[fullID] s, ok := f.treeMap[fullID]
if !ok { if !ok {
s = newState() s = newState()
f.treeMap[fullID] = s f.treeMap[fullID] = s
} }
op.Time = s.timestamp() op.Time = s.timestamp(d.Position, d.Size)
if op.Child == RootID { if op.Child == RootID {
op.Child = s.findSpareID() op.Child = s.findSpareID()
} }
@ -42,12 +46,15 @@ func (f *memoryForest) TreeMove(cid cidSDK.ID, treeID string, op *Move) (*LogMov
} }
// TreeAddByPath implements the Forest interface. // TreeAddByPath implements the Forest interface.
func (f *memoryForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string, path []string, m []KeyValue) ([]LogMove, error) { func (f *memoryForest) TreeAddByPath(d CIDDescriptor, treeID string, attr string, path []string, m []KeyValue) ([]LogMove, error) {
if !d.checkValid() {
return nil, ErrInvalidCIDDescriptor
}
if !isAttributeInternal(attr) { if !isAttributeInternal(attr) {
return nil, ErrNotPathAttribute return nil, ErrNotPathAttribute
} }
fullID := cid.String() + "/" + treeID fullID := d.CID.String() + "/" + treeID
s, ok := f.treeMap[fullID] s, ok := f.treeMap[fullID]
if !ok { if !ok {
s = newState() s = newState()
@ -59,8 +66,10 @@ func (f *memoryForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string,
for j := i; j < len(path); j++ { for j := i; j < len(path); j++ {
lm[j-i] = s.do(&Move{ lm[j-i] = s.do(&Move{
Parent: node, Parent: node,
Meta: Meta{Time: s.timestamp(), Items: []KeyValue{{Key: attr, Value: []byte(path[j])}}}, Meta: Meta{
Child: s.findSpareID(), Time: s.timestamp(d.Position, d.Size),
Items: []KeyValue{{Key: attr, Value: []byte(path[j])}}},
Child: s.findSpareID(),
}) })
node = lm[j-i].Child node = lm[j-i].Child
s.operations = append(s.operations, lm[j-i]) s.operations = append(s.operations, lm[j-i])
@ -70,15 +79,22 @@ func (f *memoryForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string,
copy(mCopy, m) copy(mCopy, m)
lm[len(lm)-1] = s.do(&Move{ lm[len(lm)-1] = s.do(&Move{
Parent: node, Parent: node,
Meta: Meta{Time: s.timestamp(), Items: mCopy}, Meta: Meta{
Child: s.findSpareID(), Time: s.timestamp(d.Position, d.Size),
Items: mCopy,
},
Child: s.findSpareID(),
}) })
return lm, nil return lm, nil
} }
// TreeApply implements the Forest interface. // TreeApply implements the Forest interface.
func (f *memoryForest) TreeApply(cid cidSDK.ID, treeID string, op *Move) error { func (f *memoryForest) TreeApply(d CIDDescriptor, treeID string, op *Move) error {
fullID := cid.String() + "/" + treeID if !d.checkValid() {
return ErrInvalidCIDDescriptor
}
fullID := d.CID.String() + "/" + treeID
s, ok := f.treeMap[fullID] s, ok := f.treeMap[fullID]
if !ok { if !ok {
s = newState() s = newState()

View File

@ -59,18 +59,27 @@ func TestForest_TreeMove(t *testing.T) {
func testForestTreeMove(t *testing.T, s Forest) { func testForestTreeMove(t *testing.T, s Forest) {
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
meta := []KeyValue{ meta := []KeyValue{
{Key: AttributeVersion, Value: []byte("XXX")}, {Key: AttributeVersion, Value: []byte("XXX")},
{Key: AttributeFilename, Value: []byte("file.txt")}} {Key: AttributeFilename, Value: []byte("file.txt")}}
lm, err := s.TreeAddByPath(cid, treeID, AttributeFilename, []string{"path", "to"}, meta) lm, err := s.TreeAddByPath(d, treeID, AttributeFilename, []string{"path", "to"}, meta)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(lm)) require.Equal(t, 3, len(lm))
nodeID := lm[2].Child nodeID := lm[2].Child
t.Run("invalid descriptor", func(t *testing.T) {
_, err = s.TreeMove(CIDDescriptor{cid, 0, 0}, treeID, &Move{
Parent: lm[1].Child,
Meta: Meta{Items: append(meta, KeyValue{Key: "NewKey", Value: []byte("NewValue")})},
Child: nodeID,
})
require.ErrorIs(t, err, ErrInvalidCIDDescriptor)
})
t.Run("same parent, update meta", func(t *testing.T) { t.Run("same parent, update meta", func(t *testing.T) {
_, err = s.TreeMove(cid, treeID, &Move{ _, err = s.TreeMove(d, treeID, &Move{
Parent: lm[1].Child, Parent: lm[1].Child,
Meta: Meta{Items: append(meta, KeyValue{Key: "NewKey", Value: []byte("NewValue")})}, Meta: Meta{Items: append(meta, KeyValue{Key: "NewKey", Value: []byte("NewValue")})},
Child: nodeID, Child: nodeID,
@ -82,7 +91,7 @@ func testForestTreeMove(t *testing.T, s Forest) {
require.ElementsMatch(t, []Node{nodeID}, nodes) require.ElementsMatch(t, []Node{nodeID}, nodes)
}) })
t.Run("different parent", func(t *testing.T) { t.Run("different parent", func(t *testing.T) {
_, err = s.TreeMove(cid, treeID, &Move{ _, err = s.TreeMove(d, treeID, &Move{
Parent: RootID, Parent: RootID,
Meta: Meta{Items: append(meta, KeyValue{Key: "NewKey", Value: []byte("NewValue")})}, Meta: Meta{Items: append(meta, KeyValue{Key: "NewKey", Value: []byte("NewValue")})},
Child: nodeID, Child: nodeID,
@ -109,10 +118,11 @@ func TestMemoryForest_TreeGetChildren(t *testing.T) {
func testForestTreeGetChildren(t *testing.T, s Forest) { func testForestTreeGetChildren(t *testing.T, s Forest) {
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
treeAdd := func(t *testing.T, child, parent Node) { treeAdd := func(t *testing.T, child, parent Node) {
_, err := s.TreeMove(cid, treeID, &Move{ _, err := s.TreeMove(d, treeID, &Move{
Parent: parent, Parent: parent,
Child: child, Child: child,
}) })
@ -165,16 +175,24 @@ func TestForest_TreeAdd(t *testing.T) {
func testForestTreeAdd(t *testing.T, s Forest) { func testForestTreeAdd(t *testing.T, s Forest) {
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
meta := []KeyValue{ meta := []KeyValue{
{Key: AttributeVersion, Value: []byte("XXX")}, {Key: AttributeVersion, Value: []byte("XXX")},
{Key: AttributeFilename, Value: []byte("file.txt")}} {Key: AttributeFilename, Value: []byte("file.txt")}}
lm, err := s.TreeMove(cid, treeID, &Move{ m := &Move{
Parent: RootID, Parent: RootID,
Child: RootID, Child: RootID,
Meta: Meta{Items: meta}, Meta: Meta{Items: meta},
}
t.Run("invalid descriptor", func(t *testing.T) {
_, err := s.TreeMove(CIDDescriptor{cid, 0, 0}, treeID, m)
require.ErrorIs(t, err, ErrInvalidCIDDescriptor)
}) })
lm, err := s.TreeMove(d, treeID, m)
require.NoError(t, err) require.NoError(t, err)
testMeta(t, s, cid, treeID, lm.Child, lm.Parent, Meta{Time: lm.Time, Items: meta}) testMeta(t, s, cid, treeID, lm.Child, lm.Parent, Meta{Time: lm.Time, Items: meta})
@ -202,18 +220,23 @@ func TestForest_TreeAddByPath(t *testing.T) {
func testForestTreeAddByPath(t *testing.T, s Forest) { func testForestTreeAddByPath(t *testing.T, s Forest) {
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
meta := []KeyValue{ meta := []KeyValue{
{Key: AttributeVersion, Value: []byte("XXX")}, {Key: AttributeVersion, Value: []byte("XXX")},
{Key: AttributeFilename, Value: []byte("file.txt")}} {Key: AttributeFilename, Value: []byte("file.txt")}}
t.Run("invalid descriptor", func(t *testing.T) {
_, err := s.TreeAddByPath(CIDDescriptor{cid, 0, 0}, treeID, AttributeFilename, []string{"yyy"}, meta)
require.ErrorIs(t, err, ErrInvalidCIDDescriptor)
})
t.Run("invalid attribute", func(t *testing.T) { t.Run("invalid attribute", func(t *testing.T) {
_, err := s.TreeAddByPath(cid, treeID, AttributeVersion, []string{"yyy"}, meta) _, err := s.TreeAddByPath(d, treeID, AttributeVersion, []string{"yyy"}, meta)
require.ErrorIs(t, err, ErrNotPathAttribute) require.ErrorIs(t, err, ErrNotPathAttribute)
}) })
lm, err := s.TreeAddByPath(cid, treeID, AttributeFilename, []string{"path", "to"}, meta) lm, err := s.TreeAddByPath(d, treeID, AttributeFilename, []string{"path", "to"}, meta)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(lm)) require.Equal(t, 3, len(lm))
testMeta(t, s, cid, treeID, lm[0].Child, lm[0].Parent, Meta{Time: lm[0].Time, Items: []KeyValue{{AttributeFilename, []byte("path")}}}) testMeta(t, s, cid, treeID, lm[0].Child, lm[0].Parent, Meta{Time: lm[0].Time, Items: []KeyValue{{AttributeFilename, []byte("path")}}})
@ -223,7 +246,7 @@ func testForestTreeAddByPath(t *testing.T, s Forest) {
testMeta(t, s, cid, treeID, firstID, lm[2].Parent, Meta{Time: lm[2].Time, Items: meta}) testMeta(t, s, cid, treeID, firstID, lm[2].Parent, Meta{Time: lm[2].Time, Items: meta})
meta[0].Value = []byte("YYY") meta[0].Value = []byte("YYY")
lm, err = s.TreeAddByPath(cid, treeID, AttributeFilename, []string{"path", "to"}, meta) lm, err = s.TreeAddByPath(d, treeID, AttributeFilename, []string{"path", "to"}, meta)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(lm)) require.Equal(t, 1, len(lm))
@ -244,7 +267,7 @@ func testForestTreeAddByPath(t *testing.T, s Forest) {
meta[0].Value = []byte("ZZZ") meta[0].Value = []byte("ZZZ")
meta[1].Value = []byte("cat.jpg") meta[1].Value = []byte("cat.jpg")
lm, err = s.TreeAddByPath(cid, treeID, AttributeFilename, []string{"path", "dir"}, meta) lm, err = s.TreeAddByPath(d, treeID, AttributeFilename, []string{"path", "dir"}, meta)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 2, len(lm)) require.Equal(t, 2, len(lm))
testMeta(t, s, cid, treeID, lm[0].Child, lm[0].Parent, Meta{Time: lm[0].Time, Items: []KeyValue{{AttributeFilename, []byte("dir")}}}) testMeta(t, s, cid, treeID, lm[0].Child, lm[0].Parent, Meta{Time: lm[0].Time, Items: []KeyValue{{AttributeFilename, []byte("dir")}}})
@ -253,7 +276,7 @@ func testForestTreeAddByPath(t *testing.T, s Forest) {
t.Run("create internal nodes", func(t *testing.T) { t.Run("create internal nodes", func(t *testing.T) {
meta[0].Value = []byte("SomeValue") meta[0].Value = []byte("SomeValue")
meta[1].Value = []byte("another") meta[1].Value = []byte("another")
lm, err = s.TreeAddByPath(cid, treeID, AttributeFilename, []string{"path"}, meta) lm, err = s.TreeAddByPath(d, treeID, AttributeFilename, []string{"path"}, meta)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(lm)) require.Equal(t, 1, len(lm))
@ -261,7 +284,7 @@ func testForestTreeAddByPath(t *testing.T, s Forest) {
meta[0].Value = []byte("Leaf") meta[0].Value = []byte("Leaf")
meta[1].Value = []byte("file.txt") meta[1].Value = []byte("file.txt")
lm, err = s.TreeAddByPath(cid, treeID, AttributeFilename, []string{"path", "another"}, meta) lm, err = s.TreeAddByPath(d, treeID, AttributeFilename, []string{"path", "another"}, meta)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 2, len(lm)) require.Equal(t, 2, len(lm))
@ -299,10 +322,21 @@ func TestForest_Apply(t *testing.T) {
func testForestTreeApply(t *testing.T, constructor func(t testing.TB) Forest) { func testForestTreeApply(t *testing.T, constructor func(t testing.TB) Forest) {
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
t.Run("invalid descriptor", func(t *testing.T) {
s := constructor(t)
err := s.TreeApply(CIDDescriptor{cid, 0, 0}, treeID, &Move{
Child: 10,
Parent: 0,
Meta: Meta{Time: 1, Items: []KeyValue{{"grand", []byte{1}}}},
})
require.ErrorIs(t, err, ErrInvalidCIDDescriptor)
})
testApply := func(t *testing.T, s Forest, child, parent Node, meta Meta) { testApply := func(t *testing.T, s Forest, child, parent Node, meta Meta) {
require.NoError(t, s.TreeApply(cid, treeID, &Move{ require.NoError(t, s.TreeApply(d, treeID, &Move{
Child: child, Child: child,
Parent: parent, Parent: parent,
Meta: meta, Meta: meta,
@ -341,6 +375,7 @@ func TestForest_GetOpLog(t *testing.T) {
func testForestTreeGetOpLog(t *testing.T, constructor func(t testing.TB) Forest) { func testForestTreeGetOpLog(t *testing.T, constructor func(t testing.TB) Forest) {
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
logs := []Move{ logs := []Move{
{ {
@ -366,7 +401,7 @@ func testForestTreeGetOpLog(t *testing.T, constructor func(t testing.TB) Forest)
}) })
for i := range logs { for i := range logs {
require.NoError(t, s.TreeApply(cid, treeID, &logs[i])) require.NoError(t, s.TreeApply(d, treeID, &logs[i]))
} }
testGetOpLog := func(t *testing.T, height uint64, m Move) { testGetOpLog := func(t *testing.T, height uint64, m Move) {
@ -407,6 +442,7 @@ func testForestTreeApplyRandom(t *testing.T, constructor func(t testing.TB) Fore
) )
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
expected := constructor(t) expected := constructor(t)
@ -444,7 +480,7 @@ func testForestTreeApplyRandom(t *testing.T, constructor func(t testing.TB) Fore
rand.Read(ops[i].Meta.Items[1].Value) rand.Read(ops[i].Meta.Items[1].Value)
} }
for i := range ops { for i := range ops {
require.NoError(t, expected.TreeApply(cid, treeID, &ops[i])) require.NoError(t, expected.TreeApply(d, treeID, &ops[i]))
} }
for i := 0; i < iterCount; i++ { for i := 0; i < iterCount; i++ {
@ -453,7 +489,7 @@ func testForestTreeApplyRandom(t *testing.T, constructor func(t testing.TB) Fore
actual := constructor(t) actual := constructor(t)
for i := range ops { for i := range ops {
require.NoError(t, actual.TreeApply(cid, treeID, &ops[i])) require.NoError(t, actual.TreeApply(d, treeID, &ops[i]))
} }
for i := uint64(0); i < nodeCount; i++ { for i := uint64(0); i < nodeCount; i++ {
expectedMeta, expectedParent, err := expected.TreeGetMeta(cid, treeID, i) expectedMeta, expectedParent, err := expected.TreeGetMeta(cid, treeID, i)
@ -534,6 +570,7 @@ func benchmarkApply(b *testing.B, s Forest, genFunc func(int) []Move) {
ops := genFunc(b.N) ops := genFunc(b.N)
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
ch := make(chan *Move, b.N) ch := make(chan *Move, b.N)
for i := range ops { for i := range ops {
@ -546,7 +583,7 @@ func benchmarkApply(b *testing.B, s Forest, genFunc func(int) []Move) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
op := <-ch op := <-ch
if err := s.TreeApply(cid, treeID, op); err != nil { if err := s.TreeApply(d, treeID, op); err != nil {
b.Fatalf("error in `Apply`: %v", err) b.Fatalf("error in `Apply`: %v", err)
} }
} }
@ -563,6 +600,7 @@ func TestTreeGetByPath(t *testing.T) {
func testTreeGetByPath(t *testing.T, s Forest) { func testTreeGetByPath(t *testing.T, s Forest) {
cid := cidtest.ID() cid := cidtest.ID()
d := CIDDescriptor{cid, 0, 1}
treeID := "version" treeID := "version"
// / // /
@ -572,12 +610,12 @@ func testTreeGetByPath(t *testing.T, s Forest) {
// |- cat1.jpg, Version=XXX (4) // |- cat1.jpg, Version=XXX (4)
// |- cat1.jpg, Version=YYY (5) // |- cat1.jpg, Version=YYY (5)
// |- cat2.jpg, Version=ZZZ (6) // |- cat2.jpg, Version=ZZZ (6)
testMove(t, s, 0, 1, 0, cid, treeID, "a", "") testMove(t, s, 0, 1, 0, d, treeID, "a", "")
testMove(t, s, 1, 2, 0, cid, treeID, "b", "") testMove(t, s, 1, 2, 0, d, treeID, "b", "")
testMove(t, s, 2, 3, 1, cid, treeID, "cat1.jpg", "TTT") testMove(t, s, 2, 3, 1, d, treeID, "cat1.jpg", "TTT")
testMove(t, s, 3, 4, 2, cid, treeID, "cat1.jpg", "XXX") testMove(t, s, 3, 4, 2, d, treeID, "cat1.jpg", "XXX")
testMove(t, s, 4, 5, 2, cid, treeID, "cat1.jpg", "YYY") testMove(t, s, 4, 5, 2, d, treeID, "cat1.jpg", "YYY")
testMove(t, s, 5, 6, 2, cid, treeID, "cat2.jpg", "ZZZ") testMove(t, s, 5, 6, 2, d, treeID, "cat2.jpg", "ZZZ")
if mf, ok := s.(*memoryForest); ok { if mf, ok := s.(*memoryForest); ok {
single := mf.treeMap[cid.String()+"/"+treeID] single := mf.treeMap[cid.String()+"/"+treeID]
@ -614,14 +652,14 @@ func testTreeGetByPath(t *testing.T, s Forest) {
}) })
} }
func testMove(t *testing.T, s Forest, ts int, node, parent Node, cid cidSDK.ID, treeID, filename, version string) { func testMove(t *testing.T, s Forest, ts int, node, parent Node, d CIDDescriptor, treeID, filename, version string) {
items := make([]KeyValue, 1, 2) items := make([]KeyValue, 1, 2)
items[0] = KeyValue{AttributeFilename, []byte(filename)} items[0] = KeyValue{AttributeFilename, []byte(filename)}
if version != "" { if version != "" {
items = append(items, KeyValue{AttributeVersion, []byte(version)}) items = append(items, KeyValue{AttributeVersion, []byte(version)})
} }
require.NoError(t, s.TreeApply(cid, treeID, &Move{ require.NoError(t, s.TreeApply(d, treeID, &Move{
Parent: parent, Parent: parent,
Child: node, Child: node,
Meta: Meta{ Meta: Meta{

View File

@ -133,11 +133,11 @@ func (s *state) removeChild(child, parent Node) {
} }
} }
func (s *state) timestamp() Timestamp { func (s *state) timestamp(pos, size int) Timestamp {
if len(s.operations) == 0 { if len(s.operations) == 0 {
return 0 return nextTimestamp(0, uint64(pos), uint64(size))
} }
return s.operations[len(s.operations)-1].Time + 1 return nextTimestamp(s.operations[len(s.operations)-1].Time, uint64(pos), uint64(size))
} }
func (s *state) findSpareID() Node { func (s *state) findSpareID() Node {

View File

@ -1,19 +1,23 @@
package pilorama package pilorama
import cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id" import (
"errors"
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
)
// Forest represents CRDT tree. // Forest represents CRDT tree.
type Forest interface { type Forest interface {
// TreeMove moves node in the tree. // TreeMove moves node in the tree.
// If the parent of the move operation is TrashID, the node is removed. // If the parent of the move operation is TrashID, the node is removed.
// If the child of the move operation is RootID, new ID is generated and added to a tree. // If the child of the move operation is RootID, new ID is generated and added to a tree.
TreeMove(cid cidSDK.ID, treeID string, m *Move) (*LogMove, error) TreeMove(d CIDDescriptor, treeID string, m *Move) (*LogMove, error)
// TreeAddByPath adds new node in the tree using provided path. // TreeAddByPath adds new node in the tree using provided path.
// The path is constructed by descending from the root using the values of the attr in meta. // The path is constructed by descending from the root using the values of the attr in meta.
// Internal nodes in path should have exactly one attribute, otherwise a new node is created. // Internal nodes in path should have exactly one attribute, otherwise a new node is created.
TreeAddByPath(cid cidSDK.ID, treeID string, attr string, path []string, meta []KeyValue) ([]LogMove, error) TreeAddByPath(d CIDDescriptor, treeID string, attr string, path []string, meta []KeyValue) ([]LogMove, error)
// TreeApply applies replicated operation from another node. // TreeApply applies replicated operation from another node.
TreeApply(cid cidSDK.ID, treeID string, m *Move) error TreeApply(d CIDDescriptor, treeID string, m *Move) error
// TreeGetByPath returns all nodes corresponding to the path. // TreeGetByPath returns all nodes corresponding to the path.
// The path is constructed by descending from the root using the values of the // The path is constructed by descending from the root using the values of the
// AttributeFilename in meta. // AttributeFilename in meta.
@ -42,3 +46,19 @@ const (
AttributeFilename = "FileName" AttributeFilename = "FileName"
AttributeVersion = "Version" AttributeVersion = "Version"
) )
// CIDDescriptor contains container ID and information about the node position
// in the list of container nodes.
type CIDDescriptor struct {
CID cidSDK.ID
Position int
Size int
}
// ErrInvalidCIDDescriptor is returned when info about tne node position
// in the container is invalid.
var ErrInvalidCIDDescriptor = errors.New("cid descriptor is invalid")
func (d CIDDescriptor) checkValid() bool {
return 0 <= d.Position && d.Position < d.Size
}

View File

@ -0,0 +1,11 @@
package pilorama
// nextTimestamp accepts the latest local timestamp, node position in a container and container size.
// Returns the next timestamp which can be generated by this node.
func nextTimestamp(ts Timestamp, pos, size uint64) Timestamp {
base := ts/size*size + pos
if ts < base {
return base
}
return base + size
}

View File

@ -0,0 +1,38 @@
package pilorama
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestNextTimestamp(t *testing.T) {
testCases := []struct {
latest Timestamp
pos, size uint64
expected Timestamp
}{
{0, 0, 1, 1},
{2, 0, 1, 3},
{0, 0, 2, 2},
{0, 1, 2, 1},
{10, 0, 4, 12},
{11, 0, 4, 12},
{12, 0, 4, 16},
{10, 1, 4, 13},
{11, 1, 4, 13},
{12, 1, 4, 13},
{10, 2, 4, 14},
{11, 2, 4, 14},
{12, 2, 4, 14},
{10, 3, 4, 11},
{11, 3, 4, 15},
{12, 3, 4, 15},
}
for _, tc := range testCases {
actual := nextTimestamp(tc.latest, tc.pos, tc.size)
require.Equal(t, tc.expected, actual,
"latest %d, pos %d, size %d", tc.latest, tc.pos, tc.size)
}
}

View File

@ -8,18 +8,18 @@ import (
var _ pilorama.Forest = (*Shard)(nil) var _ pilorama.Forest = (*Shard)(nil)
// TreeMove implements the pilorama.Forest interface. // TreeMove implements the pilorama.Forest interface.
func (s *Shard) TreeMove(cid cidSDK.ID, treeID string, m *pilorama.Move) (*pilorama.LogMove, error) { func (s *Shard) TreeMove(d pilorama.CIDDescriptor, treeID string, m *pilorama.Move) (*pilorama.LogMove, error) {
return s.pilorama.TreeMove(cid, treeID, m) return s.pilorama.TreeMove(d, treeID, m)
} }
// TreeAddByPath implements the pilorama.Forest interface. // TreeAddByPath implements the pilorama.Forest interface.
func (s *Shard) TreeAddByPath(cid cidSDK.ID, treeID string, attr string, path []string, meta []pilorama.KeyValue) ([]pilorama.LogMove, error) { func (s *Shard) TreeAddByPath(d pilorama.CIDDescriptor, treeID string, attr string, path []string, meta []pilorama.KeyValue) ([]pilorama.LogMove, error) {
return s.pilorama.TreeAddByPath(cid, treeID, attr, path, meta) return s.pilorama.TreeAddByPath(d, treeID, attr, path, meta)
} }
// TreeApply implements the pilorama.Forest interface. // TreeApply implements the pilorama.Forest interface.
func (s *Shard) TreeApply(cid cidSDK.ID, treeID string, m *pilorama.Move) error { func (s *Shard) TreeApply(d pilorama.CIDDescriptor, treeID string, m *pilorama.Move) error {
return s.pilorama.TreeApply(cid, treeID, m) return s.pilorama.TreeApply(d, treeID, m)
} }
// TreeGetByPath implements the pilorama.Forest interface. // TreeGetByPath implements the pilorama.Forest interface.

View File

@ -4,25 +4,18 @@ import (
"bytes" "bytes"
"context" "context"
"errors" "errors"
"fmt"
"github.com/nspcc-dev/neofs-node/pkg/network" "github.com/nspcc-dev/neofs-node/pkg/network"
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id" netmapSDK "github.com/nspcc-dev/neofs-sdk-go/netmap"
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
var errNoSuitableNode = errors.New("no node was found to execute the request") var errNoSuitableNode = errors.New("no node was found to execute the request")
// forEachNode executes callback for each node in the container. // forEachNode executes callback for each node in the container until true is returned.
// If the node belongs to a container, nil error is returned. // Returns errNoSuitableNode if there was no successful attempt to dial any node.
// Otherwise, f is executed for each node, stopping if true is returned. func (s *Service) forEachNode(ctx context.Context, cntNodes []netmapSDK.NodeInfo, f func(c TreeServiceClient) bool) error {
func (s *Service) forEachNode(ctx context.Context, cid cidSDK.ID, f func(c TreeServiceClient) bool) error {
cntNodes, err := s.getContainerNodes(cid)
if err != nil {
return fmt.Errorf("can't get container nodes for %s: %w", cid, err)
}
for _, n := range cntNodes { for _, n := range cntNodes {
if bytes.Equal(n.PublicKey(), s.rawPub) { if bytes.Equal(n.PublicKey(), s.rawPub) {
return nil return nil

View File

@ -9,6 +9,7 @@ import (
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/pilorama" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/pilorama"
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id" cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/eacl"
netmapSDK "github.com/nspcc-dev/neofs-sdk-go/netmap"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -66,19 +67,22 @@ func (s *Service) Add(ctx context.Context, req *AddRequest) (*AddResponse, error
return nil, err return nil, err
} }
var resp *AddResponse ns, pos, size, err := s.getContainerInfo(cid, s.rawPub)
var outErr error if err == errNotInContainer {
err = s.forEachNode(ctx, cid, func(c TreeServiceClient) bool { var resp *AddResponse
resp, outErr = c.Add(ctx, req) var outErr error
return true err = s.forEachNode(ctx, ns, func(c TreeServiceClient) bool {
}) resp, outErr = c.Add(ctx, req)
if err != nil { return true
return nil, err })
} else if resp != nil || outErr != nil { if err != nil {
return nil, err
}
return resp, outErr return resp, outErr
} }
log, err := s.forest.TreeMove(cid, b.GetTreeId(), &pilorama.Move{ d := pilorama.CIDDescriptor{CID: cid, Position: pos, Size: size}
log, err := s.forest.TreeMove(d, b.GetTreeId(), &pilorama.Move{
Parent: b.GetParentId(), Parent: b.GetParentId(),
Child: pilorama.RootID, Child: pilorama.RootID,
Meta: pilorama.Meta{Items: protoToMeta(b.GetMeta())}, Meta: pilorama.Meta{Items: protoToMeta(b.GetMeta())},
@ -108,15 +112,17 @@ func (s *Service) AddByPath(ctx context.Context, req *AddByPathRequest) (*AddByP
return nil, err return nil, err
} }
var resp *AddByPathResponse ns, pos, size, err := s.getContainerInfo(cid, s.rawPub)
var outErr error if err == errNotInContainer {
err = s.forEachNode(ctx, cid, func(c TreeServiceClient) bool { var resp *AddByPathResponse
resp, outErr = c.AddByPath(ctx, req) var outErr error
return true err = s.forEachNode(ctx, ns, func(c TreeServiceClient) bool {
}) resp, outErr = c.AddByPath(ctx, req)
if err != nil { return true
return nil, err })
} else if resp != nil || outErr != nil { if err != nil {
return nil, err
}
return resp, outErr return resp, outErr
} }
@ -127,7 +133,8 @@ func (s *Service) AddByPath(ctx context.Context, req *AddByPathRequest) (*AddByP
attr = pilorama.AttributeFilename attr = pilorama.AttributeFilename
} }
logs, err := s.forest.TreeAddByPath(cid, b.GetTreeId(), attr, b.GetPath(), meta) d := pilorama.CIDDescriptor{CID: cid, Position: pos, Size: size}
logs, err := s.forest.TreeAddByPath(d, b.GetTreeId(), attr, b.GetPath(), meta)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -162,15 +169,17 @@ func (s *Service) Remove(ctx context.Context, req *RemoveRequest) (*RemoveRespon
return nil, err return nil, err
} }
var resp *RemoveResponse ns, pos, size, err := s.getContainerInfo(cid, s.rawPub)
var outErr error if err == errNotInContainer {
err = s.forEachNode(ctx, cid, func(c TreeServiceClient) bool { var resp *RemoveResponse
resp, outErr = c.Remove(ctx, req) var outErr error
return true err = s.forEachNode(ctx, ns, func(c TreeServiceClient) bool {
}) resp, outErr = c.Remove(ctx, req)
if err != nil { return true
return nil, err })
} else if resp != nil || outErr != nil { if err != nil {
return nil, err
}
return resp, outErr return resp, outErr
} }
@ -178,7 +187,8 @@ func (s *Service) Remove(ctx context.Context, req *RemoveRequest) (*RemoveRespon
return nil, fmt.Errorf("node with ID %d is root and can't be removed", b.GetNodeId()) return nil, fmt.Errorf("node with ID %d is root and can't be removed", b.GetNodeId())
} }
log, err := s.forest.TreeMove(cid, b.GetTreeId(), &pilorama.Move{ d := pilorama.CIDDescriptor{CID: cid, Position: pos, Size: size}
log, err := s.forest.TreeMove(d, b.GetTreeId(), &pilorama.Move{
Parent: pilorama.TrashID, Parent: pilorama.TrashID,
Child: b.GetNodeId(), Child: b.GetNodeId(),
}) })
@ -205,15 +215,17 @@ func (s *Service) Move(ctx context.Context, req *MoveRequest) (*MoveResponse, er
return nil, err return nil, err
} }
var resp *MoveResponse ns, pos, size, err := s.getContainerInfo(cid, s.rawPub)
var outErr error if err == errNotInContainer {
err = s.forEachNode(ctx, cid, func(c TreeServiceClient) bool { var resp *MoveResponse
resp, outErr = c.Move(ctx, req) var outErr error
return true err = s.forEachNode(ctx, ns, func(c TreeServiceClient) bool {
}) resp, outErr = c.Move(ctx, req)
if err != nil { return true
return nil, err })
} else if resp != nil || outErr != nil { if err != nil {
return nil, err
}
return resp, outErr return resp, outErr
} }
@ -221,7 +233,8 @@ func (s *Service) Move(ctx context.Context, req *MoveRequest) (*MoveResponse, er
return nil, fmt.Errorf("node with ID %d is root and can't be moved", b.GetNodeId()) return nil, fmt.Errorf("node with ID %d is root and can't be moved", b.GetNodeId())
} }
log, err := s.forest.TreeMove(cid, b.GetTreeId(), &pilorama.Move{ d := pilorama.CIDDescriptor{CID: cid, Position: pos, Size: size}
log, err := s.forest.TreeMove(d, b.GetTreeId(), &pilorama.Move{
Parent: b.GetParentId(), Parent: b.GetParentId(),
Child: b.GetNodeId(), Child: b.GetNodeId(),
Meta: pilorama.Meta{Items: protoToMeta(b.GetMeta())}, Meta: pilorama.Meta{Items: protoToMeta(b.GetMeta())},
@ -247,15 +260,17 @@ func (s *Service) GetNodeByPath(ctx context.Context, req *GetNodeByPathRequest)
return nil, err return nil, err
} }
var resp *GetNodeByPathResponse ns, _, _, err := s.getContainerInfo(cid, s.rawPub)
var outErr error if err == errNotInContainer {
err = s.forEachNode(ctx, cid, func(c TreeServiceClient) bool { var resp *GetNodeByPathResponse
resp, outErr = c.GetNodeByPath(ctx, req) var outErr error
return true err = s.forEachNode(ctx, ns, func(c TreeServiceClient) bool {
}) resp, outErr = c.GetNodeByPath(ctx, req)
if err != nil { return true
return nil, err })
} else if resp != nil || outErr != nil { if err != nil {
return nil, err
}
return resp, outErr return resp, outErr
} }
@ -325,22 +340,25 @@ func (s *Service) GetSubTree(req *GetSubTreeRequest, srv TreeService_GetSubTreeS
return err return err
} }
var cli TreeService_GetSubTreeClient ns, _, _, err := s.getContainerInfo(cid, s.rawPub)
var outErr error if err == errNotInContainer {
err = s.forEachNode(srv.Context(), cid, func(c TreeServiceClient) bool { var cli TreeService_GetSubTreeClient
cli, outErr = c.GetSubTree(srv.Context(), req) var outErr error
return true err = s.forEachNode(srv.Context(), ns, func(c TreeServiceClient) bool {
}) cli, outErr = c.GetSubTree(srv.Context(), req)
if err != nil { return true
return err })
} else if outErr != nil { if err != nil {
return outErr return err
} else if cli != nil { } else if outErr != nil {
return outErr
}
for resp, err := cli.Recv(); err == nil; resp, err = cli.Recv() { for resp, err := cli.Recv(); err == nil; resp, err = cli.Recv() {
if err := srv.Send(resp); err != nil { if err := srv.Send(resp); err != nil {
return err return err
} }
} }
return nil
} }
queue := []nodeDepthPair{{[]uint64{b.GetRootId()}, 0}} queue := []nodeDepthPair{{[]uint64{b.GetRootId()}, 0}}
@ -391,18 +409,10 @@ func (s *Service) Apply(_ context.Context, req *ApplyRequest) (*ApplyResponse, e
return nil, err return nil, err
} }
found := false
key := req.GetSignature().GetKey() key := req.GetSignature().GetKey()
nodes, _ := s.getContainerNodes(cid)
loop: _, pos, size, err := s.getContainerInfo(cid, key)
for _, n := range nodes { if err == errNotInContainer {
if bytes.Equal(key, n.PublicKey()) {
found = true
break loop
}
}
if !found {
return nil, errors.New("`Apply` request must be signed by a container node") return nil, errors.New("`Apply` request must be signed by a container node")
} }
@ -413,7 +423,8 @@ loop:
return nil, fmt.Errorf("can't parse meta-information: %w", err) return nil, fmt.Errorf("can't parse meta-information: %w", err)
} }
return nil, s.forest.TreeApply(cid, req.GetBody().GetTreeId(), &pilorama.Move{ d := pilorama.CIDDescriptor{CID: cid, Position: pos, Size: size}
return nil, s.forest.TreeApply(d, req.GetBody().GetTreeId(), &pilorama.Move{
Parent: op.GetParentId(), Parent: op.GetParentId(),
Child: op.GetChildId(), Child: op.GetChildId(),
Meta: meta, Meta: meta,
@ -428,22 +439,25 @@ func (s *Service) GetOpLog(req *GetOpLogRequest, srv TreeService_GetOpLogServer)
return err return err
} }
var cli TreeService_GetOpLogClient ns, _, _, err := s.getContainerInfo(cid, s.rawPub)
var outErr error if err == errNotInContainer {
err := s.forEachNode(srv.Context(), cid, func(c TreeServiceClient) bool { var cli TreeService_GetOpLogClient
cli, outErr = c.GetOpLog(srv.Context(), req) var outErr error
return true err := s.forEachNode(srv.Context(), ns, func(c TreeServiceClient) bool {
}) cli, outErr = c.GetOpLog(srv.Context(), req)
if err != nil { return true
return err })
} else if outErr != nil { if err != nil {
return outErr return err
} else if cli != nil { } else if outErr != nil {
return outErr
}
for resp, err := cli.Recv(); err == nil; resp, err = cli.Recv() { for resp, err := cli.Recv(); err == nil; resp, err = cli.Recv() {
if err := srv.Send(resp); err != nil { if err := srv.Send(resp); err != nil {
return err return err
} }
} }
return nil
} }
h := b.GetHeight() h := b.GetHeight()
@ -491,3 +505,21 @@ func metaToProto(arr []pilorama.KeyValue) []*KeyValue {
} }
return meta return meta
} }
var errNotInContainer = errors.New("node doesn't belong to a container")
// getContainerInfo returns the list of container nodes, position in the container for the node
// with pub key and total amount of nodes in all replicas.
func (s *Service) getContainerInfo(cid cidSDK.ID, pub []byte) ([]netmapSDK.NodeInfo, int, int, error) {
cntNodes, err := s.getContainerNodes(cid)
if err != nil {
return nil, 0, 0, err
}
for i, node := range cntNodes {
if bytes.Equal(node.PublicKey(), pub) {
return cntNodes, i, len(cntNodes), nil
}
}
return nil, 0, 0, errNotInContainer
}

View File

@ -88,7 +88,8 @@ func (s *Service) synchronizeSingle(ctx context.Context, cid cid.ID, treeID stri
if err := m.Meta.FromBytes(lm.Meta); err != nil { if err := m.Meta.FromBytes(lm.Meta); err != nil {
return newHeight, err return newHeight, err
} }
if err := s.forest.TreeApply(cid, treeID, m); err != nil { d := pilorama.CIDDescriptor{CID: cid}
if err := s.forest.TreeApply(d, treeID, m); err != nil {
return newHeight, err return newHeight, err
} }
if m.Time > newHeight { if m.Time > newHeight {