[#1324] services/tree: Implement Object Tree Service

Object Tree Service allows changing trees assotiated with
the container in runtime.

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
support/v0.30
Evgenii Stratonikov 2022-04-22 16:30:20 +03:00 committed by fyrchik
parent 46f4ce2773
commit 62154da17c
18 changed files with 4001 additions and 0 deletions

View File

@ -0,0 +1,39 @@
package treeconfig
import (
"github.com/nspcc-dev/neofs-node/cmd/neofs-node/config"
)
// GRPCConfig is a wrapper over "grpc" config section which provides access
// to gRPC configuration of control service.
type GRPCConfig struct {
cfg *config.Config
}
const (
subsection = "tree"
grpcSubsection = "grpc"
// GRPCEndpointDefault is a default endpoint of gRPC Control service.
GRPCEndpointDefault = ""
)
// GRPC returns structure that provides access to "grpc" subsection of
// "tree" section.
func GRPC(c *config.Config) GRPCConfig {
return GRPCConfig{
c.Sub(subsection).Sub(grpcSubsection),
}
}
// Endpoint returns value of "endpoint" config parameter.
//
// Returns GRPCEndpointDefault if value is not a non-empty string.
func (g GRPCConfig) Endpoint() string {
v := config.String(g.cfg, "endpoint")
if v != "" {
return v
}
return GRPCEndpointDefault
}

View File

@ -0,0 +1,30 @@
package treeconfig_test
import (
"testing"
"github.com/nspcc-dev/neofs-node/cmd/neofs-node/config"
configtest "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/test"
treeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/tree"
"github.com/stretchr/testify/require"
)
func TestControlSection(t *testing.T) {
t.Run("defaults", func(t *testing.T) {
empty := configtest.EmptyConfig()
require.Equal(t, treeconfig.GRPCEndpointDefault, treeconfig.GRPC(empty).Endpoint())
})
const path = "../../../../config/example/node"
var fileConfigTest = func(c *config.Config) {
require.Equal(t, "127.0.0.1:8091", treeconfig.GRPC(c).Endpoint())
}
configtest.ForEachFileType(path, fileConfigTest)
t.Run("ENV", func(t *testing.T) {
configtest.ForEnvFileType(path, fileConfigTest)
})
}

View File

@ -81,6 +81,7 @@ func initApp(c *cfg) {
initAndLog(c, "pprof", initProfiler)
initAndLog(c, "prometheus", initMetrics)
initAndLog(c, "control", initControlService)
initAndLog(c, "tree", initTreeService)
initAndLog(c, "storage engine", func(c *cfg) {
fatalOnErr(c.cfgObject.cfgLocalStorage.localStorage.Open())

View File

@ -0,0 +1,41 @@
package main
import (
"context"
"net"
treeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/tree"
"github.com/nspcc-dev/neofs-node/pkg/services/tree"
"google.golang.org/grpc"
)
func initTreeService(c *cfg) {
endpoint := treeconfig.GRPC(c.appCfg).Endpoint()
if endpoint == treeconfig.GRPCEndpointDefault {
return
}
treeSvc := tree.New(
tree.WithContainerSource(c.cfgObject.cnrSource),
tree.WithNetmapSource(c.netMapSource),
tree.WithPrivateKey(&c.key.PrivateKey),
tree.WithLogger(c.log),
tree.WithStorage(c.cfgObject.cfgLocalStorage.localStorage))
treeServer := grpc.NewServer()
c.onShutdown(func() {
stopGRPC("NeoFS Tree Service API", treeServer, c.log)
treeSvc.Shutdown()
})
lis, err := net.Listen("tcp", endpoint)
fatalOnErr(err)
tree.RegisterTreeServiceServer(treeServer, treeSvc)
c.workers = append(c.workers, newWorkerFromFunc(func(ctx context.Context) {
treeSvc.Start(ctx)
fatalOnErr(treeServer.Serve(lis))
}))
}

View File

@ -46,6 +46,9 @@ NEOFS_GRPC_1_TLS_ENABLED=false
NEOFS_CONTROL_AUTHORIZED_KEYS="035839e45d472a3b7769a2a1bd7d54c4ccd4943c3b40f547870e83a8fcbfb3ce11 028f42cfcb74499d7b15b35d9bff260a1c8d27de4f446a627406a382d8961486d6"
NEOFS_CONTROL_GRPC_ENDPOINT=localhost:8090
# Tree service section
NEOFS_TREE_GRPC_ENDPOINT=127.0.0.1:8091
# Contracts section
NEOFS_CONTRACTS_BALANCE=5263abba1abedbf79bb57f3e40b50b4425d2d6cd
NEOFS_CONTRACTS_CONTAINER=5d084790d7aa36cea7b53fe897380dab11d2cd3c

View File

@ -84,6 +84,11 @@
"endpoint": "localhost:8090"
}
},
"tree": {
"grpc": {
"endpoint": "127.0.0.1:8091"
}
},
"contracts": {
"balance": "5263abba1abedbf79bb57f3e40b50b4425d2d6cd",
"container": "5d084790d7aa36cea7b53fe897380dab11d2cd3c",

View File

@ -66,6 +66,10 @@ control:
grpc:
endpoint: localhost:8090 # endpoint that is listened by the Control Service
tree:
grpc:
endpoint: 127.0.0.1:8091 # endpoint that is listened by the Tree Service
contracts: # side chain NEOFS contract script hashes; optional, override values retrieved from NNS contract
balance: 5263abba1abedbf79bb57f3e40b50b4425d2d6cd
container: 5d084790d7aa36cea7b53fe897380dab11d2cd3c

8
go.mod
View File

@ -38,7 +38,10 @@ require (
gopkg.in/yaml.v3 v3.0.1
)
require google.golang.org/api v0.44.0
require (
cloud.google.com/go v0.81.0 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521073959-f0d4d129b7f1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.22.0-beta // indirect
@ -47,8 +50,10 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
@ -87,12 +92,15 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
github.com/twmb/murmur3 v1.1.5 // indirect
github.com/urfave/cli v1.22.5 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

6
go.sum
View File

@ -17,6 +17,7 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
@ -161,6 +162,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -559,6 +561,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -687,6 +690,7 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -871,6 +875,7 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -878,6 +883,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=

View File

@ -0,0 +1,59 @@
package tree
import (
"crypto/ecdsa"
"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/pilorama"
"go.uber.org/zap"
)
type cfg struct {
log *zap.Logger
key *ecdsa.PrivateKey
nmSource netmap.Source
cnrSource container.Source
forest pilorama.Forest
}
// Option represents configuration option for a tree service.
type Option func(*cfg)
// WithContainerSource sets a container source for a tree service.
// This option is required.
func WithContainerSource(src container.Source) Option {
return func(c *cfg) {
c.cnrSource = src
}
}
// WithNetmapSource sets a netmap source for a tree service.
// This option is required.
func WithNetmapSource(src netmap.Source) Option {
return func(c *cfg) {
c.nmSource = src
}
}
// WithPrivateKey sets a netmap source for a tree service.
// This option is required.
func WithPrivateKey(key *ecdsa.PrivateKey) Option {
return func(c *cfg) {
c.key = key
}
}
// WithLogger sets logger for a tree service.
func WithLogger(log *zap.Logger) Option {
return func(c *cfg) {
c.log = log
}
}
// WithStorage sets tree storage for a service.
func WithStorage(s pilorama.Forest) Option {
return func(c *cfg) {
c.forest = s
}
}

View File

@ -0,0 +1,145 @@
package tree
import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"time"
clientcore "github.com/nspcc-dev/neofs-node/pkg/core/client"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/pilorama"
"github.com/nspcc-dev/neofs-node/pkg/network"
"github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement"
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
netmapSDK "github.com/nspcc-dev/neofs-sdk-go/netmap"
"go.uber.org/zap"
"google.golang.org/api/option"
"google.golang.org/api/transport/grpc"
)
type movePair struct {
cid cidSDK.ID
treeID string
op *pilorama.LogMove
}
const (
defaultReplicatorCapacity = 64
defaultReplicatorTimeout = time.Second * 2
)
func (s *Service) replicateLoop(ctx context.Context) {
for {
select {
case <-s.closeCh:
case <-ctx.Done():
return
case op := <-s.replicateCh:
ctx, cancel := context.WithTimeout(ctx, defaultReplicatorTimeout)
err := s.replicate(ctx, op)
cancel()
if err != nil {
s.log.Error("error during replication",
zap.String("err", err.Error()),
zap.Stringer("cid", op.cid),
zap.String("treeID", op.treeID))
}
}
}
}
func (s *Service) replicate(ctx context.Context, op movePair) error {
req := newApplyRequest(&op)
// TODO(@fyrchik): #1328 access control
//err := signature.SignDataWithHandler(s.key, req, func(key, sign []byte) {
// req.Signature = &Signature{
// Key: key,
// Sign: sign,
// }
//})
//if err != nil {
// return fmt.Errorf("can't sign data: %w", err)
//}
nodes, err := s.getContainerNodes(op.cid)
if err != nil {
return fmt.Errorf("can't get container nodes: %w", err)
}
var node clientcore.NodeInfo
for _, n := range nodes {
var lastErr error
n.IterateNetworkEndpoints(func(addr string) bool {
cc, err := grpc.Dial(ctx, option.WithEndpoint(addr))
if err != nil {
lastErr = err
return false
}
// TODO cache clients
c := NewTreeServiceClient(cc)
_, lastErr = c.Apply(ctx, req)
return lastErr == nil
})
if lastErr != nil {
s.log.Warn("failed to sent update to the node",
zap.String("last_error", lastErr.Error()),
zap.String("address", network.StringifyGroup(node.AddressGroup())),
zap.String("key", base64.StdEncoding.EncodeToString(node.PublicKey())))
}
}
return nil
}
func (s *Service) pushToQueue(cid cidSDK.ID, treeID string, op *pilorama.LogMove) {
s.replicateCh <- movePair{
cid: cid,
treeID: treeID,
op: op,
}
}
func (s *Service) getContainerNodes(cid cidSDK.ID) ([]netmapSDK.NodeInfo, error) {
nm, err := s.nmSource.GetNetMap(0)
if err != nil {
return nil, fmt.Errorf("can't get netmap: %w", err)
}
cnr, err := s.cnrSource.Get(cid)
if err != nil {
return nil, fmt.Errorf("can't get container: %w", err)
}
policy := cnr.Value.PlacementPolicy()
rawCID := make([]byte, sha256.Size)
cid.Encode(rawCID)
nodes, err := nm.ContainerNodes(policy, rawCID)
if err != nil {
return nil, err
}
return placement.FlattenNodes(nodes), nil
}
func newApplyRequest(op *movePair) *ApplyRequest {
rawCID := make([]byte, sha256.Size)
op.cid.Encode(rawCID)
return &ApplyRequest{
Body: &ApplyRequest_Body{
ContainerId: rawCID,
TreeId: op.treeID,
Operation: &LogMove{
ParentId: op.op.Parent,
Meta: op.op.Meta.Bytes(),
ChildId: op.op.Child,
},
},
}
}

View File

@ -0,0 +1,294 @@
package tree
import (
"bytes"
"context"
"errors"
"fmt"
"github.com/nspcc-dev/neofs-api-go/v2/signature"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/pilorama"
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
"go.uber.org/zap"
)
// Service represents tree-service capable of working with multiple
// instances of CRDT trees.
type Service struct {
cfg
replicateCh chan movePair
closeCh chan struct{}
}
var _ TreeServiceServer = (*Service)(nil)
// New creates new tree service instance.
func New(opts ...Option) *Service {
var s Service
for i := range opts {
opts[i](&s.cfg)
}
if s.log == nil {
s.log = zap.NewNop()
}
s.closeCh = make(chan struct{})
s.replicateCh = make(chan movePair, defaultReplicatorCapacity)
return &s
}
// Start starts the service.
func (s *Service) Start(ctx context.Context) {
go s.replicateLoop(ctx)
}
// Shutdown shutdowns the service.
func (s *Service) Shutdown() {
close(s.closeCh)
}
func (s *Service) Add(_ context.Context, req *AddRequest) (*AddResponse, error) {
b := req.GetBody()
var cid cidSDK.ID
if err := cid.Decode(b.GetContainerId()); err != nil {
return nil, err
}
err := s.verifyClient(req, cid, req.GetSignature().GetKey())
if err != nil {
return nil, err
}
log, err := s.forest.TreeMove(cid, b.GetTreeId(), &pilorama.Move{
Parent: b.GetParentId(),
Child: pilorama.RootID,
Meta: pilorama.Meta{Items: constructMeta(b.GetMeta())},
})
if err != nil {
return nil, err
}
s.pushToQueue(cid, b.GetTreeId(), log)
return &AddResponse{
Body: &AddResponse_Body{
NodeId: log.Child,
},
}, nil
}
func (s *Service) AddByPath(_ context.Context, req *AddByPathRequest) (*AddByPathResponse, error) {
b := req.GetBody()
var cid cidSDK.ID
if err := cid.Decode(b.GetContainerId()); err != nil {
return nil, err
}
err := s.verifyClient(req, cid, req.GetSignature().GetKey())
if err != nil {
return nil, err
}
meta := constructMeta(b.GetMeta())
attr := b.GetPathAttribute()
if len(attr) == 0 {
attr = pilorama.AttributeFilename
}
logs, err := s.forest.TreeAddByPath(cid, b.GetTreeId(), attr, b.GetPath(), meta)
if err != nil {
return nil, err
}
for i := range logs {
s.pushToQueue(cid, b.GetTreeId(), &logs[i])
}
nodes := make([]uint64, len(logs))
nodes[0] = logs[len(logs)-1].Child
for i, l := range logs[:len(logs)-1] {
nodes[i+1] = l.Child
}
return &AddByPathResponse{
Body: &AddByPathResponse_Body{
Nodes: nodes,
},
}, nil
}
func (s *Service) Remove(_ context.Context, req *RemoveRequest) (*RemoveResponse, error) {
b := req.GetBody()
var cid cidSDK.ID
if err := cid.Decode(b.GetContainerId()); err != nil {
return nil, err
}
err := s.verifyClient(req, cid, req.GetSignature().GetKey())
if err != nil {
return nil, err
}
if b.GetNodeId() == pilorama.RootID {
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{
Parent: pilorama.TrashID,
Child: b.GetNodeId(),
})
if err != nil {
return nil, err
}
s.pushToQueue(cid, b.GetTreeId(), log)
return new(RemoveResponse), nil
}
// Move applies client operation to the specified tree and pushes in queue
// for replication on other nodes.
func (s *Service) Move(_ context.Context, req *MoveRequest) (*MoveResponse, error) {
b := req.GetBody()
var cid cidSDK.ID
if err := cid.Decode(b.GetContainerId()); err != nil {
return nil, err
}
err := s.verifyClient(req, cid, req.GetSignature().GetKey())
if err != nil {
return nil, err
}
if b.GetNodeId() == pilorama.RootID {
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{
Parent: b.GetParentId(),
Child: b.GetNodeId(),
Meta: pilorama.Meta{Items: constructMeta(b.GetMeta())},
})
if err != nil {
return nil, err
}
s.pushToQueue(cid, b.GetTreeId(), log)
return new(MoveResponse), nil
}
func (s *Service) GetNodeByPath(_ context.Context, req *GetNodeByPathRequest) (*GetNodeByPathResponse, error) {
b := req.GetBody()
var cid cidSDK.ID
if err := cid.Decode(b.GetContainerId()); err != nil {
return nil, err
}
attr := b.GetPathAttribute()
if len(attr) == 0 {
attr = pilorama.AttributeFilename
}
nodes, err := s.forest.TreeGetByPath(cid, b.GetTreeId(), attr, b.GetPath(), b.GetLatestOnly())
if err != nil {
return nil, err
}
info := make([]*GetNodeByPathResponse_Info, 0, len(nodes))
for _, node := range nodes {
m, err := s.forest.TreeGetMeta(cid, b.GetTreeId(), node)
if err != nil {
return nil, err
}
var x GetNodeByPathResponse_Info
x.NodeId = node
x.Timestamp = m.Time
for _, kv := range m.Items {
needAttr := b.AllAttributes
if !needAttr {
for _, attr := range b.GetAttributes() {
if kv.Key == attr {
needAttr = true
break
}
}
}
if needAttr {
x.Meta = append(x.Meta, &KeyValue{
Key: kv.Key,
Value: kv.Value,
})
}
}
info = append(info, &x)
}
return &GetNodeByPathResponse{
Body: &GetNodeByPathResponse_Body{
Nodes: info,
},
}, nil
}
func (s *Service) GetSubTree(_ context.Context, req *GetSubTreeRequest) (*GetSubTreeResponse, error) {
return nil, errors.New("GetSubTree is unimplemented")
}
// Apply locally applies operation from the remote node to the tree.
func (s *Service) Apply(_ context.Context, req *ApplyRequest) (*ApplyResponse, error) {
err := signature.VerifyServiceMessage(req)
if err != nil {
return nil, err
}
var cid cidSDK.ID
if err := cid.Decode(req.GetBody().GetContainerId()); err != nil {
return nil, err
}
found := false
key := req.GetSignature().GetKey()
nodes, _ := s.getContainerNodes(cid)
loop:
for _, n := range nodes {
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")
}
op := req.GetBody().GetOperation()
var meta pilorama.Meta
if err := meta.FromBytes(op.GetMeta()); err != nil {
return nil, fmt.Errorf("can't parse meta-information: %w", err)
}
return nil, s.forest.TreeApply(cid, req.GetBody().GetTreeId(), &pilorama.Move{
Parent: op.GetParentId(),
Child: op.GetChildId(),
Meta: meta,
})
}
func constructMeta(arr []*KeyValue) []pilorama.KeyValue {
meta := make([]pilorama.KeyValue, len(arr))
for i, kv := range arr {
meta[i].Key = kv.Key
meta[i].Value = kv.Value
}
return meta
}

2453
pkg/services/tree/service.pb.go generated 100644

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,194 @@
syntax = "proto3";
package tree;
import "pkg/services/tree/types.proto";
option go_package = "github.com/nspcc-dev/neofs-node/pkg/services/tree";
// `TreeService` provides an interface for working with distributed tree.
service TreeService {
/* Client API */
// Add adds new node to the tree. Invoked by a client.
rpc Add (AddRequest) returns (AddResponse);
// AddByPath adds new node to the tree by path. Invoked by a client.
rpc AddByPath (AddByPathRequest) returns (AddByPathResponse);
// Remove removes node from the tree. Invoked by a client.
rpc Remove (RemoveRequest) returns (RemoveResponse);
// Move moves node from one parent to another. Invoked by a client.
rpc Move (MoveRequest) returns (MoveResponse);
// GetNodeByPath returns list of IDs corresponding to a specific filepath.
rpc GetNodeByPath (GetNodeByPathRequest) returns (GetNodeByPathResponse);
// GetSubTree returns tree corresponding to a specific node.
rpc GetSubTree (GetSubTreeRequest) returns (GetSubTreeResponse);
/* Synchronization API */
// Apply pushes log operation from another node to the current.
// The request must be signed by a container node.
rpc Apply (ApplyRequest) returns (ApplyResponse);
}
message AddRequest {
message Body {
bytes container_id = 1;
string tree_id = 2;
uint64 parent_id = 3;
repeated KeyValue meta = 4;
}
Body body = 1;
Signature signature = 2;
}
message AddResponse {
message Body {
uint64 node_id = 1;
}
Body body = 1;
Signature signature = 2;
};
message AddByPathRequest {
message Body {
bytes container_id = 1;
string tree_id = 2;
string path_attribute = 3;
repeated string path = 4;
repeated KeyValue meta = 5;
}
Body body = 1;
Signature signature = 2;
}
message AddByPathResponse {
message Body {
repeated uint64 nodes = 1;
uint64 parent_id = 2;
}
Body body = 1;
Signature signature = 2;
};
message RemoveRequest {
message Body {
bytes container_id = 1;
string tree_id = 2;
uint64 node_id = 3;
}
Body body = 1;
Signature signature = 2;
}
message RemoveResponse {
message Body {
}
Body body = 1;
Signature signature = 2;
};
message MoveRequest {
message Body {
// TODO import neo.fs.v2.refs.ContainerID directly.
bytes container_id = 1;
string tree_id = 2;
uint64 parent_id = 3;
uint64 node_id = 4;
repeated KeyValue meta = 5;
}
Body body = 1;
Signature signature = 2;
}
message MoveResponse {
message Body {
}
Body body = 1;
Signature signature = 2;
};
message GetNodeByPathRequest {
message Body {
bytes container_id = 1;
string tree_id = 2;
string path_attribute = 3;
repeated string path = 4;
repeated string attributes = 5;
bool latest_only = 6;
bool all_attributes = 7;
}
Body body = 1;
Signature signature = 2;
}
message GetNodeByPathResponse {
message Info {
uint64 node_id = 1;
uint64 timestamp = 2;
repeated KeyValue meta = 3;
}
message Body {
repeated Info nodes = 1;
}
Body body = 1;
Signature signature = 2;
};
message GetSubTreeRequest {
message Body {
bytes container_id = 1;
string tree_id = 2;
repeated uint64 nodes = 3;
}
Body body = 1;
Signature signature = 2;
}
message GetSubTreeResponse {
message Info {
uint64 node_id = 1;
repeated KeyValue meta = 2;
}
message Body {
repeated Info info = 1;
}
Body body = 1;
Signature signature = 2;
};
message ApplyRequest {
message Body {
bytes container_id = 1;
string tree_id = 2;
LogMove operation = 3;
}
Body body = 1;
Signature signature = 2;
}
message ApplyResponse {
message Body {
}
Body body = 1;
Signature signature = 2;
};

View File

@ -0,0 +1,335 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.19.4
// source: pkg/services/tree/service.proto
package tree
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// TreeServiceClient is the client API for TreeService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type TreeServiceClient interface {
// Add adds new node to the tree. Invoked by a client.
Add(ctx context.Context, in *AddRequest, opts ...grpc.CallOption) (*AddResponse, error)
// AddByPath adds new node to the tree by path. Invoked by a client.
AddByPath(ctx context.Context, in *AddByPathRequest, opts ...grpc.CallOption) (*AddByPathResponse, error)
// Remove removes node from the tree. Invoked by a client.
Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*RemoveResponse, error)
// Move moves node from one parent to another. Invoked by a client.
Move(ctx context.Context, in *MoveRequest, opts ...grpc.CallOption) (*MoveResponse, error)
// GetNodeByPath returns list of IDs corresponding to a specific filepath.
GetNodeByPath(ctx context.Context, in *GetNodeByPathRequest, opts ...grpc.CallOption) (*GetNodeByPathResponse, error)
// GetSubTree returns tree corresponding to a specific node.
GetSubTree(ctx context.Context, in *GetSubTreeRequest, opts ...grpc.CallOption) (*GetSubTreeResponse, error)
// Apply pushes log operation from another node to the current.
// The request must be signed by a container node.
Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResponse, error)
}
type treeServiceClient struct {
cc grpc.ClientConnInterface
}
func NewTreeServiceClient(cc grpc.ClientConnInterface) TreeServiceClient {
return &treeServiceClient{cc}
}
func (c *treeServiceClient) Add(ctx context.Context, in *AddRequest, opts ...grpc.CallOption) (*AddResponse, error) {
out := new(AddResponse)
err := c.cc.Invoke(ctx, "/tree.TreeService/Add", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) AddByPath(ctx context.Context, in *AddByPathRequest, opts ...grpc.CallOption) (*AddByPathResponse, error) {
out := new(AddByPathResponse)
err := c.cc.Invoke(ctx, "/tree.TreeService/AddByPath", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*RemoveResponse, error) {
out := new(RemoveResponse)
err := c.cc.Invoke(ctx, "/tree.TreeService/Remove", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) Move(ctx context.Context, in *MoveRequest, opts ...grpc.CallOption) (*MoveResponse, error) {
out := new(MoveResponse)
err := c.cc.Invoke(ctx, "/tree.TreeService/Move", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) GetNodeByPath(ctx context.Context, in *GetNodeByPathRequest, opts ...grpc.CallOption) (*GetNodeByPathResponse, error) {
out := new(GetNodeByPathResponse)
err := c.cc.Invoke(ctx, "/tree.TreeService/GetNodeByPath", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) GetSubTree(ctx context.Context, in *GetSubTreeRequest, opts ...grpc.CallOption) (*GetSubTreeResponse, error) {
out := new(GetSubTreeResponse)
err := c.cc.Invoke(ctx, "/tree.TreeService/GetSubTree", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResponse, error) {
out := new(ApplyResponse)
err := c.cc.Invoke(ctx, "/tree.TreeService/Apply", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// TreeServiceServer is the server API for TreeService service.
// All implementations should embed UnimplementedTreeServiceServer
// for forward compatibility
type TreeServiceServer interface {
// Add adds new node to the tree. Invoked by a client.
Add(context.Context, *AddRequest) (*AddResponse, error)
// AddByPath adds new node to the tree by path. Invoked by a client.
AddByPath(context.Context, *AddByPathRequest) (*AddByPathResponse, error)
// Remove removes node from the tree. Invoked by a client.
Remove(context.Context, *RemoveRequest) (*RemoveResponse, error)
// Move moves node from one parent to another. Invoked by a client.
Move(context.Context, *MoveRequest) (*MoveResponse, error)
// GetNodeByPath returns list of IDs corresponding to a specific filepath.
GetNodeByPath(context.Context, *GetNodeByPathRequest) (*GetNodeByPathResponse, error)
// GetSubTree returns tree corresponding to a specific node.
GetSubTree(context.Context, *GetSubTreeRequest) (*GetSubTreeResponse, error)
// Apply pushes log operation from another node to the current.
// The request must be signed by a container node.
Apply(context.Context, *ApplyRequest) (*ApplyResponse, error)
}
// UnimplementedTreeServiceServer should be embedded to have forward compatible implementations.
type UnimplementedTreeServiceServer struct {
}
func (UnimplementedTreeServiceServer) Add(context.Context, *AddRequest) (*AddResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Add not implemented")
}
func (UnimplementedTreeServiceServer) AddByPath(context.Context, *AddByPathRequest) (*AddByPathResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddByPath not implemented")
}
func (UnimplementedTreeServiceServer) Remove(context.Context, *RemoveRequest) (*RemoveResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Remove not implemented")
}
func (UnimplementedTreeServiceServer) Move(context.Context, *MoveRequest) (*MoveResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Move not implemented")
}
func (UnimplementedTreeServiceServer) GetNodeByPath(context.Context, *GetNodeByPathRequest) (*GetNodeByPathResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetNodeByPath not implemented")
}
func (UnimplementedTreeServiceServer) GetSubTree(context.Context, *GetSubTreeRequest) (*GetSubTreeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSubTree not implemented")
}
func (UnimplementedTreeServiceServer) Apply(context.Context, *ApplyRequest) (*ApplyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Apply not implemented")
}
// UnsafeTreeServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to TreeServiceServer will
// result in compilation errors.
type UnsafeTreeServiceServer interface {
mustEmbedUnimplementedTreeServiceServer()
}
func RegisterTreeServiceServer(s grpc.ServiceRegistrar, srv TreeServiceServer) {
s.RegisterService(&TreeService_ServiceDesc, srv)
}
func _TreeService_Add_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Add(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/tree.TreeService/Add",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Add(ctx, req.(*AddRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_AddByPath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddByPathRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).AddByPath(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/tree.TreeService/AddByPath",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).AddByPath(ctx, req.(*AddByPathRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_Remove_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemoveRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Remove(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/tree.TreeService/Remove",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Remove(ctx, req.(*RemoveRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_Move_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MoveRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Move(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/tree.TreeService/Move",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Move(ctx, req.(*MoveRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_GetNodeByPath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetNodeByPathRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).GetNodeByPath(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/tree.TreeService/GetNodeByPath",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).GetNodeByPath(ctx, req.(*GetNodeByPathRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_GetSubTree_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetSubTreeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).GetSubTree(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/tree.TreeService/GetSubTree",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).GetSubTree(ctx, req.(*GetSubTreeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_Apply_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ApplyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Apply(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/tree.TreeService/Apply",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Apply(ctx, req.(*ApplyRequest))
}
return interceptor(ctx, in, info, handler)
}
// TreeService_ServiceDesc is the grpc.ServiceDesc for TreeService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var TreeService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "tree.TreeService",
HandlerType: (*TreeServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Add",
Handler: _TreeService_Add_Handler,
},
{
MethodName: "AddByPath",
Handler: _TreeService_AddByPath_Handler,
},
{
MethodName: "Remove",
Handler: _TreeService_Remove_Handler,
},
{
MethodName: "Move",
Handler: _TreeService_Move_Handler,
},
{
MethodName: "GetNodeByPath",
Handler: _TreeService_GetNodeByPath_Handler,
},
{
MethodName: "GetSubTree",
Handler: _TreeService_GetSubTree_Handler,
},
{
MethodName: "Apply",
Handler: _TreeService_Apply_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "pkg/services/tree/service.proto",
}

View File

@ -0,0 +1,44 @@
package tree
import (
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-api-go/v2/signature"
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/user"
)
func (s *Service) verifyClient(req interface{}, cid cidSDK.ID, rawKey []byte) error {
// TODO(@fyrchik): #1328 access control
return nil
//nolint:govet
err := signature.VerifyServiceMessage(req)
if err != nil {
return err
}
cnr, err := s.cnrSource.Get(cid)
if err != nil {
return fmt.Errorf("can't get container %s: %w", cid, err)
}
ownerID := cnr.Value.Owner()
pub, err := keys.NewPublicKeyFromBytes(rawKey, elliptic.P256())
if err != nil {
return fmt.Errorf("invalid public key: %w", err)
}
var actualID user.ID
user.IDFromKey(&actualID, (ecdsa.PublicKey)(*pub))
if !actualID.Equals(ownerID) {
return errors.New("`Move` request must be signed by a container owner")
}
return nil
}

313
pkg/services/tree/types.pb.go generated 100644
View File

@ -0,0 +1,313 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.19.4
// source: pkg/services/tree/types.proto
package tree
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// KeyValue represents key-value pair attached to an object.
type KeyValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
}
func (x *KeyValue) Reset() {
*x = KeyValue{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_services_tree_types_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *KeyValue) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KeyValue) ProtoMessage() {}
func (x *KeyValue) ProtoReflect() protoreflect.Message {
mi := &file_pkg_services_tree_types_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KeyValue.ProtoReflect.Descriptor instead.
func (*KeyValue) Descriptor() ([]byte, []int) {
return file_pkg_services_tree_types_proto_rawDescGZIP(), []int{0}
}
func (x *KeyValue) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *KeyValue) GetValue() []byte {
if x != nil {
return x.Value
}
return nil
}
// LogMove represents log-entry for a single move operation.
type LogMove struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// ID of the parent node.
ParentId uint64 `protobuf:"varint,2,opt,name=parent_id,json=parentID,proto3" json:"parent_id,omitempty"`
// Node meta information, including operation timestamp.
Meta []byte `protobuf:"bytes,3,opt,name=meta,proto3" json:"meta,omitempty"`
// ID of the node to move.
ChildId uint64 `protobuf:"varint,4,opt,name=child_id,json=childID,proto3" json:"child_id,omitempty"`
}
func (x *LogMove) Reset() {
*x = LogMove{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_services_tree_types_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LogMove) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LogMove) ProtoMessage() {}
func (x *LogMove) ProtoReflect() protoreflect.Message {
mi := &file_pkg_services_tree_types_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LogMove.ProtoReflect.Descriptor instead.
func (*LogMove) Descriptor() ([]byte, []int) {
return file_pkg_services_tree_types_proto_rawDescGZIP(), []int{1}
}
func (x *LogMove) GetParentId() uint64 {
if x != nil {
return x.ParentId
}
return 0
}
func (x *LogMove) GetMeta() []byte {
if x != nil {
return x.Meta
}
return nil
}
func (x *LogMove) GetChildId() uint64 {
if x != nil {
return x.ChildId
}
return 0
}
// Signature of a message.
type Signature struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Sign []byte `protobuf:"bytes,2,opt,name=sign,json=signature,proto3" json:"sign,omitempty"`
}
func (x *Signature) Reset() {
*x = Signature{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_services_tree_types_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Signature) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Signature) ProtoMessage() {}
func (x *Signature) ProtoReflect() protoreflect.Message {
mi := &file_pkg_services_tree_types_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Signature.ProtoReflect.Descriptor instead.
func (*Signature) Descriptor() ([]byte, []int) {
return file_pkg_services_tree_types_proto_rawDescGZIP(), []int{2}
}
func (x *Signature) GetKey() []byte {
if x != nil {
return x.Key
}
return nil
}
func (x *Signature) GetSign() []byte {
if x != nil {
return x.Sign
}
return nil
}
var File_pkg_services_tree_types_proto protoreflect.FileDescriptor
var file_pkg_services_tree_types_proto_rawDesc = []byte{
0x0a, 0x1d, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74,
0x72, 0x65, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x04, 0x74, 0x72, 0x65, 0x65, 0x22, 0x32, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x55, 0x0a, 0x07, 0x4c, 0x6f, 0x67,
0x4d, 0x6f, 0x76, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49,
0x44, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69,
0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x44,
0x22, 0x36, 0x0a, 0x09, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x17, 0x0a, 0x04, 0x73, 0x69, 0x67, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73,
0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x73, 0x70, 0x63, 0x63, 0x2d, 0x64, 0x65, 0x76,
0x2f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_pkg_services_tree_types_proto_rawDescOnce sync.Once
file_pkg_services_tree_types_proto_rawDescData = file_pkg_services_tree_types_proto_rawDesc
)
func file_pkg_services_tree_types_proto_rawDescGZIP() []byte {
file_pkg_services_tree_types_proto_rawDescOnce.Do(func() {
file_pkg_services_tree_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_services_tree_types_proto_rawDescData)
})
return file_pkg_services_tree_types_proto_rawDescData
}
var file_pkg_services_tree_types_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_pkg_services_tree_types_proto_goTypes = []interface{}{
(*KeyValue)(nil), // 0: tree.KeyValue
(*LogMove)(nil), // 1: tree.LogMove
(*Signature)(nil), // 2: tree.Signature
}
var file_pkg_services_tree_types_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_pkg_services_tree_types_proto_init() }
func file_pkg_services_tree_types_proto_init() {
if File_pkg_services_tree_types_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_pkg_services_tree_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*KeyValue); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pkg_services_tree_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LogMove); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pkg_services_tree_types_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Signature); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pkg_services_tree_types_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_pkg_services_tree_types_proto_goTypes,
DependencyIndexes: file_pkg_services_tree_types_proto_depIdxs,
MessageInfos: file_pkg_services_tree_types_proto_msgTypes,
}.Build()
File_pkg_services_tree_types_proto = out.File
file_pkg_services_tree_types_proto_rawDesc = nil
file_pkg_services_tree_types_proto_goTypes = nil
file_pkg_services_tree_types_proto_depIdxs = nil
}

View File

@ -0,0 +1,27 @@
syntax = "proto3";
package tree;
option go_package = "github.com/nspcc-dev/neofs-node/pkg/services/tree";
// KeyValue represents key-value pair attached to an object.
message KeyValue {
string key = 1 [json_name = "key"];
bytes value = 2 [json_name = "value"];
}
// LogMove represents log-entry for a single move operation.
message LogMove {
// ID of the parent node.
uint64 parent_id = 2 [json_name = "parentID"];
// Node meta information, including operation timestamp.
bytes meta = 3 [json_name = "meta"];
// ID of the node to move.
uint64 child_id = 4 [json_name = "childID"];
}
// Signature of a message.
message Signature {
bytes key = 1 [json_name = "key"];
bytes sign = 2 [json_name = "signature"];
}