[#239] object/head: Implement new service processing

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2020-12-09 13:32:33 +03:00 committed by Alex Vanin
parent 9dd83bdf0d
commit 0e1f05ff45
11 changed files with 348 additions and 140 deletions

View file

@ -25,7 +25,6 @@ import (
getsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/get" getsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/get"
getsvcV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/get/v2" getsvcV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/get/v2"
headsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/head" headsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/head"
headsvcV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/head/v2"
putsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/put" putsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/put"
putsvcV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/put/v2" putsvcV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/put/v2"
searchsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/search" searchsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/search"
@ -44,8 +43,6 @@ type objectSvc struct {
search *searchsvcV2.Service search *searchsvcV2.Service
head *headsvcV2.Service
get *getsvcV2.Service get *getsvcV2.Service
delete *deletesvcV2.Service delete *deletesvcV2.Service
@ -140,7 +137,7 @@ func (s *objectSvc) Put(ctx context.Context) (object.PutObjectStreamer, error) {
} }
func (s *objectSvc) Head(ctx context.Context, req *object.HeadRequest) (*object.HeadResponse, error) { func (s *objectSvc) Head(ctx context.Context, req *object.HeadRequest) (*object.HeadResponse, error) {
return s.head.Head(ctx, req) return s.get.Head(ctx, req)
} }
func (s *objectSvc) Search(ctx context.Context, req *object.SearchRequest) (object.SearchObjectStreamer, error) { func (s *objectSvc) Search(ctx context.Context, req *object.SearchRequest) (object.SearchObjectStreamer, error) {
@ -342,15 +339,10 @@ func initObjectService(c *cfg) {
), ),
) )
sHeadV2 := headsvcV2.NewService(
headsvcV2.WithInternalService(sHead),
)
sGet := getsvc.New( sGet := getsvc.New(
getsvc.WithLogger(c.log), getsvc.WithLogger(c.log),
getsvc.WithLocalStorageEngine(ls), getsvc.WithLocalStorageEngine(ls),
getsvc.WithClientCache(clientCache), getsvc.WithClientCache(clientCache),
getsvc.WithHeadService(sHead),
getsvc.WithClientOptions( getsvc.WithClientOptions(
client.WithDialTimeout(c.viper.GetDuration(cfgObjectGetDialTimeout)), client.WithDialTimeout(c.viper.GetDuration(cfgObjectGetDialTimeout)),
), ),
@ -403,7 +395,6 @@ func initObjectService(c *cfg) {
&objectSvc{ &objectSvc{
put: sPutV2, put: sPutV2,
search: sSearchV2, search: sSearchV2,
head: sHeadV2,
get: sGetV2, get: sGetV2,
delete: sDeleteV2, delete: sDeleteV2,
}, },

View file

@ -35,8 +35,12 @@ type execCtx struct {
collectedObject *object.Object collectedObject *object.Object
curOff uint64 curOff uint64
head bool
} }
type execOption func(*execCtx)
const ( const (
statusUndefined int = iota statusUndefined int = iota
statusOK statusOK
@ -45,9 +49,23 @@ const (
statusOutOfRange statusOutOfRange
) )
func headOnly() execOption {
return func(c *execCtx) {
c.head = true
}
}
func withPayloadRange(r *objectSDK.Range) execOption {
return func(c *execCtx) {
c.prm.rng = r
}
}
func (exec *execCtx) setLogger(l *logger.Logger) { func (exec *execCtx) setLogger(l *logger.Logger) {
req := "GET" req := "GET"
if exec.ctxRange() != nil { if exec.headOnly() {
req = "HEAD"
} else if exec.ctxRange() != nil {
req = "GET_RANGE" req = "GET_RANGE"
} }
@ -92,7 +110,7 @@ func (exec execCtx) remotePrm() *client.GetObjectParams {
} }
func (exec *execCtx) canAssemble() bool { func (exec *execCtx) canAssemble() bool {
return exec.svc.assembly && !exec.isRaw() return exec.svc.assembly && !exec.isRaw() && !exec.headOnly()
} }
func (exec *execCtx) splitInfo() *objectSDK.SplitInfo { func (exec *execCtx) splitInfo() *objectSDK.SplitInfo {
@ -107,6 +125,10 @@ func (exec *execCtx) ctxRange() *objectSDK.Range {
return exec.prm.rng return exec.prm.rng
} }
func (exec *execCtx) headOnly() bool {
return exec.head
}
func (exec *execCtx) generateTraverser(addr *objectSDK.Address) (*placement.Traverser, bool) { func (exec *execCtx) generateTraverser(addr *objectSDK.Address) (*placement.Traverser, bool) {
t, err := exec.svc.traverserGenerator.GenerateTraverser(addr) t, err := exec.svc.traverserGenerator.GenerateTraverser(addr)
@ -126,7 +148,7 @@ func (exec *execCtx) generateTraverser(addr *objectSDK.Address) (*placement.Trav
} }
func (exec *execCtx) getChild(id *objectSDK.ID, rng *objectSDK.Range) (*object.Object, bool) { func (exec *execCtx) getChild(id *objectSDK.ID, rng *objectSDK.Range) (*object.Object, bool) {
w := newSimpleObjectWriter() w := NewSimpleObjectWriter()
p := exec.prm p := exec.prm
p.common = p.common.WithLocalOnly(false) p.common = p.common.WithLocalOnly(false)
@ -139,9 +161,9 @@ func (exec *execCtx) getChild(id *objectSDK.ID, rng *objectSDK.Range) (*object.O
p.WithAddress(addr) p.WithAddress(addr)
exec.statusError = exec.svc.get(exec.context(), p) exec.statusError = exec.svc.get(exec.context(), p.commonPrm, withPayloadRange(rng))
return w.object(), exec.status == statusOK return w.Object(), exec.status == statusOK
} }
func (exec *execCtx) headChild(id *objectSDK.ID) (*object.Object, bool) { func (exec *execCtx) headChild(id *objectSDK.ID) (*object.Object, bool) {
@ -153,9 +175,14 @@ func (exec *execCtx) headChild(id *objectSDK.ID) (*object.Object, bool) {
p.common = p.common.WithLocalOnly(false) p.common = p.common.WithLocalOnly(false)
p.WithAddress(childAddr) p.WithAddress(childAddr)
header, err := exec.svc.headSvc.head(exec.context(), Prm{ prm := HeadPrm{
commonPrm: p.commonPrm, commonPrm: p.commonPrm,
}) }
w := NewSimpleObjectWriter()
prm.SetHeaderWriter(w)
err := exec.svc.Head(exec.context(), prm)
switch { switch {
default: default:
@ -172,7 +199,7 @@ func (exec *execCtx) headChild(id *objectSDK.ID) (*object.Object, bool) {
exec.status = statusOK exec.status = statusOK
exec.err = nil exec.err = nil
return header, true return w.Object(), true
} }
} }
@ -244,6 +271,10 @@ func (exec *execCtx) writeCollectedHeader() bool {
} }
func (exec *execCtx) writeObjectPayload(obj *object.Object) bool { func (exec *execCtx) writeObjectPayload(obj *object.Object) bool {
if exec.headOnly() {
return true
}
err := exec.prm.objWriter.WriteChunk(obj.Payload()) err := exec.prm.objWriter.WriteChunk(obj.Payload())
switch { switch {

View file

@ -9,14 +9,12 @@ import (
// Get serves a request to get an object by address, and returns Streamer instance. // Get serves a request to get an object by address, and returns Streamer instance.
func (s *Service) Get(ctx context.Context, prm Prm) error { func (s *Service) Get(ctx context.Context, prm Prm) error {
return s.get(ctx, RangePrm{ return s.get(ctx, prm.commonPrm).err
commonPrm: prm.commonPrm,
}).err
} }
// GetRange serves a request to get an object by address, and returns Streamer instance. // GetRange serves a request to get an object by address, and returns Streamer instance.
func (s *Service) GetRange(ctx context.Context, prm RangePrm) error { func (s *Service) GetRange(ctx context.Context, prm RangePrm) error {
return s.get(ctx, prm).err return s.get(ctx, prm.commonPrm, withPayloadRange(prm.rng)).err
} }
func (s *Service) GetRangeHash(ctx context.Context, prm RangeHashPrm) (*RangeHashRes, error) { func (s *Service) GetRangeHash(ctx context.Context, prm RangeHashPrm) (*RangeHashRes, error) {
@ -50,14 +48,28 @@ func (s *Service) GetRangeHash(ctx context.Context, prm RangeHashPrm) (*RangeHas
}, nil }, nil
} }
func (s *Service) get(ctx context.Context, prm RangePrm) statusError { // Head reads object header from container.
//
// Returns ErrNotFound if the header was not received for the call.
// Returns SplitInfoError if object is virtual and raw flag is set.
func (s *Service) Head(ctx context.Context, prm HeadPrm) error {
return s.get(ctx, prm.commonPrm, headOnly()).err
}
func (s *Service) get(ctx context.Context, prm commonPrm, opts ...execOption) statusError {
exec := &execCtx{ exec := &execCtx{
svc: s, svc: s,
ctx: ctx, ctx: ctx,
prm: prm, prm: RangePrm{
commonPrm: prm,
},
infoSplit: objectSDK.NewSplitInfo(), infoSplit: objectSDK.NewSplitInfo(),
} }
for i := range opts {
opts[i](exec)
}
exec.setLogger(s.log) exec.setLogger(s.log)
exec.execute() exec.execute()

View file

@ -93,8 +93,8 @@ func newTestClient() *testClient {
} }
} }
func (c *testClient) GetObject(_ context.Context, p RangePrm) (*objectSDK.Object, error) { func (c *testClient) getObject(exec *execCtx) (*objectSDK.Object, error) {
v, ok := c.results[p.Address().String()] v, ok := c.results[exec.address().String()]
if !ok { if !ok {
return nil, object.ErrNotFound return nil, object.ErrNotFound
} }
@ -103,7 +103,7 @@ func (c *testClient) GetObject(_ context.Context, p RangePrm) (*objectSDK.Object
return nil, v.err return nil, v.err
} }
return cutToRange(v.obj.Object(), p.rng).SDK(), nil return cutToRange(v.obj.Object(), exec.ctxRange()).SDK(), nil
} }
func (c *testClient) head(_ context.Context, p Prm) (*object.Object, error) { func (c *testClient) head(_ context.Context, p Prm) (*object.Object, error) {
@ -126,11 +126,11 @@ func (c *testClient) addResult(addr *objectSDK.Address, obj *object.RawObject, e
}{obj: obj, err: err} }{obj: obj, err: err}
} }
func (s *testStorage) Get(p RangePrm) (*object.Object, error) { func (s *testStorage) get(exec *execCtx) (*object.Object, error) {
var ( var (
ok bool ok bool
obj *object.Object obj *object.Object
sAddr = p.Address().String() sAddr = exec.address().String()
) )
if _, ok = s.inhumed[sAddr]; ok { if _, ok = s.inhumed[sAddr]; ok {
@ -142,7 +142,7 @@ func (s *testStorage) Get(p RangePrm) (*object.Object, error) {
} }
if obj, ok = s.phy[sAddr]; ok { if obj, ok = s.phy[sAddr]; ok {
return cutToRange(obj, p.rng), nil return cutToRange(obj, exec.ctxRange()), nil
} }
return nil, object.ErrNotFound return nil, object.ErrNotFound
@ -207,6 +207,7 @@ func generateObject(addr *objectSDK.Address, prev *objectSDK.ID, payload []byte,
obj.SetContainerID(addr.ContainerID()) obj.SetContainerID(addr.ContainerID())
obj.SetID(addr.ObjectID()) obj.SetID(addr.ObjectID())
obj.SetPayload(payload) obj.SetPayload(payload)
obj.SetPayloadSize(uint64(len(payload)))
obj.SetPreviousID(prev) obj.SetPreviousID(prev)
obj.SetChildren(children...) obj.SetChildren(children...)
@ -249,11 +250,20 @@ func TestGetLocalOnly(t *testing.T) {
return p return p
} }
newHeadPrm := func(raw bool, w ObjectWriter) HeadPrm {
p := HeadPrm{}
p.SetHeaderWriter(w)
p.WithRawFlag(raw)
p.common = new(util.CommonPrm).WithLocalOnly(true)
return p
}
t.Run("OK", func(t *testing.T) { t.Run("OK", func(t *testing.T) {
storage := newTestStorage() storage := newTestStorage()
svc := newSvc(storage) svc := newSvc(storage)
w := newSimpleObjectWriter() w := NewSimpleObjectWriter()
p := newPrm(false, w) p := newPrm(false, w)
payloadSz := uint64(10) payloadSz := uint64(10)
@ -273,16 +283,24 @@ func TestGetLocalOnly(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, obj.Object(), w.object()) require.Equal(t, obj.Object(), w.Object())
w = newSimpleObjectWriter() w = NewSimpleObjectWriter()
rngPrm := newRngPrm(false, w, payloadSz/3, payloadSz/3) rngPrm := newRngPrm(false, w, payloadSz/3, payloadSz/3)
rngPrm.WithAddress(addr) rngPrm.WithAddress(addr)
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, payload[payloadSz/3:2*payloadSz/3], w.object().Payload()) require.Equal(t, payload[payloadSz/3:2*payloadSz/3], w.Object().Payload())
w = NewSimpleObjectWriter()
headPrm := newHeadPrm(false, w)
headPrm.WithAddress(addr)
err = svc.Head(ctx, headPrm)
require.NoError(t, err)
require.Equal(t, obj.CutPayload().Object(), w.Object())
}) })
t.Run("INHUMED", func(t *testing.T) { t.Run("INHUMED", func(t *testing.T) {
@ -306,6 +324,12 @@ func TestGetLocalOnly(t *testing.T) {
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.True(t, errors.Is(err, object.ErrAlreadyRemoved)) require.True(t, errors.Is(err, object.ErrAlreadyRemoved))
headPrm := newHeadPrm(false, nil)
headPrm.WithAddress(addr)
err = svc.Head(ctx, headPrm)
require.True(t, errors.Is(err, object.ErrAlreadyRemoved))
}) })
t.Run("404", func(t *testing.T) { t.Run("404", func(t *testing.T) {
@ -328,6 +352,12 @@ func TestGetLocalOnly(t *testing.T) {
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.True(t, errors.Is(err, object.ErrNotFound)) require.True(t, errors.Is(err, object.ErrNotFound))
headPrm := newHeadPrm(false, nil)
headPrm.WithAddress(addr)
err = svc.Head(ctx, headPrm)
require.True(t, errors.Is(err, object.ErrNotFound))
}) })
t.Run("VIRTUAL", func(t *testing.T) { t.Run("VIRTUAL", func(t *testing.T) {
@ -361,6 +391,13 @@ func TestGetLocalOnly(t *testing.T) {
err = svc.Get(ctx, p) err = svc.Get(ctx, p)
require.True(t, errors.As(err, &errSplit)) require.True(t, errors.As(err, &errSplit))
headPrm := newHeadPrm(true, nil)
headPrm.WithAddress(addr)
err = svc.Head(ctx, headPrm)
require.True(t, errors.As(err, &errSplit))
require.Equal(t, splitInfo, errSplit.SplitInfo())
}) })
} }
@ -440,7 +477,7 @@ func TestGetRemoteSmall(t *testing.T) {
newSvc := func(b *testPlacementBuilder, c *testClientCache) *Service { newSvc := func(b *testPlacementBuilder, c *testClientCache) *Service {
svc := &Service{cfg: new(cfg)} svc := &Service{cfg: new(cfg)}
svc.log = test.NewLogger(true) svc.log = test.NewLogger(false)
svc.localStorage = newTestStorage() svc.localStorage = newTestStorage()
svc.assembly = true svc.assembly = true
svc.traverserGenerator = &testTraverserGenerator{ svc.traverserGenerator = &testTraverserGenerator{
@ -476,6 +513,15 @@ func TestGetRemoteSmall(t *testing.T) {
return p return p
} }
newHeadPrm := func(raw bool, w ObjectWriter) HeadPrm {
p := HeadPrm{}
p.SetHeaderWriter(w)
p.WithRawFlag(raw)
p.common = new(util.CommonPrm).WithLocalOnly(false)
return p
}
t.Run("OK", func(t *testing.T) { t.Run("OK", func(t *testing.T) {
addr := generateAddress() addr := generateAddress()
addr.SetContainerID(cid) addr.SetContainerID(cid)
@ -507,28 +553,36 @@ func TestGetRemoteSmall(t *testing.T) {
}, },
}) })
w := newSimpleObjectWriter() w := NewSimpleObjectWriter()
p := newPrm(false, w) p := newPrm(false, w)
p.WithAddress(addr) p.WithAddress(addr)
err := svc.Get(ctx, p) err := svc.Get(ctx, p)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, obj.Object(), w.object()) require.Equal(t, obj.Object(), w.Object())
*c1, *c2 = *c2, *c1 *c1, *c2 = *c2, *c1
err = svc.Get(ctx, p) err = svc.Get(ctx, p)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, obj.Object(), w.object()) require.Equal(t, obj.Object(), w.Object())
w = newSimpleObjectWriter() w = NewSimpleObjectWriter()
rngPrm := newRngPrm(false, w, payloadSz/3, payloadSz/3) rngPrm := newRngPrm(false, w, payloadSz/3, payloadSz/3)
rngPrm.WithAddress(addr) rngPrm.WithAddress(addr)
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, payload[payloadSz/3:2*payloadSz/3], w.object().Payload()) require.Equal(t, payload[payloadSz/3:2*payloadSz/3], w.Object().Payload())
w = NewSimpleObjectWriter()
headPrm := newHeadPrm(false, w)
headPrm.WithAddress(addr)
err = svc.Head(ctx, headPrm)
require.NoError(t, err)
require.Equal(t, obj.CutPayload().Object(), w.Object())
}) })
t.Run("INHUMED", func(t *testing.T) { t.Run("INHUMED", func(t *testing.T) {
@ -567,6 +621,12 @@ func TestGetRemoteSmall(t *testing.T) {
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.True(t, errors.Is(err, object.ErrAlreadyRemoved)) require.True(t, errors.Is(err, object.ErrAlreadyRemoved))
headPrm := newHeadPrm(false, nil)
headPrm.WithAddress(addr)
err = svc.Head(ctx, headPrm)
require.True(t, errors.Is(err, object.ErrAlreadyRemoved))
}) })
t.Run("404", func(t *testing.T) { t.Run("404", func(t *testing.T) {
@ -605,9 +665,26 @@ func TestGetRemoteSmall(t *testing.T) {
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.True(t, errors.Is(err, object.ErrNotFound)) require.True(t, errors.Is(err, object.ErrNotFound))
headPrm := newHeadPrm(false, nil)
headPrm.WithAddress(addr)
err = svc.Head(ctx, headPrm)
require.True(t, errors.Is(err, object.ErrNotFound))
}) })
t.Run("VIRTUAL", func(t *testing.T) { t.Run("VIRTUAL", func(t *testing.T) {
testHeadVirtual := func(svc *Service, addr *objectSDK.Address, i *objectSDK.SplitInfo) {
headPrm := newHeadPrm(false, nil)
headPrm.WithAddress(addr)
errSplit := objectSDK.NewSplitInfoError(objectSDK.NewSplitInfo())
err := svc.Head(ctx, headPrm)
require.True(t, errors.As(err, &errSplit))
require.Equal(t, i, errSplit.SplitInfo())
}
t.Run("linking", func(t *testing.T) { t.Run("linking", func(t *testing.T) {
t.Run("get linking failure", func(t *testing.T) { t.Run("get linking failure", func(t *testing.T) {
addr := generateAddress() addr := generateAddress()
@ -645,6 +722,8 @@ func TestGetRemoteSmall(t *testing.T) {
}, },
}) })
testHeadVirtual(svc, addr, splitInfo)
p := newPrm(false, nil) p := newPrm(false, nil)
p.WithAddress(addr) p.WithAddress(addr)
@ -717,15 +796,15 @@ func TestGetRemoteSmall(t *testing.T) {
}, },
}) })
p := newPrm(false, newSimpleObjectWriter()) testHeadVirtual(svc, addr, splitInfo)
p := newPrm(false, NewSimpleObjectWriter())
p.WithAddress(addr) p.WithAddress(addr)
err := svc.Get(ctx, p) err := svc.Get(ctx, p)
require.True(t, errors.Is(err, object.ErrNotFound)) require.True(t, errors.Is(err, object.ErrNotFound))
svc.headSvc = c2 rngPrm := newRngPrm(false, NewSimpleObjectWriter(), 0, 1)
rngPrm := newRngPrm(false, newSimpleObjectWriter(), 0, 1)
rngPrm.WithAddress(addr) rngPrm.WithAddress(addr)
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
@ -792,18 +871,18 @@ func TestGetRemoteSmall(t *testing.T) {
}, },
}) })
w := newSimpleObjectWriter() testHeadVirtual(svc, addr, splitInfo)
w := NewSimpleObjectWriter()
p := newPrm(false, w) p := newPrm(false, w)
p.WithAddress(addr) p.WithAddress(addr)
err := svc.Get(ctx, p) err := svc.Get(ctx, p)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, srcObj.Object(), w.object()) require.Equal(t, srcObj.Object(), w.Object())
svc.headSvc = c2 w = NewSimpleObjectWriter()
w = newSimpleObjectWriter()
payloadSz := srcObj.PayloadSize() payloadSz := srcObj.PayloadSize()
off := payloadSz / 3 off := payloadSz / 3
@ -814,7 +893,7 @@ func TestGetRemoteSmall(t *testing.T) {
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, payload[off:off+ln], w.object().Payload()) require.Equal(t, payload[off:off+ln], w.Object().Payload())
}) })
}) })
@ -855,6 +934,8 @@ func TestGetRemoteSmall(t *testing.T) {
}, },
}) })
testHeadVirtual(svc, addr, splitInfo)
p := newPrm(false, nil) p := newPrm(false, nil)
p.WithAddress(addr) p.WithAddress(addr)
@ -917,11 +998,12 @@ func TestGetRemoteSmall(t *testing.T) {
}, },
}) })
testHeadVirtual(svc, addr, splitInfo)
headSvc := newTestClient() headSvc := newTestClient()
headSvc.addResult(preRightAddr, nil, object.ErrNotFound) headSvc.addResult(preRightAddr, nil, object.ErrNotFound)
svc.headSvc = headSvc
p := newPrm(false, newSimpleObjectWriter()) p := newPrm(false, NewSimpleObjectWriter())
p.WithAddress(addr) p.WithAddress(addr)
err := svc.Get(ctx, p) err := svc.Get(ctx, p)
@ -987,18 +1069,18 @@ func TestGetRemoteSmall(t *testing.T) {
}, },
}) })
svc.headSvc = c2 testHeadVirtual(svc, addr, splitInfo)
w := newSimpleObjectWriter() w := NewSimpleObjectWriter()
p := newPrm(false, w) p := newPrm(false, w)
p.WithAddress(addr) p.WithAddress(addr)
err := svc.Get(ctx, p) err := svc.Get(ctx, p)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, srcObj.Object(), w.object()) require.Equal(t, srcObj.Object(), w.Object())
w = newSimpleObjectWriter() w = NewSimpleObjectWriter()
payloadSz := srcObj.PayloadSize() payloadSz := srcObj.PayloadSize()
off := payloadSz / 3 off := payloadSz / 3
@ -1009,9 +1091,9 @@ func TestGetRemoteSmall(t *testing.T) {
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, payload[off:off+ln], w.object().Payload()) require.Equal(t, payload[off:off+ln], w.Object().Payload())
w = newSimpleObjectWriter() w = NewSimpleObjectWriter()
off = payloadSz - 2 off = payloadSz - 2
ln = 1 ln = 1
@ -1020,7 +1102,7 @@ func TestGetRemoteSmall(t *testing.T) {
err = svc.GetRange(ctx, rngPrm) err = svc.GetRange(ctx, rngPrm)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, payload[off:off+ln], w.object().Payload()) require.Equal(t, payload[off:off+ln], w.Object().Payload())
}) })
}) })
}) })

View file

@ -10,7 +10,7 @@ import (
func (exec *execCtx) executeLocal() { func (exec *execCtx) executeLocal() {
var err error var err error
exec.collectedObject, err = exec.svc.localStorage.Get(exec.prm) exec.collectedObject, err = exec.svc.localStorage.get(exec)
var errSplitInfo *objectSDK.SplitInfoError var errSplitInfo *objectSDK.SplitInfoError

View file

@ -31,6 +31,11 @@ type RangeHashPrm struct {
rngs []*objectSDK.Range rngs []*objectSDK.Range
} }
// HeadPrm groups parameters of Head service call.
type HeadPrm struct {
commonPrm
}
type commonPrm struct { type commonPrm struct {
objWriter ObjectWriter objWriter ObjectWriter
@ -50,9 +55,15 @@ type ChunkWriter interface {
WriteChunk([]byte) error WriteChunk([]byte) error
} }
// HeaderWriter is an interface of target component
// to write object header.
type HeaderWriter interface {
WriteHeader(*object.Object) error
}
// ObjectWriter is an interface of target component to write object. // ObjectWriter is an interface of target component to write object.
type ObjectWriter interface { type ObjectWriter interface {
WriteHeader(*object.Object) error HeaderWriter
ChunkWriter ChunkWriter
} }
@ -71,9 +82,9 @@ func (p *commonPrm) SetRemoteCallOptions(opts ...client.CallOption) {
p.callOpts = opts p.callOpts = opts
} }
// SetObjectWriter sets target component to write the object payload range. // SetChunkWriter sets target component to write the object payload range.
func (p *RangePrm) SetChunkWriter(w ChunkWriter) { func (p *RangePrm) SetChunkWriter(w ChunkWriter) {
p.objWriter = &rangeWriter{ p.objWriter = &partWriter{
chunkWriter: w, chunkWriter: w,
} }
} }
@ -97,3 +108,10 @@ func (p *RangeHashPrm) SetHashGenerator(v func() hash.Hash) {
func (p *commonPrm) SetCommonParameters(common *util.CommonPrm) { func (p *commonPrm) SetCommonParameters(common *util.CommonPrm) {
p.common = common p.common = common
} }
// SetHeaderWriter sets target component to write the object header.
func (p *HeadPrm) SetHeaderWriter(w HeaderWriter) {
p.objWriter = &partWriter{
headWriter: w,
}
}

View file

@ -20,7 +20,7 @@ func (exec *execCtx) processNode(ctx context.Context, addr *network.Address) boo
return true return true
} }
obj, err := client.GetObject(ctx, exec.prm) obj, err := client.getObject(exec)
var errSplitInfo *objectSDK.SplitInfoError var errSplitInfo *objectSDK.SplitInfoError

View file

@ -1,7 +1,6 @@
package getsvc package getsvc
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"github.com/nspcc-dev/neofs-api-go/pkg/client" "github.com/nspcc-dev/neofs-api-go/pkg/client"
@ -9,7 +8,6 @@ import (
"github.com/nspcc-dev/neofs-node/pkg/core/object" "github.com/nspcc-dev/neofs-node/pkg/core/object"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
"github.com/nspcc-dev/neofs-node/pkg/network/cache" "github.com/nspcc-dev/neofs-node/pkg/network/cache"
headsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/head"
"github.com/nspcc-dev/neofs-node/pkg/services/object/util" "github.com/nspcc-dev/neofs-node/pkg/services/object/util"
"github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement"
"github.com/nspcc-dev/neofs-node/pkg/util/logger" "github.com/nspcc-dev/neofs-node/pkg/util/logger"
@ -25,7 +23,7 @@ type Service struct {
type Option func(*cfg) type Option func(*cfg)
type getClient interface { type getClient interface {
GetObject(context.Context, RangePrm) (*objectSDK.Object, error) getObject(*execCtx) (*objectSDK.Object, error)
} }
type cfg struct { type cfg struct {
@ -33,12 +31,8 @@ type cfg struct {
log *logger.Logger log *logger.Logger
headSvc interface {
head(context.Context, Prm) (*object.Object, error)
}
localStorage interface { localStorage interface {
Get(RangePrm) (*object.Object, error) get(*execCtx) (*object.Object, error)
} }
clientCache interface { clientCache interface {
@ -54,7 +48,6 @@ func defaultCfg() *cfg {
return &cfg{ return &cfg{
assembly: true, assembly: true,
log: zap.L(), log: zap.L(),
headSvc: new(headSvcWrapper),
localStorage: new(storageEngineWrapper), localStorage: new(storageEngineWrapper),
clientCache: new(clientCacheWrapper), clientCache: new(clientCacheWrapper),
} }
@ -117,10 +110,3 @@ func WithTraverserGenerator(t *util.TraverserGenerator) Option {
c.traverserGenerator = t c.traverserGenerator = t
} }
} }
// WithHeadService returns option to set the utility serving object.Head.
func WithHeadService(svc *headsvc.Service) Option {
return func(c *cfg) {
c.headSvc.(*headSvcWrapper).svc = svc
}
}

View file

@ -1,7 +1,6 @@
package getsvc package getsvc
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"hash" "hash"
@ -10,10 +9,9 @@ import (
"github.com/nspcc-dev/neofs-node/pkg/core/object" "github.com/nspcc-dev/neofs-node/pkg/core/object"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
"github.com/nspcc-dev/neofs-node/pkg/network/cache" "github.com/nspcc-dev/neofs-node/pkg/network/cache"
headsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/head"
) )
type simpleObjectWriter struct { type SimpleObjectWriter struct {
obj *object.RawObject obj *object.RawObject
pld []byte pld []byte
@ -33,13 +31,11 @@ type storageEngineWrapper struct {
engine *engine.StorageEngine engine *engine.StorageEngine
} }
type headSvcWrapper struct { type partWriter struct {
svc *headsvc.Service
}
type rangeWriter struct {
ObjectWriter ObjectWriter
headWriter HeaderWriter
chunkWriter ChunkWriter chunkWriter ChunkWriter
} }
@ -47,13 +43,13 @@ type hasherWrapper struct {
hash hash.Hash hash hash.Hash
} }
func newSimpleObjectWriter() *simpleObjectWriter { func NewSimpleObjectWriter() *SimpleObjectWriter {
return &simpleObjectWriter{ return &SimpleObjectWriter{
obj: object.NewRaw(), obj: object.NewRaw(),
} }
} }
func (s *simpleObjectWriter) WriteHeader(obj *object.Object) error { func (s *SimpleObjectWriter) WriteHeader(obj *object.Object) error {
s.obj = object.NewRawFromObject(obj) s.obj = object.NewRawFromObject(obj)
s.pld = make([]byte, 0, obj.PayloadSize()) s.pld = make([]byte, 0, obj.PayloadSize())
@ -61,16 +57,12 @@ func (s *simpleObjectWriter) WriteHeader(obj *object.Object) error {
return nil return nil
} }
func (s *simpleObjectWriter) WriteChunk(p []byte) error { func (s *SimpleObjectWriter) WriteChunk(p []byte) error {
s.pld = append(s.pld, p...) s.pld = append(s.pld, p...)
return nil return nil
} }
func (s *simpleObjectWriter) Close() error { func (s *SimpleObjectWriter) Object() *object.Object {
return nil
}
func (s *simpleObjectWriter) object() *object.Object {
if len(s.pld) > 0 { if len(s.pld) > 0 {
s.obj.SetPayload(s.pld) s.obj.SetPayload(s.pld)
} }
@ -86,16 +78,24 @@ func (c *clientCacheWrapper) get(key *ecdsa.PrivateKey, addr string) (getClient,
}, err }, err
} }
func (c *clientWrapper) GetObject(ctx context.Context, p RangePrm) (*objectSDK.Object, error) { func (c *clientWrapper) getObject(exec *execCtx) (*objectSDK.Object, error) {
if exec.headOnly() {
return c.client.GetObjectHeader(exec.context(),
new(client.ObjectHeaderParams).
WithAddress(exec.address()).
WithRawFlag(exec.isRaw()),
exec.callOptions()...,
)
}
// we don't specify payload writer because we accumulate // we don't specify payload writer because we accumulate
// the object locally (even huge). // the object locally (even huge).
if p.rng != nil { if rng := exec.ctxRange(); rng != nil {
data, err := c.client.ObjectPayloadRangeData(ctx, data, err := c.client.ObjectPayloadRangeData(exec.context(),
new(client.RangeDataParams). new(client.RangeDataParams).
WithAddress(p.Address()). WithAddress(exec.address()).
WithRange(p.rng). WithRange(rng).
WithRaw(p.RawFlag()), WithRaw(exec.isRaw()),
p.callOpts..., exec.callOptions()...,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -103,22 +103,30 @@ func (c *clientWrapper) GetObject(ctx context.Context, p RangePrm) (*objectSDK.O
return payloadOnlyObject(data), nil return payloadOnlyObject(data), nil
} else { } else {
// we don't specify payload writer because we accumulate return c.client.GetObject(exec.context(),
// the object locally (even huge).
return c.client.GetObject(ctx,
new(client.GetObjectParams). new(client.GetObjectParams).
WithAddress(p.Address()). WithAddress(exec.address()).
WithRawFlag(p.RawFlag()), WithRawFlag(exec.isRaw()),
p.callOpts..., exec.callOptions()...,
) )
} }
} }
func (e *storageEngineWrapper) Get(p RangePrm) (*object.Object, error) { func (e *storageEngineWrapper) get(exec *execCtx) (*object.Object, error) {
if p.rng != nil { if exec.headOnly() {
r, err := e.engine.Head(new(engine.HeadPrm).
WithAddress(exec.address()).
WithRaw(exec.isRaw()),
)
if err != nil {
return nil, err
}
return r.Header(), nil
} else if rng := exec.ctxRange(); rng != nil {
r, err := e.engine.GetRange(new(engine.RngPrm). r, err := e.engine.GetRange(new(engine.RngPrm).
WithAddress(p.Address()). WithAddress(exec.address()).
WithPayloadRange(p.rng), WithPayloadRange(rng),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -127,7 +135,7 @@ func (e *storageEngineWrapper) Get(p RangePrm) (*object.Object, error) {
return r.Object(), nil return r.Object(), nil
} else { } else {
r, err := e.engine.Get(new(engine.GetPrm). r, err := e.engine.Get(new(engine.GetPrm).
WithAddress(p.Address()), WithAddress(exec.address()),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -137,22 +145,12 @@ func (e *storageEngineWrapper) Get(p RangePrm) (*object.Object, error) {
} }
} }
func (s *headSvcWrapper) head(ctx context.Context, p Prm) (*object.Object, error) { func (w *partWriter) WriteChunk(p []byte) error {
r, err := s.svc.Head(ctx, new(headsvc.Prm). return w.chunkWriter.WriteChunk(p)
WithAddress(p.Address()).
WithCommonPrm(p.common).
Short(false),
)
if err != nil {
return nil, err
}
return r.Header(), nil
} }
func (w *rangeWriter) WriteChunk(p []byte) error { func (w *partWriter) WriteHeader(o *object.Object) error {
return w.chunkWriter.WriteChunk(p) return w.headWriter.WriteHeader(o)
} }
func payloadOnlyObject(payload []byte) *objectSDK.Object { func payloadOnlyObject(payload []byte) *objectSDK.Object {

View file

@ -91,6 +91,28 @@ func (s *Service) GetRangeHash(ctx context.Context, req *objectV2.GetRangeHashRe
return toHashResponse(req.GetBody().GetType(), res), nil return toHashResponse(req.GetBody().GetType(), res), nil
} }
// Head serves NeoFS API v2 compatible HEAD requests.
func (s *Service) Head(ctx context.Context, req *objectV2.HeadRequest) (*objectV2.HeadResponse, error) {
resp := new(objectV2.HeadResponse)
resp.SetBody(new(objectV2.HeadResponseBody))
p, err := s.toHeadPrm(req, resp)
if err != nil {
return nil, err
}
err = s.svc.Head(ctx, *p)
var splitErr *object.SplitInfoError
if errors.As(err, &splitErr) {
setSplitInfoHeadResponse(splitErr.SplitInfo(), resp)
err = nil
}
return resp, err
}
func WithInternalService(v *getsvc.Service) Option { func WithInternalService(v *getsvc.Service) Option {
return func(c *cfg) { return func(c *cfg) {
c.svc = v c.svc = v

View file

@ -6,11 +6,12 @@ import (
"github.com/nspcc-dev/neofs-api-go/pkg" "github.com/nspcc-dev/neofs-api-go/pkg"
"github.com/nspcc-dev/neofs-api-go/pkg/client" "github.com/nspcc-dev/neofs-api-go/pkg/client"
"github.com/nspcc-dev/neofs-api-go/pkg/object" objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
"github.com/nspcc-dev/neofs-api-go/pkg/token" "github.com/nspcc-dev/neofs-api-go/pkg/token"
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object" objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-node/pkg/core/object"
objectSvc "github.com/nspcc-dev/neofs-node/pkg/services/object" objectSvc "github.com/nspcc-dev/neofs-node/pkg/services/object"
getsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/get" getsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/get"
"github.com/nspcc-dev/neofs-node/pkg/services/object/util" "github.com/nspcc-dev/neofs-node/pkg/services/object/util"
@ -30,7 +31,7 @@ func (s *Service) toPrm(req *objectV2.GetRequest, stream objectSvc.GetObjectStre
p.SetPrivateKey(key) p.SetPrivateKey(key)
body := req.GetBody() body := req.GetBody()
p.WithAddress(object.NewAddressFromV2(body.GetAddress())) p.WithAddress(objectSDK.NewAddressFromV2(body.GetAddress()))
p.WithRawFlag(body.GetRaw()) p.WithRawFlag(body.GetRaw())
p.SetRemoteCallOptions(remoteCallOptionsFromMeta(meta)...) p.SetRemoteCallOptions(remoteCallOptionsFromMeta(meta)...)
p.SetObjectWriter(&streamObjectWriter{stream}) p.SetObjectWriter(&streamObjectWriter{stream})
@ -53,11 +54,11 @@ func (s *Service) toRangePrm(req *objectV2.GetRangeRequest, stream objectSvc.Get
p.SetPrivateKey(key) p.SetPrivateKey(key)
body := req.GetBody() body := req.GetBody()
p.WithAddress(object.NewAddressFromV2(body.GetAddress())) p.WithAddress(objectSDK.NewAddressFromV2(body.GetAddress()))
p.WithRawFlag(body.GetRaw()) p.WithRawFlag(body.GetRaw())
p.SetRemoteCallOptions(remoteCallOptionsFromMeta(meta)...) p.SetRemoteCallOptions(remoteCallOptionsFromMeta(meta)...)
p.SetChunkWriter(&streamObjectRangeWriter{stream}) p.SetChunkWriter(&streamObjectRangeWriter{stream})
p.SetRange(object.NewRangeFromV2(body.GetRange())) p.SetRange(objectSDK.NewRangeFromV2(body.GetRange()))
p.SetCommonParameters(commonParameters(meta)) p.SetCommonParameters(commonParameters(meta))
return p, nil return p, nil
@ -75,15 +76,15 @@ func (s *Service) toHashRangePrm(req *objectV2.GetRangeHashRequest) (*getsvc.Ran
p.SetPrivateKey(key) p.SetPrivateKey(key)
body := req.GetBody() body := req.GetBody()
p.WithAddress(object.NewAddressFromV2(body.GetAddress())) p.WithAddress(objectSDK.NewAddressFromV2(body.GetAddress()))
p.SetRemoteCallOptions(remoteCallOptionsFromMeta(meta)...) p.SetRemoteCallOptions(remoteCallOptionsFromMeta(meta)...)
p.SetCommonParameters(commonParameters(meta)) p.SetCommonParameters(commonParameters(meta))
rngsV2 := body.GetRanges() rngsV2 := body.GetRanges()
rngs := make([]*object.Range, 0, len(rngsV2)) rngs := make([]*objectSDK.Range, 0, len(rngsV2))
for i := range rngsV2 { for i := range rngsV2 {
rngs = append(rngs, object.NewRangeFromV2(rngsV2[i])) rngs = append(rngs, objectSDK.NewRangeFromV2(rngsV2[i]))
} }
p.SetRangeList(rngs) p.SetRangeList(rngs)
@ -104,6 +105,46 @@ func (s *Service) toHashRangePrm(req *objectV2.GetRangeHashRequest) (*getsvc.Ran
return p, nil return p, nil
} }
type headResponseWriter struct {
mainOnly bool
body *objectV2.HeadResponseBody
}
func (w *headResponseWriter) WriteHeader(hdr *object.Object) error {
if w.mainOnly {
w.body.SetHeaderPart(toShortObjectHeader(hdr))
} else {
w.body.SetHeaderPart(toFullObjectHeader(hdr))
}
return nil
}
func (s *Service) toHeadPrm(req *objectV2.HeadRequest, resp *objectV2.HeadResponse) (*getsvc.HeadPrm, error) {
meta := req.GetMetaHeader()
key, err := s.keyStorage.GetKey(token.NewSessionTokenFromV2(meta.GetSessionToken()))
if err != nil {
return nil, err
}
p := new(getsvc.HeadPrm)
p.SetPrivateKey(key)
body := req.GetBody()
p.WithAddress(objectSDK.NewAddressFromV2(body.GetAddress()))
p.WithRawFlag(body.GetRaw())
p.SetRemoteCallOptions(remoteCallOptionsFromMeta(meta)...)
p.SetHeaderWriter(&headResponseWriter{
mainOnly: body.GetMainOnly(),
body: resp.GetBody(),
})
p.SetCommonParameters(commonParameters(meta))
return p, nil
}
// can be shared accross all services // can be shared accross all services
func remoteCallOptionsFromMeta(meta *session.RequestMetaHeader) []client.CallOption { func remoteCallOptionsFromMeta(meta *session.RequestMetaHeader) []client.CallOption {
xHdrs := meta.GetXHeaders() xHdrs := meta.GetXHeaders()
@ -128,7 +169,7 @@ func commonParameters(meta *session.RequestMetaHeader) *util.CommonPrm {
WithLocalOnly(meta.GetTTL() <= 1) WithLocalOnly(meta.GetTTL() <= 1)
} }
func splitInfoResponse(info *object.SplitInfo) *objectV2.GetResponse { func splitInfoResponse(info *objectSDK.SplitInfo) *objectV2.GetResponse {
resp := new(objectV2.GetResponse) resp := new(objectV2.GetResponse)
body := new(objectV2.GetResponseBody) body := new(objectV2.GetResponseBody)
@ -139,7 +180,7 @@ func splitInfoResponse(info *object.SplitInfo) *objectV2.GetResponse {
return resp return resp
} }
func splitInfoRangeResponse(info *object.SplitInfo) *objectV2.GetRangeResponse { func splitInfoRangeResponse(info *objectSDK.SplitInfo) *objectV2.GetRangeResponse {
resp := new(objectV2.GetRangeResponse) resp := new(objectV2.GetRangeResponse)
body := new(objectV2.GetRangeResponseBody) body := new(objectV2.GetRangeResponseBody)
@ -150,6 +191,10 @@ func splitInfoRangeResponse(info *object.SplitInfo) *objectV2.GetRangeResponse {
return resp return resp
} }
func setSplitInfoHeadResponse(info *objectSDK.SplitInfo, resp *objectV2.HeadResponse) {
resp.GetBody().SetHeaderPart(info.ToV2())
}
func toHashResponse(typ refs.ChecksumType, res *getsvc.RangeHashRes) *objectV2.GetRangeHashResponse { func toHashResponse(typ refs.ChecksumType, res *getsvc.RangeHashRes) *objectV2.GetRangeHashResponse {
resp := new(objectV2.GetRangeHashResponse) resp := new(objectV2.GetRangeHashResponse)
@ -161,3 +206,26 @@ func toHashResponse(typ refs.ChecksumType, res *getsvc.RangeHashRes) *objectV2.G
return resp return resp
} }
func toFullObjectHeader(hdr *object.Object) objectV2.GetHeaderPart {
obj := hdr.ToV2()
hs := new(objectV2.HeaderWithSignature)
hs.SetHeader(obj.GetHeader())
hs.SetSignature(obj.GetSignature())
return hs
}
func toShortObjectHeader(hdr *object.Object) objectV2.GetHeaderPart {
hdrV2 := hdr.ToV2().GetHeader()
sh := new(objectV2.ShortHeader)
sh.SetOwnerID(hdrV2.GetOwnerID())
sh.SetCreationEpoch(hdrV2.GetCreationEpoch())
sh.SetPayloadLength(hdrV2.GetPayloadLength())
sh.SetVersion(hdrV2.GetVersion())
sh.SetObjectType(hdrV2.GetObjectType())
return sh
}