forked from TrueCloudLab/frostfs-node
Initial commit
Initial public review release v0.10.0
This commit is contained in:
commit
dadfd90dcd
276 changed files with 46331 additions and 0 deletions
459
lib/objio/range.go
Normal file
459
lib/objio/range.go
Normal file
|
@ -0,0 +1,459 @@
|
|||
package objio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
||||
"github.com/nspcc-dev/neofs-node/lib/localstore"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
// Address is a type alias of
|
||||
// Address from refs package of neofs-api-go.
|
||||
Address = refs.Address
|
||||
|
||||
// ChopperTable is an interface of RangeChopper storage.
|
||||
ChopperTable interface {
|
||||
PutChopper(addr Address, chopper RangeChopper) error
|
||||
GetChopper(addr Address, rc RCType) (RangeChopper, error)
|
||||
}
|
||||
|
||||
// RangeChopper is an interface of the chooper of object payload range.
|
||||
RangeChopper interface {
|
||||
GetType() RCType
|
||||
GetAddress() Address
|
||||
Closed() bool
|
||||
Chop(ctx context.Context, length, offset int64, fromStart bool) ([]RangeDescriptor, error)
|
||||
}
|
||||
|
||||
// RelativeReceiver is an interface of object relations controller.
|
||||
RelativeReceiver interface {
|
||||
Base(ctx context.Context, addr Address) (RangeDescriptor, error)
|
||||
Neighbor(ctx context.Context, addr Address, left bool) (RangeDescriptor, error)
|
||||
}
|
||||
|
||||
// ChildLister is an interface of object children info storage.
|
||||
ChildLister interface {
|
||||
List(ctx context.Context, parent Address) ([]RangeDescriptor, error)
|
||||
}
|
||||
|
||||
// RangeDescriptor groups the information about object payload range.
|
||||
RangeDescriptor struct {
|
||||
Size int64
|
||||
Offset int64
|
||||
Addr Address
|
||||
|
||||
LeftBound bool
|
||||
RightBound bool
|
||||
}
|
||||
|
||||
chopCache struct {
|
||||
rangeList []RangeDescriptor
|
||||
}
|
||||
|
||||
chopper struct {
|
||||
*sync.RWMutex
|
||||
ct RCType
|
||||
addr Address
|
||||
nr RelativeReceiver
|
||||
cacheOffset int64
|
||||
cache *chopCache
|
||||
}
|
||||
|
||||
// ChopperParams groups the parameters of Scylla chopper.
|
||||
ChopperParams struct {
|
||||
RelativeReceiver RelativeReceiver
|
||||
Addr Address
|
||||
}
|
||||
|
||||
charybdis struct {
|
||||
skr *chopper
|
||||
cl ChildLister
|
||||
}
|
||||
|
||||
// CharybdisParams groups the parameters of Charybdis chopper.
|
||||
CharybdisParams struct {
|
||||
Addr Address
|
||||
ChildLister ChildLister
|
||||
|
||||
ReadySelection []RangeDescriptor
|
||||
}
|
||||
|
||||
// RCType is an enumeration of object payload range chopper types.
|
||||
RCType int
|
||||
|
||||
chopperTable struct {
|
||||
*sync.RWMutex
|
||||
items map[RCType]map[string]RangeChopper
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// RCScylla is an RCType of payload range post-pouncing chopper.
|
||||
RCScylla RCType = iota
|
||||
|
||||
// RCCharybdis is an RCType of payload range pre-pouncing chopper.
|
||||
RCCharybdis
|
||||
)
|
||||
|
||||
var errNilRelativeReceiver = errors.New("relative receiver is nil")
|
||||
|
||||
var errEmptyObjectID = errors.New("object ID is empty")
|
||||
|
||||
var errNilChildLister = errors.New("child lister is nil")
|
||||
|
||||
var errNotFound = errors.New("object range chopper not found")
|
||||
|
||||
var errInvalidBound = errors.New("invalid payload bounds")
|
||||
|
||||
// NewChopperTable is a RangeChopper storage constructor.
|
||||
func NewChopperTable() ChopperTable {
|
||||
return &chopperTable{
|
||||
new(sync.RWMutex),
|
||||
make(map[RCType]map[string]RangeChopper),
|
||||
}
|
||||
}
|
||||
|
||||
// NewScylla constructs object payload range chopper that collects parts of a range on the go.
|
||||
func NewScylla(p *ChopperParams) (RangeChopper, error) {
|
||||
if p.RelativeReceiver == nil {
|
||||
return nil, errNilRelativeReceiver
|
||||
}
|
||||
|
||||
if p.Addr.ObjectID.Empty() {
|
||||
return nil, errEmptyObjectID
|
||||
}
|
||||
|
||||
return &chopper{
|
||||
RWMutex: new(sync.RWMutex),
|
||||
ct: RCScylla,
|
||||
nr: p.RelativeReceiver,
|
||||
addr: p.Addr,
|
||||
cache: &chopCache{
|
||||
rangeList: make([]RangeDescriptor, 0),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewCharybdis constructs object payload range that pre-collects all parts of the range.
|
||||
func NewCharybdis(p *CharybdisParams) (RangeChopper, error) {
|
||||
if p.ChildLister == nil && len(p.ReadySelection) == 0 {
|
||||
return nil, errNilChildLister
|
||||
}
|
||||
|
||||
if p.Addr.ObjectID.Empty() {
|
||||
return nil, errEmptyObjectID
|
||||
}
|
||||
|
||||
cache := new(chopCache)
|
||||
|
||||
if len(p.ReadySelection) > 0 {
|
||||
cache.rangeList = p.ReadySelection
|
||||
}
|
||||
|
||||
return &charybdis{
|
||||
skr: &chopper{
|
||||
RWMutex: new(sync.RWMutex),
|
||||
ct: RCCharybdis,
|
||||
addr: p.Addr,
|
||||
cache: cache,
|
||||
},
|
||||
cl: p.ChildLister,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ct *chopperTable) PutChopper(addr Address, chopper RangeChopper) error {
|
||||
ct.Lock()
|
||||
defer ct.Unlock()
|
||||
|
||||
sAddr := addr.String()
|
||||
chopperType := chopper.GetType()
|
||||
|
||||
m, ok := ct.items[chopperType]
|
||||
if !ok {
|
||||
m = make(map[string]RangeChopper)
|
||||
}
|
||||
|
||||
if _, ok := m[sAddr]; !ok {
|
||||
m[sAddr] = chopper
|
||||
}
|
||||
|
||||
ct.items[chopperType] = m
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ct *chopperTable) GetChopper(addr Address, rc RCType) (RangeChopper, error) {
|
||||
ct.Lock()
|
||||
defer ct.Unlock()
|
||||
|
||||
choppers, ok := ct.items[rc]
|
||||
if !ok {
|
||||
return nil, errNotFound
|
||||
}
|
||||
|
||||
chp, ok := choppers[addr.String()]
|
||||
if !ok {
|
||||
return nil, errNotFound
|
||||
}
|
||||
|
||||
return chp, nil
|
||||
}
|
||||
|
||||
func (c charybdis) GetAddress() Address {
|
||||
return c.skr.addr
|
||||
}
|
||||
|
||||
func (c charybdis) GetType() RCType {
|
||||
return c.skr.ct
|
||||
}
|
||||
|
||||
func (c charybdis) Closed() bool {
|
||||
return len(c.skr.cache.rangeList) > 0
|
||||
}
|
||||
|
||||
func (c *charybdis) devour(ctx context.Context) error {
|
||||
if len(c.skr.cache.rangeList) == 0 {
|
||||
rngs, err := c.cl.List(ctx, c.skr.addr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "charybdis.pounce faild on children list")
|
||||
}
|
||||
|
||||
if ln := len(rngs); ln > 0 {
|
||||
rngs[0].LeftBound = true
|
||||
rngs[ln-1].RightBound = true
|
||||
}
|
||||
|
||||
c.skr.cache.rangeList = rngs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *charybdis) Chop(ctx context.Context, length, offset int64, fromStart bool) ([]RangeDescriptor, error) {
|
||||
if err := c.devour(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "charybdis.Chop failed on devour")
|
||||
}
|
||||
|
||||
return c.skr.Chop(ctx, length, offset, fromStart)
|
||||
}
|
||||
|
||||
func (sc *chopCache) Size() (res int64) {
|
||||
for i := range sc.rangeList {
|
||||
res += sc.rangeList[i].Size
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *chopCache) touchStart() bool {
|
||||
return len(sc.rangeList) > 0 && sc.rangeList[0].LeftBound
|
||||
}
|
||||
|
||||
func (sc *chopCache) touchEnd() bool {
|
||||
ln := len(sc.rangeList)
|
||||
|
||||
return ln > 0 && sc.rangeList[ln-1].RightBound
|
||||
}
|
||||
|
||||
func min(a, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (sc *chopCache) Chop(offset, size int64) ([]RangeDescriptor, error) {
|
||||
if offset*size < 0 {
|
||||
return nil, errInvalidBound
|
||||
}
|
||||
|
||||
if offset+size > sc.Size() {
|
||||
return nil, localstore.ErrOutOfRange
|
||||
}
|
||||
|
||||
var (
|
||||
off int64
|
||||
res = make([]RangeDescriptor, 0)
|
||||
ind int
|
||||
firstOffset int64
|
||||
)
|
||||
|
||||
for i := range sc.rangeList {
|
||||
diff := offset - off
|
||||
if diff > sc.rangeList[i].Size {
|
||||
off += sc.rangeList[i].Size
|
||||
continue
|
||||
} else if diff < sc.rangeList[i].Size {
|
||||
ind = i
|
||||
firstOffset = diff
|
||||
break
|
||||
}
|
||||
|
||||
ind = i + 1
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
var (
|
||||
r RangeDescriptor
|
||||
num int64
|
||||
)
|
||||
|
||||
for i := ind; num < size; i++ {
|
||||
cut := min(size-num, sc.rangeList[i].Size-firstOffset)
|
||||
r = RangeDescriptor{
|
||||
Size: cut,
|
||||
Addr: sc.rangeList[i].Addr,
|
||||
|
||||
LeftBound: sc.rangeList[i].LeftBound,
|
||||
RightBound: sc.rangeList[i].RightBound,
|
||||
}
|
||||
|
||||
if i == ind {
|
||||
r.Offset = firstOffset
|
||||
firstOffset = 0
|
||||
}
|
||||
|
||||
if cut == size-num {
|
||||
r.Size = cut
|
||||
}
|
||||
|
||||
res = append(res, r)
|
||||
|
||||
num += cut
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *chopper) GetAddress() Address {
|
||||
return c.addr
|
||||
}
|
||||
|
||||
func (c *chopper) GetType() RCType {
|
||||
return c.ct
|
||||
}
|
||||
|
||||
func (c *chopper) Closed() bool {
|
||||
return c.cache.touchStart() && c.cache.touchEnd()
|
||||
}
|
||||
|
||||
func (c *chopper) pounce(ctx context.Context, off int64, set bool) error {
|
||||
if len(c.cache.rangeList) == 0 {
|
||||
child, err := c.nr.Base(ctx, c.addr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "chopper.pounce failed on cache init")
|
||||
}
|
||||
|
||||
c.cache.rangeList = []RangeDescriptor{child}
|
||||
}
|
||||
|
||||
oldOff := c.cacheOffset
|
||||
|
||||
defer func() {
|
||||
if !set {
|
||||
c.cacheOffset = oldOff
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
cacheSize = c.cache.Size()
|
||||
v = c.cacheOffset + off
|
||||
)
|
||||
|
||||
switch {
|
||||
case v >= 0 && v <= cacheSize:
|
||||
c.cacheOffset = v
|
||||
return nil
|
||||
case v < 0 && c.cache.touchStart():
|
||||
c.cacheOffset = 0
|
||||
return io.EOF
|
||||
case v > cacheSize && c.cache.touchEnd():
|
||||
c.cacheOffset = cacheSize
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
var (
|
||||
alloc, written int64
|
||||
toLeft = v < 0
|
||||
procAddr Address
|
||||
fPush = func(r RangeDescriptor) {
|
||||
if toLeft {
|
||||
c.cache.rangeList = append([]RangeDescriptor{r}, c.cache.rangeList...)
|
||||
return
|
||||
}
|
||||
c.cache.rangeList = append(c.cache.rangeList, r)
|
||||
}
|
||||
)
|
||||
|
||||
if toLeft {
|
||||
alloc = -v
|
||||
procAddr = c.cache.rangeList[0].Addr
|
||||
c.cacheOffset -= cacheSize
|
||||
} else {
|
||||
alloc = v - cacheSize
|
||||
procAddr = c.cache.rangeList[len(c.cache.rangeList)-1].Addr
|
||||
c.cacheOffset += cacheSize
|
||||
}
|
||||
|
||||
for written < alloc {
|
||||
rng, err := c.nr.Neighbor(ctx, procAddr, toLeft)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "chopper.pounce failed on get neighbor")
|
||||
}
|
||||
|
||||
if diff := alloc - written; diff < rng.Size {
|
||||
if toLeft {
|
||||
rng.Offset = rng.Size - diff
|
||||
}
|
||||
|
||||
c.cacheOffset += diff
|
||||
|
||||
fPush(rng)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
c.cacheOffset += rng.Size
|
||||
fPush(rng)
|
||||
|
||||
written += rng.Size
|
||||
|
||||
if written < alloc &&
|
||||
(rng.LeftBound && toLeft || rng.RightBound && !toLeft) {
|
||||
return localstore.ErrOutOfRange
|
||||
}
|
||||
|
||||
procAddr = rng.Addr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chopper) Chop(ctx context.Context, length, offset int64, fromStart bool) ([]RangeDescriptor, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if fromStart {
|
||||
if err := c.pounce(ctx, -(1 << 63), true); err != nil && err != io.EOF {
|
||||
return nil, errors.Wrap(err, "chopper.Chop failed on chopper.pounce to start")
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.pounce(ctx, offset, true); err != nil && err != io.EOF {
|
||||
return nil, errors.Wrap(err, "chopper.Chop failed on chopper.pounce with set")
|
||||
}
|
||||
|
||||
if c.cache.Size()-c.cacheOffset < length {
|
||||
if err := c.pounce(ctx, length, false); err != nil && err != io.EOF {
|
||||
return nil, errors.Wrap(err, "chopper.Chop failed on chopper.pounce")
|
||||
}
|
||||
}
|
||||
|
||||
return c.cache.Chop(c.cacheOffset, length)
|
||||
}
|
386
lib/objio/range_test.go
Normal file
386
lib/objio/range_test.go
Normal file
|
@ -0,0 +1,386 @@
|
|||
package objio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/object"
|
||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type (
|
||||
addressSet struct {
|
||||
*sync.RWMutex
|
||||
items []RangeDescriptor
|
||||
data [][]byte
|
||||
}
|
||||
|
||||
testReader struct {
|
||||
pr object.PositionReader
|
||||
ct ChopperTable
|
||||
}
|
||||
)
|
||||
|
||||
func (r testReader) Read(ctx context.Context, rd RangeDescriptor, rc RCType) ([]byte, error) {
|
||||
chopper, err := r.ct.GetChopper(rd.Addr, rc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "testReader.Read failed on get range chopper")
|
||||
}
|
||||
|
||||
rngs, err := chopper.Chop(ctx, rd.Size, rd.Offset, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "testReader.Read failed on chopper.Chop")
|
||||
}
|
||||
|
||||
var sz int64
|
||||
for i := range rngs {
|
||||
sz += rngs[i].Size
|
||||
}
|
||||
|
||||
res := make([]byte, 0, sz)
|
||||
|
||||
for i := range rngs {
|
||||
data, err := r.pr.PRead(ctx, rngs[i].Addr, object.Range{
|
||||
Offset: uint64(rngs[i].Offset),
|
||||
Length: uint64(rngs[i].Size),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "testReader.Read failed on PRead of range #%d", i)
|
||||
}
|
||||
|
||||
res = append(res, data...)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (as addressSet) PRead(ctx context.Context, addr refs.Address, rng object.Range) ([]byte, error) {
|
||||
as.RLock()
|
||||
defer as.RUnlock()
|
||||
|
||||
for i := range as.items {
|
||||
if as.items[i].Addr.CID.Equal(addr.CID) && as.items[i].Addr.ObjectID.Equal(addr.ObjectID) {
|
||||
return as.data[i][rng.Offset : rng.Offset+rng.Length], nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("pread failed")
|
||||
}
|
||||
|
||||
func (as addressSet) List(ctx context.Context, parent Address) ([]RangeDescriptor, error) {
|
||||
return as.items, nil
|
||||
}
|
||||
|
||||
func (as addressSet) Base(ctx context.Context, addr Address) (RangeDescriptor, error) {
|
||||
return as.items[0], nil
|
||||
}
|
||||
|
||||
func (as addressSet) Neighbor(ctx context.Context, addr Address, left bool) (RangeDescriptor, error) {
|
||||
as.Lock()
|
||||
defer as.Unlock()
|
||||
|
||||
ind := -1
|
||||
for i := range as.items {
|
||||
if as.items[i].Addr.CID.Equal(addr.CID) && as.items[i].Addr.ObjectID.Equal(addr.ObjectID) {
|
||||
ind = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ind == -1 {
|
||||
return RangeDescriptor{}, errors.New("range not found")
|
||||
}
|
||||
|
||||
if left {
|
||||
if ind > 0 {
|
||||
ind--
|
||||
} else {
|
||||
return RangeDescriptor{}, io.EOF
|
||||
}
|
||||
} else {
|
||||
if ind < len(as.items)-1 {
|
||||
ind++
|
||||
} else {
|
||||
return RangeDescriptor{}, io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
return as.items[ind], nil
|
||||
}
|
||||
|
||||
func newTestNeighbor(rngs []RangeDescriptor, data [][]byte) *addressSet {
|
||||
return &addressSet{
|
||||
RWMutex: new(sync.RWMutex),
|
||||
items: rngs,
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func rangeSize(rngs []RangeDescriptor) (res int64) {
|
||||
for i := range rngs {
|
||||
res += rngs[i].Size
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestScylla(t *testing.T) {
|
||||
var (
|
||||
cid = [refs.CIDSize]byte{1}
|
||||
rngs = make([]RangeDescriptor, 0, 10)
|
||||
pieceSize int64 = 100
|
||||
pieceCount int64 = 99
|
||||
fullSize = pieceCount * pieceSize
|
||||
)
|
||||
|
||||
for i := int64(0); i < pieceCount; i++ {
|
||||
oid, err := refs.NewObjectID()
|
||||
require.NoError(t, err)
|
||||
|
||||
rngs = append(rngs, RangeDescriptor{
|
||||
Size: pieceSize,
|
||||
Offset: 0,
|
||||
Addr: Address{
|
||||
ObjectID: oid,
|
||||
CID: cid,
|
||||
},
|
||||
LeftBound: i == 0,
|
||||
RightBound: i == pieceCount-1,
|
||||
})
|
||||
}
|
||||
|
||||
oid, err := refs.NewObjectID()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
t.Run("Zero values in scylla notch/chop", func(t *testing.T) {
|
||||
scylla, err := NewScylla(&ChopperParams{
|
||||
RelativeReceiver: newTestNeighbor(rngs, nil),
|
||||
Addr: Address{
|
||||
ObjectID: oid,
|
||||
CID: cid,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := scylla.Chop(ctx, 0, 0, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 0)
|
||||
})
|
||||
|
||||
t.Run("Common scylla operations in both directions", func(t *testing.T) {
|
||||
var (
|
||||
off = fullSize / 2
|
||||
length = fullSize / 4
|
||||
)
|
||||
|
||||
scylla, err := NewScylla(&ChopperParams{
|
||||
RelativeReceiver: newTestNeighbor(rngs, nil),
|
||||
Addr: Address{
|
||||
ObjectID: oid,
|
||||
CID: cid,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
choppedCount := int((length-1)/pieceSize + 1)
|
||||
|
||||
if pieceCount > 1 && off%pieceSize > 0 {
|
||||
choppedCount++
|
||||
}
|
||||
|
||||
res, err := scylla.Chop(ctx, fullSize, 0, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, int(pieceCount))
|
||||
require.Equal(t, rangeSize(res), fullSize)
|
||||
require.Equal(t, res, rngs)
|
||||
|
||||
res, err = scylla.Chop(ctx, length, off, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, choppedCount)
|
||||
|
||||
for i := int64(0); i < int64(choppedCount); i++ {
|
||||
require.Equal(t, res[i].Addr.ObjectID, rngs[pieceCount/2+i].Addr.ObjectID)
|
||||
}
|
||||
|
||||
require.Equal(t, rangeSize(res), length)
|
||||
|
||||
res, err = scylla.Chop(ctx, length, -length, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, choppedCount)
|
||||
|
||||
for i := int64(0); i < int64(choppedCount); i++ {
|
||||
require.Equal(t, res[i].Addr.ObjectID, rngs[pieceCount/4+i].Addr.ObjectID)
|
||||
}
|
||||
|
||||
require.Equal(t, rangeSize(res), length)
|
||||
})
|
||||
|
||||
t.Run("Border scylla Chop", func(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
res []RangeDescriptor
|
||||
)
|
||||
|
||||
scylla, err := NewScylla(&ChopperParams{
|
||||
RelativeReceiver: newTestNeighbor(rngs, nil),
|
||||
Addr: Address{
|
||||
ObjectID: oid,
|
||||
CID: cid,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err = scylla.Chop(ctx, fullSize, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, res, rngs)
|
||||
|
||||
res, err = scylla.Chop(ctx, fullSize, -100, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, res, rngs)
|
||||
|
||||
res, err = scylla.Chop(ctx, fullSize, 1, false)
|
||||
require.Error(t, err)
|
||||
|
||||
res, err = scylla.Chop(ctx, fullSize, -fullSize, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, rangeSize(res), fullSize)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCharybdis(t *testing.T) {
|
||||
var (
|
||||
cid = [refs.CIDSize]byte{1}
|
||||
rngs = make([]RangeDescriptor, 0, 10)
|
||||
pieceSize int64 = 100
|
||||
pieceCount int64 = 99
|
||||
fullSize = pieceCount * pieceSize
|
||||
data = make([]byte, fullSize)
|
||||
dataChunks = make([][]byte, 0, pieceCount)
|
||||
)
|
||||
|
||||
_, err := rand.Read(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := int64(0); i < pieceCount; i++ {
|
||||
oid, err := refs.NewObjectID()
|
||||
require.NoError(t, err)
|
||||
|
||||
dataChunks = append(dataChunks, data[i*pieceSize:(i+1)*pieceSize])
|
||||
|
||||
rngs = append(rngs, RangeDescriptor{
|
||||
Size: pieceSize,
|
||||
Offset: 0,
|
||||
Addr: Address{
|
||||
ObjectID: oid,
|
||||
CID: cid,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
oid, err := refs.NewObjectID()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
t.Run("Zero values in scylla notch/chop", func(t *testing.T) {
|
||||
charybdis, err := NewCharybdis(&CharybdisParams{
|
||||
ChildLister: newTestNeighbor(rngs, nil),
|
||||
Addr: Address{
|
||||
ObjectID: oid,
|
||||
CID: cid,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := charybdis.Chop(ctx, 0, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 0)
|
||||
})
|
||||
|
||||
t.Run("Common charybdis operations in both directions", func(t *testing.T) {
|
||||
var (
|
||||
off = fullSize / 2
|
||||
length = fullSize / 4
|
||||
)
|
||||
|
||||
charybdis, err := NewCharybdis(&CharybdisParams{
|
||||
ChildLister: newTestNeighbor(rngs, nil),
|
||||
Addr: Address{
|
||||
ObjectID: oid,
|
||||
CID: cid,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
choppedCount := int((length-1)/pieceSize + 1)
|
||||
|
||||
if pieceCount > 1 && off%pieceSize > 0 {
|
||||
choppedCount++
|
||||
}
|
||||
|
||||
res, err := charybdis.Chop(ctx, fullSize, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, int(pieceCount))
|
||||
require.Equal(t, rangeSize(res), fullSize)
|
||||
require.Equal(t, res, rngs)
|
||||
|
||||
res, err = charybdis.Chop(ctx, length, off, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, choppedCount)
|
||||
|
||||
for i := int64(0); i < int64(choppedCount); i++ {
|
||||
require.Equal(t, res[i].Addr.ObjectID, rngs[pieceCount/2+i].Addr.ObjectID)
|
||||
}
|
||||
|
||||
require.Equal(t, rangeSize(res), length)
|
||||
|
||||
res, err = charybdis.Chop(ctx, length, -length, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, choppedCount)
|
||||
|
||||
for i := int64(0); i < int64(choppedCount); i++ {
|
||||
require.Equal(t, res[i].Addr.ObjectID, rngs[pieceCount/4+i].Addr.ObjectID)
|
||||
}
|
||||
|
||||
require.Equal(t, rangeSize(res), length)
|
||||
})
|
||||
|
||||
t.Run("Border charybdis Chop", func(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
res []RangeDescriptor
|
||||
)
|
||||
|
||||
charybdis, err := NewCharybdis(&CharybdisParams{
|
||||
ChildLister: newTestNeighbor(rngs, nil),
|
||||
Addr: Address{
|
||||
ObjectID: oid,
|
||||
CID: cid,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err = charybdis.Chop(ctx, fullSize, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, res, rngs)
|
||||
|
||||
res, err = charybdis.Chop(ctx, fullSize, -100, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, res, rngs)
|
||||
|
||||
res, err = charybdis.Chop(ctx, fullSize, 1, false)
|
||||
require.Error(t, err)
|
||||
|
||||
res, err = charybdis.Chop(ctx, fullSize, -fullSize, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, rangeSize(res), fullSize)
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue