forked from TrueCloudLab/frostfs-node
[#50] services/object: Implement GetRange service
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
dd16f568c3
commit
0490107165
12 changed files with 744 additions and 5 deletions
2
go.mod
2
go.mod
|
@ -13,7 +13,7 @@ require (
|
||||||
github.com/multiformats/go-multiaddr-net v0.1.2 // v0.1.1 => v0.1.2
|
github.com/multiformats/go-multiaddr-net v0.1.2 // v0.1.1 => v0.1.2
|
||||||
github.com/multiformats/go-multihash v0.0.13 // indirect
|
github.com/multiformats/go-multihash v0.0.13 // indirect
|
||||||
github.com/nspcc-dev/neo-go v0.91.1-pre.0.20200827184617-7560aa345a78
|
github.com/nspcc-dev/neo-go v0.91.1-pre.0.20200827184617-7560aa345a78
|
||||||
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200922150714-14fa89b81919
|
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200925125840-c814cc62faf4
|
||||||
github.com/nspcc-dev/neofs-crypto v0.3.0
|
github.com/nspcc-dev/neofs-crypto v0.3.0
|
||||||
github.com/nspcc-dev/tzhash v1.4.0
|
github.com/nspcc-dev/tzhash v1.4.0
|
||||||
github.com/panjf2000/ants/v2 v2.3.0
|
github.com/panjf2000/ants/v2 v2.3.0
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -267,10 +267,8 @@ github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:
|
||||||
github.com/nspcc-dev/neo-go v0.91.0/go.mod h1:G6HdOWvzQ6tlvFdvFSN/PgCzLPN/X/X4d5hTjFRUDcc=
|
github.com/nspcc-dev/neo-go v0.91.0/go.mod h1:G6HdOWvzQ6tlvFdvFSN/PgCzLPN/X/X4d5hTjFRUDcc=
|
||||||
github.com/nspcc-dev/neo-go v0.91.1-pre.0.20200827184617-7560aa345a78 h1:stIa+nBXK8uDY/JZaxIZzAUfkzfaotVw2FbnHxO4aZI=
|
github.com/nspcc-dev/neo-go v0.91.1-pre.0.20200827184617-7560aa345a78 h1:stIa+nBXK8uDY/JZaxIZzAUfkzfaotVw2FbnHxO4aZI=
|
||||||
github.com/nspcc-dev/neo-go v0.91.1-pre.0.20200827184617-7560aa345a78/go.mod h1:G6HdOWvzQ6tlvFdvFSN/PgCzLPN/X/X4d5hTjFRUDcc=
|
github.com/nspcc-dev/neo-go v0.91.1-pre.0.20200827184617-7560aa345a78/go.mod h1:G6HdOWvzQ6tlvFdvFSN/PgCzLPN/X/X4d5hTjFRUDcc=
|
||||||
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200918082543-e97f7096c6c4 h1:9pN6XX99JYL2JEP0OsYYHNoQDmVoaGaejx8LWihJvrQ=
|
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200925125840-c814cc62faf4 h1:+ko1UlGsPhKF6O1+ZDOwW7lNhXLEk+e/N/gdma5NNJo=
|
||||||
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200918082543-e97f7096c6c4/go.mod h1:FsFd1z4YzoEgPlltsUgnqna9qhcF87RHYjot0pby2L4=
|
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200925125840-c814cc62faf4/go.mod h1:FsFd1z4YzoEgPlltsUgnqna9qhcF87RHYjot0pby2L4=
|
||||||
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200922150714-14fa89b81919 h1:EBk/P6mWDqwLmE7G4aY9ypxcRRhgV1bcpjvb+WPsxm0=
|
|
||||||
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200922150714-14fa89b81919/go.mod h1:FsFd1z4YzoEgPlltsUgnqna9qhcF87RHYjot0pby2L4=
|
|
||||||
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
|
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
|
||||||
github.com/nspcc-dev/neofs-crypto v0.2.3/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw=
|
github.com/nspcc-dev/neofs-crypto v0.2.3/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw=
|
||||||
github.com/nspcc-dev/neofs-crypto v0.3.0 h1:zlr3pgoxuzrmGCxc5W8dGVfA9Rro8diFvVnBg0L4ifM=
|
github.com/nspcc-dev/neofs-crypto v0.3.0 h1:zlr3pgoxuzrmGCxc5W8dGVfA9Rro8diFvVnBg0L4ifM=
|
||||||
|
|
112
pkg/services/object/range/chain.go
Normal file
112
pkg/services/object/range/chain.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/core/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rangeTraverser struct {
|
||||||
|
chain *rangeChain
|
||||||
|
|
||||||
|
seekBounds *rangeBounds
|
||||||
|
}
|
||||||
|
|
||||||
|
type rangeBounds struct {
|
||||||
|
left, right uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectRange struct {
|
||||||
|
rng *objectSDK.Range
|
||||||
|
|
||||||
|
id *objectSDK.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
type rangeChain struct {
|
||||||
|
next, prev *rangeChain
|
||||||
|
|
||||||
|
bounds *rangeBounds
|
||||||
|
|
||||||
|
id *objectSDK.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRangeTraverser(originSize uint64, rightElement *object.Object, rngSeek *objectSDK.Range) *rangeTraverser {
|
||||||
|
right := &rangeChain{
|
||||||
|
bounds: &rangeBounds{
|
||||||
|
left: originSize - rightElement.GetPayloadSize(),
|
||||||
|
right: originSize,
|
||||||
|
},
|
||||||
|
id: rightElement.GetID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
left := &rangeChain{
|
||||||
|
id: rightElement.GetPreviousID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
left.next, right.prev = right, left
|
||||||
|
|
||||||
|
return &rangeTraverser{
|
||||||
|
chain: right,
|
||||||
|
seekBounds: &rangeBounds{
|
||||||
|
left: rngSeek.GetOffset(),
|
||||||
|
right: rngSeek.GetOffset() + rngSeek.GetLength(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rangeTraverser) next() *objectRange {
|
||||||
|
left := c.chain.bounds.left
|
||||||
|
seekLeft := c.seekBounds.left
|
||||||
|
|
||||||
|
res := new(objectRange)
|
||||||
|
|
||||||
|
if left > seekLeft {
|
||||||
|
res.id = c.chain.prev.id
|
||||||
|
} else {
|
||||||
|
res.id = c.chain.id
|
||||||
|
res.rng = objectSDK.NewRange()
|
||||||
|
res.rng.SetOffset(seekLeft - left)
|
||||||
|
res.rng.SetLength(min(c.chain.bounds.right, c.seekBounds.right) - seekLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b uint64) uint64 {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rangeTraverser) pushHeader(obj *object.Object) {
|
||||||
|
id := obj.GetID()
|
||||||
|
if !id.Equal(c.chain.prev.id) {
|
||||||
|
panic(fmt.Sprintf("(%T) unexpected identifier in header", c))
|
||||||
|
}
|
||||||
|
|
||||||
|
sz := obj.GetPayloadSize()
|
||||||
|
|
||||||
|
c.chain.prev.bounds = &rangeBounds{
|
||||||
|
left: c.chain.bounds.left - sz,
|
||||||
|
right: c.chain.bounds.left,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.chain = c.chain.prev
|
||||||
|
|
||||||
|
c.chain.prev = &rangeChain{
|
||||||
|
next: c.chain,
|
||||||
|
id: obj.GetPreviousID(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rangeTraverser) pushSuccessSize(sz uint64) {
|
||||||
|
c.seekBounds.left += sz
|
||||||
|
|
||||||
|
if c.seekBounds.left >= c.chain.bounds.right && c.chain.next != nil {
|
||||||
|
c.chain = c.chain.next
|
||||||
|
}
|
||||||
|
}
|
36
pkg/services/object/range/local.go
Normal file
36
pkg/services/object/range/local.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type localRangeWriter struct {
|
||||||
|
addr *object.Address
|
||||||
|
|
||||||
|
rng *object.Range
|
||||||
|
|
||||||
|
storage *localstore.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *localRangeWriter) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
obj, err := l.storage.Get(l.addr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrapf(err, "(%T) could not get object from local storage", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := obj.GetPayload()
|
||||||
|
left := l.rng.GetOffset()
|
||||||
|
right := left + l.rng.GetLength()
|
||||||
|
|
||||||
|
if ln := uint64(len(payload)); ln < right {
|
||||||
|
return 0, errors.Errorf("(%T) object range is out-of-boundaries (size %d, range [%d:%d]", l, ln, left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := w.Write(payload[left:right])
|
||||||
|
|
||||||
|
return int64(n), err
|
||||||
|
}
|
39
pkg/services/object/range/prm.go
Normal file
39
pkg/services/object/range/prm.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Prm struct {
|
||||||
|
local bool
|
||||||
|
|
||||||
|
addr *object.Address
|
||||||
|
|
||||||
|
rng *object.Range
|
||||||
|
|
||||||
|
traverser *rangeTraverser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prm) OnlyLocal(v bool) *Prm {
|
||||||
|
if p != nil {
|
||||||
|
p.local = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prm) WithAddress(v *object.Address) *Prm {
|
||||||
|
if p != nil {
|
||||||
|
p.addr = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prm) WithRange(v *object.Range) *Prm {
|
||||||
|
if p != nil {
|
||||||
|
p.rng = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
49
pkg/services/object/range/remote.go
Normal file
49
pkg/services/object/range/remote.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/client"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/network"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type remoteRangeWriter struct {
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
|
||||||
|
node *network.Address
|
||||||
|
|
||||||
|
addr *object.Address
|
||||||
|
|
||||||
|
rng *object.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteRangeWriter) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
addr := r.node.NetAddr()
|
||||||
|
|
||||||
|
c, err := client.New(r.key,
|
||||||
|
client.WithAddress(addr),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrapf(err, "(%T) could not create SDK client %s", r, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: change ObjectPayloadRangeData to implement WriterTo
|
||||||
|
chunk, err := c.ObjectPayloadRangeData(r.ctx, new(client.RangeDataParams).
|
||||||
|
WithRange(r.rng).
|
||||||
|
WithAddress(r.addr),
|
||||||
|
client.WithTTL(1), // FIXME: use constant
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrapf(err, "(%T) could not read object payload range from %s", r, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := w.Write(chunk)
|
||||||
|
|
||||||
|
return int64(n), err
|
||||||
|
}
|
9
pkg/services/object/range/res.go
Normal file
9
pkg/services/object/range/res.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
chunk []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Response) PayloadChunk() []byte {
|
||||||
|
return r.chunk
|
||||||
|
}
|
159
pkg/services/object/range/service.go
Normal file
159
pkg/services/object/range/service.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/core/container"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/core/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/network"
|
||||||
|
headsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/head"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/util"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
*cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*cfg)
|
||||||
|
|
||||||
|
type cfg struct {
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
|
||||||
|
localStore *localstore.Storage
|
||||||
|
|
||||||
|
cnrSrc container.Source
|
||||||
|
|
||||||
|
netMapSrc netmap.Source
|
||||||
|
|
||||||
|
workerPool util.WorkerPool
|
||||||
|
|
||||||
|
localAddrSrc network.LocalAddressSource
|
||||||
|
|
||||||
|
headSvc *headsvc.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultCfg() *cfg {
|
||||||
|
return &cfg{
|
||||||
|
workerPool: new(util.SyncWorkerPool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(opts ...Option) *Service {
|
||||||
|
c := defaultCfg()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
cfg: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetRange(ctx context.Context, prm *Prm) (Streamer, error) {
|
||||||
|
headResult, err := s.headSvc.Head(ctx, new(headsvc.Prm).
|
||||||
|
WithAddress(prm.addr).
|
||||||
|
OnlyLocal(prm.local),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "(%T) could not receive Head result", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
off, ln := prm.rng.GetOffset(), prm.rng.GetLength()
|
||||||
|
|
||||||
|
origin := headResult.Header()
|
||||||
|
|
||||||
|
originSize := origin.GetPayloadSize()
|
||||||
|
if originSize < off+ln {
|
||||||
|
return nil, errors.Errorf("(%T) requested payload range is out-of-bounds", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
right := headResult.RightChild()
|
||||||
|
if right == nil {
|
||||||
|
right = origin
|
||||||
|
}
|
||||||
|
|
||||||
|
rngTraverser := newRangeTraverser(originSize, right, prm.rng)
|
||||||
|
if err := s.fillTraverser(ctx, prm, rngTraverser); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "(%T) could not fill range traverser", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &streamer{
|
||||||
|
cfg: s.cfg,
|
||||||
|
once: new(sync.Once),
|
||||||
|
ctx: ctx,
|
||||||
|
prm: prm,
|
||||||
|
rangeTraverser: rngTraverser,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) fillTraverser(ctx context.Context, prm *Prm, traverser *rangeTraverser) error {
|
||||||
|
addr := object.NewAddress()
|
||||||
|
addr.SetContainerID(prm.addr.GetContainerID())
|
||||||
|
|
||||||
|
for {
|
||||||
|
next := traverser.next()
|
||||||
|
if next.rng != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.SetObjectID(next.id)
|
||||||
|
|
||||||
|
head, err := s.headSvc.Head(ctx, new(headsvc.Prm).
|
||||||
|
WithAddress(addr).
|
||||||
|
OnlyLocal(prm.local),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "(%T) could not receive object header", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
traverser.pushHeader(head.Header())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithKey(v *ecdsa.PrivateKey) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.key = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLocalStorage(v *localstore.Storage) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.localStore = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithContainerSource(v container.Source) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.cnrSrc = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNetworkMapSource(v netmap.Source) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.netMapSrc = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithWorkerPool(v util.WorkerPool) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.workerPool = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLocalAddressSource(v network.LocalAddressSource) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.localAddrSrc = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithHeadService(v *headsvc.Service) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.headSvc = v
|
||||||
|
}
|
||||||
|
}
|
235
pkg/services/object/range/streamer.go
Normal file
235
pkg/services/object/range/streamer.go
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/core/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/network"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/services/object/util"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Streamer interface {
|
||||||
|
Recv() (*Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type streamer struct {
|
||||||
|
*cfg
|
||||||
|
|
||||||
|
once *sync.Once
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
prm *Prm
|
||||||
|
|
||||||
|
traverser *placement.Traverser
|
||||||
|
|
||||||
|
rangeTraverser *rangeTraverser
|
||||||
|
|
||||||
|
ch chan []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type chunkWriter struct {
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
ch chan<- []byte
|
||||||
|
|
||||||
|
written uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *streamer) Recv() (*Response, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p.once.Do(func() {
|
||||||
|
p.ch = make(chan []byte)
|
||||||
|
err = p.workerPool.Submit(p.start)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "(%T) could not start streaming", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
return nil, errors.Wrapf(p.ctx.Err(), "(%T) stream is stopped by context", p)
|
||||||
|
case v, ok := <-p.ch:
|
||||||
|
if !ok {
|
||||||
|
if p.rangeTraverser.next().rng.GetLength() != 0 {
|
||||||
|
return nil, errors.Errorf("(%T) incomplete get payload range", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Response{
|
||||||
|
chunk: v,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *streamer) switchToObject(id *object.ID) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// get latest network map
|
||||||
|
nm, err := netmap.GetLatestNetworkMap(p.netMapSrc)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "(%T) could not get latest network map", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get container to read payload range
|
||||||
|
cnr, err := p.cnrSrc.Get(p.prm.addr.GetContainerID())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "(%T) could not get container by ID", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate placement traverser options
|
||||||
|
traverseOpts := make([]placement.Option, 0, 4)
|
||||||
|
|
||||||
|
// add common options
|
||||||
|
traverseOpts = append(traverseOpts,
|
||||||
|
// set processing container
|
||||||
|
placement.ForContainer(cnr),
|
||||||
|
|
||||||
|
// set success count (1st incoming full range)
|
||||||
|
placement.SuccessAfter(1),
|
||||||
|
|
||||||
|
// set identifier of the processing object
|
||||||
|
placement.ForObject(id),
|
||||||
|
)
|
||||||
|
|
||||||
|
// create placement builder from network map
|
||||||
|
builder := placement.NewNetworkMapBuilder(nm)
|
||||||
|
|
||||||
|
if p.prm.local {
|
||||||
|
// use local-only placement builder
|
||||||
|
builder = util.NewLocalPlacement(builder, p.localAddrSrc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set placement builder
|
||||||
|
traverseOpts = append(traverseOpts, placement.UseBuilder(builder))
|
||||||
|
|
||||||
|
// build placement traverser
|
||||||
|
if p.traverser, err = placement.NewTraverser(traverseOpts...); err != nil {
|
||||||
|
return errors.Wrapf(err, "(%T) could not build placement traverser", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *streamer) start() {
|
||||||
|
defer close(p.ch)
|
||||||
|
|
||||||
|
objAddr := object.NewAddress()
|
||||||
|
objAddr.SetContainerID(p.prm.addr.GetContainerID())
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
// TODO: log this
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
nextRange := p.rangeTraverser.next()
|
||||||
|
if nextRange.rng.GetLength() == 0 {
|
||||||
|
break
|
||||||
|
} else if err := p.switchToObject(nextRange.id); err != nil {
|
||||||
|
// TODO: log error
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
objAddr.SetObjectID(nextRange.id)
|
||||||
|
|
||||||
|
subloop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
// TODO: log this
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs := p.traverser.Next()
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range addrs {
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
addr := addrs[i]
|
||||||
|
|
||||||
|
if err := p.workerPool.Submit(func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var rngWriter io.WriterTo
|
||||||
|
|
||||||
|
if network.IsLocalAddress(p.localAddrSrc, addr) {
|
||||||
|
rngWriter = &localRangeWriter{
|
||||||
|
addr: objAddr,
|
||||||
|
rng: nextRange.rng,
|
||||||
|
storage: p.localStore,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rngWriter = &remoteRangeWriter{
|
||||||
|
ctx: p.ctx,
|
||||||
|
key: p.key,
|
||||||
|
node: addr,
|
||||||
|
addr: objAddr,
|
||||||
|
rng: nextRange.rng,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
written, err := rngWriter.WriteTo(&chunkWriter{
|
||||||
|
ctx: p.ctx,
|
||||||
|
ch: p.ch,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// TODO: log error
|
||||||
|
}
|
||||||
|
|
||||||
|
ln := nextRange.rng.GetLength()
|
||||||
|
uw := uint64(written)
|
||||||
|
|
||||||
|
p.rangeTraverser.pushSuccessSize(uw)
|
||||||
|
nextRange.rng.SetLength(ln - uw)
|
||||||
|
nextRange.rng.SetOffset(nextRange.rng.GetOffset() + uw)
|
||||||
|
}); err != nil {
|
||||||
|
wg.Done()
|
||||||
|
// TODO: log error
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if nextRange.rng.GetLength() == 0 {
|
||||||
|
p.traverser.SubmitSuccess()
|
||||||
|
break subloop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.traverser.Success() {
|
||||||
|
// TODO: log error
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *chunkWriter) Write(p []byte) (int, error) {
|
||||||
|
select {
|
||||||
|
case <-w.ctx.Done():
|
||||||
|
return 0, w.ctx.Err()
|
||||||
|
case w.ch <- p:
|
||||||
|
}
|
||||||
|
|
||||||
|
w.written += uint64(len(p))
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
50
pkg/services/object/range/v2/service.go
Normal file
50
pkg/services/object/range/v2/service.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||||
|
rangesvc "github.com/nspcc-dev/neofs-node/pkg/services/object/range"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service implements GetRange operation of Object service v2.
|
||||||
|
type Service struct {
|
||||||
|
*cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option represents Service constructor option.
|
||||||
|
type Option func(*cfg)
|
||||||
|
|
||||||
|
type cfg struct {
|
||||||
|
svc *rangesvc.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService constructs Service instance from provided options.
|
||||||
|
func NewService(opts ...Option) *Service {
|
||||||
|
c := new(cfg)
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
cfg: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRange calls internal service and returns v2 object payload range stream.
|
||||||
|
func (s *Service) GetRange(ctx context.Context, req *objectV2.GetRangeRequest) (objectV2.GetRangeObjectStreamer, error) {
|
||||||
|
stream, err := s.svc.GetRange(ctx, toPrm(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "(%T) could not get object payload range data", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromResponse(stream), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithInternalService(v *rangesvc.Service) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.svc = v
|
||||||
|
}
|
||||||
|
}
|
27
pkg/services/object/range/v2/streamer.go
Normal file
27
pkg/services/object/range/v2/streamer.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||||
|
rangesvc "github.com/nspcc-dev/neofs-node/pkg/services/object/range"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type streamer struct {
|
||||||
|
stream rangesvc.Streamer
|
||||||
|
|
||||||
|
body *objectV2.GetRangeResponseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *streamer) Recv() (*objectV2.GetRangeResponse, error) {
|
||||||
|
r, err := s.stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "(%T) could not read response from stream", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.body.SetChunk(r.PayloadChunk())
|
||||||
|
|
||||||
|
resp := new(objectV2.GetRangeResponse)
|
||||||
|
resp.SetBody(s.body)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
25
pkg/services/object/range/v2/util.go
Normal file
25
pkg/services/object/range/v2/util.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package rangesvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||||
|
rangesvc "github.com/nspcc-dev/neofs-node/pkg/services/object/range"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toPrm(req *objectV2.GetRangeRequest) *rangesvc.Prm {
|
||||||
|
body := req.GetBody()
|
||||||
|
|
||||||
|
return new(rangesvc.Prm).
|
||||||
|
WithAddress(
|
||||||
|
object.NewAddressFromV2(body.GetAddress()),
|
||||||
|
).
|
||||||
|
WithRange(object.NewRangeFromV2(body.GetRange())).
|
||||||
|
OnlyLocal(req.GetMetaHeader().GetTTL() == 1) // FIXME: use constant
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromResponse(stream rangesvc.Streamer) objectV2.GetRangeObjectStreamer {
|
||||||
|
return &streamer{
|
||||||
|
stream: stream,
|
||||||
|
body: new(objectV2.GetRangeResponseBody),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue